如何檢查一個單向鏈表上是否有環?

1, 最簡單的方法, 用一個指針遍歷鏈表, 每遇到一個節點就把他的內存地址(java中能夠用object.hashcode())作爲key放在一個hashtable中. 這樣當hashtable中出現重複key的時候說明此鏈表上有環. 這個方法的時間複雜度爲O(n), 空間一樣爲O(n).java

2, 使用反轉指針的方法, 每過一個節點就把該節點的指針反向:node

<!-- lang: cpp -->
Boolean reverse(Node *head) {
	Node *curr = head;
	Node *next = head->next;
	curr->next = NULL;

	while(next!=NULL) {
		if(next == head) { /* go back to the head of the list, so there is a loop */
			next->next = curr;
			return TRUE;

		}

		Node *temp = curr;
		curr = next;
		next = next->next;
		curr->next = temp;
	}

	/* at the end of list, so there is no loop, let's reverse the list back */
	next = curr->next;
	curr ->next = NULL;
	while(next!=NULL) {
		Node *temp = curr;
		curr = next;
		next = next->next;
		curr->next = temp;
	}
	return FALSE;
}

看上去這是一種奇怪的方法: 當有環的時候反轉next指針會最終走到鏈表頭部; 當沒有環的時候反轉next指針會破壞鏈表結構(使鏈表反向), 因此須要最後把鏈表再反向一次. 這種方法的空間複雜度是O(1), 實事上咱們使用了3個額外指針;而時間複雜度是O(n), 咱們最多2次遍歷整個鏈表(當鏈表中沒有環的時候).面試

這個方法的最大缺點是在多線程狀況下不安全, 當多個線程都在讀這個鏈表的時候, 檢查環的線程會改變鏈表的狀態, 雖然最後咱們恢復了鏈表自己的結構, 可是不能保證其餘線程能獲得正確的結果.安全

3, 這是通常面試官所預期的答案: 快指針和慢指針多線程

<!-- lang: cpp -->
Boolean has_loop(Node *head) {
    Node *pf = head; /* fast pointer */
    Node *ps = head; /* slow pointer */

	while(true) {
		if(pf && pf->next)
			pf = pf->next->next;
		else
			return FALSE;

		ps = ps->next;

		if(ps == pf)
			return TRUE;
	}
}

須要說明的是, 當慢指針(ps)進入環以後, 最多會走n-1步就能和快指針(pf)相遇, 其中n是環的長度. 也就是說快指針在環能不會跳過慢指針, 這個性質能夠簡單的用概括法來證實. (1)當ps在環中位置i, 而pf在環中位置i-1, 則在下一個iteration, ps會和pf在i+1相遇.
(2)當ps在環中位置i, 而pf在環中位置i-2, 則在下一個iteration, ps在i+1, pf在i, 因而在下一個iteration ps和pf會相遇在i+2位置
(3)和上面推理過程相似, 當ps在i, pf在i+1, 則他們會通過n-1個iteration在i+n-1的位置相遇. 因而慢指針的步數不會超過n-1.oop

擴展:線程

這個問題還有一些擴展, 例如, 如何找到環的開始節點? 如何解開這個環? 這些問題的本質就是如何找到有"回邊"的那個節點.指針

咱們能夠利用方法3的一個變形來解決這個問題:code

<!-- lang: cpp -->
Boolean has_loop(Node *head) {
	Node *pf = head; /* fast pointer */
	Node *ps = head; /* slow pointer */

	while(true) {  /* step 1, is there a loop? */
		if(pf && pf->next)
			pf = pf->next->next;
		else
			return FALSE;

		ps = ps->next;

		if(ps == pf)
			break;
	}

	/* step 2, how long is the loop */
	int i = 0;
	do {
		ps = ps->next;
		pf = pf->next->next;
		i++;
	} while(ps!=pf)

	/* step 3, use 2 addtional pointers with distance i to break the loop */
	ps = head;
	pf = head;

	int j;
	for(j=0; j<i; j++) { pf = pf->next; }
	
	j = 0;
	while(ps!=pf) {
		ps = ps->next;
		pf = pf->next;
		j++;
	}

	printf("loop begins at position %d, node address is %x", j, ps);

	/*step 4, break the loop*/
	for(j=0; j<=i; j++) { ps = ps->next; } //step i-1 in the loop from loop beginning
	ps->next = NULL; // break the loop
	return TRUE;
}
相關文章
相關標籤/搜索