1. 顺序表概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。
这里重点是数组,有两种顺序表:1.静态顺序表,2.动态顺序表。这里我只对动态的进行讲解。
2. 静态顺序表
2.1 框架
静态顺序表实现目的:用来存储信息;实现结构:首先静态的我们想到的连续的存储空间就是数组,所以其结构中必须有个数组,其次光有数组是不行的,我们顺序表实现肯定有增删查改的功能,假设我们实现一个增加的功能,那么我们要知道有多少元素了,然后对数组大小进行增加一个,那么这里就要知道数组的大小,也就是数组元素个数。
2.2 结构
#define MAXSIZE 100 #typedef int SequenceDataType; struct SequenceList{ int arr[MAXSIZE]; int size; };
2.3 缺点
- 空间满了需要手动设置
- 在实现删除增加功能时需要依次移动位置进行覆盖,效率低
- 程序跑完并不会留下数据(并不使用动态内存管理)
这里静态顺序表使用场景太少,并不适用,所以不对其进行说明,相信你读完后续的动态顺序表介绍对于你来说静态的就是小儿科
3. 动态顺序表
3.1 框架
首先需要动态开辟内存空间,可以保留数据,那么就要使用指针(也就相当于数组名),存储空间是连续的,还需要元素个数,使用动态内存开辟我们要考虑一个问题那就是是否扩容,所以要有结构容量。
3.2 结构
typedef int SLDataType; //对类型进行重命名,后续可以直接更改类型,来存储不同类型 typedef struct SequenceList{ SLDataType* a; int size; int capacity; }SL;//重命名结构体,方便后续书写
3.3 功能实现
3.3.1 前提
3.3.2 初始化动态顺序表
对结构内部进行改变,所以要拿到结构体的地址;指针置空,元素个数为0,容量为0
void SeqListInit(SL* ps) { assert(ps); ps->a = NULL; ps->size = 0; ps->capacity = 0; }
3.3.3 尾插数据
数组尾插数据,首先要考虑扩容问题,再对其尾部直接赋值,并且元素个数增加。(对结构体内部进行改变所以拿到结构体的地址)
void SeqListPushBack(SL* ps, SLDataType x) { assert(ps); //检查扩容问题 if (ps->size == ps->capacity) { int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//最开始状态size=capacity=0,所以先对容量开4个空间,后面直接倍数开辟 SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//考虑到扩容失败就用tmp暂时接收(realloc语法中如果此时ps->a=NULL,则此函数就相当于malloc) if (tmp == NULL) { perror("SeqListPushBack:(realloc):"); exit(-1); } ps->a = tmp;//ps->a指向开辟空间 ps->capacity = newcapacity;//结构体中容量调整 } ps->a[ps->size] = data;//给尾部数据 ps->size++;//元素个数增加 }
为什么拿tmp暂时接收?
原因:扩容有可能失败,就拿tmp暂时接收,为NULL就不给ps->a,直接结束程序,这样不易出现漏洞。
扩容有什么问题吗?
扩容会有两种扩容:原地扩容和异地扩容。也就是当我们对ps->a这个地址向后开辟空间时,如果后面空间小于我们需要开辟的空间,系统就会再找另外一块有充分空间的内存进行开辟,最后返回一个新的地址,并不是在ps->a原本位置进行开辟的。异地扩容会把之前开辟的空间释放,找新一块空间。并不影响什么但是要说明一下(不理解可以看看相关realloc知识,你也可以测试)
3.3.4 尾删数据
直接删除最后一个数据,也就是元素个数减少一个(对结构体内部进行改变所以拿到结构体的地址)
void SeqListPopBack(SL* ps) { assert(ps); //检查没有数据还Pop情况 assert(ps->size > 0); //再此使用时直接覆盖了,不需要另外创建变量来存储再置为0 ps->size--; }
3.3.5 头插数据
首先检查是否需要扩容,再需要对数组的每个元素依次向后移动一位,再在第一个位置上插入一个数据。(对结构体内部进行改变所以拿到结构体的地址)
void SeqListPushFront(SL* ps, SLDataType x) { assert(ps); //检查扩容问题 if (ps->size == ps->capacity) { int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//最开始状态size=capacity=0,所以先对容量开4个空间,后面直接倍数开辟 SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//考虑到扩容失败就用tmp暂时接收(realloc语法中如果此时ps->a=NULL,则此函数就相当于malloc) if (tmp == NULL) { perror("SeqListPushBack:(realloc):"); exit(-1); } ps->a = tmp;//ps->a指向开辟空间 ps->capacity = newcapacity;//结构体中容量调整 } //依次移动位置进行覆盖 int end = ps->size - 1; while (end >= 0) { ps->a[end + 1] = ps->a[end]; end--; } //第一个位置上插入数据 ps->a[0] = x; ps->size++;//元素个数增加 }
移动顺序是后到前:这样数据就不会丢失或者被覆盖
时间复杂度:O(N)
3.3.6 头删数据
直接依次向前移动数据即可(对结构体内部进行改变所以拿到结构体的地址)
void SeqListPopFront(SL* ps) { assert(ps); //检查没有元素还Pop情况 assert(ps->size > 0); //依次向前移动位置覆盖 int begin = 1; while (begin < ps->size) { ps->a[begin - 1] = ps->a[begin]; begin++; } ps->size--;//元素个数减少一个 }
==移动顺序是后到前:这样数据就不会丢失或者被覆盖
时间复杂度:O(N)
3.3.7 在任意位置插入数据
首先检查扩容,再对该位置之后的数据依次移动位置覆盖,再插入数据(对结构体内部进行改变所以拿到结构体的地址)
void SeqListInsert(SL* ps, int pos, SLDataType x) { assert(ps); //位置判断 assert(pos >= 0); //可在尾部插入 assert(pos <= ps->size); //检查扩容问题 if (ps->size == ps->capacity) { int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//最开始状态size=capacity=0,所以先对容量开4个空间,后面直接倍数开辟 SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));//考虑到扩容失败就用tmp暂时接收(realloc语法中如果此时ps->a=NULL,则此函数就相当于malloc) if (tmp == NULL) { perror("SeqListPushBack:(realloc):"); exit(-1); } ps->a = tmp;//ps->a指向开辟空间 ps->capacity = newcapacity;//结构体中容量调整 } //对pos后数据依次向后移动一位 int end = ps->size - 1; while (end >= pos) { ps->a[end + 1] = ps->a[end]; end--; } //插入x数据 ps->a[pos] = x; ps->size++; }
时间复杂度:O(N)
3.3.8 在任意位置删除数据
对该位置之后的数据依次移动位置覆盖即可(对结构体内部进行改变所以拿到结构体的地址)
void SeqListErase(SL* ps, int pos) { assert(ps); //删除不能越界 assert(pos >= 0); assert(pos < ps->size); // 挪动数据覆盖 int begin = pos + 1; while (begin < ps->size) { ps->a[begin - 1] = ps->a[begin]; begin++; } ps->size--; }
如果删除只有一个元素的数组可以吗?
可以的,你会发现只是执行了ps->size–操作,这样就不会找到这个数据了。
对最后一个数据进行删除可以吗?
可以的,同样只是执行了ps->size–操作。
3.3.9 从任意位置查找数据
就是从该位置向后遍历数据,找到返回下标,未找到返回-1.
int SeqListFind(SL* ps, SLDataType x, int begin) { assert(ps); //限制begin起点 assert(begin <= ps->size-1); assert(begin >= 0); //从begin开始,找到返回下标,没找到返回-1 for (int i = begin; i < ps->size; ++i) { if (x == ps->a[i]) { return i; } } return -1; }
为什么未找到返回-1呢?
这里可以随意返回,这里的-1也不是最合适的选择,假设数据中有-1的话,那么就有冲突,这里只是个样例,实际上并可以搞个提示信息,并不返回。
3.3.10 打印数据
void SeqListPrint(SL* ps) { assert(ps); for (int i = 0; i < ps->size; ++i) { printf("%d ", ps->a[i]); } printf("\n"); }
3.3.11 销毁顺序表
只有不为空的情况下销毁,否则不会销毁。(对结构体内部进行改变所以拿到结构体的地址)
void SeqListDestroy(SL* ps) { assert(ps); //不为空的情况下销毁 if (ps->a) { free(ps->a); ps->a = NULL; ps->size = 0; ps->capacity = 0; } else { perror("Sequence is NULL,can not destory!"); exit(-1); } }
3.4 优化改进
3.4.1 尾插数据优化
void SeqListPushBack(SL* ps, SLDataType x) { assert(ps); //对ps->size下标出尾插 SeqListInsert(ps, ps->size, x); }
3.4.2 尾删数据优化
void SeqListPopBack(SL* ps) { assert(ps); //检查没有数据还Pop情况 assert(ps->size > 0); //对ps->size-1下标数据删除 SeqListErase(ps, ps->size - 1); }
3.4.3 头插数据优化
void SeqListPushFront(SL* ps, SLDataType x) { assert(ps); //下标为0出插入数据 SeqListInsert(ps, 0, x); }
3.4.4 头删数据优化
void SeqListPopFront(SL* ps) { assert(ps); assert(ps->size > 0); //对下标为0出数据删除 SeqListErase(ps, 0); }
感谢大家观看,有很多不足可以私信讨论,嘻嘻!