前言

数据结构中单链表的C/C++语言描述实现模板,有详细的步骤解析及使用示例。


代码仓库


linkList.cpp

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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
//头文件————————————————————
#include <iostream>
using namespace std;

//自定义数据类型————————————————————
typedef int ELEM_TYPE; //数据的(数据)类型 依据数据的实际类型定义

//结构体
//定义
//结点
typedef struct LNode
{
ELEM_TYPE data; //数据
struct LNode *next; //指向直接后继结点的指针
} LNode;
// struct LNode:结构体数据类型的名称
//如创建一个结点的语句:struct LNode node;
//若结构体数据类型内存在指向该结构体的指针数据类型,则必须完整命名结构体数据类型的名称
//如结点结构体LNode内存在指向该结构体的指针数据类型next,则命名语句为:typedef struct LNode而不是typedef struct
// LNode:typedef给结构体数据类型起的别名,可简化语句
//如创建一个结点的语句:LNode node;
//结构体数据类型的名称和别名尽量一致,以方便记忆、使用

//函数声明————————————————————
void use_example(); //使用示例
void create_list_head(LNode *&head, ELEM_TYPE value[], int count); //创建 头插法
void create_list_tail(LNode *&head, ELEM_TYPE value[], int count); //创建 尾插法
LNode *find_node(LNode *head, ELEM_TYPE elem); //按值查找
LNode *get_node(LNode *head, int i); //按序查找
void insert_node(LNode *&head, int posi, LNode *node); //插入 前插
void insert_node_exten(LNode *&head, int posi, LNode *node); //插入 扩展:前插转后插
void delele_node(LNode *&head, int posi); //删除 前删
void delele_node_exten(LNode *&head, int posi); //删除 前删转后删
int get_length(LNode *head); //获取表长
void clear_list(LNode *&head); //清空
void merge_list_increase(LNode *head1, LNode *head2, LNode *&head3); //合并两递增表成递增表
void merge_list_reduce(LNode *head1, LNode *head2, LNode *&head3); //合并两递增表成递减表

//主函数————————————————————
int main()
{
use_example(); //使用示例

return 0;
}

//函数定义————————————————————
//使用示例
void use_example()
{
//创建
struct LNode *head; //头指针,指向头结点或第一个结点
//或LNode *head;
//一般头指针变量名为链表表名 这里用head见明知意,表示是头指针

ELEM_TYPE value[3]; //数据数组
value[0] = 100;
value[1] = 200;
value[2] = 300;

create_list_tail(head, value, 3); //创建 尾插法 元素在表中的排列:100,200,300

//按值查找
LNode *node; //结点指针

node = find_node(head, 100); //按值查找

cout << node->data << endl; //输出:100

//按序查找
node = get_node(head, 2); //按序查找

cout << node->data << endl; //输出:200

//插入 前插
node = (LNode *)malloc(sizeof(LNode)); //创建新结点 结点指针指向新结点
node->data = 400; //初始化新结点的数据域

insert_node(head, 3, node); //插入 前插 元素在表中的排列:100,200,400,300

//插入 扩展:前插转后插
node = (LNode *)malloc(sizeof(LNode)); //创建新结点 结点指针指向新结点
node->data = 500; //初始化新结点的数据域

insert_node_exten(head, 3, node); //插入 扩展:前插转后插 元素在表中的排列:100,200,500,400,300

LNode *p; //移动结点指针
p = head->next; //初始化移动结点指针 指向第一个结点(头结点的下一个结点)

while (p != NULL) //遍历
{
cout << p->data << endl; //输出:100,200,500,400,300

p = p->next; //移动结点指针
}

//删除 前删
delele_node(head, 3); //删除 前删 元素在表中的排列:100,200,400,300

//删除 前删转后删
delele_node_exten(head, 3); //删除 前删转后删 元素在表中的排列:100,200,300

p = head->next; //初始化移动结点指针 指向第一个结点(头结点的下一个结点)

while (p != NULL) //遍历
{
cout << p->data << endl; //输出:100,200,300

p = p->next; //移动结点指针
}

//获取表长
int list_length = 0; //表长

list_length = get_length(head);

cout << list_length << endl; //输出:3

//清空
clear_list(head);

list_length = get_length(head);

cout << list_length << endl; //输出:0

//合并两递增表成递增表
struct LNode *head1; //递增表1的头指针,指向头结点或第一个结点

ELEM_TYPE value1[3]; //数据数组1
value1[0] = 100;
value1[1] = 300;
value1[2] = 500;

create_list_tail(head1, value1, 3); //创建 尾插法 元素在表中的排列:100,300,500

struct LNode *head2; //递增表2的头指针,指向头结点或第一个结点

ELEM_TYPE value2[3]; //数据数组2
value2[0] = 200;
value2[1] = 400;
value2[2] = 600;

create_list_tail(head2, value2, 3); //创建 尾插法 元素在表中的排列:200,400,600

struct LNode *head3; //新递增表的头指针,指向头结点或第一个结点

merge_list_increase(head1, head2, head3); //合并两递增表成递增表

p = head3->next; //移动结点指针 指向新递增表的第一个结点
while (p != NULL) //遍历
{
cout << p->data << endl; //输出:100,200,300,400,500,600

p = p->next; //移动结点指针
}

//合并两递增表成递减少表
create_list_tail(head1, value1, 3); //创建 尾插法 元素在表中的排列:100,300,500
create_list_tail(head2, value2, 3); //创建 尾插法 元素在表中的排列:200,400,600
clear_list(head3); //清空

merge_list_reduce(head1, head2, head3); //合并两递增表成递减表

p = head3->next; //移动结点指针 指向新递减表的第一个结点
while (p != NULL) //遍历
{
cout << p->data << endl; //输出:600,500,400,300,200,100

p = p->next; //移动结点指针
}

return;
}

//函数定义————————————————————
//创建 头插法
//时间复杂度:O(n)
//注意:参数使用*&,指向指针的引用数据类型
//若只使用*,指针数据类型,在函数体中并未进行解引用调用修改*head的值,使用malloc()修改的是head的值
//所以是值拷贝,函数返回后数据丢失。地址拷贝需要引用数据类型的配合
void create_list_head(LNode *&head, ELEM_TYPE value[], int count) //参数:头指针,数据数组,数据数量
{
head = (LNode *)malloc(sizeof(LNode)); //创建头结点,头指针指向头结点
head->next = NULL; //初始化头结点的指针域

LNode *node; //新结点指针

for (int i = 0; i < count; i++) //循环创建链表
{
node = (LNode *)malloc(sizeof(LNode)); //创建新结点,新结点指针指向新结点
node->data = value[i]; //初始化新结点的数据域

node->next = head->next; //初始化新结点的指针域
head->next = node;
//插入到链表的头结点和前一新结点之间
//链表描述:头结点->当前新结点->前一新结点->...->空
//若无头结点:head=node;
}

return;
}

//创建 尾插法
//时间复杂度:O(n)
void create_list_tail(LNode *&head, ELEM_TYPE value[], int count) //参数:头指针,数据数组,数据数量
{
head = (LNode *)malloc(sizeof(LNode)); //创建头结点,头指针指向头结点
head->next = NULL; //初始化头结点的指针域

LNode *tail; //尾指针,尾指针始终指向最后一个结点
tail = head; //初始化尾指针 指向最后一个结点(头结点)

LNode *node; //新结点指针

for (int i = 0; i < count; i++) //循环创建链表
{
node = (LNode *)malloc(sizeof(LNode)); //创建新结点,新结点指针指向新结点
node->data = value[i]; //初始化新结点的数据域

tail->next = node; //插入到链表的头结点和前一新结点之间
tail = tail->next; //移动尾指针
//或者tail=node;
}

tail->next = NULL; //尾指针指向空 创建完成
//链表描述:头结点->...->前一新结点->当前新结点->空

return;
}

//按值查找
//时间复杂度:O(n)
LNode *find_node(LNode *head, ELEM_TYPE elem) //参数:头指针,元素 返回值:结点指针
{
LNode *p; //移动结点指针
p = head->next; //初始化移动结点指针 指向第一个结点(头结点的下一个结点)

while (p != NULL) //遍历
{
if (p->data == elem) //匹配
{
break; //退出查找
}

p = p->next; //若不匹配,移动结点指针
}

return p; //若找到返回结点指针,否则返回空指针
}

//按序查找
//时间复杂度:O(n)
LNode *get_node(LNode *head, int i) //参数:头指针,元素查找的位置 返回值:结点指针
{
LNode *p; //移动结点指针
p = head->next; //初始化移动结点指针 指向第一个结点(头结点的下一个结点)

if (i < 0) //不存在负数的位置
{
return NULL; //返回空指针
}

if (i == 0) //第0个位置是头结点
{
return head; //返回头指针
}

for (int j = 1; j < i; j++) //遍历
{

if (p != NULL) //不匹配
{
p = p->next; //移动结点指针
}
else //匹配
{
break; //退出查找
}
}

return p; //若找到返回结点指针,否则返回空指针
}

//插入 前插
//时间复杂度:O(n)
//因为需遍历找到元素插入位置之前的结点
void insert_node(LNode *&head, int posi, LNode *node) //参数:头指针,元素插入的位置,结点
{
LNode *p; //移动结点指针
p = get_node(head, posi - 1); //初始化移动结点指针 指向第posi-1个位置的结点

node->next = p->next; //插入 在第posi-1个位置的结点之后,占据第posi个位置,在原第posi个位置的结点之前
p->next = node;

return;
}

//插入 扩展:前插转后插
//时间复杂度:O(1)
//直接在元素插入位置之后的结点插入,再交换结点数据
void insert_node_exten(LNode *&head, int posi, LNode *node) //参数:头指针,元素插入的位置,结点
{
LNode *p; //移动结点指针
p = get_node(head, posi); //初始化移动结点指针 指向第posi个位置的结点

node->next = p->next; //插入 在第posi个位置的结点之后
p->next = node;

ELEM_TYPE temp; //临时数据

temp = p->data; //交换第posi个位置的结点和新结点的数据
p->data = node->data;
node->data = temp;

return;
}

//删除 前删
//时间复杂度:O(n)
//因为需遍历找到元素删除位置之前的结点
void delele_node(LNode *&head, int posi) //参数:头指针,元素删除的位置
{
LNode *p; //移动结点指针
p = get_node(head, posi - 1); //初始化移动结点指针 指向第posi-1个位置的结点

LNode *q; //被删结点指针
q = p->next; //初始化被删结点指针 指向第posi个位置的结点

p->next = q->next; //修改指针指向 第posi-1个位置的结点的指针指向第posi+1个位置的结点
//或者p->next=p->next->next;

free(q); //删除被删结点指针指向的结点,释放内存
}

//删除 前删转后删
//时间复杂度:O(1)
//交换结点数据,直接删除元素删除位置之后的结点
void delele_node_exten(LNode *&head, int posi) //参数:头指针,元素删除的位置
{
LNode *p; //移动结点指针
p = get_node(head, posi); //初始化移动结点指针 指向第posi个位置的结点

LNode *q; //被删结点指针
q = p->next; //初始化被删结点指针 指向第posi+1个位置的结点

p->data = q->data; //交换第posi个位置和第posi+1个位置结点的数据 此时需删除的是第posi+1个位置的结点
//或者p->data=p->next->data
p->next = q->next; //修改指针指向 第posi个位置的结点的指针指向第posi+2个位置的结点
//或者p->next=p->next->next;

free(q); //删除被删结点指针指向的结点,释放内存
}

//获取表长
int get_length(LNode *head) //参数:头指针 返回值:表长
{
LNode *p; //移动结点指针
p = head->next; //初始化移动结点指针 指向第一个结点(头结点的下一个结点)

int length = 0; //表长

while (p != NULL) //遍历 有结点
{
length++; //表长递增
p = p->next; //移动指针
}

return length; //返回表长
}

//清空
void clear_list(LNode *&head) //参数:头指针
{
LNode *p; //被删结点指针
p = head->next; //初始化被删结点指针 指向第一个结点(头结点的下一个结点)

LNode *q; //记录结点指针 指向被删结点的下一个结点

while (p != NULL) //遍历 有结点
{
q = p->next; //记录被删结点的下一个结点

free(p); //删除被删结点指针指向的结点,释放内存

p = q; //移动指针 指向下一个被删结点
}

head->next = NULL; //头结点的指针域置空 当退出遍历时已无数据结点
}

//合并两递增表成递增表
//思路:遍历两递增表,比较结点的数值域数据,使用尾插法创建新递增表
//参数:递增表1的头指针,递增表2的头指针,新递增表的头指针
void merge_list_increase(LNode *head1, LNode *head2, LNode *&head3)
{
LNode *p; //移动结点指针1
p = head1->next; //初始化移动结点指针1 指向递增表1的第一个结点(头结点的下一个结点)

LNode *q; //移动结点指针2
q = head2->next; //初始化移动结点指针2 指向递增表2的第一个结点(头结点的下一个结点)
free(head2); //释放递增表2的头结点 因为已用移动结点指针2记录递增表2的第一个结点,数据不会丢失

head3 = head1; //初始化新递增表的头指针 指向递增表1的头结点,用递增表1的头结点作为新递增表的头结点
head3->next = NULL; //初始化新递增表头结点的指针域,置空 因为已用移动结点指针1记录递增表1的第一个结点,数据不会丢失

LNode *tail; //新递增表的尾指针 始终指向新递增表的终端结点
tail = head3; //初始化新递增表的尾指针 初始时新递增表为空,尾指针指向头结点

while ((p != NULL) && (q != NULL)) //遍历两递增表
{
if (p->data <= q->data) //比较结点的数值域数据
{
tail->next = p; //将数据域数值小的结点插入新递增表表尾

p = p->next; //递增表1的当前结点已插入新递增表,更新移动结点指针1
tail = tail->next; //更新新递增表的尾指针 始终指向新递增表的终端结点
}
else //同理
{
tail->next = q;

q = q->next;
tail = tail->next;
}
}

tail->next = NULL;
//可以没有,因为最后还要将未插入的结点插入新递增表表尾,未插入结点的终端结点指针域为空

if (p != NULL) //若递增表2插入完成,递增表1还有未插入结点,则将剩余结点插入新递增表尾部
{
tail->next = p;
}
if (q != NULL) //若递增表1合并完成,递增表2还有未插入结点,则将剩余结点插入新递增表尾部
{
tail->next = q;
}

return;
}

//合并两递增表成递减表
//思路:遍历两递增表,比较结点的数值域数据,使用头插法创建新递减表
//参数:递增表1的头指针,递增表2的头指针,新递增表的头指针
void merge_list_reduce(LNode *head1, LNode *head2, LNode *&head3)
{
LNode *p; //移动结点指针1
p = head1->next; //初始化移动结点指针1 指向递增表1的第一个结点(头结点的下一个结点)

LNode *q; //移动结点指针2
q = head2->next; //初始化移动结点指针2 指向递增表2的第一个结点(头结点的下一个结点)
free(head2); //释放递增表2的头结点 因为已用移动结点指针2记录递增表2的第一个结点,数据不会丢失

head3 = head1; //初始化新递减表的头指针 指向递增表1的头结点,用递增表1的头结点作为新递减表的头结点
head3->next = NULL; //初始化新递减表头结点的指针域 因为已用移动结点指针1记录递增表1的第一个结点,数据不会丢失

LNode *temp; //临时结点指针 指向当前要插入新递减表表头的结点

while ((p != NULL) && (q != NULL)) //遍历两递增表
{
if (p->data <= q->data) //比较结点的数值域数据
{
temp = p; //将临时结点指针指向数据域数值小的结点
p = p->next; //递增表1的当前结点已被临时结点指针记录,更新移动结点指针1

temp->next = head3->next; //将临时结点指针指向头结点的下一个结点
head3->next = temp; //将头指针指向当前要插入新递减表表头的结点
}
else //同理
{
temp = q;
q = q->next;

temp->next = head3->next;
head3->next = temp;
}
}

if (p != NULL) //若递增表2插入完成,递增表1还有未插入结点,则取剩余结点一个个插入新递减表表头
{
temp = p;
p = p->next;

temp->next = head3->next;
head3->next = temp;
}
if (q != NULL) //若递增表1插入完成,递增表2还有未插入结点,则取剩余结点一个个插入新递减表表头
{
temp = q;
q = q->next;

temp->next = head3->next;
head3->next = temp;
}

return;
}

总结

不同参考资料中,单链表的描述实现各不相同,但基本思想是一致的。作者使用规范的变量命名、提供详细的步骤解析及使用示例,应用C/C++语言将其整合成模板,以帮助理解记忆。


作者的话

  • 作者:夜悊
  • 版权所有,转载请注明出处,谢谢~
  • 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
  • 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
  • 文章在认识上有错误的地方, 敬请批评指正
  • 望读者们都能有所收获

参考资料

  • 《2023版数据结构高分笔记》主编:率辉
  • 《2023年计算机组成原理考研复习指导》组编:王道论坛
  • 《大话数据结构》作者:程杰