c 语言数据结构,双链表及循环链表的相关操作
双链表
单链表是无法逆向检索的,有时候不太方便,有时候我们需要用到双链表。双链表结点的定义:
typedef struct DNode{ //定义结点类型
ElemType data; //数据域
struct DNode *prior,*next; //前驱和后继指针
}DNode,*DLinkList;
双链表的初始化
以下的代码片段是带头结点的双链表的初始化:
bool InitDLinkList(DLinkList &L){
L=(DNode *)malloc(sizeof(DNode)); //分配一个头结点
if(L==NULL) //内存不足,分配失败
return false;
L->prior=NULL; //头结点的prior 永远指向 NULL
L->next=NULL; //头结点之后暂时还没有结点
return true;
}
void testDLinkList(){
//初始化双链表
DLinkList L;
InitDLinkList(L);
//.....
}
双链表结点的样子:
双链表的插入
这种操作也相当于在p 结点之后插入s 结点,部分代码片段如下:
bool InsertNextDNode(DNode *p,DNode *s){
if(p==NULL||s==NULL) //非法参数
return false;
s->next=p->next;
if(p->next!=NULL) //如果p 结点有后继结点
p->next->prior=s;
s->prior=p;
p->next=s;
return true;
}
下面结合动图理解一下上面的代码:
双链表的删除
这不就是相当于删除p 结点的后继结点吗?看如下代码实现:
bool DeteleNextDNode(DNode *p){
if(p==NULL)
return false;
DNode *q=p->next; //找到p 的后继结点q
if(q==NULL)
return false; //p没有后继
p->next=q->next;
if(q->next!=NULL) //q 结点不是最后一个结点
q->next->prior=p;
free(q); //释放结点空间
return true;
}
结合动图理解一下下面的代码:
看到这里是不是我们也可以对一个双链表进行销毁呢?看如下代码片段:
void DestoryList(DLinkList &L){
//循环释放各个数据结点
while(L->next!=NULL)
DeleteNextDNode(L);
free(L); //释放头结点
L=NULL; //头指针指向NULL
}
双链表的遍历
-
前向遍历
while(p!=NULL){ //对p 结点作相应的处理 p=p->prior; }
-
后向遍历
while(p!=NULL){ p=p->next; }
-
前向遍历(跳过头结点)
while(p->prior!=NULL){ p=p->prior; }
双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。时间复杂度 O(n)。
循环链表
循环单链表
循环单链表也就是将尾结点的next 指针指向头结点:
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode,* LinkList;
//初始化一个循环单链表
bool InitList(LinkList &L){
L=(LNode *)malloc(sizeof(LNode)); //分配一个头结点
if(L==NULL)
return false;
L->next=L; //头结点next 指向头结点
return true;
}
//判断循环单链表是否为空的方法
bool Empty(LinkList L){
if(L->next==L)
return true;
else
return false;
}
那如何判断结点p 是否为循环单链表的表尾结点呢?就是判断p 的下一个结点是头结点即可:
bool isTail(LinkList L,LNode *p){
if(p->next==L)
return true;
else
return false;
}
循环双链表
表头结点的prior 指向表尾结点;表尾结点的next 指向头结点:
各项实现逻辑比较简单,这里就不在赘述了。
除了以上的几种线性表外,还有一种用数组的方式实现的链表 —— 静态链表,静态链表的增、删操作不需要大量移动元素,但容量固定不可变,不能随机存取,只能从头结点开始依次往后查找。 要做到心里有数哦~