1. 程式人生 > >單鏈表面試題系列之帶環連結串列的入口點

單鏈表面試題系列之帶環連結串列的入口點

***單鏈表操作之帶環連結串列的入口點***

//  本篇博文闡述如何找到帶環連結串列的入口點,那麼,首先有必要闡述一下什麼是帶環連結串列?如何判斷連結串列是否帶環? 帶環連結串列 即連結串列中有迴圈的部分,通俗的說就是沒有尾節點!例如: 判斷連結串列是否帶環: 那麼知道了什麼是帶環連結串列,接下來就是判斷連結串列是否帶環的判斷問題了,其實也很簡單,首先最簡單的是判斷出不帶環的連結串列,只要可以找到尾結點即連結串列不帶環,那麼,帶環的連結串列怎麼判斷? 這裡就用到前面部落格講到的快慢指標了,定義兩個指標:slow,fast; fast每次走兩步,slow每次走一步,在連結串列帶環的情況下,slow和fast必然會相遇,而
且相遇點必然在環內,這個不難理解吧! 既然這樣,那我們就先實現判斷連結串列帶環的程式碼! @ 先統一列出連結串列的結構體和演算法中用到的操作函式(務必仔細閱讀):
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int DataType;

typedef struct LinkNode
{
	DataType data;
	struct LinkNode* next;
}LinkNode,*pLinkNode;//結點結構體

typedef struct LinkList
{
	LinkNode* pHead;//頭結點指標
}LinkList ,*pLinkList;//連結串列


void PushBack(pLinkList pList,DataType x)
{
	pLinkNode cur = NULL;
	pLinkNode pvr = NULL;
	pLinkNode newNode = (pLinkNode)malloc(sizeof(LinkNode ));
	if(newNode == NULL)
	{
		printf("out of memory\n");
		exit(0);	
	}
	assert(pList);
	cur = pList ->pHead ;
	newNode ->data = x;
	newNode ->next = NULL;

	if(cur == NULL)
	{
		pList ->pHead  = newNode ;
		return;
	}
	while(cur)
	{
		pvr = cur;
		cur = cur->next ;
	}
	pvr->next  = newNode ;

}//尾插


void InitLinkList(pLinkList pList)
{
	assert(pList);
	pList->pHead = NULL;

}//初始化列表

@ 判斷連結串列是否帶環演算法:
//構造有環連結串列
void MakeRing(pLinkList plist1)
{
	pLinkNode cur = NULL;
	pLinkNode pvr = NULL;
	assert(plist1);

	cur = plist1 ->pHead ;

	while(cur)
	{
		pvr = cur;
		cur = cur->next ;
	}
	if(pvr != NULL)
	pvr->next  = plist1 ->pHead ;//找到尾結點,讓它可以指向前面的結點!
}

//判斷連結串列是否帶環
int Judge_Ring(pLinkList pList)
{
	pLinkNode slow = NULL;
	pLinkNode fast = NULL;

	assert(pList);

	slow = fast = pList ->pHead ;//剛開始都指向第一個結點;

	while(fast && fast->next)
	{
		slow = slow->next ;//一次走一步
		fast = fast->next ->next ;//一次走兩步
		if(slow == fast)//如果相遇則直接返回1,代表有環;
			return 1;
	}
	return 0;//否則返回0,代表無環;
}

void test3()
{
	LinkList List1 ;//自己建立一個連結串列,方便測試;
	int ret = 0;

	InitLinkList(&List1);//初始化連結串列
    PushBack(&List1, 2);//尾插
	PushBack(&List1, 2);
	PushBack(&List1, 2);
	MakeRing(&List1);
	ret =  Judge_Ring (&List1);//接收函式返回值並進行判斷;
	if(ret == 1)
		printf("yes\n");
	else
		printf("no\n");
}

int main()
{
	test3();
	system("pause");
	return 0;
}
@ 既然已經會判斷連結串列是否帶環了,接下來就得找找這環的入口點,所謂入口點,就是環的開始的那個結點;現在如 何去找這個結點呢? 我再這裡將推理過程列了出來,因為畫工太差,就只有文字描述了; @ 其實,仔細想想的話,無非就是從表頭到入口點距離為 a,當知道相遇點後,從表頭開始一個指標start,而要通過環內指標找入口點,都是一次走一步,則環內指標要與start在入口點相遇,那麼最起碼環內指標走的距離也是a,這樣的話,方向就明確了,只要找到a的關係捋一下, S = a + x;   S = nr; 則 a + x  = nr;而指標此時開始走肯定是走的是 t 這段距離,那麼看看可不可以找到 a 和 t 的關係,既然環的一圈是r, 那麼 x+t = r,沒問題吧;則前面的 a + x =nr,是不是可以變為 a = (n - 1)r + t;  即從環內指標從 t 開始走,走過 n - 1 圈後,和start從表頭開始走,直到相遇時,走過的距離都是 a(注意:a表示的是從表頭到入口點的距離);即start和環內指標相遇時必然是入口點! 程式碼實現:
//有環則找出入口點
pLinkNode  FindEntry(pLinkList pList)
{
	pLinkNode start = NULL;//從表頭開始的指標;
	pLinkNode slow = NULL;//快慢指標判斷是否有環
	pLinkNode fast = NULL;
	pLinkNode meet = NULL;//用來表示相遇點的指標;

	assert(pList);

	//找相遇點指標的和判斷是否有環的部分一樣;
	slow = fast = pList ->pHead ;

	while(fast && fast->next)
	{
		slow = slow->next ;
		fast = fast->next ->next ;
		if(slow == fast)
			break;
	}
	meet = slow;//用meet來存放相遇點;

	if(fast == NULL || fast->next == NULL)
		return NULL;

	start = pList ->pHead ;

	while(start != meet)
	{
		start = start->next ;
		meet = meet->next ;
	}
	return start;//最後返回入口點;
}

void test4()
{
	LinkList List1 ;
	pLinkNode tmp = NULL;
	int ret = 0;

	InitLinkList(&List1);
    PushBack(&List1, 1);
	PushBack(&List1, 2);
	PushBack(&List1, 3);
	PushBack(&List1, 4);
	PushBack(&List1, 5);
	PushBack(&List1, 6);
	MakeRing(&List1);
	tmp = FindEntry (&List1);
	if(tmp != NULL)
		printf("%d\n", tmp->data );
	else
		printf("error!\n");
}
畫圖功底比較差,找入口點這裡也比較難理解,所以文字描述的比較詳細; 找入口點的方法還可以用雜湊表儲存指標的方法,後續會補充! 講的比較粗糙,還望諒解!