第 3 章 线性表

来源:互联网 发布:如何在淘宝上注册网店 编辑:程序博客网 时间:2024/05/21 04:39

零个或多个数据元素的有限序列。

3.1 开场白

3.2 线性表的定义

线性表(list):零个或多个数据元素的有限序列。
在较复杂的线性表中,一个数据元素可以由若干个数据项组成。

3.3 线性表的抽象数据类型

线性表的抽象数据类型如下:

ADT 线性表(List)Data    线性表的数据对象集合为{a1, a2,...,an},每个元素的类型均为DataType。其中,除第一个元素a1外,每个一个元素有且只有一个直接前驱元素,除了最后一个元素外an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。Operation    InitList(*L):初始化操作,建立一个空的线性表L。    ListEmpty(L):若表为空,返回true,否则返回false。    ClearList(*L):将线性表清空    GetElem(L,i,*e):将线性表L中的第 i 个位置元素返回给e。    LocateElem(L,e):在线性表L中查找与给定值e相等的元素,如有,返回该元素在表中序号;否则,返回0    ListInsert(*L,i,e):在线性表L中的第i个位置插入新元素e。    ListDelete(*L,i,*e):删除线性表L中第 i 个位置元素,并用e返回其值。    ListLength(L):返回线性表L的元素个数。endADT

对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的。

3.4 线性表的顺序存储结构

3.4.1 顺序存储定义

指的是用一段地址连续的存储单元依次存储线性表的数据元素。

3.4.2 顺序存储方式

利用一维数组来实现顺序存储结构。
顺序存储结构代码:

#define MAXSIZE 20typedef int ElemType;typedef struct{    ElemType    data[MAXSIZE];    int         Length;}SqList;

顺序结构的三个属性:

  • 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。
  • 线性表的最大存储容量:数组长度MAXSIZE。
  • 线性表的当前长度:length。
    -

3.4.3 数据长度与线性长度区别

数组的长度是存放线性表的存储空间的长度,存储分配后不变。
线性表的长度是线性表中数据元素的个数。
在任意时刻,线性表的长度小于等于数组的长度。

3.4.4 地址计算方法

顺序存储结构的存取时间性能为O(1),通常把具有这一特点的存储结构称为随机存取结构

3.5 顺序存储结构的插入和删除

3.5.1 获得元素操作

#define OK      1#define ERROR   0#define TRUE    1#define FALSE   0typedef int Status;/*Status是函数的类型,其值是函数结果状态代码初始条件:顺序线性表L已存在,1<=i<=ListLength(L)操作结果:用e返回L中第i个数据元素的值*/Status GetElem(SqList L, int i, ElemType *e){    if (L.Length == 0 || i < 1 || i > L.Length)        return ERROR;    *e = L.data[i-1];    return OK;}

3.5.2 插入操作

思路:

  • 如果插入位置不合理,抛出异常;
  • 如果线性表长度大于或等于数组长度,抛出异常或动态增加容量;
  • 从最后一个位置开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
  • 将要插入元素填入位置i处;
  • 表长加1.
/*初始条件:顺序线性表L已存在,1<= i <= ListLength(L)操作结果:在L中第 i 个位置之前插入新的数据元素e,L的表长加 1*/Status ListInsert(SqList *L, int i, ElemType e){    int k;    if (L->length == MAXSIZE)        return ERROR;    if (i < 1 || i > L->Length + 1)        return ERROR;    if (i <= L->length)    {        for (k = L->Length-1; i >= i-1; --k)            L->data[k+1] = L->data[k];    }    L->data[i-1] = e;    L->length++;    return OK;}

3.5.3 删除操作

思路:

  • 如果删除位置不合理,抛出异常;
  • 取出删除元素;
  • 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
  • 表长减1.
/*初始条件:顺序线性表L已存在, 1<= i <= ListLength(L)操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减 1*/Status ListDelete(SqList *L, int i, ElemType *e){    int k;    if (L->length == 0)        return ERROR;    if (i < 1 || i > L->length)        return ERROR;    *e = L->data[i-1];    if (i < L->length)    {        for (k = i ; k < L->length; ++K)            L->data[k-1] = L->data[k];    }    L-length--;    return OK;}

最坏时间复杂度为O(n)。平均时间复杂度为O(n)。
线性表的顺序结构在存读数据是,时间复杂读为O(1);而插入和删除时,时间复杂度为O(n)。说明,线性表的顺序存储结构比较适合元素个数不太变化,而更多是存取数据的应用

3.5.4 线性表顺序存储结构的优缺点

这里写图片描述

3.6 线性表的链式存储结构

3.6.1 顺序存储结构不足的解决办法

3.6.2 线性表链式存储结构定义

为了表示每个数据元素a1与其直接后继数据元素ai+1之间的逻辑关系,对数据元素a1来说,除了存储其本省的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)。
n个结点链接成一个链表,即为线性表的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫单链表
链表中第一个结点的存储位置叫做头指针
在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域指向第一个结点的指针。

3.6.3 头指针与头结点的异同

这里写图片描述

3.6.4 线性表链式存储结构代码描述

若线性表为空表,则头结点的指针域为空。
单链表中,在C语言中可用结构指针来描述

/*线性表的单链表存储结构*/typedef struct Node{    ElemType    data;    struct Node *next;}Node;typedef struct Node *LinkList;//定义LinkList

结点由存放数据元素的数据域和存放后继结点地址的指针域组成。

3.7 单链表的读取

获得链表第 i 个数据的算法思路:

  1. 声明一个指针P指向链表第一个结点,初始化j从1开始;
  2. 当j< i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1;
  3. 若到链表末尾p为空,则说明第i个结点不存在;
  4. 否则查找成功,返回结点p的数据。
    实现代码:
/*初始条件:顺序线性表L已存在,1<= i <= ListLength(L)操作结果:用e返回L中第i个数据元素的值*/Status GetElem(LinkList L, int i, ElemType *e){    int j;    LinkList p;    p = L->next;    j = 1;    while (p && j < i)    {        p = p->next;        ++j;    }    if (!p || j > i)        return ERROR;    *e = p->data;    return OK;}

最坏情况的时间复杂度是O(n)。

3.8 单链表的插入与删除

3.8.1 单链表的插入

单链表第i个数据插入结点的算法思路:

  1. 声明一个指针p指向链表头结点,初始化j从1开始;
  2. 当j
/*初始条件:顺序线性表L已存在,1 <= i <= ListLength(L)操作结果:在L中第 i 个结点位置之前插入新的数据元素e,L的长度加 1*/Status ListInsert(LinkList *L, int i, ElemType e){    int j;    LinkList p, s;    p = *L;    j = 1;    while (p && j < i)    {        p = p->next;        ++j;    }    if (!p || j > i)    {        return ERROR;    }    s = (LinkList)malloc(sizeof(Node));    s->data =e;    s->next = p->next;    p->next = s;    return OK;}

3.8.2 单链表的删除

单链表第 i 个数据删除节点的算法思路:

  1. 声明一指针 p 指向链表头指针,初始化 j 从 1 开始;
  2. 当 j < i 时,就遍历链表,让 p 的指针向后移动,不断指向下一个结点, j 累加 1;
  3. 若到链表末尾 p 为空,则说明第 i 个结点不存在;
  4. 否则查找成功,将欲删除的结点p->next赋值给q;
  5. 单链表的删除标准语句p->next = q->next;
  6. 将 q 结点中的数据赋值给 e,作为返回;
  7. 释放 q 结点;
  8. 返回成功。
    实现代码算法:
/*初始条件:链式线性表 L 已存在, 1<= i <= ListLength(L)操作结果:删除 L 的第 i 个结点,并用 e 返回其值,L的长度减 1*/Status ListDelete(LinkList *L, int i, ElemType *e){    int j;    LinkList p, q;    p = *L;    j = 1;    while (p->next && j < i)    {        p = p->next;        ++J;    }    if (!(p->next) || j > i)        return ERROR;    q = p->next;    p->next = q->next;    *e = q->data;    free(q);    return OK;}

插入删除的施加复杂度都是O(n)。
对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显

3.9 单链表的整表创建

创建单链表的过程就是一个动态生成链表的过程。即从“空表”的初始状态起,依次建立各元素结点,并逐个插入链表。
单链表整表创建的算法思路:

  1. 声明一指针 p 和计数器变量 i;
  2. 初始化一空链表L;
  3. 让 L 的头结点的指针指向NULL,即建立一个带头结点的单链表;
  4. 循环:
    生成一新结点赋值给 p;
    随机生成一数字赋值给 p 的数据域p->data;
    将 p 插入到头结点与前一新结点之间。

实现代码算法:

//随机产生n个元素的值,建立带表头结点的单链表线性表L(头插法)void CreateListHead(LinkList *L, int n){    LinkList p;    int i;    srand(time(0));    *L = (LinkList) malloc(sizeof(Node));    (*L)->next = NULL;    for (i=0; i<n; ++i)    {        p = (LinkList) malloc(sizeof(Node));        p->data = rand()%100 + 1;        p->next = (*L)->next;        (*L)->next = p;    }}
/随机产生n个元素的值,建立带表头结点的单链表线性表L(尾插法)void CreateListTail(LinkList *L, int n){    LinkList p,r;    int i;    srand(time(0));    *L = (LinkList) malloc(sizeof(Node));    r = *L;    for (i=0; i<n; ++i)    {        p = (Node *) malloc(sizeof(Node));        p->data = rand()%100 + 1;        r->next = p;        r = p;    }    r->next = NULL;}

注意L与r的关系,L是指整个单链表,而r是指向尾结点的变量,r会随着循环不断变化结点,而L则是随着循环增长为一个多结点的链表。

3.10 单链表的整表删除

单链表整表删除的算法思路如下:

  1. 声明一结点 p 和q;
  2. 将第一个结点赋值给p;
  3. 循环:
    将下一结点赋值给q;
    释放p;
    将q赋值给p。

实现代码算法如下:

//初始条件:链式线性表L已存在,操作结果:将L重置为空表Status ClearList(LinkList *L){    LinkList p, q;    p = (*L)->next;    while (p)    {        q = p->next;        free(p);        p = q;    }    (*L)->next = NULL;    return OK;}

3.11 单链表结构与顺序存储结构优缺点

这里写图片描述
经验性的结论:

  • 若线性表频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,宜采用单链表结构。
  • 当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构。若事先知道线性表大致长度,宜用顺序存储结构。

3.12 静态链表

用数组描述的链表叫做静态链表

//线性表的静态链表存储结构#define MAXSIZE 1000typedef struct{    ElemType    data;    int         cur;} Component, StaticLinkList[MAXSIZE];

初始化数组:

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

3.12.1 静态链表的插入操作

//若备用空间链表非空,则返回分配的结点下标,否则返回0int Malloc_SLL(StaticLinkList space){    int i = space[0].cur;    if (space[0].cur)        space[0].cur = space[i].cur;    return i}//在L中第 i 个元素之前插入新的数据元素 eStatus ListInsert(StaticLinkList L, int i, ElemType e){    int j, k, l;    k = MAXSIZE - 1;    if (i < 1 || i > ListLength(L) + 1)        return ERROR;    j = Malloc_SLL(L);    if (j)    {        L[j].data = e;        for (l = 1; l <= i-1; l++)            k = L[k].cur;        L[j].cur = L[k].cur;        L[k].cur = j;        return OK;    }    return ERROR;}

3.12.2 静态链表的删除操作

//删除在L中第 i 个数据元素eStatus ListDelete(StaticLinkList L, int i){    int j, k;    if (i < 1 || i > ListLength(L))        return ERROR;    k = MAXSIZE - 1;    for (j = 1; j <= i-1; ++j)        k = L[k].cur;    j = L[k].cur;    L[k].cur = L[j].cur;    Free_SSL(L, j);    return OK;}//将下标为k的空闲结点回收到备用链表void Free_SSL(StaticLinkList space, int k){    space[k].cur = space[0].cur;    space[0].cur = k;}//初始条件:静态链表L已存在。操作结果:返回L中数据元素个数int ListLength(StaticLinkList L){    int j = 0;    int i = L[MAXSIZE].cur;    while (i)    {        i = L[i].cur;        ++j;    }    return j;}

3.12.3 静态链表优缺点

这里写图片描述
总的来说,静态链表其实是为了给没有指针的高级语言设计的一种实现单链表能力的方法。

3.13 循环链表

将单链表中终端结点的指针端由空指针改为指向头结点, 这种头尾相接的单链表称为单循环链表,简称循环链表

3.14 双向链表

双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。

//线性表的双向链表存储结构typedef struct DulNode{    ElemType data;    struct DuLNode *prior;    struct DuLNode *next;}DulNode, *DuLinkList;

3.15 总结回顾

3.16 结尾语

原创粉丝点击