数据结构及其顺序表
一、数据结构
1. 什么是数据结构?
概念:数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由那部分构成,以什么方式构成,以及数据元素之间呈现的结构。数据结构的核心在于如何有效地组织和存储数据,以便可以高效地进行数据访问、检索、插入和删除等操作。
2.为什么需要数据结构?或者说数据结构的作用是什么?
举个例子:现实中我们去人流大的医院就医时,或者去客流量大的餐厅吃饭时,我们总是会得到一个排队的号码,按号码进行就医或取餐,这样医院或者餐厅会管理的更加井井有条,办事的效率会更高,我们可以想象没有排队号码,每个人争先恐后将会照成什么样的混乱局面。同理,程序中如果不对数据进行管理,可能会导致数据丢失、操作数据困难、野指针、程序效率低下等情况。 通过数据结构,能够有效将数据组织和管理在一起。按照我们的方式任意对数据进行增删改查等操作。
二、顺序表
1. 线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
例如:苹果,香蕉,西瓜统称为水果。线性表指的是具有部分相同特性的一类数据结构的集合
-
物理上的连续性: 某些数据结构在物理上是连续存储的,如数组。这种连续性可以提高数据访问的速度,因为内存访问模式是连续的,可以利用缓存的优势。
-
逻辑上的连续性: 即使物理上不连续,数据元素在逻辑上仍然是连续的。例如,在链表中,尽管元素在物理上可能分散在内存的不同位置,但通过指针连接,它们在逻辑上形成了一个连续的序列。相当于有一条无形的逻辑线将各个元素连接起来。
2. 顺序表概念
顺序表是线性表的一种,顺序表是一种使用连续存储单元依次存储数据元素的数据结构,它支持通过索引快速访问任意位置的元素。顺序表通常通过数组实现,在内存中以连续的块存在,使得元素访问和处理非常高效。
注:顺序表的物理结构与逻辑结构都是连续的
3. 顺序表与数组的关系
顺序表的底层结构是数组,是对数组的分装,实现了基本的增删查改功能。
4. 顺序表的分类
- 静态顺序表
#define N 10 //定义数组长度
typedef int SLDataType; //定义顺序表数组类型,方便后期修改
struct SeqList {
SLDataType arr[N];
int size;//记录有效数据个数
};
typedef struct SeqList SL;//重定义顺序表名称,方便后期调用
使用定长数组创建的顺序表,顺序表的大小是固定的,如果空间初始化过大会造成空间资源浪费,如果过小则无法满足使用
- 动态顺序表
typedef int SLDataType; //定义顺序表数组类型,方便后期修改
typedef struct SeqList {
SLDataType* arr;//arr指向一块内存,可以方便后期扩容
int size;//记录有效的数据个数
int capacity;//记录空间总共可以存放多少个数据
}SL;
//typedef struct SeqList SL;//定义顺序表数组类型,方便后期修改,或者可以如上面的写法
动态顺序表可以通过内存修改进行空间的按需申请
三、顺序表的功能实现
1. 实现准备
实现功能列表:
- 顺序表 初始化
- 顺序表 销毁
- 顺序表 扩容
- 顺序表 打印
- 顺序表 头/尾部插入数据
- 顺序表 头/尾部删除数据
- 顺序表 指定位置前插入/删除数据
- 顺序表 查找数据
VS文件准备
SeqList.h 与 SeqList.c 用来提供接口与功能
test.c 用于测试功能
2. 顺序表构建与销毁
顺序表构建
SeqList.h
typedef int SLDataType; //定义顺序表数组类型,方便后期修改
typedef struct SeqList {
SLDataType* arr;//arr指向一块内存,可以方便后期扩容
int size;//记录有效的数据个数
int capacity;//记录空间总共可以存放多少个数据
}SL;
//typedef struct SeqList SL;//定义顺序表数组类型,方便后期修改,或者可以如上面的写法
顺序表销毁
SeqList.h
void SLDestory(SL* ps);//传入顺序表的地址
SeqList.c
//顺序表的销毁
void SLDestory(SL* ps) {
assert(ps);//判断ps是否为NULL
if(ps->arr != NULL){
free(ps->arr);// 释放内存
ps->arr = NULL;// 指针置空
}
ps->size = ps->capacity = 0;//数据记录置零
}
3.顺序表初始化
SeqList.h
void SLInit(SL* ps)
SeqList.c
void SLInit(SL* ps) {
assert(ps);//判断ps是否为NULL
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
4. 顺序表扩容
SeqList.h
void CheckSLCapacity(SL* ps);//检查数据是否满了
SeqList.c
void CheckSLCapacity(SL* ps) {
assert(ps);//ps不能为空格
if (ps->size == ps->capacity) {//判断有效数据个数是否等于顺序表数据个数
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//判断capacity是否为开辟数据,如果为开辟数据,则默认为其开辟4个数据
//若开辟了数据了,则将原数据变成原来的两倍记录在newCapacity中
SLDataType* Tmpps = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
//使用realloc函数,将ps->arr的内存空间扩展为newCapacity * sizeof(SLDataType)大小
//newCapacity为数据个数,sizeof(SLDataType)为单个数据的字节数目
//因为realloc返回的是void*所以要强转为SLDataType*类型
if (Tmpps == NULL) {
perror("realloc fail!\n");
exit(1);//退出程序
}
//当realloc开辟空间失败时,会返回NULL,为了原arr地址不被覆盖,则使用Tmpps
ps->arr = Tmpps;//将tmpps的值赋值给arr
ps->capacity = newCapacity;//更新数据个数
}
}
5. 顺序表打印
SeqList.h
void SLPrintf(SL s);//这里不需要对顺序表修改,所以不用传入指针
SeqLish.c
void SLPrintf(SL s) {
for (int i = 0; i < s.size; i++) {
printf("%d ", s.arr[i]);
}
printf("\n");
}
6. 顺序表头/尾部插入数据
头部插入数据
SeqList.h
//头部插入数据x
void SLPushFornt(SL* ps,SLDataType x);
SeqList.c
void SLPushFornt(SL* ps, SLDataType x) {
assert(ps);//ps不能为NULL
CheckSLCapacity(ps);//判断顺序表是否满了
if (ps->size == 0) {//如果顺序表还未插入数据,直接插入头部即可
ps->arr[0] = x;
}
else {//下面是插入了数据的情况
//1.我们要将现有的所以数据后移,采取从后往前移动更好
//方法一:
for (int i = ps->size - 1; i >= 0; i--) {
ps->arr[i + 1] = ps->arr[i];//将arr[i]处的数据移动到arr[i+1]处
}
//方法二:
memmove(&(ps->arr[1]), ps->arr, sizeof(SLDataType) * ps->size);
//将ps->arr往后的sizeof(SLDataType) * ps->size个字节移动到ps->arr[1]地址处
//2.插入数据x
ps->arr[0] = x;
}
ps->size++;//有效数据个数加一
}
尾部插入数据
SeqList.h
void SLPushBack(SL* ps, SLDataType x);
SeqList.c
void SLPushBack(SL* ps, SLDataType x) {
assert(ps);//ps不能为NULL
CheckSLCapacity(ps);//检查内存是否足够
ps->arr[ps->size++] = x;//插入数据
}
7. 顺序表头/尾部删除数据
头部删除数据
SeqList.h
void SLPopFornt(SL* ps);
SeqList.c
//头部删除数据
void SLPopFornt(SL* ps) {
assert(ps);//ps不能为NULL
assert(ps->size);//必须有数据
if (ps->size == 1) {//如果只有一个数据,直接删除
ps->size--;
}
else {//不止一个数据,arr[0]后面所有的数据前移一格即可,同样有两种方法
//方法一:
for (int i = 1; i < ps->size; i++) {
ps->arr[i - 1] = ps->arr[i];
}
//方法二:
memmove(ps->arr, &(ps->arr[1]), (ps->size - 1) * sizeof(SLDataType));
ps->size--;
}
}
尾部删除数据
SeqList.h
void SLPopBack(SL* ps);
SeqList.c
void SLPopBack(SL* ps) {
assert(ps);//ps不能为NULL
assert(ps->size);//ps->size != 0
//直接删除即可
ps->size--;
}
8. 指定位置前插入数据/指定位置删除数据
SeqList.h
void SLInsert(SL* ps, int pos, SLDataType x);
SeqList.c
void SLInsert(SL* ps, int pos, SLDataType x) {//pos为第几个数据,并非arr下标
assert(ps);//ps不能为NULL
pos -= 1;//转化为arr下标
assert(pos >= 0 && pos < ps->size);
CheckSLCapacity(ps);//判断顺序表是否满了
//后移一位arr[pos]位置及后面的数据,如上同样有两种方法,这里就只演示一种方法
memmove(&(ps->arr[pos + 1]), &(ps->arr[pos]), (ps->size - pos) * sizeof(SLDataType));
//插入数据
ps->arr[pos] = x;
ps->size++;
}
同理我们可以写出在指定位置前删除数据的函数
在指定位置删除数据
SeqList.h
void SLErace(SL* ps, int pos);
SeqList.c
//指定位置前删除数据
//指定位置删除数据
void SLErace(SL* ps, int pos) {
assert(ps);//ps不能为NULL
pos -= 1;//转化为arr下标
assert(pos >= 0 && pos < ps->size);
//前移一位arr[pos]位置及后面的数据,如上同样有两种方法,这里就只演示一种方法
memmove(&(ps->arr[pos]), &(ps->arr[pos + 1]), (ps->size - pos - 1) * sizeof(SLDataType));
ps->size--;
}
9. 查找数据
SeqList.h
void SLFind(SL* ps,SLDataType x);
SeqList.c
void SLFind(SL* ps, SLDataType x) {
assert(ps);//ps不能为NULL
int flag = 0;设立标志
//遍历顺序表找到数据
for (int i = 0; i < ps->size; i++) {
if (ps->arr[i] == x) {
printf("找到数据,位于:第%d个数据,arr[%d]\n", i + 1, i);
flag = 1;
}
}
if (flag == 0) {
printf("没有此数据!\n");
}
}
四、测试
test.c
#include "SeqList.h"
void Test() {
//创建顺序表
SL s;
//初始化测试
SLInit(&s);
//向前添加内容测试
SLPushFornt(&s, 1);
SLPushFornt(&s, 2);
SLPushFornt(&s, 3);
//打印测试
SLPrintf(s);
//向后添加内容测试
SLPushBack(&s, 4);
SLPushBack(&s, 5);
SLPushBack(&s, 6);
SLPrintf(s);
//头部删除数据测试
SLPopFornt(&s);
SLPrintf(s);
//尾部删除数据测试
SLPopBack(&s);
SLPrintf(s);
//指定位置前插入数据测试
SLInsert(&s, 3, 34);
SLPrintf(s);
//指定位置删除数据测试
SLErace(&s, 3);
SLPrintf(s);
//查找数据测试
SLFind(&s, 4);
//顺序表销毁
SLDestory(&s);
}
int main() {
Test();
return 0;
}
结果如图:
OK,到这里我们的顺序表框架就已经搭建好啦
五、End
下面是全部代码:
SeqList.h
#pragma once
//静态顺序表
//#define N 10 //定义数组长度
//typedef int SLDataType; //定义顺序表数组类型,方便后期修改
//
//struct SeqList {
// SLDataType arr[N];
// int size;//记录有效数据个数
//};
//
//typedef struct SeqList SL;//重定义顺序表名称,方便后期调用
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
//动态顺序表
typedef int SLDataType; //定义顺序表数组类型,方便后期修改
typedef struct SeqList {
SLDataType* arr;//arr指向一块内存,可以方便后期扩容
int size;//记录有效的数据个数
int capacity;//记录空间总共可以存放多少个数据
}SL;
//typedef struct SeqList SL;//定义顺序表数组类型,方便后期修改,或者可以如上面的写法
//顺序表销毁
void SLDestory(SL* ps);//传入顺序表的地址
//顺序表初始化
void SLInit(SL* ps);
//检查数据是否满了
void CheckSLCapacity(SL* ps);
//顺序表打印
void SLPrintf(SL s);//这里不需要对顺序表修改,所以不用传入指针
//头部插入数据x
void SLPushFornt(SL* ps,SLDataType x);
//尾部插入数据x
void SLPushBack(SL* ps, SLDataType x);
//头部删除数据
void SLPopFornt(SL* ps);
//尾部删除数据
void SLPopBack(SL* ps);
//指定位置前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置删除数据
void SLErace(SL* ps, int pos);
//查找数据
void SLFind(SL* ps, SLDataType x);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"
//顺序表的销毁
void SLDestory(SL* ps) {
assert(ps);//判断ps是否为NULL
if (ps->arr != NULL) {
free(ps->arr);
ps->arr = NULL;
}
ps->size = ps->capacity = 0;
}
//顺序表初始化
void SLInit(SL* ps) {
assert(ps);
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//检查顺序表数据是否满了
void CheckSLCapacity(SL* ps) {
assert(ps);//ps不能为空格
if (ps->size == ps->capacity) {//判断有效数据个数是否等于顺序表数据个数
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//判断capacity是否为开辟数据,如果为开辟数据,则默认为其开辟4个数据
//若开辟了数据了,则将原数据变成原来的两倍记录在newCapacity中
SLDataType* Tmpps = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
//使用realloc函数,将ps->arr的内存空间扩展为newCapacity * sizeof(SLDataType)大小
//newCapacity为数据个数,sizeof(SLDataType)为单个数据的字节数目
//因为realloc返回的是void*所以要强转为SLDataType*类型
if (Tmpps == NULL) {
perror("realloc fail!\n");
exit(1);//退出程序
}
//当realloc开辟空间失败时,会返回NULL,为了原arr地址不被覆盖,则使用Tmpps
ps->arr = Tmpps;//将tmpps的值赋值给arr
ps->capacity = newCapacity;//更新数据个数
}
}
//顺序表打印
void SLPrintf(SL s) {
for (int i = 0; i < s.size; i++) {
printf("%d ", s.arr[i]);
}
printf("\n");
}
//头部插入数据
void SLPushFornt(SL* ps, SLDataType x) {
assert(ps);//ps不能为NULL
CheckSLCapacity(ps);//判断顺序表是否满了
if (ps->size == 0) {//如果顺序表还未插入数据,直接插入头部即可
ps->arr[0] = x;
}
else {//下面是插入了数据的情况
//1.我们要将现有的所以数据后移,采取从后往前移动更好
//方法一:
//for (int i = ps->size - 1; i >= 0; i--) {
// ps->arr[i + 1] = ps->arr[i];//将arr[i]处的数据移动到arr[i+1]处
//}
//方法二:
memmove(&(ps->arr[1]), ps->arr, sizeof(SLDataType) * ps->size);
//将ps->arr往后的sizeof(SLDataType) * ps->size个字节移动到ps->arr[1]地址处
//2.插入数据x
ps->arr[0] = x;
}
ps->size++;//有效数据个数加一
}
//尾部插入数据
void SLPushBack(SL* ps, SLDataType x) {
assert(ps);//ps不能为NULL
CheckSLCapacity(ps);//检查内存是否足够
ps->arr[ps->size++] = x;
}
//头部删除数据
void SLPopFornt(SL* ps) {
assert(ps);//ps不能为NULL
assert(ps->size);//必须有数据
if (ps->size == 1) {//如果只有一个数据,直接删除
ps->size--;
}
else {//不止一个数据,arr[0]后面所有的数据前移一格即可,同样有两种方法
方法一:
//for (int i = 1; i < ps->size; i++) {
// ps->arr[i - 1] = ps->arr[i];
//}
//方法二:
memmove(ps->arr, &(ps->arr[1]), (ps->size - 1) * sizeof(SLDataType));
ps->size--;
}
}
//尾部删除数据
void SLPopBack(SL* ps) {
assert(ps);//ps不能为NULL
assert(ps->size);//ps->size != 0
//直接删除即可
ps->size--;
}
//指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x) {//pos为第几个数据,并非arr下标
assert(ps);//ps不能为NULL
pos -= 1;//转化为arr下标
assert(pos >= 0 && pos <= ps->size);
CheckSLCapacity(ps);//检查内存
//后移一位arr[pos]位置及后面的数据,如上同样有两种方法,这里就只演示一种方法
memmove(&(ps->arr[pos + 1]), &(ps->arr[pos]), (ps->size - pos) * sizeof(SLDataType));
//插入数据
ps->arr[pos] = x;
ps->size++;
}
//指定位置删除数据
void SLErace(SL* ps, int pos) {
assert(ps);//ps不能为NULL
pos -= 1;//转化为arr下标
assert(pos >= 0 && pos < ps->size);
//前移一位arr[pos]位置及后面的数据,如上同样有两种方法,这里就只演示一种方法
memmove(&(ps->arr[pos]), &(ps->arr[pos + 1]), (ps->size - pos - 1) * sizeof(SLDataType));
ps->size--;
}
//查找数据
void SLFind(SL* ps, SLDataType x) {
assert(ps);//ps不能为NULL
int flag = 0;
//遍历顺序表找到数据
for (int i = 0; i < ps->size; i++) {
if (ps->arr[i] == x) {
printf("找到数据,位于:第%d个数据,arr[%d]\n", i + 1, i);
flag = 1;
}
}
if (flag == 0) {
printf("没有此数据!\n");
}
}
Test.c的代码上面已经存在啦!
最后,谢谢您的浏览,希望本文章对你有所帮助!