双链表的基本知识以及增删查改的实现

满怀热忱,前往梦的彼岸


前言

之前我们对单链表进行了非常细致的剖析,现在我们所面临的则是与之相对应的双链表,我会先告诉诸位它的基本知识,再接着把它的增删查改讲一下,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语言的结尾和数据结构的开篇。


那么,敬请期待,下一篇博客。

感觉有帮助的话,就点个赞支持一下吧。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值