双向链表的定义
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
下图为双向链表的带头结构图。
下图为双向链表不带头结构图
这里我们在定义双向链表的时候,一个结点里面的next存储的下一个结点的地址,prev存储的是上一个结点的地址,这里我们就会将其链接起来。
头文件
#pragma once #include<stdio.h> #include<string.h> #include<assert.h> #include<stdlib.h> typedef int LTDateType; typedef struct ListNode { LTDateType data; struct ListNode* next; struct ListNode* prev; }LTNode; //初始化 LTNode* ListInit(); //申请新的结点 LTNode* BuyListNode(LTDateType x); //尾插 void ListPushBack(LTNode* phead, LTDateType x); //尾删 void ListPopBack(LTNode* phead); //头插 void ListPushFront(LTNode* phead, LTDateType x); //头删 void ListPopFront(LTNode* phead); //打印 void ListPrint(LTNode* phead); //查找 LTNode* ListFind(LTNode* phead, LTDateType x); //在pos位置之前进行插入 void ListInsert(LTNode* pos, LTDateType x); //删除pos位置的结点 void ListErase(LTNode* pos);
接口的实现
1、新节点的申请以及初始化链表
//申请新的结点 LTNode* BuyListNode(LTDateType x) { LTNode* newnode = (LTNode*)malloc(sizeof(LTNode)); newnode->data = x; newnode->next = NULL; newnode->prev = NULL; return newnode; } //初始化 LTNode* ListInit() { //哨兵位的头节点 LTNode* phead = (LTNode*)malloc(sizeof(LTNode)); phead->next = phead; phead->prev = phead; return phead; }
2、链表的头插尾插
//头插 void ListPushFront(LTNode* phead, LTDateType x) { assert(phead); LTNode* newnode = BuyListNode(x); LTNode* next = phead->next; phead->next = newnode; newnode->prev = phead; newnode->next = next; next->prev = newnode; //调用ListInsert也可以实现头插 //ListInsert(phead->next, x); } //尾插 void ListPushBack(LTNode* phead, LTDateType x) { assert(phead); LTNode* tail = phead->prev; LTNode* newnode = BuyListNode(x); tail->next = newnode; newnode->prev = tail; newnode->next = phead; phead->prev = newnode; //调用ListInsert也可以实现尾插 //ListInsert(phead, x); }
3、链表的头删尾删
//头删 void ListPopFront(LTNode* phead) { assert(phead); assert(phead->next != NULL); LTNode* next = phead->next; LTNode* next2 = next->next; phead->next = next2; next2->prev = phead; free(next); } //尾删 void ListPopBack(LTNode* phead) { assert(phead); assert(phead->next != phead); //找到尾 LTNode* tail = phead->prev; phead->prev = tail->prev; tail->prev->next = phead; free(tail); }
3、链表的查找
//查找 LTNode* ListFind(LTNode* phead, LTDateType x) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { if (cur->data == x) { return cur; } cur = cur->next; } return NULL; }
4、在pos位置之前进行插入以及删除pos位置的结点
//在pos位置之前进行插入 void ListInsert(LTNode* pos, LTDateType x) { assert(pos); LTNode* newnode = BuyListNode(x); LTNode* posprev = pos->prev; posprev->next = newnode; newnode->prev = posprev; newnode->next = pos; pos->prev = newnode; } //删除pos位置的结点 void ListErase(LTNode* pos) { assert(pos); LTNode* posnext = pos->next; LTNode* posprev = pos->prev; posnext->prev = posprev; posprev->next = posnext; }
5、链表的打印
//打印 void ListPrint(LTNode* phead) { assert(phead); LTNode* cur = phead->next; while (cur != phead) { printf("%d ", cur->data); cur = cur->next; } printf("\n"); }
但是相对于双向带头链表来说,有下面的优缺点:
优点
1、在任意位置插入删除效率高。O(1)
2、按需申请空间
缺点
1、不支持随机访问。(下标访问)意味着一些快排,二分查找在这种结构上不适用
热门文章
- 3月5日 | Free Trojan Node节点订阅每天更新18.3M/S免费节点订阅链接
- 4月3日 | Free Trojan Node节点订阅每天更新21.6M/S免费节点订阅链接
- 4月10日 | Free Trojan Node节点订阅每天更新18.9M/S免费节点订阅链接
- 数据库基础知识详解四:存储过程、视图、游标、SQL语句优化以及索引
- 开个小型宠物食品加工厂需要什么手续呢多少钱 开一家宠物食品加工厂
- 3月24日 | Free Trojan Node节点订阅每天更新21M/S免费节点订阅链接
- 狗狗领养协议一般是怎么规定的呢图片(狗狗领养协议书有没有法律效益)
- 动物疫苗类型有哪几种(动物疫苗种类及类型)
- EFCore 6.0入门看这篇就够了
- 修改 Docker 的默认存储路径