資料結構(雙向連結串列的實現)

万新鹏發表於2024-04-24

目錄
  • 一:帶頭雙向連結串列
    • 什麼叫雙向連結串列:
  • 二:實現
    • 宣告結構體
    • (1).建立頭結點進行初始化
    • (2).動態申請一個結點
    • (3).插入
      • 頭插入:
      • 尾插入
      • 指定位置插入(中間插入)
    • 4:刪除:
      • 頭刪除:
      • 尾刪除:
      • 中間刪除:
      • 5:列印連結串列

一:帶頭雙向連結串列

什麼叫雙向連結串列:

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

二:實現

宣告結構體

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

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

} DoubleLList_t;

(1).建立頭結點進行初始化

因為為帶頭的雙向迴圈連結串列,因此初始化頭結點時,頭結點的prev,next指標都指向自已

/**
 * @name      DoubleLList_Create
 * @brief     建立一個空雙向連結串列,空連結串列應該有一個頭結點,對連結串列進行初始化
 * @param
 * @return
 *      @retval    Head 頭結點地址
 * @date      2024/04/24
 * @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;
}

(2).動態申請一個結點

動態申請一個大小為sizeof(ListNode)的結點

/**
* @name      DoubleLList_NewNode
* @brief     建立新的結點,並對新結點進行初始化(資料域 + 指標域)
* @param     data 要建立結點的元素
* @return    程式執行成功與否
*      @retval    NULL 申請堆記憶體失敗
*      @retval    New  新結點地址
* @date      2024/04/24
* @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;
}

(3).插入

頭插入:

 * @name      DoubleLList_HeadInsert
 * @brief     在雙向連結串列的頭結點後插入
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/04/24
 * @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_TailInsert
 * @brief     將新元素插入到尾結點後面
 * @param     Head 頭指標
 * @param     data 新元素
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/04/24
 * @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

指定位置插入(中間插入)

要找到要插入的目標結點,就可以得出目標結點得下個結點(目標結點->next)

/**
 * @name      Doublellist_DestInsert
 * @brief     雙向連結串列中的指定元素後面插入新結點
 * @param     Head 頭指標
 * @param     dest 要查詢的結點
 * @param     data 要插入的資料
 * @return 程式執行成功與否
 *      @retval    false 插入失敗
 *      @retval    true  插入成功
 * @date      2024/04/24
 * @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

4:刪除:

頭刪除:

/**
 * @name      DoublelList_HeadDel
 * @brief     刪除頭結點後面的一個結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗
 *      @retval    true  刪除成功
 * @date      2024/04/24
 * @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_TailDel
 * @brief     刪除尾結點
 * @param     Head 頭指標
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 連結串列為空
 *      @retval    true  刪除成功
 * @date      2024/04/23
 * @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;
}

image

中間刪除:

/**
 * @name      DoublelList_DestDel
 * @brief     中刪, 刪除某個元素結點
 * @param     Head 頭指標
 * @param     dest 要刪除的目標元素
 * @return 程式執行成功與否
 *      @retval    false 刪除失敗, 未找到目標元素結點
 *      @retval    true  刪除成功
 * @date      2024/04/24
 * @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

5:列印連結串列

遍歷一次連結串列,終止條件為 temp != head ,因為 temp = head 說明已經迴圈了一遍

// 列印
void Listprint(DoubleLList_t* head)
{
	assert(head);
	DoubleLList_t* temp = head->next;
	while (head != temp)
	{
		printf("%d ", temp->data);
		temp = temp->next;
	}
	printf("\n");
}

相關文章