[算法|C++]如何判断链表有环

文章介绍了如何使用两个指针判断单向链表是否存在环,通过一个速度慢的指针每次移动一个节点,一个速度快的指针每次移动两个节点,当两者相遇即表明链表有环。这种方法的时间复杂度为O(n),空间复杂度为O(1)。代码示例使用C++编写,展示了具体的实现过程。此外,文章还提出了如果链表有环,可以通过让两个指针从相遇点继续前进并计算循环次数来确定环的长度。
摘要由CSDN通过智能技术生成

参考:《漫画算法-小灰的算法之旅》

目录

题目

解决方法:

代码

问题扩展


题目

有一个单向链表,链表中可能出现“环”,如下图所示。那么如何用程序来判断该链表是否为有环链表呢?

解决方法:

可以使用两盒指针来解决这个问题:

首先创建两个指针p1和p2,让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针p1每次向后移动一个节点,让指针p2每次向后移动2个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环。

第1步:p1和p2都指向节点5。

 第2步:p1指向节点3,p2指向节点7。

 第3步:p1指向节点7,p2指向节点6。

 第4步:p1指向节点2,p2指向节点1。

 第5步,p1指向节点6,p2也指向节点6,p1和p2所 指相同,说明链表有环。

此方法就类似于一个追及问题。在一个环形跑道上,两个运动员从同一地点起跑,一个运动员速度快,另一个运动员速度慢。当两 人跑了一段时间后,速度快的运动员必然会再次追上并超过速度慢的运动员,原因很简单,因为跑道是环形的。

假设链表的节点数量为n,则该算法的时间复杂度 为O(n)。除两个指针外,没有使用任何额外的存储空间,所以空间复杂度是O(1)。

代码

using namespace std;
#include<iostream>
#include<string>
//创建链表节点结构体
struct Node {
	int data;  //节点数据
	Node* next;  //指向下一个节点的指针
	Node(int _data):data(_data),next(nullptr) {
	}
	//定义了一个构造函数,该构造函数带有一个整型参数 _data,表示该节点的数据。构造函数使用了成员初始化列表,
	//将节点的数据成员 data 初始化为 _data,将节点的指针成员 next 初始化为 nullptr,表示该节点的下一个节点为空指针。
};
//判断是否有环
int isCycle(Node* head) {
	Node* p1 = head;
	Node* p2 = head;
	while (p2 != nullptr && p2->next!= nullptr) {
		p1 = p1->next;  //p1向后移动一个节点
		p2 = p2->next ->next;    //p2向后移动两个节点
		if (p1 == p2) {
			return true;
		}
	}
	return false;
}

int main()
{

	Node* node1 = new Node(5);
	Node* node2 = new Node(3);
	Node* node3 = new Node(7);
	Node* node4 = new Node(2);
	Node* node5 = new Node(6);
	Node* node6 = new Node(8);
	Node* node7 = new Node(1);
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = node5;
	node5->next = node6;
	node6->next = node7;
	node7->next = node4;
	
	cout << isCycle(node1) << endl;
	system("pause");
	return 0;
}

问题扩展

如果链表有环,如何求出环的长度?

 解:

当两个指针首次相遇,证明链表有环的时候,让两个指针从相遇点i继续循环前进,并统计前进的循环次数,直到两个指针第二次相遇。此时,统计出来的前进次数就是环长。因为指针p1每次走1步,指针p2每次走2步,两者的速度差是1步。当两个指针再次相遇时,p2比p1多走了整整1圈。因此,环长=每一次速度差×前进次数=前进次数。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kruskal算法是一种最小生成树算法,它的基本思想是将所有的边按照权值从小到大排序,然后依次加入生成树,如果加入该边不会导致生成树形成环,则加入该边,否则舍去该边。 在Kruskal算法,需要判断当前加入的边是否会导致生成树形成环。可以使用并查集来实现该功能。具体来说,对于每个节点,维护它的父节点,初始时每个节点的父节点都是它本身。每次加入一条边时,判断该边的两个端点是否在同一个并查集,如果是,则加入该边会导致生成树形成环,应该舍去该边;否则,将两个端点所在的并查集合并起来,并将该边加入生成树。 以下是使用链表实现的Kruskal算法判断回路的C++代码: ```cpp #include <iostream> #include <vector> using namespace std; struct Edge { int u, v, w; Edge(int _u, int _v, int _w) : u(_u), v(_v), w(_w) {} }; struct Node { int parent; int rank; Node() : parent(-1), rank(0) {} }; vector<Node> nodes; // 查找节点x所在的集合的代表节点 int find(int x) { if (nodes[x].parent == -1) { return x; } else { return nodes[x].parent = find(nodes[x].parent); } } // 合并x和y所在的集合 void merge(int x, int y) { int root_x = find(x); int root_y = find(y); if (root_x != root_y) { if (nodes[root_x].rank < nodes[root_y].rank) { nodes[root_x].parent = root_y; } else if (nodes[root_x].rank > nodes[root_y].rank) { nodes[root_y].parent = root_x; } else { nodes[root_y].parent = root_x; nodes[root_x].rank++; } } } // 判断加入边(u, v, w)是否形成环 bool hasCycle(int u, int v) { int root_u = find(u); int root_v = find(v); if (root_u == root_v) { return true; } else { merge(root_u, root_v); return false; } } // Kruskal算法求最小生成树 vector<Edge> kruskal(int n, vector<Edge>& edges) { vector<Edge> result; nodes.resize(n); sort(edges.begin(), edges.end(), [](Edge& e1, Edge& e2) { return e1.w < e2.w; }); for (auto& edge : edges) { if (!hasCycle(edge.u, edge.v)) { result.push_back(edge); } } return result; } int main() { int n = 6; vector<Edge> edges = { {0, 1, 4}, {0, 2, 3}, {1, 2, 1}, {1, 3, 2}, {2, 3, 4}, {3, 4, 2}, {4, 5, 6} }; vector<Edge> result = kruskal(n, edges); for (auto& edge : result) { cout << edge.u << " " << edge.v << " " << edge.w << endl; } return 0; } ``` 以上代码,find函数和merge函数分别实现了并查集的查找和合并操作。hasCycle函数判断加入边(u, v, w)是否会导致生成树形成环,如果是,则返回true,否则返回false,并且将u和v所在的集合合并起来。kruskal函数实现了Kruskal算法,其使用sort函数将所有边按照权值从小到大排序,并依次加入生成树。最后,将生成树的边保存在result向量,并返回。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值