双向链表存储结构
typedef struct DulNode
{
int data;
struct DulNode *prior;
struct DulNode *next;
}DulNode, *DuLinkList;
结点由存放数据元素的数据域和存放前驱结点和后继结点地址的两个指针组成
一、双向链表的创建
在前面单链表的创建中有头插法创建单链表,因为单链表是单向的,只需要解决next指针指向后继结点,而双向链表则需要考虑两个指针分别指向前驱和后继。
头插法插入元素时,后继为L->next,而前驱为L,那么以next正序遍历是一条链,而数据结点的prior则都指向了L,成了树形结构,故头插法不再适用。
尾插法
目的:创建长度为n的链表例如:1->2->3->4->5->6->7->8->9->10->NULL
实现:①创建L为头节点,头结点的前驱指向NULL
②添加n个节点:创建结点指针p,每次创建申请空间生成一个新的节点
利用指针操作数据域的赋值。从头指针后开始插入,每次插入的结点为链表的最后一个结点
③每次插入后把新插入的结点赋给游标r,标记最后一个结点的位置
④最后将r指向NULL作为结束
void CreateListTail(DuLinkList &L,int n)
{
DuLinkList p,r;
L=(DuLinkList)malloc(sizeof(DulNode));
r=L;
for (int i = 0; i < n; i++)
{
p=(DuLinkList)malloc(sizeof(DulNode));
p->data=i+1;
r->next=p;
p->prior=r;
r=p;
}
r->next=NULL;
}
只需要在创建单链表的函数中添加对前驱指针的指向即可
二、双向链表的读取
读取操作只需要一个方向遍历即可,所以与单链表读取相同,这里为了使用prior指针,我们先顺序到尾部,再利用prior返回遍历功能:读取链表中第i个数据的值
实现:
①创建p指向第一个结点,利用j作为结点的下标
②向后移动p到链表的尾部,再返回遍历
③得到第i个数据的值
void GetElem(DuLinkList &L,int i)
{
int j=1;
DuLinkList p=L->next;
while (p->next)
{
p=p->next;
++j;
}
while (p->prior&&j>i)
{
p=p->prior;
--j;
}
if (!p||j>i)
{
cout<<"have not found this element!"<<endl;
return ;
}
int e=p->data;
cout<<"得到的第"<<i<<"个元素是:"<<e<<endl;
return ;
}
三、双向链表的插入
由于双向链表有两个指针,所以插入操作相比单链表增加对前驱指针的操作功能:将数据e插入到链表第i个数据之后
实现:
①创建p指向第一个结点,利用j作为结点的下标
②向后移动p,直到找到所在结点,若j>i或p为空说明链表没有第i个元素
③生成新的节点add,将e赋值给add的数据域我们要先对插入的结点add进行操作
分配add的前驱和后继之后,相当于从p到p->next多了一条路
这时再砍断原本p到p->next的路表尾的后继为NULL,无法对NULL进行prior的操作,所以这里还需要判断是否是在表尾插入元素
void ListInsertAft(DuLinkList &L,int i,int e)
{
DuLinkList p=L->next;
int j=1;
while (p&&j<i)
{
p=p->next;
j++;
}
if(!p||j>i)
{
cout<<"结点不存在!"<<endl;
return ;
}
DuLinkList add=(DuLinkList)malloc(sizeof(DulNode));
add->data=e;
add->prior=p;
add->next=p->next;
if (j!=ListLength(L))
{
p->next->prior=add;
p->next=add;
}
else
{
p->next=add;
}
cout<<"成功在第"<<i<<"个元素后插入"<<e<<endl;
return ;
}
必须先“加上“add,再切断p到p->next本来的路
四、双向链表的删除
同样增加了对前驱指针的操作功能:删除链表中第i个元素
实现:
①创建p为头指针,利用j作为标记,j比p的下标大1
②向后移动p,直到找到所删除结点前一个节点,若j>i或p->next为空说明链表没有第i个元素
③创建q指向p之后的第一个结点(所删除的结点),将p的next指向q的后继结点,相当于跳过q这个结点表尾的后继为NULL,无法对NULL进行prior的操作,所以这里还需要判断是否是删除表尾元素
④释放结点q的内存
void ListDelete(DuLinkList &L,int i)
{
DuLinkList p=L,q;
int len=ListLength(L);
int j=1;
while (p->next&&j<i)
{
p=p->next;
j++;
}
if(!(p->next)||j>i)
{
cout<<"结点不存在!"<<endl;
return ;
}
q=p->next;
q->prior->next=q->next;
if (j!=len)
{
q->next->prior=q->prior;
}
cout<<"成功删除第"<<i<<"个元素:"<<q->data<<endl;
free(q);
return ;
}
五、双向链表的整表删除
与单链表相同,只需要单向进行操作即可功能:删除整个链表
实现:
①创建p指向第一个结点,将p赋值给q
②先释放掉q的内存,再向后移动p,直到表尾
③使头指针L指向NULL,表中不再有数据元素
void ClearList(LinkList &L)
{
LinkList p,q;
p=L->next;
while (p!=NULL)
{
q=p;
p=p->next;
free(q);
}
L->next=NULL;
cout<<"删除成功!"<<endl;
}
六、显示链表中的数据
与单链表相同,只需要单向进行操作即可功能:显示所有数据
实现:创建p指向第一个结点,每次向后移动输出数据即可
void show(LinkList &L)
{
LinkList p=L->next;
while (p!=NULL)
{
printf("%d->",p->data);
p=p->next;
}
cout<<"NULL"<<endl;
return ;
}
总结
本篇介绍了对双向链表的创建、插入、删除等操作。
双向链表在删除和插入上相较于单链表稍繁琐,但可以通过任意一个元素方便地访问前后结点,所占内存空间更大,是利用空间换取时间。
测试代码及运行实例
int main()
{
DuLinkList cr;
CreateListTail(cr,7);
show(cr);
GetElem(cr,7);
ListInsertAft(cr,6,50);
show(cr);
ListDelete(cr,7);
show(cr);
ClearList(cr);
show(cr);
return 0;
}