算法-鏈表之簡單算法題

上一階段的字符串系列之后很長時間都沒有更新文章,現在接著來,本階段是鏈表系列的,鏈表系列的算法題我不會再用大量篇幅寫一個問題的解決方案,針對一個問題寫一個解決方案,所以篇幅會小很多。這篇文章會介紹和鏈表相關的簡單算法題,后面還會介紹復雜算法題。

  • 添加/刪除結點
  • 從尾到頭打印鏈表
  • 翻轉單鏈表
  • 在O(1)時間內刪除鏈表結點

1.添加/刪除結點

我們都知道,鏈表的添加和刪除操作相比數組是很方便的,因為鏈表不要求結點的物理順序和邏輯順序相同,所以添加刪除結點的時候不需要像數組一樣移動大量的結點,借助指針,修改指針指向,我們就可以很方便的實現添加和刪除結點的操作。

添加/刪除結點是鏈表類算法題中比較簡單的操作了,不會有太大問題。主要是在注意細節,添加/刪除的時候要注意判斷鏈表是否為空,如果鏈表為空,就要修改頭指針的指向,也就是修改頭指針指向的地址。

代碼

鏈表的數據結構的定義:
注意:這些聲明中包含了一個自引用結構的列子。在定義結構ListNode之前,已經定義了指向該結構的指針。C語言允許定義指向尚不存在的類型的指針。

typedef struct ListNode *list_pointer;
typedef struct ListNode
{
    int value;
    list_pointer link;
};
list_pointer pHead;

在鏈表尾添加節點
有在前端,中間插入結點的,也有在尾部,這里的例子是在鏈表尾插入結點。注意,如果鏈表為空,那么添加結點需要修改頭指針的指向,所以這里接收的參數是頭指針的地址。

//pHead是指向指針的指針  ListNode** p
void addToTail(list_pointer *pHead, int value){

     list_pointer node = (list_pointer)malloc(sizeof(ListNode));
     if (node == NULL)
     {
        fprintf(stderr, "Faile\n");
        exit(1);
     }
    node->value = value;
    node->link = NULL;

    if (*pHead == NULL)
    {
    *pHead = node;
    }
    else{
        list_pointer p = *pHead;
        while (p->link != NULL){
            p = p->link;
        }
        p->link = node;
   }
}

刪除中間結點:
刪除指定值的結點,我們不知道該結點在什么位置,所以需要遍歷鏈表找到結點。

//如果刪除首節點,那么需要改變首節點指針的指向
bool deleteNode(list_pointer *pHead, int value){
    if (*pHead == NULL)
    {
        fprintf(stderr, "The linklist is empty!\n");
        exit(1);
     }
    ListNode *node = NULL;
    if ((*pHead)->value == value){//刪除首節點
        node = *pHead;
        *pHead = (*pHead)->link;
        free(node);
        return true;
    }
    else{
        list_pointer p = *pHead;
        while (p->link != NULL && p->link->value != value){
            p = p->link;
        }
        if (p->link != NULL && p->link->value == value)
        {
            node = p->link;
            p->link = p->link->link;
            free(node);
        }
    }

}

2.從尾到頭打印鏈表

看到這到道題的第一反應是從頭到尾打印鏈表會比較簡單,所以我們可以改變鏈表的指針指向,但是這樣會改變鏈表原來的結構,是否允許改變鏈表的結構,這個取決于面試官。這里的例子是不改變鏈表結構。

算法思想

從尾到頭打印鏈表也就是說先存入的元素后輸出,后存入的先輸出,和棧“后進先出”的思想一樣,所以我們可以在遍歷鏈表的時候先將元素存入棧中,再循環遍歷棧輸出元素。
想到了使用棧,而遞歸的本質就是使用棧存儲,那么我們使用遞歸也能實現同樣的效果,下面的例子就是用遞歸實現的。
代碼:

void PrintListReversingly(list_pointer pHead) {
    list_pointer p = pHead;
    if (p) {
        if (p->link) {
            PrintListReversingly(p->link);
        }
        printf("鏈表元素:%d\n", p->value);      
    }
}

使用遞歸代碼會簡潔很多,但是當鏈表非常長的時候,函數調用的層級會很深,可能會導致函數調用棧溢出,顯式的使用棧基于循環來遍歷的魯棒性會好一些。

3.翻轉單鏈表

題目:寫一個函數,輸入鏈表的頭結點,翻轉該鏈表,并返回翻轉后的鏈表的頭結點。

算法思想

翻轉鏈表

看上面的圖,如果是對結點i翻轉鏈表,就是改變i的link的指向,將它指向前一個結點h,但是這樣會導致i指向j的鏈丟失,所以我們需要存儲下這個值,也就是說,在一次改變指針指向的操作中,我們需要存儲下前一個結點h,后一個結點j,和當前結點i。此外,我們還需要明確,在翻轉了鏈表之后,原來的頭結點變成了尾結點,而現在的尾節點呢,就是NULL。分析完這些之后很容易寫出代碼。

代碼

//翻轉鏈表
list_pointer Invert(list_pointer pHead)
{
    list_pointer middle, trail;
    middle = NULL;
    //當pHead指向原鏈表的最后一個結點的link時,退出循環
    //此時middle剛好指向原鏈表的最后一個結點
    while (pHead) {
        trail = middle;
        middle = pHead;//此時middle和pHead指向的地址相同
        pHead = pHead->link;
        middle->link = trail;
    }
    return middle;
}

4.在O(1)時間內刪除鏈表結點

常規的思維,刪除鏈表結點需要知道它的前一個結點,簡單的三句代碼,就是判斷p->link->value == value,然后修改p->link = p->link->link,釋放p->link.這樣就需要遍歷鏈表,知道要刪除的結點的前一個,時間復雜度為O(n)。如果換一種思路呢?

算法思想

假設結點h,i,j是鏈表中相鄰的3個結點,現在要刪除i,可以進行下面3步:

  1. 將結點j的值復制到結點i上;
  2. 修改i的link;
  3. 釋放結點j。
    現在需要考慮一下特殊情況下是否也滿足。當刪除的節點是頭結點時,需要修改頭結點的link指針。當刪除的是尾結點時,它沒有下一個結點,所以需要遍歷鏈表,得到它的前一個結點。當鏈表只有一個結點時,刪除的結點是頭結點(尾節點),需要將頭指針的指向置為NULL。

代碼

//在O(1)內刪除一個結點
void DeleteNode(list_pointer *pHead, ListNode *node)
{
    if (!(*pHead) || !node)
        return;

    //要刪除的不是尾結點
    if (node->link)
    {
        ListNode *pNext = node->link;
        node->value = pNext->value;
        node->link = pNext->link;
        free(pNext);
    }
    //鏈表中只有一個結點刪除頭結點,也是尾結點
    else if (*pHead == node)//node->link為NULL
    {
        free(node);
        *pHead = NULL;
    }
    else//鏈表中有多個結點,刪除的是尾結點
    {
        list_pointer p = *pHead;
        while (p->link != node)
        {
            p = p->link;
        }
        p->link = NULL;
        free(node);
    }
}

總結

這些題難度都不大,使用指針很靈活,但是也很容易出錯,主要是關注細節。這篇就到這里了,不足之處,還請多指教~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容