目录
(一)顺序表是一种数据结构
(二)顺序表
(1)顺序表的必要性
(2)顺序表的概念及结构
i,线性表
(3)顺序表的分类
i,顺序表和数组的区别:
ii,顺序表分类
(三)动态顺序表的实现:
(1)头文件的解释:
(2)手把手实现动态顺序表
a,初始化
b,销毁
c,打印
d,扩容
e,头插与头删
f,尾插与尾删
g,指定位置插入与删除
h,顺序表查找某一元素
正文开始
(一)顺序表是一种数据结构
顺序表是一种数据结构,C语言有一种内置的数据结构:数组
当我们想要大量使用同⼀类型的数据时,通过手动定义大量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在⼀起,结构也可以理解为组织数据的方式。
在我们的日常生活中,也有一些结构的概念,如 楼房,牛棚,排队的队列 等。我们希望通过一定组织,来提高查找效率。
这都是数据结构的意义,而数据结构是什么呢?
数据结构是计算机存储、组织数据的⽅式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由那部分构成,以什么方式构成,以及数据元素之间呈现的结构。
顺序表是基于数组的数据结构。
(二)顺序表
(1)顺序表的必要性
【思考】有了数组,为什么还要学习其他的数据结构?
求数组的长度,求数组的有效数据个数,向下标为数据有效个数的位置插入数据(注意:这里是否要判断数组是否满了,满了还能继续插入吗).....
假设数据量非常庞大,频繁的获取数组有效数据个数会影响程序执行效率。
结论:最基础的数据结构能够提供的操作已经不能完全满⾜复杂算法实现。
(2)顺序表的概念及结构
i,线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中⼴泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串......
线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
(3)顺序表的分类
i,顺序表和数组的区别:
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。
ii,顺序表分类
顺序表分为静态顺序表和动态顺序表;
a,静态顺序表
概念:使用固定长度的数组存储数据
typedef int SLDatetype;typedef struct SList
{SLDatatype a[N];//定长数组Size_t size;//记录有效数据个数
}SL;
b,动态顺序表
概念:按需申请,可增容
typedef struct SList
{SLDatatype* arr;size_t size;//有效数据个数size_t capacity;//容量空间
}SL;
通常情况下,动态顺序表的适应性更广,使用场景多。
(三)动态顺序表的实现:
这里不加解释,直接给出头文件,根据头文件的函数声明,实现顺序表的功能:初始化,销毁,打印,扩容,头插与头删,尾插与尾删,指定位置插入与删除,以及查找顺序表的某一元素。
(共11个功能)
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h>#define INIT_CAPACITY 4 typedef int SLDataType; // 动态顺序表 -- 按需申请 typedef struct SeqList {SLDataType* a;int size; // 有效数据个数int capacity; // 空间容量 }SL;//初始化和销毁 void SLInit(SL* ps); void SLDestroy(SL* ps); void SLPrint(SL* ps); //扩容 void SLCheckCapacity(SL* ps);//头部插⼊删除 / 尾部插⼊删除 void SLPushBack(SL* ps, SLDataType x); void SLPopBack(SL* ps); void SLPushFront(SL* ps, SLDataType x); void SLPopFront(SL* ps);//指定位置之前插⼊/删除数据 void SLInsert(SL* ps, int pos, SLDataType x); void SLErase(SL* ps, int pos); int SLFind(SL* ps, SLDataType x);
(1)头文件的解释:
i,宏定义作为动态申请的初始值
#define INIT_CAPACITY 4
ii,顺序表内部的数据统一替换为SLDatatype,便于以后修改后复用。
typedef int SLDataType;
iii,SeqList结构体就是顺序表,重命名为SL,便于使用。
typedef struct SeqList { SLDataType* a; int size; // 有效数据个数 int capacity; // 空间容量 }SL;
iv,其余为函数声明。
(2)手把手实现动态顺序表
a,初始化
初始化首先形参结构体指针不为空,并且将顺序表底层的数组置为空,有效数据个数和容量大小都初始为0。
void SL_init(SL* ps) {assert(ps);ps->arr = NULL;ps->capacity = ps->size = 0; }
b,销毁
在使用完毕后,需要将顺序表销毁,需要将动态申请的堆区空间释放掉,有效数据个数和容量大小置为0。
void SLDestroy(SL* ps) {assert(ps);if (ps->arr != NULL){free(ps->arr);}ps->capacity = 0;ps->size = 0; }
c,打印
在调试代码中,需要用打印来帮助测试函数的功能
void SLPrint(SL* ps) {for (int i = 0; i < ps->size; i++){printf("%d ", ps->arr[i]);}printf("\n"); }
d,扩容
确定顺序表的容量,检查是否需要扩容,如果需要,进行将当前容量的二倍的扩容;
如何检查?
数据是一个一个插入的,当有效数据个数等于容量的时候数据就满了,每一次插入数据,都进行一次容量是否足够的检查,若满了,就进行扩容。(插入数据都需要用此函数先检查)
第一次如何使用?
在传入NULL时,realloc函数的作用等同于malloc函数。
void SLCheckCapacity(SL* ps) {if (ps->capacity == ps->size){size_t Newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLtype* tem = (SLtype*)realloc(ps->arr, Newcapacity * sizeof(SLtype));if (tem == NULL){perror("realloc fail");exit(1);}ps->arr = tem;ps->capacity = Newcapacity;} }
e,头插与头删
头插与头删:意思是在顺序表头部插入或者删除数据,由于插入数据,需要检查顺序表有效数据个数,于是要调用SL_check_capacity()函数;并且由于多了一个数据,要想将新数据插入,必须将后面的原数据后移一位:
头插:
void SLPushFront(SL* ps, SLDataType x) {assert(ps);//空间不够,扩容SL_check_capacity(ps);//空间足够,从头插入for (int i = (int)ps->size ; i > 0 ; i--){ps->arr[i] = ps->arr[i - 1];}ps->arr[0] = x;ps->size++; }
后移这一操作也可以用memove函数实现,
(由于拷贝的两个空间有重叠部分,谨慎使用memcpy,memmove函数有分情况,可以解决这一问题)
这样就省去了写for循环时考虑循环变量的范围,以及边界是否取等,以及重叠拷贝的拷贝顺序的确定,的麻烦:
void SLPushFront(SL* ps, SLDataType x) {assert(ps);//空间不够,扩容SL_check_capacity(ps);//空间足够,从头插入memmove(&ps->arr[1],&ps->arr[0],ps->size*sizeof(SLDatatype));ps->arr[0] = x;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--; }
同样可以用memmove函数避免麻烦:
void SLPopFront(SL* ps) {assert(ps);assert(ps->size);memmove(&ps->arr[0],&ps->arr[1],sizeof(SLDatatype)*(ps->size-1));ps->size--; }
f,尾插与尾删
尾插与尾删:由于是在顺序表尾部插入或者删除数据,所以不需要进行数据移动,因此简单许多:
尾插:
只需要有效数据个数加1,并将数据x存入即可;
void SLPushBack(SL* ps, SLDataType x) {assert(ps);//空间不够,扩容SL_check_capacity(ps);//空间足够,直接插入ps->arr[ps->size++] = x;}
尾删:
只需要有效数据个数减少1即可;
void SLPopBack(SL* ps) {assert(ps);assert(ps->size);ps->size--; }
g,指定位置插入与删除
指定位置插入与删除:
断言形参有效,并且传入的位置在有效数据内部;检查容量是否需要扩容;同样可以用memmove函数省时省力。
指定位置插入:
void SLInsert(SL* ps, int pos, SLDataType x) {assert(ps);assert(posi >= 0 && posi < ps->size);SL_check_capacity(ps);for (int i = (int)ps->size - 1; i >= posi; i--){ps->arr[i + 1] = ps->arr[i];}ps->arr[posi] = x;ps->size++;}
删除指定位置:
void SLErase(SL* ps, int pos) {assert(ps);assert(posi >= 0 && posi < ps->arr);for (int i = (int)posi; i < ps->size - 1; i++){ps->arr[i] = ps->arr[i + 1];}ps->size--; }
h,顺序表查找某一元素
查找某一元素,若找到,返回顺序表(数组)下标;找不到,返回-1;
int SLFind(SL* ps, SLDataType x) {assert(ps);for (int i = 0; i < ps->size; i++){if (ps->arr[i] == x){return i;}}return -1; }
完~
未经作者同意禁止转载