4、链表-单链表
来源:https://www.bilibili.com/video/BV1B4411H76f?p=15
一、链表
1、以结点的方式进行存储;
2、每个结点包含data域和next域,data域存放数据,next域指向下一个结点;
3、链表各结点的存放不一定连续;
4、链表分有头结点的和没有头结点的,可以根据实际情况确定使用哪一种。
二、单链表创建思路
2.1 增加结点
2.1.1 直接添加到尾部
1、创建一个头结点(head),用来表示单链表的起始位置(不存放数据);
2、每当需要向尾部添加一个结点,找到下一个结点为空的【当前结点】,插入到当前结点的下一个位置;
3、从头结点开始,利用一个辅助变量(结点),通过遍历的方式寻找【当前结点】
2.1.2 按照编号或者说结点位置添加
1、找到要添加位置的前一个结点,赋值给辅助变量(temp)
2、【新结点.next】=【temp.next】
3、【temp.next】=【新结点】
2.2 修改结点
1、找到要修改的结点
2、要修改的结点的内容变成新的内容
2.3 删除结点
1、找到要删除结点的前一个结点,赋值给辅助变量(temp)
2、【temp.next】=【temp.next.next】
3、被删除的结点会被自动回收
三、实现
3.1 增加结点
3.1.1 直接添加到尾部
首先创建一个代表结点的类,类中的属性包括编号,名字,昵称。同时添加了构造器,重写了toString方法。
1 //结点类 2 public class Node { 3 public int no;//编号 4 public String name;//名字 5 public String nickName;//昵称 6 public Node next; 7 8 public Node(int no, String name, String nickName) { 9 this.no = no; 10 this.name = name; 11 this.nickName = nickName; 12 } 13 14 @Override 15 public String toString() { 16 return "Node{" + 17 "no=" + no + 18 ", name='" + name + '\'' + 19 ", nickName='" + nickName + '\'' + 20 '}'; 21 } 22 }
之后创建了代表单链表的类,通过调用结点,实现单链表。类中包含一个私有的头结点属性,头结点确定后不再改变,遍历时创建一个辅助变量进行循环。目前在类中实现了一个添加结点的方法(向尾部添加),同时为了便于测试,额外增加了一个展示当前链表的方法,从头结点开始依次遍历,展示当前链表的所有结点。
1 //单链表,管理创建出来的结点 2 public class SingleLinkedList { 3 //初始化头结点 4 private Node head = new Node(0,"",""); 5 6 //向单链表尾部添加新结点 7 public void addNode(Node newNode){ 8 Node temp = head;//通过辅助变量temp遍历单链表,找到尾部的位置 9 while (true){ 10 if(temp.next == null){ 11 break; 12 } 13 temp = temp.next; 14 } 15 temp.next = newNode; 16 } 17 18 //展示链表内容 19 public void show(){ 20 if(head.next == null){ 21 System.out.println("链表为空,无法展示"); 22 return; 23 } 24 Node temp = head.next; 25 while (true){ 26 if(temp == null){ 27 break; 28 } 29 System.out.println(temp); 30 temp = temp.next; 31 } 32 } 33 }
测试
1 public static void main(String[] args) { 2 Scanner sc = new Scanner(System.in); 3 4 SingleLinkedList singleLinkedList = new SingleLinkedList(); 5 6 Node node1 = new Node(1, "张三", "Tom"); 7 Node node2 = new Node(2, "李四", "Jerry"); 8 Node node3 = new Node(3, "王五", "Simba"); 9 Node node4 = new Node(4, "赵六", "Shrek"); 10 11 singleLinkedList.show(); 12 System.out.println(); 13 14 singleLinkedList.addNode(node1); 15 singleLinkedList.show(); 16 System.out.println(); 17 18 singleLinkedList.addNode(node2); 19 singleLinkedList.show(); 20 System.out.println(); 21 22 singleLinkedList.addNode(node3); 23 singleLinkedList.show(); 24 System.out.println(); 25 26 singleLinkedList.addNode(node4); 27 singleLinkedList.show(); 28 29 30 }
结果
链表为空,无法展示 Node{no=1, name='张三', nickName='Tom'} Node{no=1, name='张三', nickName='Tom'} Node{no=2, name='李四', nickName='Jerry'} Node{no=1, name='张三', nickName='Tom'} Node{no=2, name='李四', nickName='Jerry'} Node{no=3, name='王五', nickName='Simba'} Node{no=1, name='张三', nickName='Tom'} Node{no=2, name='李四', nickName='Jerry'} Node{no=3, name='王五', nickName='Simba'} Node{no=4, name='赵六', nickName='Shrek'}
3.1.2 按照编号或者说结点位置添加
在SingleLinkedList类中加入按照编号添加节点的方法(这里是按照结点中的编号属性从小到大排列结点)
1 public void addByOrder(Node newNode){ 2 Node temp = head; 3 //考虑到可能存在相同的结点编号,这里用一个flag表示是否已经存在相同的编号 4 boolean flag = false;//默认链表中不存在newNode所属的编号 5 while (true){ 6 //当前结点下一个为空 7 if(temp.next == null){ 8 break; 9 } 10 //当前结点下一个结点编号大于新结点的编号,应该将新结点插入到当前节点后面(按编号从小到大排列) 11 if(temp.next.no > newNode.no){ 12 break; 13 } 14 //当前结点下一个结点编号与新结点相同,已经存在了,不再添加 15 if(temp.next.no == newNode.no){ 16 flag = true; 17 break; 18 } 19 temp = temp.next; 20 } 21 if(flag){ 22 System.out.printf("编号%d存在了",newNode.no); 23 System.out.println(); 24 }else { 25 newNode.next = temp.next; 26 temp.next = newNode; 27 } 28 }
测试
1 public static void main(String[] args) { 2 3 SingleLinkedList singleLinkedList = new SingleLinkedList(); 4 5 Node node1 = new Node(1, "张三", "Tom"); 6 Node node2 = new Node(2, "李四", "Jerry"); 7 Node node3 = new Node(3, "王五", "Simba"); 8 Node node4 = new Node(4, "赵六", "Shrek"); 9 10 singleLinkedList.show(); 11 System.out.println(); 12 13 singleLinkedList.addByOrder(node4); 14 singleLinkedList.show(); 15 System.out.println(); 16 17 singleLinkedList.addByOrder(node1); 18 singleLinkedList.show(); 19 System.out.println(); 20 21 singleLinkedList.addByOrder(node3); 22 singleLinkedList.show(); 23 System.out.println(); 24 25 singleLinkedList.addByOrder(node4); 26 singleLinkedList.show(); 27 System.out.println(); 28 29 singleLinkedList.addByOrder(node2); 30 singleLinkedList.show(); 31 System.out.println(); 32 }
结果
链表为空,无法展示 Node{no=4, name='赵六', nickName='Shrek'} Node{no=1, name='张三', nickName='Tom'} Node{no=4, name='赵六', nickName='Shrek'} Node{no=1, name='张三', nickName='Tom'} Node{no=3, name='王五', nickName='Simba'} Node{no=4, name='赵六', nickName='Shrek'} 编号4存在了 Node{no=1, name='张三', nickName='Tom'} Node{no=3, name='王五', nickName='Simba'} Node{no=4, name='赵六', nickName='Shrek'} Node{no=1, name='张三', nickName='Tom'} Node{no=2, name='李四', nickName='Jerry'} Node{no=3, name='王五', nickName='Simba'} Node{no=4, name='赵六', nickName='Shrek'}
3.2 修改结点
在SingleLinkedList类中加入修改结点的方法
1 //修改节点 2 public void update(Node newNode){ 3 if(head.next == null){ 4 System.out.println("链表为空,无法修改"); 5 return; 6 } 7 Node temp = head.next; 8 //是否找到要修改的结点 9 boolean flag = false; 10 11 while (true){ 12 if(temp == null){//没有与新结点编号一致的可以修改的结点 13 break; 14 } 15 if(temp.no == newNode.no){ 16 flag = true; 17 break; 18 } 19 temp = temp.next; 20 } 21 if(flag){ 22 temp.name = newNode.name; 23 temp.nickName = newNode.nickName; 24 }else { 25 System.out.printf("没有找到编号为%d的结点",newNode.no); 26 System.out.println(); 27 } 28 }
测试
1 public static void main(String[] args) { 2 3 SingleLinkedList singleLinkedList = new SingleLinkedList(); 4 5 Node node1 = new Node(1, "张三", "Tom"); 6 Node node2 = new Node(2, "李四", "Jerry"); 7 Node node3 = new Node(3, "王五", "Simba"); 8 Node node4 = new Node(1, "赵六", "Shrek"); 9 Node node5 = new Node(5, "田七", "Shrek"); 10 11 singleLinkedList.show(); 12 System.out.println(); 13 14 singleLinkedList.addByOrder(node1); 15 singleLinkedList.addByOrder(node2); 16 singleLinkedList.addByOrder(node3); 17 singleLinkedList.show(); 18 System.out.println(); 19 20 singleLinkedList.update(node5); 21 singleLinkedList.show(); 22 System.out.println(); 23 24 singleLinkedList.update(node4); 25 singleLinkedList.show(); 26 System.out.println(); 27 28 }
结果
链表为空,无法展示 Node{no=1, name='张三', nickName='Tom'} Node{no=2, name='李四', nickName='Jerry'} Node{no=3, name='王五', nickName='Simba'} 没有找到编号为5的结点 Node{no=1, name='张三', nickName='Tom'} Node{no=2, name='李四', nickName='Jerry'} Node{no=3, name='王五', nickName='Simba'} Node{no=1, name='赵六', nickName='Shrek'} Node{no=2, name='李四', nickName='Jerry'} Node{no=3, name='王五', nickName='Simba'}
3.3 删除结点
在SingleLinkedList类中加入删除结点的方法
1 public void delete(int no){ 2 if(head.next == null){ 3 System.out.println("链表为空,无法删除"); 4 return; 5 } 6 Node temp = head; 7 //是否找到要删除的结点的前一个结点 8 boolean flag = false; 9 10 while (true){ 11 if(temp.next == null){ 12 break; 13 } 14 if(temp.next.no == no){ 15 flag = true; 16 break; 17 } 18 temp = temp.next; 19 } 20 21 if(flag){ 22 temp.next = temp.next.next; 23 }else { 24 System.out.printf("没有找到编号为%d的结点",no); 25 System.out.println(); 26 } 27 }
测试
1 public static void main(String[] args) { 2 3 SingleLinkedList singleLinkedList = new SingleLinkedList(); 4 5 Node node1 = new Node(1, "张三", "Tom"); 6 Node node2 = new Node(2, "李四", "Jerry"); 7 Node node3 = new Node(3, "王五", "Simba"); 8 9 singleLinkedList.show(); 10 System.out.println(); 11 12 singleLinkedList.addByOrder(node1); 13 singleLinkedList.addByOrder(node2); 14 singleLinkedList.addByOrder(node3); 15 singleLinkedList.show(); 16 System.out.println(); 17 18 singleLinkedList.delete(2); 19 singleLinkedList.show(); 20 System.out.println(); 21 22 singleLinkedList.delete(3); 23 singleLinkedList.show(); 24 System.out.println(); 25 26 singleLinkedList.delete(2); 27 singleLinkedList.show(); 28 System.out.println(); 29 30 singleLinkedList.delete(1); 31 singleLinkedList.show(); 32 System.out.println(); 33 34 }
结果
链表为空,无法展示 Node{no=1, name='张三', nickName='Tom'} Node{no=2, name='李四', nickName='Jerry'} Node{no=3, name='王五', nickName='Simba'} Node{no=1, name='张三', nickName='Tom'} Node{no=3, name='王五', nickName='Simba'} Node{no=1, name='张三', nickName='Tom'} 没有找到编号为2的结点 Node{no=1, name='张三', nickName='Tom'} 链表为空,无法展示
四、附加操作
4.1 获取链表结点个数
1 public static int getLength(Node head){ 2 if(head.next == null){ 3 return 0; 4 } 5 int len = 0; 6 Node curNode = head.next; 7 while (curNode != null){ 8 len++; 9 curNode = curNode.next; 10 } 11 return len; 12 }
测试
1 public static void main(String[] args) { 2 SingleLinkedList singleLinkedList = new SingleLinkedList(); 3 4 Node node1 = new Node(1, "张三", "Tom"); 5 Node node2 = new Node(2, "李四", "Jerry"); 6 Node node3 = new Node(3, "王五", "Simba"); 7 Node node4 = new Node(4, "赵六", "Shrek"); 8 9 Node head = singleLinkedList.getHead(); 10 11 singleLinkedList.addByOrder(node1); 12 System.out.println(getLength(head)); 13 14 singleLinkedList.addByOrder(node2); 15 System.out.println(getLength(head)); 16 17 singleLinkedList.addByOrder(node3); 18 System.out.println(getLength(head)); 19 20 singleLinkedList.addByOrder(node4); 21 System.out.println(getLength(head)); 22 23 }
结果
1 2 3 4
4.2 查找倒数第k个节点
1 public static Node findLastIndexNode(int k,Node head){ 2 if(head.next == null){ 3 return null; 4 } 5 int len = getLength(head); 6 if(k <= 0 || len < k){ 7 return null; 8 } 9 Node curNode = head.next; 10 11 for (int i = 0; i < len - k; i++) { 12 curNode = curNode.next; 13 } 14 return curNode; 15 }
测试
1 Node lastIndexNode = findLastIndexNode(1, head); 2 System.out.println("倒数第一个"+lastIndexNode); 3 4 Node lastIndexNode2 = findLastIndexNode(2, head); 5 System.out.println("倒数第二个"+lastIndexNode2); 6 7 Node lastIndexNode3 = findLastIndexNode(3, head); 8 System.out.println("倒数第三个"+lastIndexNode3); 9 10 Node lastIndexNode4 = findLastIndexNode(4, head); 11 System.out.println("倒数第四个"+lastIndexNode4); 12 13 Node lastIndexNode5 = findLastIndexNode(5, head); 14 System.out.println("倒数第五个"+lastIndexNode5);
结果
倒数第一个Node{no=4, name='赵六', nickName='Shrek'} 倒数第二个Node{no=3, name='王五', nickName='Simba'} 倒数第三个Node{no=2, name='李四', nickName='Jerry'} 倒数第四个Node{no=1, name='张三', nickName='Tom'} 倒数第五个null
4.3链表反转
1 public static void reverse(Node head){ 2 if(head.next == null || head.next.next == null){ 3 return; 4 } 5 6 //创建一个反转后的头结点 7 Node reverseHead = new Node(0, "", ""); 8 //当前结点 9 Node curNode = head.next; 10 //代表当前结点的下一个结点 11 Node nextNode = null; 12 13 //以 向【反转后的头结点.next】插入结点的形式反转 14 while (curNode != null){ 15 nextNode = curNode.next;//必须保证取出当前结点之后,后面的数据不丢失 16 curNode.next = reverseHead.next;//必须保证插入当前结点时,当前结点的【后面】连接的是reverseHead后的所有结点 17 reverseHead.next = curNode;//必须保证插入当前结点时,当前结点的【前面】连的是reverseHead 18 curNode = nextNode; 19 } 20 head.next = reverseHead.next; 21 }
测试
1 reverse(singleLinkedList.getHead()); 2 singleLinkedList.show();
结果
Node{no=4, name='赵六', nickName='Shrek'} Node{no=3, name='王五', nickName='Simba'} Node{no=2, name='李四', nickName='Jerry'} Node{no=1, name='张三', nickName='Tom'}
4.4链表倒序打印
不改变原有的链表
1 //从尾到头打印链表 2 public static void reversePrint(Node head){ 3 if(head.next == null){ 4 return; 5 } 6 //创建栈,将结点压入栈 7 Stack<Node> stack = new Stack<>(); 8 Node curNode = head.next; 9 while (curNode != null){ 10 stack.push(curNode); 11 curNode = curNode.next; 12 } 13 while (stack.size() > 0){ 14 System.out.println(stack.pop()); 15 } 16 }
测试
1 reversePrint(singleLinkedList.getHead()); 2 System.out.println();
结果
Node{no=4, name='赵六', nickName='Shrek'} Node{no=3, name='王五', nickName='Simba'} Node{no=2, name='李四', nickName='Jerry'} Node{no=1, name='张三', nickName='Tom'}