问题:
一个链表要么以NULL结尾(非循环的),要么以循环结尾(循环的),请编写一个函数,接受链表的头指针作为参数,确定该链表是循环的还是非循环的。如果链表是循环的,函数返回true,如果是非循环的,函数返回false。注意,不能以任何方式修改链表。
解答:
这两种链表的区别在与它们的末尾。在非循环链表中,末尾节点是以NULL结束的,因此只要遍历链表,直到找到一个以NULL结尾的节点就行;但在非循环链表中,仅仅遍历链表,就会陷入死循环中。
所以我们先研究一下末尾节点。对于循环链表中末尾节点指向的节点,还有另一个指针(头指针)指向它。这意味着有两个指针指向了同一个节点,这个节点是唯一一个有两个元素指向的节点。根据这一特征,我们可以遍历该链表,检查每个节点,确定是否有两个节点指向它,如果有,则该链表肯定是循环链表,否则,该链表是非循环的,则最终会遇到一个NULL指针。不幸的是,我们很难检查指向每一个元素的节点数。
除了检查是否有两个指针指向同一节点之外,我们还可以检查是否曾经遇到过某个节点。如果发现一个曾经遇到过的节点,就表明这是一个循环的链表;如果遇到NULL指针,就表明这是一个非循环链表。现在关键是怎样判断是否遇到过某个节点呢,最简单的就是遍历每个元素时作标记,但题目不允许我们修改该链表。
那么我们可以利用链表已有的东西,因为我们知道链表的当前节点和链表的头指针,因此可以将当前节点的next指针与前面所有的节点直接进行比较。如果对于第i个节点,比较它的next指针,看是否指向了1到i-1号节点之中的某一个。如果出现相等的情况,说明这是一个循环链表。但这个算法的时间复杂度是O(n2)。
我们有一种使用两个指针的更好的算法,交替移动两个指针,且两个指针的移动速度不一样。在非循环链表中,较快的指针会先到达末尾;在循环链表中,两个指针会陷入无限循环,较快的指针最终会赶上并超过较慢的指针。如果较快的指针最终超过了较慢的指针,说明这是一个循环链表。如果它遇到一个NULL指针,说明这是一个非循环链表。
算法的实现代码如下:
#include <iostream>
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
};
void InitList(ListNode** pList)
{
*pList = (ListNode*)malloc(sizeof(ListNode));
(*pList)->m_pNext = NULL;
}
void InsertList(ListNode* pList, int data)
{
ListNode* pNewNode = (ListNode*)malloc(sizeof(ListNode));
pNewNode->m_nKey = data;
pNewNode->m_pNext = pList->m_pNext;
pList->m_pNext = pNewNode;
}
bool determinTermination(ListNode *head)
{
if(head == NULL)
return false;
ListNode *fast, *slow;
slow = fast = head;
while(true)//排除fast->m_pNext->m_pNext不存在的情况
{
if(fast == NULL || fast->m_pNext == NULL)
return false; //非循环的
slow = slow->m_pNext;
fast = fast->m_pNext->m_pNext;
if(fast == slow || fast->m_pNext == slow)
return true; //循环的
}
}
int main()
{
ListNode* pListHead = NULL;
InitList(&pListHead);
for(int i=9; i>=0; i--)
{
InsertList(pListHead, i);
}
if(determinTermination(pListHead) == true)
std::cout<<"1)单向链表是循环的"<<std::endl;
else
std::cout<<"1)单向链表是非循环的"<<std::endl;
ListNode* pTmp = pListHead;
while(pTmp->m_pNext != NULL)
{
pTmp = pTmp->m_pNext;
}
pTmp->m_pNext = pListHead->m_pNext; //连接成循环链表
if(determinTermination(pListHead) == true)
std::cout<<"2)单向链表是循环的"<<std::endl;
else
std::cout<<"2)单向链表是非循环的"<<std::endl;
system("pause");
return 0;
}
复杂度分析:
如果链表是非循环的,较快的指针会在检查了n个节点之后到达链表的末尾,而较慢的指针只遍历了1/2的节点。因此总共遍历了3/2的节点,时间复杂度是O(n)。
如果链表是循环的,较慢的指针永远也不会遍历超过一次。当较慢的指针检查了n个节点时,较快的指针已经检查了2n个节点,并超过了较慢的指针。最坏情况下,需要检查3n个节点,时间复杂度是O(n)。
因此,不论链表是循环的还是非循环的,这种两个指针的方法都比一个指针的方法好很多。
分享到:
相关推荐
循环链表与普通链表的主要区别在于最后一个节点指向了头节点,形成一个闭合的环,这在处理循环遍历或某些特定算法时非常方便。 首先,让我们详细解析`CList.java`和`CList2.java`这两个文件可能包含的内容。`CList`...
中南民族大学计算机科学学院本科课程设计:模拟实现多种内存动态分区分配算法。 根据所学知识,完成指定课程设计的内容,并规范地...4、要有菜单5、在需求分析阶段一定写好测试数据,方便对运行结果正确与否的判断。
- **2**、算法就是程序:错误,算法是解决问题的方法步骤,而程序是算法的具体实现。 - **3**、数据元素(数据项)是数据的最小单位:错误,数据项才是数据的最小单位。 - **4**、算法的五个特性:有穷性、输入、...
链表环问题是链表中常见的问题之一,涉及到如何判断链表中是否存在环以及如何找到环的入口节点等问题。 - **判断链表是否存在环**:使用“快慢指针”技术,快指针每次移动两步,慢指针每次移动一步。如果两者相遇,...
在IT行业的面试中,数据结构是必不可少的知识领域,尤其对于软件开发、算法分析以及系统设计等职位来说,对数据结构的理解深度往往直接影响到面试的成功与否。数据结构是计算机科学的基础,它研究如何组织和存储数据...
- **知识点**:该循环的条件判断涉及到平方根运算,因此时间复杂度为O(√n)。 ##### 二、填空题解析 1. **程序段的时间复杂度**: - **填空答案**:O(log2n) - **知识点**:该程序段中的循环每次都将i乘以2,...
根据节点指针的不同,链式存储结构又可以分为单链表、双链表和循环链表等多种形式。在这个实验报告中,重点介绍了单链表演示系统的设计与实现。 实验报告详细描述了基于顺序存储结构和链式存储结构来实现线性表的...
数据结构期末考试的相关知识点主要涵盖数据结构的基本概念、算法分析、特定...以上知识点涵盖了填空题、判断题和选择题的常见问题类型,是数据结构期末考试的重点复习内容。理解并掌握这些知识点对于取得高分至关重要。
而程序则是算法的具体实现形式之一。因此,判断题1的说法是错误的。 2. **数据元素与数据项的区别** - **知识点**:数据元素与数据项的区别。 - **解析**:数据元素是构成数据结构的基本单位,而数据项是数据元素...
- 控制结构:if语句用于条件判断,for和while循环用于遍历和查找联系人。 - 函数:每个功能如添加、删除、查询等通常会封装成独立的函数。 2. **结构体(struct)**: - 在通讯录系统中,我们需要定义一个结构体...
本实验报告主要探讨了数据结构中的顺序表和链表的应用,由计算机与信息工程学院的计算机类专业学生宋然完成,指导教师为李俊玲。实验目标是实现顺序表的各种基本运算算法,包括初始化、建表、删表以及元素的增删查改...
### 快乐数的概念 快乐数(Happy Number)是指一个正整数,通过不断地...综上所述,这段Java代码实现了快乐数的高效判断,并通过快慢指针的方法有效地检测了循环的存在与否。这对于理解和学习算法设计思路非常有帮助。
- **结构化编程支持**: 提供了丰富的控制结构,如条件判断、循环等。 - **可移植性**: 标准化的语法确保了C程序可以在多种不同的平台上运行。 - **模块化**: 支持函数和库的使用,便于代码复用和维护。 - **内存管理...
6. **流程控制与条件判断**:在实现系统流程时,会大量使用循环(如while、for)和条件语句(if-else)来控制程序的执行路径。例如,系统会在用户选择退出之前持续显示主菜单并等待用户输入。 7. **数据检索与排序*...
3. **控制结构**:if-else语句、switch语句和循环(for、while)用于实现逻辑判断和流程控制。 4. **函数**:编写各种功能的函数,如查询航班、预订机票等,提高代码的可读性和复用性。 5. **错误处理**:通过try-...
#### 是非判断题解析 - **1. 在目前,用于保证软件质量的主要手段是进行软件测试。** - 正确。软件测试是确保软件质量的重要环节,通过测试可以发现软件中存在的缺陷或不符合需求的地方。 - **2. 使用DMA方式传送...
- 使用素数测试算法,如Miller-Rabin素性测试或AKS素性测试算法,可以在合理的时间内判断一个大数是否为素数。 #### 16. 字符串转换为整数的功能函数实现? - `itoa`:将整数转换为字符串。 - `atoi`:将字符串...
`Set`使用`equals()`方法来判断元素是否相等,因此两个元素即使使用`==`操作符不相等,只要`equals()`返回`true`,`Set`就会认为它们是重复的。`==`操作符用于比较对象的引用,而`equals()`用于比较对象的内容。 ##...
- 使用循环和条件判断来处理截取逻辑。 #### 17. Java编程,打印昨天的当前时刻 - 使用`Calendar`或`LocalDate`类来计算昨天的日期。 #### 18. 抽象类和接口的区别? - **抽象类**:可以包含抽象方法和具体实现。...