1. 程式人生 > >資料結構與演算法(二)合併兩個有序連結串列

資料結構與演算法(二)合併兩個有序連結串列

附上部落格連結,歡迎大家前來交流學習
本系列的第一節概括性地簡單介紹了一下資料結構和演算法的概念,說實話有點虛,因為誰都知道連結串列和陣列是什麼,也都能說出雜湊和二叉樹,但真正有難度的是,在實際開發中如何去用這些資料結構,根據不同的開發需求選擇不同的資料結構和演算法,才是真正需要並且很難掌握的。

以後的章節中,我都會通過一道實際程式設計題目或者一個場景,針對一種資料結構或者演算法來解決問題,只有將資料結構和演算法用來解決實際問題,才有意義,這一節我們要解決的題目是,合併兩個有序連結串列。

題目要求:現在有兩個連結串列A和連結串列B,A中元素是升序排列的,B中元素是降序排列的,現在要求將A和B合併為一個升序排列的連結串列。

題目的意思很明確,相信大家一眼都能看明白,不過先彆著急寫程式碼,先分析分析題目的要求,首先是用到的資料結構自然是連結串列了,由於給定的連結串列已經是有序的了,因此不需要排序,不過我們注意到,兩個連結串列,一個是升序,一個是降序,這個需要額外注意,最後關於兩個連結串列的合併,涉及到連結串列的拆分,也是個難點。綜上所述,這道題的關鍵點有:
1、資料結構要用連結串列,並且是已經排序的連結串列
2、合併兩個連結串列,需要注意指標的指向
話不多說,直接擼程式碼,作為程式設計師,最好的武器就是程式碼,先簡單定義單向連結串列的結構體:

typedef struct ListNode
{
    ULONG NodeValue;
    ListNode *NextNode;
}ListNode;

這就是個最最簡單的單鏈表了,裡面儲存的資料是32位無符號整型資料,現在我們要考慮一下如何合併兩個連結串列,假如說兩個連結串列都是升序排列,那麼合併的方法就比較容易了,我們可以逐個比較兩個連結串列的元素,根據大小然後較小的元素進入到新的連結串列中,並且指向它的下一個,這一段的程式碼如下:

/* 合併兩個連結串列為一個連結串列 */
ListNode* MergeList(ListNode* ListHeadNodeOne, ListNode* ListHeadNodeTwo)
{
     if (NULL == ListHeadNodeOne)
     {
         return ListHeadNodeTwo;
     }
     else
if (NULL == ListHeadNodeTwo) { return ListHeadNodeOne; } ListNode* ListResultNode = NULL; if (ListHeadNodeOne->NodeValue < ListHeadNodeTwo->NodeValue) { ListResultNode = ListHeadNodeOne; ListResultNode->NextNode = MergeList(ListHeadNodeOne->NextNode, ListHeadNodeTwo); } else { ListResultNode = ListHeadNodeTwo; ListResultNode->NextNode = MergeList(ListHeadNodeOne, ListHeadNodeTwo->NextNode); } return ListResultNode; }

程式碼的思路很明確,入參就是兩個連結串列中需要比較的結點,如果說傳入的某一個結點為空,則直接返回另一個入參結點即可,如果說傳入的結點均不為空,則比較兩個結點所帶的資料的大小,將較小的結點返回,同時要注意到,程式碼中用到了遞迴,這點比較好理解,因為對於我們需要合併的某一個結點來說,完成一次合併後,其下一個結點,自然也能用相同的方法來找到,這樣就找到了遞迴開始的條件,作為一個遞迴演算法,還需要找到遞迴結束的條件,程式碼一開始的判斷,如果說有一個入參為空,這說明已經到連結串列末尾,如果說兩個入參均為空,這說明已經遍歷完兩個連結串列,整個遞迴結束,合併完成。

合併的演算法寫完了,但大家別忘記,題目中給的連結串列,一個是升序排列,一個是降序排列,而我們的合併演算法,前提是兩個連結串列均為升序排列才行,因此我們還需要寫一個將一個降序的連結串列反轉,這部分的程式碼如下:

/*反轉連結串列的全部元素*/
ListNode* InvertList(ListNode *ListHeadNode)
{
    ListNode* ListOldNode = NULL;
    ListNode* ListNewNode = NULL;
    ListNode* ListTempNode = NULL;

    ListOldNode = ListHeadNode;
    while (NULL != ListOldNode)
    {
        ListNode* ListTempNode2 = ListOldNode->NextNode;
        if (ListTempNode2 == NULL)
        {
            ListNewNode = ListOldNode;
        }
        ListOldNode->NextNode = ListTempNode;

        ListTempNode = ListOldNode;
        ListOldNode = ListTempNode2;
    }
    return ListNewNode;
}

這段程式碼實際上不難理解,只需要做到,記住將需要反轉的結點的下一個結點暫存起來,然後將需要反轉的結點與它指向的下一個結點交換指標位置,交換完畢以後,再將指標指向暫存的結點即可,這樣相當於是改變了連結串列指標的指向,同時要注意到,返回值是原先連結串列的連結串列尾,可以通過判斷下一結點是否為空,如果為空這說明到達連結串列末尾,此時就可以將結果返回,連結串列已經被反轉成功。

經過上面連結串列的合併與反轉,題目已經做完了,回顧一下這道題,不是很難,但是考察了對連結串列指標的掌握,如果說對於連結串列掌握不夠熟練,在實際程式設計中很容易出現訪問空指標這樣的異常情況,因此建議大家在寫程式碼時,要充分考慮程式碼的魯棒性和健壯性,寫完程式碼後儘可能再寫一些測試用例去測一下自己的程式碼是否能夠正常執行,順便附上我這個程式的執行畫面:
Before Merge List1 is :
Node 0, NodeValue is 0
Node 1, NodeValue is 1
Node 2, NodeValue is 2
Node 3, NodeValue is 3
Node 4, NodeValue is 4
Before Merge List2 is :
Node 0, NodeValue is 9
Node 1, NodeValue is 8
Node 2, NodeValue is 7
Node 3, NodeValue is 6
Node 4, NodeValue is 5

After Merge List is :
Node 0, NodeValue is 0
Node 1, NodeValue is 1
Node 2, NodeValue is 2
Node 3, NodeValue is 3
Node 4, NodeValue is 4
Node 5, NodeValue is 5
Node 6, NodeValue is 6
Node 7, NodeValue is 7
Node 8, NodeValue is 8
Node 9, NodeValue is 9

Process returned 0 (0x0) execution time : 0.283 s
Press any key to continue.

好了,這道題目就告一段落,想要原始碼的同學,可以私信聯絡我,或者直接在評論裡留下你的郵箱,我看到後會第一時間發過去的(看樣子是時候弄一個github玩玩嘍),下一節我們將利用一個數組實現min棧問題,敬請期待!