⭐️ 往期相关文章
✨链接1:数据结构和算法的概念以及时间复杂度空间复杂度详解
✨链接2:【数据结构】手撕顺序表(动态版)+代码详解
✨链接3:【数据结构】手撕单链表+代码详解
⭐️ 双向带头循环链表
图解:
双向: 链表内定义两个指针,一个 prev
一个 next
,prev
指向上一个结点的地址,next
指向下一个结点的地址。
带头: 指的是链表带哨兵卫的头结点,哨兵卫的头结点不存储有效的数据,有了哨兵卫的头结点在特定的情况下可以大大的降低代码的复杂度(如单链表中的尾插,当链表为空的时候,尾插不需要判断 phead == NULL
)。
循环:phead
的 prev
指向最后一个结点的地址,最后一个结点的 next
指向 phead
。
双向带头循环链表是最优的一种链表结构,无论头插尾插头删尾删任意位置的删除插入,时间复杂度都是 O ( 1 ) O(1) O(1),它不像单链表中有很多的缺陷,而双向带头循环链表更适合存储数据。
🌠 双向带头循环链表与顺序表之间的区别
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 物理上地址是连续的 | 逻辑上连续,但是物理不一定连续 |
🌟随机访问 | 支持 O ( 1 ) O(1) O(1) | 不支持 O ( N ) O(N) O(N) |
任意位置的插入删除数据 | 可能需要移动元素 O ( N ) O(N) O(N) | 只需要改变指针指向 O ( 1 ) O(1) O(1) |
增加元素 | 动态顺序表,空间不够需要扩容 | 按需申请空间释放空间 |
应用场景 | 需要随机访问 | 任意位置插入删除频繁 |
缓存利用率 | 高 | 低 |
缓存利用率解释: 在计算机存储中,有一个金字塔模式。从下到上速度越快但是容量越小成本越高。
- 寄存器
- 高速缓存
- 内存
- 磁盘
- 网盘
由于寄存器的速度过快,内存跟不上寄存器的速度,所有在中间引入了高速缓存,当寄存器操作时会先去高速缓存中读取数据,如果高速缓存中没有数据则去内存中读取,但是这里有一个局部命中原则,每次读取到不是一个数据而是会把这个数据后面的地址连续的数据都读取到高速缓存中,所以由于顺序表物理地址是连续的,所以后面的元素容易被命中,则下次操作后面的元素,寄存器可以直接到高速缓存中读取到。
🌠 手撕链表
接口函数
#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int DoubleListType;typedef struct DoubleList {DoubleListType data; // 链表的数据struct DoubleList* prev; // 链表的前驱指针struct DoubleList* next; // 链表的后继指针
}DoubleList;// 创建结点
DoubleList* CreateNode(DoubleListType node);
// 初始化
DoubleList* DoubleListInit();
// 销毁
void DoubleListDestroy(DoubleList* phead);
// 打印
void DoubleListPrint(DoubleList* phead);
// 尾插
void DoubleListPushBack(DoubleList* phead , DoubleListType node);
// 头插
void DoubleListPushFront(DoubleList* phead, DoubleListType node);
// 尾删
void DoubleListPopBack(DoubleList* phead);
// 头删
void DoubleListPopFront(DoubleList* phead);
// 查找
DoubleList* DoubleListFind(DoubleList* phead , DoubleListType node);
// 在 pos 之前插入
void DoubleListInsert(DoubleList* pos , DoubleListType node);
// 删除 pos 位置
void DoubleListErase(DoubleList* pos);
// 大小
int DoubleListSize(DoubleList* phead);
CreateNode
实现:
DoubleList* CreateNode(DoubleListType node) {DoubleList* newNode = (DoubleList*)malloc(sizeof(DoubleList));assert(newNode);newNode->data = node;newNode->prev = NULL;newNode->next = NULL;return newNode;
}
DoubleListInit
实现:
// 创建带哨兵卫的头结点
DoubleList* DoubleListInit() {DoubleList* pList = CreateNode(-1);pList->prev = pList;pList->next = pList;return pList;
}
DoubleListDestroy
实现:
void DoubleListDestroy(DoubleList* phead) {assert(phead);DoubleList* cur = phead->next;while (cur != phead) {DoubleList* curNext = cur->next;free(cur);cur = curNext;}// cur == pheadfree(cur);
}
DoubleListPrint
实现:
void DoubleListPrint(DoubleList* phead) {assert(phead);DoubleList* cur = phead->next;while (cur != phead) {printf("%d " , cur->data);cur = cur->next;}printf("\n");
}
DoubleListPushBack
实现:
void DoubleListPushBack(DoubleList* phead, DoubleListType node) {assert(phead);DoubleList* tail = phead->prev;DoubleList* newNode = CreateNode(node);tail->next = newNode;newNode->prev = tail;newNode->next = phead;phead->prev = newNode;
}
DoubleListPushFront
实现:
void DoubleListPushFront(DoubleList* phead, DoubleListType node) {assert(phead);DoubleList* headNext = phead->next;DoubleList* newNode = CreateNode(node);phead->next = newNode;newNode->next = headNext;newNode->prev = phead;headNext->prev = newNode;
}
DoubleListPopBack
实现:
void DoubleListPopBack(DoubleList* phead) {assert(phead);// 空链表assert(phead->next != phead);DoubleList* tail = phead->prev;DoubleList* tailPrev = tail->prev;tailPrev->next = phead;phead->prev = tailPrev;free(tail);
}
DoubleListPopFront
实现:
void DoubleListPopFront(DoubleList* phead) {assert(phead);// 空链表assert(phead->next != phead);DoubleList* headNext = phead->next;DoubleList* headNextNext = headNext->next;phead->next = headNextNext;headNextNext->prev = phead;free(headNext);
}
DoubleListFind
实现:
DoubleList* DoubleListFind(DoubleList* phead, DoubleListType node) {assert(phead);DoubleList* cur = phead->next;while (cur != phead) {if (cur->data == node) {return cur;}cur = cur->next;}return NULL;
}
DoubleListInsert
实现:
void DoubleListInsert(DoubleList* pos, DoubleListType node) {assert(pos);DoubleList* posPrev = pos->prev;DoubleList* newNode = CreateNode(node);posPrev->next = newNode;newNode->prev = posPrev;newNode->next = pos;pos->prev = newNode;
}
DoubleListErase
实现:
void DoubleListErase(DoubleList* pos) {assert(pos);DoubleList* posPrev = pos->prev;DoubleList* posNext = pos->next;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}
DoubleListSize
实现:
// 不能使用哨兵卫的头结点存储数据的长度,因为当数据类型不是 int 时,会有问题
int DoubleListSize(DoubleList* phead) {assert(phead);int size = 0;DoubleList* cur = phead->next;while (cur != phead) {size++;cur = cur->next;}return size;
}
🌠 面试官问:你可以10分钟写一个链表吗?
🌠 答:其实是可以的!
当实现 DoubleListInsert
和 DoubleListErase
函数,其他接口可以复用这两个函数。
void DoubleListPushBack(DoubleList* phead, DoubleListType node) {assert(phead);// 复用DoubleListInsert(phead , node);
}
void DoubleListPushFront(DoubleList* phead, DoubleListType node) {assert(phead);// 复用DoubleListInsert(phead->next , node);
}
void DoubleListPopBack(DoubleList* phead) {assert(phead);// 空链表assert(phead->next != phead);// 复用DoubleListErase(phead->prev);
}
void DoubleListPopFront(DoubleList* phead) {assert(phead);// 空链表assert(phead->next != phead);// 复用DoubleListErase(phead->next);
}