苦行僧DH

博客园 首页 新随笔 联系 订阅 管理

单链表

学习课程:尚硅谷数据结构与算法

目录:

  1、链表简介

  2、链表实现

  3、关于链表的五道简单面试题

    3_1:求单链表中有效节点的个数

    3_2:查找单链表中的倒数第k个结点

    3_3:反转单链表

    3_4:从尾到头打印单链表

    3_5:合并两个有序的单链表

     

 

1、链表简介

单链表分为有头和没有头的,这个随笔记录的是有头的。

 

 

先看这张图,每个框代表一个节点,然后一个节点中需要存放两个东西,一个是自己本身的数据,另一个是next(next指向了这个节点的下一个节点)。

所以我们可以看得出来,链表就像一条锁链一样,一条扣一条,锁链呢就是这个样子

 

 

 ,然后我们再回头来看前面的那张图,先看头,头部的内容就是正常的数据和next,头部的next指向了该链表中第一个节点,类似于指针(在Java中使用引用),然后我们的第一个节点中就指向了再下一个节点,这个指向下一个节点正是依靠节点中的next属性,链表就是这样,一环扣一环起来,直到最后一个节点,因为是最后一个节点了,所以next就为空(null)。

 

 

2、链表实现

这个随笔中使用Java实现

1、定义节点对象

1、定义链表对象

 

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属性都为空,那么说明该链表中是没有数据的。


方法getHead(获得头):

  获得链表中头的方法。

 

方法addHeroNode(往链表中添加节点[不考虑顺序问题]):

  直接把添加的节点加入到最后去,思路就是一直遍历,发现当前节点的下一个节点为空,那么说明已经到了尾部,既然到了尾部,那么只需要将传进来的HeroNode赋值给当前节点的下一个节点即可,如果没有到尾部,那么继续遍历


方法addHeroNodeForOrder(往链表中添加节点[从小到大的顺序]):

   

delByNo(删除节点):

 

 


updateByNo(修改节点):

   这个就不用说了吧,只要把没有顺序的添加写出来了,那么这个修改也就很简单了,直接找到对应的节点,通过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     }
查找单链表中的倒数第k个结点

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     }
合并两个有序的单链表

 

 

 

 

最后献上该教程地址:https://www.bilibili.com/video/BV1E4411H73v?p=19

posted on 2020-04-16 14:25  苦行僧DH  阅读(448)  评论(0编辑  收藏  举报