【数据结构与算法】(二)线性表、顺序表、静态顺序表、动态顺序表

文章目录

  • 【数据结构与算法】(二)线性表、顺序表、静态顺序表、动态顺序表
    • 前言
    • [2.1 线性表的定义和特点](#2.1 线性表的定义和特点)
    • [2.2 线性表的类型定义](#2.2 线性表的类型定义)
    • [2.3 线性表的顺序存储结构](#2.3 线性表的顺序存储结构)
      • [2.3.1 线性表的顺序存储表示(顺序表)](#2.3.1 线性表的顺序存储表示(顺序表))
      • [2.3.2 静态顺序表的实现](#2.3.2 静态顺序表的实现)
        • [2.3.2.1 静态顺序表的头文件申明](#2.3.2.1 静态顺序表的头文件申明)
        • [2.3.2.2 静态顺序表初始化](#2.3.2.2 静态顺序表初始化)
        • [2.3.2.3 静态顺序表销毁](#2.3.2.3 静态顺序表销毁)
        • [2.3.2.4 静态顺序表清空](#2.3.2.4 静态顺序表清空)
        • [2.3.2.5 判断静态顺序表是否为空](#2.3.2.5 判断静态顺序表是否为空)
        • [2.3.2.6 静态顺序表获取元素](#2.3.2.6 静态顺序表获取元素)
        • [2.3.2.7 静态顺序表获取第一个匹配元素的下标](#2.3.2.7 静态顺序表获取第一个匹配元素的下标)
        • [2.3.2.8 静态顺序表获取一个元素的前驱元素](#2.3.2.8 静态顺序表获取一个元素的前驱元素)
        • [2.3.2.9 静态顺序表获取一个元素的后继元素](#2.3.2.9 静态顺序表获取一个元素的后继元素)
        • [2.3.2.10 静态顺序表插入一个元素](#2.3.2.10 静态顺序表插入一个元素)
        • [2.3.2.11 静态顺序表删除一个元素](#2.3.2.11 静态顺序表删除一个元素)
        • [2.3.2.12 遍历静态顺序表并输出每一个元素](#2.3.2.12 遍历静态顺序表并输出每一个元素)
        • [2.3.2.13 测试](#2.3.2.13 测试)
      • [2.3.3 动态顺序表的实现](#2.3.3 动态顺序表的实现)
        • [2.3.3.1 动态顺序表的头文件定义](#2.3.3.1 动态顺序表的头文件定义)
        • [2.3.3.2 动态顺序表的初始化](#2.3.3.2 动态顺序表的初始化)
        • [2.3.3.3 动态顺序表的插入操作](#2.3.3.3 动态顺序表的插入操作)

【数据结构与算法】(二)线性表、顺序表、静态顺序表、动态顺序表

前言

本篇文章大部分内容摘自严蔚敏、李冬梅、吴伟民编著的由人民邮电出版社出版发行的《数据结构:C语言版》。

本文中的所有代码使用C23标准的C语言实现,因此在编译时请加上-std=c2x,代码中若存在错误,欢迎大家在评论区指出。

如果觉得本篇文章对你有帮助的话,不妨点赞、收藏加关注吧!😘❤️❤️❤️

2.1 线性表的定义和特点

线性表是最基本且最常用的一种线性结构,同时也是其他数据结构的基础,尤其是单链表,是贯穿整个数据结构课程的基本技术。

在日常生活中,线性表的例子比比皆是。例如,26个英文字母(A,B,C, ..., Z)、学生成绩表等。

从上面的示例中可以看出,它们的数据元素虽然不同,但同一线性表中的元素必定具有相同的特性,即属于同一数据对象,相邻数据元素之间存在着序偶关系。

诸如此类的由n(n >= 0)个数据特性相同的元素构成的有限序列称为线性表。

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

对于非空的线性表或线性结构,其特点是:

  1. 存在唯一的一个被称作"第一个"的数据元素;
  2. 存在唯一的一个被称作"最后一个"的数据元素;
  3. 除第一个之外,结构中的每个数据元素均只有一个前驱;
  4. 除最后一个之外,结构中的每个数据元素均只有一个后继。

2.2 线性表的类型定义

线性表是一个相当灵活的数据结构,其长度可以根据需要增长或缩短,即对线性表的数据元素不仅可以进行访问,而且可以进行插入和删除等操作。

下面给出线性表的抽象数据类型定义(只包含一些基本操作):

ADT List{
    数据对象:D = {a1, a2, ..., an},对象中每个数据元素的类型相同
    数据关系:R = {<a[i], a[i+1]> | 1 <= i < n, a[i], a[i+1] ∈ D}。

    基本操作:
        InitList(&L)
            初始条件:线性表 L 不存在。
            操作结果:构造一个空的线性表 L。
        
        DestoryList(&L)
            初始条件:线性表 L 已存在
            操作结果:销毁线性表 L。
        
        ClearList(&L)
            初始条件:线性表 L 已存在。
            操作结果:将线性表 L 清空。
        
        ListEmpty(L)
            初始条件:线性表 L 已存在。
            操作结果:若 L 为空表,则返回 true,否则返回 false。
        
        ListLength(L)
            初始条件:线性表 L 已存在。
            操作结果:返回线性表 L 的长度。
        
        GetElem(L, i, &e)
            初始条件:线性表 L 已存在,1 <= i <= ListLength(L)。
            操作结果:将线性表 L 中第 i 个位置的元素值返回给 e。
        
        LocateElem(L, e)
            初始条件:线性表 L 已存在。
            操作结果:在线性表 L 中查找与给定值 e 相等的元素,若找到返回其在表中的位置,否则返回 0。

        PriorElem(L,cur_e,&pre_e)
            初始条件:线性表L已存在
            操作结果:若cure_e是L的数据元素,且不是第一个,则用pre_e返回其前驱,否则操作失败,pre_e无定义。
        
        NextElem(L,cur_e,&next_e)
            初始条件:线性表L已存在。
            操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回其后继,否则操作失败,next_e无定义
                
        ListInsert(&L, i, e)
            初始条件:线性表 L 已存在,1 <= i <= ListLength(L) + 1。
            操作结果:在线性表 L 的第 i 个位置插入元素 e。
        
        ListDelete(&L, i, &e)
            初始条件:线性表 L 已存在,1 <= i <= ListLength(L)。
            操作结果:删除线性表 L 中第 i 个位置的元素,并用 e 返回删除的元素值。
        
        TraverseList(L)
            初始条件:线性表L已存在。
            操作结果:对线性表 L 进行遍历,在遍历过程中对 L 的每个结点访问一次。
}

2.3 线性表的顺序存储结构

2.3.1 线性表的顺序存储表示(顺序表)

线性表的顺序表示指的是用一组地址连续的存储单元一次存储线性表的数据元素,这种表示也称作线性表的顺序存储结构或顺序映像。通常,称这种存储结构的线性表为
顺序表 (Sequential List),由于顺序表和程序设计语言中定义的数组类似,因此也有人将顺序表称为数组表(Array List)。

顺序表的特点有:

  1. 连续存储:逻辑上相邻的数据元素,其物理次序也是相邻的
  2. 读取速度快(随机访问):由于顺序表是连续存储的,因此,只要确定了存储顺序表的起始位置,顺序表中的任意一个数据元素都可以随机存取(因为可以通过下标直接访问任意位置的元素)。
  3. 插入和删除操作慢:由于顺序表是连续存储的,因此每次删除和插入操作时,为了保证连续存储,需要将插入位置之后的元素向前或向后移动,这就导致顺序表的插入和删除操作较慢。

根据顺序表的最大容量是否固定,我们将顺序表分为静态顺序表和动态顺序表。

其中,静态顺序表的最大容量固定,其元素个数达到最大容量时,无法再向其中插入元素。

动态顺序表在元素个数达到最大容量时,会自动扩容,一般来说都是创建一个容量比原来大的新元素域,然后将旧顺序表中的元素copy过来。

在无特殊说明的情况下,我们的顺序表指的都是静态顺序表。

2.3.2 静态顺序表的实现

2.3.2.1 静态顺序表的头文件申明

建议自己copy一下头文件,然后自己去实现一遍

在此不得不吐槽一句,虽然C23支持了bool true 和 false作为关键字,但是大多数编辑器/插件并没有完全支持,例如vscode上的c/c++插件,只能识别bool是关键字,true和false不是关键字。

c 复制代码
#define MAX_SIZE 100

typedef int ElemType;

typedef struct StaticSequenceList{
    ElemType *elems;
    unsigned length;

    /* 使用指针封装的方法,在初始化的时候让指针指向对应函数即可 */
    void (*destroy)(struct StaticSequenceList *list);
    void (*clear)(struct StaticSequenceList *list);
    bool (*isEmpty)(struct StaticSequenceList list);
    void (*get)(struct StaticSequenceList list, unsigned i, ElemType *e);
    unsigned (*locate)(struct StaticSequenceList list, ElemType e);
    bool (*getPrior)(struct StaticSequenceList list, ElemType cur_e, ElemType *pre_e);
    bool (*getNext)(struct StaticSequenceList list, ElemType cur_e, ElemType *next_e);
    void (*insert)(struct StaticSequenceList *list, unsigned i, ElemType e);
    void (*delete)(struct StaticSequenceList *list, unsigned i, ElemType *e);
    void (*toString)(struct StaticSequenceList *list);

} StaticSequenceList;

void StaticSequenceList_Init(StaticSequenceList *list);

void StaticSequenceList_Destroy(StaticSequenceList *list);

void StaticSequenceList_Clear(StaticSequenceList *list);

bool StaticSequenceList_isEmpty(StaticSequenceList list);

void StaticSequenceList_Get(StaticSequenceList list, unsigned i, ElemType *e);

unsigned StaticSequenceList_Locate(StaticSequenceList list, ElemType e);

bool StaticSequenceList_LocatePrior(StaticSequenceList list, ElemType cur_e, ElemType *pre_e);

bool StaticSequenceList_LocateNext(StaticSequenceList list, ElemType cur_e, ElemType *next_e);

void StaticSequenceList_Insert(StaticSequenceList *list, unsigned i, ElemType e);

void StaticSequenceList_Delete(StaticSequenceList *list, unsigned i, ElemType *e);

void StaticSequenceList_toString(StaticSequenceList *list);
2.3.2.2 静态顺序表初始化
c 复制代码
void StaticSequenceList_Init(StaticSequenceList *list)
{
    assert(list);
    list->elems = malloc(sizeof(ElemType) * MAX_SIZE);
    assert(list->elems);
    list->length = 0;

    list->destroy = StaticSequenceList_Destroy;
    list->clear = StaticSequenceList_Clear;
    list->isEmpty = StaticSequenceList_isEmpty;
    list->get = StaticSequenceList_Get;
    list->locate = StaticSequenceList_Locate;
    list->getPrior = StaticSequenceList_getPrior;
    list->getNext = StaticSequenceList_getNext;
    list->insert = StaticSequenceList_Insert;
    list->delete = StaticSequenceList_Delete;
    list->toString = StaticSequenceList_toString;
}
2.3.2.3 静态顺序表销毁
c 复制代码
void StaticSequenceList_Destroy(StaticSequenceList *list)
{
    assert(list);

    free(list->elems);
    list->elems = NULL;
}
2.3.2.4 静态顺序表清空
c 复制代码
void StaticSequenceList_Clear(StaticSequenceList *list)
{
    assert(list);

    list->length = 0;
}
2.3.2.5 判断静态顺序表是否为空
c 复制代码
bool StaticSequenceList_isEmpty(StaticSequenceList list)
{
    assert(list.elems);

    return list.length == 0;
}
2.3.2.6 静态顺序表获取元素
c 复制代码
void StaticSequenceList_Get(StaticSequenceList list, unsigned int i, ElemType *e)
{
    assert(i >= 1);
    assert(i <= list.length);

    *e = list.elems[i - 1];
}
2.3.2.7 静态顺序表获取第一个匹配元素的下标
c 复制代码
unsigned StaticSequenceList_Locate(StaticSequenceList list, ElemType e)
{
    assert(list.elems);

    for (unsigned i = 0; i < list.length; i++)
    {
        if (list.elems[i] == e)
            return i + 1;
    }
    return 0;
}
2.3.2.8 静态顺序表获取一个元素的前驱元素
c 复制代码
bool StaticSequenceList_getPrior(StaticSequenceList list, ElemType cur_e, ElemType *pre_e)
{
    assert(list.elems);

    int index = -1;

    for (int i = 0; i < list.length; i++)
    {
        if (list.elems[i] == cur_e)
        {
            index = i;
            break;
        }
    }
    if (index >= 1)
    {
        *pre_e = list.elems[index - 1];
        return 1;
    }
    else
        return 0;
}
2.3.2.9 静态顺序表获取一个元素的后继元素
c 复制代码
bool StaticSequenceList_getNext(StaticSequenceList list, ElemType cur_e, ElemType *next_e)
{
    int index = -1;
    for (unsigned i = 0; i < list.length; i++)
    {
        if (list.elems[i] == cur_e)
        {
            index = i;
            break;
        }
    }
    if (index >= 0 && index < list.length -1)
    {
        *next_e = list.elems[index + 1];
        return 1;
    }else return 0;
}
2.3.2.10 静态顺序表插入一个元素
c 复制代码
void StaticSequenceList_Insert(StaticSequenceList *list, unsigned int i, ElemType e)
{
    assert(list);
    assert(list->length < MAX_SIZE);
    assert(i >= 1);
    assert(i <= list->length + 1);

    for (unsigned j = list->length; j >= i; j--)
    {
        list->elems[j] = list->elems[j - 1];
    }

    list->elems[i - 1] = e;
    list->length++;
}
2.3.2.11 静态顺序表删除一个元素
c 复制代码
void StaticSequenceList_Delete(StaticSequenceList *list, unsigned int i, ElemType *e)
{
    assert(list);
    assert(i >= 1);
    assert(i <= list->length);

    *e = list->elems[i - 1];
    for (unsigned j = i; j < list->length; j++)
    {
        list->elems[j - 1] = list->elems[j];
    }
    list->length--;
}
2.3.2.12 遍历静态顺序表并输出每一个元素
c 复制代码
void StaticSequenceList_toString(StaticSequenceList *list)
{
    assert(list->elems != NULL);

    printf("list:{\n");
    for (unsigned i = 0; i < list->length; i++)
    {
        printf("\t%d:%d\n", i + 1, list->elems[i]);
    }
    printf("}\n");
}
2.3.2.13 测试
c 复制代码
int main()
{
    StaticSequenceList list;
    StaticSequenceList_Init(&list);

    bool flag = 1;
    int op = 0;
    unsigned i = 0;
    int e = 0;
    int temp_elem = 0;

    while (flag)
    {
        list.toString(&list);

        puts("------------");
        puts("1. 插入元素");
        puts("2. 删除元素");
        puts("3. 获取元素");
        puts("4. 查找元素");
        puts("5. 查找前驱元素");
        puts("6. 查找后继元素");
        puts("7. 清空list");
        puts("8. 销毁list");
        puts("0. 退出");
        puts("-------------");

        scanf("%d", &op);
        switch (op)
        {
        case 0:
            flag = 0;
            break;
        case 1:
            printf("请输入要插入的数据及其位置:");
            scanf("%d %d", &i, &e);
            list.insert(&list, i, e);
            break;
        case 2:
            printf("请输入要删除的元素位置:");
            scanf("%d", &i);
            list.delete(&list, i, &e);
            printf("删除的元素为:%d\n", e);
            break;
        case 3:
            printf("请输入要获取的元素下标:");
            scanf("%d", &i);
            list.get(list, i, &e);
            printf("该下标的元素为:%d\n", e);
            break;
        case 4:
            printf("请输入要查找的元素的值:");
            scanf("%d", &e);
            i = list.locate(list, e);
            if (i == 0)
                printf("未找到该元素\n");
            else
                printf("第一个找到的该元素下标为:%d\n", i);
            break;
        case 5:
            printf("请输入要查找其前驱的元素:");
            scanf("%d", &e);
            if (list.getPrior(list, e, &temp_elem))
                printf("该元素的前驱为:%d\n", temp_elem);
            else
                printf("未找到该元素\n");
            break;
        case 6:
            printf("请输入要查找其后继的元素:");
            scanf("%d", &e);
            if (list.getNext(list, e, &temp_elem))
                printf("该元素的后继为:%d\n", temp_elem);
            else
                printf("未找到该元素\n");
            break;
        case 7:
            list.clear(&list);
            break;
        case 8: 
            list.destroy(&list);
            break;
        }
    }

    return 0;
}

2.3.3 动态顺序表的实现

前文提到,根据顺序表的最大容量是否固定,我们将顺序表分为静态顺序表和动态顺序表。

静态顺序表的最大容量固定,当其中的元素个数达到最大容量时,无法再向其插入元素。

动态顺序表在元素个数达到最大容量时,会自动扩容,一般来说都是创建一个容量比原来大的新元素域,然后将旧顺序表中的元素copy过来。因此,已有m个元素的动态顺序表在扩容时,需要至少m+n的存储空间,n为扩容量。

动态顺序表由于分配的空间大小可以在运行时动态决定,因此,相对于静态顺序表,动态顺序表在一定程度上可以减少部分内存开销。

2.3.3.1 动态顺序表的头文件定义

与静态顺序表相比,动态顺序表为了实现在运行时,动态分配内存空间,因此,在初始化、插入操作上有所不同,其他内容大致相同。

c 复制代码
#ifndef STATIC_ARRAY_LIST_H
#define STATIC_ARRAY_LIST_H

#define Increase_Multiple 2
#define Init_Size 10

typedef int ElemType;

typedef struct DynamicSequenceList{
    ElemType *elems;
    unsigned length;
    unsigned MAX_ZISE;  // 用来记录当前动态顺序表的最大容量,当length达到最大容量时,进行扩容。

    void (*destroy)(struct DynamicSequenceList *list);
    void (*clear)(struct DynamicSequenceList *list);
    bool (*isEmpty)(struct DynamicSequenceList list);
    void (*get)(struct DynamicSequenceList list, unsigned i, ElemType *e);
    unsigned (*locate)(struct DynamicSequenceList list, ElemType e);
    bool (*getPrior)(struct DynamicSequenceList list, ElemType cur_e, ElemType *pre_e);
    bool (*getNext)(struct DynamicSequenceList list, ElemType cur_e, ElemType *next_e);
    void (*insert)(struct DynamicSequenceList *list, unsigned i, ElemType e);
    void (*delete)(struct DynamicSequenceList *list, unsigned i, ElemType *e);
    void (*toString)(struct DynamicSequenceList *list);

} DynamicSequenceList;

void DynamicSequenceList_Init(DynamicSequenceList *list);

void DynamicSequenceList_Destroy(DynamicSequenceList *list);

void DynamicSequenceList_Clear(DynamicSequenceList *list);

bool DynamicSequenceList_isEmpty(DynamicSequenceList list);

void DynamicSequenceList_Get(DynamicSequenceList list, unsigned i, ElemType *e);

unsigned DynamicSequenceList_Locate(DynamicSequenceList list, ElemType e);

bool DynamicSequenceList_getPrior(DynamicSequenceList list, ElemType cur_e, ElemType *pre_e);

bool DynamicSequenceList_getNext(DynamicSequenceList list, ElemType cur_e, ElemType *next_e);

void DynamicSequenceList_Insert(DynamicSequenceList *list, unsigned i, ElemType e);

void DynamicSequenceList_Delete(DynamicSequenceList *list, unsigned i, ElemType *e);

void DynamicSequenceList_toString(DynamicSequenceList *list);

#endif
2.3.3.2 动态顺序表的初始化
c 复制代码
void DynamicSequenceList_Init(DynamicSequenceList *list)
{
    assert(list);
    list->elems = malloc(sizeof(ElemType) * Init_Size);
    assert(list->elems);
    list->length = 0;
    list->MAX_ZISE = Init_Size;

    list->destroy = DynamicSequenceList_Destroy;
    list->clear = DynamicSequenceList_Clear;
    list->isEmpty = DynamicSequenceList_isEmpty;
    list->get = DynamicSequenceList_Get;
    list->locate = DynamicSequenceList_Locate;
    list->getPrior = DynamicSequenceList_getPrior;
    list->getNext = DynamicSequenceList_getNext;
    list->insert = DynamicSequenceList_Insert;
    list->delete = DynamicSequenceList_Delete;
    list->toString = DynamicSequenceList_toString;
}
2.3.3.3 动态顺序表的插入操作
c 复制代码
void DynamicSequenceList_Insert(DynamicSequenceList *list, unsigned int i, ElemType e)
{
    assert(list);
    assert(list->length < list->MAX_ZISE);
    assert(i >= 1);
    assert(i <= list->length + 1);

    for (unsigned j = list->length; j >= i; j--)
    {
        list->elems[j] = list->elems[j - 1];
    }

    list->elems[i - 1] = e;
    list->length++;

    // 自增长
    if(list->length == list->MAX_ZISE){
        if(realloc(list->elems, sizeof(ElemType) * list->MAX_ZISE * Increase_Multiple)){
            list->MAX_ZISE *= Increase_Multiple;
        }
    }
}

🙌未完待续!😘❤️❤️❤️

相关推荐
续亮~16 分钟前
6、Redis系统-数据结构-05-整数
java·前端·数据结构·redis·算法
ningbaidexia1 小时前
java数据结构集合复习之ArrayList与顺序表
java·数据结构·windows
托尼沙滩裤3 小时前
【js面试题】js的数据结构
前端·javascript·数据结构
逆水寻舟4 小时前
算法学习记录2
python·学习·算法
羞儿4 小时前
【读点论文】基于二维伽马函数的光照不均匀图像自适应校正算法
人工智能·算法·计算机视觉
续亮~4 小时前
6、Redis系统-数据结构-03-压缩列表
数据结构·数据库·redis
青衫酒1455 小时前
中国剩余定理
算法
akthwyl5 小时前
2024年【安全员-C证】考试及安全员-C证免费试题
c语言·开发语言
鸽鸽程序猿5 小时前
【数据结构】顺序表
java·开发语言·数据结构·学习·算法·intellij idea
Chris-zz5 小时前
C++:继承
开发语言·c++·算法·学习方法