最近两天看《C和指针》这本书的链表部分(第十二章),感觉作者写得实在是太棒了,C指针用得太漂亮了。自己看完后稍微有点心得,记录在此以便日后复习。
这里只讨论双链表(Doubly Linked List),更具体的说是有序双链表的插入问题。
所谓双链表,就是链表中每个节点含两个节点类型的指针,一个指向下一个节点,另外一个指向上一个节点。
链表节点数据结构定义如下:
typedef struct NODE{ struct NODE *fwd;// forward---pointer to the next node in the list struct NODE *bwd;//backward---pointer to the previous node in the list int value; } Node;
如上图所示,根节点的fwd指向链表中第一个节点,第一个节点的fwd指向第二个节点,以次类推,注意最后一个节点的fwd指向NULL;最后一个节点的bwd指向前一个节点,以此类推,注意第一个节点的bwd指向NULL;此外,要注意根节点的bwd也是指向最后一个节点的。
这样,我们可以从两个方向来遍历双链表的节点,使得链表操作更加方便快捷。
对于有序双链表的插入问题,第一步就是要找到新节点的插入位置。
Node *this,*next,*new_node; for(this=root;(next=this->fwd)!=NULL;this=next){ if(next->value==new_value) return 1; if(next->value > new_value) break; }
上面是书中寻找新节点插入位置的代码。程序的意思是把new_node插入到this和next指向的节点中间,但是不插入链表中已存在的数值。
新节点插入位置分析:
1)最一般的情况就是将新值插入到链表的中间了,如下图所示:
插入新节点后的链表示意图如下:
代码实现:
this->fwd=new_node; new_node->fwd=next; next->bwd=new_node; new_node->bwd=this;
2)在链表起始位置插入新节点
插入新节点后的链表示意图如下:
代码实现:
this->fwd=new_node; new_node->fwd=next; next->bwd=new_node; new_node->bwd=NULL;
有一点请务必注意:在这种情况下,有
root=this;
3)在链表的末端插入新节点
插入新节点后的链表示意图如下:
代码实现:
this->fwd=new_node; new_node->fwd=NULL: new_node->bwd=this; root->bwd=new_node;
有一点请务必注意:
在这种情况下,有next=NULL;
4)还有一种更特殊的情况-原链表是空的
插入新节点后的链表示意图如下:
代码实现:
this->fwd=new_node; new_node->fwd=NULL; new_node->bwd=NULL; this->bwd=new_node;
有一点请务必注意:
在这种情况下,有
next=NULL;
this=root;
插入新节点的实现代码
1)自己的代码实现
/*malloc a new node for the would-be insertion */ new_node=(Node *)malloc(sizeof(Node)); if(!new_node){ printf("malloc new node error!\n"); exit(2); } new_node->value=new_value; if(!this->fwd){//the list is empty before insertion this->fwd=new_node; new_node->fwd=NULL; new_node->bwd=NULL; this->bwd=new_node; } else if(!next->bwd){//insert to the right position after root this->fwd=new_node; new_node->fwd=next; next->bwd=new_node; new_node->bwd=NULL; } else if(!next->fwd){//insert in the end this->fwd=new_node; new_node->fwd=NULL: new_node->bwd=this; root->bwd=new_node; } else{//insert in the middle of list this->fwd=new_node; new_node->fwd=next; next->bwd=new_node; new_node->bwd=this; }
我自己写的代码也能实现插入新节点的功能,但是呢,太冗余了。
2)书中的代码分析及实现
New node insertion position |
Code implementation |
notes |
middle |
1) this->fwd=new_node; 2)new_node->fwd=next; 3)next->bwd=new_node; 4)new_node->bwd=this; |
|
start |
1)this->fwd=new_node; 2)new_node->fwd=next; 3)next->bwd=new_node; 4)new_node->bwd=NULL; |
root=this; |
end |
1)this->fwd=new_node; 2)new_node->fwd=NULL: 3)new_node->bwd=this; 4)root->bwd=new_node; |
next=NULL; |
empty list |
1)this->fwd=new_node; 2)new_node->fwd=NULL; 3)new_node->bwd=NULL; 4)this->bwd=new_node; |
next=NULL; this=root; |
请仔细观察这个表格:
1.四种情况下第一、第二行代码是完全一样的。
对于end、empty两种情况,请注意notes中next=NULL。
---这个启示我们可以把第一、第二行代码从if、else语句中抽取出来,放在if、else语句之前。
2.empty、end两种情况下的第四行代码是一样的,即this->bwd=new_node,因为在empty情况下this=root;
而middle、start两种情况下第三行代码是一样的,即next->bwd=new_node;
在这儿可以发现:
this->bwd=new_node;
next->bwd=new_node;
区别在于this和next。因此我们寻找一个判别条件来判断到底是取
this->bwd=new_node;还是next->bwd=new_node。
再观察notes列,对于empty、end,next=NULL;而对于middle、start,next!=NULL,
因此next是否等于NULL便是判断条件。
if(next==NULL) this->bwd=new_node; else next->bwd=new_node;
3.对于start的第四行代码和empty的第三行代码是一样的,即new_node->bwd=NULL;
而middle的第四行和end的第三行代码是一样的,即new_node->bwd=this。
观察notes可知this是否等于root便可作为判断条件。
if(root==this) new_node->bwd=NULL; else new_node->bwd=this;
综上所述,完整代码如下:
//the following method is from the book with little change. int dll_insert(Node *root,int new_value){ Node *this,*next,*new_node; for(this=root;(next=this->fwd)!=NULL;this=next){ if(next->value==new_value) return 1; if(next->value > new_value) break; } new_node=(Node *)malloc(sizeof(Node)); if(!new_node){ printf("malloc new node error!\n"); return -1; } new_node->value=new_value; new_node->fwd=next; this->fwd=new_node; if(root==this) new_node->bwd=NULL; else new_node->bwd=this; if(next==NULL) root->bwd=new_node; else next->bwd=new_node; root->value++; return 1; }
但愿自己表述清楚了,希望自己没有使用错别字。
欢迎大家批评指正!