单向链表


基本介绍

链表是有序的列表,它在内存中的存储如下图:

单链表示意图

  • 链表是以节点的方式来存储,是链式存储
  • 每个节点包含data 域,next域:指向下一个节点
  • 如图:链表的各个节点不一定是连续存储
  • 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

单向链表的应用实例

使用带head头的单向链表实现:水浒英雄排行榜管理完成对英雄人物的增删改查操作

  • 在添加英雄时,直接添加到链表的尾部

无序添加

添加(创建)

a. 先创建一个head头节点,作用就是表示单链表的头

b. 每添加一个节点,就直接加入到链表的最后

遍历:通过一个辅助变量遍历,帮助遍历整个链表

  • 在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)

有序添加

​ 需要按照编号的顺序添加

  1. 首先找到新添加的节点的位置,是通过辅助变量({指针),通过遍历来搞定
  2. 新的节点.next =temp.next
  3. 将temp.next=新的节点
  • 修改节点功能

    思路:(1)先找到该节点,通过遍历 (2) temp.name = newHeroNode .name ; temp. nickname= newHeroNode .nickname

  • 删除节点

删除节点

从单链表中删除一个节点的思路

  1. 先找到需要删除的这个节点的前一个节点temp
  2. termn.next = temp.next.next
  3. 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收
  • 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package com.ysy.linkedlist;

public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试
//先创建节点
HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
HeroNode heroNode2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode heroNode3 = new HeroNode(3, "吴用", "智多星");
HeroNode heroNode4 = new HeroNode(4, "林冲", "豹子头");

//创建单向链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
//加入节点,无排名
// singleLinkedList.add(heroNode1);
// singleLinkedList.add(heroNode4);
// singleLinkedList.add(heroNode2);
// singleLinkedList.add(heroNode3);


//加入节点,按照编号
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.addByOrder(heroNode4);
singleLinkedList.addByOrder(heroNode2);
singleLinkedList.addByOrder(heroNode3);
// singleLinkedList.addByOrder(heroNode3);

//输出链表
singleLinkedList.list();

//测试修改节点的代码
HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟");
singleLinkedList.update(newHeroNode);

System.out.println("修改后的链表~");
singleLinkedList.list();

//删除一个节点
singleLinkedList.del(3);
System.out.println("删除后的链表~");
singleLinkedList.list();
}
}

class SingleLinkedList{
//初始化一个头节点,不要动,不存放具体的数据
private HeroNode head = new HeroNode(0, "", "");

//返回头节点
public HeroNode getHead() {
return head;
}

public void setHead(HeroNode head) {
this.head = head;
}

//添加节点到单向列表中
//思路:不考虑编号顺序
//1.找到当前链表的最后节点
//2.将最后节点的next指向新的节点
public void add(HeroNode heroNode){

//因为head绩点不能动,所以需要一个辅助遍历temp
HeroNode temp = head;
//遍历链表,找到最后
while(true){
//找到链表的最后
if(temp.next == null){
break;
}
//如果没有找到最后,将temp后移
temp = temp.next;
}
//出循环代表temp已指向了链表的最后
//将最后这个节点指向新节点
temp.next = heroNode;
}

//根据排名添加节点到链表中,如果排名存在,则输出添加失败
public void addByOrder(HeroNode heroNode){
//头节点不能动,使用辅助变量temp来寻找添加的位置
//temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;//标志添加的编号是否存在
while (true){
//链表已到最后
if(temp.next == null){
break;
}
if(temp.next.number > heroNode.number){//位置找到,插在后面
break;
}else if(temp.next.number == heroNode.number){
flag = true;//说明标号存在
break;
}
temp = temp.next;//后移,遍历链表
}
if(flag){
System.out.printf("编号 %d 已存在,不能添加\n", heroNode.number);
}else {
//插入链表中,temp后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}

//根据number来修改节点信息,number不能改
public void update(HeroNode newHeroNode){
//判断是否为空
if(head.next == null){
System.out.println("链表为空~");
return;
}

//找到需要修改的节点,根据number编号
//辅助变量,遍历链表
HeroNode temp = head.next;
boolean flag = false;
while (true){
if(temp == null){
break;//链表遍历完成
}
if(temp.number == newHeroNode.number){
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag判断是否找到要修改的节点
if(flag){
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
}else {
System.out.printf("没有找到编号 %d 的节点\n", newHeroNode.number);
}
}

//删除节点
//思路
//1.head不能动,因此我们需要---个temp辅助节点找到待删除节点的前一个节点
//2.说明我们在比较时,是temp.next.no和需要删除的节点的no比较
public void del(int number){
HeroNode temp = head;
boolean flag = false;//标志是否找到要删除的节点
while (true){
if(temp.next == null){//链表已到最后
break;
}
if(temp.next.number == number){
//找到
flag = true;
break;
}
temp = temp.next;//temp后移,遍历链表
}
//判断flag
if(flag){
temp.next = temp.next.next;
}else {
System.out.printf("要删除的 %d 节点不存在\n", number);
}
}


//显示链表,遍历
public void list(){
//判断链表是否为空
if(head.next == null){
System.out.println("链表为空");
return;
}

//因为头节点不能动,所以辅助变量temp遍历
HeroNode temp = head.next;
while (true){
//判断链表是否走到最后
if(temp == null){
break;
}
//输出节点信息
System.out.println(temp);
//将temp后移
temp = temp.next;
}
}
}

//定义HeroNode,每个HeroNode对象就是一个节点
class HeroNode{
//data域
public int number;
public String name;
public String nickName;
//next域
public HeroNode next;

//构造器
public HeroNode(int number, String name, String nickName) {
this.number = number;
this.name = name;
this.nickName = nickName;
}

/**
* 为了显示方法,重回写toString方法
* @return
*/
@Override
public String toString() {
return "HeroNode{" +
"number=" + number +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}

单向链表面试题

  • 求单链表中节点的个数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //方法:获取单链表的节点的个数(如果是带头结点的链表,需求不统计头节点)
    /**
    *
    * @param head 链表的头节点
    * @return 有效节点个数
    */
    public static int getLength(HeroNode head){
    if(head.next == null){
    //空链表
    return 0;
    }
    int length = 0;
    //定义一个辅助的变量
    HeroNode cur = head.next;
    while(cur != null){
    length++;
    cur = cur.next;//遍历
    }
    return length;
    }
  • 查找单链表中的倒数第k个结点[新浪面试题]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //查找单链表中的倒数第k个结点
    //思路:
    //1.编写一个方法,接收head节点,同时接受一个index
    //2.index 表示是倒数第index个节点
    //3.先把链表从头到尾遍历,得到链表的总的长度 getLength
    //4.得到size后,从链表的第一个开始遍历(size - index)个,就可以得到
    //5.如果找到了,则返回该节点,否则返回null
    public static HeroNode findLastIndexNode(HeroNode head, int index){
    //判断链表是否为空
    if(head.next == null){
    return null;//没有找到
    }
    //第一次遍历得到链表的长度(节点个数)
    int size = getLength(head);
    //第二次遍历 size-index 位置,就是倒数第K个点
    //先做一个index的校验
    if(index <= 0 || index > size){
    return null;
    }
    //定义辅助变量,for 循环定位到倒数的index
    HeroNode cur = head.next;
    for(int i =0; i < size-index; i++){
    cur = cur.next;
    }
    return cur;
    }
  • 单链表的反转[腾讯面试题]

    思路

    1. 先定义一个节点reverseHead=new HeroNode();
    2. 从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端.
    3. 原来的链表的head.next =reverseHead.next
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //将单链表反转
    public static void reverseList(HeroNode head){
    //如果当前链表为空,或者只有一个节点,无需反转,直接返回
    if(head.next == null || head.next.next == null){
    return;
    }

    //定义一个辅助指针,帮助我们遍历链表
    HeroNode cur = head.next;
    HeroNode next = null;//记录当前节点的下一个节点
    HeroNode reverseHead = new HeroNode(0, "", "");

    //从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端.
    while (cur != null){
    next = cur.next;//先暂时保存当前节点的下一个节点,因为后面需要使用
    cur.next = reverseHead.next;//将cur的下一个节点指向新的链表的最前端
    reverseHead.next = cur;//将cur连接到新的链表上
    cur = next;//让cur指向后一个节点
    }
    //将head.next指向reverseHead.next,实现反转
    head.next = reverseHead.next;
    }
  • 从尾到头打印单链表[百度,要求方式1:反向遍历。方式2: Stack栈]

    思路

    • 方式1:先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构,不建议
    • 方式2:可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //从尾到头打印单链表(栈)
    public static void reversePrint(HeroNode head) {
    //如果当前链表为空,不能打印
    if(head.next == null || head.next.next == null){
    return;
    }
    //创建栈,将节点压入栈
    Stack<HeroNode> stack = new Stack<>();
    HeroNode cur = head.next;
    //将链表的所有节点压入栈
    while (cur != null){
    stack.push(cur);
    cur = cur.next;//cur后移,这样就可以压入下一个节点
    }
    //将栈中的节点进行打印,pop出栈
    while (stack.size() > 0){
    System.out.println(stack.pop());
    }
    }

文章作者: Yang Shiyu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yang Shiyu !
  目录