第二章 线 性 表(顺序表、单链表、静态链表、循环链表、双向链表)

本文详细介绍了线性表的概念、抽象数据类型定义、顺序表及链表等存储结构,并提供了多种基本操作的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 线性表:是由n(n≥0)个数据元素组成的有限序列(数据元素的非空有限集合)

若将线性表记为(a1,...,ai-1,ai,ai+1,...,an),则表中ai-1领先于aiai领先于ai+1,ai-1ai的直接前驱元素,ai+1ai的直接后继。当i=1,2,...,n-1时,ai有且仅有一个直接后继,当i=2,3,...,n时,ai有且仅有一个直接前驱。

线性表元素的个数nn>=0)定义为线性表的长度,当n=0时,称为空表。

在较复杂的线性表中,一个数据元素可以有若干个数据项组成。

2. 抽象数据类型线性表的定义:

ADT {
    数据对象:D = {ai|ai∈Element,i = 1,2,3,...,n, n>=0};
    数据关系:R = {<ai-1,ai>| ai-1,ai∈D,i = 2,3,...,n};
    基本操作:
        InitList(&L);//构造一个空的线性表
        DestroyList(&L);//L已存在;销毁线性表L;
        ClearList(&L);//L已存在;把L重置为空表;
        ListEmpty(L);//若L为空表,返回TRUE;否则返回FALSE;
        ListLength(L);//返回L中元素的个数;
        GetElem(L,i,&e);//返回第i个元素赋给e;
        LocateElem(L,e,compare());//返回L中第一个与e满足函数compare()的元素的位序;
        PriorElem(L,cur_e,&pre_e);//返回cur_e的前驱元素赋给pre_e;
        NextElement(L,cur_e,&next_e);//返回cur_e的后继元素赋给next_e;
        ListInsert(&L,i,e);//在L中第i个位置插入元素e;L长度加1;
        ListDelete(&L,i,e);//删除L中第i个元素,并用e返回其值;L长度减1;
        ListTraverse(L,visit());//依次对L中每个元素调用函数visit();
} ADT List
<span style="font-family:'宋体';font-size: 10.5pt;"></span><span style="font-family:'宋体';font-size: 10.5pt;">
</span>

2.0 求两个线性表LaLb并集

//扩大La,将存在于Lb中且不存在与LA中的元素依次插入LA中(取LB中元素在LA中查访)
void union(List &La, List Lb) {
    La_len = ListLength(La);
    Lb_len = ListLength(Lb);
    for(int i = 1; i<=Lb_len; i++) {
        GetElem(Lb, i, e);
        if(!LocateElem(La, e, equal)) ListInsert(La, ++La_len, e);
    }
} //union

2.1 合并两个数据元素按值非递减排列的线性表LaLb,输出Lc

void MergeList (List La, List Lb, List &Lc) {
    InitList(Lc);
    int i = j = k = 0;
    La_len = ListLength(La);
    Lb_len = ListLength(Lb);
    while((i<=La_len)&&(j<=Lb_len)) {
        GetElem(La, i, ai);
        GetElem(Lb, j, bi);
        if(ai < bi) { ListInsert(Lc, ++k, ai); ++i; }
        else {ListInsert(Lc, ++k, bi); ++j;}
    }
    while(i<=La_len) {  GetElem(La, i++, ai);   ListInsert(Lc, ++k, ai); }
    while(i<=Lb_len) {  GetElem(Lb, j++, bi);	ListInsert(Lc, ++k, bi); }
} //MergeList

 

顺序表(线性表的顺序存储结构)

1、线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

2、存储器中每个单元都有自己的编号,这个编号称为地址。

3、顺序表的三个属性:

Ø 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。

Ø 线性表的最大存储容量:数组长度MaxSize。

Ø 线性表的当前长度:length.

//顺序表:顺序存储结构的线性表(动态分配的一维数组)
typedef struct {
     ElemType data[MAXSIZE];
     int      length;
}SqList;
Status Init_SqList(SqList *L);//初始化
ElemType GetElem_SqList(SqList L, int i) ;//返回元素
Status Insert_SqList(SqList *L, int i, ElemType e);//插入元素
ElemType Delete_SqList(SqList *L, int i);//删除指定位置元素并返回值
int Search_SqList(SqList L, ElemType e, Status(*compare)(ElemType, ElemType));//查找并返回符合比较条件的元素位置,否则返回-1
void Merge_SqList(SqList La, SqList Lb, SqList *Lc);//合并两个顺序表

4、顺序表的存取数据时间复杂度为O(1),插入删除元素时间复杂度为O(n)。

5、顺序存储结构的优缺点:

Ø 优点:1、无须为表示表中元素之间的逻辑关系而增加额外的存储空间。

     2、可以快速低存取表中任一位置的元素。

Ø 缺点:1、插入和删除操作需要移动大量元素。

     2、当线性表长度变化较大时,难以确定存储空间的容量

 3、造成存储空间的“碎片”

6、顺序表上的基本运算(初始化、插入、删除、查找、合并)

//初始化
Status Init_SqList(SqList *L) {
//     L->data = (ElemType*)malloc(MAXSIZE*sizeof(ElemType));
//     if(!L->data)   exit(OVERFLOW);
     L->length = 0;
     return OK;
}

//返回元素
ElemType GetElem_SqList(SqList L, int i) {
    if(i>=0 && i<L.length)   return L.data[i];
    return -1;  //i号元素不存在
}
//插入元素
Status Insert_SqList(SqList *L, int i, ElemType e) {
     if(L->length == MAXSIZE || i<0 || i>L->length) return ERROR;//顺序表存储空间已满,或指定插入位置无效
     int j;
     for(j=L->length; j>i; j--)
        L->data[j] = L->data[j-1];  //从i到length-1号元素后移
     L->data[j] = e;
     ++L->length;
     return OK;
}

//删除指定位置元素并返回值
ElemType Delete_SqList(SqList *L, int i) {
     if(i<0 || i>L->length-1) return ERROR;
     ElemType e = L->data[i];
     int j;
     for(j=i; j<L->length-1; j++)
        L->data[j] = L->data[j+1];  //从i+1到length-1号元素前移
     --L->length;
     return e;
}

//查找并返回符合比较条件的元素位置,否则返回-1
int Search_SqList(SqList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
     int i = 0;
     ElemType* p = L.data;
     while(i<L.length && !(*compare)(*p++, e)) ++i;
     if(i<L.length) return i;
     else return -1;
}

//合并两个顺序表,并按增序排列
void Merge_SqList(SqList La, SqList Lb, SqList *Lc) {
     ElemType* pa = La.data;    //pa指向La第一个元素
     ElemType* pb = Lb.data;
     int len = La.length + Lb.length;
     Lc->length = len < MAXSIZE ? len : MAXSIZE;
     ElemType* pc = Lc->data;
     ElemType* pa_last = La.data + La.length-1;     //pa_last指向La最后一个元素
     ElemType* pb_last = Lb.data + Lb.length-1;
     ElemType* pc_last = Lc->data + Lc->length-1;
     while(pa<=pa_last && pb<=pb_last && pc<=pc_last) {
          if(*pa<=*pb) *pc++ = *pa++;
          else *pc++ = *pb++;
     }
     while(pa<=pa_last && pc<=pc_last) *pc++ = *pa++;   //Lb中元素已全部添加到Lc
     while(pb<=pb_last && pc<=pc_last) *pc++ = *pb++;   //La中元素已全部添加到Lc
}

 

单链表

1、链表:为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。

我们把存储数据元素的信息域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像,成为结点(Node)。

n个结点(ai的存储映像)链结成一个链表,即为线性表(a1a2...an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫单链表。

//线性表的单链表存储结构(线性链表)
typedef struct LNode {
     ElemType          data;
     struct LNode     *next;
}LNode, *LinkList;
void Creat_LinkList(LinkList *L, int n);//创建以L为指向头结点指针的单链表
int Length_LinkList(LinkList L);
ElemType GetElem_LinkList(LinkList L, int i);//返回指定位置元素
Status Insert_LinkList(LinkList *L, int i, ElemType e);//插入元素
Status Delete_LinkList(LinkList *L, int i);//删除元素
Status Clear_LinkList(LinkList *L);//单链表的整表删除


2、头指针:链表中第一个结点的存储位置叫做头指针。

Ø 头指针是指链表指向第一个结点的指针,若连标有头结点,则是指向头结点的指针。

Ø 头指针具有标识作用,所以常以头指针冠以链表的名字。

Ø 无论链表是否为空,头指针均不为空,头指针是链表的必要元素。

3、头结点:有时为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,成为头结点。其数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息;指针域为指向第一个结点的指针。

Ø 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义。

Ø 有了头结点,队在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作就统一了。

Ø 头结点不一定是链表必须要素。

4、单链表的存取数据时间复杂度为O(1),插入删除元素时间复杂度为O(n)。

//创建以L为指向头结点指针的单链表,尾插法
void Creat_LinkList(LinkList *L, int n) {
     int i;
     LinkList p, q;
     *L = (LinkList)malloc(sizeof(LNode));  //先建立一个带头结点的单链表,即初始化头结点
     (*L)->next = NULL;    //数据域无效,指针域为空
     if(!(*L)) exit(OVERFLOW);
     p = *L;    //p为指向尾部的结点
     for(i=0; i<n; i++) {
          q = (LinkList)malloc(sizeof(LNode));  //生成新结点
          scanf("%d", &q->data);
          p->next = q;      //将表尾终端结点的指针指向新结点
          p = q;            //将当前的新结点定义为表尾终端结点
     }
     p->next = NULL;        //表示当前链表结束,很重要
}

//返回链表长度
int Length_LinkList(LinkList L) {
    int len = 0;
    LinkList p = L->next;
    while(p) {
        ++len;
        p = p->next;
    }
    return len;
}

//返回指定位置元素
ElemType GetElem_LinkList(LinkList L, int i) {
     LinkList p = L->next;
     int j = 0;
     while(p&&j<i) {        //寻找第i个结点
          p = p->next;
          ++j;
     }
     if(p && j<=i)  return p->data;
     return ERROR;
}

//插入元素
Status Insert_LinkList(LinkList *L, int i, ElemType e) {
     int j = 0;
     LinkList p, q;
     p = *L;
     while(p && j<i) {         //寻找第i-1个结点
          p = p->next;
          ++j;
     }
     if(!p || j>i)  return ERROR;           //第i个结点不存在
     q = (LinkList)malloc(sizeof(LNode));   //生成新结点
     q->data = e;
     q->next = p->next;
     p->next = q;
     return OK;
}

//删除并返回元素
ElemType Delete_LinkList(LinkList *L, int i) {
    int j = 0;
    LinkList p, q;
    ElemType e;
    p = *L;
    while(p->next && j<i) { //遍历寻找第i-1个结点
        p = p->next;
        ++j;
    }
    if(p->next && j<=i) {   //第i个结点不存在
        q = p->next;
        p->next = q->next;
        e = q->data;
        free(q);
        return e;
    }
    return ERROR;
}

//将L重置为空表,不删除头结点
Status Clear_LinkList(LinkList *L) {
    LinkList p, q;
    p = (*L)->next;     //p指向第一个结点
    while(p) {          //没到表尾
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;  //头结点指针域为空
    return OK;
}


静态链表

1、用数组描述的链表叫做静态链表(游标实现法)。

对于一些没有指针的语言(如BasicFortran等早期的高级编程语言),无法实现单链表结构。

2、让数组的元素由两个数据域组成,即datacur

data:用来存放数据元素。

cur:相当于单链表中的next指针,存放该元素的后继在数组中的下标(cur称为游标)。

//线性表的静态单链表存储结构
typedef struct{
     ElemType   data;   //游标(指示器cur)代替指针指示节点在数组中的相对位置
     int        cur;    //数组的第零分量可看成头结点,其指针域指示链表的第一个结点
}component, StaticLinkList[MAXSIZE];
//数组第一个元素的cur存放备用链表第一个结点的下标;最后一个元素的cur存放链表第一个元素的下标,相当于头结点
//即备用链表以0为头结点,MAXSIZE-1为尾结点;链表以MAZSIZE-1为头结点,以0为尾结点
void Init_StaticLinkList(StaticLinkList space);         //初始化
int Length_StaticLinkList(StaticLinkList L);            //返回元素个数
ElemType GetElem_StaticLinkList(StaticLinkList L, int i);   //返回指定元素
Status Insert_StaticLinkList(StaticLinkList L, int i, ElemType e); //插入元素
ElemType Delete_StaticLinkList(StaticLinkList L, int i);   //删除元素
int Malloc_StaticLinkList(StaticLinkList space);        //分配存储空间
void Free_StaticLinkList(StaticLinkList space, int k);  //释放内存

//将一维数组space中各分量链成一个备用链表,space[0].cur为头指针, "0"表示空指针
void Init_StaticLinkList(StaticLinkList space) {
    int i;
    for(i=0; i<MAXSIZE-1; i++) space[i].cur = i+1;
    space[MAXSIZE-1].cur = 0;
}

//返回静态链表的长度(即元素个数)
int Length_StaticLinkList(StaticLinkList L) {
    int len = 0;
    int i = L[MAXSIZE-1].cur;   //MAXSIZE-1为头结点,i为头指针
    while(i) {
        i = L[i].cur;
        ++len;
    }
    return len;
}

//返回指定元素
ElemType GetElem_StaticLinkList(StaticLinkList L, int i) {
    int j = 0;
    int p = L[MAXSIZE-1].cur;
    while(p && j<i) {       //寻找第i个元素
        p = L[p].cur;
        ++j;
    }
    if(p && j<=i) return L[p].data;
    return ERROR;
}
//插入元素
Status Insert_StaticLinkList(StaticLinkList L, int i, ElemType e) {
    int j, k, p;
    j = 0;
    p = MAXSIZE-1;
    while(p && j<i) {       //寻找第i-1号结点
        p = L[p].cur;
        ++j;
    }
    if(!p || j>i) return ERROR;
    k = Malloc_StaticLinkList(L);   //分配存储空间
    L[k].data = e;
    L[k].cur = L[p].cur;
    L[p].cur = k;
    return OK;
}

//删除并返回元素
ElemType Delete_StaticLinkList(StaticLinkList L, int i) {
    int j, p, k;
    ElemType e;
    j = 0;
    p = MAXSIZE-1;
    while(L[p].cur && j<i) {        //寻找第i号元素
        p = L[p].cur;
        ++j;
    }
    if(L[p].cur && j<=i) {
        k = L[p].cur;
        L[p].cur = L[k].cur;
        e = L[k].data;
        Free_StaticLinkList(L, k);  //释放内存
        return e;
    }
    return ERROR;
}
//若备用链表非空,则返回分配的结点下标,否则返回0;
int Malloc_StaticLinkList(StaticLinkList space) {
     int i = space[0].cur;  //当前数组第一个元素cur存的值
     if(space[0].cur)       //space[0].cur == 0则表明备用链表为空
        space[0].cur = space[i].cur;
     return i;              //返回第一个备用存储空间的下标
}

//将下标为k的空闲结点回收到备用链表
void Free_StaticLinkList(StaticLinkList space, int k) {
     space[k].cur = space[0].cur;
     space[0].cur = k;
}

循 环 链 表

1、将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表(circular linked)。

2、空循环链表仅由一个自成循环的头结点表示。

双 向 链 表

1、双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值