数据结构二:线性表之顺序表(不定长顺序表)的设计与实现

news2024/11/16 8:26:03

       本篇博客详细总结数据结构中的第一种结构:线性表之不定长顺序表,主要从以下几个方面梳理:线性表的定义、顺序表的概念、顺序表的基本操作:增删改查的基本思想及代码实现、基本操作的算法效率分析(时间复杂度和空间复杂度)、顺序表的优缺点适用场合,以及常见的面试题。

一、线性表的定义

       线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。因此,线性表按照存储结构又分为:顺序表和链表。

二、顺序表之不定长顺序表(动态顺序表)

2.1 顺序表的基本概念及结构

       顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,只要确定好了存储线性表的起始位置,线性表中任一数据元素都可以随机存取,所以线性表的顺序存储结构是一种随机存取的储存结构,因为高级语言中的数组类型也是有随机存取的特性,所以通常我们都使用数组来描述数据结构中的顺序储存结构,一般情况下采用数组存储。在数组上完成数据的增删查改。因此可以把顺序表就理解成数组。

2.2 顺序表的分类

顺序表一般可以分为:

  1. 静态顺序表(也称为定长顺序表):使用定长数组存储元素。
  2. 动态顺序表(也称为不定长顺序表):使用动态开辟的数组存储。

      静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。

 

2.2 接口实现(增删改查函数实现)及算法效率分析

      引入的头文件

#include "SeqList.h"
#include<assert.h>
#include<stdlib.h>

顺序表的主要操作为增删改查,这里详细展示各个操作的思想和代码实现以及算法效率分析,主要接口函数如下所示:

//接口函数的设计
//1.初始化顺序表
void InitSeqList(SeqList* plist);
//2.销毁顺序表
void DestorySeqList(SeqList* plist);
//3.清空顺序表
void ClearSeqList(SeqList* plist);
//4.打印顺序表
void ShowSeqList(SeqList* plist);
//5.顺序表判满
Status IsFullSeqList(SeqList* plist);
//6.顺序表扩容
Status GrowSeqList(SeqList* plist);           // bool  GrowSeqList(SeqList* plist);
//7.顺序表判空
Status IsEmptySeqList(SeqList* plist);
//8.头插操作
Status InsertHead(SeqList* plist, ElemType val);
//9.尾插操作      
Status InsertTail(SeqList* plist, ElemType val);
//10.指定位置插入      posindex 插入的下标
Status InsertPosVal(SeqList* plist, int posindex, ElemType val);
//11.头删
Status DeleteHead(SeqList* plist);
//12.尾删
Status DeleteTail(SeqList* plist);
//13.指定位置删除
Status DeletePos(SeqList* plist, int posindex);
//14.删除指定元素(全部删除)
Status DeleteVal(SeqList* plist, ElemType val);
//15.查找某个元素,返回下标
int SearchVal(SeqList* plist, ElemType val);
//16.冒泡排序
void BubbleSort(SeqList* plist);
//17.二分查找(前提:数组完全有序)   非递归和递归实现
int BinarySearch0(SeqList* plist, ElemType val);
int BinarySearch1(SeqList* plist, ElemType val);
//18.将两个有序顺序表合并为一个有序顺序表
void MergeList(const SeqList* pla, const SeqList* plb, SeqList* plc);

 2.2.1 宏定义和类型重命名

       为方便代码复用和实际开发效率,通常会引入一些宏定义和类型重命名,如下代码所示:

#define TRUE 1
#define FALSE 0
#define OVERFLOW -1
#define LIST_INIT_CAPACITY 10    //动态顺序表的初始容量大小
#define LIST_GROW 2              //动态顺序表的扩容倍数

typedef int Status;             //状态 TRUE  FALSE 给c语言加入bool类型的方式
typedef int ElemType;           //数组元素类型

2.2.1 顺序表的设计

//顺序表类型的设计
typedef struct SeqList
{
	ElemType* data;    //动态申请的顺序表的起始地址
	size_t size;        //顺序表有效数据个数
	size_t capacity;    //顺序表的总容量
}SeqList;

2.2.2 顺序表的初始化

//1.初始化顺序表
void InitSeqList(SeqList* plist)
{
	assert(plist != NULL);
	plist->data = (ElemType*)malloc(sizeof(ElemType) * LIST_INIT_CAPACITY);
	assert(plist->data != NULL);
	plist->size = 0;
	plist->capacity = LIST_INIT_CAPACITY;
}

2.2.3 销毁顺序表

//2.销毁顺序表   释放堆内存
void DestorySeqList(SeqList* plist)
{
	assert(plist != NULL);
	free(plist->data);
	plist->data = NULL;          //防止出现野指针
}

2.2.4 清空顺序表

//3.清空顺序表    清除有效数据
void ClearSeqList(SeqList* plist)
{
	assert(plist != NULL);
	plist->size = 0;
}

2.2.5 打印顺序表

//4.打印顺序表
void ShowSeqList(SeqList* plist)
{
	assert(plist != NULL);
	int n = plist->size;
	for (int i = 0; i < plist->size; i++)
	{
		printf("%d ", plist->data[i]);
	}
	printf("\n");
}

2.2.6 顺序表判满

//5.顺序表判满
Status IsFullSeqList(SeqList* plist)
{
	assert(plist != NULL);
	return plist->size == plist->capacity;
}

2.2.7 顺序表扩容

//6.顺序表扩容
Status GrowSeqList(SeqList* plist)          // bool  GrowSeqList(SeqList* plist);
{
	assert(plist != NULL);
	if (plist == NULL)
		return FALSE;
	int newcapacity = plist->capacity * LIST_GROW;
	ElemType* tmp = (ElemType*)realloc(plist->data, newcapacity * sizeof(ElemType));
	if (tmp == NULL)
		return OVERFLOW;
	plist->data = tmp;
	plist->capacity = newcapacity;
	return TRUE;
}

2.2.8 顺序表判空

//7.顺序表判空
Status IsEmptySeqList(SeqList* plist)
{
	assert(plist != NULL);
	return plist->size == 0;
}

2.2.9 头插

//8.头插操作            时间复杂度:O(n)
Status InsertHead(SeqList* plist, ElemType val)
{
	assert(plist != NULL);
	if (IsFullSeqList(plist) && GrowSeqList(plist) != TRUE)
	{
		return OVERFLOW;
	}
	//进行移动数据
	for (int i = plist->size-1; i >0; i--)
	{
		plist->data[i + 1] = plist->data[i];
	}
	plist->data[0] = val;
	plist->size++;
	return TRUE;
}

2.2.10 尾插

//9.尾插操作        时间复杂度:O(1)
Status InsertTail(SeqList* plist, ElemType val)
{
	assert(plist != NULL);
	if (IsFullSeqList(plist) && GrowSeqList(plist) != TRUE)
	{
		return OVERFLOW;
	}
	plist->data[plist->size] = val;
	plist->size++;
	return TRUE;
}

2.2.11 指定位置插入

 

//10.指定位置插入      posindex 插入的下标            时间复杂度:O(n)
Status InsertPosVal(SeqList* plist, int posindex, ElemType val)
{
	assert(plist != NULL);
	if (posindex<0 || posindex>plist->size)
	{
		return FALSE;
	}
	if (IsFullSeqList(plist) && GrowSeqList(plist) != TRUE)
	{
		return OVERFLOW;
	}
	//进行移动数据
	for (int i = plist->size - 1; i >= posindex; i--)
	{
		plist->data[i + 1] = plist->data[i];
	}
	plist->data[posindex] = val;
	plist->size++;
	return TRUE;
}

2.2.12  头删

//11.头删                   时间复杂度:O(n)
Status DeleteHead(SeqList* plist)
{
	assert(plist != NULL);
	if (IsEmptySeqList(plist))
	{
		return FALSE;
	}
	//进行移动数据
	for (int i = 1; i <= plist->size; i++)
	{
		plist->data[i - 1] = plist->data[i];
	}
	plist->size--;
	return TRUE;
}

2.2.13 尾删

//12.尾删                  时间复杂度:O(1)
Status DeleteTail(SeqList* plist)
{
	assert(plist != NULL);
	if (IsEmptySeqList(plist))
	{
		return FALSE;
	}
	plist->size--;  //存在局限性
	return TRUE;
}

2.2.14 指定位置删除

//13.指定位置删除         时间复杂度:O(n)
Status DeletePos(SeqList* plist, int posindex)
{
	assert(plist != NULL);
	if (posindex<0 || posindex>plist->size)
	{
		return FALSE;
	}
	//进行移动数据,元素覆盖
	for (int i = posindex; i > plist->size; i++)
	{
		plist->data[i] = plist->data[i + 1];
	}
	plist->size--;
	return TRUE;
}

2.2.15 删除指定元素

//14.删除指定元素(全部删除)          时间复杂度:O(n^2)
Status DeleteVal(SeqList* plist, ElemType val)
{
	if (plist->size == 0)
	{
		return FALSE;
	}
	else
	{
		int flag = FALSE;
		for (int i = 0; i <plist->size; )
		{
			if (plist->data[i] == val)
			{
				flag = TRUE;
				for (int j = i + 1; j < plist->size; j++)
				{
					plist->data[j - 1] = plist->data[j];
				}
				plist->size--;
			}
			else
			{
				i++;
			}
		}
		return flag;
	}
}

//补充练习Leetcode:成对出现数据,只有一个数据单独出现一次,
返回这个单独出现一次的数据的下标
/*解题思路:异或运算可以巧妙地解决出现一次的问题,
异或运算是位运算,计算效率更高,它是将两个数的二进制数按位进行异或,不同为1,相同为0
0与任何数异或结果为这个数,两个相同的数异或的结果是0,
出现偶数次的数异或两次必会被抵消,并且异或满足交换律,因此它与数组中的数字的顺序无关*/

int SearchOnlyOne(SeqList* plist)
{
	int res = 0;             //0异或一个数得到结果为这个数
	for (int i = 0; i < plist->size; i++)
	{
		res = res ^ plist->data[i];
	}
	return res;
}

2.2.16 查找某个元素


//15.查找某个元素,返回下标               时间复杂度:O(n)
int SearchVal(SeqList* plist, ElemType val)
{
	assert(plist != NULL);
	int index = -1;
	for (int i = 0; i < plist->size; i++)
	{
		if (plist->data[i] == val)
		{
			index = i;
			break;
		}
	}
	return index;
}

2.2.17 冒泡排序

//16.冒泡排序              时间复杂度:O(n^2)
void BubbleSort(SeqList* plist)
{
	assert(plist != NULL);
	if (plist->size == 0)
		return;
	bool flag = false;
	for (int i = 0; i < plist->size; i++)
	{
		for (int j = 0; j < plist ->size-i - 1; j++)
		{
			if (plist->data[j] > plist->data[j + 1])
			{
				ElemType tmp = plist->data[j];
				plist->data[j] = plist->data[j + 1];
				plist->data[j + 1] = tmp;
				flag = true;
			}
			
		}
		if (flag == false)
		{
			break;
		}
	}
}

2.2.18 二分查找


//17.二分查找(前提:数组完全有序)   非递归:时间复杂度O(logn),空间复杂度:O(1)           和递归实现:时间复杂度是O(logn),空间复杂度:O(n)   
int BinarySearch0(SeqList* plist, ElemType val)
{
	assert(plist != NULL);
	if (plist->size == 0)
		return;
	int left = 0, right = plist->size - 1;
	int index = -1;
	while (left <= right)
	{
		int midindex = (left + right) / 2;
		//int midindex = (left + right) >> 1;
		//int midindex=(right-left)/2+left;
		//int midindex=(right-left)>>1+left;
		if (plist->data[midindex] = val)
		{
			index = midindex;
		}
		else if (plist->data[midindex] > val)
		{
			right = midindex - 1;
		}
		else if(plist->data[midindex] < val)
		{
			left = midindex + 1;
		}
		return index;
	}
}


int SearchSection(SeqList* plist, int left, int right, ElemType val)
{
	if (left > right)
	{
		return -1;
	}
	int midindex = (left + right) / 2;
	if (plist->data[midindex] == val)
	{
		return midindex;
	}
	if (plist->data[midindex] > val)
	{
		return SearchSection(plist, left, midindex - 1, val);
	}
	else if (plist->data[midindex] < val)
	{
		return SearchSection(plist, midindex+1, right, val);
	}
}


int BinarySearch1(SeqList* plist, ElemType val)
{
	assert(plist != NULL);
	return SearchSection(plist, 0, plist->size - 1, val);
}

2.2.19 合并有序顺序表 

//18.将两个有序顺序表合并为一个有序顺序表:思想:谁小谁下来,谁++           时间复杂度O(m+n),空间复杂度:O(n)  
void MergeList(const SeqList* pla, const SeqList* plb, SeqList* plc)
{
	assert(pla != NULL && plb != NULL && plc != NULL);
	int i=0, j=0, z=0;
	while (i < pla->size && j < plb->size)
	{
		if (pla->data[i] < plb->data[j])
		{
			//plc->data[z++] = pla->data[i++];
			InsertTail(plc, pla->data[i++]);//考虑 扩容问题
		}
		else 
		{
			//plc->data[z++] = plb->data[j++];
			InsertTail(plc, plb->data[j++]);//考虑 扩容问题
		}
	}
	if (i >= pla->size)
	{
		while (j < plb->size)
		{
			//plc->data[z++] = plb->data[j++];
			InsertTail(plc, plb->data[j++]);//考虑 扩容问题
		}
	}
	if (j > plb->size)
	{
		while(i < pla->size)
		{
			//plc->data[z++] = pla->data[i++];
			InsertTail(plc, pla->data[i++]);//考虑 扩容问题
		}
	}

}

2.3 顺序表优缺点总结

顺序表
1、可动态增长的数组
2、数据在数组中存储时必须是连续的
缺点:
1、中间或者头部的插入删除很慢,需要挪动数据。时间复杂度是0(N)

2、空间不够时,增容会有一定消耗和空间浪费。
优点:
1、随机访问。
2、缓存利用率比较高

2.4 顺序表的问题及思考

问题:

1. 中间/头部的插入删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢?链表解决!!!

三、 数组相关面试题

1. 原地移除数组中所有的元素val,要求时间复杂度为O(N),空间复杂度为O(1)。

2. 删除排序数组中的重复项。

3. 合并两个有序数组。

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

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

相关文章

曲线生成 | 图解三次样条曲线生成原理(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 什么是样条&#xff1f;2 三次样条曲线原理2.1 曲线插值2.2 边界条件2.3 系数反解 3 算法仿真3.1 ROS C仿真3.2 Python仿真3.3 Matlab仿真 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细…

二.Winform使用Webview2在Demo1中实现地址简单校验

Winform使用Webview2在Demo1中实现地址简单校验 往期目录回顾添加对于的简单url验证提示通过上节和本节涉及到的函数有 往期目录 往期相关文章目录 专栏目录 回顾 通过一.Winform使用Webview2(Edge浏览器核心) 创建demo(Demo1)实现回车导航到指定地址 我们已经知道了解决资源…

C语言第五弹---分支语句(上)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 分支语句 1、if语句1.1、if1.2、 else1.3、 分支中包含多条语句1.4、嵌套if1.5、 悬空else问题 2、关系操作符3、 条件操作符总结 C语言是结构化的程序设计语言&…

爬虫案例—抓取找歌词网站的按歌词找歌名数据

爬虫案例—抓取找歌词网站的按歌词找歌名数据 找个词网址&#xff1a;https://www.91ge.cn/lxyyplay/find/ 目标&#xff1a;抓取页面里的所有要查的歌词及歌名等信息&#xff0c;并存为txt文件 一共46页数据 网站截图如下&#xff1a; 抓取完整歌词数据&#xff0c;如下图…

【网络奇遇记】揭秘计算机网络性能指标:全面指南

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、数据结构 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 速率1.1 数据量1.2 速率 二. 带宽三. 吞吐量四. 时延4.1 发送时延4.2 传播时延…

华南理工大学数字信号处理实验实验二源码(薛y老师)

一、实验目的 ▪ 综合运用数字信号处理的理论知识进行信号分析并利用MATLAB作为编程工具进行计算机实现&#xff0c;从而加 深对所学知识的理解&#xff0c;建立概念。 ▪ 掌握数字信号处理的基本概念、基本理论和基本方法。 ▪ 学会用MATLAB对信号进行分析和处理。 ▪ 用F…

[小程序]基于token的权鉴测试

一、服务器配置 服务器基于flask&#xff0c;需要额外安装flask_jwt_extended包 from flask import Flask #导入Flask包 from flask import request from flask import jsonify #用来返回json消息 from flask_jwt_extended import create_access_token, jwt_requi…

实战项目(一)内容管理系统

一、实现技术 前端技术&#xff1a;html、javascript(jquery、ajax、json)、css 后端技术&#xff1a;java、mysql、servlet 开发工具&#xff1a;eclipse、vscode 二、项目描述 首页仿写某大学网页&#xff0c;上面有各种栏目及栏目内容&#xff0c;管理员能登录进去对首…

基于 OpenVINO, yolov5 推理

OpenVINO 是英特尔开发的一款功能强大的深度学习工具包&#xff0c;可实现跨多个硬件平台的优化神经网络推理。在本文中&#xff0c;我们讨论了 OpenVINO 的特性和优势&#xff0c;以及它如何与领先的计算机视觉平台 Viso Suite 集成&#xff0c;以构建和交付可扩展的应用程序。…

Linux配置主机名-使用主机名访问服务器

主要需要对Hosts文件进行操作&#xff0c; Hosts是一个没有扩展名的系统文件&#xff0c;可以用记事本等工具打开&#xff0c;其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”&#xff0c; 先将自己的主机名设置成有意义&#xff0c;别人好记的样子&a…

Win10升级Win11后卡顿了?

目录 关闭动画效果 任务栏居中改为居左 调整外观和性能 其他 当你看到最后&#xff0c;还知道哪些升级WIN11后必做的优化呢&#xff1f;欢迎在评论区分享出来&#xff01;❤️ win11上市目前也有一段时间了&#xff0c;想必很多大家都已经进行更新了。新的系统确实更加简洁…

线性表--链表--单链表(不带头单向不循环链表)

关于顺序表存在的问题&#xff1a; 1.中间/头部的插⼊删除&#xff0c;时间复杂度为O(N) 2.增容需要申请新空间&#xff0c;拷贝数据&#xff0c;释放旧空间。会有不小的消耗 3.增容⼀般是呈2倍的增长&#xff0c;势必会有一定的空间浪费 要如何解决这些问题&#xff1f;用线性…

HCIA vlan练习

目录 实验拓扑 实验要求 实验步骤 1、交换机创建vlan 2、交换机上的各个接口划分到对应vlan中 3、trunk干道 4、路由器单臂路由 5、路由器DHCP设置 实验测试 华为交换机更换端口连接模式报错处理 实验拓扑 实验要求 根据图划分vlan&#xff0c;并通过DHCP给主机下发…

Android学习之路(22) ARouter原理解析

1.ARouter认知 首先我们从命名来看:ARouter翻译过来就是一个路由器。 官方定义&#xff1a; 一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦 那么什么是路由呢&#xff1f; 简单理解就是&#xff1a;一个公共平台转发系统 工作方式&…

vue项目中使用XgPlay.js播放视频

官网&#xff1a;西瓜播放器 1、首先安装下载 XgPlay.js依赖 npm i xgplayer --savenpm i xgplayer-hls.js --save2、页面引用 import FlvPlayer from "xgplayer-flv.js"; import "xgplayer/dist/index.min.css"; 3、建立dom容器 // 提供一个容器 <…

【Linux驱动】休眠与唤醒 | POLL机制 | 异步通知 | 阻塞与非阻塞 | 软件定时器

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《Linux驱动》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3d3;休眠与唤醒&#x1f3f8;内核函数&#x1f3f8;驱动框架及编程 &#x1f3d3;…

VC++中使用OpenCV进行形状和轮廓检测

VC中使用OpenCV进行形状和轮廓检测 在VC中使用OpenCV进行形状和轮廓检测&#xff0c;轮廓是形状分析以及物体检测和识别的有用工具。如下面的图像中Shapes.png中有三角形、矩形、正方形、圆形等&#xff0c;我们如何去区分不同的形状&#xff0c;并且根据轮廓进行检测呢&#…

re:从0开始的HTML学习之路 2. HTML的标准结构说明

1. <DOCTYPE html> 文档声明&#xff0c;用于告诉浏览器&#xff0c;当前HTML文档采用的是什么版本。 必须写在当前HTML文档的首行&#xff08;可执行代码的首行&#xff09; HTML4的此标签与HTML5不同。 2. <html lang“en”> 根标签&#xff0c;整个HTML文档中…

基于SpringBoot的SSM整合案例

项目目录: 数据库表以及表结构 user表结构 user_info表结构 引入依赖 父模块依赖: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.12.RELEASE</version>…

LINUX文件fd(file descriptor)文件描述符

目录 1.文件接口 1.1open 1.2C语言为什么要对open进行封装 2.fd demo代码 第一个问题 第二个问题 打开文件流程 引言&#xff1a;在学习C语言的时候&#xff0c;我们见过很多的文件的接口&#xff0c;例如fopen&#xff0c;fwrite&#xff0c;fclose等等&#xff0c;但…