动态顺序表
1. 顺序表简介
- 线性表的顺序存储是指在内存中用地址连续的一块存储空间 按照顺序存放线性表的各元素, 用这种存储形式进行存储的线性表被称为顺序表
- 顺序表是一种自定义的数据类型
- 往往通过结构体来实现
- 顺序表分为静态和动态两种
2. 动态顺序表的封装
利用C++的结构体来实现
typedef int SLDataType; // 便于顺序存储数据类型的更换, 示例为int
typedef struct SeqList
{
SLDataType* a; // 指针变量, 指向一个数组
int size; // 顺序表所存储元素的个数
int capacity; // 顺序表的容量
}Seq; // 起别名
3. 顺序表的初始化
void SLInit(Seq* s)
{
s->a = new SLDataType[sizeof(SLDataType) * 4]; // 定义一个数组, 使用new在堆区申请指定内存 , 申请了四个元素大小的内存
if (s->a == NULL)
{
perror("new failed"); // 检查, 初始化失败,打印标志
exit(-1); // 强制退出整个程序
;
}
s->capacity = 4; // 设置容量
s->size = 0; // 当前存储元素个数为0
}
4. 顺序表的增删改查
1> 向顺序表中插入元素
1) 头插法
void SLPushFront(Seq* s, SLDataType val)
{
if (s->size == s->capacity) // 判断顺序表是否已满
{
SLOpenCapacity(s); // 满了就扩容 自定义的顺序表扩容函数
for (int end = s->size - 1; end >= 0; end--)
{ // 插入元素部分
s->a[end + 1] = s->a[end];
}
s->a[0] = val;
s->size++;
}
else
{
for (int end = s->size - 1; end >= 0; end--)
{ // 插入元素部分
s->a[end + 1] = s->a[end];
}
s->a[0] = val;
s->size++;
}
}
2) 尾插法
void SLPushBack(Seq* s, SLDataType val)
{
if (s->size == s->capacity) // 判断顺序表是否已满
{
SLOpenCapacity(s); // 满了就扩容 自定义的顺序表扩容函数
s->a[s->size] = val; // 插入元素部分
s->size++;
}
else
{
s->a[s->size] = val; // 插入元素部分
s->size++;
}
}
3) 指定位置插入
void SLInsert(Seq* s, int pos, SLDataType val)
{
if (pos <= s->size&& pos >= 0) // 判断指定索引是否合法
{
if (s->size == s->capacity) // 判断顺序表是否已满
{
SLOpenCapacity(s); // 满了扩容
}
for (int i = s->size; i > pos; i--)
{ // 插入元素部分
s->a[i] = s->a[i - 1];
}
s->a[pos] = val;
s->size++;
}
else
{
cout << "请输入有效索引" << endl;
}
}
2> 删除顺序表中的元素
1) 头删法
void SLPopFront(Seq* s)
{
if (s->size > 0) // 判断顺序表是否为空
{
for (int begin = 1; begin < s->size; begin++)
{ // 删除元素部分
s->a[begin - 1] = s->a[begin];
}
s->size--;
}
else
{
assert(s->size > 0); // 使用断言函数提示程序运行错误
}
}
2) 尾删法
void SLPopBack(Seq* s)
{
if (s->size > 0) // 判断顺序表是否为空
{
s->size--; // 顺序表中元素的删除并非真的删除, 而是索引的变法, 使得体现在遍历顺序表中元素时,可视为已被删除
}
}
3) 删除指定位置元素
void SLErase(Seq* s, int pos)
{
if (pos >= 0 && pos < s->size) // 判断指定索引是否非法
{
for (int i = pos; i < s->size; i++)
{ // 删除元素部分
s->a[pos] = s->a[pos + 1];
}
s->size--;
}
}
4) 删除值为指定值的所有元素
void SLErase_P(Seq* s, SLDataType val)
{
if (s->size > 0)
{
for (int i = 0; i < s->size; i++)
{
if (s->a[i] == val)
{
for (; i < s->size; i++)
{
s->a[i] = s->a[i + 1];
}
s->size--;
}
}
}
}
3> 遍历顺序表
void SLPrint(Seq* s)
{
for (int i = 0; i < s->size; i++)
{
cout << s->a[i] << " ";
}
cout << endl;
}
4> 在顺序表中查找值为val的元素,
存在返回其索引, 不存在 返回 -1
int SLFindElement(Seq* s, SLDataType val)
{
for (int i = 0; i < s->size; i++)
{
if (s->a[i] == val)
return i;
}
return s->size;
}
5> 修改顺序表中指定位置的元素
void SLModify(Seq* s, int pos, SLDataType val)
{
if (pos >= 0 && pos < s->size)
{
s->a[pos] = val;
}
}
5. 顺序表容量的扩充
void SLOpenCapacity(Seq* s)
{
SLDataType* temp = new SLDataType[s->capacity * 2];
for (int i = 0; i < s->size; i++)
{
temp[i] = s->a[i];
}
delete[] s->a;
s->a = temp;
s->capacity *= 2;
}
6. 顺序表的销毁
void SLDestory(Seq* s)
{
if (s->a != NULL)
{
delete[] s->a;
s->a = NULL;
s->capacity = 0;
s->size = 0;
}
}
文中代码多次调用 自定义函数 SLDestory(Seq* s) ,用于顺序表容量不足时, 进行扩容。
7. 顺序表的优缺点
- 优点 :
- 元素支持随机访问, 排序后支持二分查找
- CPU高速缓存命中率比较高
- (也就是 物理上存储空间连续, 缓存利用率高)
- 缺点 :
- 头部或中间插入删除数据需要挪动数据, 效率低
- 空间不够,只能扩容, 扩容有消耗
- 原地扩容
- 异地扩容 (需要拷贝原数组)- 倍数扩容, 空间用不完, 存在空间的浪费