总结一些常见的链表问题。以下例子都是单向无头结点链表,部分带环。
1.从尾到头打印链表元素
即逆序打印链表,可以创建一个新的链表,每次找出原来链表的最后一个元素头插到新链表里,再打印出新链表,但是这种办法太麻烦而且效率低。还可以采用递归的思路,在打印当前元素之前递归调用打印函数,递归出口为指针为NULL,每调用一次,传进来的参数就往链表下一个元素移动。下图为3个元素时例子:
71 void LinkReversePrint(LinkList* head)
72 {
73 if(head == NULL){
74 // 空链表,直接返回
75 return;
76 }
77 LinkListReversePrint(head->next); // 递归调用
78 printf("[%c|%p]",head->data,head);
79 }
2.删除链表的非尾结点(不能遍历链表)
单向链表要对一个结点进行操作,通常需要遍历链表,找到当前结点的前一个,更改前一个结点的 next 指针,但是这里限制不能遍历结点,可以换一个思路,交换要删除结点和后一个结点的值,这样就可以删除后一个结点等同于删除指定结点,思路如下图所示:
83 void LinkEraserNodeNotTail(LinkList* pos)
84 {
85 if(pos == NULL){
86 return;
87 }
88 LinkList* to_delete = pos->next; // 保存要删除结点
89 pos->next = to_delete->next; // 交换 next
90 pos->data = to_delete->data;
91 DestroyNode(to_delete);
92 return;
93 }
3.单链表实现约瑟夫环
约瑟夫环是一个环,这个环遵守某种规则,每次淘汰一个成员,直到最后剩下一个成员为止。用首尾相连的单向环链表来实现,约定一个数字,开始计数,每逢这个数字就删除当前结点,然后下一个结点重新开始计数,知道剩下最后一个结点为止。
96 LinkList* JosephCircle(LinkList* head, size_t out)
97 {
98 if(head == NULL){
99 // 空链表
100 return NULL;
101 }
102 if(out == 0){ // 未约定,直接返回
103 return NULL;
104 }
105 LinkList* cur = head;
106 while(cur->next != cur){ // 剩一个结点时跳出循环
107 size_t count = 1;
108 for(; count < out; count++){
109 cur = cur->next;
110 }
111 LinkEraserNodeNotTail(cur); // 调用上一个函数(删除非尾结点)
112 }
113 return cur;
114 }
测试条件: 链表 a,b,c,d,e,f,g,h,i,j 约定为 4
4.逆置反转单链表(不允许创建新链表)
(1)方法一:创建三个指针 pre、cur、next,pre 指向头结点,cur 指向第二个,
next指向 cur 的 next 先保存 cur 的下一个操作结点,
让 cur 的 next 指向 pre ,这样就完成了 b 和 a 的逆置,cur 回到 next
,更新 next 指针,保存下一个结点。知道 cur 指向空时,最后一个结点也就逆置了。如下图所示:
117 void LinkListReverse1(LinkList** head)
118 {
119 if(head == NULL || *head == NULL){
120 return;
121 }
122 LinkList* pre = *head;
123 LinkList* cur = pre->next;
124 pre->next = NULL; // 逆置后的尾结点,设置尾结点的 next 为 NULL ,这里是 a 结点
125 while(cur != NULL){
126 LinkList* next = cur->next;
127 cur->next = pre;
128 pre = cur;
129 cur = next;
130 }
131 *head = pre;
132 return;
133 }
(2)方法二:
将头指针后的结点,头插到头结点之前,移动头结点,和方法一的思路是差不多的,操作有区别。
135 void LinkListReverse2(LinkList** head)
136 {
137 if(head == NULL || *head == NULL){
138 return;
139 }
140 LinkList* cur = *head;
141 while(cur->next != NULL){
142 LinkList* to_move = cur->next; // 拿出头结点后边的元素插入到头节点之前
143 cur->next = to_move->next; // 保存要移动结点的 next
144 to_move->next = *head; // cur的next已经更改,移动的结点需要指向头指针
145 *head = to_move; // 移动头结点
146 }
147 cur->next = NULL; // 链表结尾置空
148 return;
149 }
5.单链表排序(冒泡排序,升序)
n 个元素,冒泡排序一趟排出一个元素,第一趟比较 n-1 次,得出最大值,再进行第二趟比较,最后一个元素已经确定了,第二趟就比第一趟少比较一次,比较 n 趟。链表冒泡排序原理是一样的,但是得注意,需要创建一个尾变量指针,移动尾指针的位置,减少比较次数。
152 void LinkListBubbleSort(LinkList* head)
153 {
154 if(head == NULL){
155 return;
156 }
157 LinkList* count = head; // 趟数
158 LinkList* tail = NULL; // 次数
159 for(; count != NULL; count = count->next){
160 LinkList* cur = head;
161 for(; cur->next != tail; cur = cur->next){
162 if(cur->data > cur->next->data){
163 LinkListType pos = cur->data;
164 cur->data = cur->next->data;
165 cur->next->data = pos;
166 }
167 }
168 tail = cur; // 更新尾指针
169 }
170 }
6.合并两个有序链表,合并后依然有序
定义两个指针,比较两个元素的大小,较小的元素放到新链表中,然后小指针向后移动,再次比较,直到一个链表为空。
173 LinkList* LinkListMerge(LinkList* head1, LinkList* head2)
174 {
175 if(head1 == NULL){ // 一个链表为空,返回另一个链表
176 if(head2 == NULL){
177 return NULL;
178 }
179 return head2;
180 }else if(head2 == NULL){
181 return head1;
182 }
183 LinkList* cur1 = head1;
184 LinkList* cur2 = head2;
185 LinkList* new_head = NULL;
186 LinkList* new_tail = NULL;
187 while(cur1 != NULL && cur2 != NULL){
188 if(cur1->data < cur2->data){
189 if(new_head == NULL){
190 new_head = cur1;
191 new_tail = cur1;
192 }else{
193 new_tail->next = cur1;
194 new_tail = new_tail->next;
195 }
196 cur1 = cur1->next;
197 }else{
198 if(new_head == NULL){
199 new_head = cur2;
200 new_tail = cur2;
201 }else{
202 new_tail->next = cur2;
203 new_tail = new_tail->next;
204 }
205 cur2 = cur2->next;
206 }
207 }
208 if(cur1 == NULL){
209 new_tail->next = cur2;
210 }else{
211 new_tail->next = cur1;
212 }
213 return new_head;
214 }
7.查找单链表的中间结点(只遍历一次链表)
定义快慢指针,快指针每次走两步,慢指针每次走一步,快指针走到链表结尾的时候,满指针刚好走到一半。
217 LinkList* LinkFindMidNode(LinkList* head)
218 {
219 if(head == NULL){
220 // 空链表
221 return NULL;
222 }
223 LinkList* fast = head;
224 LinkList* slow = head;
225 while(fast != NULL && fast->next != NULL){ // fast 可以为 NULL(退出条件) 要保证 fast->next 的合法性,走两步
226 fast = fast->next->next;
227 slow = slow->next;
228 }
229 return slow;
230 }
8.寻找单链表的倒数第 k 个结点(只能遍历一次链表)
类似于找中间结点,快慢指针,快指针先走 k 步,接下来快指针走一步,慢指针走一步,当快指针走到链表尾部,慢指针走到倒数第 k 个结点。
233 LinkList* LinkFindLastKNode(LinkList* head, size_t size)
234 {
235 if(head == NULL || size == 0){
236 return NULL;
237 }
238 size_t link_size = 0;
239 LinkList* cur = head;
240 while(cur != NULL){
241 link_size ++;
242 cur = cur->next;
243 }
244 if(size > link_size){
245 // 查找的大小超过链表最大个数,查找不到
246 return NULL;
247 }
248 LinkList* fast = head;
249 LinkList* slow = head;
250 size_t count = 0;
251 while(count++ < size){
252 fast = fast->next;
253 }
254 while(fast != NULL){
255 fast = fast->next;
256 slow = slow->next;
257 }
258 return slow;
259 }
9.删除链表的倒数第 k 个结点
先找到倒数第 k 个结点,删除该结点,可以遍历链表找到前一个结点进行删除,也可以交换该结点与后一个结点的值,再删除后一个结点,第二种效率更高一点。
262 void LinkEraserLastKNode(LinkList** head, size_t size)
263 {
264 if(head == NULL || size == 0){
265 // 非法输入
266 return;
267 }
268 if(*head == NULL){
269 // 空链表
270 return;
271 }
272 LinkList* to_delete = NULL;
273 LinkList* k_node = LinkFindLastKNode(*head,size);
274 if(k_node == NULL){
275 // 无删除元素
276 return;
277 }
278 if(k_node == *head){
279 to_delete = *head;
280 *head = k_node->next;
281 DestroyNode(to_delete);
282 }
283 to_delete = k_node->next;
284 k_node->data = to_delete->data;
285 k_node->next = to_delete->next;
286 DestroyNode(to_delete);
287 }
10.判断单链表是否带环?若带环,求环的长度,入口点,并计算算法的时间复杂度和空间复杂度
定义快慢两个指针,快指针一次走两步,慢指针一次一步,快指针要是为空说明无环,快慢指针要是能相遇说明带环,如果带环,从快慢指针相遇点标记,下一次走到相遇点就是环的长度,链表头部到环入口的位移等于环入口到相遇点位移的n(n>=1)倍,所以从头结点、相遇点开始走,两个指针相等时,为环入口点。
290 LinkList* LinkHasCircle(LinkList* head) // 判断是否带环 时间复杂度 O(n) 空间复杂度O(1)
291 {
292 if(head == NULL){
293 // 空链表
294 return;
295 }
296 LinkList* fast = head;
297 LinkList* slow = head;
298 while(fast != NULL && fast->next != NULL){ // 快指针走两步,不能越界
299 fast = fast->next->next;
300 slow = slow->next;
301 if(fast == slow){
302 return fast;
303 }
304 }
305 return NULL;
306 }
308 size_t LinkCircleLength(LinkList* head) // 求环的长度 时间复杂度 O(n) 空间复杂度O(1)
309 {
310 if(head == NULL){
311 // 空链表
312 return;
313 }
314 LinkList* meet = LinkHasCircle(head);
315 if(meet == NULL){
316 // 不带环
317 return 0;
318 }
319 LinkList* cur = meet->next;
320 size_t length = 1;
321 while(cur != meet){
322 length ++;
323 cur = cur->next;
324 }
325 return length;
326 }
328 LinkList* LinkCircleEnterNode(LinkList* head) // 求环的入口点 时间复杂度 O(n) 空间复杂度O(1)
329 {
330 if(head == NULL){
331 // 空链表
332 return NULL;
333 }
334 LinkList* meet = LinkHasCircle(head);
335 if(meet == NULL){
336 // 不带环
337 return NULL;
338 }
339 LinkList* cur = head;
340 while(cur != meet){
341 cur = cur->next;
342 meet = meet->next;
343 }
344 return cur;
345 }
无环
有环
11.判断两个链
表是否相交,若相交求交点(链表不带环)
两个链表如果相交,它们将会走到同一个终点, 分别定义两个指针从两个链表头部出发,走到尾部,相同则相交。相交求交点,计算两个链表的长度,长链表指针先走多出来的长度,然后长短链表的指针一起走,每走一步比较一次是否相等,相等时即为交点。
348 size_t LinkHasCross(LinkList* head1, LinkList* head2) //是否相交
349 {
350 if(head1 == NULL || head2 == NULL){
351 // 无交点,返回0
352 return 0;
353 }
354 LinkList* cur1 = head1;
355 LinkList* cur2 = head2;
356 while(cur1->next != NULL){ // 走到链表1的尾部
357 cur1 = cur1->next;
358 }
359 while(cur2->next != NULL){ // 走到链表2的尾部
360 cur2 = cur2->next;
361 }
362 if(cur1 == cur2){
363 return 1;
364 }
365 return 0;
366 }
368 LinkList* LinkCrossNode(LinkList* head1, LinkList* head2) // 求交点,相交返回交点地址
369 {
370 if(head1 == NULL || head2 == NULL){
371 return NULL;
372 }
373
374 LinkList* cur1 = head1;
375 size_t size1 = 0; // 计算链表长度
376 while(cur1->next != NULL){
377 size1 ++;
378 cur1 = cur1->next;
379 }
380
381 size_t size2 = 0;
382 LinkList* cur2 = head2;
383 while(cur2->next != NULL){
384 size2 ++;
385 cur2 = cur2->next;
386 }
387
388 cur1 = head1;
389 cur2 = head2;
390 if(size1 > size2){
391 size_t i = 0;
392 for(; i < (size1 - size2); i++){
393 cur1 = cur1->next;
394 }
395 }else{
396 size_t i = 0;
397 for(; i < (size2 - size1); i++){
398 cur2 = cur2->next;
399 }
400 }
401 while(cur1->next != cur2->next){
402 cur1 = cur1->next;
403 cur2 = cur2->next;
404 }
405 return cur1->next;
406 }
12.判断两个链表是否相交,若相交,求交点(链表可能带环)
思路与 11 题差不多,首先求两个链表的环入口点,(1)若都不带环,链表相交的判断和交点都如上。
(2)若一个带环,一个不带环,则肯定不相交。(3)若两个链表都带环,比较两个环入口点,① 两个环入口点相同,则两个链表相交,相交于环外,计算两个链表头部到环入口点的步数,长链表先走多出来的部分,然后长短链表一起走,相遇时即为交点。② 两个环入口点不相同,判断是否可以从一个环入口点走到另一个环入口点,可以的话,相交于环上,两个环入口点都是交点;不可以的话,不相交。
409 size_t LinkHasCross2(LinkList* head1, LinkList* head2) // 判断相交
410 {
411 if(head1 == NULL || head2 == NULL){
412 return 0;
413 }
414 LinkList* circle1 = LinkCircleEnterNode(head1); // 取两个链表的环入口点
415 LinkList* circle2 = LinkCircleEnterNode(head2);
416 if(circle1 == NULL && circle2 == NULL){ // 都为空,调用无环链表相交函数
417 return LinkHasCross(head1, head2);
418 }
419 if(circle1 == NULL || circle2 == NULL){ // 一个无环,一个有环,不相交,返回0
420 return 0;
421 }
422 if(circle1 == circle2){ // 相交返回1
423 return 1;
424 }
425 for(; circle1 != circle2; circle1 = circle2->next){
426 if(circle1 == circle2){
427 return 1;
428 }
429 }
430 return 0;
431 }
433 LinkList* LinkCrossNode2(LinkList* head1, LinkList* head2) // 求交点
434 {
435 if(head1 == NULL || head2 == NULL){
436 return NULL;
437 }
438 size_t ret = LinkHasCross2(head1, head2); // 判断是否相交
439 // 不相交,返回空
440 if(ret == 0){
441 return NULL;
442 }
443 // 相交分两种情况:1.环外相交,环入口点相同;2.环上相交,入口点不相同,取两个环的环入口点
444 LinkList* circle1 = LinkCircleEnterNode(head1);
445 LinkList* circle2 = LinkCircleEnterNode(head2);
446 // 入口点相同,以入口点为终点,两个指针相同为相交点
447 if(circle1 == circle2){
448 LinkList* cur1 = head1;
449 LinkList* cur2 = head2;
450 size_t s1 = LinkListSize(head1, circle1); // 求链表头部到环入口点的步数
451 size_t s2 = LinkListSize(head2, circle2);
452 if(s1 > s2){
453 size_t i = 0;
454 while(i < (s1 - s2)){
455 cur1 = cur1->next;
456 i++;
457 }
458 while(cur1 != cur2){ // 相等时为相交点
459 cur1 = cur1->next;
460 cur2 = cur2->next;
461 }
462 return cur1;
463 }
464 size_t i = 0;
465 while(i < (s2 - s1)){
466 cur2 = cur2->next;
467 i++;
468 }
469 while(cur2 != cur1){
470 cur1 = cur1->next;
471 cur2 = cur2->next;
472 }
473 return cur2;
474 }
475 return circle1;
476 }
478 size_t LinkListSize(LinkList* head, LinkList* pos) // 辅助函数,求步数
479 {
480 if(head == NULL || pos == NULL){ // 链表到指定 pos 位置的元素个数
481 // 空链表
482 return 0;
483 }
484 size_t size = 1;
485 LinkList* cur = head;
486 while(cur->next != pos){
487 size ++;
488 cur = cur->next;
489 }
490 return size;
491 }
测试条件:(3)② 链表1:a b c d g h i j ,链表2:e f g h i j,交点为 g,环入口点为 h
13.求两个已排序单链表中相同的数据,默认升序
创建一个新链表,将两个链表的相同数据保存到新链表中。两个就链表已经有序,定义两个指针分别从旧链表头部开始比较,相同的话放入新链表,不同的话,较小数据的指针向后移,直到一个链表为空,新链表中就保存了相同数据。
598 LinkList* UnionSet(LinkList* head1, LinkList* head2)
599 {
600 if(head1 == NULL || head2 == NULL){
601 return NULL;
602 }
603 LinkList* new_head = NULL;
604 LinkList* new_tail = NULL;
605 LinkList* cur1 = head1;
606 LinkList* cur2 = head2;
607 while(cur1 != NULL && cur2 != NULL){
608 if(cur1->data > cur2->data){
609 cur2 = cur2->next;
610 }
611 if(cur1->data < cur2->data){
612 cur1 = cur1->next;
613 }else{
614 LinkListType tmp = cur1->data;
615 if(new_head == NULL){
616 new_head = new_tail = CreatNode(tmp);
617 }else{
618 new_tail->next = CreatNode(tmp);
619 new_tail = new_tail->next;
620 }
621 cur1 = cur1->next;
622 cur2 = cur2->next;
623 }
624 }
625 return new_head;
626 }
14.复杂链表的复制,链表的每一个结点,有一个next指针指向下一个结点,
还有一个random指针指向链表中的随机结点或者NULL,返回复制后的链表
(1)方法一:创建新链表,复制旧链表的结点和 next 指针,先不管 random 指针,然后计算出旧链表每个结点 random 指针距离头结点的偏移量,根据偏移量修改新链表的 random 指针。
497 ComplexLink* CreatComplexNode(ComplexLinkListType value) // 创建新结点
498 {
499 ComplexLink* new_node = (ComplexLink*)malloc(sizeof(ComplexLink));
500 new_node->data = value;
501 new_node->next = NULL;
502 new_node->random = NULL;
503 return new_node;
504 }
506 size_t CountSteps(ComplexLink* head, ComplexLink* pos) // 计算结点random指针距头结点的步数
507 {
508 if(head == NULL || pos == NULL){
509 return 0;
510 }
511 size_t count = 0;
512 ComplexLink*cur = head;
513 while(cur != pos){
514 count ++;
515 cur = cur->next;
516 }
517 return count;
518 }
520 ComplexLink* MoveCopyRandom(ComplexLink* cur, size_t steps) // 复制每个结点的random指针
521 {
522 if(cur == NULL){
523 return;
524 }
525 size_t i = 0;
526 while(i < steps){
527 cur = cur->next;
528 i++;
529 }
530 return cur;
531 }
533 ComplexLink* ComplexLinkCopy(ComplexLink* head) // 复制复杂链表
534 {
535 if(head == NULL){
536 return NULL;
537 }
538 // 先不管random指针,拷贝原链表的结点
539 ComplexLink* new_head = CreatComplexNode(head->data);
540 ComplexLink* cur = head;
541 ComplexLink* new_cur = new_head;
542 while(cur->next != NULL){
543 new_cur->next = CreatComplexNode(cur->next->data);
544 new_cur = new_cur->next;
545 cur = cur->next;
546 }
547 cur = head;
548 new_cur = new_head;
549 // 设置新结点的random值
550 for(; cur != NULL; cur = cur->next, new_cur = new_cur->next){
551 if(cur->random == NULL){
552 new_cur->random = NULL;
553 }else{
554 size_t steps = CountSteps(head, cur->random);
555 ComplexLink* ret = MoveCopyRandom(new_head, steps);
556 new_cur->random = ret;
557 }
558 }
559 return new_head;
560 }
(2)方法二:在旧链表每个结点后面插入一个一样的结点,复制 random 指针,再将插入的点拆解出来,构成新链表。
563 ComplexLink* ComplexCopy2(ComplexLink* head)
564 {
565 if(head == NULL){
566 return NULL;
567 }
568 ComplexLink* cur = head;
569 for(; cur != NULL; cur = cur->next->next){ // 在原链表上插入结点,插入后 cur 指针需要走两步
570 ComplexLink* new = CreatComplexNode(cur->data);
571 new->next = cur->next;
572 cur->next = new;
573 }
574 for(cur = head; cur != NULL; cur = cur->next->next){ // 修改新结点的 random 指针
575 ComplexLink* new_cur = cur->next;
576 if(cur->random == NULL){
577 new_cur->random = NULL;
578 continue;
579 }
580 new_cur->random = cur->random->next;
581 }
582 ComplexLink* new_head = NULL;
583 ComplexLink* new_tail = NULL;
584 for(cur = head; cur != NULL; cur = cur->next){ // 拆除新结点,组成新链表
585 ComplexLink* to_move = cur->next; // 保存要拆结点,把原链表的 next 归位
586 cur->next = to_move->next;
587 if(new_head == NULL){
588 new_head = new_tail = to_move;
589 }else{
590 new_tail->next = to_move;
591 new_tail = new_tail->next;
592 }
593 }
594 return new_head;
595 }