1.链表
链表是包含数据的独立数据结构(通常称为节点)的集合。链表中的每个节点通过链或指针连接在一起,程序通过指针访问链表中的节点,通常节点是动态分配的,但有时也能看到由节点数组构建的链表。
2.单链表
在单链表中,每个节点包含一个指向链表下一节点的指针,链表最后一个结点的指针字段的值为NULL,提示链表后面不再有其它结点。链表的起始位置可以使用一个根指针,根指针指向链表的第一个节点,注意根结点只是一个指针,它不含任何数据。
type struct NODE{
struct NODE *link;
int value;
}Node;
程序始终用链(指针)从一个结点移动到另一个结点。单链表可以通过链表从开始位置遍历链表直到结束位置,但链表无法从相反的方向进行遍历,
在单链表中插入:有序链表的插入,基本思想移位法
注意:头部和尾部的插入
头部插入稍好的解决方法是把一个指向root的指针作为参数传递给函数,然后使用间接访问,函数不仅可以获得root的值,也可以向它存储一个新的指针值。
result=sll_insert(&root,12);
int sll_inset(Node **rootp,int new_value){
Node *current;
Node *previous;
Node *new;
//得到指向第一个节点的指针
current = *rootp;
previous = NULL;
//寻找正确位置,current !=NULL是为了防止循环越过尾部
while(current !=NULL & current->value<new_value){
preview = current;
current = current->link;
}
//为新节点分配内存,并把新值存储到新节点,如果内存分配失败,返回fail
new = (Node *) malloc(sizeof(Node));
if(new ==NULL)
return FALSE;
new->value=new_value;
//把新节点插入到链表中,并返回true
new->link=current;
if(preview ==NULL)
*root =new; //插入头结点
else
previous->link=new; //中间或尾部
return true;
}
优化插入函数:消除头部特殊情况的关键在于:必须认识到链表中的每个节点都有一个指向它的指针,对于第一个节点,这是指向根结点的指针,,对于其他节点,这个指针是前一个结点的link字段,重点在于每个节点都有一个指针指向它。
root指针并不指向节点本身,而是指向节点内部的link字段,这是简化插入函数的关键,但必须能够取得当前节点的link字段的地址,在C中可通过¤t->link来达到目的。
result=sll_insert(&root,12);
int sll_inset(register Node **linkp,int new_value){
register Node *current;
register Node *new;
//寻找正确位置,current !=NULL是为了防止循环越过尾部
while((current=*linkp) !=NULL && current->value<new_value)
linkp = ¤t->link;
//为新节点分配内存,并把新值存储到新节点,如果内存分配失败,返回fail
new = (Node *) malloc(sizeof(Node));
if(new ==NULL)
return FALSE;
new->value=new_value;
//把新节点插入到链表中,并返回true
new->link=current;
*linkp = new;
return true;
}
单链表的增删改查,是需要后续学习的重要知识.
3.双向链表
单链表的替代方案就是双链表。在一个双链表中,每个节点包含两个指针--指向前一个节点的指针和指向后一个节点的指针。如此就可以双向遍历双链表。
typedef struct NODE{
struct NODE *fwd;
struct NODE *bwd;
int value;
}Node;
根节点的fwd字段指向链表的第一个节点,根节点的bwd字段指向链表的最后一个节点;若链表为空,这两个字段都为空NULL;链表第一个节点的bwd字段和最后一个节点的fwd字段都为NULL。
在双向链表中插入:当把一个节点插入到一个链表时,可能出现4中情况:
1)新值可能必须插入到链表的中间位置;
2)新值可能必须插入到链表的起始位置;
3)新增可能必须插入到链表的结束位置;
4)新增可能必须既插入到链表的起始位置,又插入到链表结束位置(即原链表为NULL);
int dll_insert(Node **rootp,int value){
Node *this;
Node *next;
Node *newnode;
//查看value是否已存在与链表中,如果是就返回,否则为新值创建一个新节点(newnode指向它)
//this将指向应该在新节点之前的那个节点,next将指向应该在新节点之后的那个节点
for(this=rootp;(next=this->fwd)!=NULL;this=next){
if(next->value==value)
return 0;
if(next->value>value)
break;
}
newnode=(Node *)malloc(sizeof(Node));
if(newnode==NULL)
return -1;
newnode->value=value;
//把新值添加到链表中
if(next!=NULL){
//情况1或2:并非位于尾部
if(this!=rootp){ //情况1:并非位于链表起始位置
newnode->fwd=next;
this->fwd=newnode;
newnode->bwd=this;
next->bwd=newnode;
}else{//情况2:位于链表起始位置
newnode->fwd=next;
rootp->fwd=newnode;
newnode->bwd=NULL;
next->bwd=newnode;
}
}else{
//情况3或4:位于链表尾部
if(this!=rootp){ //情况3:并非位于链表起始位置
newnode->fwd=NULL;
this->fwd=newnode;
newnode->bwd=this;
next->bwd=newnode;
}else{//情况4:位于链表起始位置
newnode->fwd=NULL;
rootp->fwd=newnode;
newnode->bwd=NULL;
next->bwd=newnode;
}
}
}
注意,在决定新值是否应该实际插入到链表之前,并不为它分配内存,如果事先分配内存,如果发现新增值原先已经存在于链表中,就有可能存在内存泄露。
第二版优化
int dll_insert(Node **rootp,int value){
Node *this;
Node *next;
Node *newnode;
//查看value是否已存在与链表中,如果是就返回,否则为新值创建一个新节点(newnode指向它)
//this将指向应该在新节点之前的那个节点,next将指向应该在新节点之后的那个节点
for(this=rootp;(next=this->fwd)!=NULL;this=next){
if(next->value==value)
return 0;
if(next->value>value)
break;
}
newnode=(Node *)malloc(sizeof(Node));
if(newnode==NULL)
return -1;
newnode->value=value;
//把新值添加到链表中
if(next!=NULL){
//情况1或2:并非位于尾部
newnode->fwd=next;
if(this!=rootp){ //情况1:并非位于链表起始位置
this->fwd=newnode;
newnode->bwd=this;
}else{//情况2:位于链表起始位置
rootp->fwd=newnode;
newnode->bwd=NULL;
}
next->bwd=newnode;
}else{
//情况3或4:位于链表尾部
newnode->fwd=NULL;
if(this!=rootp){ //情况3:并非位于链表起始位置
this->fwd=newnode;
newnode->bwd=this;
}else{//情况4:位于链表起始位置
rootp->fwd=newnode;
newnode->bwd=NULL;
}
next->bwd=newnode;
}
}
进一步提炼
int dll_insert(Node **rootp,int value){
Node *this;
Node *next;
Node *newnode;
//查看value是否已存在与链表中,如果是就返回,否则为新值创建一个新节点(newnode指向它)
//this将指向应该在新节点之前的那个节点,next将指向应该在新节点之后的那个节点
for(this=rootp;(next=this->fwd)!=NULL;this=next){
if(next->value==value)
return 0;
if(next->value>value)
break;
}
newnode=(Node *)malloc(sizeof(Node));
if(newnode==NULL)
return -1;
newnode->value=value;
//把新值添加到链表中
newnode->fwd=next;
this->fwd=newnode;
if(this!=rootp)
newnode->bwd=this;
else
newnode->bwd=NULL;
if(next!=NULL)
next->bwd=this;
else
rootp->bwd=newnode;
retun 1;
}
}
双向链表的增删改查,是需要后续学习的重要知识.
4.总结
单链表是一种使用指针来存储值的数据结构,节点在创建时时采用动态分配内存的方式,单链表只能以一个方向进行遍历。
双链表每个节点包含两个link字段:一个指向链表的下一节点,另一指向链表上一节点,双链表有两个根指针,分别指向第一个节点和最后一个节点,双链表可以双向遍历,双链表的插入必须修改4个指针。
5,警告总结
1)落在链表尾部的后面;
2)使用指针时应格外小心,因为C并没有对它们的使用提供安全网;
3)从if语句中提炼语句可能会改变测试结果;
6.编程提示总结
1)消除特殊情况使用代码更易于维护;
2)通过提炼语句消除if语句中的重复语句;
3)不要仅仅根据代码的大小评估它的质量;