链表经典练习题

本文介绍了16道关于链表的编程练习题,包括从尾到头打印、删除非尾节点、插入节点、约瑟夫环问题、逆置链表、冒泡排序链表、合并有序链表、查找中间节点、倒数第k个节点、删除倒数第k个节点、判断链表是否有环及环的属性、判断两链表是否相交及其交点、处理带环链表的相交问题、查找相同数据、复杂链表复制等,每题都提供了具体的代码实现思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链表经典练习题
这里写图片描述

一、从尾到头打印单链表
因为单链表是有指向的,前一节点只能指向下一节点,因此为了实现从尾到头打印链表可以使用一种非常简单的方法就是递归,先遍历再输出。
代码实现:

void LinkListReversePrint(LinkNode* head){
   85     if(head == NULL){
   86         return;//空链表
   87     }
   88     LinkListReversePrint(head -> next);
   89     printf("[%c | %p]",head ->data,head);
}

二、删除一个无头单链表的非尾节点(不能遍历)
删除一个无头单链表的非尾节点通过遍历来实现时很好实现的,但是在本题目中它最关键的一个要求就是不能遍历。
因为单链表只有next指针,只指向下一个元素,所以我们如果要删除一个节点,我们可以从要删除的节点的下一个节点入手,我们把要删除节点的下一个节点的值赋给删除节点,然后再修改删除节点的指向,再删除下一个节点,就可以实现题目所说。
具体代码实现如下:

 void LinkListPop(LinkNode** phead, LinkNode* pos){
      if(phead == NULL || pos == NULL){
          return ;//非法输入
      }   
      if(*phead == NULL){
          return;//空链表
      }   
      pos -> data = pos -> next -> data;
      pos -> next = pos -> next -> next;
      LinkNodeDestory(pos->next);
  }

三、在一个无头单链表的节点前插入一个节点(不允许遍历)

这道题其实跟上一道题很相像,我们不能对单链表节点的前一个节点进行操作,但是我们可以对其下一个节点进行处理,因此具体思路就跟上题一样,在要插入节点的后面创造一个新节点,然后把要插入节点的值赋给新节点,然后再将需要插入节点的值赋给插入节点,再改变两节点的指向就可完成实现。
具体代码实现如下:

  void LinkListInsertBefore(LinkNode** phead, LinkNode* pos, LinkNodeType value){
      if(phead == NULL || pos == NULL){
          return;//非法输入
      }
      LinkNode* new_node = LinkNodeCreate(pos -> data);
      pos -> data = value;
      new_node -> next = pos -> next;
      pos -> next = new_node;
  }

四、在单链表实现约瑟夫环
首先我们了解下什么是约瑟夫环:
讲一个比较有意思的故事:约瑟夫是犹太军队的一个将军,在反抗罗马的起义中,他所率领的军队被击溃,只剩下残余的部队40余人,他们都是宁死不屈的人,所以不愿投降做叛徒。一群人表决说要死,所以用一种策略来先后杀死所有人。
于是约瑟夫建议:每次由其他两人一起杀死一个人,而被杀的人的先后顺序是由抽签决定的,约瑟夫有预谋地抽到了最后一签,在杀了除了他和剩余那个人之外的最后一人,他劝服了另外一个没死的人投降了罗马。

我们这个规则是这么定的:
在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命。

按照如下规则去杀人:

所有人围成一圈
顺时针报数,每次报到q的人将被杀掉
被杀掉的人将从房间内被移走
然后从被杀掉的下一个人重新报数,继续报q,再清除,直到剩余一人
你要做的是:当你在这一群人之间时,你必须选择一个位置以使得你变成那剩余的最后一人,也就是活下来。
实现约瑟夫环首先需要判断选择的那个人是否是最后一个幸存的人,不是的话再进行约瑟夫环行动。
具体代码实现如下:

  LinkNode* JosephCycle(LinkNode* head, size_t food){
      if(head == NULL){
          return NULL;
      }
      if(food == 0){
          return NULL;
      }
      LinkNode* cur = head;
      while(cur != cur -> next){
          size_t i = 0;
          for(; i < food - 1; ++i){
              cur = cur -> next;
          }
          printf("food[%c]\n",cur -> data);
          cur -> data = cur -> next -> data;
          cur -> next = cur -> next -> next;
      }
      return cur;
  }

五、逆置单链表
逆置单链表就不简单的是逆序打印单链表了,我们不仅要逆置它的值还要逆置它的地址。
逆置单链表我们可以通过改变各节点的指向来处理,就拿第一个和第二个节点来说,首先我们创建一个头结点指针使其指向第一个节点,额庵后我们可以改变第二个节点的指向,使其指向第一个节点,然后将头结点指针赋给第二个节点,以此循环,从而逆置单链表。
具体代码实现如下:

  void LinkListReverse(LinkNode** phead){
      if(phead == NULL){
          return;
      }   
      if(*phead == NULL){
          return;
      }   
      LinkNode* cur = *phead;
      while(cur -> next != NULL){
          LinkNode* to_delete = cur -> next;
          cur -> next = to_delete -> next;
          to_delete -> next = *phead;
          *phead = to_delete;
      }   
  }

六、单链表冒泡排序

冒泡排序基本算法思路:

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。
3.针对所有的元素重复以上的步骤,直到没有任何一对数字需要比较。

要用单链表实现,我们同样可以使用节点和它后面一个节点互相交换值来实现。

具体代码实现如下:


                
链表作为数据结构中的重要部分,是算法训练中必不可少的练习内容。以下是一些经典链表算法练习题及对应的解答思路和代码示例。 ### 反转链表 反转链表是一个基础问题,其核心在于调整链表中节点的指向。通过使用三个指针分别记录当前节点、前一个节点和后一个节点,可以实现链表的逐个反转。 ```c struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev = NULL; struct ListNode *current = head; while (current != NULL) { struct ListNode *nextTemp = current->next; current->next = prev; prev = current; current = nextTemp; } return prev; } ``` ### 判断链表是否有环 此问题可以通过快慢指针的方法解决。定义两个指针,一个每次移动一个节点(慢指针),另一个每次移动两个节点(快指针)。如果链表中存在环,则两个指针最终会相遇;否则,快指针将遇到空节点[^2]。 ```c bool hasCycle(struct ListNode *head) { struct ListNode *slow = head; struct ListNode *fast = head; while (fast && fast->next) { slow = slow->next; fast = fast->next->next; if (slow == fast) { return true; } } return false; } ``` ### 分割链表 分割链表的问题要求将链表按照特定值分成两部分,一部分小于该值,另一部分大于或等于该值。解决这个问题的一种方法是创建两个带头节点的链表,分别用于存储小于和大于等于目标值的节点,最后将两个链表合并[^3]。 ```c struct ListNode* partition(struct ListNode* head, int x) { struct ListNode lessHead, greaterHead; struct ListNode *lessTail = &lessHead; struct ListNode *greaterTail = &greaterHead; while (head) { if (head->val < x) { lessTail->next = head; lessTail = lessTail->next; } else { greaterTail->next = head; greaterTail = greaterTail->next; } head = head->next; } greaterTail->next = NULL; lessTail->next = greaterHead.next; return lessHead.next; } ``` ### 删除链表中的重复元素 删除链表中的重复元素需要遍历链表,并检查当前节点与下一个节点的值是否相同。如果相同,则跳过下一个节点;如果不相同,则继续遍历。 ```c struct ListNode* deleteDuplicates(struct ListNode* head) { struct ListNode *current = head; while (current && current->next) { if (current->val == current->next->val) { struct ListNode *temp = current->next; current->next = temp->next; free(temp); } else { current = current->next; } } return head; } ``` ### 合并两个有序链表 合并两个有序链表的目标是将它们合并成一个新的有序链表。可以通过比较两个链表的节点值,依次选择较小的节点插入到新链表中。 ```c struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) { struct ListNode dummy; struct ListNode *tail = &dummy; while (l1 && l2) { if (l1->val < l2->val) { tail->next = l1; l1 = l1->next; } else { tail->next = l2; l2 = l2->next; } tail = tail->next; } tail->next = l1 ? l1 : l2; return dummy.next; } ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值