資料結構_連結串列_單向迴圈連結串列 & 雙向連結串列的初始化、插入、刪除、修改、查詢列印(基於C語言實現)

逸風明發表於2024-11-07

一、單向迴圈連結串列的原理與應用

思考:對於單向連結串列而言,想要遍歷連結串列,則必須從連結串列的首結點開始進行遍歷,請問有沒有更簡單的方案實現連結串列中的資料的增刪改查?

回答:是有的,可以使用單向迴圈的連結串列進行設計,單向迴圈的連結串列的使用規則和普通的單向連結串列沒有較大的區別,需要注意:單向迴圈連結串列的尾結點的指標域中必須指向連結串列的首結點的地址,由於帶頭結點的單向迴圈連結串列更加容易進行管理,所以教學以帶頭結點的為例:

img

上圖所示的就是一個典型的單向迴圈連結串列的結構,可以發現單向迴圈連結串列的結構屬於環形結構,連結串列中的最後一個結點的指標域中儲存的是連結串列的第一個結點的地址。

img

為了管理單向迴圈連結串列,需要構造頭結點的資料型別以及構造有效結點的資料型別,如下:

img

(1) 建立一個空連結串列,由於是使用頭結點,所以就需要申請頭結點的堆記憶體並初始化即可!

img

(2) 建立新結點,為新結點申請堆記憶體並對新結點的資料域和指標域進行初始化,操作如下:

img

(3) 根據情況把新結點插入到連結串列中,此時可以分為尾部插入、頭部插入、指定位置插入:
  • 頭插

img

  • 尾插

img

  • 中插

img

(4) 根據情況可以從連結串列中刪除某結點,此時可以分為尾部刪除、頭部刪除、指定元素刪除:
  • 頭刪

img

  • 尾刪

img

  • 中刪

img

程式碼

/**
 * @file name : CircularLinkedList.c
 * @brief     : 實現單向迴圈連結串列的相關功能
 * @author    :yfm3262@163.com
 * @date      :2024/11/07
 * @version   :1.0
 * @note      :
 * CopyRight (c)  2023-2024   yfm3262@163.com   All Right Reseverd
 */

單向迴圈連結串列公式

img

/**
 * 宣告單迴圈連結串列的結點
 *
 * 單向迴圈連結串列總結成公式
 *     struct xxx
 *     {
 *         //資料域(需要存放什麼型別的資料,你就定義對應的變數即可)
 *         //指標域(直接後繼的指標域)
 *     };
 *   單向迴圈連結串列的基本操作:
 *     初始化單向迴圈連結串列 √
 *     插入資料 √
 *     刪除資料 √
 *     修改資料
 *     查詢列印資料√
 */

初始化單向迴圈連結串列

構建單向迴圈連結串列結點

CircLList_t[ data |*next ]
// 指的是單向迴圈連結串列中的結點有效資料型別,使用者可以根據需要進行修改
typedef int DataType_t;

// 構造單向迴圈連結串列的結點,連結串列中所有結點的資料型別應該是相同的
typedef struct CircularLinkedList
{
    DataType_t data;                 // 結點的資料域
    struct CircularLinkedList *next; // 直接後繼的指標域
} CircLList_t;

建立一個空連結串列(僅頭結點)

img

/**
 * @name       CircLList_Create
 * @brief     建立一個空單向迴圈連結串列,僅含頭結點,並對連結串列進行初始化
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
CircLList_t *CircLList_Create(void)
{
    // 1.建立一個頭結點並對頭結點申請記憶體
    CircLList_t *Head = (CircLList_t *)calloc(1, sizeof(CircLList_t));
    if (NULL == Head)
    {
        perror("Calloc memory for Head is Failed");
        exit(-1);
    }

    // 2.對頭結點進行初始化,頭結點是不儲存資料域,指標域指向自己, 體現迴圈的思想 [date|*next]
    Head->next = Head;

    // 3.把頭結點的地址返回即可   Head-->[date|*next]
    return Head;
}

建立一個新結點

image

/**
 * @name       CircLList_NewNode
 * @brief     建立一個新的結點,並對新結點進行初始化(資料域 + 指標域)
 * @param     data 要建立結點的元素
 * @return    程式執行成功與否
 *      @retval    NULL 申請堆記憶體失敗
 *      @retval    New  新結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
CircLList_t *CircLList_NewNode(DataType_t data)
{
    // 1.建立一個新結點並對新結點申請記憶體
    CircLList_t *New = (CircLList_t *)calloc(1, sizeof(CircLList_t));
    if (NULL == New)
    {
        perror("Calloc memory for NewNode is Failed");
        return NULL;
    }

    // 2.對新結點的資料域和指標域進行初始化
    New->data = data;
    New->next = NULL;

    return New;
}

插入資料

頭插

image

/**
 * @name       CircLList_HeadInsert
 * @brief     在單向迴圈連結串列的頭結點後插入
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/04/24
 * @version   1.0
 * @note
 */
bool CircLList_HeadInsert(CircLList_t *Head, DataType_t data)
{
    // 備份頭指標, 建立操作指標
    CircLList_t *Current = Head;

    // 1.建立新結點並對新結點進行初始化
    CircLList_t *New = CircLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }

    // 2.判斷單向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後, 新結點作為首結點, 體現迴圈
    if (Head == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        New->next = New;  // 新節點指向自己, 體現迴圈, 僅有一個首結點的單迴圈連結串列
        return true;
    }

    // 3.如果單向迴圈連結串列為非空,需要讓尾結點的next指標指向首結點
    while (Current->next) // 不斷向下檢查結點指標域
    {
        Current = Current->next;         // 進入下一個結點
        if (Current->next == Head->next) // 結束條件: 達尾結點
        {
            break;
        }
    } // Current到達未尾結點
    Current->next = New;    // 尾結點指標域指向新的首結點
    New->next = Head->next; // 新結點連結舊首結點
    Head->next = New;       // 頭結點的next指標域指向新結點的地址

    return true;
}

中插

image

/**
 * @name       CircLList_DestInsert
 * @brief     單向迴圈連結串列中的指定元素後面插入新結點
 * @param     Head 頭指標
 * @param     dest 要查詢的結點
 * @param     data 要插入的資料
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.1
 * @note
 */
bool CircLList_DestInsert(CircLList_t *Head, DataType_t dest, DataType_t data)
{
    CircLList_t *Current = Head->next; // 操作指標 初始為指向首結點, 若為空連結串列則指向頭結點

    // 1.建立新結點並對新結點進行初始化
    CircLList_t *New = CircLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }

    // 2.判斷單向迴圈連結串列是否為空,如果為空,則新結點作為首結點, 體現迴圈
    if (Head == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        New->next = New;  // 新節點指向自己, 體現迴圈, 單有效結點
        return true;
    }

    // 3.若單向迴圈連結串列非空,需要讓尾結點的next指標指向新結點,新結點指向首結點
    // 目標結點是首結點, 不再繼續查詢. 目標結點非首結點, 繼續向下查詢
    while (Current->data != dest)
    {
        Current = Current->next;                                      // 進入下一個結點
        if ((Current->next == Head->next) && (Current->data != dest)) // 達到末尾 且 末尾不是目標
        {
            printf("The target doesn't exist! \n");
            return false;
        }
    } // 找到目標結點, Current此時指向目標
    // 目標結點是首結點
    if (Current == Head->next)
    {
        New->next = Current->next; // 新結點連結目標結點直接後繼
        Current->next = New;
    }
    else if (Current->next == Head->next) // 目標結點是尾結點
    {
        New->next = Head->next; // 作為新尾結點
        Current->next = New;
    }
    else // 目標結點是中間結點
    {
        New->next = Current->next; // 新結點連結目標結點直接後繼
        Current->next = New;       // 目標結點的直接後繼更新為新結點
    }
    return true;
}

尾插

image

/**
 * @name      CircLList_TailInsert
 * @brief     將新元素插入到尾結點後面
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_TailInsert(CircLList_t *Head, DataType_t data)
{
    CircLList_t *Phead = Head; // 備份頭結點地址,防止頭結點丟失

    // 1.建立新結點並對新結點進行初始化
    CircLList_t *New = CircLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }

    // 2.判斷單向迴圈連結串列是否為空,如果為空,則新結點作為首結點, 體現迴圈
    if (Head == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        New->next = New;  // 新節點指向自己, 體現迴圈
        return true;
    }

    // 3.如果單向迴圈連結串列為非空,需要讓尾結點的next指標指向新結點,新結點指向首結點
    while (Phead->next) // 不斷向下檢查結點指標域
    {
        Phead = Phead->next;           // 進入下一個結點
        if (Phead->next == Head->next) // 當到達尾結點
        {
            break;
        }
    }
    Phead->next = New;      // 尾結點指標域 連結 新結點
    New->next = Head->next; // 新結點指標域 指向 首結點

    return true;
}

刪除資料

頭刪

image

/**
 * @name       CircLList_HeadDel
 * @brief     刪除頭結點後面的一個結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_HeadDel(CircLList_t *Head)
{
    // 1.建立操作指標
    // 備份頭結點地址,防止頭結點丟失
    CircLList_t *Phead = Head;
    // 備份首結點, 用於操作
    CircLList_t *Temp = Head->next;

    // 2.判斷單向迴圈連結串列是否為空連結串列,如果為空, 則退出
    if (Head == Head->next)
    {
        printf("CircLList is Empty! \n");
        return false;
    }

    // 3.判斷連結串列中是否只有首結點

    if (Head->next == Head->next->next)
    {
        Temp->next = NULL; // 首結點的指標域指向NULL, 且防止野指標和記憶體洩漏
        Head->next = Head; // 頭結點next指標指向頭結點, 體現"迴圈"
        free(Temp);        // 釋放結點記憶體, 防止記憶體洩漏
        return true;
    }

    // 3.如果單向迴圈連結串列為非空,需要讓尾結點的next指標指向新的首結點(原首結點的直接後繼)
    while (Phead->next) // 不斷向下檢查結點指標域
    {
        Phead = Phead->next;           // 進入下一個結點
        if (Phead->next == Head->next) // 當到達尾結點
        {
            break;
        }
    }

    Phead->next = Head->next->next; // 讓尾結點的next指標指向新的首結點(原首結點的直接後繼)
    Head->next = Phead->next;       // 頭結點的指標域 修改連結為 新的首結點
    Temp->next = NULL;              // 讓舊的首結點的指標域指向NULL, 從連結串列中斷開, 且防止野指標和記憶體洩漏
    free(Temp);                     // 釋放舊首結點的記憶體, 防止記憶體洩漏
    return true;
}

中刪

image

/**
 * @name       CircLList_DestDel
 * @brief     中刪, 刪除某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_DestDel(CircLList_t *Head, DataType_t dest)
{
    CircLList_t *Current = Head->next; // 操作指標 初始為指向首結點, 若為空連結串列則指向頭結點
    CircLList_t *Prev = Head;          // 操作指標 存放當前操作指標的前一個結點地址

    // 1.判斷單向迴圈連結串列是否為空,如果為空,則報錯
    if (Head == Head->next)
    {
        printf("Error,  CircularLinkList is empty! \n");
        return false;
    }

    // 2.若單向迴圈連結串列非空
    // 目標結點是首結點, 不再繼續查詢. 目標結點非首結點, 繼續向下查詢
    while (Current->data != dest)
    {
        Prev = Current;                                               // 備份Current的前一個位置
        Current = Current->next;                                      // 進入下一個結點
        if ((Current->next == Head->next) && (Current->data != dest)) // 達到末尾 且 末尾不是目標
        {
            printf("The target doesn't exist! \n");
            return false;
        }
    } // 找到目標結點, Current此時指向目標  Prev為Current 的前一個位置
    // 目標結點是首結點
    if (Current == Head->next)
    {
        // 若連結串列只有首結點
        if (Current->next == Head->next)
        {
            Head->next = Head; // 空連結串列狀態
            Current->next = NULL;
            free(Current); // 防止記憶體洩漏
            return true;
        }
        while (Prev->next) // 不斷向下檢查結點指標域
        {
            Prev = Prev->next;            // 進入下一個結點
            if (Prev->next == Head->next) // 結束條件: 達尾結點
            {
                break;
            }
        } // Prev到達未尾結點
        Prev->next = Current->next; // 更新尾結點指標域為新首結點地址
        Head->next = Current->next; // 更新首結點連結新首結點
    }
    else if (Current->next == Head->next) // 目標結點是尾結點
    {
        Prev->next = Head->next; // 新尾結點連結首結點, 行成迴圈
    }
    else // 目標結點是中間結點
    {
        Prev->next = Current->next;
    }
    Current->next = NULL;
    free(Current); // 防止記憶體洩漏
    return true;
}

尾刪

image

/**
 * @name      CircLList_TailDel
 * @brief     刪除尾結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 連結串列為空
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_TailDel(CircLList_t *Head)
{
    CircLList_t *Current = Head->next; // 操作指標 初始為指向首結點, 若為空連結串列則指向頭結點
    CircLList_t *Prev = Head;          // 操作指標 存放當前操作指標的前一個結點地址

    // 1.判斷單向迴圈連結串列是否為空,如果為空,則報錯
    if (Head == Head->next)
    {
        printf("Error,  CircularLinkList is empty! \n");
        return false;
    }

    // 2.若單向迴圈連結串列非空
    // 若連結串列只有首結點
    if (Current->next == Head->next)
    {
        Head->next = Head; // 空連結串列狀態
        Current->next = NULL;
        free(Current); // 防止記憶體洩漏
        return true;
    }
    // 若還有別的結點
    while (Current->next) // 不斷向下檢查結點指標域
    {
        Prev = Current;
        Current = Current->next;         // 進入下一個結點
        if (Current->next == Head->next) // 結束條件: 達尾結點
        {
            break;
        }
    } // Current到達未尾結點 Prev為Current 的前一個位置
    Prev->next = Head->next; // 新尾結點連結首結點, 行成迴圈
    Current->next = NULL;
    free(Current); // 防止記憶體洩漏
    return true;
}

查詢列印資料

遍歷列印

/**
 * @name      CircLList_Print
 * @brief     從頭到尾遍歷連結串列
 * @param     Head 頭指標
 * @return    無
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
void CircLList_Print(CircLList_t *Head)
{
    // 判斷是否為空連結串列
    if (Head->next == Head)
    {
        printf("The list is empty.\n");
        return;
    }

    CircLList_t *Current = Head->next; // 指向首結點

    printf("Circular Linked List: ");

    while (Current->next) // 不斷向下檢查結點指標域
    {
        printf(" -> %d", Current->data); // 列印結點資料
        if (Current->next == Head->next) // 結束條件: 達尾結點
        {
            break;
        }
        Current = Current->next; // 進入下一個結點
    }
    printf("\n"); // 重新整理行緩衝, 輸出緩衝區
}

測試

#include "CircularLinkedList.h"

int main(int argc, char const *argv[])
{
    // 建立單向迴圈連結串列, 空連結串列
    CircLList_t *Manager = CircLList_Create();

    // 頭插法 向連結串列中插入新結點
    printf("*********************************CircLList_HeadInsert********************************\n");
    CircLList_HeadInsert(Manager, 7);
    CircLList_HeadInsert(Manager, 4);
    CircLList_HeadInsert(Manager, 1);
    CircLList_HeadInsert(Manager, 8);
    CircLList_HeadInsert(Manager, 5);
    CircLList_HeadInsert(Manager, 2);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 2 -> 5 -> 8 -> 1 -> 4 -> 7*/

    // 中插法 向連結串列中插入新結點
    printf("*********************************CircLList_DestInsert********************************\n");
    CircLList_DestInsert(Manager, 7, 9);
    CircLList_DestInsert(Manager, 4, 6);
    CircLList_DestInsert(Manager, 2, 3);
    CircLList_DestInsert(Manager, 5, 10);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9*/

    // 尾插法 向連結串列中插入新結點
    printf("*********************************CircLList_TailInsert********************************\n");
    CircLList_TailInsert(Manager, 13);
    CircLList_TailInsert(Manager, 12);
    CircLList_TailInsert(Manager, 11);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11*/

    // 頭刪法 刪除首結點
    printf("*********************************CircLList_HeadDel********************************\n");
    CircLList_HeadDel(Manager);
    CircLList_HeadDel(Manager);
    CircLList_HeadDel(Manager);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11*/

    // 中刪法 刪除指定結點
    printf("*********************************CircLList_DestDel********************************\n");
    CircLList_DestDel(Manager, 10);
    CircLList_DestDel(Manager, 1);
    CircLList_DestDel(Manager, 6);
    CircLList_DestDel(Manager, 11);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 8 -> 4 -> 7 -> 9 -> 13 -> 12*/

    // 尾刪法 刪除尾結點
    printf("*********************************CircLList_HeadDel********************************\n");
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 8 -> 4*/
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    /*Error,  CircularLinkList is empty! */
    CircLList_HeadInsert(Manager, 66);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 66*/
    // 等待使用者響應
    printf("***Press any key to exit the test***\n");
    getchar();
    return 0;
}

測試結果:

*********************************CircLList_HeadInsert********************************
Circular Linked List:  -> 2 -> 5 -> 8 -> 1 -> 4 -> 7
*********************************CircLList_DestInsert********************************
Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9
*********************************CircLList_TailInsert********************************
Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11
*********************************CircLList_HeadDel********************************
Circular Linked List:  -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11
*********************************CircLList_DestDel********************************
Circular Linked List:  -> 8 -> 4 -> 7 -> 9 -> 13 -> 12
*********************************CircLList_HeadDel********************************
Circular Linked List:  -> 8 -> 4
Error,  CircularLinkList is empty! 
Circular Linked List:  -> 66
***Press any key to exit the test***

完整程式碼

CircularLinkedList.h

#ifndef __CIRCULARLINKEDLIST_H // ifndef是(如果 沒有 定義 那麼) (__該標頭檔案的名稱)
#define __CIRCULARLINKEDLIST_H // #define是 進行定義
/**
 * @file name : CircularLinkedList.c
 * @brief     : 實現單向迴圈連結串列的相關功能
 * @author    :yfm3262@163.com
 * @date      :2024/11/07
 * @version   :1.0
 * @note      :
 * CopyRight (c)  2023-2024   yfm3262@163.com   All Right Reseverd
 */

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

/**
 * 宣告單迴圈連結串列的結點
 *
 * 單向迴圈連結串列總結成公式
 *     struct xxx
 *     {
 *         //資料域(需要存放什麼型別的資料,你就定義對應的變數即可)
 *         //指標域(直接後繼的指標域)
 *     };
 *   單向迴圈連結串列的基本操作:
 *     初始化單向迴圈連結串列 √
 *     插入資料 √
 *     刪除資料 √
 *     修改資料
 *     查詢列印資料√
 */

// 指的是單向迴圈連結串列中的結點有效資料型別,使用者可以根據需要進行修改
typedef int DataType_t;

// 構造單向迴圈連結串列的結點,連結串列中所有結點的資料型別應該是相同的
typedef struct CircularLinkedList
{
    DataType_t data;                 // 結點的資料域
    struct CircularLinkedList *next; // 直接後繼的指標域
} CircLList_t;

/**
 * @name       CircLList_Create
 * @brief     建立一個空單向迴圈連結串列,僅含頭結點,並對連結串列進行初始化
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
CircLList_t *CircLList_Create(void);

/**
 * @name       CircLList_NewNode
 * @brief     建立一個新的結點,並對新結點進行初始化(資料域 + 指標域)
 * @param     data 要建立結點的元素
 * @return    程式執行成功與否
 *      @retval    NULL 申請堆記憶體失敗
 *      @retval    New  新結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
CircLList_t *CircLList_NewNode(DataType_t data);

/**
 * @name       CircLList_HeadInsert
 * @brief     在單向迴圈連結串列的頭結點後插入
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_HeadInsert(CircLList_t *Head, DataType_t data);

/**
 * @name       CircLList_DestInsert
 * @brief     單向迴圈中的指定元素後面插入新結點
 * @param     Head 頭指標
 * @param     dest 要查詢的結點
 * @param     data 要插入的資料
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_DestInsert(CircLList_t *Head, DataType_t dest, DataType_t data);

/**
 * @name      CircLList_TailInsert
 * @brief     將新元素插入到尾結點後面
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_TailInsert(CircLList_t *Head, DataType_t data);

/**
 * @name       CircLList_HeadDel
 * @brief     刪除頭結點後面的一個結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_HeadDel(CircLList_t *Head);

/**
 * @name       CircLList_DestDel
 * @brief     中刪, 刪除某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_DestDel(CircLList_t *Head, DataType_t dest);

/**
 * @name      CircLList_TailDel
 * @brief     刪除尾結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 連結串列為空
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_TailDel(CircLList_t *Head);
/**
 * @name      CircLList_Print
 * @brief     從頭到尾遍歷連結串列
 * @param     Head 頭指標
 * @return    無
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
void CircLList_Print(CircLList_t *Head);
#endif
// 結束定義

CircularLinkedList.c

/**
 * @file name : CircularLinkedList.c
 * @brief     : 實現單向迴圈連結串列的相關功能
 * @author    :yfm3262@163.com
 * @date      :2024/11/07
 * @version   :1.0
 * @note      :
 * CopyRight (c)  2023-2024   yfm3262@163.com   All Right Reseverd
 */
#include "CircularLinkedList.h"

/**
 * @name       CircLList_Create
 * @brief     建立一個空單向迴圈連結串列,僅含頭結點,並對連結串列進行初始化
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
CircLList_t *CircLList_Create(void)
{
    // 1.建立一個頭結點並對頭結點申請記憶體
    CircLList_t *Head = (CircLList_t *)calloc(1, sizeof(CircLList_t));
    if (NULL == Head)
    {
        perror("Calloc memory for Head is Failed");
        exit(-1);
    }

    // 2.對頭結點進行初始化,頭結點是不儲存資料域,指標域指向自己, 體現迴圈的思想 [date|*next]
    Head->next = Head;

    // 3.把頭結點的地址返回即可   Head-->[date|*next]
    return Head;
}

/**
 * @name       CircLList_NewNode
 * @brief     建立一個新的結點,並對新結點進行初始化(資料域 + 指標域)
 * @param     data 要建立結點的元素
 * @return    程式執行成功與否
 *      @retval    NULL 申請堆記憶體失敗
 *      @retval    New  新結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
CircLList_t *CircLList_NewNode(DataType_t data)
{
    // 1.建立一個新結點並對新結點申請記憶體
    CircLList_t *New = (CircLList_t *)calloc(1, sizeof(CircLList_t));
    if (NULL == New)
    {
        perror("Calloc memory for NewNode is Failed");
        return NULL;
    }

    // 2.對新結點的資料域和指標域進行初始化
    New->data = data;
    New->next = NULL;

    return New;
}

/**
 * @name       CircLList_HeadInsert
 * @brief     在單向迴圈連結串列的頭結點後插入
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_HeadInsert(CircLList_t *Head, DataType_t data)
{
    // 備份頭指標, 建立操作指標
    CircLList_t *Current = Head;

    // 1.建立新結點並對新結點進行初始化
    CircLList_t *New = CircLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }

    // 2.判斷單向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後, 新結點作為首結點, 體現迴圈
    if (Head == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        New->next = New;  // 新節點指向自己, 體現迴圈, 僅有一個首結點的單迴圈連結串列
        return true;
    }

    // 3.如果單向迴圈連結串列為非空,需要讓尾結點的next指標指向首結點
    while (Current->next) // 不斷向下檢查結點指標域
    {
        Current = Current->next;         // 進入下一個結點
        if (Current->next == Head->next) // 結束條件: 達尾結點
        {
            break;
        }
    } // Current到達未尾結點
    Current->next = New;    // 尾結點指標域指向新的首結點
    New->next = Head->next; // 新結點連結舊首結點
    Head->next = New;       // 頭結點的next指標域指向新結點的地址

    return true;
}

/**
 * @name       CircLList_DestInsert
 * @brief     單向迴圈連結串列中的指定元素後面插入新結點
 * @param     Head 頭指標
 * @param     dest 要查詢的結點
 * @param     data 要插入的資料
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_DestInsert(CircLList_t *Head, DataType_t dest, DataType_t data)
{
    CircLList_t *Current = Head->next; // 操作指標 初始為指向首結點, 若為空連結串列則指向頭結點

    // 1.建立新結點並對新結點進行初始化
    CircLList_t *New = CircLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }

    // 2.判斷單向迴圈連結串列是否為空,如果為空,則新結點作為首結點, 體現迴圈
    if (Head == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        New->next = New;  // 新節點指向自己, 體現迴圈, 單有效結點
        return true;
    }

    // 3.若單向迴圈連結串列非空,需要讓尾結點的next指標指向新結點,新結點指向首結點
    // 目標結點是首結點, 不再繼續查詢. 目標結點非首結點, 繼續向下查詢
    while (Current->data != dest)
    {
        Current = Current->next;                                      // 進入下一個結點
        if ((Current->next == Head->next) && (Current->data != dest)) // 達到末尾 且 末尾不是目標
        {
            printf("The target doesn't exist! \n");
            return false;
        }
    } // 找到目標結點, Current此時指向目標
    // 目標結點是首結點
    if (Current == Head->next)
    {
        New->next = Current->next; // 新結點連結目標結點直接後繼
        Current->next = New;
    }
    else if (Current->next == Head->next) // 目標結點是尾結點
    {
        New->next = Head->next; // 作為新尾結點
        Current->next = New;
    }
    else // 目標結點是中間結點
    {
        New->next = Current->next; // 新結點連結目標結點直接後繼
        Current->next = New;       // 目標結點的直接後繼更新為新結點
    }
    return true;
}

/**
 * @name      CircLList_TailInsert
 * @brief     將新元素插入到尾結點後面
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_TailInsert(CircLList_t *Head, DataType_t data)
{
    CircLList_t *Phead = Head; // 備份頭結點地址,防止頭結點丟失

    // 1.建立新結點並對新結點進行初始化
    CircLList_t *New = CircLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }

    // 2.判斷單向迴圈連結串列是否為空,如果為空,則新結點作為首結點, 體現迴圈
    if (Head == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        New->next = New;  // 新節點指向自己, 體現迴圈
        return true;
    }

    // 3.如果單向迴圈連結串列為非空,需要讓尾結點的next指標指向新結點,新結點指向首結點
    while (Phead->next) // 不斷向下檢查結點指標域
    {
        Phead = Phead->next;           // 進入下一個結點
        if (Phead->next == Head->next) // 當到達尾結點
        {
            break;
        }
    }
    Phead->next = New;      // 尾結點指標域 連結 新結點
    New->next = Head->next; // 新結點指標域 指向 首結點

    return true;
}

/**
 * @name       CircLList_HeadDel
 * @brief     刪除頭結點後面的一個結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_HeadDel(CircLList_t *Head)
{
    // 1.建立操作指標
    // 備份頭結點地址,防止頭結點丟失
    CircLList_t *Phead = Head;
    // 備份首結點, 用於操作
    CircLList_t *Temp = Head->next;

    // 2.判斷單向迴圈連結串列是否為空連結串列,如果為空, 則退出
    if (Head == Head->next)
    {
        printf("CircLList is Empty! \n");
        return false;
    }

    // 3.判斷連結串列中是否只有首結點

    if (Head->next == Head->next->next)
    {
        Temp->next = NULL; // 首結點的指標域指向NULL, 且防止野指標和記憶體洩漏
        Head->next = Head; // 頭結點next指標指向頭結點, 體現"迴圈"
        free(Temp);        // 釋放結點記憶體, 防止記憶體洩漏
        return true;
    }

    // 3.如果單向迴圈連結串列為非空,需要讓尾結點的next指標指向新的首結點(原首結點的直接後繼)
    while (Phead->next) // 不斷向下檢查結點指標域
    {
        Phead = Phead->next;           // 進入下一個結點
        if (Phead->next == Head->next) // 當到達尾結點
        {
            break;
        }
    }

    Phead->next = Head->next->next; // 讓尾結點的next指標指向新的首結點(原首結點的直接後繼)
    Head->next = Phead->next;       // 頭結點的指標域 修改連結為 新的首結點
    Temp->next = NULL;              // 讓舊的首結點的指標域指向NULL, 從連結串列中斷開, 且防止野指標和記憶體洩漏
    free(Temp);                     // 釋放舊首結點的記憶體, 防止記憶體洩漏
    return true;
}

/**
 * @name       CircLList_DestDel
 * @brief     中刪, 刪除某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_DestDel(CircLList_t *Head, DataType_t dest)
{
    CircLList_t *Current = Head->next; // 操作指標 初始為指向首結點, 若為空連結串列則指向頭結點
    CircLList_t *Prev = Head;          // 操作指標 存放當前操作指標的前一個結點地址

    // 1.判斷單向迴圈連結串列是否為空,如果為空,則報錯
    if (Head == Head->next)
    {
        printf("Error,  CircularLinkList is empty! \n");
        return false;
    }

    // 2.若單向迴圈連結串列非空
    // 目標結點是首結點, 不再繼續查詢. 目標結點非首結點, 繼續向下查詢
    while (Current->data != dest)
    {
        Prev = Current;                                               // 備份Current的前一個位置
        Current = Current->next;                                      // 進入下一個結點
        if ((Current->next == Head->next) && (Current->data != dest)) // 達到末尾 且 末尾不是目標
        {
            printf("The target doesn't exist! \n");
            return false;
        }
    } // 找到目標結點, Current此時指向目標  Prev為Current 的前一個位置
    // 目標結點是首結點
    if (Current == Head->next)
    {
        // 若連結串列只有首結點
        if (Current->next == Head->next)
        {
            Head->next = Head; // 空連結串列狀態
            Current->next = NULL;
            free(Current); // 防止記憶體洩漏
            return true;
        }
        while (Prev->next) // 不斷向下檢查結點指標域
        {
            Prev = Prev->next;            // 進入下一個結點
            if (Prev->next == Head->next) // 結束條件: 達尾結點
            {
                break;
            }
        } // Prev到達未尾結點
        Prev->next = Current->next; // 更新尾結點指標域為新首結點地址
        Head->next = Current->next; // 更新首結點連結新首結點
    }
    else if (Current->next == Head->next) // 目標結點是尾結點
    {
        Prev->next = Head->next; // 新尾結點連結首結點, 行成迴圈
    }
    else // 目標結點是中間結點
    {
        Prev->next = Current->next;
    }
    Current->next = NULL;
    free(Current); // 防止記憶體洩漏
    return true;
}

/**
 * @name      CircLList_TailDel
 * @brief     刪除尾結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 連結串列為空
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool CircLList_TailDel(CircLList_t *Head)
{
    CircLList_t *Current = Head->next; // 操作指標 初始為指向首結點, 若為空連結串列則指向頭結點
    CircLList_t *Prev = Head;          // 操作指標 存放當前操作指標的前一個結點地址

    // 1.判斷單向迴圈連結串列是否為空,如果為空,則報錯
    if (Head == Head->next)
    {
        printf("Error,  CircularLinkList is empty! \n");
        return false;
    }

    // 2.若單向迴圈連結串列非空
    // 若連結串列只有首結點
    if (Current->next == Head->next)
    {
        Head->next = Head; // 空連結串列狀態
        Current->next = NULL;
        free(Current); // 防止記憶體洩漏
        return true;
    }
    // 若還有別的結點
    while (Current->next) // 不斷向下檢查結點指標域
    {
        Prev = Current;
        Current = Current->next;         // 進入下一個結點
        if (Current->next == Head->next) // 結束條件: 達尾結點
        {
            break;
        }
    } // Current到達未尾結點 Prev為Current 的前一個位置
    Prev->next = Head->next; // 新尾結點連結首結點, 行成迴圈
    Current->next = NULL;
    free(Current); // 防止記憶體洩漏
    return true;
}
/**
 * @name      CircLList_Print
 * @brief     從頭到尾遍歷連結串列
 * @param     Head 頭指標
 * @return    無
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
void CircLList_Print(CircLList_t *Head)
{
    // 判斷是否為空連結串列
    if (Head->next == Head)
    {
        printf("The list is empty.\n");
        return;
    }

    CircLList_t *Current = Head->next; // 指向首結點

    printf("Circular Linked List: ");

    while (Current->next) // 不斷向下檢查結點指標域
    {
        printf(" -> %d", Current->data); // 列印結點資料
        if (Current->next == Head->next) // 結束條件: 達尾結點
        {
            break;
        }
        Current = Current->next; // 進入下一個結點
    }
    printf("\n"); // 重新整理行緩衝, 輸出緩衝區
}

projecttesting.c

/**
 * @file name : projecttesting.c
 * @brief     : 實現單向迴圈連結串列的相關功能測試
 * @author    :yfm3262@163.com
 * @date      :2024/11/07
 * @version   :1.0
 * @note      :
 * CopyRight (c)  2023-2024   yfm3262@163.com   All Right Reseverd
 */

#include "CircularLinkedList.h"

int main(int argc, char const *argv[])
{
    // 建立單向迴圈連結串列, 空連結串列
    CircLList_t *Manager = CircLList_Create();

    // 頭插法 向連結串列中插入新結點
    printf("*********************************CircLList_HeadInsert********************************\n");
    CircLList_HeadInsert(Manager, 7);
    CircLList_HeadInsert(Manager, 4);
    CircLList_HeadInsert(Manager, 1);
    CircLList_HeadInsert(Manager, 8);
    CircLList_HeadInsert(Manager, 5);
    CircLList_HeadInsert(Manager, 2);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 2 -> 5 -> 8 -> 1 -> 4 -> 7*/

    // 中插法 向連結串列中插入新結點
    printf("*********************************CircLList_DestInsert********************************\n");
    CircLList_DestInsert(Manager, 7, 9);
    CircLList_DestInsert(Manager, 4, 6);
    CircLList_DestInsert(Manager, 2, 3);
    CircLList_DestInsert(Manager, 5, 10);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9*/

    // 尾插法 向連結串列中插入新結點
    printf("*********************************CircLList_TailInsert********************************\n");
    CircLList_TailInsert(Manager, 13);
    CircLList_TailInsert(Manager, 12);
    CircLList_TailInsert(Manager, 11);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11*/

    // 頭刪法 刪除首結點
    printf("*********************************CircLList_HeadDel********************************\n");
    CircLList_HeadDel(Manager);
    CircLList_HeadDel(Manager);
    CircLList_HeadDel(Manager);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11*/

    // 中刪法 刪除指定結點
    printf("*********************************CircLList_DestDel********************************\n");
    CircLList_DestDel(Manager, 10);
    CircLList_DestDel(Manager, 1);
    CircLList_DestDel(Manager, 6);
    CircLList_DestDel(Manager, 11);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 8 -> 4 -> 7 -> 9 -> 13 -> 12*/

    // 尾刪法 刪除尾結點
    printf("*********************************CircLList_HeadDel********************************\n");
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 8 -> 4*/
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    CircLList_TailDel(Manager);
    /*Error,  CircularLinkList is empty! */
    CircLList_HeadInsert(Manager, 66);
    CircLList_Print(Manager);
    /*Circular Linked List:  -> 66*/
    // 等待使用者響應
    printf("***Press any key to exit the test***\n");
    getchar();
    return 0;
}

二、雙向連結串列的原理與應用

如果想要提高單向連結串列或者單向迴圈連結串列的訪問速度,則可以在連結串列中的結點中再新增一個指標域,讓新新增的指標域指向當前結點的直接前驅的地址,也就意味著一個結點中有兩個指標域(prev + next),也被稱為雙向連結串列(Double Linked List)。

img

由於帶頭結點更加方便使用者進行資料訪問,所以本次建立一條帶頭結點的雙向不迴圈的連結串列。

img

(1) 建立一個空連結串列,由於是使用頭結點,所以就需要申請頭結點的堆記憶體並初始化即可!

img

(2) 建立新結點,為新結點申請堆記憶體並對新結點的資料域和指標域進行初始化,操作如下:

img

(3) 根據情況可以從連結串列中插入新結點,此時可以分為尾部插入、頭部插入、指定位置插入:
  • 頭插

img

  • 尾插

img

  • 中插

img

(4) 根據情況可以從連結串列中刪除某結點,此時可以分為尾部刪除、頭部刪除、指定結點刪除:
  • 頭刪

img

  • 尾刪

img

  • 中刪

img

程式碼

/**
  * @file name : 雙向連結串列
  * @brief     : 雙向連結串列的初始化、插入、刪除、修改、查詢列印
  * @author    : yfm3262@163.com
  * @date      : 2024/11/07
  * @version   : 1.0
  * @note      : 待續:修改資料、 查詢列印資料 
  * CopyRight (c)  2023-2024   yfm3262@163.com   All Right Reseverd
*/

雙向連結串列公式

/*
    雙向連結串列總結成公式
       struct xxx
       {
           //指標域(直接前驅的指標域)
           //資料域(需要存放什麼型別的資料,你就定義對應的變數即可)
           //指標域(直接後繼的指標域)
       };
    雙向連結串列的基本操作:
       初始化雙向連結串列 √
       插入資料 √
       刪除資料 √
       修改資料
       查詢列印資料
*/

初始化雙向連結串列

構建雙向連結串列結點

image

DoubleLList_t [*prev | data |*next ]
// 指的是雙向連結串列中的結點有效資料型別,使用者可以根據需要進行修改
typedef int DataType_t;

// 構造雙向連結串列的結點,連結串列中所有結點的資料型別應該是相同的
typedef struct DoubleLinkedList
{
    struct DoubleLinkedList *prev; // 直接前驅的指標域
    DataType_t data;               // 結點的資料域
    struct DoubleLinkedList *next; // 直接後繼的指標域

} DoubleLList_t;

建立一個空連結串列(僅頭結點)

image

/**
 * @name      DoubleLList_Create
 * @brief     建立一個空雙向連結串列,空連結串列應該有一個頭結點,對連結串列進行初始化
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
DoubleLList_t *DoubleLList_Create(void)
{
    // 1.建立一個頭結點並對頭結點申請記憶體
    DoubleLList_t *Head = (DoubleLList_t *)calloc(1, sizeof(DoubleLList_t));
    if (NULL == Head)
    {
        perror("Calloc memory for Head is Failed");
        exit(-1);
    }

    // 2.對頭結點進行初始化,頭結點是不儲存資料域,指標域指向NULL [prev|date|next]
    Head->prev = NULL;
    Head->next = NULL;

    // 3.把頭結點的地址返回即可   Head-->[prev|date|next]
    return Head;
}

建立一個新結點

image

/**
 * @name      DoubleLList_NewNode
 * @brief     建立新的結點,並對新結點進行初始化(資料域 + 指標域)
 * @param     data 要建立結點的元素
 * @return    程式執行成功與否
 *      @retval    NULL 申請堆記憶體失敗
 *      @retval    New  新結點地址
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
DoubleLList_t *DoubleLList_NewNode(DataType_t data)
{
    // 1.建立一個新結點並對新結點申請記憶體
    DoubleLList_t *New = (DoubleLList_t *)calloc(1, sizeof(DoubleLList_t));
    if (NULL == New)
    {
        perror("Calloc memory for NewNode is Failed");
        return NULL;
    }

    // 2.對新結點的資料域和指標域(2個)進行初始化
    New->data = data;
    New->prev = NULL;
    New->next = NULL;

    return New;
}

插入資料

頭插

image

/**
 * @name      DoubleLList_HeadInsert
 * @brief     在雙向連結串列的頭結點後插入
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool DoubleLList_HeadInsert(DoublelList_t *Head, DataType_t data)
{
    // 1.建立新結點並對新結點進行初始化
    DoubleLList_t *New = DoubleLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }
    // 2.判斷雙向連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (NULL == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        return true;
    }
    // 3.如果雙向連結串列為非空,則把新結點插入到連結串列的頭部
    New->next = Head->next; // 新結點的next指標指向原本的首結點地址
    Head->next->prev = New; // 原本的首結點的prev指標指向新結點的地址
    Head->next = New;       // 更新頭結點的next指標,讓next指標指向新結點的地址
    return true;
}

中插

image

/**
 * @name      Doublellist_DestInsert
 * @brief     雙向連結串列中的指定元素後面插入新結點
 * @param     Head 頭指標
 * @param     dest 要查詢的結點
 * @param     data 要插入的資料
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool Doublellist_DestInsert(DoubleLList_t *Head, DataType_t dest, DataType_t data)
{
    DoubleLList_t *Phead = Head; // 備份頭結點地址,防止頭結點丟失

    // 1.建立新結點並對新結點進行初始化
    DoubleLList_t *New = DoubleLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }

    // 2.判斷雙向連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (NULL == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        return true;
    }

    // 3.如果雙向連結串列為非空,此時分為3種情況(尾部or中間)
    while (Phead->next)
    {
        Phead = Phead->next;
        if (Phead->data == dest)
        {
            break;
        }
    }

    // 如果遍歷連結串列之後發現沒有目標結點,則退出即可
    if (Phead->next == NULL && Phead->data != dest)
    {
        printf("dest node is not found\n");
        return false;
    }

    // 如果遍歷連結串列找到目標結點,則分為(尾部or中間)
    if (Phead->next == NULL) // 尾插
    {
        New->prev = Phead; // 新結點的prev指標指向尾結點的地址
        Phead->next = New; // 尾結點的next指標指向新結點
    }
    else // 中間
    {
        New->next = Phead->next; // 新結點的next指標指向目標結點的直接後繼結點
        New->prev = Phead;       // 新結點的prev指標指向目標結點的地址
        Phead->next->prev = New; // 目標結點的直接後繼結點的prev指標指向新結點
        Phead->next = New;       // 目標結點的next指標指向新結點
    }
    return true;
}

尾插

image

/**
 * @name      DoubleLList_TailInsert
 * @brief     將新元素插入到尾結點後面
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool DoubleLList_TailInsert(DoubleLList_t *Head, DataType_t data)
{
    DoubleLList_t *Phead = Head; // 備份頭結點地址,防止頭結點丟失
    // 1.建立新結點並對新結點進行初始化
    DoubleLList_t *New = DoubleLList_NewNode(data);
    if (NULL == New)
    {
        printf("can not insert new node , Failed to create a node\n");
        return false;
    }
    // 2,判斷雙向連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (NULL == Head->next)
    {
        Head->next = New; // 讓頭結點的next指標指向新結點
        return true;
    }
    // 3.如果雙向連結串列為非空,則把新結點插入到連結串列的尾部
    while (Phead->next)
    {
        Phead = Phead->next;
    }
    Phead->next = New; // 尾結點的next指標指向新結點地址
    New->prev = Phead; // 新結點的prev指標指向原本的尾結點地址
    return true;
}

刪除資料

頭刪

image

/**
 * @name      DoublelList_HeadDel
 * @brief     刪除頭結點後面的一個結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool DoublelList_HeadDel(DoubleLList_t *Head)
{
    // 檢查連結串列是否為空或只有頭結點
    if (Head == NULL || Head->next == NULL)
    {
        return false;
    }

    DoubleLList_t *Phead = Head->next; // 指向頭結點後的第一個結點

    // 如果頭結點後還有更多結點
    if (Phead->next != NULL)
    {
        Phead->next->prev = Head;
    }
    Head->next = Phead->next; // 更新頭結點的next指標
    Phead->next = NULL;       // 防止野指標和記憶體洩漏
    // 清理節點
    free(Phead); // 釋放記憶體

    return true;
}

中刪

image

/**
 * @name      DoublelList_DestDel
 * @brief     中刪, 刪除某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool DoublelList_DestDel(DoubleLList_t *Head, DataType_t dest)
{
    DoubleLList_t *Phead = Head; // 備份頭結點地址,防止頭結點丟失
    // 1.判斷雙向連結串列是否為空, 如果為空, 則直接插入到頭結點之後
    if (NULL == Head->next)
    {
        printf("linked list is empty\n");
        return false;
    }
    // 2.如果雙向連結串列為非空,此時遍歷連結串列查詢沒有目標結點(找到or未找到)
    while (Phead->next)
    {
        Phead = Phead->next;

        if (Phead->data = dest)
        {
            break;
        }
    }

    // 如果連結串列中沒有目標結點,此時直接退出即可
    if (Phead->next == NULL && Phead->data != dest)
    {
        printf("dest node is not found\n");
        return false;
    }

    // 如果連結串列中發現目標結點,此時分為(頭部 or尾部 or中間)
    if (Phead == Head->next) // 頭部
    {
        Head->next = Phead->next; // 更新頭結點,讓頭結點的next指標指向首結點的直接後繼
        if (Phead->next != NULL)
        {
            Phead->next->prev = NULL;
            Phead->next = NULL;
        }
        free(Phead); // 釋放待刪除結點記憶體
    }
    else if (Phead->next == NULL) // 尾部
    {
        Phead->prev->next = NULL; // 尾結點的直接前驅結點的next指標指向NULL
        Phead->prev = NULL;       // 尾結點的prev指標指向NULL
        free(Phead);              // 釋放待刪除結點記憶體
    }
    else
    {
        Phead->prev->next = Phead->next; // 讓待刪除結點的直接前驅結點的next指標指向待刪除結點的直接後繼地址
        Phead->next->prev = Phead->prev; // 讓待刪除結點的直接後繼結點的prev指標指向待刪除結點的直接前驅地址
        Phead->next = NULL;              // 讓待刪除結點的next指標指向NULL
        Phead->prev = NULL;              // 讓待刪除結點的prev指標指向NULL
        free(Phead);                     // 釋放待刪除結點記憶體
    }
    return true;
}

尾刪

image

/**
 * @name      DoublelList_TailDel
 * @brief     刪除尾結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 連結串列為空
 *      @retval    true  刪除成功
 * @date      2024/11/07
 * @version   1.0
 * @note
 */
bool DoublelList_TailDel(DoubleLList_t *Head)
{
    // 建立操作指標
    DoubleLList_t *current = Head;

    // 檢查連結串列是否為空或只有頭結點
    if (Head == NULL || Head->next == NULL)
    {
        return false;
    }

    // 遍歷到尾結點
    while (current->next != NULL)
    {
        current = current->next;
    }

    // current現在指向尾結點
    if (current->prev != NULL)
    {
        current->prev->next = NULL; // 將尾結點的直接前驅設定為NULL
    }

    // 釋放尾結點的記憶體, 防止記憶體洩漏
    free(current);

    return true;
}

其它

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
// 主函式,用於演示
int main(int argc, char *argv[]) {


    return 0;
}

練習:

img

img

img

img

img

img

img

img

相關文章