【數據結構|劍指Offer】單向鏈表的各項操作實現

本博文着重實現《劍指Offer》上面的單向鏈表操作。

//數據結構
struct ListNode
{
	int data;
	ListNode *next;
	ListNode(int x) :data(x), next(NULL) {}
};
//在鏈表尾部添加結點
void AddToTail(ListNode **pHead, int value)
{
	ListNode *pNew = new ListNode(value);
	assert(pNew != NULL);

	if (NULL == *pHead)//原鏈表爲空
		*pHead = pNew;
	else
	{
		ListNode *pNode = *pHead;

		while (pNode->next)
			pNode = pNode->next;

		pNode->next = pNew;
	}
}
/*單向鏈表刪除操作需要找到前一個結點
二級指針操作是因爲會可能修改頭結點指針*/
void RemoveNode(ListNode **pHead, int value)
{
	if (NULL == pHead || NULL == *pHead)
		return;

	ListNode *pToDeleted = NULL;
	if ((*pHead)->data == value)
	{
		pToDeleted = *pHead;
		*pHead = (*pHead)->next;
	}
	else
	{
		ListNode *pNode = *pHead;

		while (pNode->next && pNode->next->data != value)//找到待刪除結點的前結點
			pNode = pNode->next;

		if (pNode->next && pNode->next->data == value)
		{
			pToDeleted = pNode->next;
			pNode->next = pNode->next->next;
		}
	}

	if (pToDeleted != NULL)
	{
		delete pToDeleted;
		pToDeleted = NULL;
	}
}
//反向打印:遞歸。鏈表過長會導致棧溢出
void PrintReverse(ListNode *pHead)
{
	if (pHead)
	{
		PrintReverse(pHead->next);
		cout << pHead->data << endl;
	}
}

//反向打印:棧
void PrintReverse_iteratively(ListNode *pHead)
{
	stack<ListNode *> nodes;

	ListNode *pNode = pHead;
	while (pNode)
	{
		nodes.push(pNode);
		pNode = pNode->next;
	}

	while (!nodes.empty())
	{
		pNode = nodes.top();
		cout << pNode->data << endl;
		nodes.pop();
	}
}
/*
給定單向鏈表的頭指針和一個結點指針,定義一個函數在O(1)時間刪除該結點
前提是這個結點是存在於這個鏈表中,這個由調用者保證
 
其實現思想:把下一個結點的內容複製到待刪除結點上覆蓋原有的內容,然後刪除下一個結點。
就是操作結點的數據而不是鏈表的鏈接關係。平衡二叉樹的平衡旋轉也是採用這個思想
*/
void DeleteNode(ListNode **pHead, ListNode *pToBeDeleted)
{
	if (NULL == pHead || NULL == *pHead)
		return;

	//要刪除的結點不是尾節點
	if (pToBeDeleted->next)
	{
		ListNode *pNext = pToBeDeleted->next;
		pToBeDeleted->data = pNext->data;//操作數據而非鏈接關係
		pToBeDeleted->next = pNext->next;

		delete pNext;
		pNext = NULL;
	}
	//鏈表只有一個結點
	else if (*pHead == pToBeDeleted)
	{
		delete pToBeDeleted;
		pToBeDeleted = NULL;
		*pHead = NULL;
	}
	//鏈表有多個結點,且待刪除結點是尾節點
	else//這部分時間複雜度爲O(n),但總的平均時間複雜度爲O(1)
	{
		ListNode *pNode = *pHead;
		while (pNode->next != pToBeDeleted)
			pNode = pNode->next;

		pNode->next = NULL;
		delete pToBeDeleted;
		pToBeDeleted = NULL;
	}
}
/*輸入一個鏈表,輸出該鏈表中倒數第 k 個結點
思路一:棧*/
ListNode* PrintKNode(ListNode *pHead, int k)
{
	if (NULL == pHead || k < 1)
		return NULL;

	stack<ListNode *> nodes;
	ListNode *pNode = pHead;
	while (pNode)
	{
		nodes.push(pNode);
		pNode = pNode->next;
	}

	while (!nodes.empty())
	{
		--k;
		if (0 == k)
		{
			pNode = nodes.top();
			return pNode;
		}
		nodes.pop();
	}
	return NULL;
}
/*思路二:快慢指針。快慢指針之間的距離爲k-1,當快指針到達尾結點時,慢指針恰好是倒數第k個結點*/
ListNode* FindKthToTail(ListNode *pHead, int k)
{
	if (NULL == pHead || k < 1)
		return NULL;

	ListNode *pAhead = pHead;
	ListNode *pBehind = NULL;

	for (int i = 0; i < k - 1; ++i)
	{
		if (pAhead->next)
			pAhead = pAhead->next;
		else
			return NULL;
	}

	pBehind = pHead;
	while (pAhead->next)
	{
		pAhead = pAhead->next;
		pBehind = pBehind->next;
	}

	return pBehind;
}
/*反轉鏈表:輸入一個鏈表的頭結點,反轉該鏈表並輸出反轉後鏈表的頭結點
充分考慮異常情況,尤其是清楚最後反轉後的頭結點*/
ListNode* ReverseList(ListNode *pHead)
{
	if (NULL == pHead || pHead->next == NULL)
		return pHead;

	ListNode *pPrev = NULL;
	ListNode *pNode = pHead;
	while (pNode)
	{
		ListNode *pNext = pNode->next;

		pNode->next = pPrev;
		pPrev = pNode;//這就是最後反轉後的頭結點
		pNode = pNext;
	}	
	return pPrev;
}
/*合併兩個排序的鏈表,合併後的鏈表仍是排序好的(遞增)*/
/*未排序區的兩個鏈表值小的那個頭結點,將是未排序區合併後的頭結點,同時也將作爲排序區的尾結點。
以此遞歸,每次只需比較剩下未排序的兩個鏈表的頭結點*/
ListNode* MergeList(ListNode *pHead1, ListNode *pHead2)
{
	if (NULL == pHead1)
		return pHead2;
	else if (NULL == pHead2)
		return pHead1;

	ListNode *pMergedHead = NULL;

	if (pHead1->data < pHead2->data)
	{
		pMergedHead = pHead1;
		pMergedHead->next = MergeList(pHead1->next, pHead2);
	}
	else
	{
		pMergedHead = pHead2;
		pMergedHead->next = MergeList(pHead1, pHead2->next);
	}
	return pMergedHead;
}
/*輸入兩個鏈表,找出它們的第一個公共結點
下面鏈表L1有6個結點,L2有4個結點,其第一個公共結點爲mn1*/
/*
L1:   n1 -> n2 -> n3 -> n4   
                          \
						   ——> mn1 -> mn2
						  /
L2:               m1 -> m2
*/
/*
實現思想:長的鏈表先前進若干結點到與短鏈表等長位置,然後同步前進
充分考慮異常情況
*/
int GetLength(ListNode *pHead)
{
	int leng = 0;
	while (pHead)
	{
		leng++;
		pHead = pHead->next;
	}
	return leng;
}

ListNode* FindFirstCommonNode(ListNode *L1, ListNode *L2)
{
	if (NULL == L1 || NULL == L2)
		return NULL;
	int leng1 = GetLength(L1);
	int leng2 = GetLength(L2);

	int distance = (leng1 > leng2) ? leng1 - leng2 : leng2 - leng1;
	ListNode *pFirst = NULL, *pSecond = NULL;
	if (leng1 > leng2)
	{
		pFirst = L1;
		pSecond = L2;
	}
	else
	{
		pFirst = L2;
		pSecond = L1;
	}
		
	while (distance--)//考慮非公共部分長度一樣情況
	{
		pFirst = pFirst->next;
	}

	while (pFirst && pSecond)
	{
		if (pFirst == pSecond)//尋找第一個公共結點
			return pFirst;
		pFirst = pFirst->next;
		pSecond = pSecond->next;
	}
	return NULL;//不存在公共結點情況
}
/*鏈表中環的入口結點
最常規解法:快慢指針,關鍵在於定位環的入口結點,
具體分析參見博文:http://blog.csdn.net/wenqian1991/article/details/17452715 */
ListNode* EntryNodeOfLoop(ListNode *pHead)
{
	if (NULL == pHead)
		return NULL;

	ListNode *pFast = pHead;
	ListNode *pSlow = pHead;

	//判斷是否有環
	while (pFast->next)//兩種情況跳出循環
	{
		pFast = pFast->next->next;
		pSlow = pSlow->next;
		
		if (NULL == pFast)
			return NULL;
		if (pFast == pSlow)//有環
			break;
	}

	if (NULL == pFast->next)//沒環
		return NULL;

	//尋找環的入口結點
	/*快指針移動至鏈表頭結點,同時兩結點以相同速度再次相遇就是環的開始節點
	http://blog.csdn.net/wenqian1991/article/details/17452715 */
	pFast = pHead;
	while (pFast != pSlow)
	{
		pFast = pFast->next;
		pSlow = pSlow->next;
	}

	return pFast;
}


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