數(shù)據(jù)結(jié)構(gòu):鏈表

本文內(nèi)容:
1、 什么是鏈表?
2、 鏈表共分幾類?
3、 鏈表的 C 實(shí)現(xiàn)!

總表:《數(shù)據(jù)結(jié)構(gòu)?》

工程代碼 Github: Data_Structures_C_Implemention -- Link List


1、什么是鏈表?

鏈表 [Linked List]:鏈表是由一組不必相連【不必相連:可以連續(xù)也可以不連續(xù)】的內(nèi)存結(jié)構(gòu) 【節(jié)點(diǎn)】,按特定的順序鏈接在一起的抽象數(shù)據(jù)類型。

補(bǔ)充:
抽象數(shù)據(jù)類型(Abstract Data Type [ADT]):表示數(shù)學(xué)中抽象出來的一些操作的集合。
內(nèi)存結(jié)構(gòu):內(nèi)存中的結(jié)構(gòu),如:struct、特殊內(nèi)存塊...等等之類;


2、鏈表共分幾類?

鏈表常用的有 3 類: 單鏈表、雙向鏈表、循環(huán)鏈表。
鏈表.png

鏈表的核心操作集有 3 種:插入、刪除、查找【遍歷】

單鏈表

單鏈表 [Linked List]:由各個內(nèi)存結(jié)構(gòu)通過一個 Next 指針鏈接在一起組成,每一個內(nèi)存結(jié)構(gòu)都存在后繼內(nèi)存結(jié)構(gòu)【鏈尾除外】,內(nèi)存結(jié)構(gòu)由數(shù)據(jù)域和 Next 指針域組成。

單鏈表實(shí)現(xiàn)圖示:

文字解析:

  • Data 數(shù)據(jù) + Next 指針,組成一個單鏈表的內(nèi)存結(jié)構(gòu) ;
  • 第一個內(nèi)存結(jié)構(gòu)稱為 鏈頭,最后一個內(nèi)存結(jié)構(gòu)稱為 鏈尾;
  • 鏈尾的 Next 指針設(shè)置為 NULL [指向空];
  • 單鏈表的遍歷方向單一【只能從鏈頭一直遍歷到鏈尾】

單鏈表操作集:
單向鏈表-操作.png
雙向鏈表

雙向鏈表 [Double Linked List]:由各個內(nèi)存結(jié)構(gòu)通過指針 Next 和指針 Prev 鏈接在一起組成,每一個內(nèi)存結(jié)構(gòu)都存在前驅(qū)內(nèi)存結(jié)構(gòu)和后繼內(nèi)存結(jié)構(gòu)【鏈頭沒有前驅(qū),鏈尾沒有后繼】,內(nèi)存結(jié)構(gòu)由數(shù)據(jù)域、Prev 指針域和 Next 指針域組成。

雙向鏈表實(shí)現(xiàn)圖示:

文字解析:

  • Data 數(shù)據(jù) + Next 指針 + Prev 指針,組成一個雙向鏈表的內(nèi)存結(jié)構(gòu);
  • 第一個內(nèi)存結(jié)構(gòu)稱為 鏈頭,最后一個內(nèi)存結(jié)構(gòu)稱為 鏈尾;
  • 鏈頭的 Prev 指針設(shè)置為 NULL, 鏈尾的 Next 指針設(shè)置為 NULL;
  • Prev 指向的內(nèi)存結(jié)構(gòu)稱為 前驅(qū), Next 指向的內(nèi)存結(jié)構(gòu)稱為 后繼;
  • 雙向鏈表的遍歷是雙向的,即如果把從鏈頭的 Next 一直到鏈尾的[NULL] 遍歷方向定義為正向,那么從鏈尾的 Prev 一直到鏈頭 [NULL ]遍歷方向就是反向;

雙向鏈表操作集:
雙向鏈表-操作.png
循環(huán)鏈表

單向循環(huán)鏈表 [Circular Linked List] : 由各個內(nèi)存結(jié)構(gòu)通過一個指針 Next 鏈接在一起組成,每一個內(nèi)存結(jié)構(gòu)都存在后繼內(nèi)存結(jié)構(gòu),內(nèi)存結(jié)構(gòu)由數(shù)據(jù)域和 Next 指針域組成。

雙向循環(huán)鏈表 [Double Circular Linked List] : 由各個內(nèi)存結(jié)構(gòu)通過指針 Next 和指針 Prev 鏈接在一起組成,每一個內(nèi)存結(jié)構(gòu)都存在前驅(qū)內(nèi)存結(jié)構(gòu)和后繼內(nèi)存結(jié)構(gòu),內(nèi)存結(jié)構(gòu)由數(shù)據(jù)域、Prev 指針域和 Next 指針域組成。

循環(huán)鏈表的單向與雙向?qū)崿F(xiàn)圖示:

文字解析:

  • 循環(huán)鏈表分為單向、雙向兩種;
  • 單向的實(shí)現(xiàn)就是在單鏈表的基礎(chǔ)上,把鏈尾的 Next 指針直接指向鏈頭,形成一個閉環(huán);
  • 雙向的實(shí)現(xiàn)就是在雙向鏈表的基礎(chǔ)上,把鏈尾的 Next 指針指向鏈頭,再把鏈頭的 Prev 指針指向鏈尾,形成一個閉環(huán);
  • 循環(huán)鏈表沒有鏈頭和鏈尾的說法,因?yàn)槭情]環(huán)的,所以每一個內(nèi)存結(jié)構(gòu)都可以充當(dāng)鏈頭和鏈尾;

循環(huán)鏈表操作集:
循環(huán)鏈表-操作.png

3、 鏈表的 C 實(shí)現(xiàn)!

單鏈表
  • 節(jié)點(diǎn)[內(nèi)存結(jié)構(gòu)]與鏈表的定義:

節(jié)點(diǎn)[內(nèi)存結(jié)構(gòu)],

struct __ListNode {
    ElementTypePrt data; // 數(shù)據(jù)域
    ListNode next; // 指針域 [指向下一個節(jié)點(diǎn)]
};

解析:struct __ListNode 就是一個節(jié)點(diǎn)的內(nèi)存結(jié)構(gòu),因?yàn)橐粋€節(jié)點(diǎn)包含了兩個不同的信息,而且相互沒有共享內(nèi)存,所以在 C 語言環(huán)境下選擇 struct 去表示;

1、ElementTypePrt 原型是 typedef void * ElementTypePrt;,使用 typedef 是方便后期進(jìn)行修改;這里使用指針的目的是為了讓鏈表支持更多的數(shù)據(jù)類型,使代碼的可擴(kuò)展性更強(qiáng);【如: data 可以直接指向一個單純的 int 或者 一個 struct ,又或者是一個 list 等等】

2、ListNode 原型是

typedef struct __ListNode * _ListNode;
typedef _ListNode ListNode;

這里使用 typedef 是為了后面定義函數(shù)接口,以及具體代碼實(shí)現(xiàn)更為簡潔;

單鏈表【有時也簡稱,鏈表】,

struct __List {
    unsigned int size;  // 鏈表的長度
    MatchFunc matchFunc; // 兩個節(jié)點(diǎn)的數(shù)據(jù)匹配
    DestroyFunc destroyFunc; // 節(jié)點(diǎn)數(shù)據(jù)的釋放
    ListNode head; // 鏈表的鏈頭指針
    ListNode tail; // 鏈表的鏈尾指針
};

解析:
1、MatchFunc 原型是 typedef _BOOL(*MatchFunc) (const void *key1, const void *key2); 指向形如 _BOOL(*) (const void *key1, const void *key2); 的函數(shù)指針;原因,上面提到過 data 是可以指向任意類型的,也就是說兩個節(jié)點(diǎn)的 data 怎樣才算匹配,設(shè)計(jì)者是不知道的,只有使用者才知道,所以提供一個函數(shù)接口,讓使用者根據(jù)自身的情況進(jìn)行數(shù)據(jù)匹配;也是為了代碼可擴(kuò)展性;

2、_BOOL 原型是

typedef enum __BOOL {
    LINKEDLIST_TRUE  = 1,
    LINKEDLIST_FALSE = 0,
}_BOOL;

目的是為了提高代碼的可讀性,不建議在代碼中直接使用數(shù)字,因?yàn)檎l也不知道那是個啥;【過個幾天,你自己估計(jì)也會忘掉,那個數(shù)字表示啥】

3、DestroyFunc 原型是 typedef void(*DestroyFunc) (void * data); 指向形如 void(*) (void * data); 的函數(shù)指針;data 如何進(jìn)行釋放也由使用者決定;也是為了代碼可擴(kuò)展性;

4、size + head + tail 是鏈表的基本要素,當(dāng)然您也可以根據(jù)自身情況,增加個別要素;

  • 單鏈表的核心操作集:
/* Create */ 

List List_Create(DestroyFunc des);
void List_Init(List l, DestroyFunc des);
void List_Destroy(List l);

/* Operations */

Position List_Find(List l, MatchFunc mat, ElementTypePrt const x);
Position List_FindPrevious(List l, MatchFunc mat, ElementTypePrt const x);

_BOOL List_Insert(List l, Position p, ElementTypePrt const x);
_BOOL List_Remove(List l, Position deletePrev, ElementTypePrtPrt const x);
  • 單鏈表的創(chuàng)建與銷毀:

創(chuàng)建,

List List_Create(DestroyFunc des) {
    
    List l = (List)(malloc(sizeof(struct __List)));  // 1
    if (l == NULL) { printf("ERROR: Out Of Space !"); return NULL; } // 2

    List_Init(l, des); // 3

    return l;

}

解析:
1、List_Create 函數(shù)功能是創(chuàng)建并初始化一個空鏈表;
// 1 行 1 ,malloc 函數(shù)是 C 語言中進(jìn)行內(nèi)存創(chuàng)建的函數(shù),需要提供的參數(shù)是 size_t ,就是空鏈表的要占的內(nèi)存大小,所以使用 sizeof 來計(jì)算內(nèi)存大小;

2、// 2 行 2 是防止,內(nèi)存分配失敗;

3、// 3 請移步下面的 初始化 解析;

初始化,

void List_Init(List l, DestroyFunc des) {

    if (l == NULL) { printf("ERROR: Bad List !"); return; }

    l->size = LINKEDLIST_EMPTY; // LINKEDLIST_EMPTY 就是 0
    l->matchFunc = NULL;
    l->destroyFunc = des;
    l->head = NULL;
    l->tail = NULL;

}

解析:
1、List_Init 目的就是要把一個創(chuàng)建好的表初始化為需要的空表狀態(tài);

2、LINKEDLIST_EMPTY 原型是 #define LINKEDLIST_EMPTY 0 提高代碼可讀性;

3、l->head = NULL; l->tail = NULL; 因?yàn)闆]有節(jié)點(diǎn),直接置空即可;

銷毀,

void List_Destroy(List l) {
    
    if (l == NULL) { printf("ERROR: Please Using List_Create(...) First !"); return; }

    ElementTypePrt data;

    while (!List_IsEmpty(l)) { // 1
        if ((List_Remove(l, NULL, (ElementTypePrtPrt)&data) == LINKEDLIST_TRUE) &&
            (l->destroyFunc != NULL)) { // 2
            
            l->destroyFunc(data);
        }
    }

    memset(l, 0, sizeof(struct __List)); // 3
    
}

解析:這個函數(shù)的功能就是,釋放鏈表中所有的節(jié)點(diǎn),并把鏈表置成空鏈表;
1、 // 1 List_IsEmpty 原型是

_BOOL List_IsEmpty(List l) { return ((l->size == LINKEDLIST_EMPTY) ? LINKEDLIST_TRUE
                                                                   : LINKEDLIST_FALSE); }

2、// 2 請移步下面 刪除操作 的解析;

3、 // 3 memset 原型是 void* memset(void* _Dst, int _Val, size_t _Size); 功能是,設(shè)置內(nèi)存塊的值, 這三個參數(shù)分別表示,內(nèi)存塊個數(shù)、設(shè)置的內(nèi)存單元的值、要設(shè)置的內(nèi)存空間大小;

  • 插入操作:
_BOOL List_Insert(List l, Position p, ElementTypePrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }

    /* Create New Node */
    ListNode lNew = ListNode_Create(x);  // 1

    int isLastNode = (p == NULL); // 2

    if (isLastNode) {
    
        if (List_IsEmpty(l)) { l->tail = lNew; }

        /* Insert Operations */ // 3
        lNew->next = l->head;
        l->head = lNew;

    } else {

        if (p->next == NULL) { l->tail = p; }

        /* Insert Operations */ // 4
        lNew->next = p->next;
        p->next = lNew;

    }

    /* Size ++ */
    l->size++;

    return LINKEDLIST_TRUE;

}

解析:函數(shù)的功能是在指定的節(jié)點(diǎn)后面插入一個新的節(jié)點(diǎn);
鏈表中的插入,不外乎鏈頭后面、中間位置、鏈尾后面三個位置;

1、// 1 ListNode_Create 原型是

ListNode ListNode_Create(ElementTypePrt const x) {
    
    ListNode lNew = (ListNode)malloc(sizeof(struct __ListNode));
    if (lNew == NULL) { printf("ERROR: Out Of Space !"); return NULL; }

    lNew->data = x;
    lNew->next = NULL;

    return lNew;

}

此處不解析;

2、// 2 這里是為了區(qū)分插入發(fā)生在鏈頭與鏈尾,還是中間位置與鏈尾;

3、// 3// 4 就是鏈表插入的核心操作,

插入操作圖示,
// 對應(yīng)的核心代碼
lNew->next = p->next;
p->next = lNew;
  • 刪除操作:
_BOOL List_Remove(List l, Position deletePrev, ElementTypePrtPrt const x) {
    
    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (List_IsEmpty(l)) { printf("ERROR: Empty List !"); return LINKEDLIST_FALSE; }

    int isHeadNode = (deletePrev == NULL);

    ListNode lDelete = NULL;
    if (isHeadNode) {
    
        /* Get The Deleted Data ! */
        *x = l->head->data;

        /* Delete Operations */
        lDelete = l->head;
        l->head = l->head->next;

        if (List_Size(l) == 1) { l->tail = NULL; }

    } else {

        /* Can`t Delete ... */
        if (deletePrev->next == NULL) { return LINKEDLIST_FALSE; }

        /* Get The Deleted Data ! */
        *x = deletePrev->next->data;

        /* Delete Operations */
        lDelete = deletePrev->next;
        deletePrev->next = deletePrev->next->next;

        if (deletePrev->next == NULL) { l->tail = deletePrev; }

    }

    /* Free The Deleted Node */
    free(lDelete);

    /* Size -- */
    l->size--;

    return LINKEDLIST_TRUE;
}

解析:函數(shù)功能是刪除鏈表節(jié)點(diǎn);
刪除操作在單鏈表中比較特殊,因?yàn)殒湵硎菃蜗颍磸逆滎^到鏈尾,而要刪除的節(jié)點(diǎn)并沒有指向前面節(jié)點(diǎn)的能力,所以要使用需刪除的節(jié)點(diǎn)的前一個節(jié)點(diǎn)進(jìn)行刪除操作;

刪除操作圖示,
// 對應(yīng)的核心代碼
lDelete = deletePrev->next;
deletePrev->next = deletePrev->next->next;

free(lDelete);
  • 遍歷操作:

上面的插入與刪除操作都要提供一個 Position 參數(shù),不然兩個函數(shù)沒法用,而尋找這個行為就是遍歷操作;

這里提供兩個遍歷操作,

Position List_Find(List l, MatchFunc mat, ElementTypePrt const x);
Position List_FindPrevious(List l, MatchFunc mat, ElementTypePrt const x);

遍歷其實(shí)就是匹配內(nèi)容,也就是要用到前面提到的 MatchFunc 函數(shù),這個具體的匹配函數(shù)由用使用者實(shí)現(xiàn);

Position List_Find(List l, MatchFunc mat, ElementTypePrt const x) 函數(shù):

// Position 就是 ListNode 的別名
Position List_Find(List l, MatchFunc mat, ElementTypePrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return NULL; }
    if (List_IsEmpty(l)) { printf("ERROR: Empty List !"); return NULL; }

    if (mat == NULL) { printf("ERROR: Bad Match Function !"); return NULL; }
    l->matchFunc = mat;

    Position p = NULL;
    for (p = List_Head(l); p != NULL; p = List_NodeNext(p)) {  // 1
        if (mat(x, List_NodeData(p))) { return p; } // 2
    }

    return NULL;

}

解析:函數(shù)功能是遍歷鏈表,查找與當(dāng)前節(jié)點(diǎn)內(nèi)容匹配的節(jié)點(diǎn);
1、// 1 從鏈頭開始,不斷地用 next 來進(jìn)行指針偏移,一直到鏈尾才結(jié)束【因?yàn)殒溛驳?next 肯定是 NULL 所以使用它來結(jié)束循環(huán)】;

2、// 2 這一次的匹配都使用用戶實(shí)現(xiàn)的 MatchFunc 函數(shù);

3、List_Head 就是 l->headList_NodeNext 就是 p->nextList_NodeData 就是 p->data

List_FindPrevious (...): 函數(shù)功能是遍歷鏈表,查找與當(dāng)前節(jié)點(diǎn)內(nèi)容匹配的節(jié)點(diǎn)的前一個節(jié)點(diǎn);遍歷原理與 List_Find (...) 是一樣的;

雙向鏈表
  • 節(jié)點(diǎn)[內(nèi)存結(jié)構(gòu)]與雙向鏈表的定義:

節(jié)點(diǎn)[內(nèi)存結(jié)構(gòu)],

typedef struct __DoubleListNode * _DoubleListNode;
typedef _DoubleListNode DoubleListNode;
typedef _DoubleListNode DoublePosition;
struct __DoubleListNode {
    ElementTypePrt data;
    DoubleListNode prev;
    DoubleListNode next;
};

解析:
因?yàn)殡p向鏈表有前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn),所以內(nèi)存結(jié)構(gòu)要在單鏈表的內(nèi)存結(jié)構(gòu)基礎(chǔ)上,增加一個 prev 指針指向節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn);

雙向鏈表,

typedef struct __DoubleList * _DoubleList;
typedef _DoubleList DoubleList;
struct __DoubleList {
    unsigned int size;
    MatchFunc matchFunc;
    DestroyFunc destroyFunc;
    DoubleListNode head;
    DoubleListNode tail;
};

因?yàn)殡p向鏈表只是在單鏈表的基礎(chǔ)上增加了一個遍歷方向,并沒有改變鏈表的其它內(nèi)容,所以與單鏈表的定義一致,不再進(jìn)行解析;

  • 雙向鏈表的核心操作集:
/* Create */

DoubleList DoubleList_Create(DestroyFunc des);
void DoubleList_Init(DoubleList l, DestroyFunc des);
void DoubleList_Destroy(DoubleList l);

/* Operations */

DoublePosition DoubleList_Find(DoubleList l, MatchFunc mat, ElementTypePrt const x);
DoublePosition DoubleList_Find_Reverse(DoubleList l, MatchFunc mat, ElementTypePrt const x);

_BOOL DoubleList_Insert_Prev(DoubleList l, DoublePosition p, ElementTypePrt const x);
_BOOL DoubleList_Insert_Next(DoubleList l, DoublePosition p, ElementTypePrt const x);
_BOOL DoubleList_Remove(DoubleList l, DoublePosition p, ElementTypePrtPrt const x);
  • 雙向鏈表的創(chuàng)建與銷毀:

創(chuàng)建、初始化、銷毀,這三個操作與單鏈表的實(shí)現(xiàn)基本一致,具體的可以查看工程代碼;

  • 插入操作 [分為兩種,在指定節(jié)點(diǎn)前插入和在指定節(jié)點(diǎn)后插入] :

在指定節(jié)點(diǎn)后插入

// 在指定節(jié)點(diǎn)后插入
_BOOL DoubleList_Insert_Next(DoubleList l, DoublePosition p, ElementTypePrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (p == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }

    /* Create New Node */
    DoubleListNode lNew = DoubleListNode_Create(x);

    if (DoubleList_IsEmpty(l)) {
    
        l->head = l->tail = lNew;  // 1

    } else {
    
        // 2
        lNew->prev = p;
        lNew->next = p->next;

        _BOOL isLastNode = (p->next == NULL);
        if (isLastNode) {
            l->tail = lNew;
        } else {
            p->next->prev = lNew;
        }

        p->next = lNew;

    }

    /* Size ++ */
    l->size++;

    return LINKEDLIST_TRUE;

}

解析:
1、當(dāng)鏈表是空表的時候,鏈頭和鏈尾就要指向新創(chuàng)建的節(jié)點(diǎn),這是要注意的;

2、雙向鏈表插入圖示,
// 對應(yīng)代碼
// 2
lNew->prev = p;
lNew->next = p->next;
p->next->prev = lNew;
p->next = lNew;

在指定節(jié)點(diǎn)前插入

// 在指定節(jié)點(diǎn)前插入
_BOOL DoubleList_Insert_Prev(DoubleList l, DoublePosition p, ElementTypePrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (p == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }

    /* Create New Node */
    DoubleListNode lNew = DoubleListNode_Create(x);

    if (DoubleList_IsEmpty(l)) {

        l->head = l->tail = lNew;

    } else {

        lNew->next = p;
        lNew->prev = p->prev;

        _BOOL isFirstNode = (p->prev == NULL);
        if (isFirstNode) {
            l->head = lNew;
        } else {
            p->prev->next = lNew;
        }

        p->prev = lNew;

    }

    /* Size ++ */
    l->size++;

    return LINKEDLIST_TRUE;

}

解析:這個插入方法的核心代碼是,

lNew->next = p;
lNew->prev = p->prev;
p->prev->next = lNew;
p->prev = lNew;

其實(shí)原理是一樣的,只是插入的方向不同;你把上面的兩個插入方法的核心代碼對比一下就知道了,只是把 prevnext 的位置改了一下;而能輕松的原因是,鏈表的雙向遍歷只是單純的方向不同,其它沒有任何區(qū)別,非常像兩個單鏈表組合在一起;

  • 刪除操作:
_BOOL DoubleList_Remove(DoubleList l, DoublePosition p, ElementTypePrtPrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (p == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }
    if (DoubleList_IsEmpty(l)) { printf("ERROR: Empty List !"); return LINKEDLIST_FALSE; }

    /* Get Data */
    *x = p->data;

    _BOOL isHeadNode = ( p == l->head );

    if (isHeadNode) {
        
        l->head = p->next;

        _BOOL isEmpty = (l->head == NULL);
        if (isEmpty) {
            l->tail = NULL;
        } else {
            // p->next->prev = NULL;
            l->head->prev = NULL; 
        }

    } else {
    
        p->prev->next = p->next;

        _BOOL isLastNode = (p->next == NULL);
        if (isLastNode) {
            l->tail = p->prev;
        } else {
            p->next->prev = p->prev;
        }

    }

    /* Free The Deleted Node */
    free(p);

    /* Size -- */
    l->size--;

    return LINKEDLIST_TRUE;

}

解析:函數(shù)功能是刪除指定的節(jié)點(diǎn);由于雙向鏈表有前驅(qū)節(jié)點(diǎn),所以可以輕松地通過指定的節(jié)點(diǎn)的 prev 指針得到前一個節(jié)點(diǎn),而不像單鏈表的刪除那樣要使用指定節(jié)點(diǎn)的前一個節(jié)點(diǎn)再進(jìn)行刪除操作;

核心刪除操作圖示,
// 對應(yīng)的核心代碼
p->prev->next = p->next;
p->next->prev = p->prev;

free(p);

與單鏈表的刪除相比,雙向鏈表的刪除操作更加簡單;

  • 遍歷操作:因?yàn)槭请p向鏈表所以遍歷就有兩個方向;
DoublePosition DoubleList_Find(DoubleList l, MatchFunc mat, ElementTypePrt const x);
DoublePosition DoubleList_Find_Reverse(DoubleList l, MatchFunc mat, ElementTypePrt const x);

從鏈頭到鏈尾的遍歷 DoubleList_Find

DoublePosition DoubleList_Find(DoubleList l, MatchFunc mat, ElementTypePrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return NULL; }
    if (DoubleList_IsEmpty(l)) { printf("ERROR: Empty List !"); return NULL; }

    if (mat == NULL) { printf("ERROR: Bad Match Function !"); return NULL; }
    l->matchFunc = mat;

    DoublePosition p = NULL;
    for (p = DoubleList_Head(l); p != NULL; p = DoubleList_NodeNext(p)) {
        if (mat(x, DoubleList_NodeData(p))) { return p; }
    }

    return NULL;

}

它的實(shí)現(xiàn)與單鏈表的 List_Find 方法實(shí)現(xiàn)原理上完全一樣,不同的就是函數(shù)名和節(jié)點(diǎn)而已,所以你如果單鏈表的看懂了,那么這里也就懂了。

從鏈尾到鏈頭的遍歷 DoubleList_Find_Reverse

DoublePosition DoubleList_Find_Reverse(DoubleList l, MatchFunc mat, ElementTypePrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return NULL; }
    if (DoubleList_IsEmpty(l)) { printf("ERROR: Empty List !"); return NULL; }

    if (mat == NULL) { printf("ERROR: Bad Match Function !"); return NULL; }
    l->matchFunc = mat;

    DoublePosition p = NULL;
    for (p = DoubleList_Tail(l); p != NULL; p = DoubleList_NodePrev(p)) {
        if (mat(x, DoubleList_NodeData(p))) { return p; }
    }

    return NULL;

}

原理與上面的方法是一致的,它們不同的只是方向,前者是,
for (p = DoubleList_Head(l); p != NULL; p = DoubleList_NodeNext(p))
[ head --到-- Tail (next) ]
后者是,for (p = DoubleList_Tail(l); p != NULL; p = DoubleList_NodePrev(p))
[ Tail --到-- Next (prev) ] ;

循環(huán)鏈表

單向循環(huán)鏈表:

  • 單向循環(huán)鏈表的節(jié)點(diǎn)與鏈表:
/* struct */
struct __CircularListNode {
    ElementTypePrt data;
    CircularListNode next;
};

struct __CircularList {
    unsigned int size;
    MatchFunc matchFunc;
    DestroyFunc destroyFunc;
    CircularListNode head;
};

與單鏈表唯一的不同是,沒有 tail 指針,因?yàn)殒湵硎茄h(huán)的,所以不存在尾這個說法;

  • 單向循環(huán)鏈表的核心操作集:與單鏈表的操作集也是一樣的,這里就列出來一下;
/* Create */

CircularList CircularList_Create(DestroyFunc des);
void CircularList_Init(CircularList l, DestroyFunc des);
void CircularList_Destroy(CircularList l);

/* Operations */
CircularPosition CircularList_Find(CircularList l, MatchFunc mat, ElementTypePrt const x);
CircularPosition CircularList_FindPrevious(CircularList l, MatchFunc mat, ElementTypePrt const x);

_BOOL CircularList_Insert(CircularList l, CircularPosition p, ElementTypePrt const x);
_BOOL CircularList_Remove(CircularList l, CircularPosition deletePrev, ElementTypePrtPrt const x);
  • 單向循環(huán)鏈表的創(chuàng)建與銷毀:與單鏈表的實(shí)現(xiàn)完全一樣;

  • 插入操作:

_BOOL CircularList_Insert(CircularList l, CircularPosition p, ElementTypePrt const x) {
    
    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (p == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }

    /* Create New Node */
    CircularListNode lNew = CircularListNode_Create(x);

    if (CircularList_IsEmpty(l)) {
        
        p->next = p;
        l->head = p;

    } else {
    
        /* Insert Operations */
        lNew->next = p->next;
        p->next = lNew;

    }

    /* Size ++ */
    l->size++;

    return LINKEDLIST_TRUE;

}

因?yàn)闆]有 NULL 指針的存在,所以插入過程的出錯判斷就變得很少;

單向循環(huán)鏈表插入操作圖示,
// 對應(yīng)的代碼
lNew->next = p->next;
p->next = lNew;

這里的核心代碼與單鏈表插入操作的核心代碼是一樣的;

  • 刪除操作:
_BOOL CircularList_Remove(CircularList l, CircularPosition deletePrev, ElementTypePrtPrt const x) {
    
    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (deletePrev == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }
    if (CircularList_IsEmpty(l)) { printf("ERROR: Empty List !"); return LINKEDLIST_FALSE; }

    *x = deletePrev->next->data;

    CircularListNode lDelete = NULL;

    _BOOL isOnlyOneNode = (deletePrev->next == deletePrev);
    if (isOnlyOneNode) {
        
        lDelete = deletePrev->next;
        l->head = NULL;

    } else {
    
        /* Delete Operations */
        lDelete = deletePrev->next;
        deletePrev->next = deletePrev->next->next;

        if (lDelete == l->head) { l->head = lDelete->next; }

    }

    /* Free The Deleted Node */
    if (lDelete != NULL) { free(lDelete); }

    /* Size -- */
    l->size--;

    return LINKEDLIST_TRUE;

}

單向鏈表刪除操作圖示,
// 對應(yīng)核心代碼
lDelete = p->next;
p->next = p->next->next;

free(lDelete);

這里的核心代碼與單鏈表刪除操作的核心代碼是一樣的;

  • 遍歷操作:與單鏈表的遍歷原理一樣,不同的是結(jié)束條件;

CircularPosition CircularList_Find(...) 函數(shù):

CircularPosition CircularList_Find(CircularList l, MatchFunc mat, ElementTypePrt const x) {
    
    if (l == NULL) { printf("ERROR: Bad List !"); return NULL; }
    if (CircularList_IsEmpty(l)) { printf("ERROR: Empty List !"); return NULL; }

    if (mat == NULL) { printf("ERROR: Bad Match Function !"); return NULL; }
    l->matchFunc = mat;

    CircularPosition p = CircularList_Head(l);

    while ( ! mat(x, CircularList_NodeData(p))) {
        p = CircularList_NodeNext(p);
        if (p == CircularList_Head(l)) { p = NULL; break; }
    }

    return p;

}

解析,
這里主要是結(jié)束條件的選擇,循環(huán)鏈表的起點(diǎn)是 head 那么結(jié)束自然而然也是 head 了,所以 if (p == CircularList_Head(l)) { p = NULL; break; } 直接跳出循環(huán)就可以了;

CircularPosition CircularList_FindPrevious(...) 函數(shù):

CircularPosition CircularList_FindPrevious(CircularList l, MatchFunc mat, ElementTypePrt const x) {

    if (l == NULL) { printf("ERROR: Bad List !"); return NULL; }
    if (CircularList_IsEmpty(l)) { printf("ERROR: Empty List !"); return NULL; }

    if (mat == NULL) { printf("ERROR: Bad Match Function !"); return NULL; }
    l->matchFunc = mat;

    CircularPosition p = CircularList_Head(l);

    while ( ! mat(x, CircularList_NodeData(CircularList_NodeNext(p)))) {

        CircularPosition nextP = CircularList_NodeNext(p);
        if (nextP == CircularList_Head(l)) {

            if ( ! mat(x, CircularList_NodeData(CircularList_NodeNext(p)))) { 
                p = NULL; 
                break;
            }

            break;
        }

        p = nextP;

    }

    return p;

}

解析,
這里的起點(diǎn)是 head->next ,但是由于循環(huán)與前一個節(jié)點(diǎn)的約束條件,結(jié)束點(diǎn)就是 headnext 節(jié)點(diǎn),因?yàn)?head->next 要被訪問兩次,所以最后一次的訪問,就是鏈表遍歷結(jié)束的條件;

        CircularPosition nextP = CircularList_NodeNext(p);
        if (nextP == CircularList_Head(l)) {

            if ( ! mat(x, CircularList_NodeData(CircularList_NodeNext(p)))) { 
                p = NULL; 
                break;
            }

            break;
        }

這里是先判斷下一個節(jié)點(diǎn)是否是 head 已經(jīng)結(jié)束了,因?yàn)?head 是最后一個要被訪問的 ;

雙向循環(huán)鏈表:

  • 雙向循環(huán)鏈表的節(jié)點(diǎn)與鏈表:
/* struct */
struct __DoubleCircularListNode {
    ElementTypePrt data;
    DoubleCircularListNode prev;
    DoubleCircularListNode next;
};

struct __DoubleCircularList {
    unsigned int size;
    MatchFunc matchFunc;
    DestroyFunc destroyFunc;
    DoubleCircularListNode head;
};

與雙向鏈表的實(shí)現(xiàn)區(qū)別就是,沒有 tail 指針;

  • 雙向循環(huán)鏈表的核心操作集:與雙向鏈表的操作集一致;
/* Create */

DoubleCircularList DoubleCircularList_Create(DestroyFunc des);
void DoubleCircularList_Init(DoubleCircularList l, DestroyFunc des);
void DoubleCircularList_Destroy(DoubleCircularList l);

/* Operations */
DoubleCircularPosition DoubleCircularList_Find(DoubleCircularList l, MatchFunc mat, ElementTypePrt const x);
DoubleCircularPosition DoubleCircularList_Find_Reverse(DoubleCircularList l, MatchFunc mat, ElementTypePrt const x);

_BOOL DoubleCircularList_InsertInTail(DoubleCircularList l, ElementTypePrt const x);
_BOOL DoubleCircularList_Insert_Prev(DoubleCircularList l, DoubleCircularPosition p, ElementTypePrt const x);
_BOOL DoubleCircularList_Insert_Next(DoubleCircularList l, DoubleCircularPosition p, ElementTypePrt const x);
_BOOL DoubleCircularList_Remove(DoubleCircularList l, DoubleCircularPosition p, ElementTypePrtPrt const x);
  • 雙向循環(huán)鏈表的創(chuàng)建與銷毀:與雙向鏈表的實(shí)現(xiàn)一致;

  • 插入操作:

指定節(jié)點(diǎn)前的插入,

_BOOL 
DoubleCircularList_Insert_Prev( DoubleCircularList l, 
                                DoubleCircularPosition p, 
                                ElementTypePrt const x ) {
    
    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (p == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }

    /* Create New Node */
    DoubleCircularListNode lNew = DoubleCircularListNode_Create(x);

    if (DoubleCircularList_IsEmpty(l)) {

        l->head = lNew;
        l->head->prev = lNew;
        l->head->next = lNew;
    }
    else {

        lNew->next = p;
        lNew->prev = p->prev;

        p->prev->next = lNew;
        p->prev = lNew;

    }

    /* Size ++ */
    l->size++;

    return LINKEDLIST_TRUE;

}

指定節(jié)點(diǎn)后的插入,

_BOOL 
DoubleCircularList_Insert_Next( DoubleCircularList l,
                                DoubleCircularPosition p, 
                                ElementTypePrt const x ) {

    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (p == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }

    /* Create New Node */
    DoubleCircularListNode lNew = DoubleCircularListNode_Create(x);

    if (DoubleCircularList_IsEmpty(l)) {

        l->head = lNew;
        l->head->prev = lNew;
        l->head->next = lNew;

    }
    else {

        lNew->prev = p;
        lNew->next = p->next;

        p->next->prev = lNew;
        p->next = lNew;

    }

    /* Size ++ */
    l->size++;

    return LINKEDLIST_TRUE;

}

解析,
上面的兩個插入操作與雙向鏈表的實(shí)現(xiàn)原理是一樣的,區(qū)別就是插入操作的錯誤處理更少;

雙向循環(huán)鏈表插入操作圖示,
// 對應(yīng)核心代碼
lNew->prev = p;
lNew->next = p->next;
p->next->prev = lNew;
p->next = lNew;
  • 刪除操作:
_BOOL 
DoubleCircularList_Remove( DoubleCircularList l,
                           DoubleCircularPosition p,
                           ElementTypePrtPrt const x ) {

    if (l == NULL) { printf("ERROR: Bad List !"); return LINKEDLIST_FALSE; }
    if (p == NULL) { printf("ERROR: Bad Position !"); return LINKEDLIST_FALSE; }

    if (DoubleCircularList_IsEmpty(l)) { 
        printf("ERROR: Empty List !");
        return LINKEDLIST_FALSE; 
    }

    /* Get Data */
    *x = p->data;

    _BOOL isHeadNode = (p == l->head);
    if (isHeadNode) {

        l->head = p->next;
        if (l->size == 1) { l->head = NULL; }

    }

    p->next->prev = p->prev;
    p->prev->next = p->next;

    /* Free The Deleted Node */
    free(p);

    /* Size -- */
    l->size--;

    return LINKEDLIST_TRUE;

}

解析,
上面的刪除操作與雙向鏈表的實(shí)現(xiàn)原理一致,區(qū)別在于這里的錯誤處理更少;

雙向循環(huán)鏈表刪除操作圖示,
// 對應(yīng)的核心代碼
p->next->prev = p->prev;
p->prev->next = p->next;

free(p);
  • 遍歷操作:與雙向鏈表的實(shí)現(xiàn)原理一致,區(qū)別在于結(jié)束條件的判斷;
DoubleCircularPosition DoubleCircularList_Find(DoubleCircularList l, MatchFunc mat, ElementTypePrt const x);
DoubleCircularPosition DoubleCircularList_Find_Reverse(DoubleCircularList l, MatchFunc mat, ElementTypePrt const x);

DoubleCircularList_Find(...) 函數(shù):

DoubleCircularPosition 
DoubleCircularList_Find( DoubleCircularList l, 
                         MatchFunc mat,
                         ElementTypePrt const x ) {

    if (l == NULL) { printf("ERROR: Bad List !"); return NULL; }
    if (DoubleCircularList_IsEmpty(l)) { printf("ERROR: Empty List !"); return NULL; }

    DoubleCircularPosition p = DoubleCircularList_Head(l);

    while ( ! mat(x, DoubleCircularList_NodeData(p)) ) {
        p = DoubleCircularList_NodeNext(p);
        if (p == DoubleCircularList_Head(l)) { p = NULL; break; }
    }

    return p;

}

解析,
head 開始,通過 next 不斷地向后訪問后繼節(jié)點(diǎn),到達(dá) head 處結(jié)束 ,對應(yīng)的結(jié)束代碼 if (p == DoubleCircularList_Head(l)) { p = NULL; break; } ;

DoubleCircularList_Find_Reverse(...) 函數(shù):

DoubleCircularPosition
DoubleCircularList_Find_Reverse( DoubleCircularList l,
                                 MatchFunc mat,
                                 ElementTypePrt const x ) {
    
    if (l == NULL) { printf("ERROR: Bad List !"); return NULL; }
    if (DoubleCircularList_IsEmpty(l)) { printf("ERROR: Empty List !"); return NULL; }
    
    DoubleCircularPosition p = DoubleCircularList_Head(l);

    while ( ! mat(x, DoubleCircularList_NodeData(p))) {
        p = DoubleCircularList_NodePrev(p);
        if (p == DoubleCircularList_Head(l)) { p = NULL; break; }
    }

    return p;

}

解析,
head 開始,通過 prev 不斷地向后訪問前驅(qū)節(jié)點(diǎn),到達(dá) head 處結(jié)束 ,對應(yīng)的結(jié)束代碼同樣是 if (p == DoubleCircularList_Head(l)) { p = NULL; break; } ;


參考書籍:
1、《算法精解_C語言描述(中文版)》
2、《數(shù)據(jù)結(jié)構(gòu)與算法分析—C語言描述》

寫到這里,本文結(jié)束!下一篇,《數(shù)據(jù)結(jié)構(gòu):棧與隊(duì)列》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內(nèi)容