目录
1. 引言
数据结构是计算机科学中的重要基础,而顺序表作为一种简单而常用的数据结构,为我们解决实际问题提供了便利。顺序表将数据元素按照逻辑顺序依次存储在一块连续的存储空间中,具有存储空间利用率高、元素访问速度快和支持随机访问等优势。在实际应用中,顺序表可以用来解决各种问题,如图书馆管理系统和学生成绩管理系统。
然而,顺序表的插入和删除操作较慢,并且大小固定,需要进行扩容。综合考虑其优势和限制,我们可以合理运用顺序表来组织和管理数据,提高工作效率。在接下来的博客中,我们将深入探讨顺序表的实现原理、应用场景以及优化方法,帮助读者更好地理解和应用这一重要的数据结构。
2. 定义顺序表的结构体
此片文章讨论的是动态的顺序表,因此,在定义的时候此顺序表中会由三个变量:
- 第一个是一个任意元素类型的指针;
- 第二个是记录此顺序表的容量;
- 第三个是记录当前顺序表中含有几个元素。
代码示例如下:
typedef int SLDataType; typedef struct SeqList { SLDataType* a; //任意元素类型(此处以int为例) int size; // 当前顺序表有多少元素 int capacity; //顺序表的总容量 }SL;
3. 顺序表的初始化
使用顺序表,首先就是要给顺序表初始化,初始化的时候需要给:
- 当前顺序表的容量随意给个值;
- 上述定义的元素类型动态开辟一个空间(用malloc或者是realloc函数);
- 其次就是把 0 赋给 size。
代码如下:
void InitSeqList(SL* ps) { assert(ps); ps->capacity = 4; SLDataType* tmp = (SLDataType*)malloc(capacity * sizeof(SLDataType)); if (tmp != NULL) { ps->a = tmp; } else { perror("InitSeqList -> malloc:"); return; } ps->size = 0; }
4. 打印顺序表
顺序表的打印思路较为简单,直接遍历以下就好,直接上代码:
void SLPrint(SL* ps) { int i = 0; for (i = 0; i < ps->size; i++) { printf("%d ", ps->a[i]); } printf("\n"); }
5. 顺序表头尾位置的插入和删除
5.0 关于扩容函数
当顺序表需要插入元素的时候,都要首先判断一下顺序表的空间是否满了,即:
ps -> size == ps -> capacity
如果满了就需要扩容,那么如何扩容呢? 思路如下:
- 首先判断上述代码是否为真;
- 之后再让新的容两变为原来的两倍;
- 其次再使用realloc函数增加空间;
- 判断realloc函数返回值;
具体代码如下:
void CheckCapacity(SL* ps) { if (ps->capacity == ps->size) { int newcapacity = 2 * (ps->capacity); SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity); if (tmp != NULL) { ps->capacity = newcapacity; ps->a = tmp; } else { perror("CheckCapacity -> realloc:"); return; } } }
5.1 顺序表的尾插
顺序表的尾插步骤如下:
- 首先需要判断当前顺序表元素是否满了,需要调用上方的CheckCapacity函数;
- 之后就可与直接在size的位置插入数据,size继续加1即可;
代码如下:
void test2() { SL sl; InitSeqList(&sl); SLPushBack(&sl, 1); SLPushBack(&sl, 2); SLPushBack(&sl, 3); SLPushBack(&sl, 4); SLPushBack(&sl, 5); SLPushBack(&sl, 6); SLPushBack(&sl, 7); SLPrint(&sl); } void SLPushBack(SL* ps, SLDataType x) { assert(ps); CheckCapacity(ps); ps->a[ps->size] = x; ps->size++; }
运行结果如下:
5.2 顺序表的头插
顺序表头插步骤如下:
- 首先还是要判断顺序表元素是否满了,是否要进行扩容;
- 其次是要将所有元素向后移动一个位置;
- 最后再首元素位置插入一个元素。
代码如下:
void SLPushFront(SL* ps, SLDataType x) { assert(ps); CheckCapacity(ps); int end = ps->size - 1; while (end >= 0) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[0] = x; ps->size++; } void test3() { SL sl; InitSeqList(&sl); SLPushFront(&sl, 1); SLPushFront(&sl, 2); SLPushFront(&sl, 3); SLPushFront(&sl, 4); SLPushFront(&sl, 5); SLPushFront(&sl, 6); SLPushFront(&sl, 7); SLPrint(&sl); }
代码解释如下图:
运行结果如下:
5.3 顺序表的尾删
顺序表尾删步骤如下:
- 首先要判断此时顺序表的元素是否为空;
- 之后直接让ps -> size 减1 就好;
代码实现如下:
void SLPopBack(SL* ps) { assert(ps); assert(ps->size > 0); ps->size--; } void test3() { SL sl; InitSeqList(&sl); SLPushFront(&sl, 1); SLPushFront(&sl, 2); SLPushFront(&sl, 3); SLPushFront(&sl, 4); SLPushFront(&sl, 5); SLPushFront(&sl, 6); SLPushFront(&sl, 7); SLPrint(&sl); SLPopBack(&sl); SLPrint(&sl); }
运行结果如下:
5.4 顺序表的头删
顺序表的头删步骤如下:
- 判断顺序表是否为空;
- 将所有的元素向前移动一位;
- 让ps->size 减1。
代码实现如下:
void SLPopFront(SL* ps) { assert(ps); assert(ps->size > 0); int begin = 0; while (begin < ps->size - 1) { ps->a[begin] = ps->a[begin + 1]; begin++; } ps->size--; } void test3() { SL sl; InitSeqList(&sl); SLPushFront(&sl, 1); SLPushFront(&sl, 2); SLPushFront(&sl, 3); SLPushFront(&sl, 4); SLPushFront(&sl, 5); SLPushFront(&sl, 6); SLPushFront(&sl, 7); SLPrint(&sl); SLPopFront(&sl); SLPrint(&sl); }
代码解释如下图:
代码运行结果如下:
6. 顺序表在任意位置的插入和删除
6.1 顺序表在任意位置的插入
顺序表在任意位置插入步骤如下:
- 首先要判断这个位置是否满足: assert(pos >= 0 && pos <= (ps -> size)
- 其次,要将此位置之后的元素全部向后移动一个
- 最后,在此位置插入元素,并将size + 1。
代码如下:
void SLInsert(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos >= 0 && pos <= ps->size); int end = ps->size - 1; while (end >= pos) { ps->a[end + 1] = ps->a[end]; end--; } ps->a[pos] = x; ps->size++; } void test4() { SL sl; InitSeqList(&sl); SLInsert(&sl, 0, 1); SLInsert(&sl, 0, 2); SLPrint(&sl); }
代码终止条件解释图如下:
代码运行结果如下:
6.2 顺序表在任意位置的删除
顺序表在任意位置的删除步骤如下:
- 首先要判断此时顺序表中是否有元素;
- 其次要判断删除的位置是否满足:pos >= 0 && pos < ps -> size;
- 最后要将此位置之后的所有元素向前移动一个位置,将size--;
具体代码如下:
void SLEras(SL* ps, int pos) { assert(ps); assert(ps->size > 0); assert(pos >= 0 && pos < ps->size); int begin = pos; while (begin < ps->size) { ps->a[begin] = ps->a[begin + 1]; begin++; } ps->size--; } void test4() { SL sl; InitSeqList(&sl); SLInsert(&sl, 0, 1); SLInsert(&sl, 0, 2); SLPrint(&sl); SLEras(&sl, 0); SLPrint(&sl); }
代码终止条件图如下:
代码运行结果如下:
7. 顺序表的查找
顺序表的查找直接遍历就好,如果有就返回下标,没有就返回-1。
具体代码如下:
int SLFind(SL* ps, SLDataType x) { assert(ps); assert(ps->size > 0); int i = 0; while (i < ps->size) { if (ps->a[i] == x) { return i; } i++; } return -1; } void test4() { SL sl; InitSeqList(&sl); SLInsert(&sl, 0, 1); SLInsert(&sl, 0, 2); SLPrint(&sl); SLEras(&sl, 0); SLPrint(&sl); int ret =SLFind(&sl, 1); printf("%d", ret); }
代码运行结果如下:
8. 顺序表的释放
顺序表的内存是动态开辟的,因此需要释放,具体代码如下:
void SLDestroy(SL* ps) { assert(ps); if (ps->a != NULL) { free(ps->a); ps->a = NULL; } ps->capacity = 0; ps->size = 0; }
9. 顺序表的优缺点
顺序表作为一种常用的数据结构,具有以下优点:
存储空间利用率高:顺序表将数据元素按照逻辑顺序连续存储在一块内存中,不需要额外的指针来连接各个元素,因此存储空间利用率高。
元素访问速度快:由于顺序表中的元素在内存中是连续存放的,可以通过下标快速定位到对应的数据,因此元素的访问速度很快。
支持随机访问:由于顺序表支持通过下标直接访问元素,因此可以随机访问任意位置的元素,而不需要从头开始遍历。
然而,顺序表也存在一些缺点:
插入和删除操作较慢:当需要在顺序表中插入或删除元素时,需要进行数据的搬移操作,时间复杂度较高。
大小固定,需要扩容:顺序表的大小是固定的,一旦存储空间不够,就需要进行扩容操作,这会带来一定的开销。
综上所述,顺序表作为一种简单而高效的数据结构,在实际应用中具有广泛的应用。但在使用时需要注意其插入和删除操作的效率,并合理规划存储空间大小,以充分发挥其优势和避免其限制。
10. 结语
顺序表的存储空间利用率高、元素访问速度快以及支持随机访问,使其在实际应用中具有重要的作用。然而,顺序表的插入和删除操作较慢,并且大小固定,需要进行扩容。在实际应用中,我们需要根据具体情况来选择合适的数据结构,以提高工作效率。