1.双链表与单链表的区别主要是在于双链表中,每个节点都包含两个指针——指向前一个节点的指针,和指向后一个节点的指针。这就便于我们从任何方向遍历整个链表。
下面是节点类型的说明:
typedef struct NODE{
struct NODE *fwd;
struct NODE *bwd;
int value;
}Node;
构造了一个简单的链表节点,此时,我们需要两个根指针,一个指向链表的第一个节点,另一个指向链表的最后一个节点。
我们可以考虑为根指针声明一个完整的节点,但应该注意其value字段不被使用,我们仅仅是用该节点来表示根指针,其并不存在于链表之中,我们让rootp->fwd表示链表第一个节点,而rootp->指向链表最后一个节点,这就需要在main函数中为该节点分配空间,且初始化rootp->fwd以及rootp->bwd为NULL。
Node *rootp;
rootp=(Node *)malloc(sizeof(Node)); //为根指针分配空间,并初始化链表第一部分为NULL
rootp->bwd=NULL;
rootp->fwd=NULL;
这时可以在专门的free函数中释放该空间,也可以在main函数最后释放。
2.实现值的有序插入
这时需要考虑节点插入的位置,一般来讲有4种情况:
1.插入位置在链表中间段;
2.插入位置在链表初始段;
3.插入位置在链表尾部;
4.该链表为空链表;
需要分别考虑这4种不同情况下节点的fwd及bwd指针指向。
以下代码详细描述了这四种情况下的指针指向:
if(next!=NULL) //四种情况
{ //1.中间 2.起始 3.末尾 4.空
if(this!=rootp) //1.中间
{
newnode->fwd=next;
this->fwd=newnode;
next->bwd=newnode;
newnode->bwd=this;
}
else{ //2.初始
newnode->fwd=next;
rootp->fwd=newnode;
newnode->bwd=NULL;
next->bwd=newnode;
}
}
else{
if(this!=rootp) //末尾非空
{
newnode->fwd=next;
this->fwd=newnode;
newnode->bwd=this;
rootp->bwd=newnode;
}
else{ //空
newnode->fwd=NULL;
rootp->fwd=newnode;
newnode->bwd=NULL;
rootp->bwd=newnode;
}
}
同时,我们可以注意到以上单纯实现插入的代码段比较冗余,由于if语句内有功能相似或相同的代码,我们可以将其提取出现,减少代码量。
比如每个if语句中都有newnode->fwd=next,以及this->fwd=newnode.且在程序运行到该段时,newnode及this指针一定不为NULL,故可以直接提取出来。如下是改进后的完整的插入节点的代码段。
ps:注意到根指针rootp的存储空间是在main函数中通过malloc语句分配,存储在堆(heap)中,在做插入、删除等系列操作时,并不需要改变rootp的值,故在函数参数列表中并不需要像单链表头指针一样传入其地址(当然,传地址也是可以的)。
这里是直接将rootp的值传入到函数中。
int dll_insert(Node *rootp,int num)
{
Node *next; //插入位置的下一个
Node *this; //插入位置的前一个
Node *newnode;
// for(this=rootp;(next=this->fwd)!=NULL;this=next)
for(this=rootp,next=rootp->fwd;next;this=next,next=next->fwd) //寻找插入位置,从头开始
{
if(next->value==num) //插入不同数
return 0;
else if(next->value>num) //一旦找到比待插入数大的位置,跳出循环;
break;
}
newnode=(Node *)malloc(sizeof(Node)); //为新插入的节点分配空间
if(newnode==NULL)
return -1;
newnode->value=num;
newnode->fwd=next; //以下是实现插入(简化版)
this->fwd=newnode;
if(this!=rootp) //非初始
{
newnode->bwd=this;
}
else{ //初始
newnode->bwd=NULL;
}
if(next!=NULL) //非结尾
{
next->bwd=newnode;
}
else{ //结尾(新加入的节点在最后一个位置)
rootp->bwd=newnode;
}
return 1;
}
3.删除节点
在删除某个节点时,需要注意删除的位置是在初始位置,中间位置,还是末尾位置,同时需注意删除之后链表是否为空。
void reduce(Node *rootp) //删除
{
Node *q,*p,*t; //t可以木有√
int isFound=0; //标记是否找到
int num;
puts("Please enter the num to delete:");
scanf("%d",&num);
while(getchar()!='\n')
continue;
for(q=NULL,p=rootp->fwd;p;q=p,p=p->fwd) //删除
{
if(p->value==num) //如果找到了
{
isFound=1;
if(q)
{
q->fwd=p->fwd; //
if(p->fwd==NULL) //最后位置
{
rootp->bwd=q; //表示q位置为最后一个,删除p;
}
else{
// t=p->fwd; //p->fwd->bwd=q;
p->fwd->bwd=q; //不是最后一个,删除p,p的后一个的前一个为q
}
}
else{
rootp->fwd=p->fwd; //q为NULL,那么p是第一个,删除p,根指针指向第二个(可能为空,这样整个链表就是空的了),现在是第一个了
if(p->fwd==NULL) //p是最后一个。说明删除之后就没有了
rootp->bwd=NULL; //整个链表为空, rootp->bwd=NULL rootp->fwd=p->fwd,而p->fwd为空
else{
p->fwd->bwd=NULL;//这个意思是p后面还有,那么该节点为第一个了,其bwd要指向NULL;
}
}
free(p);
break;
}
}
if(!isFound)
{
printf("Not found.\n");
}
}
3.正向打印及反向打印
这个操作比较简单,不多做介绍。
void print(Node *rootp) //打印
{
Node *p;
for(p=rootp->fwd;p;p=p->fwd) //正向打印,以根指针的初始指向为开始,以链表初始节点开始
{
printf("The num is %d.\n",p->value);
}
}
void bkprint(Node *rootp) //反向打印
{
Node *p;
for(p=rootp->bwd;p;p=p->bwd) //反向打印,以根指针的bwd为始,指向的是链表最后一个节点
{
printf("The num is %d.\n",p->value);
}
}