求解链表是否存在环、环中节点个数、环的起始结点

0. 定义结点并创建链表

代码实现:

struct ListNode // 定义节点
{
	int val;
	struct ListNode * next;
	ListNode(int x):val(x),next(NULL){}
};

ListNode* CreateLinkList(int n, int m) // 创建一条长度为n的链表,在第m个位置设置环的起始点
{
	if(n<=0||m>n)
		return NULL;
	ListNode * pHead = NULL;
	ListNode * p = NULL;
	ListNode * pStart = NULL; // 保存环的起始结点
	for(int i=0;i<n;i++)
	{
		ListNode * node = new ListNode(i+1);
		if(i+1==m)
			pStart = node;
		if(i==0)
		{
			pHead = node;
			p = pHead;
		}
		else
		{
			p->next = node;
			p = p->next;
		}
	}
	p->next = pStart;
	return pHead;
}


1. 求解链表是否存在环?

       错误思路:从头结点开始遍历链表,当再次遍历到头结点则可判断该链表存在环。这种解法没有充分理解题目的意思,链表中存在的环不一定是尾结点指向头结点(形如“O”),也可能是尾结点指向非头结点(形如“6”)。

      正确思路:设置两个指针,一个快指针每次走两步,一个慢指针每次只走一步。当链表中存在环时,快指针一定会和慢指针相遇于环中的一结点;当链表中不存在环时,快指针先遍历完整个链表。

代码实现:

bool IsHaveCircle(ListNode* pHead)  // 判断链表里是否有环
{
	if(pHead==NULL)
		return true; // 空链表默认无环
	ListNode * slow = pHead;
	ListNode * fast = pHead;
	while(fast!=NULL)
	{
		fast = fast->next;
		if(fast!=NULL)
		{
			fast = fast->next;
			slow = slow->next;
		}
		if(slow==fast)
			break;
	}
	if(fast==NULL)
		return false;
	else
		return true;
}


2. 求解环中节点个数

        正确思路:从问题1中我们得知,若链表中存在环,则快指针与慢指针必定相遇于环中任一结点,因此当相遇时我们开始计数直到下次相遇,慢指针所移动步数即为环中结点个数(设环中结点数为r,另设从第一次相遇开始到第二次相遇时慢指针移动S步,那么快指针移动步数为2S,且有2S=S+r,因此r=S)。

代码实现:

int CountCircle(ListNode* pHead) // 计算环中节点个数
{
	if(pHead==NULL)
		return 0;
	ListNode * slow = pHead;
	ListNode * fast = pHead;
	while(fast!=NULL) // 先找到相遇点
	{
		fast = fast->next;
		if(fast!=NULL)
		{
			fast = fast->next;
			slow = slow->next;
		}
		if(fast==slow)
			break;
	}
	if(fast==NULL)
		return 0; // 无环返回0
	int count = 0; //从相遇点开始计数,直至下一次相遇
	do{
		fast = fast->next->next;
		slow = slow->next;
		count++;
	}while(fast!=slow);
	return count;
}


3. 求解环的起始结点

正确思路:问题1提到当慢指针与快指针相遇时,必定相遇于环中某一结点,并且相遇时慢指针是第一次进入环(这是因为:若假设相遇前慢指针已经遍历过一遍环了,那么快指针必定可以遍历两遍环,必定与慢指针相遇;这与假设矛盾,因此第一次相遇时慢指针是第一次进入环)。现设整个链表长度为L,头结点距离环的起始结点的长度为l,而环起始结点距离相遇结点的长度为x,环的长度为r。

(1) 假设从开始到第一次相遇时慢指针移动S步,那么快指针移动步数为2S,则有2S=S+nr  --->  S = nr;

(2) 根据慢指针是第一次入环,那么第一次相遇时,慢指针总共步数S为l+x, 而S=nr, 则有 l+x = nr ---> l+x = (n-1)r + r ---> l + x = (n-1)r + L - l  ---> l = (n-1)r + L - I - x.

      根据(2)中推出的公式l = (n-1)r + L - I - x,我们可以得出结论:若有一指针从表头开始遍历,另一指针从相遇点开始遍历,这两指针必将相遇于环的起始点,并且从相遇点开始遍历的指针在相遇时已经遍历过(n-1)次环。那么我们求解起始结点的步骤可以分为两步:第一步,找到相遇点;第二步,根据相遇点找到环的起始点。

代码实现:

ListNode* EntryNodeOfLoop(ListNode* pHead) // 找出环的起始点
{
	if(pHead==NULL)
		return NULL;
	ListNode * slow = pHead;
	ListNode * fast = pHead;
	while(fast!=NULL) // 找出相遇点
	{
		fast = fast->next;
		if(fast!=NULL)
		{
			fast = fast->next;
			slow = slow->next;
		}
		if(fast==slow)
			break;
	}
	if(fast==NULL)
		return NULL;
	slow = pHead; // 一个从开头遍历,一个从相遇点开始遍历
	while(slow!=fast) // 相遇时的节点即为环的起始点
	{
		slow = slow->next;
		fast = fast->next;
	}
	return fast;
}

4. 主程序调用

int main()
{
	ListNode * pHead = NULL;
	pHead = CreateLinkList(5,5);
	bool result = IsHaveCircle(pHead);
	if(result)
		cout<<"Yes"<<endl;
	else
		cout<<"No"<<endl;
	ListNode * pStart = NULL;
	pStart = EntryNodeOfLoop(pHead);
	if(pStart==NULL)
		cout<<"Not exist the node"<<endl;
	else
		cout<<"The value of the node is "<<pStart->val<<endl;
	cout<<CountCircle(pHead)<<endl;
	return 0;
}

在C语言中,我们可以创建一个循链表来模拟约瑟夫的问题。首先,我们需要定义链表节点结构体,包含一个数据成员(这里假设是学生的学号)和指向下一个节点的指针。对于约瑟夫,我们将需要维护一个起始位置和一个步长。 以下是一个简单的实现步骤: 1. **定义节点结构体**: ```c typedef struct Node { int student_id; struct Node* next; } Node; ``` 2. **初始化链表**: - 创建头结点和尾节点 - 设置每个节点的学号(可以根据实际需求生成) 3. **设置约瑟夫规则**: - 定义一个`next_position(int, int)`函数,接收当前学生的位置和步长作为参数,计算新的位置(取模基数4),并返回新位置的学生编号 4. **约瑟夫算法**: - 初始化两个变量,一个代表起始位置(通常设为1),另一个代表步长(根据队列长度设置) - 使用while循遍历链表,每次移动到新的位置,直到找到特定位置的学生(可能是结束条件,比如某个特殊值或达到某个特定次数) 5. **链表操作**: - 插入节点时考虑循链接:如果插入位置是最后一个节点之后,则将其连接到第一个节点;如果是第一个节点之前,则更新头节点的next指向新插入的节点。 6. **删除节点**: - 如果要删除特定位置的节点,可以先找到该位置的前一个节点,然后将其next指向后一个节点。 以下是一个简化的示例代码片段,展示了如何构建链表和执行约瑟夫的基本逻辑,但没有完整的主循和错误处理: ```c // 初始化链表 Node* create_circle_list() { // 实现链表创建代码 } // 获取下一位学生 int josephus(Node* head, int position, int step) { Node* current = head; for (int i = 0; i < step - 1; ++i) { current = current->next; } return current->student_id; // 返回新位置的学生编号 } // 主函数(用于演示) void josephus_game() { Node* list_head = create_circle_list(); int start = 1; // 起始位置 int step = get_queue_length(); // 根据学号后三位/2向下取整计算步长 while (/* 判断是否到达结束条件 */) { int next_student = josephus(list_head, start, step); // 更新链表并可能处理结果 start = next_student; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值