一、雙向迴圈連結串列的原理與應用
雙向迴圈連結串列與雙向連結串列的區別:指的是雙向迴圈連結串列的首結點中的prev指標成員指向連結串列的尾結點,並且雙向迴圈連結串列的尾結點裡的next指標成員指向連結串列的首結點,所以雙向迴圈連結串列也屬於環形結構。
由於帶頭結點更加方便使用者進行資料訪問,所以本次建立一條帶頭結點的雙向迴圈的連結串列。
(1) 建立一個空連結串列,由於是使用頭結點,所以就需要申請頭結點的堆記憶體並初始化即可!
(2) 建立新結點,為新結點申請堆記憶體並對新結點的資料域和指標域進行初始化,操作如下:
(3) 根據情況可以從連結串列中插入新結點,此時可以分為尾部插入、頭部插入、指定位置插入:
- 頭插
- 尾插
- 中插
(4) 根據情況可以從連結串列中刪除某結點,此時可以分為尾部刪除、頭部刪除、指定結點刪除:
- 頭刪
- 尾刪
- 中刪
程式碼
/**
* @file name : DoubleLinkedList.c
* @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
* {
* //指標域(直接前驅的指標域)
* //資料域(需要存放什麼型別的資料,你就定義對應的變數即可)
* //指標域(直接後繼的指標域)
* };
* 雙向迴圈連結串列的基本操作:
* 初始化單向迴圈連結串列 √
* 插入資料 √
* 刪除資料 √
* 修改資料
* 查詢列印資料√
*/
初始化雙向迴圈連結串列
構建雙向迴圈連結串列結點
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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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 :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 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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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 :yfm3262@163.com
* @date :2024/04/26
* @version :1.0
* @note :
* CopyRight (c) 2023-2024 yfm3262@163.com All Right Reseverd
*/
#include "DoubleCirLList.h"
/**
* @name DoubleCirLList_Create
* @brief 建立一個空雙向迴圈連結串列,空連結串列應該有一個頭結點,頭結點前驅指標域和後繼指標域名均指向自己, 對連結串列進行初始化Head-->[*prev|data|*next]
* @param
* @return
* @retval Head 頭結點地址
* @date 2024/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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/11/07
* @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 :yfm3262@163.com
* @date :2024/11/07
* @version :1.0
* @note :
* CopyRight (c) 2023-2024 yfm3262@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;
}
二、棧的原理與應用
大家學習資料結構的目的是為了更好的處理和儲存資料,對於順序表而言改查比較容易,增刪比較麻煩,對於鏈式表而言,增刪比較簡單,改查比較麻煩,所以每種資料結構都有不同的特點,使用者需要選擇合適的資料結構。
思考:資料結構中有一種結構稱為棧,而linux記憶體中的棧空間就是基於此設計的,請問棧應該如何設計?
之前學習linux記憶體分割槽的時候,已經知道棧記憶體自頂向下進行遞增,其實棧和順序表以及鏈式表都一樣,都屬於線性結構,儲存的資料的邏輯關係也是一對一的。
只不過棧是一種特殊的線性表,特殊在棧的一端是封閉的,資料的插入與刪除只能在棧的另一端進行,也就是棧遵循“*後進先出*”的原則。也被成為“LIFO”結構,意思是“last input first output”。
棧(stack),儲存貨物或供旅客住宿的地方,可引申為倉庫、中轉站,所以引入到計算機領域裡,就是指資料暫時儲存的地方,所以才有進棧(PUSH)、出棧(POP)的說法。
棧就像是一摞書,拿到新書時我們會把它放在書堆的最上面,取書時也只能從最上面的新書開始取。
閉合的一端被稱為棧底(Stack Bottom),允許資料的插入與刪除的一端被稱為棧頂(Stack Top),不包含任何元素的棧被稱為空棧。
- 把資料插入到棧空間的動作被稱為入棧或者壓棧
- 從棧空間中刪除資料的動作被稱為出棧或者彈棧
由於棧也是一種線性結構,所以可以以陣列或者連結串列作為基礎,在此基礎上實現棧的操作。
-
以陣列作為基礎實現棧空間(順序棧)
陣列在記憶體中佔用一塊連續的空間,也就是陣列元素的記憶體地址是連續的。為了實現棧,一般是把陣列頭作為棧底,陣列頭部到陣列尾部作為棧的增長方向,也就是使用者只在陣列尾部對資料進行插入和刪除。
為了方便管理順序棧所以需要構造管理順序棧資訊的結構體型別,用於記錄重要引數,如下:
(1) 建立一個空的順序棧,併為記錄順序棧資訊的結構體申請堆記憶體,並進行初始化即可!
(2) 根據棧的特性,把新元素從棧頂入棧,也就是從陣列的尾部進行元素插入,操作如下:
(3) 根據棧的特性,把元素從棧頂出棧,也就是把元素從陣列的尾部把元素刪除,操作如下:
(4) 對順序棧中的元素進行遍歷,只需要從順序棧的棧底開始向棧頂進行遍歷,操作如下:
-
以連結串列作為基礎實現棧空間(鏈式棧)
如果打算實現鏈式棧,一般是以連結串列作為基礎,一般是把連結串列頭部作為棧頂,方便資料的插入和刪除(頭插+頭刪),鏈式棧相當於是一個單向不迴圈的連結串列。
練習:
設計一個進位制轉換程式,使用順序棧設計一個把十進位制數轉換為十六進位制數的介面,實現當透過鍵盤輸入一個非負的十進位制數,可以在終端輸出對應的十六進位制數。
/********************************************************************************************************
*
*
* 該程式實現順序棧元素的增刪改查,目的是提高設計程式的邏輯思維,另外為了提高可移植性,所以順序棧中元素的
* 資料型別為DataType_t,使用者可以根據實際情況修改順序表中元素的型別。
*
* 另外,為了方便管理順序棧,所以使用者設計SeqStack_t結構體,該結構體中包含三個成員:棧底地址+棧容量+棧頂元素的下標
*
*
*
* Copyright (c) 2023-2024 yfm3262@163.com All right Reserved
* ******************************************************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
//指的是順序棧中的元素的資料型別,使用者可以根據需要進行修改
typedef int DataType_t;
//構造記錄順序棧SequenceStack各項引數(棧底地址+棧容量+棧頂元素的下標)的結構體
typedef struct SequenceStack
{
DataType_t * Bottom; //記錄棧底地址
unsigned int Size; //記錄棧容量
int Top; //記錄棧頂元素的下標
}SeqStack_t;
//建立順序表並對順序棧進行初始化
SeqStack_t * SeqStack_Create(unsigned int size)
{
//1.利用calloc為順序棧的管理結構體申請一塊堆記憶體
SeqStack_t *Manager = (SeqStack_t *)calloc(1,sizeof(SeqStack_t));
if(NULL == Manager)
{
perror("calloc memory for manager is failed");
exit(-1); //程式異常終止
}
//2.利用calloc為所有元素申請堆記憶體
Manager->Bottom = (DataType_t *)calloc(size,sizeof(DataType_t));
if (NULL == Manager->Bottom)
{
perror("calloc memory for Stack is failed");
free(Manager);
exit(-1); //程式異常終止
}
//3.對管理順序棧的結構體進行初始化(元素容量 + 最後元素下標)
Manager->Size = size; //對順序棧中的容量進行初始化
Manager->Top = -1; //由於順序棧為空,則棧頂元素的下標初值為-1
return Manager;
}
//判斷順序棧是否已滿
bool SeqStack_IsFull(SeqStack_t *Manager)
{
return (Manager->Top + 1 == Manager->Size) ? true : false;
}
//入棧
bool SeqStack_Push(SeqStack_t *Manager, DataType_t Data)
{
//1.判斷順序棧是否已滿
if ( SeqStack_IsFull(Manager) )
{
printf("SeqStack Full is Full!\n");
return false;
}
//2.如果順序棧有空閒空間,則把新元素新增到順序棧的棧頂
Manager->Bottom[++Manager->Top] = Data;
return true;
}
//判斷順序棧是否為空
bool SeqStack_IsEmpty(SeqStack_t *Manager)
{
return (-1 == Manager->Top) ? true : false;
}
//出棧
DataType_t SeqStack_Pop(SeqStack_t *Manager)
{
DataType_t temp = 0; //用於儲存出棧元素的值
//1.判斷順序棧是否為空
if ( SeqStack_IsEmpty(Manager) )
{
printf("SeqStack is Empty!\n");
return;
}
//2.由於刪除了一個元素,則需要讓順序棧的棧頂元素下標-1
temp = Manager->Bottom[Manager->Top--];
return temp;
}
//遍歷順序表的元素
void SeqStack_Print(SeqStack_t *Manager)
{
for (int i = 0; i <= Manager->Top; ++i)
{
printf(" Stack Element[%d] = %d\n",i,Manager->Bottom[i]);
}
}
//十進位制轉換為十六進位制
void SeqStack_Dec2Hex(SeqStack_t *Manager,unsigned int Data)
{
int remainder; //用於儲存求餘之後的餘數
//1.迴圈對非負整數進行求餘 Data % 16
do
{
remainder = Data % 16;
//分析餘數的範圍 0~9 10~15 -->A~F
if (remainder < 10)
{
SeqStack_Push(Manager,remainder+'0');
}
else
{
SeqStack_Push(Manager,remainder+'A'-10);
}
Data /= 16;
}while(Data != 0);
//2.把順序棧中的元素依次出棧
printf("0x");
while( !SeqStack_IsEmpty(Manager) )
{
printf("%c", SeqStack_Pop(Manager) );
}
printf("\n");
}
// ")hel()()loworl(()d"
bool SeqStack_IsStringVaild(SeqStack_t *Manager,const char *Str)
{
char *Pstr = Str; //備份地址,防止地址丟失
//1.迴圈遍歷字串,尋找'('
while( *Pstr )
{
//判斷當前地址下的字元是否為'(',如果是則入棧
if (*Pstr == '(')
{
SeqStack_Push(Manager,'(');
}
if( *Pstr == ')' )
{
//判斷空棧
if(SeqStack_IsEmpty(Manager))
{
return false;
}
SeqStack_Pop(Manager);
}
Pstr++;
}
//2.判斷棧是否為空
if(!SeqStack_IsEmpty(Manager))
{
return false;
}
return true;
}
int main(int argc, char const *argv[])
{
return 0;
}
透過鍵盤輸入一個包括 '(' 和 ')' 的字串string ,判斷字串是否有效。要求設計演算法實現檢查字串是否有效,有效的字串需滿足以下條件:
A. 左括號必須用相同型別的右括號閉合。
B. 左括號必須以正確的順序閉合。
C. 每個右括號都有一個對應的相同型別的左括號。