单向链表的实现(不设立虚拟头节点)

(希望我所描述的,给你带来收获!)——关于阅读笔者数据结构系列,建议先将代码粘至IDE,然后对照文字解释进行理解

开始抛出——什么是链表?

答:链表Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)——————(摘自维基百科)

我的补充:链表具有真正意义上的动态!而动态数组是通过扩容缩容的手段达到一定程度的动态!链表使用“节点”去存储数据,插入数据时就动态的申请节点!

我们首要思考的是,我们应该用什么样的“节点”去存储数据,在这里我采用的是私有内部类的设计——内部类相关知识传送门(待补充!):

节点代码如下:

 1 public class LinkedList<E> {
 2     private class Node {
 3         public E e;  //数据域
 4         public Node next;  //引用域(java中不存在指针说法,但也可以理解成指针)
 5 
 6         public Node() {
 7             this(null,null);
 8         }
 9 
10         public Node(E e) {
11             this.e = e;
12             this.next = null;
13         }
14 
15         public Node(E e,Node next) {
16             this.e = e;
17             this.next = next;
18         }
19 
20         @Override
21         public String toString() {
22             return e.toString();
23         }
24     }
  }

 整体代码的补充:

 1 public class LinkedList<E> {
 2     private class Node {
 3         public E e;
 4         public Node next;
 5 
 6         public Node() {
 7             this(null,null);
 8         }
 9 
10         public Node(E e) {
11             this.e = e;
12             this.next = null;
13         }
14 
15         public Node(E e,Node next) {
16             this.e = e;
17             this.next = next;
18         }
19 
20         @Override
21         public String toString() {
22             return e.toString();
23         }
24     }
25     private Node head;
26     private int size;
27 
28     public int getSize() {
29         return size;
30     }
31 
32     public boolean isEmpty() {
33         return size == 0;
34     }
35 
36     public void addFirst(E e) {
37         head = new Node(e,head);
38         size ++;
39     }
40 
41     public void add(int index, E e) {
42         if (index < 0 || index > size)
43             throw new IllegalArgumentException("index is illegal");
44         if (index == 0)
45             addFirst(e);
46         else {
47             Node prev = head;
48             for (int i = 0; i < index - 1; i++) {
49                 prev = prev.next;
50             }
51             prev.next = new Node(e,prev.next);
52             size ++;
53         }
54     }
55 
56     public void addLast(E e) {
57         add(size,e);
58     }
59 
60     //测试打印输出
61     @Override
62     public String toString() {
63         StringBuilder stringBuilder = new StringBuilder();
64         stringBuilder.append(String.format("LinkedList size = %d\n",size));
65         stringBuilder.append("head [");
66         Node cur = head;
67         for (int i = 0; cur != null; cur = cur.next) {
68             stringBuilder.append(cur);
69             if (cur.next != null)
70                 stringBuilder.append("->");
71         }
72         stringBuilder.append("] tail");
73         return String.valueOf(stringBuilder);
74     }
75 
76     public static void main(String[] args) {
77         LinkedList<Integer> linkedList = new LinkedList<>();
78         for (int i = 0; i < 10; i++) {
79             linkedList.addLast(i);
80         }
81         System.out.println(linkedList.toString());
82     }
83 }

  关于addFirst方法的阐述:

这里我们首先设立一个头节点(head)的概念,在这里!如果链表有数据,头节点其实就是链表头的那个节点(如果链表没有数据呢?那么null就是该链表的“表头”)

1、addFirst也就是说,在链表的第一个位置插入一个数据元素!

2、想要在表头插入一个元素e,必须先创建一个节点去承载   ---->>>Node  node = new Node(e);

3、因为这个节点要当表头了,所以它要放在head的前面  ---->>> node.next = head;

4、这个节点成为新的表头了,所以head这个名字要交给这个新的节点 ---->>> head = node;

5、size是链表长度的变量,添加节点成功,则size ++

总结以上:三段代码可以直接浓缩为  head = new Node(e,head); ------->这和Node类的双参构造函数有关

  关于add(int index, E e)方法的阐述:

要强调的是,这里的index是我们人为意识去强加给链表的(目的是我们在底层操作的时候,更便于存取数据)——数组具有索引(index),可以通过索引值直接取数据,但链表不可以!

很自然的,add方法是说:我们要在index位置上存储元素e

第一步:我们需要找到 index - 1的那个节点,因为每一个节点只有指向下一个节点的“引用域”(这是单向链表)

第二步:index - 1位置的节点我们称为 prev,待插入的节点称为 cur,我们需要操作的是----->>>cur.next = prev.next;

(prev是(index - 1)位置的节点,那 prev.next 就是index位置的节点了,cur 需要替换 prev.next 的位置得先像 prev 学习,cur.next 要成为index位置上的节点)

第三步:当cur.next在index位置上,要想明白的是prev.next也在index位置上 执行----->>> prev.next = cur;(prev.next 始终是index位置节点,我把cur放在index位置上,那自然的cur.next 就是index + 1上的位置了——插入了新节点cur!)

第四步:新增节点  我们要维护我们的size变量------->>>size ++

总结来说:三个步骤依然可以用-------->prev.next = new Node(e,prev.next);这样的形式来表达!

在阐述add方法的时候,我用了index - 1位置这样的描述,认真阅读了代码的朋友会发现add方法中for循环的条件语句是这样的:

for (int i = 0; i < index - 1; i++) {
                prev = prev.next;
            }

 该循环表明的是,当 i == index - 2是for的最后一次循环;那index - 2才是待插入位置的前一个位置(prve)?

答:前面阐述的add方法中,笔者只是借用index、index - 1  来表明逻辑上的前后关系、因为实际上在不设立虚拟节点的前提下,我们默认表头的第一个节点就是head节点!  循环体中 prev = prev.next; 语句, 假使 i == 0,第一次循环就已经使得prev在第二个位置上了;假设index可以取-1(-1是0的前一个节点,要在-1 和 0之间插入数据),如果i == 0,prev实则在index为1的位置,要使得插入成功,prev必须往回走两步才能走到-1得位置,因此就是相差2,index - 2的逻辑来得自然(-1这种思考方式个人认为很奇特,读者可以换种思路——比如在index == 3位置上插入节点;可以画图理解)

猜你喜欢

转载自www.cnblogs.com/zhixiangshu/p/10174684.html