数据结构-链表的那些事(上)(一)
一、先来唠一唠?
上一篇泛型适宜本意还想继续往下写写,来一下协变与逆变,奈何不知道怎么该往下写,等等我思考一下,在继续写~接下来聊聊数据结构这一话题,想到数据结构就想起了那一年我在工院上课就睡觉的时光,真实暴遣天物呀,不扯开始话题,数据结构可能在我们工作的时候用的不算太多,但是实际上不管java或者C#都将这些封装到我们常用的类里面,就比如说集合就是数据结构的真实写照~
二、数组先来扯一扯?
数组不算是集合,但是还是想用来他的东西来说明数据结构的一些东西,不算是偏题哈~我们都知道数组的所有元素都是存储在操作系统分配的内存块中,是一块连续的存储空间,可以通过特定的元素的索引作为数组的下标,我使用一个图来表示一下数组中在内存中的存储方式,大家看了下图基本就明白了,这里要引入一个概念线性表,也是数据结构里面最简单的关系,一对一,零个或多个数据元素的有限序列,线性表分成2种存储结构一种是顺序表另外一种是链表,这里我们先说顺序表,数组也是我们顺序表的具体体现。前面我们已经提到他的概念,重点一块连续的空间排列,元素与其相邻的元素是在物理上相邻,这里我们看出当我们访问一个数组元素的话的只要知道下标那么他的访问速度是最快的,但是这里也是有缺点的,数组大小固定,另外如果做插入和删除的操作的时候每个位置还需要偏移,这样在时间开销上很大,这也是我们在使用数组的时候需要注意的问题,另外就是如果数组刚开始声明的值比较大的时候会比较占用内存空间,造成空间浪费,既然出现了这个问题java是如何帮我们解决的?
在java里面有专门的API支持扩容,如下所示,这样我们只要指定合理的扩容长度那么就不会在空间上照成浪费喽,当然还有更好的,那就是集合,既然引进了动态数组的概念我们不得不谈下ArryList,俺不才某天看了遍文章,有了自己看看源码的冲动,最近也在看数据结构因此有了这篇和接下来的文章,集合也是程序中很重要的一部分,那就我们一起携手探秘集合。
int[] arry={1,3,4};
int[] arry2=Arrays.copyOf(arry, arry.length+5);
看源码第一步看继承结构
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
AbstractList<E>是一个抽象的泛型类,是对List<E>接口的实现,RandomAccess这个是一个空接口,实现这个接口可以支持快速访问;具体的定义可以跳进去看下Java的解释,其实
我感觉就是单链表和顺序表之间访问元素的差距,不知道我理解的对不对,有问题希望大神给我指出来,小树的成长还需要你们,我写了一个demo比较二者的差距大家可以试着运行下,差距
还是很大,下面是我实现了大致上的对比,剩下的2个接口一个是实现浅拷贝,另外一个就是序列化;
实现了RandomAccess接口
实现了RandomAccess接口需要5秒
未实现了RandomAccess接口
未实现了RandomAccess接口需要100秒
package com.wtz.demo; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.RandomAccess; public class DemotTest{ public static void main(String[] args) { // TODO Auto-generated method stub List arrayList=new ArrayList(); List linkeList=new LinkedList<>(); for (int i = 0; i < 10000000; i++) { arrayList.add(i); linkeList.add(i); } getAllTime(arrayList); getAllTime(linkeList); } private static void getAllTime(List list) { long startTime; long endTime; if(list instanceof RandomAccess){ System.out.println("实现了RandomAccess接口"); startTime=System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { list.get(i); } endTime=System.currentTimeMillis(); System.out.println("实现了RandomAccess接口需要"+(endTime-startTime)+"秒"); }else { System.out.println("未实现了RandomAccess接口"); startTime=System.currentTimeMillis(); for (Iterator iterator=list.iterator();iterator.hasNext();) { iterator.next(); } endTime=System.currentTimeMillis(); System.out.println("未实现了RandomAccess接口需要"+(endTime-startTime)+"秒"); } } }
接下来看看构造函数,常量那些应该结合方法看没必要先就看,但是构造函数相对比较重要的一点,这是我们调用方法的第一步所以必须认识清楚;下面是我自己的一些理解,
public ArrayList(int initialCapacity) {
//初始化你提供的值大小的数组 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {
//为0就为空数组 this.elementData = EMPTY_ELEMENTDATA; } else {
//小于0就抛出异常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList() {
//初始化为一个为10数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
//集合转化为Object[] public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { //如果返回的不是object[]数组转化为object数组 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
接下来就是方法了主要是List接口的实现,一些什么add,size这些方法我下面的单链表就有实现,在这里我不做过多介绍,这里我就说下一些比较有意思的方法
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0)
//扩容 grow(minCapacity); }
//这个地方就比较有意思这里涉及到JVM内存方面的东西了下面这个连接搞定我们的问题
//https://www.ibm.com/developerworks/java/library/j-codetoheap/index.html private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { int oldCapacity = elementData.length;
//这个也比较有意思这个地方会将老数组的位置减半,就是为了内存不浪费 int newCapacity = oldCapacity + (oldCapacity >> 1);
//扩容一半也不够就直接最大 if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
//如果最大超过最大值那么就溢出 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
ArrayList基本就说到这里,相信大家剩下基本都能读懂,我这个我也参考了好多才有点明白,大家努力起来哈哈~~转行还是有点小不容易,,等等基础差不都以后还是要拜读下JVM~~
三、链表来扯一扯?
什么链表?初始化时分配一个元素的存储空间,另外就是需要有指针指向另外的元素,这里谈一下链表的好处,这里无需直接分配内存,克服了数组缺点,但是失去数组读取快的优点,另外因为有指针的存在,空间开销比较大。根据指针的类型可分为:单向链表,双向链表以及循环链表。这里是我自己的理解哈,没有人这么说,这里说一下单链表的特性,还是先用图表示什么是单链表?这下面就是一个单链表,单链表的特点就是可以包含多个节点,每个节点指向后续的元素,最后一个节点指向为NULL,接下来我们用代码表示一下
1.上面我们已经对单链表的一个节点进行声明接下来的我们说一说链表的操作
1、遍历链表
2、链表中添加一个元素
3、链表中删除一个元素
4、查询链表中的一个元素
2.单链表的遍历
1.沿着指针遍历
2.遍历的时候显示该节点的内容
3.直到指针为NULL的时候结束
3.单链表的插入
单链表的插入的情况可分为3种:
1.在元素的开始插入一个新的节点,新节点指向原来的第一个节点
2.在元素的中间随机插入一个节点,上一个节点指向插入节点,本节点指向插入的下一个个节点
3.在元素的结尾插入一个节点,上一个节点的Next指向插入的这个节点,本节点指向Null
4.单链表中删除一个元素
单链表的删除的情况可分为3种:
1.在元素的开始删除
2.在元素的中间删除,next指向下一个节点
3.在元素的结尾删除,next指向null
下面是主要用Java实现的主要代码
1 package com.wtz.singleLinkedList; 2 3 /** 4 * Created by wangt on 2017/6/22. 5 */ 6 public class MySingleLinkedList<T> { 7 private Node<T> first;//第一节点 8 9 public MySingleLinkedList(){ 10 this.first=null; 11 } 12 13 /** 14 * 添加第一个子节点 15 * @param data 16 */ 17 public void addFirstNode(T data){ 18 Node<T> node=new Node<T>(data); 19 node.setNext(first); 20 first=node; 21 } 22 23 /** 24 * 移除第一个节点 25 * @return 26 */ 27 public Node removeFirstNode(){ 28 Node<T> tempNode=first; 29 first=tempNode.getNext(); 30 return tempNode; 31 } 32 33 /** 34 * 增加中间的节点 35 * @param index 36 * @param data 37 */ 38 public void add(int index,T data) 39 { 40 Node<T> node=new Node<T>(data); 41 Node<T> current=first.getNext(); 42 Node<T> previous=first.getNext(); 43 int count=1; 44 while (count<index){ 45 previous=current; 46 current=current.getNext(); 47 count++; 48 } 49 node.setNext(current); 50 previous.setNext(node); 51 } 52 53 /** 54 * 删除中间的节点 55 * @param index 56 * @return 57 */ 58 public Node<T> remove(int index){ 59 Node<T> current=first.getNext(); 60 Node<T> previous=first.getNext(); 61 int count=1; 62 while (count<index){ 63 count++; 64 previous=current; 65 current=current.getNext(); 66 } 67 if(current==first){ 68 first=first.getNext(); 69 }else { 70 previous.setNext(current.getNext()); 71 } 72 return current; 73 } 74 75 /** 76 * 展示所有的节点 77 */ 78 public void showAllNode() 79 { 80 Node<T> current=first; 81 while (current!=null){ 82 System.out.print(current.getItem()); 83 current=current.getNext(); 84 } 85 } 86 87 /** 88 * 根据索引查询节点的信息 89 * @param index 90 * @return 91 */ 92 public Node<T> findNode(int index){ 93 Node<T> current=first; 94 int count=0; 95 while (count<index){ 96 count++; 97 current=current.getNext(); 98 } 99 System.out.print(current.getItem()); 100 return current; 101 } 102 }
下面是子节点的定义
package com.wtz.singleLinkedList; /** * Created by wangt on 2017/6/22. */ public class Node<T> {//定义一个节点 public Node<T> getNext() { return next; } public void setNext(Node<T> next) { this.next = next; } private Node<T> next; public T getItem() { return item; } public void setItem(T item) { this.item = item; } private T item; public Node(T item) { this.item=item; } }
下面是测试代码
package com.wtz.singleLinkedList; import org.junit.Test; /** * Created by wangt on 2017/6/22. */ public class TestNode { public static void main(String[] args){ MySingleLinkedList<Integer> mySingleLinkedList=new MySingleLinkedList<Integer>(); mySingleLinkedList.addFirstNode(1); mySingleLinkedList.addFirstNode(6); mySingleLinkedList.addFirstNode(5); // mySingleLinkedList.showAllNode(); // mySingleLinkedList.removeFirstNode(); // mySingleLinkedList.showAllNode(); // mySingleLinkedList.add(2,10); // mySingleLinkedList.showAllNode(); // mySingleLinkedList.remove(2); // mySingleLinkedList.showAllNode(); mySingleLinkedList.findNode(2); } }
四、说下心得
上面说的一些东西侧面还是发现自己基础还是不太好,还要努力,另外还是再推下我的java群,438836709欢迎大家,,,看着反应写下一篇,我感觉写一篇还是挺累的