1. 数据结构相关概念
1.1 什么是数据结构
数据结构是由“数据”和“结构”两词组合而来。
什么是数据?
常见的数值1、2、3、4…、教务系统里保存的用户信息(姓名、性别、年龄、学历等等)、网页里肉眼可以看到的信息(文字、图片、视频等等),这些都是数据。
什么是结构?
当我们想要大量使用同⼀类型的数据时,通过手动定义大量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将⼤量的数据组织在⼀起,结构也可以理解为组织数据的方式。
概念: 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由哪部分构成,以什么方式构成,以及数据元素之间呈现的结构。
总结:
1)能够存储数据(如顺序表、链表等结构)
2)存储的数据能够方便查找
1.2 为什么需要数据结构
程序中如果不对数据进行管理,可能会导致数据丢失、操作数据困难、野指针等情况。通过数据结构,能够有效将数据组织和管理在⼀起。按照我们的方式任意对数据进行增删改查等操作。
最基础的数据结构:数组。
【思考】有了数组,为什么还要学习其他的数据结构?
假定数组有10个空间,已经使用了5个,向数组中插入数据的步骤:
- 求数组的长度,求数组的有效数据个数
- 向下标为数据有效个数的位置插入数据(注意:这里是否要判断数组是否满了,满了还能继续插入吗)
- 假设数据量非常庞大,频繁的获取数组有效数据个数会影响程序执行效率。
结论: 最基础的数据结构能够提供的操作已经不能完全满足复杂算法实现。
2. 顺序表的概念及结构
线性表:
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的⼀条直线;但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表:
逻辑结构是线性的、物理结构是连续的。
顺序表和数组的区别:
顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口。
3. 顺序表分类
- 静态顺序表
概念:使用定长数组存储元素
//静态顺序表 #define N 100 typedef int SLDataType;//顺序表中数组类型不一定是整型,如果要变为字符类型,就可以在这里直接改;如果不这样定义,就要在代码中改很多次 struct SeqList { SLDataType a[N];//定长数组 int size;//有效数据个数 };
静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费
- 动态顺序表
//动态顺序表 typedef int SLDataType; typedef struct SeqList { SLDataType* arr;//存储数据的底层结构 int capacity;//记录顺序表的空间大小 int size;//记录顺序表当前有效的数据个数 }SL; //typedef struct SeqList SL;
4. 实现动态顺序表
4.1 初始化
void SLInit(SL* ps) { assert(ps); ps->arr = NULL; ps->size = ps->capacity = 0; }
4.2 顺序表的尾部插入
void SLCheckCapacity(SL* ps) { if (ps->size == ps->capacity) { int newCapacity = 0 == ps->capacity ? 4 : 2 * ps->capacity; SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType)); if (NULL == tmp) { perror("realloc fail!"); exit(1); } //扩容成功 ps->arr = tmp; ps->capacity = newCapacity; } } void SLPushBack(SL* ps, SLDataType x) { //断言 -- 粗暴的解决方式 //assert(ps != NULL); assert(ps); //if判断 -- 温柔的解决方式 //if (NULL == ps) //{ // return; //} //空间不够,扩容 SLCheckCapacity(ps); //空间足够,直接插入 ps->arr[ps->size++] = x; //ps->size++; }
注:
扩容的原则:成倍数的增加(1.5倍、2倍)
4.3 打印顺序表
void SLPrint(SL* ps) { assert(ps); for (int i = 0; i < ps->size; i++) { printf("%d ", ps->arr[i]); } printf("\n"); }
4.4 顺序表的头部插入
void SLPushFront(SL* ps, SLDataType x) { assert(ps); //判断是否扩容 SLCheckCapacity(ps); //旧数据往后挪动一位 for (int i = ps->size; i > 0; i--) { ps->arr[i] = ps->arr[i - 1]; } ps->arr[0] = x; ps->size++; }
4.5 顺序表的尾部删除
void SLPopBack(SL* ps) { assert(ps); assert(ps->size); //顺序表不为空 ps->size--; }
4.6 顺序表的头部删除
void SLPopFront(SL* ps) { assert(ps); assert(ps->size); //不为空执行挪动操作 for (int i = 0; i < ps->size - 1; i++) { ps->arr[i] = ps->arr[i + 1]; } ps->size--; }
4.7 指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos >= 0 && pos <= ps->size); SLCheckCapacity(ps); //pos及之后的数据往后挪动一位,pos空出来 for (int i = ps->size; i > pos; i--) { ps->arr[i] = ps->arr[i - 1]; } ps->arr[pos] = x; ps->size++; }
4.8 删除指定位置数据
void SLErase(SL* ps, int pos) { assert(ps); assert(pos >= 0 && pos < ps->size); //pos以后的数据往前挪动一位 for (int i = pos; i < ps->size - 1; i++) { ps->arr[i] = ps->arr[i + 1]; } ps->size--; }
4.9 在顺序表中查找x
int SLFind(SL* ps, SLDataType x) { //加上断言对代码的健壮性更好 assert(ps); for (int i = 0; i < ps->size; i++) { if (x == ps->arr[i]) { return i; } } return -1; }
4.10 顺序表的销毁
void SLDestroy(SL* ps) { assert(ps); free(ps->arr); ps->arr = NULL; ps->size = ps->capacity = 0; }
完整代码:
//SeqList.h #include <stdio.h> #include <stdlib.h> #include <assert.h> 静态顺序表 // //#define N 100 // //typedef int SLDataType; // //struct SeqList //{ // SLDataType a[N]; // int size; //}; //动态顺序表 typedef int SLDataType; typedef struct SeqList { SLDataType* arr;//存储数据的底层结构 int capacity;//记录顺序表的空间大小 int size;//记录顺序表当前有效的数据个数 }SL; //typedef struct SeqList SL; //初始化和销毁 void SLInit(SL* ps); void SLDestroy(SL* ps); void SLPrint(SL* ps);//保持接口一致性 //顺序表的头部/尾部插入 void SLPushBack(SL* ps, SLDataType x); void SLPushFront(SL* ps, SLDataType x); //顺序表的头部/尾部删除 void SLPopBack(SL* ps); void SLPopFront(SL* ps); //指定位置之前插入数据 //删除指定位置数据 void SLInsert(SL* ps, int pos, SLDataType x); void SLErase(SL* ps, int pos); int SLFind(SL* ps, SLDataType x);
//SeqList.c #include "SeqList.h" //初始化和销毁 void SLInit(SL* ps) { assert(ps); ps->arr = NULL; ps->size = ps->capacity = 0; } void SLCheckCapacity(SL* ps) { if (ps->size == ps->capacity) { int newCapacity = 0 == ps->capacity ? 4 : 2 * ps->capacity; SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType)); if (NULL == tmp) { perror("realloc fail!"); exit(1); } //扩容成功 ps->arr = tmp; ps->capacity = newCapacity; } } //顺序表的头部/尾部插入 void SLPushBack(SL* ps, SLDataType x) { //断言 -- 粗暴的解决方式 //assert(ps != NULL); assert(ps); //if判断 -- 温柔的解决方式 //if (NULL == ps) //{ // return; //} //空间不够,扩容 SLCheckCapacity(ps); //空间足够,直接插入 ps->arr[ps->size++] = x; //ps->size++; } void SLPushFront(SL* ps, SLDataType x) { assert(ps); //判断是否扩容 SLCheckCapacity(ps); //旧数据往后挪动一位 for (int i = ps->size; i > 0; i--) { ps->arr[i] = ps->arr[i - 1]; } ps->arr[0] = x; ps->size++; } //顺序表的头部/尾部删除 void SLPopBack(SL* ps) { assert(ps); assert(ps->size); //顺序表不为空 ps->size--; } void SLPopFront(SL* ps) { assert(ps); assert(ps->size); //不为空执行挪动操作 for (int i = 0; i < ps->size - 1; i++) { ps->arr[i] = ps->arr[i + 1]; } ps->size--; } //指定位置之前插入数据 void SLInsert(SL* ps, int pos, SLDataType x) { assert(ps); assert(pos >= 0 && pos <= ps->size); SLCheckCapacity(ps); //pos及之后的数据往后挪动一位,pos空出来 for (int i = ps->size; i > pos; i--) { ps->arr[i] = ps->arr[i - 1]; } ps->arr[pos] = x; ps->size++; } //删除指定位置数据 void SLErase(SL* ps, int pos) { assert(ps); assert(pos >= 0 && pos < ps->size); //pos以后的数据往前挪动一位 for (int i = pos; i < ps->size - 1; i++) { ps->arr[i] = ps->arr[i + 1]; } ps->size--; } //在顺序表中查找x int SLFind(SL* ps, SLDataType x) { //加上断言对代码的健壮性更好 assert(ps); for (int i = 0; i < ps->size; i++) { if (x == ps->arr[i]) { return i; } } return -1; } void SLDestroy(SL* ps) { assert(ps); free(ps->arr); ps->arr = NULL; ps->size = ps->capacity = 0; } void SLPrint(SL* ps) { assert(ps); for (int i = 0; i < ps->size; i++) { printf("%d ", ps->arr[i]); } printf("\n"); }
//test.c #include "SeqList.h" void slTest01() { SL sl; SLInit(&sl); //测试尾插 SLPushBack(&sl, 1); SLPushBack(&sl, 2); SLPushBack(&sl, 3); SLPushBack(&sl, 4); SLPrint(&sl); //SLPushBack(&sl, 5); //SLPrint(&sl); //SLPushBack(NULL, 6); //头插 //SLPushFront(&sl, 5); //SLPushFront(&sl, 6); //SLPushFront(&sl, 7); //SLPrint(&sl); //尾删 //SLPopBack(&sl); //SLPopBack(&sl); //SLPopBack(&sl); //SLPopBack(&sl); //SLPrint(&sl); //头删 //SLPopFront(&sl); //SLPopFront(&sl); //SLPopFront(&sl); //SLPopFront(&sl); //SLPrint(&sl); //指定位置插入 //SLInsert(&sl, 0, 100); //SLPrint(&sl); //SLInsert(&sl, sl.size, 200); //SLPrint(&sl); //SLInsert(&sl, 100, 300); //SLPrint(&sl); //删除指定位置的数据 //SLErase(&sl, 0); //SLPrint(&sl); //SLErase(&sl, sl.size - 1); //SLPrint(&sl); SLErase(&sl, 1); SLPrint(&sl); } void slTest02() { SL sl; SLInit(&sl); //测试尾插 SLPushBack(&sl, 1); SLPushBack(&sl, 2); SLPushBack(&sl, 3); SLPushBack(&sl, 4); SLPrint(&sl); //测试查找 //int ret = SLFind(&sl, 3); int ret = SLFind(&sl, 30); if (ret < 0) { printf("数据不存在,查找失败!"); } else { printf("数据找到了,在下标为%d位置\n", ret); } } int main() { //slTest01(); slTest02(); return 0; }