C語言實(shí)例之雙向鏈表增刪改查
一、雙向鏈表介紹
雙向鏈表(Doubly Linked List)是一種常見的數(shù)據(jù)結(jié)構(gòu),在單鏈表的基礎(chǔ)上增加了向前遍歷的功能。與單向鏈表不同,雙向鏈表的每個(gè)節(jié)點(diǎn)除了包含指向下一個(gè)節(jié)點(diǎn)的指針外,還包含指向前一個(gè)節(jié)點(diǎn)的指針。
作用和原理:
(1)插入和刪除操作:由于雙向鏈表中每個(gè)節(jié)點(diǎn)都有指向前一個(gè)節(jié)點(diǎn)的指針,所以在雙向鏈表中進(jìn)行插入或刪除操作時(shí),相對(duì)于單向鏈表更加高效??梢酝ㄟ^修改前后節(jié)點(diǎn)的指針來完成插入和刪除,而無需遍歷鏈表。
(2)雙向遍歷:雙向鏈表支持從頭部到尾部以及從尾部到頭部的雙向遍歷。這在某些場(chǎng)景下非常有用,例如需要反向查找、刪除最后一個(gè)節(jié)點(diǎn)等。
(3)增加了靈活性:由于每個(gè)節(jié)點(diǎn)都具有指向前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)的指針,雙向鏈表在某些特定場(chǎng)景下更靈活。例如,需要在鏈表中間插入或刪除節(jié)點(diǎn),或者需要修改前一個(gè)節(jié)點(diǎn)的信息。
雙向鏈表的原理很簡單。每個(gè)節(jié)點(diǎn)由數(shù)據(jù)域和兩個(gè)指針組成,其中一個(gè)指針指向前一個(gè)節(jié)點(diǎn),一個(gè)指針指向后一個(gè)節(jié)點(diǎn)。頭節(jié)點(diǎn)指向鏈表的第一個(gè)節(jié)點(diǎn),尾節(jié)點(diǎn)指向鏈表的最后一個(gè)節(jié)點(diǎn)。通過調(diào)整節(jié)點(diǎn)之間的指針,可以在雙向鏈表中執(zhí)行插入、刪除和遍歷等操作。
使用場(chǎng)景:
(1)編輯器中的撤銷和重做功能:雙向鏈表可以用于實(shí)現(xiàn)撤銷和重做功能,每次編輯操作都將其結(jié)果存儲(chǔ)為一個(gè)節(jié)點(diǎn),并使用指針鏈接起來。通過雙向鏈表,可以方便地在撤銷和重做之間進(jìn)行切換。
(2)瀏覽器的導(dǎo)航歷史:瀏覽器的導(dǎo)航歷史可以使用雙向鏈表來保存已訪問的頁面,每個(gè)頁面作為一個(gè)節(jié)點(diǎn),并使用指針鏈接起來,以便進(jìn)行前進(jìn)和后退操作。
(3)實(shí)現(xiàn)LRU緩存替換算法:LRU緩存中,最近最少使用的數(shù)據(jù)被淘汰,可以使用雙向鏈表來維護(hù)緩存中的數(shù)據(jù),最近訪問的數(shù)據(jù)位于鏈表的頭部,最久未訪問的數(shù)據(jù)位于鏈表的尾部。
(4)實(shí)現(xiàn)雙向隊(duì)列:雙向鏈表可以用于實(shí)現(xiàn)雙向隊(duì)列(Dequeue),支持在隊(duì)列的兩端進(jìn)行插入和刪除操作。
雙向鏈表提供了更多的靈活性和功能,特別是當(dāng)需要在雙向遍歷、頻繁的插入和刪除操作等場(chǎng)景下使用。在許多常見的數(shù)據(jù)結(jié)構(gòu)和算法中都有廣泛的應(yīng)用。
二、代碼實(shí)現(xiàn)
以下是使用C語言實(shí)現(xiàn)的完整雙向鏈表代碼,包含了鏈表的創(chuàng)建、增加、刪除、修改、排序和插入等功能。代碼中封裝了一套完整的子函數(shù),以方便使用。
#include <stdio.h> #include <stdlib.h> ? // 雙向鏈表節(jié)點(diǎn)結(jié)構(gòu) typedef struct Node { ? ?int data; ? ? ? ? ? ? ?// 數(shù)據(jù)域 ? ?struct Node* prev; ? ? // 指向前一個(gè)節(jié)點(diǎn)的指針 ? ?struct Node* next; ? ? // 指向后一個(gè)節(jié)點(diǎn)的指針 } Node; ? // 創(chuàng)建新節(jié)點(diǎn) Node* createNode(int data) { ? ?Node* newNode = (Node*)malloc(sizeof(Node)); ? ?if (newNode == NULL) { ? ? ? ?printf("Failed to allocate memory for new node\n"); ? ? ? ?return NULL; ? } ? ?newNode->data = data; ? ?newNode->prev = NULL; ? ?newNode->next = NULL; ? ?return newNode; } ? // 在鏈表末尾添加節(jié)點(diǎn) void append(Node** head, int data) { ? ?Node* newNode = createNode(data); ? ?if (newNode == NULL) { ? ? ? ?return; ? } ? ?if (*head == NULL) { ? ? ? ?*head = newNode; ? } else { ? ? ? ?Node* current = *head; ? ? ? ?while (current->next != NULL) { ? ?// 找到鏈表末尾 ? ? ? ? ? ?current = current->next; ? ? ? } ? ? ? ?current->next = newNode; ? ? ? ?newNode->prev = current; ? } } ? // 在鏈表頭部添加節(jié)點(diǎn) void prepend(Node** head, int data) { ? ?Node* newNode = createNode(data); ? ?if (newNode == NULL) { ? ? ? ?return; ? } ? ?if (*head == NULL) { ? ? ? ?*head = newNode; ? } else { ? ? ? ?newNode->next = *head; ? ? ? (*head)->prev = newNode; ? ? ? ?*head = newNode; ? } } ? // 在指定位置插入節(jié)點(diǎn) void insert(Node** head, int data, int position) { ? ?if (position < 0) { ? ? ? ?printf("Invalid position\n"); ? ? ? ?return; ? } ? ?if (position == 0) { ? ? ? ?prepend(head, data); ? ? ? ?return; ? } ? ?Node* newNode = createNode(data); ? ?if (newNode == NULL) { ? ? ? ?return; ? } ? ?Node* current = *head; ? ?int count = 0; ? ?while (count < (position - 1) && current != NULL) { ? ?// 找到插入位置前一個(gè)節(jié)點(diǎn) ? ? ? ?current = current->next; ? ? ? ?count++; ? } ? ?if (current == NULL) { ? ? ? ?printf("Invalid position\n"); ? ? ? ?free(newNode); ? ? ? ?return; ? } ? ?newNode->next = current->next; ? ?newNode->prev = current; ? ?if (current->next != NULL) { ? ? ? ?current->next->prev = newNode; ? } ? ?current->next = newNode; } ? // 刪除指定位置的節(jié)點(diǎn) void removeAt(Node** head, int position) { ? ?if (*head == NULL) { ? ? ? ?printf("List is empty\n"); ? ? ? ?return; ? } ? ?if (position < 0) { ? ? ? ?printf("Invalid position\n"); ? ? ? ?return; ? } ? ?Node* current = *head; ? ?int count = 0; ? ?if (position == 0) { ? ? ? ?*head = current->next; ? ? ? ?if (*head != NULL) { ? ? ? ? ? (*head)->prev = NULL; ? ? ? } ? ? ? ?free(current); ? ? ? ?return; ? } ? ?while (count < position && current != NULL) { ? ?// 找到要?jiǎng)h除的節(jié)點(diǎn) ? ? ? ?current = current->next; ? ? ? ?count++; ? } ? ?if (current == NULL) { ? ? ? ?printf("Invalid position\n"); ? ? ? ?return; ? } ? ?current->prev->next = current->next; ? ?if (current->next != NULL) { ? ? ? ?current->next->prev = current->prev; ? } ? ?free(current); } ? // 修改指定位置的節(jié)點(diǎn)值 void modify(Node* head, int position, int data) { ? ?Node* current = head; ? ?int count = 0; ? ?while (count < position && current != NULL) { ? ?// 找到要修改的節(jié)點(diǎn) ? ? ? ?current = current->next; ? ? ? ?count++; ? } ? ?if (current == NULL) { ? ? ? ?printf("Invalid position\n"); ? ? ? ?return; ? } ? ?current->data = data; } ? // 對(duì)鏈表進(jìn)行排序 void sort(Node** head) { ? ?if (*head == NULL) { ? ? ? ?printf("List is empty\n"); ? ? ? ?return; ? } ? ?Node* current = *head; ? ?Node* temp = NULL; ? ?int swapped; ? ?do { ? ? ? ?swapped = 0; ? ? ? ?current = *head; ? ? ? ?while (current->next != NULL) { ? ? ? ? ? ?if (current->data > current->next->data) { ? ? ? ? ? ? ? ?int tmp = current->data; ? ? ? ? ? ? ? ?current->data = current->next->data; ? ? ? ? ? ? ? ?current->next->data = tmp; ? ? ? ? ? ? ? ?swapped = 1; ? ? ? ? ? } ? ? ? ? ? ?current = current->next; ? ? ? } ? ? ? ?temp = current; ? } while (swapped); } ? // 打印鏈表 void printList(Node* head) { ? ?if (head == NULL) { ? ? ? ?printf("List is empty\n"); ? ? ? ?return; ? } ? ?Node* current = head; ? ?while (current != NULL) { ? ? ? ?printf("%d ", current->data); ? ? ? ?current = current->next; ? } ? ?printf("\n"); } ? // 釋放鏈表內(nèi)存 void freeList(Node** head) { ? ?if (*head == NULL) { ? ? ? ?return; ? } ? ?Node* current = *head; ? ?Node* next = NULL; ? ?while (current != NULL) { ? ? ? ?next = current->next; ? ? ? ?free(current); ? ? ? ?current = next; ? } ? ?*head = NULL; } ? int main() { ? ?Node* head = NULL; ? ?append(&head, 5); ? ?append(&head, 3); ? ?prepend(&head, 9); ? ?insert(&head, 7, 1); ? ?removeAt(&head, 2); ? ?modify(head, 0, 2); ? ?sort(&head); ? ?printList(head); ? ?freeList(&head); ? ?return 0; }
代碼里實(shí)現(xiàn)了創(chuàng)建雙向鏈表、在鏈表末尾添加節(jié)點(diǎn)、在鏈表頭部添加節(jié)點(diǎn)、在指定位置插入節(jié)點(diǎn)、刪除指定位置的節(jié)點(diǎn)、修改指定位置的節(jié)點(diǎn)值、對(duì)鏈表進(jìn)行排序、打印鏈表及釋放鏈表內(nèi)存等功能。
三、思路講解
代碼里定義了一個(gè)雙向鏈表節(jié)點(diǎn)結(jié)構(gòu),包含數(shù)據(jù)域(data)、指向前一個(gè)節(jié)點(diǎn)的指針(prev)和指向后一個(gè)節(jié)點(diǎn)的指針(next)。
typedef struct Node { ? ?int data; ? ?struct Node* prev; ? ?struct Node* next; } Node;
(1)createNode函數(shù)用于創(chuàng)建新節(jié)點(diǎn)。分配內(nèi)存以存儲(chǔ)節(jié)點(diǎn),并檢查內(nèi)存分配是否成功。設(shè)置節(jié)點(diǎn)的數(shù)據(jù)域?yàn)閭魅氲臄?shù)據(jù),并將前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)的指針都設(shè)置為NULL。最后,返回新創(chuàng)建的節(jié)點(diǎn)的指針。
(2)append函數(shù)用于在鏈表末尾添加節(jié)點(diǎn)。首先調(diào)用createNode函數(shù)創(chuàng)建一個(gè)新節(jié)點(diǎn)。如果頭節(jié)點(diǎn)為空,則將新節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)。否則,遍歷鏈表直到找到最后一個(gè)節(jié)點(diǎn),將新節(jié)點(diǎn)連接到最后一個(gè)節(jié)點(diǎn)的下一個(gè)位置,并設(shè)置新節(jié)點(diǎn)的prev指針指向最后一個(gè)節(jié)點(diǎn)。
(3)prepend函數(shù)用于在鏈表頭部添加節(jié)點(diǎn)。首先,調(diào)用createNode函數(shù)創(chuàng)建一個(gè)新節(jié)點(diǎn)。如果頭節(jié)點(diǎn)為空,則將新節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)。否則,將新節(jié)點(diǎn)的next指針指向當(dāng)前的頭節(jié)點(diǎn),將當(dāng)前頭節(jié)點(diǎn)的prev指針指向新節(jié)點(diǎn),然后將新節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)。
(4)insert函數(shù)用于在指定位置插入節(jié)點(diǎn)。首先,檢查插入位置是否合法。如果插入位置為0,則調(diào)用prepend函數(shù)在鏈表頭部插入節(jié)點(diǎn)。否則,調(diào)用createNode函數(shù)創(chuàng)建一個(gè)新節(jié)點(diǎn),然后遍歷鏈表直到找到插入位置前一個(gè)節(jié)點(diǎn),將新節(jié)點(diǎn)插入到這兩個(gè)節(jié)點(diǎn)之間,即將新節(jié)點(diǎn)的next指針指向前一個(gè)節(jié)點(diǎn)的next指針?biāo)赶虻墓?jié)點(diǎn),將新節(jié)點(diǎn)的prev指針指向前一個(gè)節(jié)點(diǎn),然后更新新節(jié)點(diǎn)兩側(cè)節(jié)點(diǎn)的指針。
(5)removeAt函數(shù)用于刪除指定位置的節(jié)點(diǎn)。首先,檢查鏈表是否為空。如果鏈表為空,則輸出相應(yīng)的提示信息。如果要?jiǎng)h除的位置為0,即刪除頭節(jié)點(diǎn),需要特殊處理,即將頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置為新的頭節(jié)點(diǎn),并將新的頭節(jié)點(diǎn)的prev指針設(shè)置為NULL。否則,遍歷鏈表直到找到要?jiǎng)h除的節(jié)點(diǎn),將要?jiǎng)h除節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)的next指針指向要?jiǎng)h除節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),然后更新兩側(cè)節(jié)點(diǎn)的指針。
(6)modify函數(shù)用于修改指定位置的節(jié)點(diǎn)值。首先,遍歷鏈表直到找到要修改的節(jié)點(diǎn),然后將該節(jié)點(diǎn)的數(shù)據(jù)域設(shè)置為傳入的新數(shù)據(jù)。
(7)sort函數(shù)用于對(duì)鏈表進(jìn)行排序。首先,檢查鏈表是否為空。如果鏈表為空,則輸出相應(yīng)的提示信息。使用冒泡排序算法,重復(fù)遍歷鏈表并比較相鄰節(jié)點(diǎn)的值,如果前一個(gè)節(jié)點(diǎn)的值大于后一個(gè)節(jié)點(diǎn)的值,則交換它們的值。重復(fù)此過程,直到鏈表沒有發(fā)生交換為止。
(8)printList函數(shù)用于打印鏈表中的所有節(jié)點(diǎn)的值。首先,檢查鏈表是否為空。如果鏈表為空,則輸出相應(yīng)的提示信息。遍歷鏈表的每個(gè)節(jié)點(diǎn),并輸出節(jié)點(diǎn)中存儲(chǔ)的數(shù)據(jù)。
(9)freeList函數(shù)用于釋放鏈表的內(nèi)存。首先,檢查鏈表是否為空。如果鏈表為空,則直接返回。遍歷鏈表的每個(gè)節(jié)點(diǎn),使用free函數(shù)釋放節(jié)點(diǎn)的內(nèi)存,并將節(jié)點(diǎn)指針設(shè)為NULL,最后將頭節(jié)點(diǎn)指針設(shè)為NULL。
以上就是C語言實(shí)例之雙向鏈表增刪改查的詳細(xì)內(nèi)容,更多關(guān)于C語言雙向鏈表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
VS2019中CMake項(xiàng)目如何指定c++語言標(biāo)準(zhǔn)
這篇文章主要介紹了VS2019中CMake項(xiàng)目如何指定c++語言標(biāo)準(zhǔn),需要的朋友可以參考下2020-02-02詳解C++中的const關(guān)鍵字及與C語言中const的區(qū)別
這篇文章主要介紹了C++中的const關(guān)鍵字及與C語言中const的區(qū)別,const將所修飾的變量對(duì)象轉(zhuǎn)化為常量,需要的朋友可以參考下2016-04-04C語言實(shí)現(xiàn)訪問及查詢MySQL數(shù)據(jù)庫的方法
這篇文章主要介紹了C語言實(shí)現(xiàn)訪問及查詢MySQL數(shù)據(jù)庫的方法,涉及C語言基于libmysql.lib實(shí)現(xiàn)訪問MySQL數(shù)據(jù)庫的相關(guān)操作技巧,需要的朋友可以參考下2018-01-01c語言中abs()和fabs()的區(qū)別點(diǎn)整理
在本篇文章里小編給大家分享的是關(guān)于c語言abs()和fabs()的區(qū)別,有需要的朋友們可以參考學(xué)習(xí)下。2020-02-02C語言實(shí)現(xiàn)多線程定時(shí)器實(shí)例講解
在本篇文章里小編給各位分享的是一篇關(guān)于C語言實(shí)現(xiàn)多線程定時(shí)器實(shí)例講解內(nèi)容,有需要的朋友們可以參考學(xué)習(xí)下。2021-01-01