从零开始学习数据结构--2.3线性表之循环,双向链表

上节我们学到了链表是的定义和链表的一系列操作

接下来我们学习循环链表和双向链表

循环链表

循环链表的定义:将单链表的终端结点的指针域由空指针改为指向头结点,九十整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,也就是单链表

其实就是链表的最后一个指针域指向了头结点,就是这么简单

        非空表

空表

我们要注意循环链表中间没有NULL指针,所以我们遍历的时候,如果要终止我们不能用 p 或者 p ->next 是否为空来判断

那我们该怎么办呢?

我们最后的NULL是不是换成了头指针呀,那我们是不是可以判断他是否等于头指针呀

所以涉及遍历操作的时候,终止条件是我们判断它们是否等于头指针

p! = NULL     =>      p! = L

p->next ! = NULL    =>     p->next !  = L

接下来我们看一下一个实例

带尾指针循环链表的合并(将Tb合并在Ta之后)

那我们该怎么操作呢?

算法思想:①p存表头结点                   p = Ta->next;

                  ②Tb的表头连到Ta的表尾    Ta->next = Tb->next->next;

                  ③释放Tb表的头节点        delete Tb->next;

                  ④修改指针                      Tb->next = p;

所以我们代码如下

LinkList Connect(LinkList Ta, LinkList Tb){
    p = Ta->next;
    Ta->next = Tb->next->next;
    delete Tb->next;
    Tb->next = p;
    return Tb;
 }

循环链表就这些内容

接下来我们讲双向链表

为什么有双向链表呢

我们知道单链表里面的一个,我们要找后续结点很简单,但是要找链表里面的前驱结点就困难了,我们可以具体来看一下

单链表的结点->有指示后继的指针域-> 找后继结点方便;

即查找某结点的后继结点的执行时间为O(1).

->无指示前驱的指针域->找前驱结点难;从表头出发找

即查找某节点的前驱结点的执行时间为O(n);

所以我们引出了双向链表

双向链表的定义

双向链表:在单链表的每个节点里面再增加一个指向其直接前驱的指针 prior ,这样链表中就形成了有两个方向不同的链,故成为双向链表

那我们就不能用之前的单链表的结构了,得重新创造双向链表结点的结构

typedef struct DuLNode{
    Elemtype  data;
    structDuLNode  *prior,*next;
  }DuLNode,*DuLinkList;

//长这样
/*    ------------------------------------------
     |             |                |           |
     |    prior    |     data       |    next   |
     |             |                |           |
     |             |                |           |
      ------------------------------------------
*/

具体长这样

双向循环链表

和单循环链表类似,双向链表也可以有循环链表

        让头结点的前驱指针指向链表的最后一个结点

        让最后一个结点的后继指针指向头结点

大概长这样

双向循环链表有一个特点叫做

对称性

假设指针p指向某一结点

p->prior->next  =  p  =  p->next->prior

双向链表的优点:再涉及一个方向的时候,他们和单向链表没有区别。但在插入和删除的时候,则需要修改两个方向的指针的时候,两者的操作的时间复杂度均为O(n)

双向链表的一系列操作

双向链表的插入

算法思想:①把新建的结点的prior域换成a结点

                  ②a结点的next域换成s

                  ③把新建结点的next域换成b

                  ④b结点的prior域换成s

void ListInsert_DuL(DuLinkList &L, Int i, Elemtype e){
    if(p = GetElemP_DuL(L,i) == 0)    //这个是我们查找我们要插入的位置
    return ERROR;                   //没找到我们就返回error
    s = new DuLNode;                //创建新结点
    s->data = e;                    //保存数据
    s->prior = p->prior;            //①
    p->prior->next = s;             //②
    s->next = p;                    //③
    p->prior = s;                   //④
    return OK; 
}//LinInsert_DuL

双向链表的删除

算法思想:

        ①保存要删除的结点的data域

        ②将p的下一个结点赋予p的前置的结点的next域

        ③将p的上一个结点赋予p的下一个结点的prior域

        ④释放p

void ListDelete_DuL(DuLink &L, Int i, ElemType &e){
    if(p = GetElemP_DuL(L,i) == 0)
    return ERROR;
    e = p->data;
    p->prior->next = p->next;
    p->next->prior = p->prior;
    free(p);
    return OK;
}//ListDelete_DuL

单链表,循环链表和双向链表的时间效率比较

这里我就直接引用王卓老师的表格了

链表和顺序表的比较

链式存储结构的优点:

①结点空间可以动态申请和释放;

②数据元素的逻辑次序靠结点的指针来指示,插入和删除时不需要移动数据元素

链式结构的缺点:

①:存储密度小,每个节点的指针域需额外占用存储空间。当每个结点的数据域占字节不多时,指针域所占存储空间的比重显得很大。

【存储密度:结点数据本身占用的空间和整个结点结构中所占的存储量之比

比如说,我一个结点data域占8个字节,next域占4个字节

那么存储密度就是   :8/12 = 67%

所以说,存储密度越大,存储空间的利用率就越高,顺序表的存储密度为1(100%),而链表的存储密度小于1】

②链式存储结构是非随机存取结构。对任一结点的操作都要从头指针链查找到该节点,则增加了算法的复杂度

具体比较如下:

声明一下:本系列的所有文章有些引用了王卓老师的内容,并不用于商业用途,仅限本人自学作为笔记,如有侵权,我立马删除修改!!!!

好了,第一章讲完了,下一章节我们讲站与队列。

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值