动态顺序表
经过以上分析,在程序运行的过程中,静态顺序表的表容量是不可修改的,导致存储空间有时会不够用,也有时会空余比较多,我们更希望可以有动态申请的空间的方法,实现顺序表容量的动态化。
C语言/C++ 中提供了动态申请空间的库函数(malloc)关键字(new),因此我们可以利用他们实现顺序表的动态化
此处使用C语言的malloc函数
类型定义
#include <iostream>
#include <assert.h>
#include <stdlib.h>
#define InitSize 10//默认最大长度
typedef struct {
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
int* data; //指向动态分配数组的指针
}SeqList;
由于malloc函数返回的是所申请空间的地址,因此我们需要用指针来接受,还是那句话,为了方便起见,我们用 int* 来接收
初始化
void InitList(SeqList& L) {
L.data = (int*)malloc(sizeof(int) * InitSize); //指向动态申请的空间
assert(L.data);
L.length = 0; //未加入数据前,初始长度为0
L.MaxSize = InitSize; //设置最大长度
}
我们传入了相应顺序表的引用,引用可以规避函数形参的二级指针,这点还是很方便的。
assert 断言函数,当申请空间失败时,该函数可以帮助我们抛出异常,此函数再之后的文章中会经常出现。
申请完空间后,此时的数组还未存储数据,因为我们设定当前容量为0 ,并且规定数组的最大长度为初设设定的长度。
读到这里,读者不难发现一些问题。初始化时,只不过是将申请的内存放在了堆区,数组的大小依然是预先设置好的,并不能实现运行过程中的动态内存。这便是接下来要讨论的问题。
动态内存,那我们就要充分利用动态,我们可以在程序的运行过程中动态的给数组进行扩容。通过 if 语句判断当前的容量是否超标,然后进行扩容。
扩容与插入
一般情况下,扩容往往是在插入元素时发生的。
扩容函数
//动态给顺序表扩容
void IncreaseLen(SeqList& L, int len) {
int* p = L.data;
L.data = (int*)malloc(sizeof(int)* (len+L.MaxSize));
assert(L.data);
//转移元素
for (int i = 0; i < L.length; i++) {
L.init[i] = p[i];
}
p = NULL;
free(p);
L.MaxSize = L.MaxSize + len;
}
我们先用p指针记录下之前空间的地址,然后再用malloc函数申请一块更大的空间,参数len接收的是要增大的容量。扩容后,要将原来空间内的元素转移到新的空间中,再将原来的堆区的空间释放掉,
最后标记数组的最大空间已改变
有了扩容函数,我们便可以配合插入函数来使用
由于插入后需要移动大量的元素,该操作经常使用,我们可以将其抽象成一个函数来经常调用。
元素移动后,直接插入 ele即为待插入的元素
插入
void Insertele_1(SeqList& L, int i, int ele) {//将第i个及其后面的元素后移 j从最后一个元素(下标为length-1)开始
for (int j = L.length-1; j >= i - 1; j--) // 需要移动的最后一个元素的下标为(i-1)
L.data[j + 1] = L.data[j];
L.data[i-1] = ele; //第i个元素的数组下标为 i-1
L.length++;
}
//在顺序表的 第 i 个位置上插入一个元素
bool Insertele_2(SeqList& L, int i, int ele) {
if (i<1 || i>L.length + 1)
return false;
if (L.length == L.MaxSize) {
IncreaseLen(L, 1);
Insertele_1(L, i, ele);
return true;
}
Insertele_1(L, i, ele);
return true;
}
此时,Insertele_2 函数(实现插入功能)的结构就十分清晰了
- 判断要插入的位置是否合法
- 判断数组的容量是否满了, 满了就扩容(调用扩容函数)后再插入(调用实现插入操作的函数)
- 未满的话直接插入
该数组的空间是由我们程序员动态分配的,因此可以多出一些小操作,例如销毁。
小操作
//销毁线性表
void DestroySqList(SeqList& L) {
if (L.data)
free(L.data);
}
// 清空线性表
void ClaerSqList(SeqList& L) {
L.length = 0;
}
//求线性表长度
int GetLength(SeqList& L) {
return L.length;
}
//判断是否非空
bool EmptyJudge(SeqList& L) {
if (L.length)
return false;
return true;
}
//获取线性表中 第i个元素 函数直接返回第 i 个元素
int Getele(SeqList& L, int i) {
if (i < 1 || i>L.length)
return -1;
return L.data[i - 1];
}
以上操作原理和实现较为容易,此处不再赘述。
删除和遍历
//删除顺序表上第 i 个元素 并返回
bool Delele(SeqList& L, int i, int& ele) {
if (i<1 || i>L.length)
return false;
ele = L.data[i - 1];
for (int j = i-1; j <= L.length-1; j++) {
L.data[j] = L.data[j+1];
}
L.length--;
return true;
}
//遍历顺序表
void printSqlist(SeqList& L) {
for (int i = 0; i < L.length; i++) {
printf("%d ", L.data[i]);
}
printf("\n");
}
动态顺序表和静态顺序表的增删查改区别并不大,不同的就是动态顺序表在插入的时候可以添加扩容函数从而实现数组长度的动态化。
动态和静态的优缺点都是相同的,详情可以看上篇文章,这里简单提到。
优点
- 存储密度大(数据本身的存储空间/结点所占的存储空间)
- 随机访问 我们在访问表中的元素时只需传入值或者位序即可,访问速度较快
缺点
- 在插入,删除相应的元素时,需要移动大量的元素,插入和删除操作的时间复杂度较高。
- 静态存储,初始化的表的大小难以更改,数据元素的更熟不能任意扩充。
综合优缺点,顺序表适合存储元素数量相对固定,访问较多,而插入和删除较少的线性数据类型。
所有代码
#include <iostream>
#include <assert.h>
#include <stdlib.h>
#define InitSize 10//默认最大长度
typedef struct {
int MaxSize; //顺序表的最大容量
int length; //顺序表的当前长度
int* data; //指向动态分配数组的指针
}SeqList;
void InitList(SeqList& L) {
L.data = (int*)malloc(sizeof(int) * InitSize); //指向动态申请的空间
assert(L.data);
L.length = 0; //未加入数据前,初始长度为0
L.MaxSize = InitSize; //设置最大长度
}
//动态给顺序表扩容
void IncreaseLen(SeqList& L, int len) {
int* p = L.data;
L.data = (int*)malloc(sizeof(int)* (len+L.MaxSize));
assert(L.data);
//转移元素
for (int i = 0; i < L.length; i++) {
L.init[i] = p[i];
}
p = NULL;
free(p);
L.MaxSize = L.MaxSize + len;
}
void Insertele_1(SeqList& L, int i, int ele) {//将第i个及其后面的元素后移 j从最后一个元素(下标为length-1)开始
for (int j = L.length-1; j >= i - 1; j--) // 需要移动的最后一个元素的下标为(i-1)
L.data[j + 1] = L.data[j];
L.data[i-1] = ele; //第i个元素的数组下标为 i-1
L.length++;
}
//在顺序表的 第 i 个位置上插入一个元素
bool Insertele_2(SeqList& L, int i, int ele) {
if (i<1 || i>L.length + 1)
return false;
if (L.length == L.MaxSize) {
IncreaseLen(L, 1);
Insertele_1(L, i, ele);
return true;
}
Insertele_1(L, i, ele);
return true;
}
//销毁线性表
void DestroySqList(SeqList& L) {
if (L.data)
free(L.data);
}
// 清空线性表
void ClaerSqList(SeqList& L) {
L.length = 0;
}
//求线性表长度
int GetLength(SeqList& L) {
return L.length;
}
//判断是否非空
bool EmptyJudge(SeqList& L) {
if (L.length)
return false;
return true;
}
//获取线性表中 第i个元素 函数直接返回第 i 个元素
int Getele(SeqList& L, int i) {
if (i < 1 || i>L.length)
return -1;
return L.data[i - 1];
}
//删除顺序表上第 i 个元素 并返回
bool Delele(SeqList& L, int i, int& ele) {
if (i<1 || i>L.length)
return false;
ele = L.data[i - 1];
for (int j = i-1; j <= L.length-1; j++) {
L.data[j] = L.data[j+1];
}
L.length--;
return true;
}
//遍历顺序表
void printSqlist(SeqList& L) {
for (int i = 0; i < L.length; i++) {
printf("%d ", L.data[i]);
}
printf("\n");
}
如果本文对你有帮帮助,还请点赞收藏哦!