【数据结构】数据结构及其顺序表

一、数据结构

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的代码上面已经存在啦!

最后,谢谢您的浏览,希望本文章对你有所帮助!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值