双链表(DoubleLinkList)数据结构的基本操作实现详解,重磅来袭!结构复杂但高效~
目錄
- 一、前言
- 二、整體框架設計
- 三、函數實現
- 1. createDBLSTNode
- 2. doubleListInit
- 3. doubleListPrint
- 4. doubleListDestory
- 5. doubleListEmpty
- 6. doubleListSize
- 7. doubleListPushBack
- 8. doubleListPushFront
- 9. doubleListInsert
- 10. doubleListErase
- 11. doubleListPopBack
- 12. doubleListPopFront
- 13. doubleListFind
- 四、完整代碼
一、前言
今天講一下最實用的雙鏈表,這個比單鏈表更厲害了,下圖中,頭尾節點的增刪都為O(1),雖結構復雜一點,但是效率更高
時間復雜度好壞可以參考前面所寫的數據結構進行比較
🔗 🔗 🔗 順序表 🔗 🔗 🔗
👉 👉 👉 單鏈表 👈 👈 👈
二、整體框架設計
還是和之前一樣實現doubleLinkList分為三個文件,這樣可讀性更高,便于調試
test.c 專門用于函數調用、調試
doubleList.h 只用于函數聲明
doubleList.c 用于函數實現
三、函數實現
在實現函數之前,我們想一想雙鏈表需要怎樣設計呢?如果按照單鏈表的思想去設計,那么尾插要達到O(1)是根本做不到的,除非頭節點可以直接去訪問尾節點,但單鏈表中當前節點要訪問前一個節點的話,不做記錄是根本不可能訪問到的呀
那怎么辦呢?
有人肯定想到了在結構體中添加一個新的成員,創建一個pre指針用于存儲當前節點的前一個節點的地址不就可以了嘛
那么問題又來了,頭節點怎么就能直接訪問到尾節點了呢?
帶著這個疑惑我們參考一下上圖,發現head節點的前一個節點是指向最后一個節點的,那么也就實現了一步到位
我們先來看下頭文件的構
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h>typedef int DataType;typedef struct doubleLinkList{DataType data;struct doubleLinkList* pre;struct doubleLinkList* next; }DBLST;DataType的定義是為了方便修改數據類型,這樣定義一下,下次變char類型就實現了全局修改,awesome~
結構體中有三個成員分別用于數據存儲,當前節點的前一個節點地址記錄和當前節點的下一個節點的地址記錄。
1. createDBLSTNode
首先最基本的函數就是專用于新節點創建,因為這個函數后續需要頻繁使用
DBLST* createDBLSTNode(DataType x){DBLST* newNode = (DBLST*)malloc(sizeof(DBLST));if(newNode == NULL){printf("Failed to create a new node");exit(-1);}newNode->pre = NULL;newNode->next = NULL;newNode->data = x;return newNode; }2. doubleListInit
這個初始化和前面的順序表,單鏈表不太一樣
我們先創建一個節點,將新節點的地址傳給原來的head,然后將此節點的pre和next都指向自己,就完成了雙鏈表的創建
?? 注意:這里參數傳的是二級指針
因為需要改變外面指針的地址,debug來看一下
函數跑完出來后,可以看到原先傳進去的地址現在變成了0x100688100,同時可以看到pre和next的地址都指向自己
3. doubleListPrint
打印節點數據需要注意的是,這是一個循環鏈表,如果沒有判斷停止的條件,那么就會導致死循環
所以我們需要判斷一下,當cur轉一圈回來等于頭節點時,停止打印
void doubleListPrint(DBLST* head){assert(head);DBLST* cur = head->next;while(cur != head){printf("%d ", cur->data);cur = cur->next;}printf("\n"); }4. doubleListDestory
這里要注意如果我們直接free掉當前節點,那么下一個節點就找不到了,因此我們要先將下一個節點next進行存儲,然后再釋放當前節點,轉一圈回來后就只剩一個head了,將其釋放置空。
//釋放鏈表中的所有節點 void doubleListDestory(DBLST* head){ DBLST* cur = head->next;while(cur!=head){DBLST* next = head->next;free(cur);cur->next = NULL;cur = next;}free(head);head = NULL; }5. doubleListEmpty
判斷鏈表是否為空,當cur的下一個還是等于自己返回true
bool doubleListEmpty(DBLST* head){assert(head);return head == head->next; }6. doubleListSize
//計算節點個數 size_t doubleListSize(DBLST* head){assert(head);size_t count = 0;DBLST* tail = head;while(tail->next != head){count++;tail = tail->next;}return count; }7. doubleListPushBack
前面講到實現尾插,時間復雜度為O(1),那么這里我們就先存儲tail,然后創建一個新的節點,并與tail和頭節點建立鏈接即可
void doubleListPushBack(DBLST* head, DataType x){assert(head);//首先,找到尾節點DBLST* tail = head->pre;DBLST* newNode = createDBLSTNode(x);tail->next = newNode;newNode->pre = tail;newNode->next = head;head->pre = newNode; }8. doubleListPushFront
和pushback一樣,先將哨兵的下一個節點記錄下來,然后創建新節點將其鏈接
void doubleListPushFront(DBLST* head, DataType x){assert(head);//先記錄next node信息DBLST* next = head->next;//創建新節點DBLST* newNode = createDBLSTNode(x);head->next = newNode;newNode->pre = head;newNode->next = next;next->pre = newNode; }9. doubleListInsert
其實我們發現,如果一個函數可以專門用來創建新節點并和前后建立鏈接那么pushBack和pushFront的操作就可以免去很多步驟
//在pos前去s插入 void doubleListInsert(DBLST* pos, DataType x){assert(pos);DBLST* newNode = createDBLSTNode(x);DBLST* posPre = pos->pre;posPre->next = newNode;newNode->pre = posPre;newNode->next = pos;pos->pre = newNode; }我們的pushBack和pushFront就可以這么寫
void doubleListPushFront(DBLST* head, DataType x){assert(head);doubleListInsert(head->next, x); } void doubleListPushBack(DBLST* head, DataType x){assert(head);doubleListInsert(head, x); }10. doubleListErase
刪除front和back的思想也可以創建一個函數來實現
void doubleListErase(DBLST* pos){assert(pos);DBLST* pre = pos->pre;DBLST* next = pos->next;free(pos);pre->next = next;next->pre = pre; }11. doubleListPopBack
這里我們直接調用erase函數就可以了
?? 這里要判斷一下當前節點是否為空
12. doubleListPopFront
void doubleListPopFront(DBLST* head){assert(head);assert(!doubleListEmpty(head));doubleListErase(head->pre); }13. doubleListFind
找特定的值,如果找不到返回NULL
DBLST* doubleListFind(DBLST* head, DataType x){DBLST* cur = head->next;while(cur != head){if(cur->data == x){return cur;}cur = cur->next;}return NULL; }四、完整代碼
下面是博主跑的測試用例
// // test.c // DoubleList_SecondTime // // Created by Henry on 2021/8/19. // Copyright ? 2021 Henry. All rights reserved. //#include "doubleList.h"void TestFunc1(){DBLST* list;doubleListInit(&list);doubleListPushBack(list, 2);doubleListPushBack(list, 4);doubleListPushBack(list, 6);doubleListPushBack(list, 8);doubleListPushFront(list, 200);doubleListPushFront(list, 100);doubleListPrint(list);doubleListPopBack(list);doubleListPrint(list);doubleListPopBack(list);doubleListPrint(list);doubleListPopFront(list);doubleListPrint(list);//在找到這個值,在他前面插入DBLST* pos = doubleListFind(list, 6);if(pos){doubleListInsert(pos, 60);}else{printf("Cannot find this element");}doubleListPrint(list);doubleListDestory(list); }int main(int argc, const char * argv[]) {TestFunc1();return 0; }Gitee鏈接🔗 🔗 🔗
👉 👉 👉 DoubleLinkList Folder👈 👈 👈
創作不易,如果文章對你幫助的話,點贊三連哦:)
總結
以上是生活随笔為你收集整理的双链表(DoubleLinkList)数据结构的基本操作实现详解,重磅来袭!结构复杂但高效~的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 各种深度聚类方法摘要
- 下一篇: 大数据中位数怎么运算_java 计算中位