当前位置: 首页 > news >正文

数据结构实战:顺序表全解析 - 从零实现到性能分析

1.2 线性表

基础概念与结构特性

定义:由 n ( n >= 0 )个数据特性相同的元素构成的有限序列,称为线性表。(线性表是n个元素的有限序列,其中 n 个数据是相同数据类型的。)

分类:顺序表和链表

核心属性:

  • 长度: 线性表中实际包含的数据元素个数 n 称为线性表的长度。
  • 空表: 当长度 n=0 时,该线性表称为空表。

非空线性表的结构特征:
对于任意非空线性表,其元素序列需满足以下四个结构特性:

  1. 首元素唯一性: 必存在一个唯一的元素被称作“第一个”元素(也称首元素)。
  2. 尾元素唯一性: 必存在一个唯一的元素被称作“最后一个”元素(也称尾元素)。
  3. 前驱性: 除第一个元素外,其余所有元素都只有一个前驱(紧邻其前的元素)。
  4. 后继性: 除最后一个元素外,其余所有元素都只有一个后继(紧邻其后的元素)。

定义:用一组连续的内存单元依次存储线性表的各个元素,也就是说,逻辑上相邻的元素,实际的物理存储空间也是连续的。(这和数组类似,在数组里,'A 后面是 B' (这是逻辑上的前后关系),就意味着 A 的储存单元紧挨着 B 的储存单元(这是物理上的连续排列)。

一.顺序表 - 定义和初始化以及遍历

(1)顺序表定义

//宏定义与类型定义
#define MAXSIZE 100
typedef int ElemType; //定义 int 的别名为 ElemType (方便后续修改数据类型)//结构体定义(SeqList)
typedef struct{ElemType data[MAXSIZE]; //声明了一个长度为MAXSIZE(100)的整数类型的数组int length; //用于记录当前顺序表中实际存储了多少个元素
}SeqList;

(2)顺序表初始化

//初始化函数 (initList)
void initList(SeqList *L)
{L->length = 0;
}

通过传入结构体的指针L,将其中存储当前元素数量的 length 成员设置为 0。这使得当我们声明一个新的顺序表时,它就是空的。

//mian函数
int main(int argc,char const *argv[])
{//声明一个顺序表并初始化SeqList list;initList(&list);printf("初始化成功,目前长度占用%d\n",list.length);printf("目前占用内存%zu字节\n",sizeof(list.data)); //打印数组部分占用的字节数return 0;
}

(3)遍历函数

//遍历
void listElem(SeqList *L)
{for (int i = 0; i < L->length; i++){printf("%d",L->data[i]);}printf("\n");
}

二.顺序表 - 尾部插入元素

//添加元素的核心代码
int appendElem(SeqList *L,ElemType e) //传入顺序表的指针,想要添加的元素
{if(L->length>=MAXSIZE){printf("顺序表已满\n");return 0;}// 核心逻辑:添加元素L->data[L->length] = e; //L->data 数组,L->length 数组的下标L->length++;return 1;
}
  • L->data 表示:“找到指针 L 所指向的顺序表,并访问它的数据数组部分。”
  • L->length 表示:“找到指针 L 所指向的顺序表的长度变量。”

  在main函数里调用appendElem函数

//mian函数
int main(int argc,char const *argv[])
{//声明一个顺序表并初始化SeqList list;initList(&list);printf("初始化成功,目前长度占用%d\n",list.length);printf("目前占用内存%zu字节\n",sizeof(list.data)); //打印数组部分占用的字节数//调用函数appendElem(&list,88);appendElem(&list,45);appendElem(&list,43);appendElem(&list,17);listElem(&list);return 0;
}

结果如下:

三.顺序表 - 中间插入元素

//插入元素的核心代码
int insertElem(SeqList *L,int pos,ElemType e)
{if(L->length >= MAXSIZE){printf("表已经满了\n");return 0;}if(pos < 1 || pos > L->length){printf("插入位置错误\n");return 0;}//核心逻辑if (pos <= L->length){for (int i = L->length-1; i >= pos-1; i--){L->data[i+1] = L->data[i];}L->data[pos-1] = e;L->length++;}return 1;
}

在main函数里调用appendElem函数

int main(int argc, char const* argv[])
{//声明一个顺序表并初始化SeqList list;initList(&list);printf("初始化成功,目前长度占用%d\n", list.length);printf("目前占用内存%zu字节\n", sizeof(list.data)); //打印数组部分占用的字节数appendElem(&list, 88);appendElem(&list, 45);appendElem(&list, 43);appendElem(&list, 17);listElem(&list);//调用函数insertElem(&list,2,18);listElem(&list);return 0;
}

结果如下:

四.顺序表 - 删除元素

虽然说是删除,但本质上是覆盖的过程。

34覆盖56,43覆盖34,45覆盖43,12覆盖45,后面就没有元素覆盖12了,所以我们可以看最后顺序表中有两个12,那么我们怎么处理呢?

答案是:长度-1( L->length-- )就可以了

//删除元素的核心逻辑
int deleteElem(SeqList* L, int pos, ElemType* e) 
{*e = L->data[pos - 1];  //*e用来储存被删掉数据的位置(L->data[pos-1])if (pos < L->length){for (int i = pos; i < L->length; i++){L->data[i - 1] = L->data[i];}}L->length--;return 1;}

mian函数中调用

//mian函数
int main(int argc, char const* argv[])
{//声明变量delDataElemType delData;//声明一个顺序表并初始化SeqList list;initList(&list);printf("初始化成功,目前长度占用%d\n", list.length);printf("目前占用内存%zu字节\n", sizeof(list.data)); //打印数组部分占用的字节数appendElem(&list, 88);appendElem(&list, 45);appendElem(&list, 43);appendElem(&list, 17);listElem(&list);insertElem(&list,2,18);listElem(&list);//调用函数deleteElem(&list, 2, &delData);printf("被删除的数据为:%d\n", delData);listElem(&list);return 0;
}

结果如下:

五.顺序表 - 查找

//查找的核心逻辑
int findElem(SeqList* L,ElemType e)
{for (int i = 0; i < L->length; i++){if (L->data[i] == e){return i + 1;}}return 0;
}

mian中调用

//mian函数
int main(int argc, char const* argv[])
{ElemType delData;//声明一个顺序表并初始化SeqList list;initList(&list);printf("初始化成功,目前长度占用%d\n", list.length);printf("目前占用内存%zu字节\n", sizeof(list.data)); //打印数组部分占用的字节数appendElem(&list, 88);appendElem(&list, 45);appendElem(&list, 43);appendElem(&list, 17);listElem(&list);insertElem(&list,2,18);listElem(&list);deleteElem(&list, 2, &delData);printf("被删除的数据为:%d\n", delData);listElem(&list);//调用printf("%d\n", findElem(&list, 43));return 0;
}

结果如下:

六.顺序表 - 动态分配内存地址初始化

//动态分配内存地址初始化
typedef struct {ElemType* data;// 存储元素数据的指针,指向一块动态分配的内存int length; // 当前顺序表中元素的数量
} SeqList;SeqList* initList(SeqList* L) 
{//在堆内存中开辟SeqListd结构体空间SeqList* L = (SeqList*)malloc(sizeof(SeqList)); //在堆内存中开辟顺序表储存数据的连续空间(相当于上面的data(100))L->data = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);//初始化长度L->length = 0;//返回指向新创建的顺序表的指针return L;
}

将数据从栈内存中转移到堆内存中开辟空间

分析:

SeqList* L = (SeqList*)malloc(sizeof(SeqList)); 

malloc函数表示在内存当中开辟一片内存空间,默认返回值是void*(通用类型数据的指针),所以要做类型的强制转换(SeqList*),sizeof(SeqList)表示SeqList需要的空间,用SeqList指针接返回的值(SeqList* L)

完整代码实例:

#include <stdio.h>
#include <stdlib.h>#define MAXSIZE 100
typedef int ElemType;// 动态分配顺序表结构体
typedef struct {ElemType* data;  // 存储元素数据的指针,指向一块动态分配的内存int length;      // 当前顺序表中元素的数量
} SeqList;// 动态分配内存地址初始化
SeqList* initList()  // 移除参数,直接返回指针
{// 在堆内存中开辟SeqList结构体空间SeqList* L = (SeqList*)malloc(sizeof(SeqList)); if (L == NULL) {printf("结构体内存分配失败\n");return NULL;}// 在堆内存中开辟顺序表储存数据的连续空间L->data = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);if (L->data == NULL) {printf("数据内存分配失败\n");free(L);  // 释放之前分配的结构体return NULL;}// 初始化长度L->length = 0;// 返回指向新创建的顺序表的指针return L;
}// 销毁顺序表
void destroyList(SeqList* L) {if (L != NULL) {if (L->data != NULL) {free(L->data);  // 先释放数据数组}free(L);  // 再释放结构体}
}// 遍历函数
void listElem(SeqList* L) {if (L->length == 0) {printf("顺序表为空\n");return;}printf("顺序表元素:");for (int i = 0; i < L->length; i++) {printf("%d ", L->data[i]);}printf("\n");
}// 尾部插入元素
int appendElem(SeqList* L, ElemType e) {if (L->length >= MAXSIZE) {printf("顺序表已满\n");return 0;}L->data[L->length] = e;L->length++;return 1;
}// 主函数测试
int main() {// 创建顺序表SeqList* list = initList();if (list == NULL) {printf("顺序表初始化失败\n");return -1;}printf("顺序表初始化成功,当前长度:%d\n", list->length);// 添加元素appendElem(list, 10);appendElem(list, 20);appendElem(list, 30);appendElem(list, 40);// 遍历显示listElem(list);printf("当前长度:%d\n", list->length);// 销毁,避免内存泄漏destroyList(list);return 0;
}

顺序表的核心概念总结

  1. 基于数组 (Array-based):数据存储在一个固定大小的数组中,这保证了物理存储是连续的

  2. 随机访问 (Random Access):正因为物理地址连续,我们可以通过索引(L->data[i])高效地访问任何一个元素,时间复杂度为 O(1)。

  3. 插入和删除效率低appendElem(即在表尾添加)是高效的 O(1)操作(只要没满),但如果在表头或中间插入/删除元素,需要将后面所有元素整体移动,时间复杂度是 O(n)。

但缺点是:

 容量固定:一旦定义了 MAXSIZE,此表的最大容量就是固定的。当 length == MAXSIZE 时,表满。

  插入/删除效率低:平均需要移动半个表,时间复杂度O(n)。

  • 插入时的"多米诺效应":在位置i插入新元素,需要将i之后的所有元素都向后移动一位

  • 删除时的"补位操作":删除位置i的元素,需要将i之后的所有元素都向前移动一位

那么,有没有一种数据结构,既能保持线性表"逻辑上相邻"的特性,又能在物理存储上摆脱连续性的束缚,从而解决插入删除的效率问题呢?

答案是肯定的——这就是我们接下来要重点学习的链表(Linked List)

敬请期待下一篇:《数据结构实战(二)链表:打破存储连续性的高效线性表》

http://www.dtcms.com/a/473379.html

相关文章:

  • 【C++进阶】继承上 概念及其定义 赋值兼容转换 子类默认成员函数的详解分析
  • 华为matebook16s 2022禁用触摸板和触摸屏操作
  • GridRow 和 Column 有啥区别
  • 030159网站建设与维护中国科技成就素材
  • Echarts 5.6.0 Grid 坐标系中 Y 轴可视化的优化之路
  • Java 线程池如何知道一个线程的任务已经执行完成
  • JVM字节码与类的加载(一):类的加载过程详解
  • 强军网网站建设网站需要备案才能建设吗
  • 耄大厨——AI厨师智能体(3-工具调用)
  • (二)黑马React(导航/账单项目)
  • SA-LSTM
  • 【Java并发】深入理解synchronized
  • Docker 安装 Harbor 教程
  • Python+Flask+Prophet 汽车之家二手车系统 逻辑回归 二手车推荐系统 机器学习(逻辑回归+Echarts 源码+文档)✅
  • AI_NovelGenerator:自动化长篇小说AI生成工具
  • 济南网站制作开通免费个人简历模板官网
  • 全链路智能运维中的异常检测与根因定位技术
  • 解构 CodexField:创作者经济到模型金融化的代币逻辑与潜力
  • SpringBoot 实现自动数据变更追踪
  • C语言⽂件操作讲解(3)
  • 对网站做数据分析北京市建设工程信息
  • 1.6虚拟机
  • XCP服务
  • Excel - Excel 列出一列中所有不重复数据
  • 如何获取用户右击的Excel单元格位置
  • 昆明企业网站建设公司虹口建设机械网站制作
  • 宁波p2p网站建设黑龙江省建设安全网站
  • Spring Boot 3零基础教程,自动配置机制,笔记07
  • Spring通关笔记:从“Hello Bean”到循环依赖的奇幻漂流
  • 【Spring Security】Spring Security 密码编辑器