數據結構與算法之鏈表

  軟件設計中,最經常使用的兩種數據存儲結構就是順序存儲結構和鏈式存儲結構,順序存儲結構中用的最多的即是數組了,而鏈式存儲結構用的比較多的應該是單鏈表以及它們的變形。git

  單鏈表中只有一個指向下一個結點的指針,而且最後一個元素的next指針爲NULL;循環鏈表與單鏈表的區別就是最後一個指針指向頭結點;雙向鏈表中,每一個結點不只有指向下一個結點的next指針,還有一個指向前一個結點的prior指針,頭結點的prior和尾結點的next爲NULL。由於都是單鏈表的變形,因此這裏主要分析單鏈表。github

  與順序存儲空間不一樣,鏈式存儲空間並非連續的,數組中,直接經過索引來訪問元素。而單鏈表中它是經過每一個結點中的next指針來找到下一個結點的。算法

一、 單鏈表的結構體數組

  單鏈表的結構體中包含兩個元素,一個是數據元素,一個是指向下一個結點的地址。下列結構體中data的數據類型能夠是基本數據類型,也能夠是複雜數據類型。ui

typedef struct SingleListNode{
    int data;
    struct SingleListNode *next;
}ListNode;

二、單鏈表的建立spa

若是返回值爲NULL,則建立失敗,不然建立成功。設計

ListNode* allocNode(int data){
    ListNode *pNode;
    pNode = (ListNode*)malloc(sizeof(ListNode));

    if(NULL != pNode){
        memset(pNode,0,sizeof(ListNode));
        pNode->data = data;
    }
    return pNode;
}

三、單鏈表查找指針

若是找到對應的元素,返回結點地址,不然返回NULL。code

ListNode* findDataInSingleList(const ListNode *pListNode,int data){
    ListNode *pNode;
    if(NULL == pListNode)
        return NULL;
    if(data == pListNode->data)
        return (ListNode*)pListNode;
    pNode = pListNode->next;
    while(pNode){
        if(data == pNode->data)
            return pNode;
        pNode = pNode->next;
    }
    return NULL;
}

四、單鏈表的插入blog

找到鏈表的最後一個結點,即next爲NULL的結點,而後新建一個結點插入。

bool insertNodeIntoSingleList(ListNode **pListNode,int data){
    ListNode *pNode,*ppListNode;
    if(NULL == pListNode)
        return false;
    pNode = createSingleListNode(data);
    if(NULL == pNode)
        return false;
    if(NULL == *pListNode)
        *pListNode = pNode;
    else{
        ppListNode = *pListNode;
        while(ppListNode->next)
            ppListNode = ppListNode->next;
        ppListNode->next = pNode;
    }
    return true;
}

五、單鏈表的刪除

找到元素等於data的結點pNode,用指針pPre表示pNode的前一個結點,把pPre的next指向pNode的next結點,而後free pNode.

bool deleteFromSingleList(ListNode **pListNode,int data){
    if(NULL == pListNode || NULL == *pListNode)
        return false;
    ListNode *pNode = *pListNode->next;
    if(*pListNode->data == data){
        free(*pListNode);
        *pListNode = pNode;
        return true;
    }
    ListNode *pPre = *pListNode;
    while(pNode){
        if(pNode->data == data){
            pPre = pNode->next;
            free(pNode);
            return true;
        }
        pPre = pNode;
        pNode = pNode->next;
    }
    return false;
}

六、釋放鏈表

鏈表是經過mallic動態建立的,因此每次用完之後,必須free,以避免內存泄漏。

void freeSingleList(ListNode *pListNode){
    if(NULL == pListNode)
        return ;
    ListNode *pNode = NULL;
    while(pListNode){
        pNode = pListNode->next;
        free(pListNode);
        pListNode = pNode;
    }
}

 

關於鏈表的算法:

最經常使用的就是快慢指針,第七、八、9用的都是這個思想。

還有就是藉助第三指針的概念。其實對鏈表的操做都是對指針的操做,理解鏈表首先得理解指針。下面舉幾個經常使用的鏈表算法。

七、找出鏈表的中間結點

用兩個快慢指針,快指針每次走兩步,慢指針每次走一步,當快指針走到鏈表尾部時,慢指針所指向的結點即爲中間結點。

ListNode* findMidPositionOfList(const ListNode *pListNode){
    ListNode *pQuick = pListNode,*pSlow = pListNode:
    if(NULL == pListNode)
        return NULL;
    while(pQuick){
        pSlow = pSlow->next;
        pQuick = pQuick->next;
        if(NULL == pQuick)
            break;
        pQuick = pQuick->next;
    }
    return pSlow;
}

八、找出倒數第k個結點

快指針先走k步,而後快慢指針一塊兒走,知道快指針走到鏈表 末尾,慢指針即爲倒數第k個結點。

ListNode* findNodeCountBackwards(const ListNode *pListNode,int k){
    if(NULL == pListNode)
        return NULL;
    ListNode *pQuick = pListNode,*pSlow = pListNode:
    while(k--){
        if(NULL == pQuick)
            return NULL;
        pQuick = pQuick->next;
    }
    while(pQuick){
        pQuick = pQuick->next;
        pSlow = pSlow->next;
    }
    return pSlow;
}

 

九、判斷鏈表是否有環

快指針每次走兩步,慢指針每次走一步,當快指針走到NULL的時候,說明該鏈表確定不是循環鏈表,不然,當快指針==慢指針時,也就是快指針追上了慢指針,說明該鏈表有環。

bool judgeCircleInList(const ListNode *pListNode){
    if(NULL == pListNode)
        return false;
    ListNode *pQuick = pListNode,*pSlow = pListNode:
    while(pQuick){
        pQuick = pQuick->next;
        if(NULL == pQuick)
            return false;
        pQuick = pQuick->next;
        pSlow = pSlow->next;
        if(pQuick == pSlow)
            return true;
    }
}

 

十、判斷兩個鏈表是否有公共結點

判斷兩個鏈表有沒有公共結點的方法有不少,最簡單粗暴的就是窮竭搜索,可是時間複雜度爲o(n2)。還有就是把一個鏈表的尾結點與另外一個結點的首結點連起來組成一個鏈表,而後判斷這個鏈表有沒有環。固然還有其餘方法,這裏只寫了一種我的認爲比較簡單方法,既然有公共結點,那它們從某一個結點開始以後的結點都相等,因此它們的最後一個結點確定相等。因此只須要判斷兩個鏈表的最後一個結點是否相等即可。

bool judgeCommonNode(const ListNode *pList1,const ListNode *pList2){
    if(NULL == pList1 || NULL == pList2)
        return false;
    while(pList1->next)
        pList1 = pList1->next;
    while(pList2->next)
        pList2 = pList2->next;
    
    return pList1==pList2;
}

十一、找出兩個鏈表的首個公共結點

兩個鏈表有公共結點,那他們從最後一個結點到第一個公共結點都是相等的,長度也同樣,咱們只須要從兩個鏈表離尾結點相同距離處開始搜索,就能夠找到第一個公共結點了。

int countSizeOfList(const ListNode *pListNode){
    if(NULL == pListNode)
        return 0;
    int count = 0;
    while(pListNode){
        count ++;
        pListNode = pListNode->next;
    }
    return count;
}
ListNode* findFirstCommonNode(const ListNode *pList1,const ListNode *pList2){
    if(NULL == pList1 || NULL == pList2)
        return NULL;
    int size1 = countSizeOfList(pList1);
    int size2 = countSizeOfList(pList2);
    if(size1 > size2){
        int index = size1 - size2;
        while(index--)
            pList1 = pList1->next;
    }
    else if(size1 < size2){
        int index = size2 - size1;
        while(index--)
            pList2 = pList2->next;
    }
    while(pList1){
        if(pList1 == pList2)
            return pList1;
        pList1 = pList1->next;
        pList2 = pList2->next;
    }
}

十二、鏈表的反轉

鏈表的反轉能夠用棧來處理,也就是遞歸,可是遞歸自己效率過低,因此這裏咱們用循環來處理。用一個結點來保存上一個結點,用另外一個結點保存下一個結點,而後處理本結點。

ListNode* reverseList(ListNode *pListNode){
    ListNode *pGuard = NULL,*pNode = pListNode;
    if(NULL == pListNode)
        return NULL;
    pGuard = pListNode->next;
    pListNode->next = NULL;
    pListNode = pGuard;
    while(pListNode->next){
        pGuard = pListNode->next;
        pListNode->next = pNode;
        pNode = pListNode;
    }
    return pListNode;
}

完整代碼詳見:https://github.com/whc2uestc/DataStructure-Algorithm/tree/master/list

相關文章
相關標籤/搜索