目錄
- 一:帶頭雙向連結串列
- 什麼叫雙向連結串列:
- 二:實現
- 宣告結構體
- (1).建立頭結點進行初始化
- (2).動態申請一個結點
- (3).插入
- 頭插入:
- 尾插入
- 指定位置插入(中間插入)
- 4:刪除:
- 頭刪除:
- 尾刪除:
- 中間刪除:
- 5:列印連結串列
一:帶頭雙向連結串列
什麼叫雙向連結串列:
結點互相指向的,首結點指向null,尾結點指向null。
如果想要提高單向連結串列或者單向迴圈連結串列的訪問速度,則可以在連結串列中的結點中再新增一個指標域,讓新新增的指標域指向當前結點的直接前驅的地址,也就意味著一個結點中有兩個指標域(prev + next),也被稱為雙向連結串列(Double Linked List)。
二:實現
宣告結構體
// 指的是雙向連結串列中的結點有效資料型別,使用者可以根據需要進行修改
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;
}
尾插入
/**
* @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;
}
指定位置插入(中間插入)
要找到要插入的目標結點,就可以得出目標結點得下個結點(目標結點->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;
}
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;
}
尾刪除:
/**
* @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;
}
中間刪除:
/**
* @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;
}
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");
}