上节我们学到了链表是的定义和链表的一系列操作
接下来我们学习循环链表和双向链表
循环链表
循环链表的定义:将单链表的终端结点的指针域由空指针改为指向头结点,九十整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,也就是单链表
其实就是链表的最后一个指针域指向了头结点,就是这么简单
非空表
空表
我们要注意循环链表中间没有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】
②链式存储结构是非随机存取结构。对任一结点的操作都要从头指针链查找到该节点,则增加了算法的复杂度
具体比较如下:
声明一下:本系列的所有文章有些引用了王卓老师的内容,并不用于商业用途,仅限本人自学作为笔记,如有侵权,我立马删除修改!!!!
好了,第一章讲完了,下一章节我们讲站与队列。