算法精解(三):C語言描述(鏈表常見問題)

1.如何判斷兩個單鏈表是否相交,如果相交,找出交點(兩個鏈表都不存在環)

如果兩個單鏈表相交,那應該呈“Y”字形,則交點之後的節點是相同的。

所以判斷是否相交,只需看兩個鏈表的最後一個節點是否爲同一個即可。

假設兩個單鏈表的長度分別爲L1、L2(L1 > L2),則(L1-L2)的值就是交匯之前兩個鏈表的長度差;

因此,只有讓更長的鏈表先走L1-L2步,然後兩個鏈表開始一起走,如果某次走到一個相同的節點,該節點即爲交點。

typedef struct ListNode{
	int data;
	struct ListNode *next;
}ListNode;               //定義節點

static int GetListLength(ListNode *head)
{
	int n=0;
	while(head != NULL)     
	{
		n++;
	}
	return n;
}

static ListNode * FindCommonNode(ListNode *a,ListNode *b)
{
	int i;
	int n1 = GetListLength(a);
	int n2 = GetListLength(b);
	
	//如果a<b,則互換a,b位置重新取長度
	if(a<b)
	{
		return FindCommonNode(b,a);
	}

	for(i=0;i<n1-n2;i++)  //較長的先走多出長度
	{
		a = a->next;
	}
	//相同長度時,同時向下走,指向相同時,則爲交點
	while(a != NULL && a != b)
	{
		a = a->next;
		b = b->next;
	}
	return a;
}

2.判斷一個鏈表是否有環,並找到環的入口點 

如果一個單鏈表有環,那應該呈“6”字形或“O”形。

設置兩個指針(fast, slow),初始值都指向頭節點,slow每次前進一步,fast每次前進二步,如果鏈表存在環,則fast必定先進入環,而slow後進入環,兩個指針必定 相遇:如果鏈表是呈"O"字形,則slow剛好遍歷完一次的時候,與fast相遇;如果呈“6”字形,則更早相遇。

當fast若與slow相遇時,slow還沒有遍歷完鏈表,而fast已經在環內循環了n圈(1<=n)。假設slow走了s步,則 fast走了2s步(fast步數還等於s 加上在環上多轉的n圈),設環長爲r,則:

2s = s + nr,簡化爲 s= nr

s = x + y,x爲鏈表起點到環入口點的距離,y是slow在環內走過的距離;

可以得到 x = y - s = y - nr,從鏈表頭、相遇點分別設一個指針(p1, p2),每次各走一步,當p1走過距離x時到達入口點,而p2走過的距離爲y-nr,y是相遇點與入口點的距離,因此y也走到了入口點,也就是說p1、p2在環入口點相遇了。

//鏈表是否有環
static ListNode* FindLoopPort(ListNode* head)
{
	ListNode* slow = head;
	ListNode* fast = head;
	
	//找相遇節點
	while(fast != NULL && fast->next != NULL)
	{
		slow = slow->next;
		fast = fast->next->next;
		if(slow == fast)
			break;
	}
	if(fast == NULL || fast->next ==NULL)  //走完鏈表,無環
		return NULL;
		
	//有環時,此時fast在相遇點,slow設置爲起點,再相遇時,爲環入口點
	slow = head;
	while(slow != fast)
	{
		slow = slow->next;
		fast = fast->next;
	}
	return slow;
}

 3.求一個單鏈表(無環)的中間節點

設置兩個指針(fast, slow),初始值都指向頭節點,slow每次前進一步,fast每次前進二步,當fast走到末尾時,slow剛好指向中間節點。

4.假如鏈表長度爲N,如何返回鏈表的倒數第K個結點

假設用兩個指針,指針P1先走K-1步,然後指針P2纔開始走,當指針P1遍歷完鏈表時,P2還剩K-1個結點沒有遍歷,此時,P2爲第K個節點,返回P2即可。

//求倒數第n個節點
ListNode *FindLastKNode(ListNode *Head, int n)
{
    ListNode* p1=head;
    ListNode* p2=head;
    while(--n == 0 && p1 != NULL)
    {
        p1 = p1->next;
    }
    if(p1 == NULL) return NULL;
    while(p1->next != NULL)
    {
        p2 = p2->next;
        p1 = p1->next;
    }
    return p2;
}

5.如何反轉一個單鏈表(或逆序輸出)

static Node* ReverseList(Node* Head)
{
	Node* pNode = Head;
	Node* pNext = NULL;
	Node* pPrev = NULL;

	while (pNode)
	{
		pNext = pNode->next;
		if (NULL == pNext)
		{
			Head = pNode;
		}
		pNode->next = pPrev; //斷開原本的鏈,反轉指向
		pPrev = pNode;       //將逆置後的頭,付給pPrev,pPrev始終爲逆置後鏈表的第一位
		pNode = pNext;       //將節點後推一位,循環反轉

	}
	return Head;
}

//遞歸實現逆置,此時默認爲有頭結點的鏈表
 Node* reverselist1(Node* head)
{
	static Node *h = head; //記錄頭結點 
	Node *c = NULL;
    //當遍歷到鏈表結尾部分是,a爲倒數第二位,b爲倒數第一位
	if (head == NULL || head->next == NULL)    
		return head; 
	c = reverselist1(head->next); //開始遞歸
	if (head != h)      //沒有返回到頭結點時,從最後一位依次逆轉指向,並把逆轉後的節點返回,繼續進行逆轉
	{
		head->next->next = head;
		head->next = NULL;
		return c;
	}
	else        //返回到頭結點時,將已經全部逆置的鏈表附在頭結點後面,完成逆置。返回頭結點。
	{
		head->next = c;
		return head;
	}
	
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章