❀❀❀ 文章由@不准备秃的大伟原创 ❀❀❀
♪♪♪ 若有转载,请联系博主哦~ ♪♪♪
❤❤❤ 致力学好编程的宝藏博主,代码兴国!❤❤❤
又和大家见面啦!在大家看到这个标题的时候其实就已经发现了:我们的C语言的基础知识大部分已经学完了呢~真厉害,给自己鼓个掌吧,啪叽啪叽~ 现在开始我们要进击数据结构了!是不是很激动呢?诶,先憋激动,在开始前我们先了解一下数据结构是什么吧!
我们在生活中无论什么事几乎都少不了逻辑吧,比如是先吃饭还是先睡觉,是先给大伟的文章点赞还是先收藏一下(^▽^ ),再更具体一点,我们的手机软件,比如电话啊,QQ啊,微信啊,里面不是都有联系人吗,我们可以在手机上对联系人进行一系列操作:修改信息,增加联系人,删除联系人,查找联系人等等等等.......这些啊其实都是我们的数据结构。
而概念性来说:
数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的 数据元素的集合。
我爱说实话,数据结构是非常非常重要的!无论是我们刷题目,以后面试,甚至是以后工作了之后,数据结构都是会一直会陪伴我们的,而大伟我也会一直陪着大家的(✿◡‿◡)
那我们要如何学好数据结构呢?简单,像这样就行:
哈哈,开个玩笑,别当真啦!其实呢,重要的是需要画图和思考,虽然这两点在哪里都很重要就是了~
好的,废话不多说,那我们就开始今天的内容:顺序表吧!
顺序表
在动手写码顺序表前,我们需要先来分清楚两个概念:线性表和顺序表:
那有铁汁好奇了,线性表是啥呢?
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
简单来说,线性表就是在逻辑上是一条线,而实际的物理结构不一定是一条线。而我们今天要写的顺序表就是一个线性表,且他的底层就是数组,是对数组进行了封装。
谈到顺序表,我们就有两个选择:静态顺序表和动态顺序表。那么这两个有什么区别呢?我们通过代码来看一看:
静态:
typedef int SQLDataType;//重命名int
#define N 10
typedef struct SeqList {
SQLDataType arr[N];
int size;//顺序表的实际大小
}SeqList;//重命名结构体
动态:
typedef int SQLDataType;
typedef struct SeqList {
SQLDataType *arr;
int size;
int capacity;
}SeqList;
不知道大家还记不记得typedef了,没错,typedef将int 和 struct SeqList进行重命名,而我为什么要这么做呢?大家可以思考一下,其实也就是typedef的好处是什么:
鸡屁踢老弟给出了以下几种答案:
- 代码可读性:为复杂的数据类型创建简洁的别名,提高代码的可读性
- 平台无关性:在一些情况下,数据类型的大小可能因编译器和操作系统的不同而有所变化,可以为不同平台定义相同的别名,提高代码的可移植性
- 简化复杂声明:声明一个函数指针类型可能会变得更加清晰
- 抽象底层实现:使你的代码更容易维护。通过定义数据类型的别名,你可以在不影响代码其他部分的情况下更改底层实现
- 提高代码的一致性:确保在整个代码库中一致地使用相同的数据类型,减少错误和混淆的可能性
umm,说的很好,不亏是鸡踢屁牢弟,简单来说,就是便于书写和阅读,可以以便后面根据需求修改类型,如可以直接把一大片的int改为char,且便于维护。
那我们再回到上面的两段代码,我们会发现,静态顺序表是通过N来确定整个顺序表的最大容积;而动态顺序表是定义了个指针,后期可以通过动态内存开辟来实现最大容积的变化,还有size来确定有效数据,capacity来确定最大数据。
这样一对比铁汁们就会发现哪个方法会更好呢? 当然是动态更好了,对吧?
好的,我们确定了我们的顺序表是由动态开辟的,那我们可以玩(对动态表做什么)什么呢?
来,让我们联想到微信联系人,我们可以加小姐姐的wx(增),可以删掉不喜欢的基佬(删),查找自己的好舍友(查),修改新加的朋友的备注(改)。诶~对了,我们实现的就是增删查改。
代码实现:
和之前的扫雷一样,我们还是开了三个文件,分别为:Sql.c Sql.h test.c(当然了,名字是自己取的,我这么取的原因是顺序表英文为Sequence List,这样子更容易理解)
这里插一嘴啊~ 其实关于C语言的命名有一定的规则,如:大驼峰命名法等,有兴趣的铁汁们下来自己查啊~(ง •_•)ง
首先我们先定义个结构体(Sql.h),当然了别忘了重命名:
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
typedef int SQLDataType;
typedef struct SeqList {
SQLDataType *arr;//数组保存数据
int size;//有效数据体积
int capacity;//最大体积
}SeqList;
然后是我们的声明增删查改(Sql.h),但是在此之外我们还多了一些操作:
//顺序表初始化
void SeqListInit(SeqList* ps);
//检查空间,如果满了,进行增容
void CheckCapacity(SeqList* ps);
// 顺序表尾插
void SeqListPushBack(SeqList* ps, SQLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* ps);
// 顺序表头插
void SeqListPushFront(SeqList* ps, SQLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* ps);
// 顺序表查找
int SeqListFind(SeqList* ps, SQLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SQLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* ps, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* ps);
// 顺序表打印
void SeqListPrint(SeqList* ps);
因为一开始只是一张白纸,所以初始化一下没问题吧,既然都初始化了,没有空间怎么可以呢,再来个检查空间也没问题吧,增的话,在头部增加,在尾部增加,在特定位置增加没话说吧,删的话,删头,删为,删特定位置也没话说吧,查找,修改,再打印个链表以便观察,最后再销毁,有始有终,完美!
好,开码!
Sql.c
初始化:
void SeqListInit(SeqList* ps)
{
ps->arr = NULL;//一开始啥都没有
ps->capacity = ps->size = 0;//一开始啥都没有
}
检查空间:
void CheckCapacity(SeqList* ps)
{
if (ps->capacity == ps->size)//若有效容积等于最大容积了
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//若顺序表为空,先给四个空间,若不为空,两倍空间
SQLDataType* tmp = (SQLDataType*)realloc(ps->arr, newCapacity * sizeof(SQLDataType));//扩容(开辟空间)
if (tmp == NULL)//若开辟失败
{
perror("malloc failed!");
return;
}
ps->arr = tmp;//修改空间
ps->capacity = newCapacity;//修改最大体积
}
}
头插:
void SeqListPushFront(SeqList* ps, SQLDataType x)
{
assert(ps);//断言,若ps不为空
CheckCapacity(ps);//检查体积够不够了
for (int i = ps->capacity; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];//从最后一个元素的后一个元素往前覆盖,直到第一个元素
}
ps->arr[0] = x;//修改首元素的值
ps->size++;//修改size的值
}
尾插:
void SeqListPushBack(SeqList* ps, SQLDataType x)
{
assert(ps);
CheckCapacity(ps);
ps->arr[ps->size] = x;//直接在最后一个有效元素有一个位置插入
ps->size++;//修改size大小
}
特定位置插入:
void SeqListInsert(SeqList* ps, size_t pos, SQLDataType x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);//确保pos(下标)位置有效
CheckCapacity(ps);
for (int i = ps->size; i > pos ; i--)
{
ps->arr[i] = ps->arr[i - 1];//从最后一个元素后面一个元素开始往前覆盖,直到下标为pos位置
}
ps->arr[pos] = x;//修改pos位置的值
ps->size++;//修改size
}
头删:
void SeqListPopFront(SeqList* ps)
{
assert(ps);
int i;
for ( i = 0; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];//从前往后一次覆盖
}
ps->arr[i] = -1;//将最后一个元素修改为-1(这句代码其实可以不需要,放上去也有问题,大家思考一下为什么)
ps->size--;//修改size
}
尾删:
void SeqListPopBack(SeqList* ps)
{
assert(ps);
ps->arr[ps->size - 1] = -1;//(同样的,这句代码可以有问题并可以不要)
ps->size--;//修改size
}
特定位置删除:
void SeqListErase(SeqList* ps, size_t pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int i;
for ( i = pos; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];//从pos位置往后,依次覆盖
}
ps->arr[i] = -1;//(多余,错误)
ps->size--;//修改size
}
查找:
int SeqListFind(SeqList* ps, SQLDataType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
return i;//遍历,如果找到了,返回下标
}
return -1;//如果找不到,返回个无效的下标
}
打印:
void SeqListPrint(SeqList* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);//遍历一下,没啥好说的
}
printf("\n");
}
void SeqListDestory(SeqList* ps)
{
assert(ps);
free(ps->arr);//释放空间
ps->arr = 0;
ps->capacity = ps->size = 0;
}
OK,OK,上面就是我们的的动态单链表的代码实现,现在考验大家的大家的记性和认真程度了:还记得博主括号内的问题吗?不记得?呵,赶紧回去看!o( ̄ε ̄*) 。昂,看到了吧?简单来说问题就是 ps->arr[i] = -1 为什么有问题,为什么可以不需要?
emm,怎么说捏,我们在插入数据的时候范围是不限的吧,所以我们也可以插入一个-1进去吧?那如果我们现在把这个位置设置为-1,那可不可以算是插入了个-1呢?’(°ー°〃),所以,我们完全可以直接不给他赋值,任由他自由,反正我们后面也不要关心他的值对吧~?
所以啊,我们的代码实现可以多种多样,逻辑上和实用性上都要考虑。那我们顺序表的代码写完了,不得来玩一玩?┗|*`0′*|┛
test.c
#include"Sql.h"
int main()
{
SeqList ps;
SeqListInit(&ps);
SeqListPushBack(&ps, 1);
SeqListPushBack(&ps, 2);
SeqListPushBack(&ps, 3);
SeqListPushBack(&ps, 4);
SeqListPrint(&ps);//1 2 3 4
SeqListPushFront(&ps, 7);
SeqListPushFront(&ps, 6);
SeqListPushFront(&ps, 5);
SeqListPrint(&ps);//5 6 7 1 2 3 4
SeqListPopBack(&ps);
SeqListPopBack(&ps);
SeqListPrint(&ps);//5 6 7 1 2
SeqListPopFront(& ps);
SeqListPopFront(&ps);
SeqListPrint(&ps);// 7 1 2
if (SeqListFind(&ps, 1))
printf("1的下标为%d\n", SeqListFind(&ps, 1));
else
printf("找不到!");//1的下标为1
SeqListInsert(&ps, 1, 5);
SeqListPrint(&ps);// 7 5 1 2
SeqListErase(&ps, 2);
SeqListPrint(&ps);// 7 5 2
SeqListDestory(&ps);
SeqListPrint(&ps);//NULL
return 0;
}
让我们看看运行框内打印的是不是和我们想要的一样呢?
通过对比,我们发现实际输出的结果和我们想要的效果完全一样啊,而且代码返回为0,这也就说明了我们代码写的没有问题。
害,累鼠博主了,看到这里还不给大伟来个一键三连?o(^▽^)o。今天的顺序表也就到此为止了啊,下次带大家来看看顺序表的实际应用~期待着吧!
I have no secret of success but hard work. 除辛勤工作之外,我别无成功的秘诀。
本篇博客也就到此为止了,送大家一碗鸡汤,勉励自己以及这世界上所有追逐梦想的赤子趁年华尚好努力提升自己,莫欺少年穷!