链表是一种数据结构,类似于长长的锁链串联起来一组对象。链表分为单链表和双链表,对于单链表来说,数据的查找只能从第一个数据开始,每一个数据只能顺着找到下一个数据,无法返回,对于双向链表,每个数据可以同时找到它的上一个节点和下一个节点。
单向链表如下所示
可以看出,链表包括两部分信息,一个是多个的节点,另一个是头节点和各个节点之间的连接关系。
对于节点,我们可以定义一个节点类来描述,这个节点类药包括链表指针和值,对于第二部分信息,首先记录头节点,然后将每一个链表指针指向下一个即可。
此外,为了方便使用,链表类中我们还需要提供链表长度、节点位置、链表的增删改查等功能。
链表的基本结构和需要实现的函数功能如下:
package dataStructure; public class CopyOfListNode { class Node { public Node next = null; public int val; public Node(int val){ this.val = val; } } private int listSize; private Node head = null; /** * 判定节点是否为空 * @return */ public boolean isEmpty(){ return listSize == 0;} /** * 返回链表大小 * @return */ public int size(){ return listSize; } /** * 获取指定位置的节点 * @param index * @return */ public int get(int index){ return -1; } /** * 获取节点在链表中的位置 * @param node * @return */ public int indexOf(int value){ return -1; } /** * 添加节点 * @param index * @param node */ public void add(int value){ } /** * 删除节点 * @param index */ public void delete(int index){ } /** * 插入节点 * @param index * @param node */ public void insert(int index, int value){ } /** * 打印链表所有节点 */ public void printList(){ } /** * 构造测试用例 * @param args */ public static void main(String[] args){ } }
需要实现的功能有:判定链表是否为空、链表的大小、获取指定位置的节点信息、获取节点的位置、添加节点、删除节点、插入节点、打印链表几个功能
需要维持的成员变量有链表大小listSize,和头节点head,功能的实现如下:
1.判断链表是否为空和链表大小的功能,我们可以直接根据链表大小返回。
代码如下:
/** * 判定节点是否为空 * @return */ public boolean isEmpty(){ return listSize == 0;} /** * 返回链表大小 * @return */ public int size(){ return listSize; }
2.获取指定位置的节点
根据链表的性质,从头节点开始依次移动直到到达指定位置,需要注意的是需要先判定索引是否有效,防止出现空指针异常
/** * 获取指定位置的节点 * @param index * @return */ public int get(int index){ Node node = head; if(index < 0 || index >= listSize) return -1; for(int i=0; i<index; i++){ //移到下一节点 node = node.next; } return node.val; }
3.获取节点在链表中的位置
遍历链表,知道找到所需节点,或者遍历完成推出节点返回未找到信息
/** * 获取节点在链表中的位置 * @param node * @return */ public int indexOf(int value){ int index = 0; Node currentNode = head; while(currentNode!=null && currentNode.val!=value){ //转移到下一个节点 currentNode = currentNode.next; index++; } //确认是否找到所需元素 if(currentNode == null) return -1; else return index; }
4.添加节点
添加节点需要分两种情况考虑,头节点和其他节点,因为头节点没有前节点,只需要直接将head赋值即可,对于后续节点,需要将链表内最后一个节点的指针指向所要添加的节点,另外,需要注意将链表大小变量listSize+1,方便统计大小
代码如下:
/** * 添加节点 * @param index * @param node */ public void add(int value){ if(head == null){ head = new Node(value); }else{ Node nd = head; while(nd.next != null){ nd = nd.next; } nd.next = new Node(value); } listSize++; }
5.删除节点
删除节点主要是删除节点的连接关系,分为删除头节点,删除尾节点,删除中间节点。
删除头节点,删除头节点的连接关系,将第二个作为新的头节点即可
删除尾节点,将尾节点与前一个节点的连接关系去掉即可
删除中间节点,将要删除节点前后连接关系删除,并将前一个节点直接指向后一个节点即可
需要注意的是,对于删除头节点和中间节点,需要提前将后半段链表的信息保存下来,防止丢失,删除后的节点在java中会自动被GC回收
代码如下:
1 /** 2 * 删除节点 3 * @param index 4 */ 5 public void delete(int index){ 6 //首先检查节点是否存在 7 if(index >= listSize || index < 0){ 8 System.out.println("该节点不存在"); 9 return; 10 } 11 //定位到要删除节点 12 if(index == 0){ 13 //首节点 14 head = head.next; 15 }else{ 16 //定位到前一个节点 17 Node currentNode = head; 18 for(int i=0; i<index-1; i++){ 19 currentNode = currentNode.next; 20 } 21 if(index == listSize) 22 //尾节点处理 23 currentNode.next = null; 24 else 25 //中间节点 26 currentNode.next = currentNode.next.next; 27 } 28 listSize--; 29 }
6.打印链表
根据链表信息依次打印即可
代码:
1 /** 2 * 打印链表所有节点 3 */ 4 public void printList(){ 5 Node currentNode = head; 6 while(currentNode!=null){ 7 System.out.print(currentNode.val+" "); 8 currentNode = currentNode.next; 9 } 10 }
整体的代码及测试用例如下:
1 package dataStructure; 2 3 public class ListNode { 4 class Node { 5 public Node next = null; 6 public int val; 7 public Node(int val){ 8 this.val = val; 9 } 10 } 11 12 private int listSize; 13 private Node head = null; 14 /** 15 * 判定节点是否为空 16 * @return 17 */ 18 public boolean isEmpty(){ 19 return listSize == 0;} 20 21 /** 22 * 返回链表大小 23 * @return 24 */ 25 public int size(){ 26 return listSize; 27 } 28 29 /** 30 * 获取指定位置的节点 31 * @param index 32 * @return 33 */ 34 public int get(int index){ 35 Node node = head; 36 if(index < 0 || index >= listSize) 37 return -1; 38 for(int i=0; i<index; i++){ 39 //移到下一节点 40 node = node.next; 41 } 42 return node.val; 43 } 44 45 /** 46 * 获取节点在链表中的位置 47 * @param node 48 * @return 49 */ 50 public int indexOf(int value){ 51 int index = 0; 52 Node currentNode = head; 53 while(currentNode!=null && currentNode.val!=value){ 54 //转移到下一个节点 55 currentNode = currentNode.next; 56 index++; 57 } 58 //确认是否找到所需元素 59 if(currentNode == null) 60 return -1; 61 else 62 return index; 63 } 64 65 /** 66 * 添加节点 67 * @param index 68 * @param node 69 */ 70 public void add(int value){ 71 if(head == null){ 72 head = new Node(value); 73 }else{ 74 Node nd = head; 75 while(nd.next != null){ 76 nd = nd.next; 77 } 78 nd.next = new Node(value); 79 } 80 listSize++; 81 } 82 83 /** 84 * 删除节点 85 * @param index 86 */ 87 public void delete(int index){ 88 //首先检查节点是否存在 89 if(index >= listSize || index < 0){ 90 System.out.println("该节点不存在"); 91 return; 92 } 93 //定位到要删除节点 94 if(index == 0){ 95 //首节点 96 head = head.next; 97 }else{ 98 //定位到前一个节点 99 Node currentNode = head; 100 for(int i=0; i<index-1; i++){ 101 currentNode = currentNode.next; 102 } 103 if(index == listSize) 104 //尾节点处理 105 currentNode.next = null; 106 else 107 //中间节点 108 currentNode.next = currentNode.next.next; 109 } 110 listSize--; 111 } 112 113 /** 114 * 插入节点 115 * @param index 116 * @param node 117 */ 118 public void insert(int index, int value){ 119 //首先检查插入位置是否合适 120 if(index >= listSize || index < 0){ 121 System.out.println("索引无效"); 122 return; 123 } 124 //分位置插入 125 if(index == 0){ 126 //头节点,只保存后面节点 127 Node tmpNode = head;//保存后面当前位置节点信息 128 head = new Node(value); 129 head.next = tmpNode; 130 }else{ 131 //中间节点,保存前一个节点和后节点信息 132 Node preNode = head; 133 for(int i=0; i<index-1; i++){ 134 preNode = preNode.next; 135 } 136 Node currentNode = preNode.next; 137 preNode.next = new Node(value); 138 preNode.next.next = currentNode; 139 } 140 listSize++; 141 } 142 143 /** 144 * 打印链表所有节点 145 */ 146 public void printList(){ 147 Node currentNode = head; 148 while(currentNode!=null){ 149 System.out.print(currentNode.val+" "); 150 currentNode = currentNode.next; 151 } 152 } 153 154 public static void main(String[] args){ 155 ListNode testList = new ListNode(); 156 for(int i=0; i<50; i++) 157 testList.add(i); 158 System.out.println("链表是否为空:"+testList.isEmpty()); 159 System.out.println("链表大小:"+testList.size()); 160 System.out.println("获取头节点:"+testList.get(0)); 161 System.out.println("获取尾节点:"+testList.get(49)); 162 System.out.println("获取位置为10的中间节点:"+testList.get(10)); 163 System.out.println("获取节点值10的位置:"+testList.indexOf(10)); 164 System.out.println("获取值为1000的节点位置:"+testList.indexOf(1000)); 165 testList.delete(0); 166 System.out.println("删除头节点后大小:"+testList.size()); 167 testList.delete(testList.size()-1); 168 System.out.println("删除尾节点后大学:"+testList.size()); 169 testList.delete(2); 170 System.out.println("删除中间点2后大小:"+testList.size()); 171 System.out.println("打印链表:"); 172 testList.printList(); 173 testList.insert(0, 0); 174 System.out.println("添加0到头节点后大小:"+testList.size()); 175 testList.insert(3, 3); 176 System.out.println("添加3到中间节点3处大小:"+testList.size()); 177 System.out.println("打印链表:"); 178 testList.printList(); 179 } 180 }
此外还有循环链表、双向链表,实现方式基本如此