【数据机构】2. 线性表之“顺序表”

news2025/5/10 9:29:19

- 第 96 篇 -
Date: 2025 - 05 - 09
Author: 郑龙浩/仟墨
【数据结构 2】

文章目录

  • 数据结构 - 2 -
  • 线性表之“顺序表”
    • 1 基本概念
    • 2 顺序表(一般为数组)
      • ① 基本介绍
      • ② 分类 (静态与动态)
      • ③ 动态顺序表的实现
        • **test.c文件:**
        • **SeqList.h文件:**
        • **SeqList.c文件:**

数据结构 - 2 -

线性表之“顺序表”

1 基本概念

一种逻辑结构,表示元素之间具有一对一的线性关系(即除首尾元素外,每个元素有且只有一个前驱和一个后继)

  • 逻辑上是“一条线”的结构(如 a₁ → a₂ → a₃ → ... → aₙ
  • 不关心物理存储方式(可以是连续内存或离散内存)

线性表(linear list) 是 n 个具有相同特征的数据元素的有限序列。线性表是一种在实际中广泛使用的的数据结构,常见的有: 顺序表、链表、栈、列队、字符串

线性表在逻辑上是线性结构,也就是连续的一条直线,但是在物理结构上并不一定是连续的,线性表在物理存储时,通常以数组和链表结构的形式存储

  • 顺序表是在物理和逻辑上都是连续的
  • 链表在逻辑上是连续的,在物理是非连续的

什么叫做逻辑结构呢?什么叫做物理结构呢?

  • 物理结构 –> 内存中的存储结构

  • 链表结构 –> 是我们想象出来的存储结构,为了方便我们自己理解和使用

扩展概念

内存一般分为四个区域

  • 静态去(数据段)
  • 常量区(代码段)

2 顺序表(一般为数组)

① 基本介绍

线性表的一种物理实现方式,基于连续内存(通常是数组)存储元素

  • 从物理和逻辑上都是连续的
  • 支持随机访问(通过下标直接访问,时间复杂度 O(1)
  • 插入 / 删除需移动元素(时间复杂度 O(n)

② 分类 (静态与动态)

顺序表分为两种

  • 静态顺序表 –> 使用定长数组存储 (数组长度是固定的)

    0123456789
  • 动态顺序表 –> 使用动态开辟的数组存储 (长度可以改)

    比如使用malloc

    p1,p2,p3 的地址并不是连续的,通过链表的形式可以在上一个元素中存下一个元素的地址,后面同理,直到最后一个元素

③ 动态顺序表的实现

补充:

#pragma once 什么作用?是解决头文件被重复包含的问题。比如第一次遇到#include "math.h",后续再遇到相同的 #include "math.h" 的时候,直接跳过,避免重复内容

我用VS写的动态顺序表以及一些用于顺序表的函数,内容如下

test.c文件:
#define _CRT_SECURE_NO_WARNINGS
#include "SeqList.h"

// 测试头尾插入删除
void Test_SeqList1() {
	SeqList s;
	SeqListInit(&s);

	printf("\n尾插6次,依次插入 1 ~ 6:\n");
	SeqListPushBack(&s, 1); 
	SeqListPushBack(&s, 2); 
	SeqListPushBack(&s, 3);
	SeqListPushBack(&s, 4);
	SeqListPushBack(&s, 5);
	SeqListPushBack(&s, 6);
	SeqListPrint(&s);

	printf("尾删1次:\n\n");
	SeqListPopBack(&s);
	SeqListPrint(&s);

	printf("\n头插6次,依次插入111,222,333,444,555,666:\n");
	SeqListPushFront(&s, 111);
	SeqListPushFront(&s, 222);
	SeqListPushFront(&s, 333);
	SeqListPushFront(&s, 444);
	SeqListPushFront(&s, 555);
	SeqListPushFront(&s, 666);
	SeqListPrint(&s);

	printf("\n头删2次:\n");
	SeqListPopFront(&s);SeqListPopFront(&s);
	SeqListPrint(&s);

	printf("\n查找顺序表中的数据(找到返回1,没有返回0):\n");
	printf("查找222:%d\n", SeqListFind(&s, 222));
	printf("查找123:%d\n", SeqListFind(&s, 123));

	printf("\n对数据进行排序\n");
	QuickSort(&s, 0, s.size);
	SeqListPrint(&s);
}
int main(void) {
	Test_SeqList1();
	return 0;
}
SeqList.h文件:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 顺序表 --> 静态存储
// 只是将数组简单的封装了一下,并不能按需索取
#define N 100
typedef int SLDataType; // 将int名字变为SLDataType, 有什么好处呢,如果以后想将下面的所有的int变为double 的话,不需要改下面的类型,直接在这将int变为double即可
// 顺序表,在有效数组中必须是连续的
typedef struct SeqList1 {
    SLDataType arr[100]; // 定长数组
    size_t size; // 有效数据的个数 --> 有效数据长度
}SeqList1;

// 顺序表 --> 动态存储    用的比较多的还是动态数据表
typedef int SLDataType; // 将int名字变为SLDataType, 有什么好处呢,如果以后想将下面的所有的int变为double 的话,不需要改下面的类型,直接在这将int变为double即可
// 顺序表,在有效数组中必须是连续的
typedef struct SeqList {
    SLDataType* array; // 指向动态开辟的数组
    size_t size; // 有效数据个数 --> 有效数据长度
    size_t capacity; // 容量的大小 capacity 英文意思 “容量”
}SeqList;

// 接口 ---> 增删查改
// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl);
// 顺序表销毁
void SeqListDestory(SeqList* psl);
// 顺序表打印
void SeqListPrint(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);
// 顺序表查找
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);
// 交换两个元素
void Swap(SLDataType* a, SLDataType* b);
// 顺序表排序
void QuickSort(SeqList* psl, size_t L, size_t R);
// 顺序表二分查找
int SeqListBinarySearch(SeqList* psl, SLDataType x);
SeqList.c文件:
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 顺序表 --> 静态存储
// 只是将数组简单的封装了一下,并不能按需索取
#define N 100
typedef int SLDataType; // 将int名字变为SLDataType, 有什么好处呢,如果以后想将下面的所有的int变为double 的话,不需要改下面的类型,直接在这将int变为double即可
// 顺序表,在有效数组中必须是连续的
typedef struct SeqList1 {
    SLDataType arr[100]; // 定长数组
    size_t size; // 有效数据的个数 --> 有效数据长度
}SeqList1;

// 顺序表 --> 动态存储    用的比较多的还是动态数据表
typedef int SLDataType; // 将int名字变为SLDataType, 有什么好处呢,如果以后想将下面的所有的int变为double 的话,不需要改下面的类型,直接在这将int变为double即可
// 顺序表,在有效数组中必须是连续的
typedef struct SeqList {
    SLDataType* array; // 指向动态开辟的数组
    size_t size; // 有效数据个数 --> 有效数据长度
    size_t capacity; // 容量的大小 capacity 英文意思 “容量”
}SeqList;

// 接口 ---> 增删查改
// 基本增删查改接口
// 顺序表初始化
void SeqListInit(SeqList* psl) {
    psl->array = NULL; // 初始化数组为空
    psl->size = 0; // 元素个数为 0
    psl->capacity = 0; // 容量为 0
}

// 顺序表销毁
void SeqListDestory(SeqList* psl) {
    free(psl->array);
    psl->array = NULL; // 将指针指向 “空” --> 也就是重置为空指针
    psl->size = 0; // 有效数据个数重置为 0
    psl->capacity = 0; // 容量大小重置为 0
}

// 顺序表打印
void SeqListPrint(SeqList* psl) {
    // assert(psl);
    for (int i = 0; i < psl->size; ++i) {
        printf("%d ", psl->array[i]);
    }
    printf("\n");
}

// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* psl) {
    // 如果满了,需要“增容” --> 增容多少呢,一般来说,都增二倍   多了太多,少了太少
    if (psl->size >= psl->capacity) {
        // 一定要判断是否为0,若为0,则增容为4,否则 0*2*2*2...不管*多少个,都是0
        size_t new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2;  // 初始容量设为4,后续二倍
        // new_arr 定义该变量是为了保护原本数组,假设扩容失败,就不会对原数据进行任何修改
        SLDataType* new_arr = (SLDataType*)realloc(psl->array, sizeof(SLDataType) * new_capacity);
        // 判断增容是否失败 --> 若指向的是空指针,则增容失败
        if (new_arr == NULL) {
            printf("扩容失败\n");
            return ;
        }
        // 若成功扩容,则不会执行上面 if 的语句,而是下面
        psl->array = new_arr;
        psl->capacity = new_capacity;
    }
}

// 顺序表尾插
void SeqListPushBack(SeqList* psl, SLDataType x) {
    // assert(psl); // 若psl为空,则终止执行,否则,执行 --> 仅用于Debug模式,在 Release 模式下会被禁用
    CheckCapacity(psl); // 检查是否要进行扩容
    psl->array[psl->size] = x; // 插入 x
    psl->size++; // 增加有效数据个数 ++
}

// 顺序表尾删
void SeqListPopBack(SeqList* psl) {
    // assert(psl); // 若psl为空,则终止执行,否则,执行 --> 仅用于Debug模式,在 Release 模式下会被禁用
    //psl->array[psl->size - 1] = 0; // 最后一个数据重置为 0 --> 是否重置为0都可以,做这一步操作只是为了删除“脏数据”,一般来说不重置,因为重置的话效率降低,而不重置也不影响使用
    psl->size--; // 有效数据个数--
}
// 顺序表头插 --> 将数据往后挪动
void SeqListPushFront(SeqList* psl, SLDataType x) {
    // assert(psl);
    CheckCapacity(psl); // 检查是否要进行扩容
    int end = (int)psl->size - 1; // 1指向最后一个数据 (用int,如果用size_t的话是不会出现end < 0的情况的)
    // 从最后一个数据开始,往后挪一位,直到将第一个数据挪到第二个数据的为止
    while (end >= 0) {
        psl->array[end + 1] = psl->array[end]; // 将指向数据挪动到下一位
        --end; // 向前遍历,依次指向前一数据
    }
    psl->array[0] = x; // 表头部插入x
    psl->size++; // 表有效数据++
}
// 顺序表头删
void SeqListPopFront(SeqList* psl) {
    //assert(psl);
    int start = 0;
    while (start < psl->size - 1) {
        psl->array[start] = psl->array[start + 1]; // 将当前数据存储到下一位
        start++; // 向后遍历,依次指向后一个数据
    }
    psl->size--;
}
// 顺序表查找  参数1是数组地址   参数2是查找的数据
int SeqListFind(SeqList* psl, SLDataType x) {
    // assert(psl);
    for (int i = 0; i < psl->size; i++) {
        if (psl->array[i] == x) return i;
    }
    return -1;
}
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* psl, size_t pos, SLDataType x) {
    assert(psl && pos <= psl->size);  // 必须添加边界检查
    CheckCapacity(psl); // 检查是否要进行扩容
    int end = (int)psl->size - 1; // 存储当前需要移动的位置
    while (end >= 0 && end >= pos) {
        psl->array[end + 1] = psl->array[end];
        end--; // 指向前一个
    }
    psl->array[pos] = x;  // 插入 x
    psl->size++; // 有效数据个数++
}
// 顺序表删除pos位置的值  
void SeqListErase(SeqList* psl, size_t pos) {
     assert(psl && pos <= psl->size);  // 必须添加边界检查
    int start = (int)pos;
    while (start < psl->size - 1) {
        psl->array[start] = psl->array[start + 1];
        start++;
    }
    psl->size--; // 有效数据个数--
}

// 交换两个元素
void Swap(SLDataType* a, SLDataType* b) {
    SLDataType tmp = *a;
    *a = *b;
    *b = tmp;
}
// 顺序表排序
void QuickSort(SeqList* psl, size_t L, size_t R) {
    if (L >= R)
        return ;
    int left = (int)L, right = (int)R;
    int key = left;//定义基准点key
    while (left < right)//当left<right说明还没相遇,继续数组内元素的交换
    {

        while (left < right && psl->array[right] >= psl->array[key])//right找小
        {
            right--;
        }
        while (left < right && psl->array[left] <= psl->array[key])//left找大
        {
            left++;
        }
        Swap(psl->array + right, psl->array + left); // 交换 left 和 right 位置的元素
    }
    Swap(psl->array + key, psl->array + left); // 此时left与right已经指向了同一个位置,只需要将基准点k的元素与left(right)指向的元素进行互换即可
    // 此时left位置的元素就是原来key位置的元素,而left位置左边全部是小于psl->array[left]的元素,left右边全部是大于psl->array[left]的元素
    if (left > 0) QuickSort(psl, L, left - 1); // 对左半部分进行排序
    if (left < R) QuickSort(psl, left + 1, R);// 对右半部分进行排序
}
// 顺序表二分查找 未找到->返回-1
int SeqListBinarySearch(SeqList* psl, SLDataType x) {
    // assert(psl);
    if (psl->size == 0) return -1;
    int left = 0, right = (int)psl->size - 1, mid/*中间*/; // 确定最初查找范围
    while (left <= right) {
        mid = left + ((right - left) >> 1);
        if (x < psl->array[mid]) // 在mid的左边
            right = mid - 1;
        else if (x > psl->array[mid]) // 在mid的右边
            left = mid + 1;
        else
            return mid; // 找到了
    }
    return -1; // 未找到
}

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

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

相关文章

opencv中的图像特征提取

图像的特征&#xff0c;一般是指图像所表达出的该图像的特有属性&#xff0c;其实就是事物的图像特征&#xff0c;由于图像获得的多样性&#xff08;拍摄器材、角度等&#xff09;&#xff0c;事物的图像特征有时并不特别突出或与无关物体混杂在一起&#xff0c;因此图像的特征…

【JVM-GC调优】

一、预备知识 掌握GC相关的VM参数&#xff0c;会基本的空间调整掌握相关工具明白一点&#xff1a;调优跟应用、环境有关&#xff0c;没有放之四海而皆准的法则 二、调优领域 内存锁竞争cpu占用io 三、确定目标 【低延迟】&#xff1a;CMS、G1&#xff08;低延迟、高吞吐&a…

shopping mall(document)

shopping mall&#xff08;document&#xff09; 商城的原型&#xff0c;学习&#xff0c;优化&#xff0c;如何比别人做的更好&#xff0c;更加符合大众的习惯 抄别人会陷入一个怪圈&#xff0c;就是已经习惯了&#xff0c;也懒了&#xff0c;也不带思考了。 许多产品会迫于…

qiankun微前端任意位置子应用

qiankun微前端任意位置子应用 主项目1、安装qiankun2、引入注册3、路由创建4、路由守卫 二、子项目1、安装sh-winter/vite-plugin-qiankun2、main.js配置3、vite.config.js配置 三、问题解决 主项目 1、安装qiankun npm i qiankun -S2、引入注册 创建存放子应用页面 //whpv…

第十五章,SSL VPN

前言 IPSec 和 SSL 对比 IPSec远程接入场景---client提前安装软件&#xff0c;存在一定的兼容性问题 IPSec协议只能够对感兴趣的流量进行加密保护&#xff0c;意味着接入用户需要不停的调整策略&#xff0c;来适应IPSec隧道 IPSec协议对用户访问权限颗粒度划分的不够详细&…

spring5.x讲解介绍

Spring 5.x 是 Spring Framework 的重要版本升级&#xff0c;全面拥抱现代 Java 技术栈&#xff0c;其核心改进涵盖响应式编程、Java 8支持、性能优化及开发模式创新。以下从特性、架构和应用场景三个维度详细解析&#xff1a; 一、核心特性与架构改进 Java 8 全面支持 Spring …

荣耀A8互动娱乐组件部署实录(第3部分:控制端结构与房间通信协议)

作者&#xff1a;曾在 WebSocket 超时里泡了七天七夜的苦命人 一、控制端总体架构概述 荣耀A8控制端主要承担的是“运营支点”功能&#xff0c;也就是开发与运营之间的桥梁。它既不直接参与玩家行为&#xff0c;又控制着玩家的行为逻辑和游戏规则触发机制。控制端的主要职责包…

levelDB的数据查看(非常详细)

起因:.net大作业天气预报程序(WPF)答辩时&#xff0c;老师问怎么维持数据持久性的&#xff0c;启动时加载的数据存在哪里&#xff0c;我明白老师想考的应该是json文件的解析&#xff08;正反&#xff09;&#xff0c;半天没答上来存那个文件了&#xff08;老师默认这个文件是自…

在Fiddler中添加自定义HTTP方法列并高亮显示

在Fiddler中添加自定义HTTP方法列并高亮显示 Fiddler 是一款强大的 Web 调试代理工具&#xff0c;允许开发者检查和操作 HTTP 流量。一个常见需求是自定义 Web Sessions 列表&#xff0c;添加显示 HTTP 方法&#xff08;GET、POST 等&#xff09;的列&#xff0c;并通过颜色区…

基于公共卫生大数据收集与智能整合AI平台构建测试:从概念到实践

随着医疗健康数据的爆发式增长,如何有效整合、分析和利用这些数据已成为公共卫生领域的重要挑战。传统方法往往难以应对数据的复杂性、多样性和海量性,而人工智能技术的迅猛发展为解决这些挑战提供了新的可能性。基于数据整合与公共卫生大数据的AI平台旨在构建一个全面的生态…

clahe算法基本实现

一、背景介绍 图像算法处理中&#xff0c;经常需要遇到图像对比度调整的情况&#xff0c;CLAHE(Contrast Limited Adaptive Histogram Equalization)则是一种基于直方图&#xff0c;使用非常普遍的图像对比度调整算法。 关于这个算法的介绍有很多&#xff0c;基本原理这些&…

python打卡day20

特征降维------特征组合&#xff08;以SVD为例&#xff09; 知识点回顾&#xff1a; 奇异值的应用&#xff1a; 特征降维&#xff1a;对高维数据减小计算量、可视化数据重构&#xff1a;比如重构信号、重构图像&#xff08;可以实现有损压缩&#xff0c;k 越小压缩率越高&#…

数字化转型-4A架构之数据架构

系列文章 数字化转型-4A架构&#xff08;业务架构、应用架构、数据架构、技术架构&#xff09; 数字化转型-4A架构之业务架构 数字化转型-4A架构之应用架构 数据架构 Data Architecture&#xff08;DA&#xff09; 1. 定义 数据架构&#xff0c;是组织管理数据资产的科学之…

React 第三十七节 Router 中 useOutlet Hook的使用介绍以及注意事项

React Router 中的 useOutlet 是 v6 版本新增的 Hook&#xff0c;用于在父路由组件中访问当前嵌套的子路由元素。它提供了比 <Outlet> 组件更灵活的控制方式&#xff0c;适合需要根据子路由状态进行动态处理的场景。 一、useOutlet的基本用法 import { useOutlet } fro…

AGV通信第3期|AGV集群智能应急响应系统:从故障感知到快速恢复

随着智慧工厂物流系统复杂度的提升&#xff0c;AGV运行过程中的异常处理能力已成为保障生产连续性的关键指标。面对突发障碍、设备故障等意外状况&#xff0c;传统依赖人工干预的响应模式已无法满足现代智能制造对时效性的严苛要求。 一、AGV异常应急体系面临的挑战 响应时效瓶…

军事目标无人机视角坦克检测数据集VOC+YOLO格式4003张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;4003 标注数量(xml文件个数)&#xff1a;4003 标注数量(txt文件个数)&#xff1a;4003 …

软件安全(二)优化shellcode

我们在上一节课中所写的shellcode&#xff0c;其中使用到的相关的API是通过写入其内存地址来实现调用。这种方法具有局限性&#xff0c;如切换其他的操作系统API的内存地址就会发生变化&#xff0c;从而无法正常调用。 所谓的shellcode不过是在目标程序中加一个区段使得程序可…

RabbitMQ-运维

文章目录 前言运维-集群介绍多机多节点单机多节点 多机多节点下载配置hosts⽂件配置Erlang Cookie启动节点构建集群查看集群状态 单机多节点安装启动两个节点再启动两个节点验证RabbitMQ启动成功搭建集群把rabbit2, rabbit3添加到集群 宕机演示仲裁队列介绍raft算法协议 raft基…

深度学习基础--目标检测常见算法简介(R-CNN、Fast R-CNN、Faster R-CNN、Mask R-CNN、SSD、YOLO)

博主简介&#xff1a;努力学习的22级本科生一枚 &#x1f31f;​&#xff1b;探索AI算法&#xff0c;C&#xff0c;go语言的世界&#xff1b;在迷茫中寻找光芒​&#x1f338;​ 博客主页&#xff1a;羊小猪~~-CSDN博客 内容简介&#xff1a;常见目标检测算法简介​&#x1f…

LINUX CFS算法解析

文章目录 1. Linux调度器的发展历程2. CFS设计思想3. CFS核心数据结构3.1 调度实体(sched_entity)3.2 CFS运行队列(cfs_rq)3.3 任务结构体中的调度相关字段 4. 优先级与权重4.1 优先级范围4.2 权重映射表 (prio_to_weight[])优先级计算4.3.1. static_prio (静态优先级)4.3.2. n…