何为顺序表?
如果用一组连续的存储单元依次存放线性表中的所有元素,则称该类型的线性表为顺序表。其特点是元素的存储位置与逻辑位置一一对应,因此顺序表通常用数组来表示。
顺序表的定义:
1、静态顺序表:使用定长数组存储元素
- 缺陷:给小了不够用,给大了可能浪费,非常不实用
#define N 100
typedef int sldatatype; //后续要存储其它类型时方便更改
typedef struct slNode{
sldatatype a[N]; //定长顺序表
size_t len; //顺序表的长度(元素个数)
}seqlist;
2、动态顺序表:使用动态开辟的数组存储元素(以下操作用动态顺序表做)
- 动态顺序表可根据我们的需要分配空间大小
- len 表示当前顺序表中已存放的数据个数
- capacity 表示顺序表总共能够存放的数据个数
typedef struct slNode{ sldatatype *a; //指向动态开辟的数组 size_t len; //数据个数 size_t capacity;//容量大小 }seqlist;
顺序表的初始化:
void seqlistInit(seqlist*sl)
{
assert(sl!=NULL);//断言,防止传进来的指针为空
/*
if(sl==NULL){
cout<<"您的顺序表为空"<<endl;
break;
};
*/
sl->a==NULL; //初始顺序表为空
sl.len=0; //初始数据个数为0
sl.capacity=0; //初始空间容量为0
}
顺序表的销毁释放:
void seqlistDestroy(seqlist *sl)
{
assert(sl!=NULL);//断言
free(sl->a); //释放动态开辟的空间
sl->a = NULL; //置空
sl->len = 0;
sl->capacity = 0;
}
检查顺序表容量是否已满,进行增容
为什么不采取插一个数据,增容一个空间的方式呢,因为这样也太麻烦了,代价也太大了,一般情况下,为了避免频繁的增容,当空间满了后,我们不会一个一个的去增,而是一次增容 2 倍,当然也不会一次增容太大,比如 3 倍 4 倍,空间可能会浪费,2 倍是一个折中的选择。
void checkCapacity(seqlist *sl)
{
assert(sl!=NULL);
if(sl->len==sl->capacity)
{
size_t newCapacity;
if(sl->capacity==0)
newCapacity = sl->capacity =4;
else
newCapacity = sl->capacity * 2;
//因为sl->a不能直接开辟,因此借助于p进行开辟
sldatatype *p = (sldatatype *)realloc(sl->a,newCapacity*sizeof(sldatatype));
if(p==NULL)
{
perror("realloc出错");
exit(-1);
}
sl->a = p; //p不为空,开辟成功
sl->capacity = newcapacity; //更新容量
}
}
顺序表尾插
void seqlistPushback(seqlist* sl, sldatatype x)
{
//判断是否为空
assert(sl != NULL);
//判断是否容量已满,若满进行开辟
checkCapacity(sl);
sl->a[sl->len] = x; //尾插数据
sl->len++;
}
顺序表尾删
void seqlistPopback(seqlist *sl)
{
assert(sl !=NULL);
assert(sl->len != 0);
sl->len--; //有效数据-1
//不知道sldatatype是什么类型的数据,不能冒然的赋值为0
//psl->a[psl->size - 1] = 0;
}
顺序表头插
void seqlistPushfront(seqlist* sl , sldatatype x)
{
assert(sl != NULL);
checkCapacity(sl);
//错误代码
//错误分析,思路:将i位置的值赋给i+1位置的值:。
//第一次后,i的值假设为5,则i+1的值为5,之后就将5往后传递,从而后面值都为5
/*int i = 0;
for (; i < sl->len; i++)
{
sl->a[i + 1] = sl->a[i];
}*/
int i = 0;
for (i = sl->len; i > 0; i--) {
sl->a[i] = sl->a[i - 1];
}
sl->a[0] = x;
sl->len++;
}
顺序表头删
void seqlistPopfront1(seqlist* sl) {
assert(sl != NULL);
assert(sl->len != 0);
for (int i = 1; i < sl->len; i++) {
sl->a[i - 1] = sl->a[i];
}
sl->len--;
}
打印顺序表
void seqlistPrint(const seqlist* sl)
{
assert(sl != NULL);
if (sl->len == 0)
{
cout << "顺序表为空" << endl;
return;
}
else {
int i = 0;
for (; i < sl->len; i++)
{
cout << sl->a[i] << endl;
}
cout << endl;
return;
}
}
在顺序表中查找指定值
int seqlistFind(const seqlist* sl,sldatatype x)
{
assert(sl != NULL);
for (int i = 0; i < sl->len; i++) {
if (sl->a[i] == x)
return i;
}
return -1;
}
在顺序表指定下标位置插入数据
void seqlistInsert(seqlist* sl, size_t pos,sldatatype x)
{
assert(sl!=NULL); //断言
assert(pos >0 && pos <= sl->len+1); //检查pos下标的合法性
checkCapacity(sl); //检查顺序表容量是否已满
size_t i = 0;
for (i = sl->len; i >= pos; i--) //将pos位置后面的数据依次向后挪动一位
{
sl->a[i] = sl->a[i - 1];
}
sl->a[pos-1] = x; //插入数据
sl->len++; //有效数据个数+1
}
//头插改进
void seqlistPushfront2(seqlist* sl,sldatatype x)
{
seqlistInsert(sl, 1, x);
}
在顺序表中删除指定下标位置的数据
void seqlistErase(seqlist* sl, size_t pos)
{
assert(sl); //断言
assert(sl->len > 0); //顺序表不能为空
assert(pos > 0 && pos <= sl->len); //检查pos下标的合法性
size_t i = 0;
for (i = pos ; i <= sl->len; i++) //将pos位置后面的数据依次向前挪动一位
{
sl->a[i - 1] = sl->a[i];
}
sl->len--; //有效数据个数-1
}
查看顺序表中有效数据个数
可能大家会有一个疑问,我在主函数里面直接通过定义的结构体变量直接访问就好了呀,为啥还要弄一个函数嘞?
在数据结构中有一个约定,如果要访问或修改数据结构中的数据,不要直接去访问,要去调用它的函数来访问和修改,这样更加规范安全,也更方便检查是否出现了越界等一些错误情况。
但越界不一定报错,系统对越界的检查是一种抽查
越界读一般是检查不出来的
越界写如果是修改到标志位才会检查出来
(系统在数组末尾后设的有标志位,越界写时,恰好修改到标志位了,就会被检查出来)
size_t seqlistLen(const seqlist* sl)
{
assert(sl); //断言
return sl->len;
}
修改指定下标位置的数据
void seqlistModify(seqlist* sl, size_t pos, sldatatype x)
{
assert(sl); //断言
assert(sl->len > 0); //顺序表不能为空
assert(pos > 0 && pos <= sl->len); //检查pos下标的合法性
sl->a[pos-1] = x; //修改pos下标处对应的数据
}
完整代码:(vs2022运行成功)
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
#define N 100
typedef int sldatatype; //后续要存储其它类型时方便更改
typedef struct slNode {
sldatatype* a; //指向动态开辟的数组
size_t len; //数据个数
size_t capacity;//容量大小
}seqlist;
//检查顺序表容量是否满了,好进行增容
void checkCapacity(seqlist* sl)
{
assert(sl != NULL);
if (sl->len == sl->capacity)
{
size_t newCapacity;
if (sl->capacity == 0)
newCapacity = sl->capacity = 4;
else
newCapacity = sl->capacity * 2;
//因为sl->a不能直接开辟,因此借助于p进行开辟
//realloc包含了已有的数据信息,会进行拷贝
sldatatype* p = (sldatatype*)realloc(sl->a, newCapacity * sizeof(sldatatype));
if (p == NULL)
{
perror("realloc出错");
exit(-1);
}
sl->a = p; //p不为空,开辟成功
sl->capacity = newCapacity; //更新容量
}
}
//初始化顺序表
void seqlistInit(seqlist* sl)
{
assert(sl != NULL);//断言,防止传进来的指针为空
/*
if(sl==NULL){
cout<<"您的顺序表为空"<<endl;
break;
};
*/
sl->a = NULL; //初始顺序表为空
sl->len = 0; //初始数据个数为0
sl->capacity = 0; //初始空间容量为0
}
//销毁(释放)顺序表
void seqlistDestroy(seqlist* sl)
{
assert(sl != NULL);//断言
free(sl->a); //释放动态开辟的空间
sl->a = NULL; //置空
sl->len = 0;
sl->capacity = 0;
}
//顺序表尾插
void seqlistPushback(seqlist* sl, sldatatype x)
{
//判断是否为空
assert(sl != NULL);
//判断是否容量已满,若满进行开辟
checkCapacity(sl);
sl->a[sl->len] = x; //尾插数据
sl->len++;
}
//顺序表打印
void seqlistPrint(const seqlist* sl)
{
assert(sl != NULL);
if (sl->len == 0)
{
cout << "顺序表为空" << endl;
return;
}
else {
int i = 0;
for (; i < sl->len; i++)
{
cout << sl->a[i] << endl;
}
cout << endl;
return;
}
}
//顺序表尾删
void seqlistPopback(seqlist* sl)
{
assert(sl != NULL);
assert(sl->len != 0);
sl->len--; //有效数据-1
//不知道sldatatype是什么类型的数据,不能冒然的赋值为0
//psl->a[psl->size - 1] = 0;
}
//顺序表头插1
void seqlistPushfront1(seqlist* sl, sldatatype x)
{
assert(sl != NULL);
checkCapacity(sl);
//错误代码
//错误分析,思路:将i位置的值赋给i+1位置的值:。
//第一次后,i的值假设为5,则i+1的值为5,之后就将5往后传递,从而后面值都为5
/*int i = 0;
for (; i < sl->len; i++)
{
sl->a[i + 1] = sl->a[i];
}*/
int i = 0;
for (i = sl->len; i > 0; i--) {
sl->a[i] = sl->a[i - 1];
}
sl->a[0] = x;
sl->len++;
}
//顺序表头删1
void seqlistPopfront1(seqlist* sl) {
assert(sl != NULL);
assert(sl->len != 0);
for (int i = 1; i < sl->len; i++) {
sl->a[i - 1] = sl->a[i];
}
sl->len--;
}
//在顺序表中查找指定值,删除指定值,在此基础上进行操作
int seqlistFind(const seqlist* sl, sldatatype x)
{
assert(sl != NULL);
for (int i = 0; i < sl->len; i++) {
if (sl->a[i] == x)
return i;
}
return -1;
}
//在顺序表指定下标位置插入数据,可进行头插和尾插
void seqlistInsert(seqlist* sl, size_t pos, sldatatype x)
{
assert(sl != NULL); //断言
assert(pos > 0 && pos <= sl->len + 1); //检查pos下标的合法性
checkCapacity(sl); //检查顺序表容量是否已满
size_t i = 0;
for (i = sl->len; i >= pos; i--) //将pos位置后面的数据依次向后挪动一位
{
sl->a[i] = sl->a[i - 1];
}
sl->a[pos - 1] = x; //插入数据
sl->len++; //有效数据个数+1
}
//头插改进2
void seqlistPushfront2(seqlist* sl, sldatatype x)
{
seqlistInsert(sl, 1, x);
}
//在顺序表中删除指定下标位置的数据
void seqlistErase(seqlist* sl, size_t pos)
{
assert(sl); //断言
assert(sl->len > 0); //顺序表不能为空
assert(pos > 0 && pos <= sl->len); //检查pos下标的合法性
size_t i = 0;
for (i = pos; i <= sl->len; i++) //将pos位置后面的数据依次向前挪动一位
{
sl->a[i - 1] = sl->a[i];
}
sl->len--; //有效数据个数-1
}
//顺序表头删2
void seqlistPopfront2(seqlist* sl)
{
seqlistErase(sl, 0); //改造头删接口
}
//查看顺序表中有效数据个数
size_t seqlistLen(const seqlist* sl)
{
assert(sl); //断言
return sl->len;
}
//修改指定下标位置的数据
void seqlistModify(seqlist* sl, size_t pos, sldatatype x)
{
assert(sl); //断言
assert(sl->len > 0); //顺序表不能为空
assert(pos > 0 && pos <= sl->len); //检查pos下标的合法性
sl->a[pos - 1] = x; //修改pos下标处对应的数据
}
int main()
{
seqlist s1;
seqlistInit(&s1);
//尾插数据
seqlistPushback(&s1, 5);
seqlistPushback(&s1, 3);
seqlistPushback(&s1, 4);
seqlistPushback(&s1, 6);
seqlistPushback(&s1, 8);
seqlistPushback(&s1, 0);
seqlistPrint(&s1);
//尾删除
seqlistPopback(&s1);
seqlistPrint(&s1);
//头插
seqlistPushfront1(&s1, 101);
seqlistPrint(&s1);
//头删
seqlistPopfront1(&s1);
seqlistPopfront1(&s1);
seqlistPrint(&s1);
//查询指定元素
cout << "查询5元素的位置:" << seqlistFind(&s1, 5) << endl;
cout << endl;
//指定位置插入元素
seqlistInsert(&s1, 3, 666);
seqlistPrint(&s1);
//在顺序表中删除指定下标位置的数据
seqlistErase(&s1, 3);
seqlistPrint(&s1);
//查看顺序表中有效数据个数
cout << "查看顺序表中有效数据个数:" << seqlistLen(&s1) << endl << endl;
//修改指定下标位置的数据
seqlistModify(&s1, 2, 999);
seqlistPrint(&s1);
//指定位置插入元素,可以进行尾插
seqlistInsert(&s1, 5, 666);
seqlistPrint(&s1);
}
参考:
1.(1条消息) C++中的assert用法_喜欢吃冰棍de谷利文君的博客-CSDN博客_c++ assert
2. 大部分以这篇文章为核心,有些小地方进行了改进(基本关于postion的问题)(1条消息) 【数据结构入门】顺序表(SeqList)详解(初始化、增、删、查、改)_CodeWinter的博客-CSDN博客_seqlist
3.(1条消息) 动态内存分布——malloc,calloc,realloc,free的使用。以及关于动态内存的常见错误。_我的代码爱吃辣的博客-CSDN博客
4.(1条消息) c++编程之perror()_kangshuangzhu的博客-CSDN博客_c++ perror
5.(c++建议还是使用visual studio)vscode配置C/C++环境(超级详细)Day2022/12/02_DreamSuif的博客-CSDN博客_vscode配置c/c++环境