[例 2-8] 用前插法建立一个单链表。所谓“前插法”是指每次新结点总是作为首元结点插入在链表的头结点之后。这是另一种建立单链表的方式,插入的结果使得链表中各结点中数据的逻辑顺序与输入数据的顺序正好相反。
[程序 2-21] 用前插法创建一个单链表
#include <stdlib.h>
void insertFront(LinkList&first,DataType endTag){
//endTag是约定的输入序列结束的标志。其取值的约定同程序2-28
DataType val;cin>>val;
if(val==endTag)return; //链表数据输入结束,停止创建
LinkNode*s=new LinkNode; //创建新结点
if(!s){cerr<<"存储分配错误!\n";exit(1);} //链接stdlib.h和iostream.h
s->data=val; //s结点赋值
s->link=first->link;first->link=s; //作为首元结点链入头结点之后
insertFront(first,endTag); //递归创建后续链表
};
void main(void){
LinkList*L=new LinkNode;
if(!L){cerr<<"存储分配错误!\n";exit(1);}
cin>>L->data; //输入约定的输入序列结束标志
L->link=NULL; //链表置空
insertFront(L,L->data); //递归建立单链表
};
[例 2-9] 使用尾递归实现的程序可以很容易地转换为用循环实现的非递归程序。它们爱时间复杂度上都是一样的,但空间复杂度降低为O(1)。如单链表的输出可以使用程序2-27说给出的尾递归方式实现,也可以直接用循环实现。
[程序 2-22] 正向输出单链表各结点值得非递归算法
void write(LinkNode*first){ //非递归(迭代)算法
while(first->link!NULL){ //循环输出到链空
cout<<first->link->data<<endl; //链表非空,输出结点数据
first=first->link; //继续输出下一结点
}
};
单链表的应用举例
可以用有序链表来表示集合。链表中每个节点表示集合的一个成员各个节点所表示的成员e0,e1,…,en在链表中按升序排列,即e0<e1<…<en。因此,在一个有序链表中寻找一个成员时,一般不用查找整个链表,查找效率可以提高很多。此外用有序链表表示集合,集合成员可以无限增加。
用有序链表表示集合时的结构定义与带头结点的单链表大致相同,程序如下:
[程序 2-23] 基于有序链表表示集合的结构定义
typedef int DataType; //假定集合元素的数据类型为int型
typedaf struct node{ //集合的结点定义
DataType data; //每个成员的数据
struct node*link; //链接指针
}SetNode;
typedef struct LinkSet;{ //集合的定义
SetNode*first,*last; //表头与表尾指针
};
[例 2-10] 集合的查找算法Contains是最常用的集合操作,它判断一个元素x是否在集合中。实际上它就是一个在有序链表中循序检测的过程,从链表的首元结点开始,沿链表查找,直到找到这个元素或遇到大于这个元素的一个元素。如果是前者,则查找成功,该元素在此集合中;否则查找失败,该元素不在此集合中。
[程序 2-24] 集合Contains算法的实现
SetNode*Contains(LinkSet&S,DataType x){
//如果x是集合S的成员,则函数返回与x匹配的集合结点地址,否则函数返回NULL
SetNode*p=s.first->link; //链的扫描指针
while(p!=NULL&&p->data<x) //循链搜索
p=p->link;
if(p!=NULL&&p->data==x)return p; //找到
else return NULL; //未找到
};
[例 2-11] 函数addMembr给出集合的插入算法。为了把一个新元素x插入到集合中,算法先查找x的插入位置。如果找到x,则不能插入,否则插入在刚刚大于x的元素前面。这是必须用一个指针pre记忆p的前趋结点地址,新元素插入在pre与p中间。
[程序 2-25] 集合addMember算法的实现
int addMember(LinkSet&s,DataType x){
//把新元素x加入到集合之中,若集合中已有此元素,则函数返回0,否则函数返回1
SetNode*p=S.first->link,*pre=S.first; //p是扫描指针,pre是p的前趋
while(p!=NULL&&p->data<x) //循链扫描
{pre=p;p=p->link;}
if(p!=NULL&&p->data==x)return 0; //集合中已有此元素,不插入
SetNode*q=new SetNode; //创建值为x的结点,用q指示
if(!q){cerr<<"存储分配错误!\n";exit(1);} //链接stdlib.h和iostream.h
q->data=x;q->link=p;pre->link=q; //链入
if(!p)S.last=q; //链到链尾时改链尾指针
return 1;
};
[例 2-12] 函数delMember是删除指定元素的算法。同样需要先搜索删除结点位置,如果没有找到这个元素,则不能删除;否则必须将被删元素所在结点从链中摘下,也要求一个指针pre记忆p的前趋结点地址。
[程序 2-26] 集合delMember算法的实现
int delMember(LinkSet&S.DataType x){
//把集合中成员x删去,若集合不空且元素x在集合中,则函数返回true,否则函数返回0
SetNode*p=s.first->link,*pre=s.link;
while(p!=NULL&&p->data<x) //循链扫描
{pre=p;p=->link;}
if(p!=NULL&&p->data==x){ //找到,可以删除结点p
pre->link=p->link; //重新链接,从链上摘下结点p
if(p==S.last)S.last=pre; //删去链尾时改链尾指针
delete p; //删除含x结点
return 1;
}
else return 0; //集合中无此元素,不能删除
};
[例 2-13] 集合的“并“运算就是合并两个有序链表并消除重复元素的过程。为此,需要对两个有序链表顺序检测,当两个链表都未检测完时比较对应元素的值,把小的插入到结果链表中;当其中有一个链表检测完时另一个链表复制到结果链表中。
[程序 2-27] 集合Merge算法的实现
void Merge(LinkSet&LA,LinkSet&LB,LinkSet&LC){
//求集合LA与集合LB并,结果通过LC返回,要求LC已存在且为空
SetNode*pa=LA.first->link,*pb=LB.first->link; //LA和LB集合的链表扫描指针
SetNode*p,*pc=LC.first; //结果链表的存放指针
while(pa!=NULL&&pb!=NULL){ //两个结合都未检测完
if(pa->data<=pb->data){ //LA集合中元素值小或相等
pc->link=new SetNode;
if(!pc->link){cerr<<"存储分配失败!\n";exit(1);}
pc->link->data=pa->data;
pa=pa->link;if(pa->data==pb->data)pb=pb->link;
}
else{ //LB集合中元素值小
pc->link=new SetNode;
if(!pc->link){cerr<<"存储分配错误!\n";exit(1);}
pc->link->data=pb->data;
pb=pb->link;
}
pc=pc->link;
}
p=(pa!=NULL)?pa:pb; //处理未处理完的集合
while(p!=NULL){ //向结果链表中逐个复制
pc->link=new<<SetNode;
if(!pc->link){cerr<<"存储分配错误!\n";exit(1);}
pc->link-data=p->data;
pc=pc->link;p=p->link;
}
pc->link=NULL;LC.last=pc; //结果链表收尾
};
其他集合的运算,如“类”、“差”等都可以用类似的程序结构实现。这些关于有序链表的操作在后续章节经常会用到,请充分体会。