"海阔凭鱼跃,天高任鸟飞"
作者:Mylvzi
文章主要内容:顺序表讲解
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表是线性表的一种,是一种用来存储相同类型数据元素的结构,通过顺序表可以实现对存储元素的增删查改
分类:静态顺序表-->无法增容(不常用)
动态顺序表-->可以增容(广泛使用)
那么如何创建一个顺序表呢?我们知道顺序表是用来管理相同数据类型元素的集合,那就要知道所管理数据的基本信息,我们要知道都有哪些数据以及数据的类型(int,float,char.......),有效数据个数,以及容量(便于扩容) ,所以要使用结构体来管理链表的这些信息;
静态顺序表就是利用定长数组存储数据的顺序表
//静态顺序表 无法增容
#define N 10
typedef int SLDataType;//顺序表数据类型
typedef struct SeqList {
SLDataType arr[N];//使用数组存放链表中的元素
int size;//定义有效数据个数
}SeqList;
创建思路:
前提操作:创建结构体存储所要管理数据的基本信息,
顺序表相关:初始化顺序表,销毁顺序表,打印顺序表,管理顺序表(增删查改),判断是否需要扩容;
//定义结构体变量存储数据的相关信息(放在头文件之中)
typedef int SLDataType;//类型设置为SLDataType是为了便于修改类型
typedef struct SeqList {
SLDataType* a;//指向动态开辟的内存(指明数据类型) a就是数组的地址
int size;//有效数据个数
int capacity;//空间大小
}SL;//定义了一个具有struct SeqList类型的结构体SL;
//头文件 函数声明
//初始化顺序表
void SLInit(SL* ps);//使用结构体指针来初始化顺序表
//删除顺序表
void SLDestroy(SL* ps);
//打印顺序表
void SLPrint(SL* ps);
//函数定义
//初始化顺序表
void SLInit(SL* ps)//ps是一个结构体指针变量
{
//都要检查传递的指针是否为NULL
assert(ps);
ps->a = (SLDataType*)malloc(sizeof(SLDataType)*4);//动态开辟内存
if (ps->a == NULL)
{
perror("malloc");
exit(-1);
}
ps->size = 0;//初始化有效数据个数
ps->capacity = 4;//初始化空间大小
}
//删除顺序表
void SLDestroy(SL* ps)
{
//都要检查传递的指针是否为NULL
assert(ps);
free(ps->a);//删除顺序表。及时释放内存即可,不要忘了置空指针
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
//打印顺序表
void SLPrint(SL* ps)
{
//都要检查传递的指针是否为NULL
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
尾插:
在原先链表末尾插入一个相同类型的数据;插入,size++;
//尾插
void SLPushBack(SL* ps, SLDataType x);//传两个数据,第一个是链表对应的结构体指针,第二个是相同类型的元素
//函数定义
void SLPushBack(SL* ps, SLDataType x)
{
//都要检查传递的指针是否为NULL
assert(ps);
检查是否满容
//SLCheckCapacity(ps);
插入数据
//ps->a[ps->size] = x;
//ps->size++;//插入后,有效数据增多
//复用
SLInsert(ps, ps->size, x);
}
尾删:
删除链表的末尾的数据,怎么去删除一个数据呢?不需要置为0(因为本来就可能是0,没有意义),直接size--即可,代表有效数据的删除,不需要管删除的数据;
注意:
1.删除的时候有可能删除完毕(size=0),这也是不规范的,因为这个内存再也无法使用了;
2.不能删完了再添加数据,删着删到数组的下标为负数(有可能越界访问,越界访问不一定会报错),所以要检查是否删完,检查有两种方式,温柔检查(退出程序),暴力检查(assert)
3.删除后不用free,因为根本就不能free部分动态内存
//尾删
void SLPopBack(SL* ps);
//函数定义
//尾删
void SLPopBack(SL* ps)
{
//都要检查传递的指针是否为NULL
assert(ps);
//温柔检查
//if (ps->size == 0)
//{
// return ;
//}
暴力检查(推荐)
//assert(ps->size > 0);//只要size==0就报错
//ps->size--;//直接让有效数据减一即可
//复用
SLErase(ps, ps->size - 1);
}
头插:
在顺序表头部位置插入数据,但是又不能在开头开辟空间,所以只能挪动数据,从后往前挪动数据(避免被覆盖)所以顺序表头插的效率不高(O(N));
头删:
在顺序表头部位置删除元素;从前往后挪动数据即可(要记得暴力检查)
不能a++‘不能动的动态内存开辟的初始地址
//头插头删
//头插
void SLPushFront(SL* ps);
//头删
void SLPopFront(SL* ps);
//函数定义
//头插头删
//头插
void SLPushFront(SL* ps,SLDataType x)
{
//都要检查传递的指针是否为NULL
assert(ps);
插入数据都要检查是否满容
//SLCheckCapacity(ps);
挪动数据-->从后往前挪动
//int end = ps->size - 1;
//while (end)
//{
// ps->a[end + 1] = ps->a[end];
// end--;
//}
将头部位置赋值为x
//ps->a[0] = x;
//ps->size++;
//复用
SLInsert(ps, 0, x);
}
//头删
void SLPopFront(SL* ps)
{
//都要检查传递的指针是否为NULL
assert(ps);
暴力检查
//assert(ps->size > 0);
挪动数据
//int begin = 0;
//while (begin < ps->size - 1)//size-1是最后一个有效数据的索引
//{
// ps->a[begin] = ps->a[begin + 1];
//}
//ps->size--;
//复用
SLErase(ps, 0);
}
在pos位置插入数据:
前面的不动,后面的整体往后移
思路:检查顺序表是否存在,pos位置是否合理,检查是否满容,从后往前挪动数据,插入元素
删除pos位置的元素
检查顺序表是否存在,pos位置是否合理,暴力检查,删除之后从前往后挪动数据
SLFind函数:
不知道具体下标,在数组内找到寻找数据的下标
//插入数据
// 在pos位置插入x(pos就是数组下标)
void SLInsert(SL* ps, int pos, SLDataType x);
//找到具体元素的下标,再插入元素
int SLFind(SL* ps, SLDataType x);
// 删除pos位置的值
void SLErase(SL* ps, int pos);
//函数定义
//插入数据
// 在pos位置插入x
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);//检查顺序表是否存在
assert(pos >= 0 && pos <= ps->size);//pos的位置不能越界
SLCheckCapacity(ps);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end+1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
//找到具体元素的下标,再插入元素
int SLFind(SL* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
// 删除pos位置的值
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);//删除时,pos不能是size
//暴力检查
assert(ps->size > 0);
//删除数据
int begin = pos + 1;
while (begin < ps->size)
{
ps->a[begin-1] = ps->a[begin];
begin++;
}
ps->size--;
}
注意: 在完成SLInsert函数和SLErase函数之后,可以在直接在头插头删,尾插尾删函数之中替换复用,大大减少代码量
修改pos位置的值
//修改pos位置的值
void SLModify(SL* ps, int pos, SLDataType x);
//函数定义
//修改pos位置的值
void SLModify(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
管理数据时需要着重注意以下两个方面:
删除元素:暴力检查-->assert(ps->size > 0)
增加元素:是否满容-->SLCheckCapacity
/判断是否需要扩容
void SLCheckCapacity(SL* ps);
//函数定义
//判断是否需要扩容
void SLCheckCapacity(SL* ps)
{
//都要检查传递的指针是否为NULL
assert(ps);
//size >= capacity时需要扩容,使用realloc函数
if (ps->size == ps->capacity)
{
//扩容一般扩为原来的两倍
SLDataType* tmp = (SLDataType*)realloc(ps->a, ps->capacity * 2 * (sizeof(SLDataType)));
if (tmp == NULL)
{
perror("realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
}
以上就是顺序表的相关内容,顺序表是学习数据结构的基础,要好好体验动态顺序表的创建过程,在编写过程中做到函数命名规范,写一段代码就监测的好习惯