比较两种思路的差异
在 2012年07月05日 那天写的 已经有 12799 次阅读了
感谢
参考或原文
前面我们大约把单链表 ADT 的基本操作都过了一遍,但是这还不够。单链表在面试与笔试中出现的几率很高,接下来我们再花点时间把常见的单链表面试题尽可能过一遍,彻底掌握单链表~
那开始我们的第一个面试题?不妨做做“单链表反转”,或者说“单链表
逆序”吧?还是基于前面的例子。
究竟要如何
反转呢?我们不妨拿一个例子来说明一下算法。
我先画一个单链表,这个单链表有4个元素。我的思路就是,每次把第二个元素提到最前面来。比如下面是第一次交换,我们先让头结点的next域指向结点a2,再让结点a1的next域指向结点a3,最后将结点a2的next域指向结点a1,就完成了第一次交换。
![](http://www.nowamagic.net/librarys/images/201209/2012_09_13_01.jpg)
[align=center]第一次交换[/align]
然后进行相同的交换将结点a3移动到结点a2的前面,然后再将结点a4移动到结点a3的前面就完成了反转。
![](http://www.nowamagic.net/librarys/images/201209/2012_09_13_02.jpg)
[align=center]第二次交换[/align]
![](http://www.nowamagic.net/librarys/images/201209/2012_09_13_03.jpg)
[align=center]第三次交换[/align]
思路有了,那就可以写代码了。这里我们需要额外的两个工作指针来辅助交换。这个下面的步骤慢慢理解下,结合图片。注意结点之间的关系要先断再连。
步骤:
定义当前结点 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;
函数设计如下:
02 | Status ListReverse(LinkList L) |
04 | LinkList current,pnext,prev; |
05 | if (L == NULL || L->next == NULL) |
07 | current = L->next; /* p1指向链表头节点的下一个节点 */ |
08 | pnext = current->next; |
13 | pnext->next = current; |
16 | printf ( "交换后:current = %d,next = %d \n" ,current->data,current->next->data); |
18 | //printf("current = %d,next = %d \n",current->data,current->next->data); |
19 | L->next = current; /* 将链表头节点指向p1 */ |
其实在你写函数的时候,我也写了个函数,也能运行。思路也差不多,不过你的current一直是表的第一个结点,我这里的current始终是首元结点的值,我的函数需要每次对pnext重新赋值。一会解释下。
01 | Status ListReverse2(LinkList L) |
10 | while
(current->next != NULL) |
13 | current->next = p->next; |
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。
这个是程序运行的结果。
02 | // 原链表,current = 68, pnext = 55,68指向18,55指向18,头结点指向55 |
03 | -> 68 -> 55 -> 18 -> 45 -> 41 -> 43 -> 5 -> 28 -> 80 -> 67 |
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 |
12 | -> 41 -> 45 -> 18 -> 55 -> 68 -> 43 -> 5 -> 28 -> 80 -> 67 |
14 | -> 43 -> 41 -> 45 -> 18 -> 55 -> 68 -> 5 -> 28 -> 80 -> 67 |
16 | -> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68 -> 28 -> 80 -> 67 |
18 | -> 28 -> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68 -> 80 -> 67 |
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 |
26 | -> 67 -> 80 -> 28 -> 5 -> 43 -> 41 -> 45 -> 18 -> 55 -> 68 |
最后附上完整代码,反转有两个函数。
方法1,current始终保持在第一位,pnext与prev遍历并完成交换。
方法2,current始终是原链表的第一个数,然后把pnext不断移动到首位。
008 | #define MAXSIZE 20 /* 存储空间初始分配量 */ |
010 | typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ |
011 | typedef int ElemType; /* ElemType类型根据实际情况而定,这里假设为int */ |
019 | typedef struct Node *LinkList; |
022 | Status InitList(LinkList *L) |
024 | *L=(LinkList) malloc ( sizeof (Node)); /* 产生头结点,并使L指向此头结点 */ |
025 | if (!(*L)) /* 存储分配失败 */ |
029 | (*L)->next=NULL; /* 指针域为空 */ |
034 | /* 初始条件:顺序线性表L已存在。操作结果:返回L中数据元素个数 */ |
035 | int ListLength(LinkList L) |
038 | LinkList p=L->next; /* p指向第一个结点 */ |
047 | /* 初始条件:顺序线性表L已存在。操作结果:将L重置为空表 */ |
048 | Status ClearList(LinkList *L) |
051 | p=(*L)->next; /* p指向第一个结点 */ |
058 | (*L)->next=NULL; /* 头结点指针域为空 */ |
063 | /* 操作结果:依次对L的每个数据元素输出 */ |
064 | Status ListTraverse(LinkList L) |
076 | Status visit(ElemType c) |
078 | printf ( "-> %d " ,c);
原文地址:http://www.nowamagic.net/librarys/veda/detail/2241 |
082 | /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */ |
083 | /* 操作结果:用e返回L中第i个数据元素的值 */ |
084 | Status GetElem(LinkList L, int
i,ElemType *e) |
087 | LinkList p; /* 声明一结点p */ |
088 | p = L->next; /* 让p指向链表L的第一个结点 */ |
090 | while
(p && j < i) /* p不为空或者计数器j还没有等于i时,循环继续 */ |
092 | p = p->next; /* 让p指向下一个结点 */ |
096 | return
ERROR; /* 第i个元素不存在 */ |
097 | *e = p->data; /* 取第i个元素的数据 */ |
102 | /* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */ |
103 | /* 若这样的数据元素不存在,则返回值为0 */ |
104 | int LocateElem(LinkList L,ElemType e) |
111 | if (p->data==e) /* 找到这样的数据元素 */ |
119 | /* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */ |
120 | void CreateListHead(LinkList *L, int n) |
124 | srand ( time (0)); /* 初始化随机数种子 */ |
125 | *L = (LinkList) malloc ( sizeof (Node)); |
126 | (*L)->next = NULL; /* 先建立一个带头结点的单链表 */ |
129 | p = (LinkList) malloc ( sizeof (Node)); /* 生成新结点 */ |
130 | p->data = rand ()%100+1; /* 随机生成100以内的数字 */ |
131 | p->next = (*L)->next; |
132 | (*L)->next = p; /* 插入到表头 */ |
136 | /* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */ |
137 | void CreateListTail(LinkList *L, int n) |
141 | srand ( time (0)); /* 初始化随机数种子 */ |
142 | *L = (LinkList) malloc ( sizeof (Node)); /* L为整个线性表 */ |
143 | r=*L; /* r为指向尾部的结点 */ |
146 | p = (Node *) malloc ( sizeof (Node)); /* 生成新结点 */ |
147 | p->data = rand ()%100+1; /* 随机生成100以内的数字 */ |
148 | r->next=p; /* 将表尾终端结点的指针指向新结点 */ |
149 | r = p; /* 将当前的新结点定义为表尾终端结点 */ |
151 | r->next = NULL; /* 表示当前链表结束 */ |
154 | /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L), */ |
155 | /* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */ |
156 | Status ListInsert(LinkList *L, int
i,ElemType e) |
160 | p = *L; /* 声明一个结点 p,指向头结点 */ |
162 | while
(p && j < i) /* 寻找第i个结点 */ |
168 | return
ERROR; /* 第i个元素不存在 */ |
169 | s = (LinkList) malloc ( sizeof (Node)); /* 生成新结点(C语言标准函数) */ |
171 | s->next = p->next; /* 将p的后继结点赋值给s的后继 */ |
172 | p->next = s; /* 将s赋值给p的后继 */ |
176 | /* 初始条件:顺序线性表L已存在,1≤i≤ListLength(L) */ |
177 | /* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */ |
178 | Status ListDelete(LinkList *L, int
i,ElemType *e) |
184 | while
(p->next && j < i) /* 遍历寻找第i个元素 */ |
189 | if
(!(p->next) || j > i) |
190 | return
ERROR; /* 第i个元素不存在 */ |
192 | p->next = q->next; /* 将q的后继赋值给p的后继 */ |
193 | *e = q->data; /* 将q结点中的数据给e */ |
194 | free (q); /* 让系统回收此结点,释放内存 */ |
199 | Status ListReverse(LinkList L) |
201 | LinkList current,pnext,prev; |
202 | if (L == NULL || L->next == NULL) |
204 | current = L->next; /* p1指向链表头节点的下一个节点 */ |
205 | pnext = current->next; |
206 | current->next = NULL; |
210 | pnext->next = current; |
214 | //printf("current = %d,next = %d \n",current->data,current->next->data); |
215 | L->next = current; /* 将链表头节点指向p1 */ |
219 | Status ListReverse2(LinkList L) |
228 | while
(current->next != NULL) |
231 | current->next = p->next; |
235 | printf ( "current = %d, \n" , current -> data); |
249 | printf ( "链表L初始化完毕,ListLength(L)=%d\n" ,ListLength(L)); |
251 | printf ( "\n1.整表创建(头插法) \n2.整表创建(尾插法) \n3.遍历操作 \n4.插入操作" ); |
252 | printf ( "\n5.删除操作 \n6.获取结点数据 \n7.查找某个数是否在链表中 \n8.置空链表" ); |
253 | printf ( "\n9.链表反转逆序" ); |
254 | printf ( "\n0.退出 \n请选择你的操作:\n" ); |
259 | CreateListHead(&L,10); |
260 | printf ( "整体创建L的元素(头插法):\n" ); |
266 | CreateListTail(&L,10); |
267 | printf ( "整体创建L的元素(尾插法):\n" ); |
278 | printf ( "要在第几个位置插入元素?" ); |
280 | printf ( "插入的元素值是多少?" ); |
282 | ListInsert(&L,pos,value); |
290 | ListDelete(&L,pos,&e); |
291 | printf ( "删除第%d个元素成功,现在链表为:\n" , pos); |
297 | printf ( "你需要获取第几个元素?" ); |
300 | printf ( "第%d个元素的值为:%d\n" , pos, e); |
305 | printf ( "输入你需要查找的数:" ); |
309 | printf ( "第%d个元素的值为%d\n" ,k,pos); |
311 | printf ( "没有值为%d的元素\n" ,pos); |
317 | printf ( "\n清空L后:ListLength(L)=%d\n" ,ListLength(L)); |