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

舟清颺發表於2024-04-26

版本:

2024年4月26日 V1.0 釋出於部落格園

/**
 * @file name : DoubleLinkedList.c
 * @brief     : 實現雙向迴圈連結串列的相關功能
 * @author    :RISE_AND_GRIND@163.com
 * @date      :2024/04/26
 * @version   :1.0
 * @note      :
 * CopyRight (c)  2023-2024   RISE_AND_GRIND@163.com   All Right Reseverd
 */

目錄

目錄
  • 目錄
  • 雙向迴圈連結串列公式
  • 初始化雙向迴圈連結串列
    • 構建雙向迴圈連結串列結點
    • 建立一個空連結串列(僅頭結點)
    • 建立一個新結點
  • 插入資料
    • 頭插
    • 中插
    • 尾插
  • 刪除資料
    • 頭刪
    • 中刪
    • 尾刪
  • 查詢列印資料
    • 遍歷列印
  • 測試
    • 測試結果:
  • 完整程式碼
    • DoubleCirLList.h
    • DoubleCirLList.c
    • projecttesting.c


雙向迴圈連結串列公式

image

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

初始化雙向迴圈連結串列

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

DoubleLList_t[ *prev | data |*next ]

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

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

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

/**
 * @name      DoubleCirLList_Create
 * @brief     建立一個空雙向迴圈連結串列,空連結串列應該有一個頭結點,頭結點前驅指標域和後繼指標域名均指向自己, 對連結串列進行初始化Head-->[*prev|data|*next]
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
DoubleLList_t *DoubleCirLList_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.對頭結點進行初始化,頭結點是不儲存資料域,指標域指向自身即可,體現“迴圈”
    Head->prev = Head;
    Head->next = Head;
    // 3.把頭結點的地址返回即可
    return Head;
}

建立一個新結點

/**
 * @name      DoubleCirLList_NewNode
 * @brief     建立新的結點,並對新結點進行初始化(直接前驅指標域+ 資料域 + 直接後繼指標域) [*prev|data|*next]
 * @param     data 要建立結點的元素
 * @return    程式執行成功與否
 *      @retval    NULL 申請堆記憶體失敗
 *      @retval    New  新結點地址
 * @date      2024/04/26
 * @version   1.0
 * @note      新結點指標域初始化後預設指向自己
 */
DoubleLList_t *DoubleCirLList_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->prev = New;
    New->data = data;
    New->next = New;

    return New;
}

插入資料

頭插

image

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

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

    // 2.判斷雙向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (Head->next == Head)
    {

        Head->next = New; // 讓頭結點的next指標指向新結點
        return true;
    }

    Head->next->prev->next = New; // 首結點前驅為尾結點地址, 將尾結點連結新首結點
    New->prev = Head->next->prev; // 將新結點前驅連結尾結點

    New->next = Head->next; // 新首結點連結舊首結點
    Head->next->prev = New; // 舊首結點連結新結點
    Head->next = New;       // 頭結點連結新首結點

    return true;
}

中插

image

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

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

    // 2.判斷雙向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (Head->next == Head)
    {

        Head->next = New; // 讓頭結點的next指標指向新結點
        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->next == Head->next) // 目標結點是尾結點
    {

        New->next = Head->next; // 尾處理: 新結點直接後繼連結首結點
        Head->next->prev = New; // 頭處理: 首結點直接前驅連結新結點
        New->prev = Current;    // 內部處理: 新結點直接前驅連結舊尾結點
        Current->next = New;    // 內部處理: 舊結點連結新尾結點
    }
    else // 目標結點是中間結點 或首結點
    {
        New->next = Current->next;
        New->prev = Current;
        Current->next->prev = New;
        Current->next = New;
    }
    return true;
}

尾插

image

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

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

    // 2.判斷雙向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (Head->next == Head)
    {

        Head->next = New; // 讓頭結點的next指標指向新結點
        return true;
    }

    // 3.如果雙向迴圈連結串列為非空,需要讓尾結點的next指標指向新結點,新結點指向首結點
    New->next = Head->next;       // 新結點連結首結點
    New->prev = Head->next->prev; // 新結點連結尾結點
    Head->next->prev->next = New; // 內部處理: 舊尾結點連結新尾結點
    Head->next->prev = New;       // 首結點連結新尾結點

    return true;
}

刪除資料

頭刪

image

/**
 * @name      DoubleCirLList_HeadDel
 * @brief     刪除首節點
 * @param      Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
bool DoubleCirLList_HeadDel(DoubleLList_t *Head)
{
    // 1.建立操作指標
    // 指向頭結點, 操作指標
    DoubleLList_t *Current = Head;

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

    // 3.判斷連結串列非空連結串列
    // ①若只有首結點
    if (Head->next == Head->next->next)
    {
        Head->next->prev = NULL; // 首結點指標域處理, 防止記憶體洩漏
        Head->next->next = NULL;
        free(Head->next);  // 釋放首結點, 防止記憶體洩漏
        Head->next = Head; // 頭結點指向自己, 表示迴圈
        return true;
    }
    // ②若不止首結點
    Head->next->prev->next = Head->next->next; // 尾結點直接後繼指標域連結新首結點
    Current = Head->next;                      // 操作指標備份首結點地址
    Head->next = Current->next;                // 頭結點連結新首結點
    Head->next->prev = Current->prev;          // 新首結點直接前驅指標域連結尾結點
    Current->prev = NULL;
    Current->next = NULL;
    free(Current); // 釋放首結點, 防止記憶體洩漏
    return true;
}

中刪

image

/**
 * @name       DoubleCirLList_DestDel
 * @brief     中刪, 刪除雙向迴圈連結串列某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
bool DoubleCirLList_DestDel(DoubleLList_t *Head, DataType_t dest)
{
    // 1.建立操作指標
    // 指向頭結點, 操作指標
    DoubleLList_t *Current = Head;

    // 2.判斷雙向迴圈連結串列是否為空連結串列,如果為空, 則退出
    if (Head == Head->next)
    {
        printf("DoubleLList is Empty! \n");
        return false;
    }
    // 3.若雙向迴圈連結串列非空
    // 尋找目標結點
    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)
    {
        // ①若連結串列只有首結點
        if (Current->next == Head->next)
        {
            // 刪除首結點, 變成空連結串列
            Head->next = Head;

        } // ②若連結串列不止首節點
        Head->next->prev->next = Current->next; // 更新尾結點指標域為新首結點地址
        Current->next->prev = Head->next->prev; // 更新新首節點的前驅指標域連結尾結點
        Head->next = Current->next;             // 更新首結點連結新首結點
    }
    else if (Current->next == Head->next) // ③目標結點是尾結點
    {
        Current->prev->next = Head->next; // 新尾結點連結首結點, 行成迴圈
        Head->next->prev = Current->prev;
    }
    else // ④目標結點是中間結點
    {
        Current->prev->next = Current->next;
        Current->next->prev = Current->prev;
    }
    // 刪除目標結點
    Current->prev = NULL;
    Current->next = NULL;
    free(Current); // 防止記憶體洩漏
    return true;
}

尾刪

image

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

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

    Current = Head->next->prev; // 備份尾結點
    // 3.①若雙向迴圈連結串列非空
    //  若連結串列只有首結點
    if (Head->next == Head->next->next)
    {
        // 刪除首結點, 變成空連結串列
        Head->next = Head;
        // printf("只有首節點的值 %d \n", Head->next->data);
    }
    else if (Head->next != Head->next->next) // ②若首結點直接前驅不是自己, 則還有別的結點
    {
        // printf("不止只有首節點的值 %d \n", Head->next->data);
        Head->next->prev = Current->prev; // 首結點直接前驅連結新尾結點
        Current->prev->next = Head->next; // 新尾結點的直接後繼指標域連結首結點
    }
}

查詢列印資料

遍歷列印

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

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

    printf("Double Circular Linked List: ");

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

測試

#include "DoubleCirLList.h"

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

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

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

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

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

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

    // 尾刪法 刪除尾結點
    printf("*********************************DoubleCirLList_TailDel********************************\n");
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_Print(Manager);
    /*Double Circular Linked List:  -> 8 -> 4*/
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    /*Error,  Double Circular Linked List is empty! */
    DoubleCirLList_HeadInsert(Manager, 66);
    DoubleCirLList_Print(Manager);
    /*Double Circular Linked List:  -> 66*/

    // 等待使用者響應
    printf("***Press any key to exit the test***\n");
    getchar();
    return 0;
}

測試結果:

*********************************DoubleCirLList_HeadInsert********************************
Double Circular Linked List:  -> 2 -> 5 -> 8 -> 1 -> 4 -> 7
*********************************DoubleCirLList_DestInsert********************************
Double Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9
*********************************DoubleCirLList_TailInsert********************************
Double Circular Linked List:  -> 2 -> 3 -> 5 -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11
*********************************DoubleCirLList_HeadDel********************************
Double Circular Linked List:  -> 10 -> 8 -> 1 -> 4 -> 6 -> 7 -> 9 -> 13 -> 12 -> 11
*********************************DoubleCirLList_DestDel********************************
Double Circular Linked List:  -> 8 -> 4 -> 7 -> 9 -> 13 -> 12
*********************************DoubleCirLList_TailDel********************************
Double Circular Linked List:  -> 8 -> 4
Error,  Double Circular Linked List is empty! 
Double Circular Linked List:  -> 66
***Press any key to exit the test***

完整程式碼

DoubleCirLList.h

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

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

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

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

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

/**
 * @name      DoubleCirLList_Create
 * @brief     建立一個空雙向迴圈連結串列,空連結串列應該有一個頭結點,頭結點前驅指標域和後繼指標域名均指向自己, 對連結串列進行初始化Head-->[*prev|data|*next]
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
DoubleLList_t *DoubleCirLList_Create(void);

/**
 * @name      DoubleCirLList_NewNode
 * @brief     建立新的結點,並對新結點進行初始化(直接前驅指標域+ 資料域 + 直接後繼指標域) [*prev|data|*next]
 * @param     data 要建立結點的元素
 * @return    程式執行成功與否
 *      @retval    NULL 申請堆記憶體失敗
 *      @retval    New  新結點地址
 * @date      2024/04/26
 * @version   1.0
 * @note      新結點指標域初始化後預設指向自己
 */
DoubleLList_t *DoubleCirLList_NewNode(DataType_t data);

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

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

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

/**
 * @name      DoubleCirLList_HeadDel
 * @brief     刪除首節點
 * @param      Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
bool DoubleCirLList_HeadDel(DoubleLList_t *Head);

/**
 * @name       DoubleCirLList_DestDel
 * @brief     中刪, 刪除雙向迴圈連結串列某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
bool DoubleCirLList_DestDel(DoubleLList_t *Head, DataType_t dest);

/**
 * @name      DoubleCirLList_TailDel
 * @brief     刪除尾結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 連結串列為空
 *      @retval    true  刪除成功
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
bool DoubleCirLList_TailDel(DoubleLList_t *Head);

/**
 * @name      DoubleCirLList_Print
 * @brief     從頭到尾遍歷連結串列
 * @param     Head 頭指標
 * @return    無
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
void DoubleCirLList_Print(DoubleLList_t *Head);

#endif
// 結束定義

DoubleCirLList.c

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

/**
 * @name      DoubleCirLList_Create
 * @brief     建立一個空雙向迴圈連結串列,空連結串列應該有一個頭結點,頭結點前驅指標域和後繼指標域名均指向自己, 對連結串列進行初始化Head-->[*prev|data|*next]
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
DoubleLList_t *DoubleCirLList_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.對頭結點進行初始化,頭結點是不儲存資料域,指標域指向自身即可,體現“迴圈”
    Head->prev = Head;
    Head->next = Head;
    // 3.把頭結點的地址返回即可
    return Head;
}

/**
 * @name      DoubleCirLList_NewNode
 * @brief     建立新的結點,並對新結點進行初始化(直接前驅指標域+ 資料域 + 直接後繼指標域) [*prev|data|*next]
 * @param     data 要建立結點的元素
 * @return    程式執行成功與否
 *      @retval    NULL 申請堆記憶體失敗
 *      @retval    New  新結點地址
 * @date      2024/04/26
 * @version   1.0
 * @note      新結點指標域初始化後預設指向自己
 */
DoubleLList_t *DoubleCirLList_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->prev = New;
    New->data = data;
    New->next = New;

    return New;
}

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

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

    // 2.判斷雙向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (Head->next == Head)
    {

        Head->next = New; // 讓頭結點的next指標指向新結點
        return true;
    }

    Head->next->prev->next = New; // 首結點前驅為尾結點地址, 將尾結點連結新首結點
    New->prev = Head->next->prev; // 將新結點前驅連結尾結點

    New->next = Head->next; // 新首結點連結舊首結點
    Head->next->prev = New; // 舊首結點連結新結點
    Head->next = New;       // 頭結點連結新首結點

    return true;
}

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

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

    // 2.判斷雙向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (Head->next == Head)
    {

        Head->next = New; // 讓頭結點的next指標指向新結點
        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->next == Head->next) // 目標結點是尾結點
    {

        New->next = Head->next; // 尾處理: 新結點直接後繼連結首結點
        Head->next->prev = New; // 頭處理: 首結點直接前驅連結新結點
        New->prev = Current;    // 內部處理: 新結點直接前驅連結舊尾結點
        Current->next = New;    // 內部處理: 舊結點連結新尾結點
    }
    else // 目標結點是中間結點 或首結點
    {
        New->next = Current->next;
        New->prev = Current;
        Current->next->prev = New;
        Current->next = New;
    }
    return true;
}

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

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

    // 2.判斷雙向迴圈連結串列是否為空,如果為空,則直接插入到頭結點之後
    if (Head->next == Head)
    {

        Head->next = New; // 讓頭結點的next指標指向新結點
        return true;
    }

    // 3.如果雙向迴圈連結串列為非空,需要讓尾結點的next指標指向新結點,新結點指向首結點
    New->next = Head->next;       // 新結點連結首結點
    New->prev = Head->next->prev; // 新結點連結尾結點
    Head->next->prev->next = New; // 內部處理: 舊尾結點連結新尾結點
    Head->next->prev = New;       // 首結點連結新尾結點

    return true;
}

/**
 * @name      DoubleCirLList_HeadDel
 * @brief     刪除首節點
 * @param      Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
bool DoubleCirLList_HeadDel(DoubleLList_t *Head)
{
    // 1.建立操作指標
    // 指向頭結點, 操作指標
    DoubleLList_t *Current = Head;

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

    // 3.判斷連結串列非空連結串列
    // ①若只有首結點
    if (Head->next == Head->next->next)
    {
        Head->next->prev = NULL; // 首結點指標域處理, 防止記憶體洩漏
        Head->next->next = NULL;
        free(Head->next);  // 釋放首結點, 防止記憶體洩漏
        Head->next = Head; // 頭結點指向自己, 表示迴圈
        return true;
    }
    // ②若不止首結點
    Head->next->prev->next = Head->next->next; // 尾結點直接後繼指標域連結新首結點
    Current = Head->next;                      // 操作指標備份首結點地址
    Head->next = Current->next;                // 頭結點連結新首結點
    Head->next->prev = Current->prev;          // 新首結點直接前驅指標域連結尾結點
    Current->prev = NULL;
    Current->next = NULL;
    free(Current); // 釋放首結點, 防止記憶體洩漏
    return true;
}

/**
 * @name       DoubleCirLList_DestDel
 * @brief     中刪, 刪除雙向迴圈連結串列某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/04/26
 * @version   1.0
 * @note
 */
bool DoubleCirLList_DestDel(DoubleLList_t *Head, DataType_t dest)
{
    // 1.建立操作指標
    // 指向頭結點, 操作指標
    DoubleLList_t *Current = Head;

    // 2.判斷雙向迴圈連結串列是否為空連結串列,如果為空, 則退出
    if (Head == Head->next)
    {
        printf("DoubleLList is Empty! \n");
        return false;
    }
    // 3.若雙向迴圈連結串列非空
    // 尋找目標結點
    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)
    {
        // ①若連結串列只有首結點
        if (Current->next == Head->next)
        {
            // 刪除首結點, 變成空連結串列
            Head->next = Head;

        } // ②若連結串列不止首節點
        Head->next->prev->next = Current->next; // 更新尾結點指標域為新首結點地址
        Current->next->prev = Head->next->prev; // 更新新首節點的前驅指標域連結尾結點
        Head->next = Current->next;             // 更新首結點連結新首結點
    }
    else if (Current->next == Head->next) // ③目標結點是尾結點
    {
        Current->prev->next = Head->next; // 新尾結點連結首結點, 行成迴圈
        Head->next->prev = Current->prev;
    }
    else // ④目標結點是中間結點
    {
        Current->prev->next = Current->next;
        Current->next->prev = Current->prev;
    }
    // 刪除目標結點
    Current->prev = NULL;
    Current->next = NULL;
    free(Current); // 防止記憶體洩漏
    return true;
}

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

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

    Current = Head->next->prev; // 備份尾結點
    // 3.①若雙向迴圈連結串列非空
    //  若連結串列只有首結點
    if (Head->next == Head->next->next)
    {
        // 刪除首結點, 變成空連結串列
        Head->next = Head;
        // printf("只有首節點的值 %d \n", Head->next->data);
    }
    else if (Head->next != Head->next->next) // ②若首結點直接前驅不是自己, 則還有別的結點
    {
        // printf("不止只有首節點的值 %d \n", Head->next->data);
        Head->next->prev = Current->prev; // 首結點直接前驅連結新尾結點
        Current->prev->next = Head->next; // 新尾結點的直接後繼指標域連結首結點
    }
}

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

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

    printf("Double 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    :RISE_AND_GRIND@163.com
 * @date      :2024/04/26
 * @version   :1.0
 * @note      :
 * CopyRight (c)  2023-2024   RISE_AND_GRIND@163.com   All Right Reseverd
 */

#include "DoubleCirLList.h"

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

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

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

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

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

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

    // 尾刪法 刪除尾結點
    printf("*********************************DoubleCirLList_TailDel********************************\n");
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_Print(Manager);
    /*Double Circular Linked List:  -> 8 -> 4*/
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    DoubleCirLList_TailDel(Manager);
    /*Error,  Double Circular Linked List is empty! */
    DoubleCirLList_HeadInsert(Manager, 66);
    DoubleCirLList_Print(Manager);
    /*Double Circular Linked List:  -> 66*/

    // 等待使用者響應
    printf("***Press any key to exit the test***\n");
    getchar();
    return 0;
}

相關文章