顺序表(SequenceList)数据结构的基本操作实现详解
Posted SuchABigBug
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了顺序表(SequenceList)数据结构的基本操作实现详解相关的知识,希望对你有一定的参考价值。
一、前言
数据结构非常之多,后续小编会逐个更新,简单的存储数据一般有顺序表,链表(单链表和双链表),串等
创建完数据结构后不仅要存储数据,以后还需要查找数据,如果高效的进行查找也需要搜索树,哈希表等这样的结构来实现
有很多人有这样一个疑问:是先学数据结构还是先学算法?
其实数据结构和算法是不分家的,数据结构中包含一些算法,有些算法的解决又离不开数据结构,比如实现Dijkstra最短路径问题是基于Graph的数据结构实现的
二、整体设计框架
实现顺序表,我们可以分为三个文件,这样可读性更高,如果所有函数都在一个文件里会显得臃肿,其次不便于调试,之前实现 扫雷游戏也是按照图中框架来设计
- test.c专门用于函数调用、调试
- sequenceList.h 只用于函数声明
- sequenceList.c 用于函数实现
三、函数实现
首先我们看一下头文件,顺序表有哪些函数需要实现以及结构的设计。
//typedef char SeqDataType;
typedef int SeqDataType;
typedef struct SeqList{
SeqDataType* data;
int size; //当前数组有效数据个数
int capacity; //总容量大小,不够则进行扩容
}SLT;
void SeqListInit(SLT* list);
这里的重新定义了一个SeqDataType方便后续类型的修改,可能是int,可能是char,也可能是float类型的数据
然后接着定义了一个SeqList结构,里面的第一个成员用于数据存储,第二个size和下面的capacity不要搞混淆了,前者是表示当前有多少个有效数据个数,后者是容器说明一共能存储多少数据,如果不够则进行扩容
顺序表一般都采用动态的,因为你不知道用户需要用多少空间
1. SeqListInit
我们在 sequenceList.c 中对SeqListInit函数进行初始化
void SeqListInit(SLT* list){
assert(list);
list->data = NULL;
list->size = 0;
list->capacity = 0;
}
这里我们的函数中都需要进行一下断言,防止用这个函数的人不小心传了一个NULL进来,反之不判断deubg需要花很多时间
ps: 记得#include <assert.h>
2. SeqListDestory
用完之后记得free开辟的动态空间,并把data置空
void SeqListDestroy(SLT* s){
if(s->data){
free(s->data);
s->data = NULL;
}
s->capacity = 0;
s->size = 0;
}
3. SeqListCheckCapacity
我们在进行push操作之前需要确认一下容量是否为空,如果为则默认分配4个空间大小,如果不为空在原来容量基础上扩容二倍。
为什么是二倍不是三倍四倍?
这是防止开多了空间浪费,后续讲解的链表可以很好的解决这个问题
//检查容量是已满
void SeqListCheckCapacity(SLT* s){
assert(s);
size_t sz = s->capacity == 0 ? 4: (s->capacity)*2;
if(s->size == s->capacity){
SeqDataType* newSize = (SeqDataType*)realloc(s->data, sizeof(SeqDataType)*sz);
if(newSize!=NULL){
s->data = newSize;
}else{
printf("Failed to realloc \\n");
}
}
}
4. SeqListPushBack(尾插)
此函数就直接调用SeqListCheckCapacity来检测容量大小
//尾插
void SeqListPushBack(SLT* s ,SQDataType x){
assert(s);
SeqListCheckCapacity(s);
s->arr[s->size] = x;
s->size++;
}
5. SeqListPopBack(尾删)
注意⚠️ :这里我们要判断一下size的大小,如果size已经为0了,那么必须提醒用户,如果不加这一句判断,删过头编译器是不会报错的,后续就不便于debug咯
void SeqListPopBack(SLT* s){
assert(s);
assert(s->size>0);
s->size--;
}
6. SeqListPushFront(头插)
这里的头插需要注意数据向后挪的顺序,顺序是从最后一个数据开始向后挪动,从前开始向后挪会导致数据被覆盖
void SeqListPushFront(SLT* s, SeqDataType x){
assert(s);
SeqListCheckCapacity(s);
for(int i=s->size; i>0; --i){
s->data[i] = s->data[i-1];
}
s->data[0] = x;
s->size++;
}
7. SeqListPopFront(头删)
和上面一样,需要判断注意一下size的大小,头删的顺序也需要注意,写程序的时候下标也需要注意一下不要越界哦
至此线性顺序表的增删已经完成✅ ,不难发现数组的缺点就显现出来了,我们需要头插和头删所需要的时间复杂度是O(N),想象一下,有一个非常长的数组,如果需要频繁的头插头删那么效率就会有所下降,链表可以很好的解决这个问题!
void SeqListPopFront(SLT* s){
assert(s);
assert(s->size>0);
for(int i=1; i<s->size; ++i){
s->data[i-1] =s->data[i];
}
s->size--;
}
8. SeqlistFind
查找某个值的下表位置,找不到该值则返回-1
int SeqlistFind(SLT* s, SeqDataType x){
assert(s);
for(int i=0; i<s->size; ++i){
if(s->data[i] == x ){
return i;
}
}
printf("Cannot find it \\n");
return -1;
}
9. SeqListInsert(任意位置插入)
如果在第pos个位置插入数据,后面的数据需要向后挪动
其次,我们要注意循环体里的int i,如果size为0再-1,那么int type会发生整形提升,因此我们用size_t保险一点
//在pos的位置进行插入
void SeqListInsert(SLT* s, size_t pos, SeqDataType x){
assert(s);
assert(s->size >= pos);
SeqListCheckCapacity(s);
/*
如果为零,就会发生错 i>=pos 中的i发生了提升
总之,我们需要避免负数给到无符号避免有符号数变成负数以后被强制提升成有符号
*/
// for(int i=s->size-1; i>=pos; i--){
// s->data[i+1] = s->data[i];
// }
for(size_t i=s->size; i>pos; --i){
s->data[i] = s->data[i-1];
}
s->data[pos] = x;
s->size++;
}
9. SeqListErase(任意位置删除)
void SeqListErase(SLT* s, size_t pos){
assert(s);
assert(s->size>0);
for(size_t i = (pos+1); i<s->size; ++i){
s->data[i-1] = s->data[i];
}
s->size--;
}
10. SeqListAt
void SeqListAt(SLT* s, size_t pos, SeqDataType x){
assert(s);
assert(s->size > pos);
s->data[pos] = x;
}
11. SeqListSize
size_t SeqListSize(SLT* s){
assert(s);
return s->size;
}
四、完整代码
下面附上测试代码及函数实现代码
1. test.c 测试代码
//
// test.c
// SequenceList
//
// Created by Henry on 2021/8/17.
// Copyright © 2021 Henry. All rights reserved.
//
#include "sequenceList.h"
void TestFunc1(){
SLT s;
SeqListInit(&s);
SeqListInit(&s);
SeqListPushBack(&s,1);
SeqListPushBack(&s,2);
SeqListPushBack(&s,5);
SeqListPushBack(&s,7);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
SeqListPushFront(&s, 100);
SeqListPushFront(&s, 200);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
SeqListInsert(&s, 1, 5);
SeqListPrint(&s);
SeqlistFind(&s, 100);
//找到这个值
int findThisNumber = SeqlistFind(&s, 2);
//然后修改这个位置的值
SeqListAt(&s, findThisNumber, 7);
SeqListPrint(&s);
SeqListErase(&s, 0);
SeqListPrint(&s);
SeqListDestroy(&s);
}
int main(int argc, const char * argv[]) {
TestFunc1();
return 0;
}
2. sequenceList.h 头文件
//
// sequenceList.h
// SequenceList
//
// Created by Henry on 2021/8/17.
// Copyright © 2021 Henry. All rights reserved.
//
#ifndef sequenceList_h
#define sequenceList_h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#endif /* sequenceList_h */
typedef int SeqDataType;
typedef struct SeqList{
SeqDataType* data;
int size; //当前数组有效数据个数
int capacity; //总容量大小,不够则进行扩容
}SLT;
void SeqListInit(SLT* s);
void SeqListDestroy(SLT* s);
void SeqListPushBack(SLT* s, SeqDataType x);
void SeqListPopBack(SLT* s);
void SeqListPushFront(SLT* s, SeqDataType x);
void SeqListPopFront(SLT* s);
void SeqListPrint(SLT* s);
//查找顺序表中的一个数
int SeqlistFind(SLT* p, SeqDataType x);
void SeqListInsert(SLT* p, size_t pos, SeqDataType x);
void SeqListErase(SLT* p, size_t pos);
size_t SeqListSize(SLT* p);
void SeqListAt(SLT* p, size_t pos, SeqDataType x);
3. sequenceList.c 函数实现
//
// sequenceList.c
// SequenceList
//
// Created by Henry on 2021/8/17.
// Copyright © 2021 Henry. All rights reserved.
//
#include "sequenceList.h"
void SeqListInit(SLT* list){
assert(list);
list->data = NULL;
list->size = 0;
list->capacity = 0;
}
void SeqListDestroy(SLT* s){
if(s->data){
free(s->data);
s->data = NULL;
}
s->capacity = 0;
s->size = 0;
}
void SeqListCheckCapacity(SLT* s){
assert(s);
size_t sz = s->capacity == 0 ? 4: (s->capacity)*2;
if(s->size == s->capacity){
SeqDataType* newSize = (SeqDataType*)realloc(s->data, sizeof(SeqDataType)*sz);
if(newSize!=NULL){
s->data = newSize;
}else{
printf("Failed to realloc \\n");
}
}
}
void SeqListPushBack(SLT* s, SeqDataType x){
assert(s);
SeqListCheckCapacity(s);
s->data[s->size] = x;
s->size++;
}
void SeqListPrint(SLT* s){
for(int i=0; i<s->size;++i){
printf("%d ", s->data[i]);
}
printf("\\n");
}
void SeqListPopBack(SLT* s){
assert(s);
assert(s->size>0);
s->size--;
}
void SeqListPushFront(SLT* s, SeqDataType x){
assert(s);
SeqListCheckCapacity(s);
for(int i=s->size; i>0; --i){
s->data[i] = s->data[i-1];
}
s->data[0] = x;
s->size++;
}
void SeqListPopFront(SLT* s){
assert(s);
assert(s->size>0);
for(int i=1; i<s->size; ++i){
s->data[i-1] =s->data[i];
}
s->size--;
}
int SeqlistFind(SLT* s, SeqDataType x){
assert(s);
for(int i=0; i<s->size; ++i){
if(s->data[i] == x ){
return i;
}
}
printf("Cannot find it \\n");
return -1;
}
//在pos的位置进行插入
void SeqListInsert(SLT* s, size_t pos, SeqDataType x){
assert(s);
assert(s->size >= pos);
SeqListCheckCapacity(s);
/*
如果为零,就会发生错 i>=pos 中的i发生了提升
总之,我们需要避免负数给到无符号避免有符号数变成负数以后被强制提升成有符号
*/
// for(int i=s->size-1; i>=pos; i--){
// s->data[i+1] = s->data[i];
// }
for(size_t i=s->size; i>pos; --i){
s->data[i] = s->data[i-1];
}
s->data[pos] = x;
s->size++;
}
void SeqListErase(SLT* s, size_t pos){
assert(s);
assert(s->size>0);
for(size_t i = (pos+1); i<s->size; ++i){
s->data[i-1] = s->data[i];
}
s->size--;
}
size_t SeqListSize(SLT* s){
assert(s);
return s->size;
}
void SeqListAt(SLT* s, size_t pos, SeqDataType x){
assert(s);
assert(s->size > pos);
s->data[pos] = x;
}
肝了一个小时手把手实现,加调试,如果觉得还不错快给俺点个赞👍 ,come on pls~
以上是关于顺序表(SequenceList)数据结构的基本操作实现详解的主要内容,如果未能解决你的问题,请参考以下文章