数据结构与算法:线性表-顺序表(顺序存储)

一、线性表的定义(逻辑结构)

线性表是由 n (n >= 0) 个相同数据类型的数据元素组成的有限序列,其中 n 为线性表的表长,当 n = 0 时,线性表为空表。如果用 L 命名线性表,那么一般表示为:L = (a1, a2, …, an),其中

  • a1 是唯一的第一个数据元素,又称为表头元素。
  • an 是唯一的最后一个数据元素,又称为表尾元素。
  • 除了第一个数据元素外,每个数据元素有且仅有一个直接前驱。
  • 除了最后一个数据元素外,每个数据元素有且仅有一个直接后驱。
    以上就是线性表的逻辑特性,所以,线性表的逻辑结构如下
    在这里插入图片描述

二、线性表的基本操作

一个数据结构的基本操作是指最核心、最基本的操作,其他较为复杂的操作可以通过调用其基本操作来实现,线性表主要的基本操作如下

init_list(*list); // 初始化线性表,构造一个空表
destroy_list(*list); // 销毁线性表
print_list(*list); // 打印线性表的所有数据元素
list_append(*list, elem); // 从线性表的末尾插入一个元素
list_insert(*const list, index, elem); // 插入一个元素到线性表中, 参数 index 是下标,从 0 开始
list_delete(*list, index, *elem); // 从线性表中删除一个元素,参数 index 是下标,从 0 开始
list_get_elem(*list, index); // 获取线性表中 index 下标的值
list_locate_elem(*list, elem); // 在线性表中查找第一个元素值为 elem 的下标,找到返回相应的下标,否则返回 -1

三、线性表的顺序表示(物理结构)

顺序存储的方式实现的线性表称为顺序表,它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个数据元素在物理位置上也相邻。
在这里插入图片描述

在 C 语言中,使用数组来描述线性表的顺序存储结构,数组可以静态分配,也可以动态分配。

  • 使用静态分配时,由于数组的容量和空间已经固定死,当数据空间占满,再加入新的数据就会产生溢出,导致程序崩溃。用静态分配来描述线性表的数据结构可以如下
#define MAX_CAP 1024	// 顺序表容量
typedef struct {elem_type data[MAX_CAP];	// 顺序表的元素,elem_type 是数据元素类型int length;	// 顺序表的当前长度
}seq_list_t;
  • 使用动态分配时,存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,当数据空间占满,就开辟一块更大的存储空间,用以替换原来的存储空间。用动态分配来描述线性表的数据结构可以如下
#define INIT_CAP 8	// 顺序表容量
typedef struct {elem_type* data;		// 指向数组的首元素int capacity;	// 顺序表的容量int length;		// 顺序表的当前长度
}seq_list_t;

四、顺序表基本操作的实现

seq_list.h

#pragma once
#include <stdbool.h>
#include <string.h>#define INIT_CAP 8		// 默认最大容量extern const char* err_strings[];
/*
const char* err_strings[] = {"success","invalid index","pointer can't NULL"
};
*/
#define ERR_SUCCESS 0
#define ERR_INVALID_INDEX 1
#define ERR_POINTER_NULL 2typedef struct {int* data;		// 指向数组的首元素int capacity;	// 顺序表的容量int length;		// 顺序表的当前长度
}seq_list_t;// 初始化顺序表
void init_list(seq_list_t* const list);	
// 销毁顺序表
void destroy_list(seq_list_t* const list);
// 打印顺序表的所有数据元素
void print_list(seq_list_t* const list);// 插入一个元素到顺序表中, 参数 index 是下标,从 0 开始
int list_insert(seq_list_t* const list, const int index, const int elem);
// 从顺序表的末尾插入一个元素
void list_append(seq_list_t* const list, const int elem);
// 从顺序表中删除一个元素,参数 index 是下标,从 0 开始
int list_delete(seq_list_t* const list, const int index, int* elem);
// 顺序表是否为空,即顺序表的表长是否为 0,为 0 返回 true,否则返回 false
bool list_empty(seq_list_t* const list);
// 获取顺序表的表长
int list_length(seq_list_t* const list);
// 获取顺序表中 index 下标的值
int list_get_elem(seq_list_t* const list, const int index, int *elem);
// 在顺序表中查找第一个元素值为 elem 的下标,找到返回相应的下标,否则返回 -1
int list_locate_elem(seq_list_t* const list, const int elem);// 扩容
void increase_cap(seq_list_t* const list);

seq_list.c

#include "seq_list.h"
#include <stdio.h>	// printf 函数
#include <stdlib.h>	// malloc free 函数const char* err_strings[] = {"success","invalid index","pointer can't NULL"
};void init_list(seq_list_t* const list) {list->capacity = INIT_CAP;list->data = (int*)malloc(sizeof(int) * list->capacity);	// 用 malloc 函数申请一片连续的存储空间list->length = 0;
}
void destroy_list(seq_list_t* const list) {free(list->data);	// 用 free 函数释放一片连续的存储空间
}void print_list(seq_list_t* const list) {for (int i = 0; i < list->length; i++)printf("%d\n", list->data[i]);printf("print_list finished\n");
}int list_insert(seq_list_t* const list, const int index, const int elem) {if (index < 0 || index > list->length)return ERR_INVALID_INDEX;if (list->length >= list->capacity)	// 容量已满,扩容increase_cap(list);// 把第 index 索引(包括第 index 索引) 后的每个个元素往后移for (int i = list->length; i > index; i--)list->data[i] = list->data[i-1];list->data[index] = elem;list->length++;return ERR_SUCCESS;
}
void list_append(seq_list_t* const list, const int elem) {if (list->length >= list->capacity) // 容量已满,扩容increase_cap(list);list->data[list->length] = elem;list->length++;
}int list_delete(seq_list_t* const list, const int index, int* elem) {if (index < 0 || index >= list->length) return ERR_INVALID_INDEX;if (elem)*elem = list->data[index];// 把第 index 索引后的每一个元素向前移for (int i = index; i < list->length; i++)list->data[i] = list->data[i + 1];list->length--;return ERR_SUCCESS;
}bool list_empty(seq_list_t* const list) {return list->length == 0 ? true:false;
}int list_length(seq_list_t* const list) {return list->length;
}int list_get_elem(seq_list_t* const list, const int index, int *elem) {if (!elem)return ERR_POINTER_NULL;if (index < 0 || index >= list->length)return ERR_INVALID_INDEX;*elem = list->data[index];return ERR_SUCCESS;
}int list_locate_elem(seq_list_t* const list, const int elem) {for (int i = 0; i < list->length; i++) {if (list->data[i] == elem)return i;}return -1;
}void increase_cap(seq_list_t* const list) {int* p = list->data;list->capacity = list->capacity * 2;list->data = (int*)malloc(sizeof(int) * list->capacity);// 将数据复制到新区域for (int i = 0; i < list->length; i++)list->data[i] = p[i];free(p);
}

main.c

#include <stdio.h>
#include "seq_list.h"int main(int argc, const char* argv[]) {seq_list_t list;init_list(&list);if (list_empty(&list))printf("list empty\n");elseprintf("list length = %d\n", list_length(&list));for (int i = 0; i < 3; i++)list_append(&list, i);print_list(&list);int err = -1;int elem = 5;int index = 0;err = list_insert(&list, index, elem);if (err != ERR_SUCCESS)printf("list_insert failed: %s, length = %d, index = %d\n", err_strings[err], list_length(&list), index);elseprint_list(&list);index = 5;err = list_delete(&list, index, &elem);if (err != ERR_SUCCESS)printf("list_delete failed: %s, length = %d, index = %d\n", err_strings[err], list_length(&list), index);elseprintf("list_delete success: index = %d, elem = %d\n", index, elem);index = 2;err = list_get_elem(&list, index, &elem);if (err != ERR_SUCCESS)printf("list_get_elem failed: %s, length = %d, index = %d\n", err_strings[err], list_length(&list), index);elseprintf("list_get_elem success: index = %d, elem = %d\n", index, elem);elem = 4;index = list_locate_elem(&list, elem);if (index != -1)printf("index = %d, elem = %d\n", index, elem);elseprintf("elem = %d not in the list\n", elem);destroy_list(&list);return 0;
}

五、算法分析

1. 插入操作

int list_insert(seq_list_t* const list, const int index, const int elem) {if (index < 0 || index > list->length)return ERR_INVALID_INDEX;if (list->length >= list->capacity)	// 容量已满,扩容increase_cap(list);// 把第 index 索引(包括第 index 索引) 后的每个个元素往后移for (int i = list->length; i > index; i--)list->data[i] = list->data[i-1];list->data[index] = elem;list->length++;return ERR_SUCCESS;
}
  • 最好情况:在表尾插入,数据元素后移语句不执行,时间复杂度为:O(1)
  • 最坏情况:在表头插入,数据元素后移语句执行 n 次,时间复杂度为:O(n)
  • 平均情况:在已有 n 个数据元素的顺序表中,假设插入每个位置的概率是相等的,那么插入到每个位置的概率为: 1/n。又插入到第 1 个位置数据元素,后移语句需要执行 n 次;插入到第 2 个位置数据元素,后移语句需要执行 n-1 次;…;插入到第 n 个位置数据元素,后移语句需要执行 0 次。那么,平均移动次数为 1 n ∑ i = 1 n ( n + n − 1 + . . . + 0 ) = 1 n n ∗ n 2 = n 2 \frac{1}{n}\sum_{i=1}^{n}(n + n-1 + ... + 0)=\frac{1}{n}\frac{n*n}{2}=\frac{n}{2} n1i=1n(n+n1+...+0)=n12nn=2n,所以,插入操作的时间复杂度为:O(n)

2. 删除操作

int list_delete(seq_list_t* const list, const int index, int* elem) {if (index < 0 || index >= list->length) return ERR_INVALID_INDEX;if (elem)*elem = list->data[index];// 把第 index 索引后的每一个元素向前移for (int i = index; i < list->length; i++)list->data[i] = list->data[i + 1];list->length--;return ERR_SUCCESS;
}
  • 最好情况:删除表尾数据元素,数据元素前移语句不执行,时间复杂度为:O(1)
  • 最坏情况:删除表头数据元素,数据元素前移语句执行 n-1 次,时间复杂度为:O(n)
  • 平均情况:在已有 n 个数据元素的顺序表中,假设删除每个位置数据元素的概率是相等的,那么删除每个位置的元素的概率为: 1/n。又删除第 1 个位置数据元素,前移语句需要执行 n-1 次;删除第 2 个位置数据元素,前移语句需要执行 n-2 次;…;删除第 n 个位置数据元素,前移语句需要执行 0 次。那么,平均移动次数为 1 n ∑ i = 1 n ( n − 1 + . . . + 0 ) = 1 n n ( n − 1 ) 2 = n − 1 2 \frac{1}{n}\sum_{i=1}^{n}(n-1 + ... + 0)=\frac{1}{n}\frac{n(n-1)}{2}=\frac{n-1}{2} n1i=1n(n1+...+0)=n12n(n1)=2n1,所以,删除操作的时间复杂度为:O(n)

3. 按值查找

int list_locate_elem(seq_list_t* const list, const int elem) {for (int i = 0; i < list->length; i++) {if (list->data[i] == elem)return i;}return -1;
}
  • 最好情况:查找的元素在表头时,只需要比较 1 次,时间复杂度为:O(1)
  • 最坏情况:查找的元素在表尾(或不存在)时,需要比较 n 次,时间复杂度为:O(n)
  • 平均情况:在已有 n 个数据元素的顺序表中,假设查找的数据元素出现在顺序表中的位置的概率是相等的,那么查找元素所在的位置的概率为:1/n。又查找的元素出现在第 1 个位置,需要比较的次数为 1 次;查找的元素出现在第 2 个位置,需要比较的次数为 2 次;…;查找的元素出现在第 n 个位置,需要比较的次数为 n 次。那么平均比较次数为 1 n ∑ i = 1 n ( 1 + 2 + . . . + n ) = 1 n n ( n + 1 ) 2 = n + 1 2 \frac{1}{n}\sum_{i=1}^{n}(1 + 2 + ... + n)=\frac{1}{n}\frac{n(n+1)}{2}=\frac{n+1}{2} n1i=1n(1+2+...+n)=n12n(n+1)=2n+1,所以,按值查找的时间复杂度为:O(n)

4. 随机访问

int list_get_elem(seq_list_t* const list, const int index, int *elem) {if (!elem)return ERR_POINTER_NULL;if (index < 0 || index >= list->length)return ERR_INVALID_INDEX;*elem = list->data[index];return ERR_SUCCESS;
}

通过顺序表的首地址和下标,可以在 O(1) 时间内找到指定的元素。

六、总结

用顺序存储实现的线性表,即顺序表

  • 插入操作的时间复杂度为:O(n)
  • 删除操作的时间复杂度为:O(n)
  • 按值查找的时间复杂度为:O(n)
  • 随机访问的时间复杂度为:O(1)

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

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

相关文章

从源码到实践:Java集合框架面试核心知识点全解析

在Java开发中&#xff0c;集合框架&#xff08;Java Collections Framework&#xff09;是最基础也最常用的工具集。无论是处理业务逻辑时的数据暂存&#xff0c;还是高性能场景下的算法优化&#xff0c;集合的使用都贯穿始终。因此&#xff0c;Java集合相关的面试题几乎是所有…

【深度学习新浪潮】空间计算的医疗应用技术分析(简要版)

空间计算是一种通过融合计算机视觉、传感器技术与三维渲染,将虚拟内容精准锚定到物理空间,实现数字世界与现实世界无缝交互的技术体系。其核心在于让计算机理解真实环境的结构、位置和动态,从而支持自然交互(如手势、语音、眼动)和沉浸式体验。例如,苹果Vision Pro通过实…

win电脑没有xcode怎么上传ipa

在上架IOS项目的时候&#xff0c;遇到一个问题&#xff0c;如下图&#xff0c;在app store connect上架的时候&#xff0c;需要选择一个构建版本&#xff0c;然后它在下方提示&#xff0c;点击查看上传工具后&#xff0c;会发现需要下载xcode或mac命令行等工具来上传编译后的文…

相机标定与3D重建技术通俗讲解

一、什么是相机标定&#xff1f;能解决什么问题&#xff1f; 相机标定是计算机视觉中的基础技术&#xff0c;简单来说&#xff0c;就是确定相机从3D世界拍摄到2D图像时的"转换规则"。具体解决两个核心问题&#xff1a; 相机内部属性&#xff1a;如焦距&#xff08;…

DeepSeek-Reasoner推理模型示例

《DEEPSEEK原生应用与智能体开发实践 王晓华 书籍 图书》【摘要 书评 试读】- 京东图书 在之前讲解的示例中&#xff08;指这个示例&#xff1a;通过Prompt提示构建思维链-CSDN博客&#xff09;&#xff0c;无论是进行日常对话还是调用特定工具&#xff0c;我们所依赖的底层技…

常说的电源芯片到底指什么?

电源芯片是电子系统中用于管理、转换和分配电能的集成电路&#xff0c;根据功能和应用场景的不同&#xff0c;主要分为以下几类&#xff1a; 一、线性稳压器&#xff08;LDO, Low Dropout Regulator&#xff09; LDO内部的基本电路情况如下&#xff1a; LDO内部主要分为四大部…

【大模型学习】项目练习:套壳DeepSeek

这里是阿川的博客&#xff0c;祝您变得更强 ✨ 个人主页&#xff1a;在线OJ的阿川 &#x1f496;文章专栏&#xff1a;AI入门到进阶 &#x1f30f;代码仓库&#xff1a; 写在开头 现在您看到的是我的结论或想法&#xff0c;但在这背后凝结了大量的思考、经验和讨论 &#x1f4…

笔记03:布线-过孔的调用与添加

布线-过孔的调用与添加 &#xff08;1&#xff09;在进行PCB设计时&#xff0c;都必须使用到过孔&#xff0c;对走线进行换层处理。在走线进行打过孔之前&#xff0c;必须先要添加过孔&#xff0c;这样在PCB布线时才可以使用过孔。 &#xff08;2&#xff09;需要使用pad des…

在vscode中,Python程序的内置对象、关键字、自定义函数名/类名、字符串进行着色,说明分别是什么颜色?

在 VS Code 中&#xff0c;Python 代码的着色完全取决于你当前使用的主题。不同主题&#xff08;如 Dark, Monokai, Solarized Dark, Light, Quiet Light 等&#xff09;对不同类型的代码元素会使用不同的颜色。 一、Default Dark&#xff08;默认的深色主题&#xff09; impo…

Visual Studio 中使用 AddressSanitizer 指南

Visual Studio 中使用 AddressSanitizer 指南 基于 Microsoft Visual Studio 2022&#xff0c;支持 MSVC 和 Clang 编译器链&#xff0c;本文详细说明如何在 VS 中配置和使用 AddressSanitizer&#xff0c;用于检测内存误用&#xff0c;如消息释放后访问、超界读写等类型错误。…

Flink Sink函数深度解析:从原理到实践的全流程探索

在Flink的数据流处理体系中&#xff0c;Sink函数作为数据处理的最终出口&#xff0c;肩负着将处理后的数据写入外部存储引擎的关键使命。它如同数据旅程的终点站&#xff0c;决定着数据的最终归宿与应用价值。深入理解Sink函数的工作原理、核心概念及实现方式&#xff0c;对构建…

Codex+ 自建中转 API 部署教程(Windows 版)

&#x1f4cc; 一、前置环境准备 安装 Node.js 和 Codex CLI&#xff1a; npm install -g openai/codex准备 OpenAI API Key 确保你已有的中转接口兼容 OpenAI 格式&#xff0c; &#x1f4cc; 二、设置 PowerShell 环境变量 # 设置你的 API Key&#xff08;使用哪家的看你的…

Centos 7离线部署Nginx 高效省时

给脚本执行权限&#xff1a;chmod x install_nginx.sh以root用户运行&#xff1a;sudo ./install_nginx.sh 脚本如下&#xff1a; #!/bin/bash # Nginx一键化部署脚本&#xff08;修复版本开机自启&#xff09; # 需要以root权限运行set -e # 任何命令失败时立即退出脚本# 定…

P7915 [CSP-S 2021] 回文

题目描述 给定正整数 n n n 和整数序列 a 1 , a 2 , … , a 2 n a_1, a_2, \ldots, a_{2 n} a1​,a2​,…,a2n​&#xff0c;在这 2 n 2 n 2n 个数中&#xff0c; 1 , 2 , … , n 1, 2, \ldots, n 1,2,…,n 分别各出现恰好 2 2 2 次。现在进行 2 n 2 n 2n 次操作&#xf…

小智AI -- ESP32-S3 DIY面包板WIFI-LCD彩屏

DIY 所需硬件 开发板&#xff1a;ESP32-S3-DevKitC-1&#xff08;选择 WROOM N16R8 模组&#xff09; Goouuu ESP32-S3-N16R8开发板数字麦克风&#xff1a;INMP441 INMP441全向麦克风模块功放&#xff1a;MAX98357A MAX98357 I2S 音频放大器模块腔体喇叭&#xff1a;8Ω 2~3W 或…

家用网络进行DNS优选

家用网络进行DNS优选的好处主要体现在以下几个方面&#xff1a; 提升网络访问速度&#xff1a; DNS优选通过选择响应时间更快的DNS服务器&#xff0c;减少域名解析的延迟&#xff0c;从而加快网页加载和应用访问速度。尤其在访问国内外网站时&#xff0c;选择合适的DNS服务器可…

刷题 | 牛客 - js中等题-下 (更ing)45/54知识点解答

JS45 数组去重 描述 为 Array 对象添加一个去除重复项的方法 示例1 输入&#xff1a; [false, true, undefined, null, NaN, 0, 1, {}, {}, a, a, NaN] 复制输出&#xff1a; [false, true, undefined, null, NaN, 0, 1, {}, {}, a] Array.prototype.uniq function () …

vue3使用krpano1.22

官方文档链接 https://krpano.com/docu/js/#top 例子 https://krpano.com/releases/1.22/viewer/examples/javascript-interface/js-api-examples.html https://krpano.com/viewsource.html?releases/1.22/viewer/examples/javascript-interface/js-api-examples.html 注…

2025年AI面试推荐榜单,数字化招聘转型优选

一、AI面试为何成为2025招聘标配&#xff1f; 2025年企业对AI面试的需求从“效率工具”升级为“战略级招聘伙伴”。数据显示&#xff0c;超7成企业计划年内全面引入AI面试&#xff0c;其中技术岗、全球化招聘及蓝领用工场景需求增速显著。以下以综合技术实力、行业口碑及落地能…

人机协作新篇章:艾利特按摩机器人如何重塑健康生活

引言&#xff1a;按摩机器人的需求爆发 在快节奏的现代生活中&#xff0c;亚健康人群比例持续攀升。据《全球健康产业白皮书》显示&#xff1a; 85%的都市人群存在肌肉劳损问题专业理疗师供需缺口达1&#xff1a;3200精准按摩服务成本年均增长18% 这一背景下&#xff0c;按摩…