1 典型线性数据结构
只允许在序列末端进行操作,这种线性结构称为栈
只允许在序列两端进行操作,这种线性结构称为队列
允许在序列任意位置进行操作,这种线性结构称为线性表
1.1 线性结构的逻辑描述
1.栈
- 最后一个元素所在端称为栈顶,第一个元素所在端称为栈底
- 不含任何元素的栈称为空栈
- 后进先出
2.队列
- 允许插入的一端称为队尾,允许删除的一端称为队头
- 不含任何元素的队列称为空队列
- 先进先出
3.线性表
- 线性表中所含数据元素的个数称为线性表的长度
- 长度为0的线性表称为空表
- 按元素值非递减或非递增排列的线性表称为有序线性表,简称有序表
1.2 线性结构的存储表示
顺序存储和链式存储
2 顺序栈
采用顺序存储结构的栈称为顺序栈,即需要用一片地址连续的空间来存储栈的元素
2.1 栈的顺序表示和实现
1.类型定义
typedef struct {
ElemType *elem; //存储空间的基址
int top; //栈顶元素的下一个位置,简称栈顶位标
int size; //当前分配的存储容量
int increment; //扩容时,增加的存储容量
}SqStack;
2.常用操作
2.1.接口
Status InitStack_Sq(SqStack &S,int size,int inc);//初始化顺序栈S
Status DestroyStack_Sq(SqStack &S);//销毁顺序栈S
Status StackEmpty_Sq(SqStack S);//判断栈S是否为空,空则返回true,否则FALSE
void ClearStack_Sq(SqStack &S);//清空栈S
Status Push_Sq(SqStack &S,ElemType e);//元素e压入栈S
Status Pop_Sq(SqStack &S,ElemType &e);//栈S的栈顶元素出栈,并用e返回
Status GetTop_Sq(SqStack S,ElemType &e);//取栈S的栈顶元素,并用e返回
2.2.初始化操作
Status InitStack_Sq(SqStack &S,int size,int inc){
S.elem=(ElemType*)malloc(size*sizeof(ElemType));//分配存储空间
if(NULL==S.elem) return OVERFLOW;
S.top=0; //置S为空栈
S.size=size; //初始容量值
S.increment=inc; //初始增量值
return OK;
}
2.3.入栈
首先先检查存储空间,若已满则增加存储容量。然后将新元素放入栈顶位标,并将S.top加1
Status Push_Sq(SqStack &S,ElemType e) {
ElemType *newbase;
if(S.top>=S.size) {
newbase=(ElemType*)realloc(S.elem, (S.size+S.increment)*sizeof(ElemType));
S.elem=newbase;
S.size+=S.increment;
}
S.elem[S.top++]=e; //e入栈,并将S.top加1
return OK;
}
情况二:
typedef struct {
ElemType *elem;
ElemType *top; //更改定义
int size;
int increment;
}SqStack;
Status Push_Sq(SqStack &S,ElemType e){
ElemType *newbase;
if(*(S.top)>=S.size) {
newbase = (ElemType*)realloc(S.elem, (S.size+S.increment)*sizeof(ElemType));
if(newbase==NULL) return ERROR;
S.elem = newbase;
S.size += S.increment;
}
*(S.top++) = e;
return OK;
}
2.4.销毁
Status DestroyStack_Sq(SqStack &S){
S.top = 0;
S.size = 0;
return OK;
}
2.5.判空
Status StackEmpty_Sq(SqStack S){
if(S.top==0) return true;
return false;
}
情况二:
typedef struct {
ElemType *elem;
ElemType *top; //更改定义
int size;
int increment;
}SqStack;
Status StackEmpty_Sq(SqStack S){
if(S.top==S.elem) return true;
return false;
}
2.6.清空
void ClearStack_Sq(SqStack &S){
S.top = 0;
}
2.7.出栈
Status Pop_Sq(SqStack &S,ElemType &e){
if(S.top==0) return ERROR;
e = S.elem[S.top-1];
S.top--;
return OK;
}
情况二:
typedef struct {
ElemType *elem;
ElemType *top; //更改定义
int size;
int increment;
}SqStack;
Status Pop_Sq2(SqStack2 &S, ElemType &e) {
// Add your code here
if(S.elem == S.top) return ERROR;
e = *(--S.top);
return OK;
}
2.8.取栈顶元素
Status GetTop_Sq(SqStack S,ElemType &e) {
if(S.top==0) return ERROR;
e = S.elem[S.top-1];
return OK;
}
2.2.应用举例
1.数制转换
void Conversion(int N, int rd)//N为需要被转换的十进制数,rd为具体的进制
{ SqStack S;
InitStack_Sq(S,MAXSIZE,INCREMENT);
while(N!=0) {
Push_Sq(S,N%rd);
N/=rd;
}
ElemType e;
//打印该数字
while(FALSE == StackEmpty_Sq(S)) {
Pop_Sq(S,e);
printf("%d",e);
}
}
2.括号匹配
依次读取每个括号,若是左括号则入栈;若是右括号,则先检查栈,若栈空,则右括号多余;否则与栈顶元素比较,若匹配,则左括号出栈;否则不匹配;
当读入完所有括号时检查栈,栈空则匹配正确;否则左括号多余
Status matchBracketSequence(char* exp, int n)//exp为括号串,n为括号串的长度
{ SqStack S;
InitStack_Sq(S,n,INCREMENT);
ElemType e;
int i=0;
while(i<n) {
switch(exp[i]) {
case'(':
case'[':
case'{':Push_Sq(S,exp[i++]);break;
case')':
case']':
case'}':
if(TRUE == StackEmpty_Sq(S)) return ERROR;//右括号是多余的
else {
GetTop_Sq(S,e);
if((exp[i]==')'&&e=='(')||(exp[i]==']'&&e=='[')||(exp[i]=='}'&&e=='{')) {
Pop_Sq(S,e);
i++;
} else return ERROR;//右括号是非所期待的
}
break;
}
}
if(TRUE == StackEmpty_Sq(S)) return OK;
else return ERROR;//左括号是多余的
}
3 循环队列
3.1.类型定义
采用顺序存储结构的队列,需要按队列可能的最大长度分配存储空间
typedef struct {
ElemType *elem; //存储空间的基址
int front; //队头位标
int rear; //队尾位标,指向队尾元素的下一位置
int maxSize; //存储容量
}SqQueue;
顺序存储的队列为空时Q.frontQ.rear;为满时Q.rearQ.maxSize;
顺序存储的队列容易造成假溢出
解决假溢出:一是出队时,将全部元素向前移动,但算法效率变低。
二是将队列看成首尾相连的顺序队列,称为循环队列。
3.2.判断队列满和空
循环队列,由于入队时Q.rear循环向前追赶Q.front,出队时Q.front向前追赶Q.rear,造成队满和队空时Q.front和Q.rear均相等,无法通过此条件来判断循环队列是空还是满
解决该问题的方法:
1.设一标志域标识队列的空和满
2.设一个长度域记录队列中元素的个数
3.少用一个元素空间,一旦Q.front==(Q.rear+1)%Q.maxSize则认为队满;
判断循环队列为空则是:Q.front==Q.rear
3.3.常用操作
1.接口
Status InitQueue_Sq(SqQueue &Q,int size);
//构造一个空队列Q,最大队列长度为size
Status DestroyQueue_Sq(SqQueue &Q);
//销毁队列Q,Q不再存在
void ClearQueue_Sq(SqQueue &Q);
//将Q置为空队列
Status QueueEmpty_Sq(SqQueue Q);
//判空
int QueueLength_Sq(SqQueue Q);
//返回队列Q中元素个数,即队列的长度
Status GetHead_Sq(SqQueue Q,ElemType &e);
//若队列不空,则用e返回Q的队列头元素,并返回OK,否则返回ERROR
Status EnQueue_Sq(SqQueue &Q,ElemType e);
//在当前队列的尾元素之后插入元素e为新的队列尾元素
Status DeQueue_Sq(SqQueue &Q,ElemType &e);
//若队列不空则删除当前队列Q中的头元素,用e返回其值,并返回OK,否则返回ERROR
2.初始化操作
Status InitQueue_Sq(SqQueue &Q,int size) {
Q.elem=(ElemType*)malloc(size*sizeof(ElemType));
if(NULL=Q.elem) return OVERFLOW;
Q.maxSize=size;
Q.front=Q.rear=0;//置Q为空队列
return OK;
}
3.出队
Status DeQueue_Sq(SqQueue &Q,ElemType &e) {
if(Q.front==Q.rear) return ERROR;
e=Q.elem[Q.front];
Q.front=(Q.front+1)%Q.maxSize;//Q.front循环加一
return OK;
}
4.入队
Status EnQueue_Sq(SqQueue &Q,ElemType e) {
if((Q.rear+1)%Q.maxSize==Q.front) return ERROR;
Q.elem[Q.rear]=e;
Q.rear=(Q.rear+1)%Q.maxSize;
return OK;
}
5.求长度
int QueueLength_Sq(SqQueue Q) {
return (Q.rear-Q.front+Q.maxSize)%Q.maxSize;
}
3.4.注意
由于在循环队列初始化时必须分配指定容量的存储空间,所以当无法预估队列的最大长度时,宜用链队列
4 顺序表
采用顺序存储结构表示的线性表称为顺序表。用一组地址连续的存储单元依次存放线性表的数据元素。
若在顺序表中插入或删除元素,则需要移动操作位置之后的所有元素。因此,为避免移动元素,在顺序表的接口定义中只考虑在表尾插入和删除元素,如此实现的顺序表也称为栈表。
4.1.类型定义
typedef struct {
ElemType *elem;4
int length; //当前长度
int size;
int increment;
}SqList;
4.2.常用操作
1.接口
Status InitList_Sq(SqList &L,int size,int inc);//初始化顺序表L
Status DestroyList_Sq(SqList &L);//销毁顺序表
Status ClearList_Sq(SqList &L);//清空顺序表
Status ListEmpty_Sq(SqList L);//判空
int ListLength_Sq(SqList L);//返回元素个数
Status GetElem_Sq(SqList L,int i,ElemType &e);//返回L中第i个元素的值
int Search_Sq(SqList L,ElemType e);//顺序查找元素e,成功时返回该元素在表中第一次出现的位置,否则返回-1
Status ListTraverse_Sq(SqList L,Status(*visit)(ElemType e));//遍历,依次对每个元素调用函数visit()
Status PutElem_Sq(SqList &L,int i,ElemType e);//将L中第i个元素赋值为e
Status Append_Sq(SqList &L,ElemType e);//在顺序表L表尾添加元素e
Status DeleteLast_Sq(SqList &L,ElemType &e);//删除表尾元素
Status Pop_Sq(SqList &L,ElemType &e);//出表
2.初始化
Status InitList_Sq(SqList &L,int size,int inc) {
L.elem=(ElemType*)malloc(size*sizeof(ElemType));
if(L.elem==NULL) return OVERFLOW;
L.length=0;
L.size=size;
L.increment=inc;
return OK;
}
3.返回元素个数
int ListLength_Sq(SqList L) {
return L.length;
}
4.返回第i个元素的值
Status GetElem_Sq(SqList L,int i,ElemType &e) {
if(i<0 || i>L.length) return ERROR;
e=L.elem[i];
return OK;
}
5.顺序查找
int Search_Sq(SqList L,ElemType e) {
int i=0;
while(i<L.length && L.elem[i]!=e) i++;
if(i<L.length) return i;
else return -1;
}
为确定元素在表中的位置,需要将表中的元素依次与给定值比较。查找成功时比较次数的期望值称为查找成功的平均查找长度,简称ASL。
对于有n个元素的顺序表,查找成功的平均查找长度可表示为[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MrfbufMx-1632154147025)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210919223949425.png)]
其中pi为查找第i元素的概率,且pi的和为1;ci为查找第i元素时需要与给定值进行比较的元素的个数,显然ci根据查找算法的不同而不同。
6.表尾添加元素
Status Append_Sq(SqList &L,ElemType e) {
ElemType *newbase;
if(L.length>=L.size) {
newbase = (ElemType*)realloc(L.elem, (L.size+L.increment)*sizeof(ElemType));
if(NULL == newbase) return OVERFLOW;
L.elem = newbase;
L.size += L.increment;
}
L.elem[L.length] = e;
L.length++;
return OK;
}
7.表尾删除元素
Status DeleteLast_Sq(SqList &L,ElemType &e) {
if(L.length==0) return ERROR;
e=L.elem[L.length-1];
--L.length;
return OK;
}
8.出表
Status Pop_Sq(SqList &L,ElemType &e) {
if(0 == L.length) return ERROR;
L.length --;
e=L.elem[L.length];
return OK;
}
4.3.有序顺序表的归并
从La和Lb的第一个元素开始,依次比较其值的大小,将较小的元素添加到Lc中,并取有序顺序表的下一项,直到La或Lb处理完最后一个元素。并将尚未处理完的La或Lb的剩余部分添加到Lc中
void MergeList_Sq(SqList La,SqList Lb,SqList &Lc) {
int i=0,j=0,size,increment=10;
ElemType ai,bj;
int l1=ListLength_Sq(La),l2=ListLength_Sq(Lb);
//用接口获取长度大小
size = l1+l2;
InitList_Sq(Lc,size,increment);
while(i<l1 && j<l2) {
GetElem_Sq(La,i,ai);
GetElem_Sq(Lb,j,bj);
if(ai<=bj) {
Append_Sq(Lc,ai);
++i;
} else {
Append_Sq(Lc,bj);
++j;
}
}
while(i<l1) {
GetElem_Sq(La,i++,ai);
Append_Sq(Lc,ai);
}
while(j<l2) {
GetElem_Sq(Lb,j++,bj);
Append_Sq(Lc,bj);
}
}
4.4.一元稀疏多项式
采用压缩存储,只存非零系数以及对应的指数
该顺序表是指数有序的
4.4.1.类型定义
typedef struct {
float coef;//系数
int expn;//指数
}ElemType,Term;//项
typedef struct {
Term *elem;//存储空间基址
int length;//长度(项数)
}Poly;//一元稀疏多项式
4.4.2.常用操作
1.接口
Status CreatePoly(Poly &P,Term e[],int n);
//由数组e的前n项构建一元稀疏多项式P
Status DestroyPoly(Poly &P);
//销毁一元稀疏多项式P
Status PrintPoly(Poly P);//打印输出P
int PolyLength(Poly P);//返回P的项数
void AddPoly(Poly pa,Poly pb,Poly &pc);
//两个一元系数多项式做加法,即pc=pa+pb;
void SubPoly(Poly pa,Poly pb,Poly &pc);
//两个一元系数多项式做减法,即pc=pa-pb;
void MulPoly(Poly pa,Poly pb,Poly &pc);
//两个一元系数多项式做乘法,即pc=pa×pb;
void DivPoly(Poly pa,Poly pb,Poly &pc);
//两个一元系数多项式做除法,即pc=pa/pb;
2.构建一元稀疏多项式
Status CreatePoly(Poly &P,Term e[],int n) {
int i;
P.elem=(Term*)malloc(n*sizeof(Term));
if(NULL==P.elem) return OVERFLOW;
for(i=0;i<n;i++) P.elem[i]=e[i];
P.length=n;
return OK;
}
3.加法操作
思路:从pa和pb的第一项开始,依次比较指数大小,直到pa或pb处理完最后一项;若不相等则将指数较小项添加到pc,并取所在多项式的下一项;若相等则指数不变,系数相加,若结果非零,则添加到pc,pa和pb都取下一项;然后将未处理完的pa或pb的剩余部分依次添加到pc
void AddPoly(Poly pa,Poly pb,Poly &pc) {
int i=0,j=0,k=0;
float c;
pc.elem=(Term*)malloc((pa.length+pb.length)*sizeof(Term));
if(NULL==pc.elem) return OVERFLOW;
while(i<pa.length && j<pb.length) {//两个多项式均为处理结束
if(pa.elem[i].expn<pb.elem[j].expn)//pa的指数较小
pc.elem[k++]=pa.elem[i++];
else if(pa.elem[i].expn>pb.elem[j].expn)//pb的指数较小
pc.elem[k++]=pb.elem[j++];
else {//指数相等
c=pa.elem[i].coef+pb.elem[j].coef;//系数相加
if(c!=0) {
pc.elem[k].expn=pa.elem[i].expn;
pc.elem[k].coef=c;
k++;
}
i++;j++;//pa和pb均取下一项
}
}
if(i==pa.length)//pa已处理完,将pb剩余部分添加到pc
while(j<pb.length) pc.elem[k++]=pb.elem[j++];
if(j==pb.length)//pb已处理完,将pa剩余部分添加到pc
while(i<pa.length) pc.elem[k++]=pa.elem[i++];
if(k<pa.length+pb.length)//根据实际长度重新分配pc的空间
if(NULL==
(pc.elem=(Term*)realloc(pc.elem,k*sizeof(Term))))
return OVERFLOW;
pc.length=k;
return OK;
}
4.5.稀疏矩阵
零元数目远远多于非零元数目,并且非零元的分布没有规律的矩阵称为稀疏矩阵
压缩存储:原则为不存储零元
1.三元组顺序表
采用顺序表作为稀疏矩阵的压缩存储结构,称该顺序表为三元组顺序表,简称为三元组表;
类型定义:
typedef struct {
int i,j;//非零元的行和列
ElemType e;//非零元的值
}Triple;//三元组
typedef struct {
Triple *data;//非零元三元组表,0号单元未用,即从1开始存
int mu,nu,tu;//矩阵的行数,列数和非零元个数
}TSMatrix;//三元组顺序表
2.按列转置
如果只是简单交换a.data中行和列的内容,那么得到的c.data是一个按列优先顺序存储的稀疏矩阵,要得到按行优先顺序存储的矩阵b.data,就必须重新排列三元组c.data的顺序。
为避免重新排列,考虑到矩阵A的列是转置后矩阵B的行,因此,按a.data的列序转置,所得到的转置矩阵B的三元组表b.data必定是按行优先存放的。
算法思路:对A中的每一列col(1≤col≤n),通过从头至尾扫描三元表a.data,找出所有列号等于col的三元组,将它们的行号和列号互换后依次放入b.data中。
Status TransposeSMatrix(TSMatrix M,TSMatrix &T) {
int j,p,q;
T.mu=M.nu;T.nu=M.mu;T.tu=M.tu;
if(T.tu!=0){
T.data=(Triple*)malloc((T.tu+1)*sizeof(Triple));
if(NULL==T.data) return OVERFLOW;
q=1;
for(j=1;j<=M.nu;++j) {
for(p=1;p<=M.tu;++p) {
if(M.data[p].j==j) {
T.data[q].i=M.data[p].j;
T.data[q].j=M.data[p].i;
T.data[q].e=M.data[p].e;
q++;
}
}
}
}
return OK;
}
3.快速转置
上面的算法复杂度较高,故改进算法。
按矩阵A的a.data中三元组的次序进行转置,并将转置后的三元组直接置入B中恰当的位置。如果能确定矩阵A中每一列(即B中的每一行)的第一个非零元在b.data中应存储的位置(称为该列的起始位置),那么在对a.data中的三元组依次做转置时,便可以直接放到b.data中正确的位置。
为了确定矩阵A中的第j列在矩阵B中的起始位置,需要先求得矩阵A中的第j列之前所有非零元的个数。矩阵A中第j列的起始位置等于第j-1列的起始位置加上第j-1列的非零元个数。
为此,需要设置两个一维数组num和cpos。num[j]用来统计矩阵A的a.data中第j列非零元素的个数。cpos[j]表示矩阵A的a.data中第j列的第一个非零元在b.data中的起始位置。显然有:cpos[1]=1;cpos[j]=cpos[j-1]+num[j-1];
Status FastTransposeSMatrix(TSMatrix M,TSMatrix &T) {
int j,q,k,p;
int *num,*cops;
T.mu=M.nu;T.nu=M.mu;T.tu=M.tu;
if(T.tu!=0) {
T.data=(Triple*)malloc((T.tu+1)*sizeof(Triple));
num=(int*)malloc((M.nu+1)*sizeof(int));
//为num和cops分配空间
cops=(int*)malloc((M.nu+1)*sizeof(int));
if(NULL==T.data||NULL==num||NULL==cops) return OVERFLOW;
for(j=1;j<=M.nu;++j) num[j]=0//初始化num
for(k=1;k<=M.tu;++k) //求M中每一列所含非零元的个数
++num[M.data[k].j];
cops[1]=1;
for(j=2;j<=M.nu;++j)//求每一列的第一个非零元在b.data中的序号
cops[j]=cops[j-1]+num[j-1];
for(p=1;p<=M.tu;++p) {//转置矩阵元素
j=M.data[p].j;
q=cpos[j];
T.data[q].i=M.data[p].j;
T.data[q].j=M.data[p].i;
T.data[q].e=M.data[p].e;
++cpos[j];//更新为第j列下一个非零元的位置
}
}
free(num);free(cpos);
return OK;
}
5 链栈
采用链式存储结构的栈为链栈,设栈S=(a1,a2,…an),其中a1为栈底元素,an为栈顶元素,栈顶指针指向an,当栈为空时,栈顶指针为NULL。
5.1.类型定义
typedef struct LSNode {
ElemType data;//数据域
struct LSNode *next;//指针域
}LSNode,*LStack;//结点和链栈类型
5.2.常用操作
1.接口
void InitStack_LS(LStack &s);//初始化链栈S
void DestroyStack_LS(LStack &S);//销毁链栈S
Status StackEmpty_LS(LStack S);//判空
Status Push_LS(LStack &S,ElemType e);//元素e压入栈
Status Pop_LS(LStack &S,ElemType &e);//栈S的栈顶元素出栈,并用e返回
Status GetTop_LS(LStack S,ElemType &e);//取栈S的栈顶元素,并用e返回
2.入栈操作
Status Push_LS(LStack &S,ElemType e){
LSNode *t;
t=(LSNode*)malloc(sizeof(LSNode));//为元素e分配结点空间
if(NULL==t) return OVERFLOW;
t->data=e;
t->next=S;S=t;//在栈顶位置插入新结点
return OK;
}
3.出栈
Status Pop_LS(LStack &S,ElemType &e) {
LSNode *t;
if(NULL==S) return ERROR;//判断是否为空栈
t=S;//用t指向栈顶元素结点
e=S->data;
S=S->next;//删除栈顶元素结点
free(t);//释放结点
return OK;
}
6 链队列
采用链式存储结构的队列称为链队列。
6.1.类型定义
typedef struct LQNode {
ElemType data;
struct LQNode *next;
}LQNode,*QueuePtr;
typedef struct {
QueuePtr front;//队头指针
QueuePtr rear;//队尾指针
}LQueue;//链队列
6.2.常用操作
1.接口
void InitQueue_LQ(LQueue &Q);//构造一个空队列Q
void DestroyQueue_LQ(LQueue &Q);//销毁队列Q
Status QueueEmpty_LQ(LQueue Q);//判空
int QueueLength_LQ(Lqueue Q);//返回Q中元素个数,即队列的长度
Status GetHead_LQ(LQueue Q,ElemType &e);//若队列不空,则返回队头元素
Status EnQueue_LQ(LQueue &Q,ElemType e);//在队列Q的队尾插入元素e
Status DeQueue_LQ(LQueue &Q,ElemType &e);//若Q非空,删除队头元素
2.入队
Status EnQueue_LQ(LQueue &Q,ElemType e) {//队尾插入元素
LQNode *p;
p=(LQNode*)malloc(sizeof(LQNode));
if(NULL==p) return OVERFLOW;
p->data=e;p->next=NULL;
if(NULL==Q.front) Q.front=p;//e插入空队列,可将判断条件改为NULL==Q.rear; 而Q.rear==Q.front的情况为空或者只有一个
else Q.rear->next=p;//p插入非空队列
Q.rear=p;//队尾指针指向新的队尾
return OK;
}
3.出队
Status DeQueue_LQ(LQueue &Q,ElemType &e) {
LQNode *p;
if(NULL==Q.front) return ERROR;//队列为空则返回ERROR
p=Q.front;//指向队头结点
e=p->data;
Q.front=p->next;
if(Q.rear==p) Q.rear=NULL;//队列只有一个结点,删去后队列为空
free(p);
return OK;
}
7 线性表的链式表示和实现
能快速在任意位置插入和删除元素的链式存储结构——链表。
7.1.单链表
7.1.1.类型定义
typedef struct LNode {
ElemType data;
struct LNode *next;
}LNode,*LinkList;
指向单链表的第一个结点的指针称为头指针,它唯一确定该单链表
单链表中存放的数据元素个数称为单链表的表长,如存放线性表的单链表表长为n,n=0时称为空表
存放单链表中第一个数据元素a1的结点称为首元结点,存放最后一个数据元素an的结点称为尾元结点
单链表有不带头结点和带头结点两种形式,头结点指的是在单链表的首元结点之前附设的一个结点,头结点的数据域可以不存储任何信息,也可存储一些附加信息,如线性表的长度等,头结点的指针域指向首元节点。当单链表为空表时,若不带头结点,其头指针L为NULL;若带头结点,其头结点的指针域为NULL
若无特殊说明,单链表通常带头结点。
7.1.2.常用操作
1.接口
Status InitList_L (LinkList &L); // 初始化一个只含头结点的空单链表L
Status DestroyList_L (LinkList &L); // 销毁单链表L
Status ClearList_L (LinkList &L); // 将单链表L置为空表
Status ListEmpty_L(LinkList L);//判空
int ListLength_L(LinkList L);//求单链表L的表长
LNode *Search_L(LinkList L,ElemType e);//查找,返回单链表L中第一个数据域值为e的结点地址
LNode *NextElem_L(LNode *p);//返回p结点的直接后继结点的指针,若p结点是尾元结点则返回NULL;
LNode* MakeNode_L(ElemType e); // 构造数据域为e的单链表结点
Status InsertAfter_L(LNode *p, LNode *q) ; // 在p结点之后插入q结点
Status DeleteAfter_L(LNode *p, ElemType &e); //删除p结点的直接后继结点,用e返回结点值,若L为空表或p为尾元结点则操作失败
Status CreatList_L(LinkList &L, int n, ElemType *A);// 建立一个长度为n的单链表,数组A[]提供n个元素值
Status CreatList_LH(LinkList &L, int n, ElemType *A);//头插入
Status CreatList_LN(LinkList &L, int n, ElemType *A);//无头结点的头插入
void ListTraverse_L(LinkList L,Status (*visit)(ElemType e));//遍历单链表L
2.初始化操作
构造一个只含头结点的空单链表
Status InitList_L (LinkList &L) {
L=(LNode*)malloc(sizeof(LNode));
if(NULL==L) return OVERFLOW;
L->next=NULL;//头结点的next域为空
return OK;
}
3.查找元素
LNode *Search_L(LinkList L,ElemType e) {
LNode *p;
if(NULL==L) return NULL;//L不存在
p=L->next;//令p指向首元结点
while(p!=NULL && p->data!=e) p=p->next;
return p;//若e存在,则p->data为e,否则p为NULL
}
4.求直接后继
LNode *NextElem_L(LNode *p) {//返回p结点的直接后继结点的指针,若p结点是尾元结点则返回NULL;
if(NULL==p) return NULL;
return p->next;
}
5.构造节点
LNode* MakeNode_L(ElemType e) {
LNode *p;
p=(LNode*)malloc(sizeof(LNode));
if(p!=NULL){
p->data=e;
p->next=NULL;
}
return p;
}
6.插入直接后继节点操作
Status InsertAfter_L(LNode *p, LNode *q) {// 在p结点之后插入q结点
if(NULL==p || NULL==q) return ERROR;//参数不合理
q->next=p->next;
p->next=q;
return OK;
}
7.删除直接后继结点操作
Status DeleteAfter_L(LNode *p, ElemType &e) {//删除p结点的直接后继结点,用e返回结点值,若L为空表或p为尾元结点则操作失败
LNode *q;
if(NULL==p || NULL==p->next) return ERROR;//删除位置不合理,且两个条件不能互换
q=p->next;
p->next=q->next;
e=q->data;
free(q);
return OK;
}
8.建立单链表
尾插入
Status CreatList_L(LinkList &L, int n, ElemType *A) {
// 建立一个长度为n的单链表,数组A[]提供n个元素值
LNode *p,*q;
int i;
if(OVERFLOW==InitList_L(L)) return OVERFLOW;
p=L;
for(i=0;i<n;i++) {
q=MakeNode_L(A[i]);
InsertAfter_L(p,q);
p=q;//令p指向当前的尾元结点
}
return OK;
}
头插入
A=[1,2,3,4,5],单链表的顺序也应该是[1,2,3,4,5]
Status CreatList_L(LinkList &LH, int n, ElemType *A) {
LNode *p;
int i;
if(OVERFLOW==InitList_L(L)) return OVERFLOW;
p=L;
for(i=n-1;i>0;i--) {
q=MakeNode_L(A[i]);
InsertAfter_L(L,q);
}
return OK;
}
不带头结点的头插入
Status CreatList_LN(LinkList &L, int n, ElemType *A) {
LNode *q;
int i;
L = NULL;//赋初值
for(i=n-1; i>=0; i--) {
q = MakeNode_L(A[i]);
q->next = L;//新节点的next指向以前的第一个
L = q;
}
}
9.单链表的逆转
将头结点与首元结点断开,作为逆置后单链表的头结点,依次将a1,a2,…an结点插入到头结点之后的位置,最后插入的结点在最前面
void InverseList(LinkList L) {//对带头结点的单链表L实现逆置
LNode *p,*q;
if(NULL==L->next||NULL==L->next->next) return;
p=L->next;//指针p始终指向待插入结点
L->next=NULL;//将头结点与第一个结点断开
while(p!=NULL){//将第一个到最后一个结点依次插入到头结点后面
q=p->next;//指针q始终指向待插入节点的后继
InsertAfter_L(L,p);//将待插入结点插入到头结点后面
p=q;//指针p指向下一个待插入结点
}
}
7.1.3.有序单链表的合并
与两个有序顺序表的合并不同,有序单链表合并可不额外分配空间,每个结点都使用原来的空间,只需要修改其指针域即可。
void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc) {
LinkList pa,pb,pc,temp;
pa=La->next;pb=Lb->next;
Lc=pc=La;//使用La的头结点作为Lc的头结点
while(pa&&pb)//依次处理La和Lb当前结点
if(pa->data<=pb->data){//将pa结点插入到Lc
temp=pa->next;//令temp指向La中下一个待处理的结点
InsertAfter_L(pc,pa);//将pa结点插入至Lc的表尾
pc=pa;//令pc指向pa结点
pa=temp;//令pa指向La中下一个待处理的结点
} else {//将pb结点插入到Lc
temp=pb->next;
InsertAfter_L(pc,pb);
pc=pb;
pb=temp;
}
pc->next=pa?pa:pb;//插入La或Lb中的剩余段
free(Lb);//释放Lb的头结点
}
7.2.双向链表
如果需要经常访问结点的前驱,则可在结点中增加指向直接前驱的指针域。这样的链表称为双向链表,可在其接口定义中增加插入直接前驱和删除当前结点的操作,时间复杂度均为O(1)。空的双向链表只包含头结点,其两个指针域值都为NULL。
7.2.1.类型定义
typedef struct DuLNode {
ElemType data;
struct DuLNode *prior,*next;//分别指向直接前驱和直接后继
}DuLNode,*DuLinkList;
7.2.2.常用操作
1.插入前驱结点
Status InsertBefore_DuL(DuLNode *p,DuLNode *q){//在p结点前插入q结点
if(NULL==p||NULL==q||NULL==p->prior) return ERROR;
//p指向头结点时不能在之前插入
q->prior=p->prior;
q->next=p;
q->prior->next=q;
p->prior=q;
return OK;
}
2.删除结点
若p结点为头结点,由于不能进行删除头结点的操作,则返回ERROR;
Status Delete_DuL(DuLNode *p,ElemType &e) {//删除并释放p结点
if(NULL==p||NULL==p->prior) return ERROR;
if(p->next!=NULL) p->next->prior=p->prior;
p->prior->next=p->next;
e=p->data;
free(p);
return OK;
}
7.3.循环链表
7.3.1.单循环链表
特点是尾元结点的指针域指向头结点,从而使链表首尾相连构成一个环,可以从任一结点出发沿后继指针链遍历整个链表。
1.类型定义
typedef LinkList CirLinkList;
2.初始化
Status InitList_CL(CirLinkList &L){//初始化空的单循环链表L
L=(LNode*)malloc(sizeof(LNode));
if(NULL==L) return OVERFLOW;
L->next=L;//头结点的next域指向自己
return OK;
}
3.删除后继
Status DeleteAfter_CL(CirLinkList L,LNode*p,ElemType &e) {
//删除单循环链表L中p结点的直接后继结点并用参数e返回被删结点的值
LNode *q;
if(L==L->next) return ERROR;//空表无法删除结点
if(p->next==L) p=L;//若p为尾元结点,令p指向头结点
q=p->next;
p->next=q->next;
e=q->data;
free(q);
return OK;
}
7.3.2.双向循环链表
令尾元结点的next指针域指向头结点,头结点的prior指针域指向尾结点
一个空的双向循环链表只包含一个头结点,头结点的next域和prior域均指向头结点本身
7.4.线性表两种存储结构的比较
链表的优点:在链表的任意位置插入或删除元素只需修改相应指针,不需要移动元素。按需动态分配,不需要按最大需求预先分配一块连续空间。
除结点
若p结点为头结点,由于不能进行删除头结点的操作,则返回ERROR;
Status Delete_DuL(DuLNode *p,ElemType &e) {//删除并释放p结点
if(NULL==p||NULL==p->prior) return ERROR;
if(p->next!=NULL) p->next->prior=p->prior;
p->prior->next=p->next;
e=p->data;
free(p);
return OK;
}
7.3.循环链表
7.3.1.单循环链表
特点是尾元结点的指针域指向头结点,从而使链表首尾相连构成一个环,可以从任一结点出发沿后继指针链遍历整个链表。
1.类型定义
typedef LinkList CirLinkList;
2.初始化
Status InitList_CL(CirLinkList &L){//初始化空的单循环链表L
L=(LNode*)malloc(sizeof(LNode));
if(NULL==L) return OVERFLOW;
L->next=L;//头结点的next域指向自己
return OK;
}
3.删除后继
Status DeleteAfter_CL(CirLinkList L,LNode*p,ElemType &e) {
//删除单循环链表L中p结点的直接后继结点并用参数e返回被删结点的值
LNode *q;
if(L==L->next) return ERROR;//空表无法删除结点
if(p->next==L) p=L;//若p为尾元结点,令p指向头结点
q=p->next;
p->next=q->next;
e=q->data;
free(q);
return OK;
}
7.3.2.双向循环链表
令尾元结点的next指针域指向头结点,头结点的prior指针域指向尾结点
一个空的双向循环链表只包含一个头结点,头结点的next域和prior域均指向头结点本身
7.4.线性表两种存储结构的比较
链表的优点:在链表的任意位置插入或删除元素只需修改相应指针,不需要移动元素。按需动态分配,不需要按最大需求预先分配一块连续空间。
栈表:随机存取