数据结构笔记①——线性表

线性表

线性表的顺序表示和实现

//线性表格式
#define LIST_INIT_SIZE 100
typedef struct{
    ElemType elem[LIST_INIT_SIZE]  //复合型(用结构体定义的类型)用该类型的指针动态分配数组
    int length;
}Sqlist;
Sqlist L;
L.data=(ElemType*)malloc(sizeof(ElemType)*MaxSize);

//注意!逻辑位序与物理位序相差1

//多项式线性表
#define MAXSIZE 1000  //多项式可能达到的最大长度

typedef struct{       //多项式非零项的定义
    float p;          //系数
    int e;            //指数
}Polynomial;

typedef struct{
    Polynomial *elem; //存储空间的基地址
    int length;       //多项式当前项的个数
}Sqlist;              //多项式的顺序存储结构类型为Sqlist

//图书表
#define MAXSIZE 10000

typedef struct{
    char no[20];
    char name[50];
    float price;
}Book;

typedef struct{
    Book *elem;
    int length;
}Sqlist; 

//函数结果状态代码
#define TRUE 1
#define FALSE 0
#define OK 1
#define INFEASIBLE -1
#define OVERFLOW -2

//Status 是函数的类型,其值是函数结果状态代码
typedef int Status;
typedef char ElemType;

顺序表基本操作

顺序表:逻辑结构与存储结构一致,可快速计算任何一个数据元素的存储位置,因此可粗略认为访问每个元素所花时间相等,这种存取元素的方法称为随机存取法。

没有占用辅助空间,空间复杂度S(n)=O(1)

优点

1 存储密度大(结点本身所占存储量/结点结构所占存储量=1)

2 可以随机存取表中任一元素,O(1)

缺点

1 在插入、删除某一元素时,需要移动大量元素

2 浪费存储空间&静态存储形式,数据元素个数不能自由扩充

#define OK 1
#define OVERFLOW -2
#define ElemType xxx  //依据需要填入
#define ERROR -1

typedef struct {
    ElemType elem[LIST_INIT_SIZE];
    int length;
} Sqlist;
//初始化线性表
Status InitList_Sq(Sqlist &L) {
    L.elem = new ElemType[MAXSIZE];
    if(!L.elem) exit(OVERFLOW);
    L.length = 0;
    return OK;
}

//销毁线性表
void DestroyList(Sqlist &L) {
    if(L.elem) delete L.elem;
}

//清空线性表
void ClearList(Sqlist &L) {
    L.length = 0;
}

//求线性表的长度
int GetLength(Sqlist L) {
    return (L.length);
}

//判断线性表是否为空
int IsEmpty(Sqlist L) {
    if(L.length == 0) return 1;
    else return 0;
}

//顺序表的取值(根据位置i获取相应位置数据元素的内容)随机存取,O(1)
int GetElem(Sqlist L,int i,ElemType &e) {
    if(i<1|| i>L.length) return ERROR;    //判断i值是否合理
    e = L.elem[i-1];    //第i-1个单元存储第i个数据
}

//顺序表的查找,平均时间复杂度为O(n)
int LocateELem(Sqlist L,ElemType e) {
//在线性表中查找值为e的数据元素,返回其序号(是第几个元素)
    //使用for循环
    for(i=0; i<L.length; i++)
        if(L.elem[i] == e) return i+1;//查找成功,返回序号
    return 0;//查找失败,返回0
    //使用while循环
    i=0;
    while(i<L.length && L.elem[i]!=e) i++;
    if(i<L.length) return i+1;
    return 0;
}

//顺序表的插入,平均时间复杂度为O(n),插入位置+移动次数=n+1,插入位置有n+1种可能
Status ListInsert_Sq(SqList &L,int i,ElemType e) {
    if(i<1||i>L.length+1) return ERROR; //i值不合法,插入位置可以从第1个位置到第n+1个位置,下标对应0~n
    if(L.length==MAXSIZE) return ERROR; //当前存储空间已满
    for(j=L.length-1; j>=i-1; j--)
        L.elem[j+1]=L.elem[j];    //插入位置及之后的元素后移
    L.elem[i-1]=e;                //将新元素e放入第i个位置
    L.length++;                   //表长增加1
    return OK;
}

//顺序表的删除,平均时间复杂度为O(n),删除位置+移动次数=n,删除位置有n种可能
Status ListDelete_Sq(Sqlist &L,int i) {
    if((i<1)||(i>L.length)) return ERROR; //i值不合法,删除位置只能是1~n
    for(j=i; j<=L.length-1; j++)
        L.elem[j-1]=L.elem[j];
    L.length--;
    return OK;
}

线性表的链式表示和实现

单链表

typedef struct LNode { //声明结点的类型和指向结点的指针类型
    ElemType data;       //结点的数据域,多个数据项则预先定义为一个结构类型ElemType
    struct LNode *next;  //结点的指针域
} LNode,*LinkList;   //LinkList为指向结构体Lnode的指针类型
//通常 定义链表L:LinkList L; 定义结点指针p:LNode *p;

//单链表的初始化(带头结点的单链表):生成新结点作头结点,用头指针L指向头结点,头结点指针域置空
Status InitList_L(LinkList &L) {
    L=new LNode; //或L=(LinkList)malloc(sizeof(LNode));new获得的是指向新结点的指针,把新结点的地址赋值给L
    L->next=NULL;
    return OK;
}

//判断链表是否为空(无元素,头指针和头结点仍然在)即判断头结点指针域是否为空
int ListEmpty(LinkList L) { //若L为空表,则返回1,否则返回0
    if(L->next) //非空
        return 0;
    else
        return 1;
}

//单链表的销毁:链表销毁后不存在(头结点和头指针均不存在):从头指针开始,依次释放所有结点
Status DestroyList_L(LinkList &L) {
    LNode *p;
    while(L) { //判断头指针是否为空的简化写法,L!=NULL
        p=L;
        L=L->next;
        delete p;
    }
    return OK;
}

//清空单链表:从首元结点开始依次释放所有结点,并将头结点指针域设置为空
//(链表仍存在,但链表中无元素,成为空链表,头指针和头结点仍然在)
Status ClearList(LinkList &L) {
    LNode *p,*q; //或LinkList p,q;
    p=L->next;
    while(p) {     //没到表尾
        q=p->next;
        delete p;
        p=q;
    }
    L->next=NULL;  //头结点指针域
    return OK;
}

//求单链表的表长:从首元结点开始,依次计数所有结点
int ListLength_L(LinkList L) { //返回L中数据元素个数
    LinkList p;  //用LNode *p;可读性更好
    p=L->next;   //p指向第一个结点
    i=0;
    while(p) {   //遍历单链表,统计结点数
        i++;
        p=p->next;
    }
    return i;
}

//获取线性表L中的某个数据元素的内容,通过变量e返回(从第一个结点L->next顺链扫描,用指针p指向当前扫描到的结点)
Status GetElem_L(LinkList L,int i,ElemType &e) {
    p=L->next;
    j=1;  //初始化
    while(p&&j<i) { //向后扫描,直到p指向第i个元素或p为空
        p=p->next;
        ++j;
    }
    if(!p||j>i) return ERROR; //第i个元素不存在(元素位置超过长度p==NULL或是元素位置<1)
    e=p->data;  //取第i个元素
    return OK;
}

//单链表的查找(时间复杂度O(n))

//按值查找:根据指定数据获取该数据所在的位置(该数据的地址)
LNode *LocateElem_L(LinkList L,ElemType e) {
//在线性表L中查找值为e的数据元素,找到则返回其地址,失败则返回NULL
    p=L->next;
    while(p&&p->data!=e)
        p=p->next;
    return p;
}

//按值查找:根据指定数据获取该数据所在的位置序号(是第几个数据元素)
int LocateElem_L(LinkList L,ElemType e) {
//返回L中值为e的数据元素的位置序号,查找失败返回0
    p=L->next;
    j=1;
    while(p&&p->data!=e) {
        p=p->next;
        j++;
    }
    if(p) return j;
    else return 0;
}

//插入和删除时间复杂度O(1),但由于要从头查找前驱结点,所耗时间复杂度为O(n)

//单链表的插入:在第i个结点前插入值为e的新结点
Status ListInsert_L(LinkList &L,int i,ElemType e) {
    p=L;
    j=0;
    while(p&&j<i-1) { //寻找第i-1个结点
        p=p->next;
        ++j;
    }
    if(!p||j>i-1) return ERROR;  //i大于表长+1或者小于1,插入位置非法
    s=new LNode;
    s->data=e;  //生成新结点s,将结点s的数据域置为e
    s->next=p->next;  //将结点s插入L中
    p->next=s;
    return OK;
}

//单链表删除第i个结点:首先找到第i-1个结点的存储位置p(根据需要保存第i个结点的值),再令p->next指向第i+1个结点,释放第i个结点的空间
Status ListDelete_L(LinkList &L,int i,ElemType &e) {
    p=L;
    j=0;
    while(p->next&&j<i-1) {
        p=p->next;    //寻找第i个结点,并令p指向其前驱
        ++j;
    }
    if(!(p->next)||j>i-1) return ERROR;  //删除位置不合理
    q=p->next;  //临时保存被删结点的地址以备释放
    p->next=q->next;  //改变删除结点前驱结点的指针域
    e=q->data;  //保存删除结点的数据域
    delete p;  //释放删除结点的空间
    return OK;
}

//单链表的建立

//头插法 时间复杂度O(n) 原先头结点后的所有结点接在新结点后,再将新结点接在头结点后
void CreateList_H(LinkList &L,int n) {
    L=new LNode;
    L->next=NULL;  //先建立一个带头结点的单链表
    for(i=n; i>0; --i) {
        p=new LNode;
        cin>>p->data;
        p->next=L->next;  //插入到表头
        L->next=p;
    }
}

//尾插法 时间复杂度O(n) 尾指针r指向尾结点,新结点插入到尾结点后,r指向新结点
void CreateList_R(LinkList &L,int n) { //正位序输入n个元素的值,建立带表头结点的单链表L
    L=new LNode;
    L->next=NULL;
    r=L;  //尾指针r指向头结点
    for(i=0; i<n; ++i) {
        p=new LNode;
        cin>>p->data;  //生成新结点,输入元素值
        p->next=NULL;
        r->next=p;  //插入到表尾
        r=p;  //r指向新的尾结点
    }
}

循环链表

循环链表:是一种头尾相接的链表(即表中最后一个结点的指针域指向头结点,整个链表形成一个环)

优点:从表中任一结点出发均可找到表中其他结点

注意:由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p->next是否为空,而是判断它们是否等于头指针。p!=L 和 p->next!=L

尾指针表示单循环链表:a1的存储位置是:R->next->next an的存储位置是:R 时间复杂度均为O(1) 头指针表示则不够方便,找an的时间复杂度:O(n)

//带尾指针循环链表的合并(将Tb合并在Ta之后):p存表头结点;Tb表头连接到Ta表尾;释放Tb表头;修改指针
LinkList Connect(LinkList Ta,LinkList Tb){  //假设Ta、Tb都是非空的单循环链表
    p=Ta->next;  //1、p存表头结点
    Ta->next=Tb->next->next;  //2、Tb表头连接Ta表尾
    delete Tb->next;  //3、释放Tb表头结点
    Tb->next=p;  //修改指针
    return Tb;
}//时间复杂度为O(1)

双向链表

双向链表:在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,这样链表中就形成了有两个方向不同的链,故称为双向链表。

typedef struct DuLNode{
    Elemtype data;
    struct DuLNode *prior,*next;
}DuLNode,*DuLinkList;

//双向链表的插入
void ListInsert_DuL(DuLinkList &L,int i,ElemType e){  //在带头结点的双向循环链表L中第i个位置之前插入元素e
    if(!(p=GetElemP_DuL(L,i))) return ERROR;
    s=new DuLNode; s->data=e;
    s->prior=p->prior; p->prior->next=s;
    s->next=p; p->prior=s;
    return OK;
}

//双向链表的删除  //时间复杂度O(n)
void ListDelete_DuL(DuLinkList &L,int i,ElemType &e){  //删除带头结点的双向循环链表L的第i个元素,并用e返回
    if(!(p=GetElemP_DuL(L,i))) return ERROR;
    e=p->data;
    p->prior->next=p->next;
    p->next->prior=p->prior;
    delete p;
    return OK;
}

线性表的应用

线性表的合并

【问题描述】假设利用两个线性表La和Lb分别表示两个集合A和B,现要求一个新的集合A=A∪B。如:La=(7,5,3,11),Lb=(2,6,3)→La=(7,5,3,11,2,6)

【算法步骤】依次取出Lb中的每个元素,执行以下操作:

1 在La中查找该元素

2 如果找不到,则将其插入La的最后

算法的时间复杂度是:O(ListLength(La)*ListLength(Lb))

void union(List &La,List Lb){
    La_len=ListLength(La);
    Lb_len=ListLength(Lb);
    for(i=1;i<=Lb_len;i++){
        GetElem(Lb,i,e);
        if(!LocateElem(La,e)) ListInsert(&La,++La_len,e);
    }
}

有序表的合并

【问题描述】已知线性表La和Lb中的数据元素按值非递减有序排列,现要求将La和Lb归并为一个新的线性表Lc,且Lc中的数据元素仍按值非递减有序排列。如:La=(1,7,8) , Lb=(2,4,6,8,10,11) → Lc=(1,2,4,6,7,8,8,10,11)

【算法步骤】

1 创建一个空表Lc

2 依次从La或Lb中"摘取"元素值较小的结点插入到Lc表的最后,直至其中一个表变空为止

3 继续将La或Lb其中一个表的剩余结点插入在Lc表的最后

用顺序表实现

注意:先取值,后自增。因为顺序表中元素在物理上也相邻,可以使用指针++,在链表中则不可以。

算法的时间复杂度是: O(ListLength(La)+ListLength(Lb)) 因为合并后没有剔除相同元素(或理解成取最坏情况)

算法的空间复杂度是: O(ListLength(La)+ListLength(Lb)) 因为Lc中元素的个数是两表元素之和

void MergeList_Sq(SqList LA,SqList LB,SqList &LC){  //合并结果通过LC返回
    pa=LA.elem;
    pb=LB.elem;  //指针pa和pb的初值分别指向两个表的第一个元素
    LC.length=LA.length+LB.length;  //新表长度为待合并两表的长度之和
    LC.elem=new ElemType[LC.length];  //为合并后的新表分配一个数组空间
    pc=LC.elem;  //指针pc指向新表的第一个元素
    pa_last=LA.elem+LA.length-1;  //指针pa_last指向LA表的最后一个元素
    pb_last=LB.elem+LB.length-1;  //指针pb_last指向LB表的最后一个元素
    while(pa<=pa_laast && pb<=pb_last){  //两个表都非空
        if(*pa<=*pb) *pc++=*pa++;  //依次"摘取"两表中值较小的结点
        else *pc++=*pb++;
    }
    while(pa<=pa_last) *pc++=*pa++;  //LB表已到达表尾,将LA中剩余元素加入LC
    while(pb<=pb_last) *pc++=*pb++;  //LA表已到达表尾,将LB中剩余元素加入LC
}

用链表实现

用La的头结点作为Lc的头结点

注意:插入剩余段的操作 pc->next=pa?pa:pb; 相当于 if(pa) pc->next=pa; else pc->next=pb;

算法的时间复杂度是: O(ListLength(La)+ListLength(Lb)) 考虑最坏情况,La和Lb中元素依次轮流加到Lc中

算法的空间复杂度是: O(1) 因为直接在原先链表上利用指针进行操作(修改指针),无需额外空间

void MergeList_L(LinkList &La,LinkList &Lb,LinkList &Lc){
    pa=La->next; pb=Lb->next;
    pc=Lc=La;  //用La的头结点作为Lc的头结点
    while(pa && pb){
        if(pa->data=pb->data){
            pc->next=pa;
            pc=pa;
            pa=pa->next;
        }
        else{
            pc->next=pb;
            pc=pb;
            pb=pb->next;
        }
    }
    pc->next=pa?pa:pb;  //插入剩余段
    delete Lb;  //释放的头结点
}

案例分析与实现

一元多项式的运算:实现两个多项式加、减、乘运算

思路:将多项式的系数视为线性表 $P=((p_1,e_1),(p_2,e_2),···,(p_m,e_m))$ 每一项的指数 i 隐含在其系数 $p_i$ 的序号中

void PolyOperate(SqList &L1,SqList &L2,SqList &L3){
    for (int i=0;i<L1.length && i<L2.length;++i){
        L3.elem[i]=L1.elem[i]+L2.elem[i];
        L3.length+=1;
    }
    if (L1.length<=L2.length){
        for (int j=L1.length;j<L2.length;++j){
            L3.elem[j]=L2.elem[j];
            L3.length+=1;
        }
    }
    else{
        for (int j=L2.length;j<L1.length;++j){
            L3.elem[j]=L1.elem[j];
            L3.length+=1;
        }
    }
}

稀疏多项式的运算

顺序存储结构

思路:只存储系数不为0的项,避免浪费空间。每一项存储 系数a[i]指数 ,将其构成的序列记为线性表 $P=((p_1,e_1),(p_2,e_2),···,(p_m,e_m))$

【算法步骤】

1 创建一个新数组c

2 分别从头遍历比较a和b的每一项 { 指数相同,对应系数相加,若其和不为0,则在c中增加一个新项 × 指数不相同,则将指数较小的项复制到c中 }

3 一个多项式已遍历完毕时,将另一个剩余项依次复制到c中即可

缺点:存储空间分配不灵活,运算的空间复杂度高

链式存储结构

思路:将每一项视为链表中的一个结点,利用指针进行操作

typedef struct PNode{
    float coef;  //系数
    int expn;  //指数
    struct PNode *next;  //指针域
}PNode,*Polynomial;

【算法步骤】

1 创建一个只有头结点的空链表

2 根据多项式的项的个数n,循环n次执行以下操作

  • 生成一个新结点 *s
  • 输入多项式当前项的系数和指数赋给新结点 *s 的数据域
  • 设置一前驱指针pre,用于指向待找到的第一个大于输入项指数的结点的前驱,pre初值指向头结点
  • 指针q初始化,指向首元结点
  • 循链向下逐个比较链表中当前结点与输入项指数,找到第一个大于输入项指数的结点 *q
  • 将输入项结点 *s 插入到结点 *q 之前
void CreatePolyn(Polynomial &P,int n){  //输入m项的系数和指数,建立表示多项式的有序列表P
    P=new PNode;
    P->next=NULL;  //先建立一个带头结点的单链表
    for(i=1;i<=n;++i){  //依次输入n个非零项
        s=new PNode;  //生成新结点
        cin>>s->coef>>s->expn;  //输入系数和指数
        pre=P;  //pre用于保存q的前驱,初值为头结点
        q=P->next;  //q初始化,指向首元结点
        while(q && q->expn<s->expn){  //找到第一个大于输入项指数的项*q
            pre=q;
            q=q->next;
        }
        s->next=q;  //将输入项s插入到q和其前驱结点pre之间
        pre->next=s;
    }
}

3 多项式相加

  • 指针p1和p2初始化,分别指向Pa和Pb的首元结点

  • p3指向和多项式的当前结点,初值为Pa的头结点

  • 当指针p1和p2均未到达相应表尾时,则循环比较p1和p2所指结点对应的指数值 (p1->expn与p2->expn),有下列3种情况:

    • 当p1->expn == p2->expn时,则将两个结点中的系数相加 { 若和不为零,则修改p1所指结点的系数值,同时删除p2所指结点 × 若和为零,则删除p1和p2所指结点 }

    • 当p1->expn < p2->expn时,则应摘取p1所指结点插入到 “和多项式" 链表中去

    • 当p1->expn > p2->expn时,则应摘取p2所指结点插入到 “和多项式” 链表中去

  • 将非空多项式的剩余段插入到p3所指结点之后

  • 释放Pb的头结点

void SPO_II(LinkList &La,LinkList &Lb,LinkList &Lc){
    LNode *pa=La->next;
    LNode *pb=Lb->next;
    Lc=La;
    LNode *pc=Lc;
    while(pa && pb){
        if(pa->data.index<pb->data.index){
            pc->next=pa;
            pc=pc->next;
            pa=pa->next;
        }
        else if(pa->data.index>pb->data.index){
            pc->next=pb;
            pc=pc->next;
            pb=pb->next;
        }
        else if(pa->data.index==pb->data.index){
            pa->data.coef+=pb->data.coef;
            pc->next=pa;
            pc=pc->next;
            pa=pa->next;
            pb=pb->next;
        }
    }
    pc->next=(pa?pa:pb);
    delete Lb;
}

图书信息管理系统

struct Book{
    char id[20];  //ISBN
    char name[50];  //书名
    int price;  //定价
};

顺序表

typedef struct{
    Book *elem;
    int length;
}SqList;

链表

type struct LNode{
    Book data;
    struct LNode *next;
}LNode,*LinkList;