目录
一.概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
顺序表一般可以分为:
1.静态顺序表:使用定长数组存储
2.动态顺序表:使用动态开辟的数组存储(而我们常用的就是动态顺序表)
二.动态顺序表的实现
1.用结构体定义一个顺序表
//动态顺序表的实现
typedef int SLDataType; //定义SLDataType为int型,方便改写数组中存储的数据类型
typedef struct SeqList
{
SLDataType* data;
int size; //数组的有效长度
int capacity; //数组的容量
}SL;
2.顺序表的初始化
先定义出一个空的顺序表
//顺序表的初始化
//这里先不malloc,在扩容里面进行malloc
void SeqListInit(SL* p)
{
p->data = NULL;
p->size = p->capacity = 0; //数组有效长度和容量都为0
}
3.顺序表的扩容
扩容用一个函数单写出来:在顺序表中插入数据时都需要对表的容量进行判断,当插入时直 接调用会方便很多。
只要是插入数据的操作都要调用这个函数进行判断是否需要扩容
//扩容
void SeqListCheckCapacity(SL* p)
{
if (p->capacity == p->size)
{
//第一次扩容当表为空时,新空间为4 不为空时,容量扩为原来的2倍
int newcapacity = p->capacity == 0 ? 4 : p->capacity * 2; //用三目操作符来开辟新的空间
SLDataType* tem = (SLDataType*)realloc(p->data, sizeof(SLDataType) * newcapacity); //当p->data为NULL时,realloc就相当于malloc
if (tem == NULL)
{
printf("realloc fail\n");
exit(-1);
}
p->capacity = newcapacity;
p->data = tem;
}
}
4.尾插
//尾插
void SeqListPshBack(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps); //判断容量,是否需要扩容
ps->a[ps->size] = x; //将x插入最后面
ps->size++; //有效长度++
}
5.尾删
尾删:比较简单,直接将有效长度减一就行
这里使用assert断言,如果顺序表删空了,就会直接报错
用assert的好处:能直观的看出哪里报错(头文件:#include<assert.h>)
//尾删
void SeqListPopBack(SL* ps)
{
assert(ps->size > 0); //用assert断言,顺序表删空时直接报错
ps->size--;
}
要熟练并且习惯的使用assert(减少代码调试时间)
如下图:能直观的展现出是哪个地方出错了(这里是删空了)
6.头插
头插:把顺序表中的所有数据往后移动一格,空出来第一个位置,插入数据
//头插
void SeqListPushFront(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps); //判断是否需要增加空间
int end = ps->size - 1;
while (end >= 0) //从后往前进行挪动数据
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x; //将数据插入头部
ps->size++; //有效长度++
}
7.头删
头删:删除第一个数据,后面的数据往前移动一格
//头删
void SeqListPopFront(SL* ps)
{
assert(ps->size > 0); //防止删空,用assert进行判断
int begin = 1;
while (begin < ps->size) //从前往后挪动数据
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--; //有效长度减一
}
8.查找一个值的位置
遍历顺序表,找到这个值,返回下标
//查找一个值的位置
int SeqListFind(SL* ps, SLDataType x)
{
int begin = 0;
while (begin < ps->size) //遍历顺序表
{
if (x == ps->a[begin])
{
return begin;
}
begin++;
}
return -1;
}
9.在pos处插入一个值(这个函数可以替代头插,尾插)
这个函数可以配合上面查找一个值的位置使用(在查找处插入pos)
//在pos处插入x 这个函数可以替代头插和尾插,可以直接调用这个函数
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(pos >= 0 && pos <= ps->size); //断言判断pos的位置是否合法
SeqListCheckCapacity(ps); //判断是否需要增加空间
int end = ps->size;
while (pos <= end) //挪动数据
{
ps->a[end] = ps->a[end - 1];
end--;
}
ps->a[pos] = x;
ps->size++; //有效长度++
}
10.删除pos位置的值 (这个函数可以替代头删,尾删)
这个函数可以配合上面查找一个值的位置使用(删除pos位置的值)
//删除pos的位置 这个函数可以替代掉头删和尾删,直接调用这个函数
void SeqListErase(SL* ps, int pos)
{
assert(pos >= 0 && pos < ps->size); //断言判断pos的位置是否合法
while (pos < ps->size) //挪动数据
{
ps->a[pos - 1] = ps->a[pos];
pos++;
}
ps->size--; //有效长度--
}
11.打印函数
//打印
void SeqListPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
12.释放空间
用完顺序表后要进行空间的释放
//用完后要释放空间
void SeqListDestory(SL* ps)
{
if (ps) //ps不为空
{
free(ps->a);
ps->size = 0;
ps->capacity = 0;
}
}
总结
- 我个人认为这个扩容函数写的很有意思,每次插入只需要调用扩容函数,去判断顺序表的空间
- 要习惯使用assert进行断言,方便我们找到错误
- 从上面过程也能直观的体现出,顺序表在插入和删除时需要挪动大量数据,效率不高
全部代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
//动态顺序表
typedef struct SeqList
{
SLDataType* a;
int size; //表示数组中储存了多少个数据
int capacity; //数组实际能存数据的空间的容量是多大
}SL;
//判断容量,来进行扩充
void SeqListCheckCapacity(SL* ps)
{
//如果没有空间或者空间不足,那么我们就扩容
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
}
//顺序表的初始化
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = ps->capacity = 0;
}
//尾插
void SeqListPshBack(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps); //判断容量,是否需要扩容
ps->a[ps->size] = x; //将x插入最后面
ps->size++; //有效长度++
}
//尾删
void SeqListPopBack(SL* ps)
{
assert(ps->size > 0); //用assert断言,顺序表删空时直接报错
ps->size--;
}
//头插
void SeqListPushFront(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps); //判断是否需要增加空间
int end = ps->size - 1;
while (end >= 0) //从后往前进行挪动数据
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x; //将数据插入头部
ps->size++; //有效长度++
}
//头删
void SeqListPopFront(SL* ps)
{
assert(ps->size > 0); //防止删空,用assert进行判断
int begin = 1;
while (begin < ps->size) //从前往后挪动数据
{
ps->a[begin - 1] = ps->a[begin];
begin++;
}
ps->size--;
}
//查找一个值的位置
int SeqListFind(SL* ps, SLDataType x)
{
int begin = 0;
while (begin < ps->size)
{
if (x == ps->a[begin])
{
return begin;
}
begin++;
}
return -1;
}
//在pos处插入x 这个函数可以替代头插和尾插,可以直接调用这个函数
void SeqListInsert(SL* ps, int pos, SLDataType x)
{
assert(pos >= 0 && pos <= ps->size);
//判断是否需要增加空间
SeqListCheckCapacity(ps);
int end = ps->size;
//挪动数据
while (pos <= end)
{
ps->a[end] = ps->a[end - 1];
end--;
}
ps->a[pos] = x;
ps->size++;
}
//删除pos的位置 这个函数可以替代掉头删和尾删,直接调用这个函数
void SeqListErase(SL* ps, int pos)
{
assert(pos >= 0 && pos < ps->size);
while (pos < ps->size)
{
ps->a[pos - 1] = ps->a[pos];
pos++;
}
ps->size--;
}
//打印
void SeqListPrint(SL* ps)
{
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//用完后要释放空间
void SeqListDestory(SL* ps)
{
if (ps) //ps不为空
{
free(ps->a);
ps->size = 0;
ps->capacity = 0;
}
}
int main()
{
SL s1 = { 0 };
SeqListInit(&s1);
printf("尾插插入数据:");
SeqListPshBack(&s1, 1);
SeqListPshBack(&s1, 2);
SeqListPshBack(&s1, 3);
SeqListPshBack(&s1, 4);
SeqListPrint(&s1);
printf("尾删一个数据:");
SeqListPopBack(&s1);
SeqListPrint(&s1);
printf("头插两个数据:");
SeqListPushFront(&s1, 10);
SeqListPushFront(&s1, 20);
SeqListPrint(&s1);
printf("头删一个数据:");
SeqListPopFront(&s1);
SeqListPrint(&s1);
printf("查找2的位置(下标):%d\n", SeqListFind(&s1, 2));
printf("在2处插入100:");
SeqListInsert(&s1, 2, 100);
SeqListPrint(&s1);
printf("删除100:");
SeqListErase(&s1, 3);
SeqListPrint(&s1);
//释放空间
SeqListDestory(&s1);
return 0;
}
感谢大家的观看,希望你能从这篇文章中学到一些东西(如有错误,提醒我,我会及时修改)
谢谢大家!!!