单链表反转/逆序的两种方法
来源:互联网 发布:曼秀雷敦润肤乳 知乎 编辑:程序博客网 时间:2024/05/21 00:20
- 前面我们大约把单链表 ADT 的基本操作都过了一遍,但是这还不够。单链表在面试与笔试中出现的几率很高,接下来我们再花点时间把常见的单链表面试题尽可能过一遍,彻底掌握单链表~
- 那开始我们的第一个面试题?不妨做做“单链表反转”,或者说“单链表逆序”吧?还是基于前面的例子。
究竟要如何反转呢?我们不妨拿一个例子来说明一下算法。
- 我先画一个单链表,这个单链表有4个元素。我的思路就是,每次把第二个元素提到最前面来。比如下面是第一次交换,我们先让头结点的next域指向结点a2,再让结点a1的next域指向结点a3,最后将结点a2的next域指向结点a1,就完成了第一次交换。
第一次交换
- 然后进行相同的交换将结点a3移动到结点a2的前面,然后再将结点a4移动到结点a3的前面就完成了反转。
第二次交换
第三次交换
- 思路有了,那就可以写代码了。这里我们需要额外的两个工作指针来辅助交换。这个下面的步骤慢慢理解下,结合图片。注意结点之间的关系要先断再连。
步骤:
- 定义当前结点 current,初始值为首元结点,current = L->next;
- 定义当前结点的后继结点 pnext, pnext = current->next;
- 只要 pnext 存在,就执行以下循环:
- 定义新节点 prev,它是 pnext的后继结点,prev = pnext->next;
- 把pnext的后继指向current, pnext->next = current;
- 此时,pnext 实际上已经到了 current 前一位成为新的current,所以这个时候 current 结点实际上成为新的 pnext,current = pnext;
- 此时,新的 current 就是 pnext,current = pnext;
- 而新的 pnext 就是 prev,pnext = prev;
- 最后将头结点与 current 重新连上即可,L->next = current;
函数设计如下:
01
/* 单链表反转/逆序 */
02
Status ListReverse(LinkList L)
03
{
04
LinkList current,pnext,prev;
05
if
(L == NULL || L->next == NULL)
06
return
L;
07
current = L->next;
/* p1指向链表头节点的下一个节点 */
08
pnext = current->next;
09
current->next = NULL;
10
while
(pnext)
11
{
12
prev = pnext->next;
13
pnext->next = current;
14
current = pnext;
15
pnext = prev;
16
printf
(
"交换后:current = %d,next = %d \n"
,current->data,current->next->data);
17
}
18
//printf("current = %d,next = %d \n",current->data,current->next->data);
19
L->next = current;
/* 将链表头节点指向p1 */
20
return
L;
21
}
- 其实在你写函数的时候,我也写了个函数,也能运行。思路也差不多,不过你的current一直是表的第一个结点,我这里的current始终是首元结点的值,我的函数需要每次对pnext重新赋值。一会解释下。
01
Status ListReverse2(LinkList L)
02
{
03
LinkList current, p;
04
05
if
(L == NULL)
06
{
07
return
NULL;
08
}
09
current = L->next;
10
while
(current->next != NULL)
11
{
12
p = current->next;
13
current->next = p->next;
14
p->next = L->next;
15
L->next = p;
16
}
17
return
L;
18
}
- p = current->next; p 就相当于前面的 pnext。(图1中a2即为p)
- current->next = p->next; p->next 就相当于 prev的角色,这句代码意思是 current 的后继指向 prev.(相当于图1中a1->next = a3(a2->next))
- p->next = L->next; 这句就是 p 的后继直接指向首元节点。(相当于图1中a2->next = a1)
- L->next = p; 然后再将头结点指向 p。(相当于图1中L->next = a2)
- 参照图就很容易理解上面的步骤了。我觉得我这么写比你的清晰一些。我先将current指向prev,再将pnext指向current,最后将头结点指向pnext。
这个是程序运行的结果。
01
整体创建L的元素(头插法):
02
// 原链表,current = 68, pnext = 55,68指向18,55指向18,头结点指向55
03
-> 68 -> 55 -> 18 -> 45 -> 41 -> 43 -> 5 -> 28 -> 80 -> 67
04
05
// 第一次交换后,原链表变成这样
06
-> 55 -> 68 -> 18 -> 45 -> 41 -> 43 -> 5 -> 28 -> 80 -> 67
07
// 进行第二次交换,pnext = 18,68指向45,18变成头结点
08
-> 18 -> 55 -> 68 -> 45 -> 41 -> 43 -> 5 -> 28 -> 80 -> 67
09
// 进行第三次交换,pnext = current->next = 45,68指向41,45变成头结点
10
-> 45 -> 18 -> 55 -> 68 -> 41 -> 43 -> 5 -> 28 -> 80 -> 67
11
// ……
12
-> 41 -> 45 -> 18 -> 55 -> 68 -> 43 -> 5 -> 28 -> 80 -> 67
13
14
-> 43 -> 41 -> 45 -> 18 -> 55 -> 68 -> 5 -> 28 -> 80 -> 67
15
16
-> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68 -> 28 -> 80 -> 67
17
18
-> 28 -> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68 -> 80 -> 67
19
20
-> 80 -> 28 -> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68 -> 67
21
// current 68 没有后继,反转结束
22
-> 67 -> 80 -> 28 -> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68
23
24
25
反转L后
26
-> 67 -> 80 -> 28 -> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68
最后附上完整代码,反转有两个函数。
- 方法1,current始终保持在第一位,pnext与prev遍历并完成交换。
- 方法2,current始终是原链表的第一个数,然后把pnext不断移动到首位。
001
#include "stdio.h"
002
003
#define OK 1
004
#define ERROR 0
005
#define TRUE 1
006
#define FALSE 0
007
008
#define MAXSIZE 20 /* 存储空间初始分配量 */
009
010
typedef
int
Status;
/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
011
typedef
int
ElemType;
/* ElemType类型根据实际情况而定,这里假设为int */
012
013
typedef
struct
Node
014
{
015
ElemType data;
016
struct
Node *next;
017
}Node;
018
/* 定义LinkList */
019
typedef
struct
Node *LinkList;
020
021
/* 初始化顺序线性表 */
022
Status InitList(LinkList *L)
023
{
024
*L=(LinkList)
malloc
(
sizeof
(Node));
/* 产生头结点,并使L指向此头结点 */
025
if
(!(*L))
/* 存储分配失败 */
026
{
027
return
ERROR;
028
}
029
(*L)->next=NULL;
/* 指针域为空 */
030
031
return
OK;
032
}
033
034
/* 初始条件:顺序线性表L已存在。操作结果:返回L中数据元素个数 */
035
int
ListLength(LinkList L)
036
{
037
int
i=0;
038
LinkList p=L->next;
/* p指向第一个结点 */
039
while
(p)
040
{
041
i++;
042
p=p->next;
043
}
044
return
i;
045
}
046
047
/* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */
048
Status ClearList(LinkList *L)
049
{
050
LinkList p,q;
051
p=(*L)->next;
/* p指向第一个结点 */
052
while
(p)
/* 没到表尾 */
053
{
054
q=p->next;
055
free
(p);
056
p=q;
057
}
058
(*L)->next=NULL;
/* 头结点指针域为空 */
059
return
OK;
060
}
061
062
/* 初始条件:顺序线性表L已存在 */
063
/* 操作结果:依次对L的每个数据元素输出 */
064
Status ListTraverse(LinkList L)
065
{
066
LinkList p=L->next;
067
while
(p)
068
{
069
visit(p->data);
070
p=p->next;
071
}
072
printf
(
"\n"
);
073
return
OK;
074
}
075
076
Status visit(ElemType c)
077
{
078
printf
(
"-> %d "
,c);
079
return
OK;
080
}
081
082
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
083
/* 操作结果:用e返回L中第i个数据元素的值 */
084
Status GetElem(LinkList L,
int
i,ElemType *e)
085
{
086
int
j;
087
LinkList p;
/* 声明一结点p */
088
p = L->next;
/* 让p指向链表L的第一个结点 */
089
j = 1;
/* j为计数器 */
090
while
(p && j < i)
/* p不为空或者计数器j还没有等于i时,循环继续 */
091
{
092
p = p->next;
/* 让p指向下一个结点 */
093
++j;
094
}
095
if
( !p || j>i )
096
return
ERROR;
/* 第i个元素不存在 */
097
*e = p->data;
/* 取第i个元素的数据 */
098
return
OK;
099
}
100
101
/* 初始条件:顺序线性表L已存在 */
102
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
103
/* 若这样的数据元素不存在,则返回值为0 */
104
int
LocateElem(LinkList L,ElemType e)
105
{
106
int
i=0;
107
LinkList p=L->next;
108
while
(p)
109
{
110
i++;
111
if
(p->data==e)
/* 找到这样的数据元素 */
112
return
i;
113
p=p->next;
114
}
115
116
return
0;
117
}
118
119
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */
120
void
CreateListHead(LinkList *L,
int
n)
121
{
122
LinkList p;
123
int
i;
124
srand
(
time
(0));
/* 初始化随机数种子 */
125
*L = (LinkList)
malloc
(
sizeof
(Node));
126
(*L)->next = NULL;
/* 先建立一个带头结点的单链表 */
127
for
(i=0; i < n; i++)
128
{
129
p = (LinkList)
malloc
(
sizeof
(Node));
/* 生成新结点 */
130
p->data =
rand
()%100+1;
/* 随机生成100以内的数字 */
131
p->next = (*L)->next;
132
(*L)->next = p;
/* 插入到表头 */
133
}
134
}
135
136
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */
137
void
CreateListTail(LinkList *L,
int
n)
138
{
139
LinkList p,r;
140
int
i;
141
srand
(
time
(0));
/* 初始化随机数种子 */
142
*L = (LinkList)
malloc
(
sizeof
(Node));
/* L为整个线性表 */
143
r=*L;
/* r为指向尾部的结点 */
144
for
(i=0; i < n; i++)
145
{
146
p = (Node *)
malloc
(
sizeof
(Node));
/* 生成新结点 */
147
p->data =
rand
()%100+1;
/* 随机生成100以内的数字 */
148
r->next=p;
/* 将表尾终端结点的指针指向新结点 */
149
r = p;
/* 将当前的新结点定义为表尾终端结点 */
150
}
151
r->next = NULL;
/* 表示当前链表结束 */
152
}
153
154
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */
155
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
156
Status ListInsert(LinkList *L,
int
i,ElemType e)
157
{
158
int
j;
159
LinkList p,s;
160
p = *L;
/* 声明一个结点 p,指向头结点 */
161
j = 1;
162
while
(p && j < i)
/* 寻找第i个结点 */
163
{
164
p = p->next;
165
++j;
166
}
167
if
(!p || j > i)
168
return
ERROR;
/* 第i个元素不存在 */
169
s = (LinkList)
malloc
(
sizeof
(Node));
/* 生成新结点(C语言标准函数) */
170
s->data = e;
171
s->next = p->next;
/* 将p的后继结点赋值给s的后继 */
172
p->next = s;
/* 将s赋值给p的后继 */
173
return
OK;
174
}
175
176
/* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */
177
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
178
Status ListDelete(LinkList *L,
int
i,ElemType *e)
179
{
180
int
j;
181
LinkList p,q;
182
p = *L;
183
j = 1;
184
while
(p->next && j < i)
/* 遍历寻找第i个元素 */
185
{
186
p = p->next;
187
++j;
188
}
189
if
(!(p->next) || j > i)
190
return
ERROR;
/* 第i个元素不存在 */
191
q = p->next;
192
p->next = q->next;
/* 将q的后继赋值给p的后继 */
193
*e = q->data;
/* 将q结点中的数据给e */
194
free
(q);
/* 让系统回收此结点,释放内存 */
195
return
OK;
196
}
197
198
/* 单链表反转/逆序 */
199
Status ListReverse(LinkList L)
200
{
201
LinkList current,pnext,prev;
202
if
(L == NULL || L->next == NULL)
203
return
L;
204
current = L->next;
/* p1指向链表头节点的下一个节点 */
205
pnext = current->next;
206
current->next = NULL;
207
while
(pnext)
208
{
209
prev = pnext->next;
210
pnext->next = current;
211
current = pnext;
212
pnext = prev;
213
}
214
//printf("current = %d,next = %d \n",current->data,current->next->data);
215
L->next = current;
/* 将链表头节点指向p1 */
216
return
L;
217
}
218
219
Status ListReverse2(LinkList L)
220
{
221
LinkList current, p;
222
223
if
(L == NULL)
224
{
225
return
NULL;
226
}
227
current = L->next;
228
while
(current->next != NULL)
229
{
230
p = current->next;
231
current->next = p->next;
232
p->next = L->next;
233
L->next = p;
234
ListTraverse(L);
235
printf
(
"current = %d, \n"
, current -> data);
236
}
237
return
L;
238
}
239
240
int
main()
241
{
242
LinkList L;
243
Status i;
244
int
j,k,pos,value;
245
char
opp;
246
ElemType e;
247
248
i=InitList(&L);
249
printf
(
"链表L初始化完毕,ListLength(L)=%d\n"
,ListLength(L));
250
251
printf
(
"\n1.整表创建(头插法) \n2.整表创建(尾插法) \n3.遍历操作 \n4.插入操作"
);
252
printf
(
"\n5.删除操作 \n6.获取结点数据 \n7.查找某个数是否在链表中 \n8.置空链表"
);
253
printf
(
"\n9.链表反转逆序"
);
254
printf
(
"\n0.退出 \n请选择你的操作:\n"
);
255
while
(opp !=
'0'
){
256
scanf
(
"%c"
,&opp);
257
switch
(opp){
258
case
'1'
:
259
CreateListHead(&L,10);
260
printf
(
"整体创建L的元素(头插法):\n"
);
261
ListTraverse(L);
262
printf
(
"\n"
);
263
break
;
264
265
case
'2'
:
266
CreateListTail(&L,10);
267
printf
(
"整体创建L的元素(尾插法):\n"
);
268
ListTraverse(L);
269
printf
(
"\n"
);
270
break
;
271
272
case
'3'
:
273
ListTraverse(L);
274
printf
(
"\n"
);
275
break
;
276
277
case
'4'
:
278
printf
(
"要在第几个位置插入元素?"
);
279
scanf
(
"%d"
,&pos);
280
printf
(
"插入的元素值是多少?"
);
281
scanf
(
"%d"
,&value);
282
ListInsert(&L,pos,value);
283
ListTraverse(L);
284
printf
(
"\n"
);
285
break
;
286
287
case
'5'
:
288
printf
(
"要删除第几个元素?"
);
289
scanf
(
"%d"
,&pos);
290
ListDelete(&L,pos,&e);
291
printf
(
"删除第%d个元素成功,现在链表为:\n"
, pos);
292
ListTraverse(L);
293
printf
(
"\n"
);
294
break
;
295
296
case
'6'
:
297
printf
(
"你需要获取第几个元素?"
);
298
scanf
(
"%d"
,&pos);
299
GetElem(L,pos,&e);
300
printf
(
"第%d个元素的值为:%d\n"
, pos, e);
301
printf
(
"\n"
);
302
break
;
303
304
case
'7'
:
305
printf
(
"输入你需要查找的数:"
);
306
scanf
(
"%d"
,&pos);
307
k=LocateElem(L,pos);
308
if
(k)
309
printf
(
"第%d个元素的值为%d\n"
,k,pos);
310
else
311
printf
(
"没有值为%d的元素\n"
,pos);
312
printf
(
"\n"
);
313
break
;
314
315
case
'8'
:
316
i=ClearList(&L);
317
printf
(
"\n清空L后:ListLength(L)=%d\n"
,ListLength(L));
318
ListTraverse(L);
319
printf
(
"\n"
);
320
break
;
321
322
case
'9'
:
323
ListReverse2(L);
324
printf
(
"\n反转L后\n"
);
325
ListTraverse(L);
326
printf
(
"\n"
);
327
break
;
328
329
case
'0'
:
330
exit
(0);
331
}
332
}
333
334
}
http://www.nowamagic.net/librarys/veda/detail/2241
延伸阅读
此文章所在专题列表如下:
- 第01话:线性表的概念与定义
- 第02话:线性表的抽象数据类型ADT定义
- 第03话:线性表的顺序存储结构
- 第04话:线性表的初始化
- 第05话:线性表的遍历、插入操作
- 第06话:判断线性表是否为空与置空操作
- 第07话:线性表的查找操作
- 第08话:线性表删除某个元素
- 线性表顺序存储的优缺点
- 线性表链式存储结构的由来与基本概念
- 单链表的头指针、头结点与首元结点
- 单链表的结构体定义与声明
- 单链表的初始化
- 单链表的插入与遍历操作
- 单链表的删除某个元素的操作
- 获取单链表中的指定位置的元素
- 查找某数在单链表中的位置
- 用头插法实现单链表整表创建
- 用尾插法实现单链表整表创建
- 将单链表重置为空表
- 单链表反转/逆序的两种方法
- 单链表反转/逆序的第三种方法
- 求单链表倒数第N个数
- 用标尺法快速找到单链表的中间结点
- 如何判断链表是否有环的存在
- 单链表建环,无环链表变有环
- 删除单链表中的重复元素
0 0
- 单链表反转/逆序的两种方法
- 单链表反转/逆序的两种方法
- 单链表反转/逆序的两种方法
- 单链表反转/逆序的两种方法
- 单链表反转的两种方法
- 单链表反序,逆序的两种方法
- 单链表的反转/逆序的三种方法
- 单链表反转/逆序的三种方法(整理)
- (28)单链表的两种反转方法
- C#反转字符串的两种方法
- 将字符串反转的两种方法
- 三位数反转的两种方法
- 字符串反转的两种方法
- 一种简单的单链表逆序、反转的方法
- 求逆序对数的两种方法
- 两种方法求单链表逆序
- 单链表的两种逆序算法
- 链表反转的两种实现方法
- 理解javascript闭包和C++闭包
- prepare 预编译SQL:Mysql
- 黑马程序员——多线程
- 基于MapReduce的连接查询算法
- testlink和mantis集成
- 单链表反转/逆序的两种方法
- 数据库(增、删、改、查)
- 杭电acm--2051
- [LeetCode]Move Zeroes
- 几种GC内存回收机制概要
- jQeury插件 - 图片剪切器 (jQuery.jCutter)
- mysql预处理学习和总结
- 《Linux内核设计与实现》读书笔记(七)- 中断处理
- 周记 - 9.20