💙系列文章💙
【初阶数据结构与算法】第一篇:算法中的时间复杂度和空间复杂度
目录
前言
🌏一、双链表概念
🍤第一个结点属于多开辟的空间,为了方便头插头删,后续结点为真正的链表内容。
内容:
🍤一个指向前面结点的指针
🍤一个存储数据的空间
🍤一个指向后面结点的指针
🌏二、代码实现
🍯1.双链表定义
🍤与链表不同的是,双链表结构更复杂,但其实实现起来比单链表简单,并且还有一个哨兵
//此双链表带哨兵位
typedef int typedefData;
typedef struct Double_linked_list {
typedefData Data; //数据
struct Double_linked_list* next;//下一个指针
struct Double_linked_list* prev;//上一个指针
}Double_linked_list;
🍯2.双链表初始化两种形式
🍤初始化时,我们要将哨兵位地址传过去,因为我们要修改哨兵位的值,将哨兵位值修改为0,两个指针都指向他自己
//初始化不带返回值
void InitializeNotReturn_Double_linked_list(Double_linked_list** pphead) {
assert(pphead);
*pphead = Application_Double_linked_list(0);
(*pphead)->next = *pphead;
(*pphead)->prev = *pphead;
}
🍤这里不用传送哨兵位的值,因为直接在,函数中创建一个哨兵,并且将值设为0,两个指针指向他自己,最后将这个地址返回
//初始化带返回值
Double_linked_list* InitializeReturn_Double_linked_list(void) {
Double_linked_list* phead = Application_Double_linked_list(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
🍯3.双链表申请空间
🍤新的空间设为我们需要的值,将两个指针都置为空,返回新空间的地址
//申请一个空间
Double_linked_list* Application_Double_linked_list(typedefData x) {
Double_linked_list* newnode = (Double_linked_list*)malloc(sizeof(Double_linked_list));
if (newnode == NULL) {
printf("fail malloc:%s\n", strerror(errno));
exit(-1);
}
newnode->next = NULL;
newnode->prev = NULL;
newnode->Data = x;
return newnode;
}
🍯4.双链表尾插
🍤1.创建新结点存储数据
🍤2.原尾结点与新结点进行相互链接
🍤3.新结点与头结点相互链接
//尾插
void PushBack_Double_linked_list(Double_linked_list* phead, typedefData x) {
assert(phead);
Double_linked_list* newnode = Application_Double_linked_list(x);
Double_linked_list* tail = phead->prev;
tail->next = newnode;
newnode->prev = tail;
phead->prev = newnode;
newnode->next = phead;
}
🍯5.双链表尾删
🍤1.找到尾结点和倒数第二个结点
🍤2.释放尾结点
🍤3.头结点和倒数第二个结点链接
void PopBack_Double_linked_list(Double_linked_list* phead) {
assert(phead);
Double_linked_list* tail = phead->prev;
Double_linked_list* tailprev = tail->prev;
phead->prev = tailprev;
tailprev->next = phead;
free(tail);
tail = NULL;
}
🍯6.双链表打印
🍤用一个cur节点指针来走,走到head的位置就停下来
void Print_Double_linked_list(Double_linked_list* phead) {
assert(phead);
Double_linked_list* cur = phead->next;
printf("Head ");
while (cur != phead) {
printf("<-> %d ", cur->Data);
cur = cur->next;
}
printf("<-> Head\n");
}
🍯7.双链表头插
🍤1.创建新结点存储数据
🍤2.新结点与头结点后的第一个结点链接
🍤3.第三步,新结点与头结点链接
void PushFront_Double_linked_list(Double_linked_list* phead, typedefData x) {
assert(phead);
Double_linked_list* newnode = Application_Double_linked_list(x);
Double_linked_list* cur = phead->next;
Double_linked_list* curnext = cur->next;
cur->next = newnode;
newnode->prev = cur;
curnext->prev = newnode;
newnode->next = curnext;
}
🍯8.双链表头删
🍤1.头结点与头删结点后的结点链接
🍤2.释放头删结点
void PopFront_Double_linked_list(Double_linked_list* phead) {
assert(phead);
Double_linked_list* cur = phead->next;
Double_linked_list* curnext = cur->next;
phead->next = curnext;
curnext->prev = phead;
free(cur);
cur = NULL;
}
🍯9.双链表查找
🍤直接遍历
Double_linked_list* Find_Double_linked_list(Double_linked_list* phead, typedefData x) {
assert(phead);
Double_linked_list* cur = phead->next;
while (cur != phead) {
if (cur->Data == x) {
return cur;
}
cur = cur->next;
}
return NULL;
}
🍯10.中间插入及头插和尾插优化
🍍中间插入
🍤任意位置前插入是在给定的
pos
结点之后插入
void Insert_Double_linked_list(Double_linked_list* pos, typedefData x) {
assert(pos);
Double_linked_list* newnode = Application_Double_linked_list(x);
Double_linked_list* next = pos->next;
newnode->next = next;
next->prev = newnode;
pos->next = newnode;
newnode->prev = pos;
}
🍍头插优化
🍤直接传头的下一个节点
void PushFront_Double_linked_list_other(Double_linked_list* pos, typedefData x) {
assert(pos);
//直接下一个节点插入就好了
Insert_Double_linked_list(pos->next, x);
}
🍍尾插优化
🍤直接传头的上一个节点
void PushBack_Double_linked_list_other(Double_linked_list* pos, typedefData x) {
assert(pos);
//直接在哨兵位之前插入即可,循环链表
Insert_Double_linked_list(pos->prev, x);
}
🍯11.中间删除及头删和尾删优化
🍍中间删除
🍤判断是不是只有头了,只有头则不要删除
void Erase_Double_linked_list(Double_linked_list* phead, Double_linked_list* pos) {
assert(phead && pos);
//防止头节点为空
assert(phead->next != phead);
Double_linked_list* pospre = pos->prev;
Double_linked_list* posnext = pos->next;
pospre->next = posnext;
posnext->prev = pospre;
free(pos);
pos = NULL;
}
🍍头删优化
🍤同样判断是不是头,如果是头则不要删除
void PopFront_Double_linked_list_other(Double_linked_list* phead) {
assert(phead);
//防止头节点为空
assert(phead->next != phead);
Erase_Double_linked_list(phead, phead->next);
}
🍍尾删优化
🍤同样判断是不是头,如果是头则不要删除
void PopBack_Double_linked_list_other(Double_linked_list* phead) {
assert(phead);
//防止头节点为空
assert(phead->next != phead);
Erase_Double_linked_list(phead, phead->prev);
}
🍯12.双链表销毁
🍤轮流释放节点,最后释放头
void Destroy_Double_linked_list(Double_linked_list* phead) {
assert(phead);
Double_linked_list* cur = phead->next;
Double_linked_list* curnext = cur->next;
while (cur != phead) {
free(cur);
cur = curnext;
curnext = curnext->next;
}
free(phead);
phead = NULL;
}
🌏总结
链表和顺序表的对比
不同点 | 顺序表 | 链表 |
---|---|---|
存储空间上 | 物理上连续 | 逻辑上连续 |
随机访问 | 支持 | 不支持 |
任意位置插入删除 | 要移动元素,O(N) | 只要改变指针指向 |
插入数据 | 要考虑扩容,会带来一定的空间消耗 | 没有容量这个概念,可以按需申请和释放 |
缓存利用率 | 高 | 低 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |