2.线性数据结构

1 典型线性数据结构

只允许在序列末端进行操作,这种线性结构称为

只允许在序列两端进行操作,这种线性结构称为队列

允许在序列任意位置进行操作,这种线性结构称为线性表

1.1 线性结构的逻辑描述

1.栈

  1. 最后一个元素所在端称为栈顶,第一个元素所在端称为栈底
  2. 不含任何元素的栈称为空栈
  3. 后进先出

2.队列

  1. 允许插入的一端称为队尾,允许删除的一端称为队头
  2. 不含任何元素的队列称为空队列
  3. 先进先出

3.线性表

  1. 线性表中所含数据元素的个数称为线性表的长度
  2. 长度为0的线性表称为空表
  3. 按元素值非递减或非递增排列的线性表称为有序线性表,简称有序表

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.线性表两种存储结构的比较

链表的优点:在链表的任意位置插入或删除元素只需修改相应指针,不需要移动元素。按需动态分配,不需要按最大需求预先分配一块连续空间。

栈表:随机存取

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值