数据结构与算法之链表

  • 由指针把若干个结点连接成链状结构。它是一种动态结构,因为在创建链表时无须知道链表长度。
  • 插入一个结点时只需要为新结点分配内存,然后调整指针的指向即可。
  • 如果链表的头指针会有更改,那么参数应该设为指向指针的指针,即ListNode **pHead。
struct ListNode
{
     int m_nKey;
     ListNode *m_pNext;
};
  • 优点:空间效率高,因为是每添加一个结点才分配一次内存,所以没有闲置的内存。插入和删除效率高O(1)
  • 缺点:时间效率低,即查找效率低O(n);

链表元素的删除

题目:在链表中找到第一个含有某值的结点并删除该结点;

  • void RemoveNode(ListNode **pHead, int value);

解题思路:

(1)若头结点的值 = value,则删除头结点,更改头指针指向头结点的下一个结点

(2)依次遍历,找到要被删除结点的上一个结点,调整指针的指向;

从尾到头打印链表(剑指offer---面试题5)

题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值;

  • void PrintListReversingly_Iteratively(ListNode *pHead);
  • void PrintListReversingly_Recursively(ListNode *pHead);

解题思路:

(1)迭代法:用栈作辅助空间;

(2)递归法:每次访问结点的时候,先递归输出它后面的结点,再输出该结点本身;

在O(1)时间删除链表结点(剑指offer---面试题13)

题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点;

  • void DeleteNode(ListNode **pListHead, ListNode *pToDeleted);

解题思路:

(1)若pToDeleted不是尾结点,则把该pToDeleted的下一个结点pNext的值和指针赋值给pToDeleted,然后删除pNext;

(2)若pToDeleted是尾结点且链表只有一个结点,则删除头结点,更改头指针*pListHead的值;

(3)若pToDeleted是尾结点且链表有多个结点,则从头遍历找到pToDeleted的前一个结点,然后删除pToDeleted;

链表中倒数第k个结点(剑指offer---面试题15)

题目:输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点;

  • ListNode *FindKthToTail(ListNode *pListHead, unsigned int k);

方法一:遍历两次,第一次找到链表中结点的个数n,第二次从头开始找到第n+1-k个结点即是倒数第k个结点;

解题思路:两个指针P1和P2

(1)两个指针P1和P2都指向第一个结点,然后P2向后走k-1步;

(2)P1和P2同时向后移动,当P2走到末尾的时候,P1即是倒数第k个结点;

(3)边界情况:头指针pListHead为空、k=0(k-1=0xFFFFFFFF)、k < 链表结点个数n

相关题目:求链表的中间结点、判断一个单向链表是否形成了环形结构;(两个指针P1和P2,P1一次走一步,P2一次走两步)

反转链表(剑指offer---面试题16)

题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点;

  • ListNode *ReverseList(ListNode *pHead);

解题思路:

(1)初始化pPre = NULL, pNode = pHead, pNext = NULL;

(2)从头开始遍历每一个结点pNode,并保存pNext = pNode->m_pNext,调整pNode的指针即pNode->m_pNext = pPre;

(3)重新更新pPre和pNode的值,重复第二步;

(4)直到遍历到最后一个结点即pNode->m_pNext == NULL时,那么pReverseHead = pNode;

合并两个排序的链表(剑指offer---面试题17)

题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。例如链表1是1->3->5->7,链表2是2->4->6->8,那么合并后的升序链表3是1->2->3->4->5->6->7->8;

  • ListNode *Merge(ListNode *pHead1, ListNode *pHead2);

解题思路:

(1)若pHead1为空,则pMergeHead = pHead2;反之若pHead2为空,则pMergeHead = pHead1;

(2)若pHead1 < pHead2,则pMergeHead = pHead1,然后再递归排序Merge(pHead1->m_pNext, pHead2);

(3)若pHead1 > pHead2,则pMergeHead = pHead2,然后再递归排序Merge(pHead1, pHead2->m_pNext);

复杂链表的复制(剑指offer---面试题26)

题目:请实现函数复制一个复杂链表。在复杂链表中,每个结点除了有一个m_pNext指针指向下一个结点,还有一个m_pSibling指向链表中的任意结点或者NULL;

  • ComplexListNode *ReconnectNodes(ComplexListNode *pHead);

方法一:第一步复制原始链表的每一个结点并用m_pNext连接起来;第二步设置每个结点的m_pSibling指针O(n^2);

缺点:时间复杂度太高O(n^2);

方法二:第一步复制原始链表的每一个结点并用m_pNext连接起来,把<N, N'>的配对信息保存到一个哈希表中;第二步根据哈希表来设置每个结点的m_pSibling指针;

缺点:需要空间复杂度O(n);

解题思路:

(1)根据原始链表的每个结点N创建对应的N',并把N’连接到N的后面;

(2)设置复制出来的结点N‘的m_pSbiling;

(3)把该链表拆分成两个链表,即奇数位置的结点就是原始链表,偶数位置的结点就是复制出来的链表;

二叉搜索树和双向链表(剑指offer---面试题27)

详情见“树”专栏;

两个链表的第一个公共结点(剑指offer---面试题37)

题目:输入两个链表,找出它们的第一个公共结点;

  • ListNode *FindFirstCommonNode(ListNode *pHead1, ListNode *pHead2);

方法一:两个辅助栈分别存放两个链表的每个结点,接下来比较两个栈顶的结点是否相同,若相同则出栈,直到找到最后一个相同的结点;

缺点:需要空间复杂度O(m+n);

解题思路:两个指针P1和P2

(1)分别遍历两个链表可以得到它们的长度,假设较长链表的长度为long,较短链表的长度为short,长度的差值diff = long - short;

(2)指针P1在long链表先走diff步,然后P1和P2同时走,若P1 == P2则该结点为两个链表的第一个公共结点;

圆圈中最后剩下的数字(剑指offer---面试题45)

题目:0,1...,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里面删除第m个数字,求出这个圆圈里剩下的最后一个数字。例如0,1,2,3,4这5个数字组成一个圆圈,如果从0开始每次删除第3个数字,则删除的前四个数字依次是2、0、4、1,因此最后剩下的数字是3;

  • int LastRemaining(unsigned int n, unsigned int m);

解题思路:构建辅助链表

(1)构建辅助链表list,先将数组中的左右元素push_back到list中;

(2)遍历整个链表,找到第m个位置current并保存next = ++current;

(3)然后删除current并设置current = next继续遍历;(注意边界条件即遍历到list.end()时要重新设置为list.begin()

(4)直到list中只有一个元素为止即退出while循环;

链表环中的入口结点(剑指offer---面试题56)

题目:一个链表中包含环,如何找出环的入口结点?

  • ListNode *EntryNodeOfLoop(ListNode *pHead);

解题思路:两个指针P1和P2

(1)找到环中某个结点:构建两个P1和P2,P1一次走一步,P2一次走两步,若干次后若P1 == P2则表示在环中相遇,返回该相遇结点;

(2)计算环的长度n:相遇结点一直向前移动,直到再次回到这个结点就可以得到环中结点个数了;

(3)找到入口结点:P1和P2都从头结点开始,P1先走n步,然后P1和P2同时走,若干次后若P1 == P2则表示在环的入口结点相遇,返回该结点即可;

新方法:首先同步骤1即找到环中的相遇结点pMeetNode,然后设置P1 = pHead,P2 = pMeetNode,两个指针同时走第一次相遇的结点就是环的入口结点;

原理:假设头结点到入口结点的距离是a,相遇结点到入口结点的距离是b,环的长度是n。步骤1中当P1和P2相遇时,此时P1走了a+b步,P2走了a+b+kn=2*(a+b),那么a+b=kn,所以从头结点开始走a步刚好到达入口结点,而且从相遇结点开始走a步也刚好到达入口结点;

删除链表中重复的结点(剑指offer---面试题57)

题目:在一个排序的链表中,如何删除重复的结点?例如链表1-2-3-3-4-4-5,删除后是1-2-5;

  • void deleteDuplication(ListNode **pHead);

解题思路:

(1)初始化pPreNode = NULL;

(2)遍历每一个结点pNode,若pNode的值 == pNext的值,则从pNode开始遍历并删除和pNode的值相同的结点;

(3)删除结点后,若删除结点是头结点则更新头结点pHead的值,否则连接下一个没有重复的结点即pPreNode->m_pNext = pNext;

转载于:https://www.cnblogs.com/bo1990/p/11442035.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
循环链表是一种特殊类型的链表,其尾节点指向头节点形成一个循环。相比于普通链表,循环链表可以更方便地遍历链表或进行某些操作。对于数据结构与算法的循环链表的随堂实验,我无法直接引用提供的参考内容中的具体实验内容。但是,一般的循环链表实验可能会包括以下几个步骤: 1. 定义循环链表节点类。 2. 实现循环链表的插入操作,可以在链表的任意位置插入新的节点。 3. 实现循环链表的删除操作,可以删除链表中的指定节点。 4. 实现循环链表的查找操作,可以根据给定的条件查找链表中的节点。 5. 实现循环链表的遍历操作,可以按照特定的顺序遍历链表中的所有节点。 请根据具体的实验要求,参考引用内容中的链表定义与用法,进行相应的实验设计与实现。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [Python实现的数据结构与算法链表详解](https://download.csdn.net/download/weixin_38545117/13771586)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Python数据结构与算法链表定义与用法实例详解【单链表、循环链表】](https://download.csdn.net/download/weixin_38660295/13786240)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值