什么是数据结构
数据结构是由数据和结构两个词结合而来,
那么数据由是什么
就比如我们日常生活中的1,2,3,4,5,a,b,c,d,e文字信息图片等,这些就是数据
那么结构又是什么?
想像一下如果我们有大量数据要管理,举一个例子就像我们计算机电脑硬盘里面的东西,如果我们在硬盘里面对于那些文件随便防止任何类型的文件都放在一堆,如果我们要想找某一个文件那将会非常困难,可读性非常差。
如果我们把他们提前整理好,同类型的文件放在一起,形成一个结构这样查找起来就方便多了。
数据结构是计算机存储,组织数据的方式
什么是顺序表
顺序表是一种数据结构它使用计算机内存中的一组地址连续的存储单元来存储线性表的元素。这种存储方式使得线性表中逻辑上相邻的数据元素在物理存储位置上也相邻,通过这种方式,顺序表可以高效地执行插入、删除、查找等操作。顺序表的主要优点包括动态分配空间、支持随机访问元素,以及逻辑顺序与物理顺序的一致性。顺序表可以分为静态和动态两种形式,静态顺序表适用于元素数量固定的情形,而动态顺序表的大小可以根据需要调整。
根据上面的内容我们了解到顺序表是一种数据结构,在计算机内存一组连续的地址可以用来存放东西,提到内存中连续的地址,我们就很容易想到一个东西,叫数组,其实顺序表的底层逻辑就是数组,只是对数组进行了封装,实现了常用的查增删改等接口。
顺序表的分类
顺序表分为静态顺序表和动态顺序表
静态顺序表
顾名思义静态顺序表是静态的,大小是不可改变的,像这张图片
动态顺序表
顾名思义动态顺序表是动态的大小是可以改变的,像这张图片
根据这两个顺序表的对比,我们可以对比出他们谁好谁坏,可以很明显的看到动态顺序表要明显优于静态顺序表动态顺序表的一个很明显的优势是可以动态增容,而静态顺序表不能,想象一下如果我们要存放数据,使用静态顺序表那么一次性必须申请非常大的空间才能防止空间不够用,而动态顺序表就不用,他可以根据需要数据的大小随意调整,这里我们主要讲动态顺序表。
动态顺序表的C语言实现
如果我们要创建一个顺序表,我们首先想到的就是创建一个结构体用来存放动态顺序表里面的一些信息,像可以空间大小,或者实际使用空间,下面是代码
typedef int Data_type
struct seqlist
{
Data_type* arr;//数组地址
int size;//实际使用空间
int memespace//总空间大小
};
typedef struct seqlist sl;//重定义方便书写代码
这里我们创建了一个结构体用来存放整形数据的顺序表相关信息,又重定义了一下,这样有助于我们书写代码。
现在我们来说一下具体思路
具体思路
我们创建了一个int size和int memespace来维护我们的arr数组顺序表
在他们的基础之上我们来实现增删查改功能,在写完一个功能的时候在测试一下这就是思路。
初始化
首先我们先来看看初始化
void Initseqlist(sl*ps)
{
ps->arr = NULL;
ps->size = 0;
ps->memespace = 0;
}
这里我们用了一个指针来接收seqlist结构体,为什么要采用地址传递,那是因为这样可以减少内存的占用将结构体的指针作为参数传递给函数,这样函数内部的操作是指向原始结构体的指针,可以直接修改结构体这里我们把顺序表数组和实际使用空间,和总共大小空间都初始化了一下赋值为0和空指针NULL,同样也可以在初始化的时候也可以分配内存大小空间,像这样
(int*)malloc(2*sizeof(Data_type));//申请了两个大小为int的空间就
我们来看看主函数
我们后续的测试功能全在这个主函数里面;
int main()
{
sl SL;//通过结构体创建了一个名为SL的变量,前面定义过的
Initseqlist(&SL);//初始化这个SL变量
return 0;
}
申请空间
下面是代码
void cheakmemory(sl* ps)
{
if(ps->memespace == ps->size)
{
int new_memespace = ps->memespace == 0?4;2*ps->memespace;
Data_type*temp = (Data_type*)realloc(ps->arr,new_memespace*sizeof(Data_type));
if(temp == NULL)
{
perror("realloc");
printf("申请空间失败\n");
}
ps->memespace = new_memepace;
ps->arr = temp;
}
这里我们首先用了个if来判断是否需要新增空间如果实际使用空间和总共大小空间相等的话就代表空间满了需要申请空间,就进入if语句
int new_memespace = ps->memespace == 0?4;2*ps->memespace;
这段代码的意思是如果总共大小空间等于0的话就预备申请4个空间如果不等于0就预备申请原来大小的两倍空间,为什么要申请两倍的空间呢,那是一个数学问题,在数学中得到如果大小不够申请原来的两到三倍空间是最合理的,就像手机存储一样最开始8g到16g到现在的128,256g都是成倍提升的。为什么这里是预备申请,是因为在下一行代码
Data_type*temp = (Data_type*)realloc(ps->arr,new_memespace*sizeof(Data_type));
这里才是在实际申请空间,这里用了realloc函数来申请空间如果不知道realloc内存函数怎么用详见这篇文章
动态内存管理
这里的判断都是和realloc函数相关的,这里就不过多说明了
if(temp == NULL)
{
perror("realloc");
printf("申请空间失败\n");
}
ps->memespace = new_memepace;
ps->arr = temp;
}
这里将新申请的空间大小重新赋值给原始空间大小,把新空间的地址重新赋值给原始空间地址
尾部插入数据
下面是代码
void slpushback(sl*ps,Data_type x)
{
assert(ps);
cheakmemory(ps);//看看空间够不够用
ps->arr[ps->size] = x;
ps->size++;
}
这里我们来画个图就好理解了
增加完之后一定要让size有效数据自增1
头部插入数据
我们了解了尾插,我们来看看头查,头部插入数据要难一点,总的来说头查就是要让所用数据向后面挪动一位,我们画图看看
下面是代码
void slpushfront(sl *ps,Data_type x)
{
assert(ps);
cheakmemroy(ps);
for(int i = ps->size;i<0;i++)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[0] = x;//结束后0下标处就空闲下来了,就可以写入数据了
size++;
}
尾部删除数据
尾删挺简单的,就是让实际空间大小减一就行了,不用去实际去删除就像机械硬盘删除数据一样,不会去实际删除,只是告诉系统这里可以写入东西了,下一次写入的时候直接覆盖写入上去,下面是代码
void del_Databack(sl*ps)
{
assert(ps);//断言一下指针不能为空
ps->size--;
}
头部删除数据
这里和头部插入数据的思想方法有点类似就是把后面的元素往前移动一位,我们来看看图片
下面是代码。
void del_Datafront(sl*ps)
{
assert(ps);
assert(ps->size);//要删除的有效数据大小不能为0
for(int i = 0;i<ps->size-1;i++)
{
ps->arr[i] = ps->arr[size+1];
}
ps->size--;
}
任意位置删除
关于任意位置删除,我们只需要把要删除数据后面的元素往前面移动覆盖就行了,像这样
下面是代码
void del_anywhere(sl*ps,int pos)
{
assert(ps);
assert(pos != 0 && pos <ps->size);
for(int i = pos;i<size-1;i++)
{
ps->arr[i] = ps->arr[i+1];
}
ps->size--;
}
任意位置添加数据
在任意位置添加数据,我们只需要把指定添加位置后面的数据整体往后面移动一位就可以了,像这样
下面是代码
void add_angwhere(sl*ps,int pos,intnumber)
{
assert(ps);
assert(pos!=0 && pos<ps->size);
for(int i = ps->size;i>pos;i--)
{
ps->arr[i] = ps->arr[i-1];
}
ps->arr[pos] = number;
ps->size++;
}
任意位置查找
void find_number(sl* ps, int number)
{
for (int i = 0; i <= ps->size - 1; i++)
{
if (ps->arr[i] == number)
{
printf("找到了下标是%d", i);
}
}
printf("没找到\n");
}
这里就是下表访问查找了,通过下表访问一个一个去找,如果for循环都走完了就是没找到。
释放空间
void freespace(sl* ps)
{
if (ps->arr != NULL)
{
free(ps->arr);
}
ps->memespace = 0;
ps->size = 0;
}
这样运行完程序之后要把占用空间全部还给操作系统才安全
下面是整体实现代码
#pragma once
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
typedef int Data_type;
struct seqlist
{
Data_type* arr;
int size;//数据实际容量
int memespace;//总共空间大小
};
typedef struct seqlist sl;//重定义
void Initseqlist(sl* ps);//初始化
void cheakmemory(sl* ps);//检测空间够不够
void slpushback(sl* ps, Data_type x);//尾插
void slpushfront(sl* ps, Data_type x);//头插
void del_Databack(sl* ps);//尾删
void del_Datafront(sl* ps);//头删
void del_anywhere(sl* ps, int pos);//任意位置删除
void add_anywhere(sl* ps, int pos, int number);//任意位置添加
void find_number(sl* ps, int number);//查找
void Initseqlist(sl* ps)//初始化
{
ps->arr = NULL;//(int*)malloc(2*sizeof(Data_type));也可以在初始化就申请空间
ps->memespace = 0;//2;
ps->size = 0;
}
void freespace(sl* ps)
{
if (ps->arr != NULL)
{
free(ps->arr);
}
ps->memespace = 0;
ps->size = 0;
}
void slprint(sl* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->arr[i]);
}
}
void cheakmemory(sl* ps)//创建空间
{
if (ps->memespace == ps->size)
{
int new_memspace = ps->memespace == 0 ? 4 : 2 * ps->memespace;
Data_type* temp = (Data_type*)realloc(ps->arr, new_memspace * sizeof(Data_type));
if (temp == NULL)
{
perror("realloc");
printf("\n申请空间失败");
}
ps->memespace = new_memspace;
ps->arr = temp;
}
}
void slpushback(sl* ps, Data_type x)//尾插
{
assert(ps);
cheakmemory(ps);
ps->arr[ps->size] = x;
ps->size++;
}
void slpushfront(sl* ps, Data_type x)//头插
{
assert(ps);
cheakmemory(ps);
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[0] = x;
ps->size++;
}
void del_Databack(sl* ps)//尾删
{
assert(ps);
ps->size--;
}
void del_Datafront(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 del_anywhere(sl* ps, int pos)//任意位置删除
{
assert(ps);
assert(pos != 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
ps->size--;
}
void add_anywhere(sl* ps, int pos, int number)//任意位置添加
{
assert(ps);
assert(pos != 0 && pos < ps->size);
cheakmemory(ps);
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = number;
ps->size++;
}
void find_number(sl* ps, int number)
{
for (int i = 0; i <= ps->size - 1; i++)
{
if (ps->arr[i] == number)
{
printf("找到了下标是%d", i);
}
}
}
int main()
{
sl SL;
Initseqlist(&SL);
cheakmemory(&SL);
slpushback(&SL, 1);
slpushback(&SL, 2);
slpushfront(&SL, 3);
slpushfront(&SL, 4);
slpushfront(&SL, 3);
slpushfront(&SL, 5);
slprint(&SL);
printf("\n");
del_Databack(&SL);
slprint(&SL);
printf("\n");
del_Datafront(&SL);
slprint(&SL);
printf("\n");
del_anywhere(&SL,1);
slprint(&SL);
printf("\n");
add_anywhere(&SL, 2, 10);
slprint(&SL);
printf("\n");
find_number(&SL, 10);
freespace(&SL);
return 0;
}
运行结果
以上就是关于顺序表的讲解了,如果有错误欢迎指正。