单链表
学习课程:尚硅谷数据结构与算法
目录:
1、链表简介
单链表分为有头和没有头的,这个随笔记录的是有头的。
先看这张图,每个框代表一个节点,然后一个节点中需要存放两个东西,一个是自己本身的数据,另一个是next(next指向了这个节点的下一个节点)。
所以我们可以看得出来,链表就像一条锁链一样,一条扣一条,锁链呢就是这个样子
,然后我们再回头来看前面的那张图,先看头,头部的内容就是正常的数据和next,头部的next指向了该链表中第一个节点,类似于指针(在Java中使用引用),然后我们的第一个节点中就指向了再下一个节点,这个指向下一个节点正是依靠节点中的next属性,链表就是这样,一环扣一环起来,直到最后一个节点,因为是最后一个节点了,所以next就为空(null)。
2、链表实现
这个随笔中使用Java实现
1、定义节点对象
每个节点中都有存放数据的字段最少应该包含:存放数据的字段,指向下一个节点的字段。
我们的节点就命名为HeroNode,存放数据的字段就是存储梁山英雄编号、名称、别名,指向下一个节点的字段就是next
代码:
1 /*数据英雄节点*/ 2 class HeroNode { 3 public int no;/*编号*/ 4 public String name;/*姓名*/ 5 public String aliasName;/*别名,这三个属性就是存放数据的字段*/ 6 public HeroNode next;/*下一个节点,这个属性就是只想下一个节点的字段,当他有下一个节点的时候,该属性存放下一个节点的引用,如果没有下一个节点的时候,他就是空*/ 7 8 public HeroNode(int no, String name, String aliasName) { 9 this.no = no; 10 this.name = name; 11 this.aliasName = aliasName; 12 } 13 14 @Override 15 public String toString() { 16 return "HeroNode{" + 17 "no=" + no + 18 ", name='" + name + '\'' + 19 ", aliasName='" + aliasName + '\'' + 20 '}'; 21 } 22 }
2、定义链表对象
这个时候如果你心里在想为什么要定义链表对象,那么你展开这个
为什么?
既然前面都已经定义了节点对象,那我直接创建多个节点对象,然后再一个一个next指向下去不就好了吗,为甚要搞个链表对象。
因为是这样的,如果我们只用节点对象,那么是可以完成链表的创建、并且把各个节点串联起来。但是既然是一个数据结构,那么基本的增删改查肯定要有,我们对于节点的增删改查应该把它整理起来,而不是当我们需要用的时候才去写,并且前面我们还说了需要一个头结点,所以创建链表的目的就是规范节点的使用方法,以及头节点的创建。当我们创建丁一完链表对象了以后,我们使用这个链表只需要创建一个链表对象,然后调用操作方法,而不需要去关心其他的东西
你如果有疑问,看完了上面的你的疑问多少应该会解答一点,如果没有解决你的疑问,那没关系,不碍事,我们定义完链表对象后,你先使用链表对象进行操作链表,操作完后你再重新用你的想法去操作、定义、使用,如果你的想法弄出来后更好用,那再好不过,如果你发现你的想法并不是很理想,那你就回过头来再试试我这个链表对象。
代码:量稍微有点大,所以折叠起来,自定打开
1 class SingleLikedList { 2 3 /*头节点*/ 4 private HeroNode head = new HeroNode(0, null, null); 5 6 /*判断是否为空*/ 7 public boolean isEmpty() { 8 return head.next == null; 9 } 10 11 /*获得头节点*/ 12 public HeroNode getHead() { 13 return head; 14 } 15 16 /*添加节点,不考虑顺序问题,直接添加到链表尾部*/ 17 public boolean addHeroNode(HeroNode heroNode) { 18 HeroNode temp = head; 19 while (true) { 20 if (temp.next == null) { 21 break; 22 } else { 23 temp = temp.next; 24 } 25 } 26 temp.next = heroNode; 27 return true; 28 } 29 30 /*添加节点,排序,升序,直接添加到链表尾部*/ 31 public boolean addHeroNodeForOrder(HeroNode heroNode) { 32 HeroNode temp = head; 33 while (true) { 34 if (temp.next == null) { 35 temp.next = heroNode; 36 return true; 37 } else if (temp.next.no > heroNode.no) { 38 /* 39 * 如果当前temp的next的no比heroNode的no大 40 * 那么当前temp的next就赋值给heroNode的next 41 * temp的next就是当前的heroNode 42 * 如果不会就画图 43 * */ 44 heroNode.next = temp.next; 45 temp.next = heroNode; 46 return true; 47 } else if (temp.next.no == heroNode.no) { 48 throw new RuntimeException(heroNode.no + "与现有节点的no" + temp.next.no + "相同,不允许插入!"); 49 } 50 temp = temp.next; 51 } 52 } 53 54 /*删除节点,根据no*/ 55 public boolean delByNo(int no) { 56 /*存储头下一个节点*/ 57 HeroNode temp = head; 58 while (temp != null) { 59 if (temp.next.no == no) { 60 temp.next = temp.next.next; 61 return true; 62 } 63 temp = temp.next; 64 } 65 return false; 66 } 67 68 /*修改节点,通过no*/ 69 public boolean updateByNo(HeroNode newHeroNode) { 70 HeroNode temp = head.next; 71 while (temp != null) { 72 if (temp.no == newHeroNode.no) { 73 temp.name = newHeroNode.name; 74 temp.aliasName = newHeroNode.aliasName; 75 return true; 76 } 77 temp = temp.next; 78 } 79 return false; 80 } 81 82 /*展示节点中所有的数据*/ 83 public void displayAll() { 84 HeroNode temp = head.next; 85 while (true) { 86 if (temp == null) { 87 break; 88 } 89 System.out.println(temp); 90 temp = temp.next; 91 } 92 } 93 }
代码解释:
属性head: 头的作用是指向第一个节点,原则上不存放任何数据,它的作用仅仅是指向第一个节点。
方法isEmpty(判断链表是否为空): head属性的作用就是指向第一个节点,如果连head的next属性都为空,那么说明该链表中是没有数据的。
获得链表中头的方法。
方法addHeroNode(往链表中添加节点[不考虑顺序问题]): 直接把添加的节点加入到最后去,思路就是一直遍历,发现当前节点的下一个节点为空,那么说明已经到了尾部,既然到了尾部,那么只需要将传进来的HeroNode赋值给当前节点的下一个节点即可,如果没有到尾部,那么继续遍历
delByNo(删除节点):
这个就不用说了吧,只要把没有顺序的添加写出来了,那么这个修改也就很简单了,直接找到对应的节点,通过no判断,然后更改里面的数据字段就行了
displayAll(展示所有的节点数据): 遍历打印所有的节点数据,展示一手
|
3、关于链表的五道简单面试题
1、求链表中有效节点的数量
传入头节点,在原先遍历的基础上更改,原先是打印节点,现在是直接一个变量++,对于节点是否有效可以进行相应业务逻辑判断
1 public static int getLength(HeroNode head) {
2 HeroNode temp = head.next;
3 int length = 0;
4 while (temp != null) {
5 length++;
6 temp = temp.next;
7 }
8 return length;
9 }
2、查找单链表中的倒数第k个结点
传入头节点, 这个其实有一个规律总长度-倒数k就是该节点中单链表中正数的索引(从0开始),比如总长度为7,找倒数第1个,那么就是正数索引就是从0开始的6,
1 public static HeroNode getLastIndex(int index, HeroNode head) {
2 /*拿到总长度,总长度-倒数k,就拿到正数的需要找的那个节点的下标,*/
3 int length = getLength(head);
4 /*如果查找的倒数大于总长度或者小于,那么就超出边界,说明没有*/
5 if (index > length || index < 0) {
6 return null;
7 }
8 /*拿到正数的索引下标*/
9 int toIndex = length - index;
10 HeroNode temp = head;
11 for (int i = 0; i <= toIndex; i++) {
12 temp = temp.next;
13 }
14 return temp;
15 }
3、反转单链表
传入头节点,这个有点复杂。
1 /*3、反转单链表*/
2 public static void reverseList(HeroNode head) {
3 /*如果空链表或者只有一个元素的链表,则不需要反转*/
4 if (head.next == null || head.next.next == null) {
5 return;
6 }
7 /*记录当前的*/
8 HeroNode current = head.next;
9 /*新头*/
10 HeroNode newHead = new HeroNode(0, "", "");
11 /*辅助,记录原节点的下一个节点*/
12 HeroNode next = null;
13 while (current != null) {
14 /*首先保存当前节点的下一个节点*/
15 next = current.next;
16 /*然后当前节点的下一个节点保存的是新节点的后面的节点*/
17 current.next = newHead.next;
18 /*新节点的直接子节点就是当前current*/
19 newHead.next = current;
20 /*往后移动一个位置*/
21 current = next;
22 }
23
24 /*将新头节点后面的数据赋值给原头节点*/
25 head.next = newHead.next;
26 }
我们需要将上方的数据就存入新节点中。
存的时候,只需要将新head的next部分赋值给新加入节点的next,然后再将新加入的节点赋值给新head的next。
这个要视频才好理解,博客园不好弄视频,我就给个地址大家去看,https://www.bilibili.com/video/BV1E4411H73v?p=22
4、从尾到头打印链表内的数据
传入头节点,利用栈的先进后出特点,先将数据压入栈,然后再一个一个的弹栈。
1 /*4、从头到尾打印单链表*/ 2 public static void reversePrint(HeroNode head) { 3 if (head.next == null) { 4 System.out.println("链表为空"); 5 return; 6 } 7 /*栈*/ 8 Stack<HeroNode> stack = new Stack(); 9 HeroNode temp = head.next; 10 while (temp != null) { 11 stack.push(temp); 12 temp = temp.next; 13 } 14 /*现在压栈完毕,出栈*/ 15 while (!stack.isEmpty()) { 16 System.out.println(stack.pop()); 17 } 18 }
5、合并两个有序的单链表
传入两个链表对象,第一个是目标,第二个是源,会将源的数据合并到目标中。
1、如果目标链表数据和源链表数据都为空,那么就不用合并,结束。
2、如果源链表数据为空,那么也不用合并,结束。
3、如果目标链表数据为空,那么直接将源链表数据赋值给目标链表,结束。
4、前面我们的链表对象有一个顺序添加节点的方法,那么这里直接遍历源数据,然后一个一个的添加到目标链表中。需要注意的是,不能直接将源链表的节点取出放入目标链表中,由于Java中引用的问题,拿到源链表中节点数据后复制一份再添加到目标链表中
1 /*5、合并两个有序的有序的链表*/ 2 public static void mergeList(SingleLikedList target, SingleLikedList source) { 3 /*如果两个next都为空,那么直接不需要合并,或者源链表next为空,那么也不需要合并*/ 4 if ((target.getHead().next == null && source.getHead().next == null) || source.getHead().next == null) { 5 return; 6 } 7 /*如果目标链表没有数据,那么直接将源链表头赋值给目标链表就行*/ 8 if (target.getHead().next == null) { 9 HeroNode head = target.getHead(); 10 head = source.getHead(); 11 return; 12 } 13 /*将sourceHead的的数据加入链表*/ 14 HeroNode temp = source.getHead().next; 15 while (temp != null) { 16 target.addHeroNodeForOrder(new HeroNode(temp.no, temp.name, temp.aliasName)); 17 temp = temp.next; 18 } 19 }