前言:
大家好,好久不见,由于种种原因,顺序表这一专题拖了很久。不过没关系,接下来我将介绍关于顺序表的一些知识。这一块需要我们对之前学习的‘指针’ ‘结构体’ 掌握到位,接下来让我们一起学习吧!
1.顺序表的概念
在学习顺序表之前,我们不妨先了解一下线性表
线性表(linear list )是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
那么线性表到底是什么呢
我们用图来说明一下
从实际意义上来讲,线性表提供了一些列基本的操作,包括插入元素、删除元素、查找元素、获取元素数量等. 当然,它的底层结构是数组
今天我们学习的顺序表就是线性表的一种
2.顺序表的分类
2.1 静态顺序表
概念:使用定长数组储存元素
定义宏
在这里我们定义宏 #define N 7 为了方便改变N 的大小,当我们代码量非常之多时,我们对于简单定义的数据可以用 宏 来表示,直观且方便修改
一键替换
上述代码中出现了 typedef int SLDataType;
前面几章的知识我们知道 typedef 是给一个东西起一个别名
在这里,我们给 “int” 起一个别名 “SLDataType”
为什么这么做呢?
在我们之后投入到工作中后,我们会发现,工作任务麻烦又复杂,万一情况突变,上级要求你把这个项目某个区块中 所有 int 类型的全部转化为 char 类型的,到那时候,再高级的编译器也无法精确到我们需要改变的区块,这时候我们根据需要把每个项目中 数据类型 给它起个别名,这样问题就解决了,需要改变类型时我们只需要改变它的别名,这样既安全又不会干扰其它区块的相同类型
静态顺序表的缺陷
空间给少了不够用,给多了浪费空间
给定的数组长度,若不够,会导致后续的数据保存失败
数据丢失:非常严重的技术事故,严重时威胁工作和公司的利益
2.2 动态顺序表
概念:按需申请数组长度
3.动态顺序表的实现
这一部分内容之前,先说明一件事情,其实越往后学习,就越会发现,我们在定义一个区块或者一个项目的名称时,通常是有它实际意义在内的,所以我们要习惯一些复杂的名称.
创建源文件和头文件
首先,我们创建源文件和头文件
基本结构体
接着,我们写出基本结构体(对于这部分知识不了解的老头,可以回头看看结构体那一章哦!)
初始化
这里首先我们来看看如何初始化
SeqList.h
SeqList.c
test.c
大家可以调试一下,这里不再赘述太多操作啦
强调:一定要注意指针的正确使用方法:传参、传地址等
插入数据
在此过程中,我们可以实现头插(SLPushBack)和尾插(SLPushFront)
即
尾插
我们首先学习比较复杂的尾插
尾插分为3种情况
当处于前两种情况时,我们可以直接插入 arr[size]=6
而当空间不够时,我们会选择 扩容
扩容有三种办法,分别是
当实际操作后我们会发现,前两种方法是不可用的,它们大量浪费内存资源,且效率低下,而后面这个方法在大部分情况下是没问题的
在这里,作者水平有限,无法说明,不过百度会给出答案
回顾我们之前学习 动态内存管理 用到的 三个函数 malloc calloc realloc
在这里我们会用到 realloc
void SLCheckCapacity(SL* ps) { if (ps->size == ps->capacity) //判断是否需要扩容 { int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//防止影响capacity SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//开辟4个整型大小的空间 if (tmp == NULL) { perror("realloc fail!"); exit(1);//退出 } //扩容成功 ps->arr = tmp; ps->capacity = newCapacity; } } //顺序表的尾部插入 void SLPushBack(SL* ps, SLDataType x) { //空间不够,扩容 SLCheckCapacity(ps); //空间足够,直接插入 ps->arr[ps->size++] = x; //ps->size++; }
这里运用的三目操作符需要大家注意
我们测试一下
发现没问题
当然,我们需要判断 ps 是否为NULL
头插
这里分两种情况,即空间足够和空间不够的情况
void SLPushFront(SL* ps, SLDataType x) { assert(ps); //判断是否扩容 SLCheckCapacity(ps); //旧数据往后挪动一位,从后往前遍历 for (int i = ps->size; i > 0; i--) { ps->arr[i] = ps->arr[i - 1];//i=1 ps->arr[1]=ps->arr[0] } ps->arr[0] = x; ps->size++; }
我们测试一波
没问题
删除
同样的,这里也分为头删和尾删
尾删
尾删比较简单,我们先看尾删
这里有两种情况
在这里特别注意:当删除一个数据时应让 size - -
好,那么问题来了
当我们进行尾删时是让删除的数据删除,还是置为假呢,因为当你想要删除数据时,其实是给它一个负数(假),这该怎么理解呢?
其实我们只需要改成红色的就行啦
这是为啥呢?
当我们让size -- 时,它指向了前一个数据,而当我们想要进行后续的操作时,
比如让我们修改、查找数据二时,size 只标记了下标为2处的数据,这样我们就可以忽视下标为3处的数据,不影响我们操作数据
我们来测试一下
尾插四次,尾删两次,没有问题
而当我们删过头时,会发生什么呢?
我们会发现,直接报错(断言失败)
头删
而头删就有点烦人了
涉及到挪动数据(太烦人了)
头删也分两种情况
没有数据的情况我们直接用断言判断,我们重点看有数据的情况
当顺序表不为空时,后面的数据往前挪动一位,size--
接着我们测试一下
没有问题
同样的,当我们多删时,会出现报错
指定位置插入和删除
这一块儿呢比较简单,我们来看一下
指定位置之前插入数据
假如我们在 pos=3 位置插入 x=100
首先我们需要判断ps是否为NULL和判断空间是否够不够
我们需要pos及之后的数据往后挪一位,pos空出来
我们来详细分析一下这段代码
我们测试一下
但是当我们pos 输入超过顺序表范围呢?
是随机值,所以我们要改进一下
指定位置删除数据
这里我们也只考虑顺序表不为空的情况
跟插入差不多,我们需要删除指定数据,然后让后面数据向前挪动(补缺)
这里我就粗略概况啦,跟插入差不多
我们测试一下
注意:不能删除size位置,size无数据,如果执行,会报错
源代码
test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "SeqList.h" void slTest01() { SL sl; SLInit(&sl); //尾插 SLPushBack(&sl, 1); //ctrl+d快速复制 SLPushBack(&sl, 2); SLPushBack(&sl, 3); SLPushBack(&sl, 4); SLPrint(&sl); //1 2 3 4 //SLPushBack(&sl, 5); //SLPrint(&sl); 头插 //SLPushFront(&sl, 5); //SLPushFront(&sl, 6); //SLPushFront(&sl, 7); //SLPrint(&sl); //7 6 5 1 2 3 4 尾删 //SLPopBack(&sl); //SLPopBack(&sl); //SLPopBack(&sl); //SLPrint(&sl); //3 4 头删 //SLPopFront(&sl); //SLPopFront(&sl); //SLPopFront(&sl); //SLPrint(&sl); // 4 指定位置插入 //SLInsert(&sl, 0, 520); //SLPrint(&sl); //520 1 2 3 4 //SLInsert(&sl, sl.size, 1314); //SLPrint(&sl); //520 1 2 3 4 1314 //SLInsert(&sl, 100, 1314); //SLPrint(&sl); //指定位置删除数据 //SLErase(&sl, 0); //SLPrint(&sl); //2 3 4 //SLErase(&sl, sl.size - 1); //SLPrint(&sl); //2 3 //SLErase(&sl, 1); //SLPrint(&sl); //1 3 4 } int main() { slTest01(); return 0; }
SeqList.h
#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> //静态顺序表 // //#define N 100 //typedef int SLDataType; // //struct SeqList //{ // int a[N]; // int size; //}; //动态顺序表 typedef int SLDataType; typedef struct SeqList { SLDataType* arr; //存储数据的底层结构 int capacity; //记录顺序表的空间大小 int size; //记录顺序表当前有效的数据个数 }SL; //typedef struct SeqList SL; //初始化和销毁 void SLInit(SL* ps); void SLDestroy(SL* ps); void SLPrint(SL* ps); //顺序表的头插/尾插 void SLPushBack(SL*ps,SLDataType x); void SLPushFront(SL* ps, SLDataType x); //顺序表的头部/尾部删除 void SLPopBack(SL* ps); void SLPopFront(SL* ps); //指定位置之前插入数据 //指定位置删除数据 void SLInsert(SL* ps, int pos, SLDataType x); void SLErase(SL* ps, int pos);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "SeqList.h" //初始化和销毁 void SLInit(SL* ps) { ps->arr = NULL; ps->size = ps->capacity = 0; } void SLCheckCapacity(SL* ps) { if (ps->size == ps->capacity) //判断是否需要扩容 { int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//防止影响capacity SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//开辟4个整形大小的空间 if (tmp == NULL) { perror("realloc fail!"); exit(1);//退出 } //扩容成功 ps->arr = tmp; ps->capacity = newCapacity; } } //顺序表的头部/尾部插入 void SLPushBack(SL* ps, SLDataType x) { assert(ps != NULL);//断言 //assert(ps); //if (ps == NULL) //{ // return; //} //空间不够,扩容 SLCheckCapacity(ps); //空间足够,直接插入 ps->arr[ps->size++] = x; //ps->size++; } void SLPushFront(SL* ps, SLDataType x) { assert(ps); //判断是否扩容 SLCheckCapacity(ps); //旧数据往后挪动一位,从后往前遍历 for (int i = ps->size; i > 0; i--) { ps->arr[i] = ps->arr[i - 1];//i=1 ps->arr[1]=ps->arr[0] } ps->arr[0] = x; ps->size++; } //顺序表的头部/尾部删除 void SLPopBack(SL* ps) { //判断顺序表是否为空 assert(ps); //不能传空 assert(ps->size); //顺序表内数据不能为零 //顺序表不为空 //ps->arr[ps->size - 1] = -1; //ps->size--; ps->size--; } void SLPopFront(SL* ps) { assert(ps); assert(ps->size); //不为空执行挪动操作 for (int i=0 ;i < ps->size-1; i++) { ps->arr[i] = ps->arr[i + 1]; } ps->size--; } void SLPrint(SL* ps) { for (int i = 0; i < ps->size; i++) { printf("%d ", ps->arr[i]); } printf("\n"); } //指定位置之前插入数据 void SLInsert(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos >= 0 && pos <= ps->size); //判断pos是否超出顺序表范围 SLCheckCapacity(ps);//判断空间是否够不够 //pos 及之后的数据往后挪动一位,pos空出来 for (int i = ps->size; i > pos; i--) { ps->arr[i] = ps->arr[i - 1]; } ps->arr[pos] = x; ps->size++; } //指定位置删除数据 void SLErase(SL* ps, int pos) { assert(ps); assert(pos >= 0 && pos < ps->size); //注意:不能删除size位置,size无数据,如果执行,会报错 //pos 以后的数据往前挪动一位 for (int i = pos; i < ps->size; i++) { ps->arr[i] = ps->arr[i + 1]; } ps->size--; }
总结:以上就是顺序表的全部内容啦,这是数据结构的开端,希望我们一起进步!
作者留言:代码非唯一性,若有错误欢迎指出!
创作时间:2024.5.10