满怀热忱,前往梦的彼岸
前言
之前我们对单链表进行了非常细致的剖析,现在我们所面临的则是与之相对应的双链表,我会先告诉诸位它的基本知识,再接着把它的增删查改讲一下,ok,正文开始。
一.链表的种类
我们上一篇也说了。链表一共有八种,具体可以看下文。
【单链表实现通讯录(增删查改) - CSDN App】http://t.csdnimg.cn/EABuy
我们之前所说的单链表指的是不带头不循环单向链表,而双链表则是带头循环双向链表,只要会了这两个,那八种链表就都会了。
二.为什么引入双链表?
单链表的结点中只有一个指向下一个节点的指针,使得单链表要访问某个结点的前驱结点时,只能从头开始遍历,时间复杂度为O(n)。为了克服上述缺点,引入了双链表。
双链表的结点中有两个指针pre和next,分别指向前驱结点和后继结点。
typedef int LTDataType;
typedef struct LTNode
{
LTDataType val;
struct LTNode* next;
struct LTNode* pre;
}LTNode;
三.双向链表的结构
四.双链表各个接口的实现
双链表初始化
void LTInit(LTNode** pphead)
{
assert(pphead);
*pphead = (LTNode*)malloc(sizeof(LTNode));
(*pphead)->next = *pphead;
(*pphead)->pre = *pphead;
(*pphead)->val = -1;
}
双链表的销毁
void LTDestroy(LTNode* phead)
{
assert(phead);
LTNode* new = phead->next;
while (new != phead)
{
LTNode* nnew = new->next;
free(new);
new = nnew;
}
free(phead);
}
双链表的内容打印
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* new = phead->next;
while (new != phead)
{
printf("%d ", new->val);
new = new->next;
}
puts("");
}
判断双链表是否为空
bool LTEmpty(LTNode* phead)
{
assert(phead);
if (phead == phead->next)
return true;
return false;
}
双链表的尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* new = creat(x);
phead->pre->next =new;
new->pre = phead->pre;
new->next = phead;
phead->pre = new;
}
双链表的尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
if (phead->next == phead)
return;
LTNode* new = phead->pre;
new->pre->next = phead;
phead->pre = new->pre;
free(new);
}
双链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
LTNode* new = creat(x);
new->next = phead->next;
phead->next->pre = new;
phead->next = new;
new->pre = phead;
}
双链表的头删
void LTPopFront(LTNode* phead)
{
assert(phead);
if(phead->next != phead)
printf("链表已经空了\n");
else
{
LTNode* new = phead->next;
phead->next = new->next;
new->next->pre = phead;
free(new);
}
}
指定位置的插入
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
LTNode* new = creat(x);
new->next = pos->next;
pos->next->pre = new;
pos->next = new;
new->pre = pos;
}
指定位置的删除
void LTErase(LTNode* pos)
{
assert(pos);
pos->pre->next = pos->next;
pos->next->pre = pos->pre;
free(pos);
}
数据查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
assert(phead);
LTNode* new = phead->next;
while (new != phead)
{
if (x == new->val)
return new;
new = new->next;
}
return NULL;
}
创建给定数据对应的双链表指针
LTNode* creat(LTDataType x)
{
LTNode* p = (LTNode*)malloc(sizeof(LTNode));
p->next = p;
p->pre = p;
p->val = x;
return p;
}
五. 顺序表和双向链表的优缺点分析
不同点 链表 顺序表
存储空间上 物理上⼀定连续 逻辑上连续,理上不⼀定连续
随机访问 ⽀持O(1) 不⽀持:O(N)
任意位置插⼊或者删除元素 可能要搬移元素,效率低O(N) 只需修改指针指向
插⼊ 动态顺序表,空间不够时要扩容 没有容量的概念
应⽤场景 元素⾼效存储+频繁访问 任意位置插⼊和删除频繁
总结
ok,到这里链表算是过去了,我们翻过了数据结构的两座山,第一座山是顺序表,当时我们是以通讯录作为结尾,第二座是链表,我们写了单链表的增删查,又在此基础上写了通讯录,现在有写出了双链表的增删查改等功能,通讯录的话,写了那么多次了,就不写了,咱写个用链表造出来的贪吃蛇,我之前写过过一个,但那个没有用链表,而且功能也不齐全,这次写个完全版,作为C语言的结尾和数据结构的开篇。
那么,敬请期待,下一篇博客。
感觉有帮助的话,就点个赞支持一下吧。