【数据结构】顺序表(SeqList)(增、删、查、改)详解

news2024/9/25 7:14:52

一、顺序表的概念和结构

1、顺序表的概念:

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构一般情况下采用数组存储。在数组上完成数据的增删查改


 2、顺序表的结构:

(1)静态顺序表:使用定长数组存储元素

缺点:只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致 N 定大了,空间开多了浪费,开少了不够用。

// 顺序表的静态存储
#define N 10
typedef int SLDataType;

typedef struct SeqList
{
	SLDataType array[N];// 定长数组
	size_t size;// 有效数据个数
}SeqList;

 (2)动态顺序表:使用动态开辟的数组存储元素

优点:动态顺序表可以根据需要动态的分配空间大小。

// 顺序表的动态存储
typedef int SLDataType; //类型重命名,后续要存储其它类型时方便更改

typedef struct SeqList
{
	SLDataType* a;// 指向动态开辟的数组
	size_t size;// 有效数据个数(当前顺序表中已存放的数据个数)
	size_t capacity;// 容量大小(顺序表总共能够存放的数据个数)
}SeqList;

:size_t 数据类型表示 C 中任何对象所能达到的最大长度,它是无符号整数。 


 二、动态顺序表的接口实现

1、创建文件

  1. test.c(主函数、测试顺序表各个接口功能)
  2. SeqList.c(动态顺序表接口函数的实现)
  3. SeqList.h(动态顺序表的类型定义、接口函数声明、引用的头文件)

  


 2、SeqList.h 头文件代码

// SeqList.h
#pragma once // 防止头文件被二次引用

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

typedef int SLDataType; // 后续要存储其它类型时方便直接更改

// 顺序表的动态存储
typedef struct SeqList
{
    SLDataType* a; // 指向动态开辟的数组
    size_t size ; // 有效数据个数
    size_t capicity ; // 容量空间的大小
}SeqList;

// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl);
// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* psl);
// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* psl);
// 顺序表打印
void SeqListPrint(SeqList* psl);
// 顺序表查找
int SeqListFind(SeqList* psl, SLDataType x); 
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos);
// 查看顺序表中的有效数据个数 
size_t SeqListSize(const SeqList* psl);
// 修改指定下标位置的数据
void SeqListAt(SeqList* psl, size_t pos, SLDataType x);

 三、在 SeqList.c 中实现各个接口函数

1、初始化顺序表

// 初始化顺序表
void SeqListInit(SeqList* psl)
{
	assert(psl); // 断言 -- 防止传进来的指针为空

	psl->a = NULL; // 初始化顺序表为空
    psl->size = 0; // 初始数据个数为0
	psl->capacity = 0; // 初始空间容量为0
}

2、顺序表销毁

// 销毁顺序表
void SeqListDestroy(SeqList* psl)
{
	assert(psl); // 断言 -- 防止传进来的指针为空

	free(psl->a); // 释放动态开辟的空间
    psl->a = NULL; // 置空
	psl->size = 0; // 数据个数置为0
	psl->capacity = 0; // 空间容量大小置为0
}

3、检查空间,如果满了,进行增容 

// 检查顺序表容量是否满了,好进行增容
void CheckCapity(SeqList* psl)
{
	if (psl->size == psl->capacity) // 检查容量,满了则增容
	{
        // 原来容量为0,扩容为4;不为0,扩容为原来的2倍
		size_t newcapacity = psl->capacity == 0 ? 4 : 2 * (psl->capacity);
		psl->a = (SeqList*)realloc(psl->a, sizeof(SLDateType) * newcapacity); // 扩容
		psl->capacity = newcapacity; // 更新容量
	}
}
为什么不采取插一个数据,增容一个空间的方式呢?

因为这样做很麻烦,代价也很大。一般情况下,为了避免频繁的增容,当空间满了之后,我们不会选择一个一个的去增,而是一次增容 2 倍,当然也不会一次增容太大,比如 3 倍 4 倍,这样空间可能会造成浪费,所以 2 倍是一个折中的选择。


 realloc 在开辟动态内存空间时,如果传给它的是一个空指针,那么他就会开辟一个新的内存空间,用法类似malloc


4、顺序表尾插

// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDateType x) // O(1)
{
	// 不需要断言 空指针也符合条件

    // 第一种写法:
    /* CheckCapacity(psl); // 检查顺序表容量是否已满
	psl->a[psl->size] = x; // 尾插数据
	psl->size++; // 有效数据个数+1 */

    // 第二种写法:
	SeqListInsert(psl, psl->size, x);
}


5、顺序表尾删

// 顺序表尾删
void SeqListPopBack(SeqList* psl) // O(1)
{
	assert(psl); // 断言

    // 第一种写法:
    /* assert(psl->size > 0); // 尾删 -- 顺序表不能为空
	//psl->a[psl->size - 1] = 0; // 不知道SLDataType是什么类型的数据,不能冒然的直接赋值为0
	psl->size--; // 有效数据个数-1 */

    // 第二种写法:
	SeqListErase(psl, psl->size - 1);
}
关于在程序中检查错误的方式:

(1)温柔检查法: 如果出现错误,程序就不再继续执行。因为一般情况下,程序运行成功就返回0,则运行失败就返回-1。
(2)暴力检查法(推荐):如果发生错误,程序会报警告,可以直接知道出错位置。

// 温柔处理方式
    if (psl->size > 0)
    {
        psl->a[ps->size - 1] = 0;
        psl->size--;
    }

// 暴力处理方式
    assert(psl->size > 0);
    psl->size--;

:不知道 SLDataType 是什么类型的数据,不能冒然的将顺序表最后一个数据赋值为 0,我们只需将有效数据个数 size 减 1 即可达到尾删的目的。 


6、顺序表头插

// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDateType x) // O(n)
{
    // 头插不需要断言 空指针也符合条件

    /* CheckCapacity(psl); // 检查顺序表容量是否已满
	for (int i = psl->size - 1; i >= 0; i--) // 顺序表中[0,size-1]的元素依次向后挪动一位
	{
		psl->a[i + 1] = psl->a[i];
	}
	psl->a[0] = x; // 头插数据
	psl->size++; // 有效数据个数+1 */

	SeqListInsert(psl, 0, x);
}


7、顺序表头删

// 顺序表头删
void SeqListPopFront(SeqList* psl) // O(n)
{
	assert(psl); // 断言

    // 方法一:
    /* assert(psl->size > 0);  //顺序表不能为空
	for (int i = 1; i < psl->size; i++) // 顺序表中[1,size-1]的元素依次向前挪动一位
	{
		psl->a[i - 1] = psl->a[i];
	}
	psl->size--; // 有效数据个数-1 */

    // 方法二:
	SeqListErase(psl, 0);
}


8、顺序表打印

// 打印顺序表
void SeqListPrint(SeqList* ps)
{
	assert(ps); // 断言

    if (psl->size == 0) // 顺序表为空
	{
		printf("顺序表为空\n");
		return;
	}

    // 顺序表不为空
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

 9、顺序表查找指定值

// 顺序表查找
int SeqListFind(SeqList* psl, SLDateType x)
{
	assert(psl); // 断言
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
		{
			return i; //查找到,返回该值在数组中的下标
		}
	}
	return -1; // 没查找到
}


 10、顺序表在pos位置插入x

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDateType x)
{
	assert(psl); // 断言
	assert(pos >= 0 && pos <= (psl->size)); // 检查pos下标的合法性
	CheckCapity(psl);

    // 第一种写法:
	/* size_t end = psl->size;
	while (end > pos)
	{
		psl->a[end] = psl->a[end - 1];
		end--;
	} */

    //第二种写法:
    size_t i = 0;
	for (i = psl->size; i > pos; i--)  // 将pos位置后面的数据依次向后挪动一位
	{
		psl->a[i] = psl->a[i - 1];
	}

	psl->a[pos] = x; // 插入数据
	psl->size++; // 有效数据个数+1
}

:原先下面这种写法,当顺序表为空 size = 0 时,会导致 i = -1,执行 i >= pos 时,i 被算术转换成无符号数,而无符号数的 -1 是一个值很大的正数远远大于 pos,满足条件进入循环,会造成越界访问。 

int i = 0;
for (i = psl->size - 1; i >= pos; i--)
	psl->a[i + 1] = psl->a[i];

:转换并不会改变 i 本身的值,而是在执行 i >= pos 时,生成一个临时值与 pos 进行比较。如果在顺序表头部(pos = 0)插入数据,i 最终也会减成 -1,被算术转换后变成一个很大的数。


总结避免负数给到无符号数,或者避免有符号数变成负数后,被算术转换或整型提升后,变成一个很大的数。按照第二种写法就可以避免 i 变成负数(-1)了。


 实现了此接口,顺序表头插尾插相当于在下标为 0psl -> size-1 位置处插入数据。



11、顺序表删除pos位置的值

// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl); // 断言
    assert(psl->size > 0); // 顺序表不能为空
	assert(pos >= 0 && pos < psl->size); // 检查pos下标的合法性

    // 第一种写法:
	/* size_t start = pos;
	while (start < psl->size-1)
	{
		psl->a[start] = psl->a[start + 1];
		start++;
	} */

    //第二种写法:
    size_t i = 0;
	for (i = pos + 1; i < psl->size; i++)  // 将pos位置后面的数据依次向前挪动一位
	{
		psl->a[i - 1] = psl->a[i];
	}

	psl->size--; // 有效数据个数-1
}

实现了此接口,顺序表头删尾删相当于删除下标为 0 psl -> size-1 位置处的数据。



12、查看顺序表中的有效数据个数 

// 查看顺序表中的有效数据个数
size_t SeqListSize(const SeqList* psl)
{
	assert(psl); // 断言
	return psl->size;
}
为什么不选择在主函数里面直接通过定义的结构体变量直接访问,还要弄一个相关函数呢?

在数据结构中,如果要访问或者修改数据结构中的数据不要直接访问,而是应该去调用它的函数来访问和修改,这样会更加规范和安全,也更方便检查出是否出现了越界等错误情况


数组越界是不一定报错的,系统对越界的检查是设岗检查。

  • 越界读(读了不属于自己的数据),一般是检查不出来的,往往并不会造成内存奔溃。

  • 越界写(缓冲区溢出)如果是修改到标志位才会被检查出来,会造成数据破坏,严重会造成内存奔溃。

(系统在数组末尾后设的有标志位,越界写时,恰好修改到标志位了,就会被检查出来)


13、修改指定下标位置的数据

// 修改指定下标位置的数据
void SeqListAt(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl); // 断言
    assert(psl->size > 0); // 顺序表不能为空
	assert(pos >= 0 && pos < psl->size); // 检查pos下标的合法性
	psl->a[pos] = x; // 修改pos下标处对应的数据
}

 四、代码整合

// SeqList.c
#include "SeqList.h"

// 初始化顺序表
void SeqListInit(SeqList* psl)
{
	assert(psl); // 断言 -- 防止传进来的指针为空

	psl->a = NULL; // 初始化顺序表为空
    psl->size = 0; // 初始数据个数为0
	psl->capacity = 0; // 初始空间容量为0
}

// 销毁顺序表
void SeqListDestroy(SeqList* psl)
{
	assert(psl); // 断言 -- 防止传进来的指针为空

	free(psl->a); // 释放动态开辟的空间
    psl->a = NULL; // 置空
	psl->size = 0; // 数据个数置为0
	psl->capacity = 0; // 空间容量大小置为0
}

// 检查顺序表容量是否满了,好进行增容
void CheckCapity(SeqList* psl)
{
	if (psl->size == psl->capacity) // 检查容量,满了则增容
	{
        // 原来容量为0,扩容为4;不为0,扩容为原来的2倍
		size_t newcapacity = psl->capacity == 0 ? 4 : 2 * (psl->capacity);
		psl->a = (SeqList*)realloc(psl->a, sizeof(SLDateType) * newcapacity); // 扩容
		psl->capacity = newcapacity; // 更新容量
	}
}

// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDateType x) // O(1)
{
	SeqListInsert(psl, psl->size, x);
}

// 顺序表尾删
void SeqListPopBack(SeqList* psl) // O(1)
{
	assert(psl); // 断言
	SeqListErase(psl, psl->size - 1);
}

// 顺序表头插
void SeqListPushFront(SeqList* psl, SLDateType x) // O(n)
{
	SeqListInsert(psl, 0, x);
}

// 顺序表头删
void SeqListPopFront(SeqList* psl) // O(n)
{
	assert(psl); // 断言
	SeqListErase(psl, 0);
}

// 打印顺序表
void SeqListPrint(SeqList* ps)
{
	assert(ps); // 断言

    if (psl->size == 0) // 顺序表为空
	{
		printf("顺序表为空\n");
		return;
	}

    // 顺序表不为空
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

// 顺序表查找
int SeqListFind(SeqList* psl, SLDateType x)
{
	assert(psl); // 断言
	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
		{
			return i; //查找到,返回该值在数组中的下标
		}
	}
	return -1; // 没查找到
}

// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDateType x)
{
	assert(psl); // 断言
	assert(pos >= 0 && pos <= (psl->size)); // 检查pos下标的合法性
	CheckCapity(psl);

    size_t i = 0;
	for (i = psl->size; i > pos; i--)  // 将pos位置后面的数据依次向后挪动一位
	{
		psl->a[i] = psl->a[i - 1];
	}
	psl->a[pos] = x; // 插入数据
	psl->size++; // 有效数据个数+1
}

// 顺序表删除pos位置的值
void SeqListErase(SeqList* psl, size_t pos)
{
	assert(psl); // 断言
    assert(psl->size > 0); // 顺序表不能为空
	assert(pos >= 0 && pos < psl->size); // 检查pos下标的合法性

    size_t i = 0;
	for (i = pos + 1; i < psl->size; i++)  // 将pos位置后面的数据依次向前挪动一位
	{
		psl->a[i - 1] = psl->a[i];
	}
	psl->size--; // 有效数据个数-1
}

// 查看顺序表中的有效数据个数
size_t SeqListSize(const SeqList* psl)
{
	assert(psl); // 断言
	return psl->size;
}

// 修改指定下标位置的数据
void SeqListAt(SeqList* psl, size_t pos, SLDataType x)
{
	assert(psl); // 断言
    assert(psl->size > 0); // 顺序表不能为空
	assert(pos >= 0 && pos < psl->size); // 检查pos下标的合法性
	psl->a[pos] = x; // 修改pos下标处对应的数据
}

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

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

相关文章

Redis Cluster 在Spring中遇到的问题

Redis集群配置可能会在运行时更改。可以添加新节点&#xff0c;可以更改特定插槽的主节点。还有可能因为master宕机或网络抖动等原因&#xff0c;引起了主从切换。 无法感知集群槽位变化 SpringBoot2.x 开始默认使用的 Redis 客户端由 Jedis 变成了 Lettuce&#xff0c;但是当…

忽略nan值,沿指定轴计算标准(偏)差numpy.nanstd()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 沿指定轴方向 计算标准(偏)差 numpy.nanstd() [太阳]选择题 import numpy as np a np.array([[1,2],[np.nan,3]]) print("【显示】a ") print(a) print("【执行】np.std(a)&qu…

QT项目代码去UI界面常用开发步骤

QT项目代码去UI界面常用开发步骤 因项目开发需求&#xff0c;领导要求整个QT项目中不要用UI方式来实现界面&#xff0c;这样能保障程序运行稳定性以及代码的逻辑和可读性,先记录具体操作步骤如下&#xff1a; 1、首先我们通过拖控件的方式来实现界面的设计效果&#xff0c…

ARM汇编基本变量的定义和使用

一、ARM汇编中基本变量是什么? 数字变量: GBLA LCLA SETA 逻辑变量:GBLL LCLL SETL 字符串:GBLS LCLS SETLS 注意需要TAB键定义变量和行首改变值 二、使用步骤 1.引入库 代码如下(示例): GBLA led_num Reset_Handler PROCEXPORT Reset_Handler [WEA…

HCIP BGP综合实验

题目 1、AS1存在两个环回&#xff0c;一个地址为192.168.1.0/24该地址不能在任何协议中宣告&#xff1b; 2、AS3中存在两个环回&#xff0c;一个地址为192.168.2.0/24该地址不能在任何协议中宣告&#xff0c;最终要求这两个环回可以互相通讯&#xff1b; 3、AS间的骨干链路I…

Vue3搭建启动

Vue3搭建&启动 一、创建项目二、启动项目三、配置项目1、添加编辑器配置文件2、配置别名3、处理sass/scss4、处理tsx 四、添加Eslint 一、创建项目 npm create vite 1.project-name 输入项目名vue3-vite 2.select a framework 选择框架 3.select a variant 选择语言 二、启…

idea 安装 插件jrebel 报错LS client not configured.

这个报错找了好久&#xff0c;有博主说版本不对&#xff0c;我脑子没反应过来以为是随便换一个低版本的就行&#xff0c;没想到只能是2022.4.1 这个版本才行 一定要用jrebel 2022.4.1的插件版本&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 插件下载地址&…

网络面试合集

传输层的数据结构是什么&#xff1f; 就是在问他的协议格式&#xff1a;UDP&TCP 2.1.1三次握手 通信前&#xff0c;要先建立连接&#xff0c;确保双方都是在线&#xff0c;具有数据收发的能力。 2.1.2四次挥手 通信结束后&#xff0c;会有一个断开连接的过程&#xff0…

❤️创意网页:绚丽粒子雨动画

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

Codeforces Round 889 (Div. 2) 题解

晚上睡不着就来总结一下叭~&#xff08;OoO&#xff09; 赛后榜(希望不要被Hack...Orz) 终榜&#xff01;&#xff01;&#xff01; 瞬间的辉煌(呜呜呜~) 先不放图了。。怕被dalaoHack...呜呜呜~ 总结 7.29半夜比赛&#xff0c;本来是不想打的&#xff0c;感觉最近做的题太多…

Manjaro KDE 22.1.3vmware无法复制文件

Wayland 是 X11 的现代替代品&#xff0c;几十年来 X11 一直是 Linux 上的默认窗口系统。 Wayland 是一种通信协议&#xff0c;定义 X Window 显示服务器和客户端应用程序之间的消息传递。 软件还不兼容 使用X11即可

JavaScript中的switch语句

switch语句和if语句一样&#xff0c;同样是运用于条件循环中&#xff1b; 下面例子我们用switch实现 例如如果今天是周一就学习HTML&#xff0c;周二学习CSS和JavaScript&#xff0c;周三学习vue&#xff0c;周四&#xff0c;周五学习node.js&#xff0c;周六周日快乐玩耍&…

微服务项目,maven无法加载其他服务依赖

微服务项目&#xff0c;导入了工具类工程&#xff0c;但是一直报错&#xff0c;没有该类&#xff0c; 检查maven 这里的Maven的版本与idea版本不匹配可能是导致依赖加载失败的最重要原因 检查maven配置&#xff0c;我这是原来的maven&#xff0c;home 修改之后,就不报错了

39.密码长度改变图片模糊

密码长度改变图片模糊 html部分 <div class"bg"></div> <div class"container"><h1>Image Password Strength</h1><h3>Change the password to see the effect</h3><div class"email" style&quo…

Mybatis-Flex 比 MyBatis-Plus更轻量,高性能

一、Mybatis-Flex是什么&#xff1f; Mybatis-Flex 是一个优雅的 Mybatis 增强框架&#xff0c;它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库&#xff0c;其内置的 QueryWrapper^亮点 帮助我们极大的减少了 SQL 编写的工作的同时…

MQTT服务器详细介绍:连接物联网的通信枢纽

随着物联网技术的不断发展&#xff0c;MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;协议作为一种轻量级、可靠、灵活的通信协议&#xff0c;被广泛应用于物联网领域。在MQTT系统中&#xff0c;MQTT服务器扮演着重要的角色&#xff0c;作为连接物联网设备和…

MDK5__配色方案的修改

一、必要的知识 与MDK主题相关的文件有两个&#xff0c;在X:\Keil_v5\UV4路径下&#xff1a; global.propglobal.prop.def其中global.prop.def是系统默认的主题配置 如果修改过字体等&#xff0c;系统会生成一个global.prop。 二、修改的步骤 1、打开工程 菜单 Edit 下 Con…

AXI协议之AXILite开发设计(四)—Block Design使用

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 2、AXI interconnect互联组件的使用…

【Linux后端服务器开发】select多路转接IO服务器

目录 一、高级IO 二、fcntl 三、select函数接口 四、select实现多路转接IO服务器 一、高级IO 在介绍五种IO模型之前&#xff0c;我们先讲解一个钓鱼例子。 有一条大河&#xff0c;河里有很多鱼&#xff0c;分布均匀。张三是一个钓鱼新手&#xff0c;他钓鱼的时候很紧张&a…

REST API的基础:HTTP

在本文中&#xff0c;我们将深入探讨万维网数据通信的基础 - HTTP。 什么是超文本&#xff1f; HTTP&#xff08;超文本传输协议&#xff09;的命名源于“超文本”。 那么&#xff0c;什么是超文本&#xff1f; 想象一下由超链接组成的文本、图像和视频的混合物。这些链接充当我…