初级数据结构(一)——顺序表

news2024/11/24 22:35:37

文中代码源文件已上传:数据结构源码

1、顺序表的特点

1.1、数组

        现实中数据记录一般都记录在表格中,如进货单、菜单等,它们的最大特点就是有序。表述中可以用第一项、第二项、第 n 项来描述表格中某个数据或者某串数据。在 C 语言中,数组的特点恰好匹配此功能。

        由于数组在内存中的储存方式就如同列表依序排布,对数组可以用 arr[n] 或者 *(arr+n) 来迅速获得第 n-1 项数据。再加上而且数组是 C 语言的原生类型,创建数组极其便利,作为有序数据的载体着实是不二之选。

1.2、结构

        顺序表为了方便使用,除了用数组作为数据载体外,一般还包含记录数组空间大小和开辟空间大小的两个变量。常以结构体将这三个变量作为成员变量进行囊括。主要有两种创建方式:

        柔性数组顺序表( Sequence table with flexible array ):

#include <stdlib.h>

//重定义数据类型
typedef int DATA;

//创建并重定义结构体类型
typedef struct SeqTab
{
    int size;           //数据长度
    int capacity;       //数据空间大小
    DATA arr[];         //数据载体柔性数组
}SeqTab;

int main()
{
    //开辟结构体空间
    SeqTab* sqList = (SeqTab*)malloc(sizeof(SeqTab) + sizeof(DATA)*4);
    //初始化数据长度及空间大小
    sqList->size = 0;
    sqList->capacity = 4;

    return 0;
}

        数组指针顺序表( Sequence table with array pointer ):

#include <stdlib.h>

//重定义数据类型
typedef int DATA;

//创建并重定义结构体类型
typedef struct SeqTab
{
    int size;           //数据长度
    int capacity;       //数据空间大小
    DATA* arr;          //数据载体数组指针
}SeqTab;

int main()
{
    //创建结构体变量
    SeqTab sqList;
    //开辟数据载体数组空间
    sqList.arr = (DATA*)malloc(sizeof(DATA)*4);
    //初始化数据长度及空间大小
    sqList.size = 0;
    sqList.capacity = 4;

    return 0;
}

2、顺序表创建

        接下来以数组指针顺序表为例进行演示。

2.1、文件结构

        seqTab.h :用于创建结构体类型及声明函数;

        sqFunction.c :用于创建顺序表初始化及增删改查的函数;

        main.c :仅创建 main 函数,用作测试。

2.2、前期工作

        在 seqTab.h 中先写入以下内容:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//用于初始化顺序表时开辟空间
#define INIT_SIZE 4

//顺序表数据类型,方便顺序表储存数据类型修改
typedef int DATATYPE;

//创建顺序表结构体类型
typedef struct SeqTab
{
	DATATYPE* data;
	int size;
	int capacity;
}SeqTab;

//---------------------函数声明---------------------
//初始化顺序表
extern void DataInit(SeqTab*);

//销毁顺序表
extern void DataDestory(SeqTab*);

//打印顺序表
extern void DataPrint(const SeqTab*);

         在 sqFunction.c 中包含 seqTab.h 并分别创建一个初始化顺序表和销毁顺序表的函数:

#include "seqTab.h"

//初始化顺序表
void DataInit(SeqTab* sq)
{
    //断言确保结构体指针不为空
	assert(sq);
    //为顺序表开辟空间
	sq->data = (DATATYPE*)malloc(INIT_SIZE * sizeof(DATATYPE));
    //加入判断防止空间开辟失败
	if (sq->data == NULL)
	{
		perror("Malloc Fail");
		return;
	}
    //初始化记录数据长度及开辟空间长度的变量
	sq->size = 0;
	sq->capacity = INIT_SIZE;
}

//销毁顺序表
void DataDestory(SeqTab* sq)
{
    //断言确保结构体指针不为空
	assert(sq);
    //释放顺序表数据空间
	free(sq->data);
    //数组指针置空
	sq->data = NULL;
    //数组长度及空间尺寸全部置0
	sq->size = 0;
	sq->capacity = 0;
}

//打印顺序表
void DataPrint(const SeqTab* sq)
{
    //断言确保结构体指针不为空
	assert(sq);
    //遍历顺序表并逐个数据打印
	for (int i = 0; i < sq->size; i++)
	{
		printf("%d ", sq->data[i]);
	}
	printf("\n");
}

        最后在 main.c 中包含 seqTab.h,并创建一个顺序表及初始化:

#include "seqTab.h"

int main()
{
	//创建顺序表
	SeqTab sqList;

    //初始化顺序表
	DataInit(&sqList);

    return 0;
}

        至此,前期工作准备完毕,之后便是对顺序表的数据进行增删改查。

3、顺序表操作

3.1、增

        插入数据一般为头部插入数据、尾部插入数据及指定位置插入数据。插入数据时除了写入数据到数组中,还需时刻判断开辟的空间尺寸是否足以容纳已有数据。

        先将以下三个函数声明添加到 seqTab.h 中:

//指定位置插入数据
extern void DataInsert(SeqTab*, int, DATATYPE);
//头部插入数据
extern void DataPushHead(SeqTab*, DATATYPE);
//尾部插入数据
extern void DataPushTail(SeqTab*, DATATYPE);

        然后便是 DataInsert 函数。如图:

        据此可以轻松写出其代码。以下写入 sqFunction.c 中: 

void DataInsert(SeqTab* sq, int pos, DATATYPE data)
{
    //数据有效性判断
	assert(sq);
	if (pos < 0 || pos > sq->size)
	{
		printf("Illegal Position : %d\n", pos);
		return;
	}
    //空间不足则创建空间
	if (sq->size + 1 >= sq->capacity)
	{
        //申请新空间
		DATATYPE* ptr_newSqData = (DATATYPE*)realloc(sq->data, sizeof(DATATYPE) * (sq->capacity * 2));
        //空间申请结果判断
		if (ptr_newSqData == NULL)
		{
			perror("Realloc Fail");
			return;
		}
        //赋予新空间地址
		sq->data = ptr_newSqData;
        //空间大小记录翻倍
		sq->capacity *= 2;
	}
    //数据后移直至腾出 pos 位置的空间
	for (int i = sq->size; i > pos; i--)
	{
		sq->data[i] = sq->data[i - 1];
	}
    //写入数据
	*(sq->data + pos) = data;
    //数据长度+1
	sq->size++;
}

        至于头插尾插数据,只不过是上述函数 pos 位置的区别。因此:

//pos = 0 便是头插
void DataPushHead(SeqTab* sq, DATATYPE data)
{
	DataInsert(sq, 0, data);
}
//pos = 数据尺寸便是尾插
void DataPushTail(SeqTab* sq, DATATYPE data)
{
	DataInsert(sq, sq->size, data);
}

        在 main 函数里写入下列代码验证一下:

	DataInsert(&sqList, 10, 32);   //报错
	DataPushTail(&sqList, 10);
	DataPrint(&sqList);            //打印 10
	DataPushHead(&sqList, 20);
	DataPrint(&sqList);            //打印 20 10
	DataPushHead(&sqList, 3);
	DataPushTail(&sqList, 6);
	DataPushHead(&sqList, 8);
	DataPushHead(&sqList, 7);
	DataPushHead(&sqList, 2);
	DataPushTail(&sqList, 100);
	DataPushTail(&sqList, 432);
	DataPrint(&sqList);            //打印 2 7 8 3 20 10 6 10 432

        结果与预期无误。至此插入功能便已完成。

3.2、删

        正如插入数据分为头插、尾插及指定插,删除也分头删及任意位删。与插入相反,删除数据需要在数据删除结束后关注数据长度与开辟的数据空间,当空间分配过大时,对多余空间进行回收。

        同样先将以下三个函数声明添加到 seqTab.h 中:

//指定位置删除数据
extern void DataRemove(SeqTab*, int);
//删除头部数据
extern void DataPopHead(SeqTab*);
//删除尾部数据
extern void DataPopTail(SeqTab*);

         DataRemove 函数流程图如下:

        根据图中逻辑在 sqFunction.c 中写入以下:

void DataRemove(SeqTab* sq, int pos)
{
    //数据有效性判断
	assert(sq);
	if (pos < 0 || pos > sq->size - 1)
	{
		printf("Illegal Position : %d\n", pos);
		return;
	}
    //列表不为空则执行
	if (sq->size - 1 >= 0)
	{
        //由 pos 位开始,之后所有数据前移 1 位
		for (int i = pos; i < sq->size - 1; i++)
		{
			sq->data[i] = sq->data[i + 1];
		}
        //数据长度-1
		sq->size--;
	}
    //开辟空间过大则执行
	if (sq->size < sq->capacity / 2)
	{
        //申请新空间
		DATATYPE* ptr_newSqData = (DATATYPE*)realloc(sq->data, sizeof(DATATYPE) * (sq->capacity / 2));
        //空间申请结果判断
		if (ptr_newSqData == NULL)
		{
			perror("Realloc Fail");
			return;
		}
        //赋予新空间地址
		sq->data = ptr_newSqData;
        //空间大小记录减半
		sq->capacity /= 2;
	}
}

         至于头删尾删数据,也同样是上述函数 pos 位置的区别:

//头删 pos = 0
void DataPopHead(SeqTab* sq)
{
	DataRemove(sq, 0);
}
//尾删 pos = 数据长度-1
void DataPopTail(SeqTab* sq)
{
	DataRemove(sq, sq->size - 1);
}

        之后同样是验证,将以下代码写到之前测试代码之后:

	DataRemove(&sqList, 100);    //报错
	DataRemove(&sqList, 3);
	DataPrint(&sqList);          //打印 2 7 8 20 10 6 100 432
	DataPopHead(&sqList);
	DataPrint(&sqList);          //打印 7 8 20 10 6 100 432
	DataPopTail(&sqList);
	DataPrint(&sqList);          //打印 7 8 20 10 6 100
	DataPopTail(&sqList);
	DataPopTail(&sqList);
	DataPopTail(&sqList);
	DataPopTail(&sqList);
	DataPopTail(&sqList);
	DataPrint(&sqList);          //打印 7
	DataPopTail(&sqList);
	DataPopTail(&sqList);
	DataPrint(&sqList);          //因为 size = 0 ,pos = -1 , 报错

         删除数据功能完毕。

3.3、改

        改数据的功能实现起来,逻辑上毫无难度可言。以下函数声明添加到 seqTab.h 中:

//修改指定位置数据
extern void DataModify(SeqTab*, int, DATATYPE);

         在 sqFunction.c 中写入:

void DataModify(SeqTab* sq, int pos, DATATYPE data)
{
    //数据有效性判断
	assert(sq);
	if (pos < 0 || pos > sq->size - 1)
	{
		printf("Illegal Position : %d\n", pos);
		return;
	}
    //修改数据
	sq->data[pos] = data;
}

        在 main 函数中追加以下代码验证:

	for (int i = 0; i < 10; i++)
	{
		DataPushTail(&sqList, i);
	}
	DataPrint(&sqList);            //打印 0 1 2 3 4 5 6 7 8 9
	DataModify(&sqList, 100, 30);  //报错
	DataModify(&sqList, 3, 30);
	DataPrint(&sqList);            //打印 0 1 2 30 4 5 6 7 8 9

         完毕。

3.4、查

        查到数据返回该数据的位置,查不到返回 -1 。同样很简单。 seqTab.h 中写入声明:

//在表中查找数据
extern int DataSearch(SeqTab*, DATATYPE);

        然后是 sqFunction.c 中写入:

int DataSearch(SeqTab* sq, DATATYPE data)
{
    //数据有效性判断
	assert(sq);
    //遍历数组
	for (int i = 0; i < sq->size; i++)
	{
        //如果找到数据则返回下标
		if (sq->data[i] == data)
		{
			return i;
		}
	}
    //遍历完毕仍找不到数据返回 -1
	return -1;
}

        main 函数中验证:

	int num = 1200;
	int pos = DataSearch(&sqList, num);
	if (pos == -1)
	{
		printf("The number \"%d\" is not exist!\n", num);
	}
	else
	{
		printf("The position of number \"%d\" is %d\n", num, pos);
	}//打印不存在

	num = 9;
	pos = DataSearch(&sqList, num);
	if (pos == -1)
	{
		printf("The number \"%d\" is not exist!\n", num);
	}
	else
	{
		printf("The position of number \"%d\" is %d\n", num, pos);
	}//打印 9

	DataModify(&sqList, DataSearch(&sqList, 8), 11001);
	DataPrint(&sqList);    //打印 0 1 2 30 4 5 6 7 11001 9

        至此增删改查功能实现均已完毕。除此之外,还有排序、截断等其他一系列可以自定的操作。总之,操作顺序表就是操作数组,实现起来难度几乎为 0 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1287922.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决ssr服务端渲染程序启动报错: ReferenceError: location is not defined

现象&#xff1a; 原因&#xff1a;chatgpt给出的解释很到位&#xff1a; 该错误表明代码尝试访问 location 对象&#xff0c;该对象通常在浏览器环境中可用。 然而&#xff0c;你的服务器端代码正在 Node.js 环境中运行&#xff0c;而在这个环境中 location 对象未定义。 问…

Kafka 生产者 API 指南:深入理解生产者的实现与最佳实践

Kafka 是一个高性能、分布式的消息中间件系统&#xff0c;而其生产者 API 是连接应用程序与 Kafka 集群之间的纽带。本篇博客将深入探讨 Kafka 生产者 API 的核心概念、用法&#xff0c;以及一些最佳实践&#xff0c;帮助你更好地利用 Kafka 构建可靠的消息生产系统。 1. Kafk…

uniapp得app云打包问题

获取appid&#xff0c;具体可以查看详情 也可以配置图标&#xff0c;获取直接生成即可 发行 打包配置 自有证书测试使用时候不需要使用 编译打包 最后找到安装包apk安装到手机 打包前&#xff0c;图片命名使用要非中文&#xff0c;否则无法打包成功会报错

Kafka中的Topic

在Kafka中&#xff0c;Topic是消息的逻辑容器&#xff0c;用于组织和分类消息。本文将深入探讨Kafka Topic的各个方面&#xff0c;包括创建、配置、生产者和消费者&#xff0c;以及一些实际应用中的示例代码。 1. 介绍 在Kafka中&#xff0c;Topic是消息的逻辑通道&#xff0…

SpringBoot集成mail发送邮件

前言 发送邮件功能&#xff0c;借鉴 刚果商城&#xff0c;根据文档及项目代码实现。整理总结便有了此文&#xff0c;文章有不对的点&#xff0c;请联系博主指出&#xff0c;请多多点赞收藏&#xff0c;您的支持是我最大的动力~ 发送邮件功能主要借助 mail、freemarker以及rocke…

MQTT框架和使用

目录 MQTT框架 1. MQTT概述 1.1 形象地理解三个角色 1.2 消息的传递 2. 在Windows上体验MQTT 2.1 安装APP 2.2 启动服务器 2.3 使用MQTTX 2.3.1 建立连接 2.3.2 订阅主题 2.3.3 发布主题 2.4 使用mosquitto 2.4.1 发布消息 2.4.2 订阅消息 3. kawaii-mqtt源码分析…

STM32下载程序的五种方法

刚开始学习 STM32 的时候&#xff0c;很多小伙伴满怀热情买好了各种设备&#xff0c;但很快就遇到了第一个拦路虎——如何将写好的代码烧进去这个黑乎乎的芯片&#xff5e; STM32 的烧录方式多样且灵活&#xff0c;可以根据实际需求选择适合的方式来将程序烧录到芯片中。本文将…

ESP32-Web-Server编程-在网页中插入图片

ESP32-Web-Server编程-在网页中插入图片 概述 图胜与言&#xff0c;在网页端显示含义清晰的图片&#xff0c;可以使得内容更容易理解。 需求及功能解析 本节演示在 ESP32 Web 服务器上插入若干图片。在插入图片时还可以对图片设置一个超链接&#xff0c;用户点击该图片时&a…

go-fastfds部署心得

我是windows系统安装 Docker Desktop部署 docker run --name go-fastdfs&#xff08;任意的一个名称&#xff09; --privilegedtrue -t -p 3666:8080 -v /data/fasttdfs_data:/data -e GO_FASTDFS_DIR/data sjqzhang/go-fastdfs:lastest docker run&#xff1a;该命令用于运…

常见测试技术都有哪些?

测试技术是用于评估系统或组件的方法&#xff0c;目的是发现它是否满足给定的要求。系统测试有助于识别缺口、错误&#xff0c;或与实际需求不同的任何类型的缺失需求。测试技术是测试团队根据给定的需求评估已开发软件所使用的最佳实践。这些技术可以确保产品或软件的整体质量…

我想修改vCenter IP地址

部署vCenter Server Appliance后&#xff0c;您可以在vCenter修改DNS设置并选择域名服务器使用。您可以编辑vCenter Server Appliance的IP地址设置。从vSphere 6.5开始正式支持vCenter修改IP地址。因此可以更改vCenter Server Appliance的IP地址和DNS设置。 注意&#xff1a;更…

AI助力智慧农业,基于YOLOv3开发构建农田场景下的庄稼作物、田间杂草智能检测识别系统

智慧农业随着数字化信息化浪潮的演变有了新的定义&#xff0c;在前面的系列博文中&#xff0c;我们从一些现实世界里面的所见所想所感进行了很多对应的实践&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《自建数据集&#xff0c;基于YOLOv7开发构建农田场景下杂草…

Javaweb之前端工程打包部署的详细解析

6 打包部署 我们的前端工程开发好了&#xff0c;但是我们需要发布&#xff0c;那么如何发布呢&#xff1f;主要分为2步&#xff1a; 前端工程打包 通过nginx服务器发布前端工程 6.1 前端工程打包 接下来我们先来对前端工程进行打包 我们直接通过VS Code的NPM脚本中提供的…

Linux gtest单元测试

1 安装git sudo apt-get install git2 下载googletest git clone https://github.com/google/googletest.git3 安装googletest 注意1: 如果在 make 过程中报错,可在 CMakeLists.txt 中增加如下行,再执行下面的命令: SET(CMAKE_CXX_FLAGS “-std=c++11”) 注意2: CMakeLists…

AI助力智慧农业,基于YOLOv5全系列模型【n/s/m/l/x】开发构建不同参数量级农田场景下庄稼作物、杂草智能检测识别系统

紧接前文&#xff0c;本文是农田场景下庄稼作物、杂草检测识别的第二篇文章&#xff0c;前文是基于YOLOv3这一网络模型实现的目标检测&#xff0c;v3相对来说比较早期的网络模型了&#xff0c;本文是基于最为经典的YOLOv5来开发不同参数量级的检测端模型。 首先看下实例效果&a…

【QT】Qt常用数值输入和显示控件

目录 1.QAbstractslider 1.1主要属性 2.QSlider 2.1专有属性 2.2 常用函数 3.QScrollBar 4.QProgressBar 5.QDial 6.QLCDNumber 7.上述控件应用示例 1.QAbstractslider 1.1主要属性 QSlider、QScrollBar和Qdial3个组件都从QAbstractSlider继承而来&#xff0c;有一些共有的属性…

精准定位安全续航 无人机解决方案打造交通巡逻新模式

现代城市交通管理是城市现代化的重要组成部分&#xff0c;但传统的交通管理系统存在一系列复杂繁琐的问题&#xff0c;同时&#xff0c;交警执勤也存在较大的安全隐患。为应对这一挑战&#xff0c;复亚智能深入研究无人机技术及应用&#xff0c;推出了一套全面的无人机解决方案…

[BPE]论文实现:Neural Machine Translation of Rare Words with Subword Units

文章目录 一、完整代码二、论文解读2.1 模型架构2.2 BPE 三、过程实现四、整体总结 论文&#xff1a;Neural Machine Translation of Rare Words with Subword Units 作者&#xff1a;Rico Sennrich, Barry Haddow, Alexandra Birch 时间&#xff1a;2016 一、完整代码 这里我…

uniapp踩坑之项目:使用过滤器将时间格式化为特定格式

利用filters过滤器对数据直接进行格式化&#xff0c;注意&#xff1a;与method、onLoad、data同层级 <template><div><!-- orderInfo.time的数据为&#xff1a;2023-12-12 12:10:23 --><p>{{ orderInfo.time | formatDate }}</p> <!-- 2023-1…

D7292 双向直流电机驱动电路 ( 速度可控 ) 7V~20V 400mA,峰值电流可达1.2A 采用DIP8、SOP8的封装形式

D7292是一块带有制动和速度控制功能的双向直流电机单片电路。它可以用来驱动CDP、VCR 和 TOY等负载。该电路通过两个逻辑输入管脚的电压&#xff0c;可以控制电机正反 个方向转动以及制动。并且可以通过改变速度控制管脚的电压&#xff0c;从而方便的改变电机的速度。D7292采用…