1、从尾到头来打印单链表
在以前我们学习C语言的时候,我们都比较熟悉的就是逆序字符、汉诺塔、还有就是实现斐波那契数列的时候我们当时所用的方法就是递归,在这类问题的实现过,我们就对递归这个算法换是比较熟悉的,今天我们就来用递归的思想来实现从尾到头来打印链表。
那么我们刚开始拿到这个题的时候有点困惑,一时半会没想到解决的办法,最后才想到了递归这个方法。我最后得出逆序打印链表主要有三种做法:
1、用递归的方法进行实现,这个方法虽然比较好用,但是不容易想到,代码相对于来说是比较简单的。
2、定义一个新的链表,然后在将第一个元素放入这个新的链表中,然后调用头插的方法,最后能够将链表实现逆序。
3、第三种方法是实现的是定义三指针的方法:
每次都采用这种调用头插的方法:依次往后进行每次调用头插之后让三个指针都往后进行遍历,然后在调用头插这样依次进行循环,直到所有的元素都进行逆序完毕。
void LinkListReversePrint(LinkNode* head) // 逆序打印单链表
{
if (head == NULL){
return NULL;
}
LinkListReversePrint(head->next);//使用递归的思想来实现
printf("[%c]|[%p]", head->data, head);
}
2、前面我们已经实现了在pos之前插入一个元素
现在我们就来继续实现下在pos之前去插入元素,条件是在不允许遍历链表的前提下,我们先来说说实现的思想就是我们之前也实现过在pos之后去插入一个元素,我们就将未知的问题就转化为已知的问题,先来在pos之后去插入一个元素,然后将其新插入的元素和pos的值最后激进型交换我们就能实现在pos前去插入一个元素。
我们现在就来用相关的图来演示下我们的思路:
具体的代码实现就如下所示:
void LinkListInsert2(PLinkNode* phead, LinkNode* pos, LinkNodeType value)
//在这里运用的主要的做法就是先调用后插,然后最后再交换两个位置的值
255 {
256 if(phead == NULL || pos==NULL)
257 {
258 return;
259 }
260 LinkListInsertAfter(phead,pos, value);
261 LinkNodeType ret = pos->data;
262 pos->data = pos->next->data;
263 pos->next->data = ret;
264 return;
265 }
3、单链表实现约瑟夫环
我们先来了解下一个比较有意思的故事:约瑟夫是犹太军队的一个将军,在反抗罗马的起义中,他所率领的军队被击溃,只剩下残余的部队40余人,他们都是宁死不屈的人,所以不愿投降做叛徒。一群人表决说要死,所以用一种策略来先后杀死所有人。
于是约瑟夫建议:每次由其他两人一起杀死一个人,而被杀的人的先后顺序是由抽签决定的,约瑟夫有预谋地抽到了最后一签,在杀了除了他和剩余那个人之外的最后一人,他劝服了另外一个没死的人投降了罗马。
我们这个规则是这么定的:
- 在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命。
- 按照如下规则去杀人:
- 所有人围成一圈
顺时针报数,每次报到q的人将被杀掉 被杀掉的人将从房间内被移走 然后从被杀掉的下一个人重新报数,继续报q,再清除,直到剩余一人
在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命。
在单链表实现约瑟夫环的过程就如下所示:
266 LinkNode* JosephCycle(LinkNode* head, size_t m)
267 //约瑟夫环
268 //主要实现的是三个步骤:构环,报数、删除
269 {
270 if(head==NULL){
271 return;
272 }
273 size_t count=0;
274 LinkNode* cur=head;
275 LinkNode* tmp= NULL;
276 while(cur->next!=cur){
277 count++;
278 cur=tmp;
279 cur=cur->next;
280 if(count==m)
281 {
282 printf("[%c]\n",tmp->data);
283 LinkListErase2(&tmp,tmp);
284 count=0;
285 }
286 }
287 return cur;
288 }
289
4、单链表的逆置/反转
在单链表的逆置反转中主要有两种做法:
方法一:
代码的实现如下所示:
//这里主要实现的是通过先修改头指针的指向,进行实现的
void LinkListReverse(LinkNode** head)
293 {
294 if(head==NULL){
295 return;
296 }
297 if((*head)->next==NULL){
298 return;
299 }
300 LinkNode* cur=*head;
301 LinkNode* to_erase=NULL;
302 while(cur->next!=NULL)
303 {
304 to_erase=cur->next;
305 cur->next=to_erase->next;
306 LinkListPushFront(head,to_erase->data);
307 Destroynode(to_erase);
308 }
309 }
方法二:
这种方法主要是定义三个指针的方法,进行逆置的,相对来说有点复杂,我们有时候是很不容易去想到的
//通过定义三个指针的方法
314 void LinkListReverse2(LinkNode** head){
315 if(head==NULL){
316 return;
317 }
318 if((*head)->next==NULL){
319 return;
320 }
321 LinkNode* pre=*head;
322 LinkNode* cur=pre->next;
323 LinkNode* pro=NULL;//pro刚开始必须初始化为空指针,因为每次执行完,所指的内容试不同的
324 while(pre->next!=NULL){
325 pro=cur->next;
326 pre->next=pro;
327 cur->next=pre;
328 cur->next=*head;
329 *head=cur;
330 cur=pre->next;
331 }
332 }
5、单链表的冒泡排序
冒泡排序之前我们已经写过很多的比较类似的这种方法实现的不同的要求。
void swp(LinkNodeType *p1,LinkNodeType *p2){//刚开始我在这没有定义成指针类型,最后才明白
形参是实参的一份临时拷贝,必须用指针进行传里面的内容
335
336 LinkNodeType ret=*p1;
337 *p1=*p2;
338 *p2=ret;
339 }
340
341 void LinkListBubbleSort(LinkNode** head)
342 // 单链表的冒泡排序
343 {
344 if(head==NULL){
345 return;
346 }
347
348 LinkNode* cur=*head;//趟数
349 LinkNode* tmp=*head;//次数
350 LinkNode* tal=NULL;//尾指针
351 for(cur=*head;cur->next!=NULL;cur=cur->next){
352 for(tmp=*head;tmp->next!=NULL;tmp=tmp->next){
353 if((tmp->data)>(tmp->next->data)){
354 swp(&(tmp->data),&(tmp->next->data));
355 }
356 }
357 }
358 tmp=tal;
359 }
360
361
6、将两个有序的链表合并成一个链表
首先我们定义两个指针指向各自的链表,在定义new_head指向指针的头部,定义new_tail指向尾部,cur1和cur2进行比较,将小的值放入到新链表中。
程序需要考虑如下情况:两个链表(函数参数)都有可能为空,可能连个链表其中一个为空,可能两个链表相等,也可能其中一个链表已经遍历完了,另一个链表还有很多元素,所以需要首先处理特殊情况。
1、非递归实现
LinkNode* LinkListMerge(LinkNode* head1, LinkNode* head2)// 将两个有序链表, 合并成一个有序链表
{
//首先处理特殊情况 两个链表相等;l1为空链表,l2不为空
//l1不为空,l2为空链表
//先遍历一次让new_head指向最小的头指针,然后cur1、cur2进行往下走
LinkNode* cur1=head1;
LinkNode* cur2=head2;
if(head1==head2){
return head1;
}
if((head1!=NULL)&&(head2=NULL)){
return head1;
}
if((head1=NULL)&&(head2!=NULL)){
return head2;
}
LinkNode* new_head=NULL;
LinkNode* new_tail=NULL;
if((cur1->data)>(cur2->data)){
new_head=cur2;
cur2=cur2->next;
}
else
{
new_head=cur1;
cur1=cur1->next;
}
new_tail=new_head;
while((cur1!=NULL)&&(cur2!=NULL)){
if((cur1->data)<(cur2->data)){
new_tail->next=cur1;
new_tail=cur1;//new_tail是往后遍历的指针
cur1=cur1->next;
}
else{
new_tail->next=cur2;
new_tail=cur2;
cur2=cur2->next;
}
}
if(cur1!=NULL){
new_tail->next=cur1;
}
else
{
new_tail->next=cur2;
}
return new_head;
}
7、找到链表最中间的节点
该题目可算是比较有新意了,我们今天使用的方法是采用快慢指针的方法,之前我们在学习C语言的时候已经接触到了,今天我们就来看看采用这种方法进行解决问题,我们定义一个快指针,一个慢指针,先让慢指针走一步,快指针走两步,当快指针走到最后一个元素的时候,刚好慢指针就走到了最中间的位置,因此也就实现了我们所期望的结果。
LinkNode* FindMidNode(LinkNode* head)//找到最中间的节点
415 {
416 if(head==NULL){
417 return NULL;
418 }
419 LinkNode* fast=head;
420 LinkNode* slow=head;
421 while((fast!=NULL)&&(fast->next)!=NULL){//fast走的快,当fast满足条件时,slow肯定满 足条件
422 slow=slow->next;
423 fast=fast->next->next;
424 }
425 return slow;
426 }
8、找到倒数第k个结点,允许只遍历一次
首先我们的思路也是刚才的采用快慢指针的方法,首先让快指针走k步,然后就让slow、fast同时走,知道fast为空时,slow所指的位置就是倒数第k个结点
LinkNode* FindLastKNode(LinkNode* head, size_t K)//找到倒数第 K 个节点.
431 {
432 if(head==NULL){
433 return;
434 }
435 LinkNode* fast=head;
436 LinkNode* slow=head;
437 int k=2;//k必须要初始化确定的数值
438 int i=0;
439 for(i=0;i<k;i++){//先让快指针走k步
440 if(fast==NULL){
441 break;
442 }
443 fast=fast->next;
444 }
445 while(fast!=NULL){//快指针走k步之后,两个指针同时走
446 slow=slow->next;
447 fast=fast->next;
448 }
449 return slow;
450 }
451
9、删除倒数k个结点
我们先来判断该节点是不是和链表的长度进行比较,在特殊位置的话就采用特殊位置的删除的方法,当k等于所要删除的链表的长度,我们就调用头删,要是超过链表的长度我们就返回,然后再对这些位置进行删除。
void EraseLastKNode(LinkNode** head, size_t K)//删除倒数第K个节点
454 {
455 if(head==NULL){
456 return;
457 }
458 if(*head==NULL){
459 return;
460 }
461 size_t len=LinkListSize(*head);
462 int k=3;//注意要进行初始化,这样才能找到要删的位置
463 if(k>len){
464 return;
465 }
466 if(k==len){
467 LinkListPopBack(head);
468 }
469 int i=0;
470 LinkNode* cur=*head;
471 for(i=0;i<len-(k+1);i++){//先让cur找到k结点前面的结点,然后我们所要删除的结点就是
cur->next,这样再进行删除。
472 cur=cur->next;
473 }
474 LinkNode* to_kill=cur->next;
475 cur->next=to_kill->next;
476 Destroynode(to_kill);
477 }
10、判定单链表是否带环. 如果带环返回1
在判断链表是否代还我们采用的方法也是前面我们所用到的方法,那就是定义两个指针,快指针和慢指针,我们让快指针走两步,慢指针走一步,只要链表带环,两个指针总会相遇的。
int HasCycle(LinkNode* head)// 判定单链表是否带环. 如果带环返回1
480 {
481 if(head==NULL){
482 return;
483 }
484 LinkNode* fast=head;
485 LinkNode* slow=head;
486 while((fast!=NULL)&&(fast->next!=NULL)){
487 fast=fast->next->next;
488 slow=slow->next;
489 if(fast==slow){
490 return 1;
491 }
492 }
493 return 0;
494 }
11、如果链表带环,求环的长度
我们定义两个指针fast和slow两个指针,只要我们知道fast和slow相遇的地点,然后我们再定义一个指针,绕环一周之后,我们得到的就是环的长度。
方法一:
size_t GetCycleLen(LinkNode* head)//如果链表带环, 求出环的长度
495 {
496 if(head==NULL){
497 return;
498 }
499 LinkNode* fast=head;
500 LinkNode* slow=head;
501 while((fast!=NULL)&&(fast->next!=NULL)){
502 fast=fast->next->next;
503 slow=slow->next;
504 if(fast==slow)
505 break;
506 }
507 int count=1;//因为最后一次siow->next=fast,不会进入
508 //循环中,所以次数少加一次,所以count要从1开始
509 while(slow->next!=fast){
510 count++;
511 slow=slow->next;
512 }
513 return count;
514 }
方法一的简化:
size_t GetCycleLen2(LinkNode* head)//如果链表带环, 求出环的长度
518 {
519 if(head==NULL){
520 return;
521 }
522 LinkNode* meet = HasCycle(head);//求出快慢指针的相遇点
523 if (meet == NULL){
524 //不带环的链表
525 return 0;
526 }
527 LinkNode* cur = meet;
528 size_t count = 1;
529 while (cur->next != meet){ //循环在meet的前一个位置停止,少加一次,count从1开始数
530 cur = cur->next;
531 ++count;
532 }
533 return count;
534 }
12、如果链表带环,我们求环的入口点
我们的基本思路就是让头指针从头出发,相遇点指针从相遇点出发,当两个指针重合,那么这点就是我们所要找的入口点。
7 LinkNode* GetCycleEntry(LinkNode* head)// 如果链表带环, 求出环的入口
538 {
539 if(head==NULL){
540 return;
541 }
542 LinkNode* meet= HasCycle(head);
543 LinkNode* cur=head;
544 LinkNode* cur2=meet;
545 if(meet==NULL){
546 return ;
547 }
548 while(cur!=cur2){
549 cur=cur->next;
550 cur2=cur2->next;
551 }
552 return cur;
553 }
13、判断两个链表是否相交,(假设链表不带环)
首先我们需要知道的是两个链表相交的情况只有Y字型的,没有X字型的。
我们的思路就是首先让第一个链表从第一个链表的头节点开始出发,第二个链表也从它的头结点进行出发,最后判断最后一个结点是否相等,那么这两个链表就有交点。
LinkNode* HasCross(LinkNode* head1, LinkNode* head2)//判定两个链表是否相交, 并求出交点
556 {
557 if(head1==NULL){
558 return NULL;
559 }
560 if(head2==NULL){
561 return NULL;
562 }
563 LinkNode* cur1=head1;
564 LinkNode* cur2=head2;
565 while(cur1->next!=NULL){
566 cur1=cur1->next;
567 }
568 while(cur2->next!=NULL){
569 cur2=cur2->next;
570 }
571 if(cur1==cur2){
572 return cur1;
573 }
574 else
575 {
576 return NULL;
14、判断链表是否相交,假设可能带环
1、如果两链表都不带环(相交)
2、两个链表都带环 a、入口相等,交点在环外 b、入口不相等,交点在环上
3、两个链表一个带环,一个不带环,一定不相交
int HasCrossWithCycle(LinkNode* head1, LinkNode* head2)// 判定两个链表是否相交.//但是链表可能带环 ;它的返回值return 如果相交, 返回1, 否则返回0
{
if((head1==NULL)||(head2==NULL))
{
return 0;
}
LinkNode* entry1=GetCycleEntry(head1);
LinkNode* entry2=GetCycleEntry(head2);
//1、如果两链表都不带环(相交)
if((entry1==NULL)&&(entry2==NULL)){
return HasCross(head1,head2)!=NULL?1:0;
}
//2、两个链表都带环
if(entry1!=NULL&& entry2!=NULL){
// a、入口相等,交点在环外
if(entry1==entry2){
return 1;
}
// b、入口不相等,交点在环上,两个入口点是不同的,但是能从其中的一个入口绕环几周,最后能到达另外一个入口点,该情况下两个环入口就是链表的交点。
if(entry1!=entry2){
LinkNode* cur1=entry1;
LinkNode* cur2=entry2;
while(cur1->next!=entry1){
cur1=cur1->next;
if(cur1==cur2){
return 1;
}
}
return 0;
}
}
// 3、两个链表一个带环,一个不带环,一定不相交
if((entry1!=NULL&& entry2==NULL)||(entry1==NULL &&entry2!=NULL)){
return 0;
}
}
15、求两个链表的交集
求两个有序链表的交集的整体思路就是分别定义两个指针 cur1, cur2,指向两个对应的链表的首元素, 然后将 cur1 和 cur2 所指的链表的 data 进行比较, 如果相等, 将这个结点插入到一个新链表中, 然后两个指针 cur1, cur2, 一起向后走一步,如果不相等, 就将 data 值小的那个指针向后移动, 而另外一个指针不动, 在进行比较,重复以上动作,直到 cur1 或者 cur2 两个中其中一个为空,则停止循环。
624 LinkNode* UnionSet(LinkNode* head1, LinkNode* head2)//求两个有序链表的交集;返回表示交集的新链表
625 {
626 if(head1==NULL || head2==NULL){
627 return NULL;
628 }
629 LinkNode* cur1=head1;
630 LinkNode* cur2=head2;
631 LinkNode* newhead=NULL;
632 LinkNode* newtail=NULL;
633 while(cur1!=NULL&&cur2!=NULL){
634 if(cur1->data<cur2->data){
635 cur1=cur1->next;
636 }else if(cur1->data>cur2->data){
637 cur2=cur2->next;
638 }
639 else{
640 //cur1->data-cur2->dat
641
642 if(newhead==NULL){
643 newhead= CreateNode(cur1->data);
644 newtail=newhead;
645 }
646 else{
647 newtail->next=CreateNode(cur1->data);
648 newtail=newtail->next;
649 }
650 cur1=cur1->next;
651 cur2=cur2->next;
652 }
653 }
654 return newhead;
655 }
16、拷贝复杂链表
链表的数据结构
typedef struct ComplexNode {
LinkType data;
struct ComplexNode* next;
struct ComplexNode* random;
} ComplexNode;
复杂链表中除了 data, next, 之外,还有一个 random, 它可能指向链表中任何一个节点, 还可能指向空。
在进行复杂链表的拷贝时, 可以采用下面的方法, 先将复杂链表按照简单链表进行复制,将其复制到一个新链表中, 此时的新链表还是一个简单链表, 然后再遍历旧链表,求出每一个节点所对应的 random 相对于头节点的偏移量, 再遍历新链表, 根据所求得的偏移量确定新链表中的 random 的指针的指向。
ComplexNode* CreateComplexNode(LinkNodeType value){
ComplexNode* newnode = (ComplexNode*)malloc(sizeof(ComplexNode));
newnode->data = value;
newnode->random = NULL;
newnode->next = NULL;
return newnode;
}
size_t Diff(ComplexNode* src, ComplexNode* dst){
if (src == NULL || dst == NULL){
return (size_t)-1;//无符号长整形是一个很大的数字用来表示找不到
}
size_t count = 0;
while (src != NULL){
//src跳出循环有两种情况src找到了dst,返回偏移量,src走到NULL也没找到,返回(size_t)-1
if (src == dst){
return count;
}
count++;
src = src->next;
}
return (size_t)-1;
}
ComplexNode* Step(ComplexNode* pos, size_t offset){//从pos的位置走offset的位置
if (pos == NULL){
return NULL;
}
size_t i = 0;
for (; i<offset; i++){
pos = pos->next;
}
return pos;
}
ComplexNode* CopyComplex(ComplexNode* head)// 拷贝复杂链表
{
if (head == NULL){
return;
}
ComplexNode* newhead = NULL;
ComplexNode* newtail = NULL;
ComplexNode* cur = head;
//复制链表
for (; cur != NULL; cur = cur->next){
ComplexNode* newnode = CreateComplexNode(cur->data);
if (newnode == NULL){
newtail = newhead = newnode;
}
else{
newtail->next = CreateComplexNode(cur->data);
newtail = newtail->next;
}
}
//使cur和newcur保持同步,尽享便利
ComplexNode* newcur = newhead;
for (cur = head; cur != NULL && newcur != NULL; cur = cur->next, newcur = newcur->next){
//cur到这里已经指向空了,我们必须对cur进行重置
if (cur->random == NULL){
newcur->random = NULL;
continue;
}
size_t offset = Diff(head, cur->random);//求每个random指针修改head 的偏移步数
newcur->random = Step(newcur, offset);//根据偏移量修改新链表的random指针的指向
}
return newhead;
}
方法二:
ComplexNode* CopyComplex2(ComplexNode* head){//拷贝复杂链表
ComplexNode* cur = head;
//遍历旧链表,给每一个节点都创建一个对应的节点,并且将这个节点插入到旧节点之后
for (; cur != NULL; cur = cur->next->next){
ComplexNode* newnode = CreateComplexNode(cur->data);
newnode->next = cur->next;
cur->next = newnode;
}
//遍历链表,更新每个新节点的random指针
for (cur = head; cur != NULL; cur = cur->next->next){
if (cur->random == NULL){
cur->next->random = NULL;
continue;
}
cur->next->random = cur->random->next;
}
//再遍历链表,把新节点拆下来,组装成新链表
ComplexNode* newhead = NULL;
ComplexNode* newtail = NULL;
for (cur = head; cur != NULL; cur = cur->next){
ComplexNode* to_delete = cur->next;
cur->next = to_delete->next;
if (newhead == NULL){
newhead = newtail = to_delete;
}
else{
newtail->next = to_delete;
newtail = newtail->next;
}
}
return newhead;
}
本文详细介绍了单链表的各种操作,包括逆序打印、插入元素、实现约瑟夫环、链表逆置、冒泡排序、合并有序链表、查找中间节点等。此外,还探讨了如何处理带有环的链表,如寻找环的长度和入口点。
4544

被折叠的 条评论
为什么被折叠?



