数据结构——顺序表,C语言实现(附有源码实现)
文章目录
顺序表
概念
- 概念走一波:
- 线性表的顺序存储结构简称为**“顺序表”**
- 顺序表:就是用一段地址连续的存储空间单元依次 (关键字),存储数据
- 说俗一点就是开辟一块连续的存储空间使用,非常具有代表意义的就是 :数组
- 开辟空间的方式的不同顺序表又可以分为两种:
- 一种:静态的顺序表:
- 二种:动态的顺序表:
这里我说明一下:我主要是以代码为主,思路为辅助,中间穿插结构思维,大家都懂得,数据结构怎么可以少了图,来梳理思维呢?我也不例外(毕竟小生也只是一介凡夫俗子,不是什么大牛!),所以思维我一般是以图的形式表示出来:
静态顺序表
- 所谓的静态顺序表:就是在栈区中开辟空间:一般是通过数组实现的
- 存在的缺点:
- 栈帧
- 不够灵活:开辟的空间是固定死的,开辟的空间太小了,就会产生空间不够用的情况;
开辟的空间大了,极大概率会存在大量的空间使用不到,浪费空间 - 内存中的栈区有限,开辟的空间过大,一般编译器会报错,或者警告
- 静态顺序表与动态顺序表相比,更为简单况且动态顺序表都为了,静态的一般也没有问题
- 基于上述原因:所以我这里就对静态顺序表功能上只是实现了一点点,重点是动态顺序表。
- 好了,废话不多说,上操作:
在操作之前:传授一点本人的小技巧:
- 对于数据结构:一般是用于项目上的实现功能:所以我们以文件功能分布的形式:写代码
- 每写一部分就运行一下,错误的范围更小,容易找出来BUG,不要写了一大坨之后,然后感叹!我终于写完了,之后再调式运行一波,你懂得:报出一堆的错误,看都看不过来,更别说改BUG了,自己搞自己,并成功的把自己从入门搞到了放弃,呵呵,开个玩笑,作为一名合格的程序员怎么会向BUG屈服呢!更何况还是自己写的BUG .更不能投降,放弃。所以为了减轻自己的负担,还是写一部分,再调式运行一下,看看是否有错误。
- 在标识符的命名上尽量做到见闻知意
- 最后一个:无论是声明函数,还是变量的声明:请使用上 extern 修饰,函数,变量,代码好风格;
实现
- 首先创建相关文件:如下图:
创建静态顺序表
- 我们使用#define 宏的使用定义顺序表的容量:
- 我们使用关键字typedef 对数据类型进行别名:
- 好处就是 :方便后期一些需求的修改,比如:你的顺序表的容量需要扩容时候,以及数据保存类型的修改比如:保存的数据是 double 类型,因为我们使用了,define , typedef ,只要简单的修改一下 它们define , typedef 所对应的数值,就可以了,而不需要一个一个的找出来,再一个一个的修改,在大型项目中,其好处体现的会更加明显
- 其目的就是不要写死了
- 还有一个好处就是数据信息化:我们知道数据是没有用的,数据的信息才有用,比如 数据100,一百的什么?谁知道,所以就没有意义,而一百元,信息:人民币(毛爷爷),有意义了吧!
// 静态顺序表
#define MAX_SIZE 10 // 顺序表的固定容量
typedef int SQDataType; // 对 int 类型的别名,方便后面的数据类型的修改
// 静态的顺序表 (结构体)
typedef struct SeqList
{
SQDataType data[MAX_SIZE]; // 定义数组容量大小
int size; // 记入数据的有效个数
}SL;
初始化静态顺序表
extern void SeqListInit(SL* sl); // 初始化静态的顺序表 ,extern 声明加上它,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListInit(SL* sl) // 初始化顺序表
{
for (int i = 0; i < MAX_SIZE; i++)
{
sl->data[i] = 0;
}
sl->size = 0;
// 也可以使用函数 memset 头文件是 :#include<string.h>
// memset(pa->data, 0,sizeof(SQDataType)*MAX_SIZe );
// 将data 数组初始化为 0;
}
静态顺序表的尾插法
- 注意:我们在插入数据之前必须先判断一下是否还有空间可以存放数据
- 没有空间存放数据的就报错:打印错误提示、
- 插入数据失败,停止程序的运行,防止影响后面的
extern void SeqListPushBack(SL* sl,SQDataType x); // 静态的顺序表的尾插法;,extern 声明 ,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListPushBack(SL*sl,SQDataType x ) // 顺序表的尾插法
{
if (sl->size == MAX_SIZE) // 1.首先判断是否有空间可以插入
{
printf("顺序表已经满了,无法再进行插入操作了\n");
perror("SeqListPushBack"); // 打印错误;
exit(0); // 程序停止运行,
}
sl->data[sl->size] = x; // 插入数据 x ;
sl->size++;
}
静态顺序表的实现就到这里不都介绍了,下面我们进入动态顺序表的实现
动态顺序表
-
动态顺序表的是在动态内存(堆区)开辟空间,来存放数据
-
与静态顺序表最不同的是动态顺序表基本上是不存在空间不够的情况,因为可以动态扩容
-
动态顺序表开辟空间更加灵活,一般情况下是不存在,太多的空间上的浪费的
-
基本上都需要用上指针的知识,所以对于指针还不了解的朋友,可以先复习复习,不然,可能,你不太好理解代码的实现的
创建动态顺序表
typedef int SQDataType; // 对 int 类型的别名,方便后面的数据类型的修改
typedef struct SeqList
{
SQDataType* Data; // 创建一个SQDataType类型(int 类型的别名)的指针,指向动态(堆区) 开辟空间的数组
int size; // 有效数据的个数
int capacity; // 容量空间的大小
}SL;
动态顺序表的初始化
extern void SeqListInit(SL* ps); // 初始化动态的顺序表,extern 声明加上它,代码好风格,注意这一 // 行代码是放在有关头文件的文件,当中去的
void SeqListInit(SL* ps) // 初始化顺序表
{
ps->Data = NULL;
ps->size = 0;
ps->capacity = 0;
}
动态顺序表的判断与扩容
- 这里我们使用 static 修饰函数,提高项目的安全性,减少后期项目的维护成本,static 具体请看该博客[static的详细介绍]((91条消息) 一点都不安静的 ——static_月光下的编程魔术师的博客-CSDN博客)
- 一般扩容的大小为,原来的 2 倍,如果太小了,你每次都会需要增容的
如果太大了,你就会太浪费空间了,所以大小要适宜,不可太大,太小 - 注意:realloc 的性质单位字节,开辟一个 为 0 的空间,返回的值是 NULL;
- 开辟空间失败的处理方式:
- 打印错误: perror
- 释放空间,置为NULL;
- 程序停止,exit(0)
static void SeqListCheckCapacity(SL*ps) // 检查是否动态顺序是否满了,并扩容
{
if (ps->size == ps->capacity) // 检查是否动态顺序表是否满了
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SQDataType* ptr = (SQDataType*)realloc( ps->Data, newcapacity * sizeof(SQDataType));
if (ptr == NULL) // 判断动态内存(堆区)开辟扩容空间是否成功
{
perror("ptr"); // 打印错误原因
free(ptr); // 开辟空间失败,手动释放空间,
ptr = NULL; // 开辟空间失败,防止非法访问(因为该空间已经释放了,不属于你 // 的了,你再想通过该指针访问的话,就是越界,非法访问了 ),所 // 以置为NULL;
exit(0); // exit(0);空间开辟失败,程序停止运行,
}
else // 开辟(堆区)空间开辟成功
{
ps->Data = ptr; // 交付控制权
ps->capacity = newcapacity;
}
}
}
上述代码解析:
-
static void SeqListCheckCapacity(SL*ps) static修饰函数是该函数只能在本文件中使用,外部文件无法直接访问,但是可以被间接访问(函数接口),封装函数,提高项目的安全性,减少后期的维护成本
-
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; SQDataType* ptr = (SQDataType*)realloc( ps->Data, newcapacity * sizeof(SQDataType)); /* 因为你一开始的(ps->capacity)容量初始化的是 0 ,因为 realloc 函数的一些性质单位字节,开辟一个 0 字节空间的大小返回的是NULL * 所以这里当 capacity 容量为 0 ,我们赋值为 4 ,三目运算符: int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; */
-
这里还有一个注意点,在这里使用 sizeof() 而不是直接使用 4 个字节来表示,原因有两点:
- 一个最开始的通过typedef 对 int类型取的别名,方便后期的修正,
- 还有一个就是兼容性,对于16位的电脑 int 类型的大小是 2 个字节,而32 位的是 4 个字 节。防止因为电脑的位数而出错
动态顺序表的尾插法
- 首先我们需要判断是否需要扩容,注意所有的存入数据的时候,都需要判断是否需要扩容
- 所以尾插简单,直接就是在 ps->size 该末尾插入就OK了,注意插入了,一个有效的数据 ps->size 需要加 1 的
extern void SeqListPushBack(SL* ps,SQDataType x) // 动态的顺序表的尾插法,extern 声明 ,代码 // 好风格,注意这一行代码是放在有关头文件的文 件,当中去的
void SeqListPushBack(SL* ps, SQDataType x) // 动态顺序表的尾插法
{
SeqListCheckCapacity(ps); // 判断尾插时,空间是否满了
ps->Data[ps->size] = x;
ps->size++;
}
动态顺序表的头插法
-
同样需要判断是否需要扩容空间
-
主要的目的就是把:在保证有效数据不会被覆盖掉的情况下,把头位置腾出来(就是下标为 0 的位置)
-
然后把数据插入到 下标为 0 的位置中,并且 ps->size 加 1
extern void SeqListPushFront(SL* ps, SQDataType x); // 动态顺序表的头插法 extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListPushFront(SL* ps, SQDataType x) // 动态顺序表的头插法
{
SeqListCheckCapacity(ps); // 判断头插法,空间是否满了
int end = ps->size - 1; // 实际存放有数据的下标位置
while (end >= 0)
{
ps->Data[end + 1] = ps->Data[end];
end--;
}
ps->Data[0] = x;
ps->size++;
}
动态顺序表的尾删法
- 这个简单只要把记入有效值的 ps->size 减 减就可以了
extern void SeqListPopBack(SL* ps); // 动态顺序表的尾删,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListPopBack(SL* ps) // 动态顺序表的尾删
{
if (ps->size == 0) // 判断是否有数据的存在可以删除的
{
printf("没有数据可以删除,失败\n");
exit(0); // 删除失败, 退出程序
}
ps->size--; // 直接把保存的有效数减减即可
}
动态顺序表的头删法
-
首先需要判断是否有数据存放,有数据才可以删除的
-
通过头位置+ 1 从前往后移动覆盖数值:从而达到删除的效果
extern void SeqListPopFront(SL* ps); // 动态顺序表的头删,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListPopFront(SL* ps) // 动态顺序表的头删
{
if (ps->size == 0) // 首先判断是否存在可以删除的数据
{
printf("没有可以删除的数据,所以删除失败\n");
exit(0); // 删除失败,程序停止运行
}
int start = 1;
while (start < ps->size) // 注意循环的跳出条件
{
ps->Data[start -1] = ps->Data[start];
start++;
}
ps->size--;
}
动态顺序表在 pos 的位置插入数据
- 首先判断插入的位置是否合理:if (pos < 0 || pos >ps->capacity ) {插入的位置不可以小于 0 ,其次是 插入的位置不可以大于ps->capacity 的位置 ,因为没有开辟的空间是不可以被使用的,不然就是越界,非法访问了}
- 因为是插入所以同样必须判断是否需要扩容
extern void SeqListInsert(SL* ps, int pos, SQDataType x); // 动态顺序表在pos的位置插入数据,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListInsert(SL* ps, int pos, SQDataType x) // 动态顺序表在pos的位置插入数据
{
if (pos < 0 || pos > ps->capacity) // 判断插入的数据是否存在插入位置的错误
{
perror("SeqListInsert"); // 打印错误原因
exit(0); // 插入位置错误,程序退出
}
SeqListCheckCapacity(ps);
int end = ps->size - 1; // 记入的是数据的有效位置的下标
while (end >= pos) // 所有数据后移动,腾出位置给 pos插入
{
ps->Data[end + 1] = ps->Data[end];
end--;
}
ps->Data[pos] = x;
ps->size++;
}
动态顺序表删除 pos 位置的值
- 首先判断的位置是否合理:if (pos < 0 || pos > ps->size ) {插入的位置不可以小于 0 ,其次是 删除的位置不可以大于 ps->size 记录有效数据的位置,因为没有数据不可以删除的 }
extern void SeqListErase(SL* ps, int pos); // 动态顺序表删除 pos 位置的值,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListErase(SL* ps, int pos) // 动态顺序表删除 pos 位置的值
{
if (pos < 0 || pos > ps->size) // 判断删除的位置是否正确
{
perror("SeqListErase"); // 打印错误原因
exit(0); // 退出程序
}
int start = pos + 1; // 把删除的位置的后一位
while (start < ps->size) // 所有数据往前移动,覆盖
{
ps->Data[start -1] = ps->Data[start];
start++;
}
ps->size--;
}
查找 X 数值 的下标
- 这个简单直接遍历整个顺序表,判断是否相等,相等返回其找到的下标,没找到提示不存在该数值。
extern void SeqListFind(SL* ps, SQDataType x); // 查找 x 值,的下标,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListFind(SL* ps, SQDataType x) // 查找 x 值,的下标
{
int sign = 1; // 标记判断
for (int i = 0; i < ps->size; i++)
{
if (x == ps->Data[i])
{
sign = 0;
printf("数值 %d 的位置是在下标为:%d的位置\n", x, i);
break;
}
}
if (sign == 1)
{
printf("数值 %d 不存在\n", x);
}
}
修改动态顺序表 pos 下标位置的值
- 首先判断它修改的位置是否合理:if (pos < 0 || pos >ps->size) 不可以小于(pos < 0) 0 的位置,其次是不可以大于 (pos >ps->size)记录有效数据的数量
- 位置合理的话,直接通过赋值修改就可以 了
extern void SeqListModity(SL* ps, int pos, SQDataType x); // 修改动态顺序表的 pos 下标位置的数值,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListModity(SL* ps, int pos, SQDataType x) // 修改动态顺序表的 pos 下标位置的数值
{
if (pos < 0 || pos >ps->size) // 判断修改的位置是否合理
{
perror("SeqListModity"); // 打印错误原因
exit(0); // 修改失败,退出程序
}
ps->Data[pos] = x;
}
动态顺序表的打印
extern void SeqListPrint(SL* ps); // 动态顺序表的打印,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListPrint(SL* ps) // 动态顺序表的打印
{
for (int i = 0; i < ps->size; i++)
{
printf("%d\t", ps->Data[i]);
}
printf("\n");
}
动态顺序表的销毁
- 把动态开辟(堆区)的空间,释放,归还回操作系统,
- 把指向动态开辟空间的指针置为 NULL,防止非法访问,越界访问
- ps->capacity = 0
- ps->size = 0
extern void SeqListDestay(SL* ps); // 动态顺序表的销毁,extern 声明,代码好风格,注意这一行代码是放在有关头文件的文件,当中去的
void SeqListDestay(SL* ps) // 动态顺序表的销毁
{
free(ps->Data); // 释放动态(堆区)开辟的空间
ps->Data = NULL; // 置为 NULL,防止越界访问,以及非法访问
ps->capacity = 0;
ps->size = 0;
}
main 中一个选择开关
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void menu()
{
printf("******************************************\n");
printf(" 1.打印数据 2.尾插 \n");
printf(" 3.头 插 4.头删 \n");
printf(" 5.尾 删 6.pos位置删除 \n");
printf(" 7.pos位置插入 8.pos查找 \n");
printf(" 9.修改数据 0.销毁 \n");
printf("******************************************\n");
}
int main()
{
SL ps; // 定义一个动态的顺序表的结构类型变量
int option = 0;
int sign = 0;
int num = 0;
SeqListInit(&ps); // 初始化线性表
do
{
menu();
printf("请选择功能:");
scanf("%d", &option);
system("cls"); // 刷新控制台
switch (option)
{
case DESTROY :
SeqListDestay(&ps); // 动态顺序表的销毁
break;
case PRINT :
SeqListPrint(&ps); // 动态顺序表打印
break;
case ENDINSERT : // 动态顺序表尾插
printf("请输入尾插的数值:");
scanf("%d", &sign);
SeqListPushBack(&ps, sign);
break;
case HEADINSERT : // 动态顺序表头插
printf("请输入头插的数值:");
scanf("%d", &sign);
SeqListPushFront(&ps, sign);
break;
case HEADNEIETE: // 动态顺序表头删
SeqListPopFront(&ps);
break;
case ENDDELETE: // 动态顺序表尾删
SeqListPopBack(&ps);
break;
case POSDDIETE: // 动态顺序表pos删
printf("请输入需要删除的的下标:");
scanf("%d", &sign);
SeqListErase(&ps, sign);
break;
case POSINSERT: // 动态顺序表pos插
printf("请输入需要插入的的下标:");
scanf("%d", &sign);
printf("\n");
printf("请输入需要插入的的数值:");
scanf("%d", &num);
SeqListInsert(&ps, sign, num);
break;
case POSCHECK : // 动态顺序表pos查找
printf("请输入需要查找的数值:");
scanf("%d", &sign);
SeqListFind(&ps, sign);
break;
case POSAMEND: // 动态顺序表pos修改
printf("请输入需要修改的对应的下标:");
scanf("%d", &sign);
printf("\n");
printf("请输入需要修改的对应的数值:");
scanf("%d", &num);
SeqListModity(&ps, sign, num);
break;
default :
printf("请不要输入无关的数值\n");
break;
}
} while (option != 0);
return 0;
}
自定义的头文件
- 注意:枚举一般是大写,枚举是逗号,枚举的最后一个不用加逗号,枚举是依次递增的
#pragma once // 避免头文件按的重复引用,提高程序的效率
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
typedef int SQDataType; // 对 int 类型的别名,方便后面的数据类型的修改
typedef struct SeqList
{
SQDataType* Data; // 指向动态(堆区)开辟空间的数组
int size; // 有效数据的个数
int capacity; // 容量空间的大小
}SL;
enum Function // 枚举
{
DESTROY = 0, // 销毁
PRINT, // 打印
ENDINSERT, // 尾插
HEADINSERT, // 头插
HEADNEIETE, // 头删 // 注意:枚举一般是大写,枚举是逗号,枚举的最后一个不用加逗号,
ENDDELETE, // 尾删
POSDDIETE, // pos删
POSINSERT, // pos插
POSCHECK, // pos查
POSAMEND // pos改
};
extern void menu(); // 打印菜单;
extern void SeqListInit(SL* ps); // 初始化动态的顺序表 ,extern 声明加上它,代码好风格
extern void SeqListPushBack(SL* ps,SQDataType x); // 动态的顺序表的尾插法,extern 声明 ,代码好风格
extern void SeqListPushFront(SL* ps, SQDataType x); // 动态顺序表的头插法 extern 声明,代码好风格
extern void SeqListPrint(SL* ps); // 动态顺序表的打印
extern void SeqListPopBack(SL* ps); // 动态顺序表的尾删
extern void SeqListPopFront(SL* ps); // 动态顺序表的头删
extern void SeqListInsert(SL* ps, int pos, SQDataType x); // 动态顺序表在pos的位置插入数据
extern void SeqListErase(SL* ps, int pos); // 动态顺序表删除 pos 位置的值
extern void SeqListFind(SL* ps, SQDataType x); // 查找 x 值,的下标
extern void SeqListModity(SL* ps, int pos, SQDataType x); // 修改动态顺序表的 pos 下标位置的数值
extern void SeqListDestay(SL* ps); // 动态顺序表的销毁
最后
每博一文案
真正的贵人,我一定无理由的支持你,不一定在经济上鼎力相助,你也不一定会时时夸赞你,但一定能在关键的时刻警醒你。行于所当行止于所当止
在合适的时候做出合适的事情,扶持着当局者迷的你愿意在关键时刻点醒你的人,一定愿意设身处地地问着,想希望你往好的方向走。
你的贵人可能是你身边时刻提醒你的朋友,可能是不断给你劝诫教伙的父母可能是工作上给你建议的领导同事抛射感情因素,客观劝诫你的人,才是真正希望你变得更好的人,这时不妨反观自己。
谁的话能真正帮到我,我以后又该怎么做,做到心中有数,形势才更有善果。
—————— 一禅心灵庙语
限于自身的水平,其中存在的错误希望大家给予指教,韩信点兵——多多益善!,谢谢大家,后会有期,江湖再见!