【数据结构】线性表 _顺序表 链表的增删查改 _[细节分析+代码实现]

news2024/10/6 12:23:59

快速导航

1.线性表

2.顺序表 

2.1 概念及结构

2.2 静态结构和动态结构的比较

2.3 接口实现(重点)

2.3.1 SeqList(初始化) &SeqListPrint(依次打印表中数据)

2.3.2 SeqListPushBack(尾插)

2.3.3 SeqListPushFront(头插)

2.3.4 SeqListPopBack(尾删) & SeqListPopFront(头删)

2.3.5 SeqListInsert(任意位置pos的插入)

2.3.6 SeqListErase(任意位置pos的删除)

2.3.7 SeqListFind(查找) &SeqListModify(修改)

2.3.8 SeqListDestory(销毁)

2.4 顺序表的问题及改善

3.链表

3.1 链表的概念及结构 

3.2 带头双向循环链表的实现

3.2.1 DDLInit(初始化) 

3.2.2 DLLPushBack(尾插) &DLLPushFront(头插)

3.2.3 DLLPopBack(尾删) &DLLPopFront(头删)

3.2.5 DLLErase(任意位置的删除) 

3.2.6 DLLFind(查找) & DLLModify(修改)

3.2.7 DLLDestory(销毁) 

1.线性表

线性表是N个具有相同特性的数据元素的有限序列; 常见的线性表有:顺序表、链表、栈和队列等。

线性表在逻辑上是线性结构,也就是说是在逻辑上可以看做是一条连续的直线;但在物理上结构并不一定是连续的,比如链表;

线性表在物理上存储时,通常以数组和链式结构的形式。

2.顺序表 

2.1 概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,采用数组存储。

注意:顺序表中的数据是从第一个位置开始,连续存储的;这和数组是不同的,数组中的数据可以在任意位置存放。

2.2 静态结构和动态结构的比较

1.静态结构(数组元素大小是固定的)

#define N 10
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType a[N];
	int size;
}SeqList;

 2.动态开辟数组(空间动态开辟)

typedef struct SeqList
{
	SLDataType* a;//指向动态数组的指针
	int size;     //元素个数
	int capacity; //容量
}SeqList;

对比两种结构,静态的结构有着一个致命的缺点:空间一次性开辟,若空间过大容易造成空间的浪费; 若空间过小不够用。

而动态开辟的数组就没有这种烦恼了,可以先把空间开小一点;若空间不够可以进行扩容。

考虑到静态结构的缺陷,我们在实现顺序表的时候使用的是动态开辟的数组。

2.3 接口实现(重点)

2.3.1 SeqList(初始化) &SeqListPrint(依次打印表中数据)

初始化的过程中首先要避免一个常见的问题:在传参的时候不能传结构体,而要传结构体指针

如果传的是结构体,那么形参是实参的一份临时拷贝,形参的改变不会影响实参。

void SeqListInit(SeqList* psl)
{
	assert(psl);

	SL->a = NULL;//数组先初始化为空,后面用动态内存开辟函数开辟空间
	SL->capacity = SL->size = 0;//容量和元素数量都初始化为0
}

打印的过程就很简单了,就把顺序表当做数组进行遍历就好了。

//这里使用指针不使用指针都可以进行打印,但是使用指针的话减少了拷贝的消耗
void SeqListPrint(SeqList* psl)
{
	assert(psl);

	int i = 0;
	for (i = 0; i < psl->size; i++)
	{
		printf("%d ", psl->a[i]);
	}
	printf("\n");
}

2.3.2 SeqListPushBack(尾插)

在很多人看来尾插很简单,直接插入数据,++size就行了。但是很容易忽略一个点,那就是插入数据之前顺序表是否已经满了,如果满了,就需要进行扩容。无论是尾插、头插、还是随机位置的插入,都需要检查是否需要扩容,所以在这里就直接封装成一个函数CheckCapacity:

两种扩容的情况:

1.顺序表为空时插入数据

2.顺序表已满

这两种情况可以归结为一种情况:psl->size == psl->capacity

思路:借助一个变量newcapacity进行扩容,如果psl->capacity == 0那么把capacity的值初始化为4,如果不是这种情况,那么

把容量扩为原来容量的二倍,也就是newcapacity = psl->capacity * 2;

void Checkcapacity(SeqList* psl)
{
	assert(psl);

	if (psl->capacity == psl->size)
	{
		int newcapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = (SLDataType*)realloc(psl->a, newcapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("Checkcapacity::realloc");
			exit(-1);
		}
		psl->a = tmp;
		psl->capacity = newcapacity;
	}
}

这里有一个函数需要注意:realloc, 如果传入的指针不为空,就根据传入的空间大小进行扩容;若果传入的指针为空,realloc就相当于malloc开辟空间。

下面给出SeqListPushBack函数的完整实现:

void SeqListPushBack(SeqList* psl, SLDataType x)
{
	assert(psl);

	Checkcapacity(psl);
	psl->a[psl->size] = x;
	psl->size++;
}

2.3.3 SeqListPushFront(头插)

头插的话需要移动数据,而且只能从后往前移动,否则会导致数据的覆盖;其次因为是插入操作,还是要检查扩容。

void SeqListPushFront(SeqList* psl, SLDataType x)
{
	assert(psl);

	Checkcapacity(psl);
	int end = psl->size - 1;
	while (end >= 0)
	{
		psl->a[end + 1] = psl->a[end];
		end--;
	}
	psl->a[0] = x;
	psl->size++;
}

2.3.4 SeqListPopBack(尾删) & SeqListPopFront(头删)

尾删和尾插在进行之前都需要检查顺序表中是否有元素,判断size是否为0;

尾删的话直接 --size的个数就可以了,但是头删还需要挪动元素;挪动数据只能从前往后,否则也是会造成数据的覆盖。

void SeqListPopBack(SeqList* psl)
{
	assert(psl);
	assert(psl->size);

	psl->size--;
}
 


void SeqListPopFront(SeqList* psl)
{
	assert(psl);
	assert(psl->size);

	int begin = 1;
	while (begin < psl->size)
	{
		psl->a[begin - 1] = psl->a[begin];//注意数组越界的问题
		begin++;
	}
	psl->size--;
}

2.3.5 SeqListInsert(任意位置pos的插入)

pos是顺序表中数组的下标,在任意位置进行插入难免的是挪动数据,插入的时需要从后往前挪动,以避免数据的覆盖;

还有一个需要去注意的点是pos是数组下标,是有范围的,一定要注意数组的越界问题。

void SeqListInsert(SeqList* psl, int pos, SLDataType x)
{
	assert(psl);
	assert(pos >= 0 && pos <= psl->size);//==的情况是尾插

	Checkcapacity(psl);
	int end = psl->size - 1;
	while (end >= pos)
	{
		psl->a[end + 1] = psl->a[end];
		end--;
	}
	psl->a[pos] = x;
	psl->size++;
}

既然是任意位置的插入,那么头插和尾插是可以复用的:

void SeqListPushBack(SeqList* psl, SLDataType x)
{
	SeqListInsert(psl, psl->size, x);
}

void SeqListPushFront(SeqList* psl, SLDataType x)
{
	SeqListInsert(psl, 0, x);
}

2.3.6 SeqListErase(任意位置pos的删除)

任意位置的删除同样是需要挪动数据的,和插入不同的是需要删除需要从前往后挪数据

void SeqListErase(SeqList* psl, int pos)
{
	assert(psl);
	assert(pos >= 0 && pos < psl->size);//同时检查了size的数目大于0

	int begin = pos + 1;
	while (begin < psl->size)
	{
		psl->a[begin - 1] = psl->a[begin];
		begin++;
	}
	psl->size--;
}

同样的,头删和尾删可以复用:

void SeqListPopBack(SeqList* psl)
{
	SeqListErase(psl, psl->size - 1);
}
 
void SeqListPopFront(SeqList* psl)
{
	SeqListErase(psl, 0);
}

2.3.7 SeqListFind(查找) &SeqListModify(修改)

//直接遍历即可,返回的是整型的下标
int SeqListFind(SeqList* psl, SLDataType x)
{
	assert(psl);
	assert(psl->size);

	for (int i = 0; i < psl->size; i++)
	{
		if (psl->a[i] == x)
			return i;
	}
	return -1;
}

修改可以配合查找使用,查找到的下标传给SeqListModify可以直接完成修改:

void SeqListModify(SeqList* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	ps->a[pos] = x;
}

2.3.8 SeqListDestory(销毁)

注意顺序表sl中动态开辟的数组有可能还没有开空间(psl->a=NULL),直接psl->a会报错。

void SeqListDestory(SeqList* psl)
{
	assert(psl);

	if (psl->a)
	{
		psl->a = NULL;
		psl->capacity = psl->size = 0;
	}
}

2.4 顺序表的问题及改善

问题:

  • 中间/头部的插入和删除,需要挪动数据,时间复杂度为O(N)
  • 增容需要重新申请空间,可能需要拷贝数据,释放旧的空间,会有不小的损耗
  • 增容一般呈现二倍增长,势必会有一定的空间浪费

当容量为100时,满了之后扩容为200,但是我们只需要再插入5个数据,后面就没有数据插入了,就浪费了95个数据空间。

思考:如何根据上述问题进行优化?

改善方案:1.按需申请空间  2.头部或者中间的插入删除,不需要挪动数据

这种改善的方案是可以实现的,也就是另一种线性存储——链表。

3.链表

3.1 链表的概念及结构 

概念:链表是一种物理存储结构上非连续,而逻辑上连续的存储结构。

 注意:

  • 链式结构在逻辑上是连续的,但是在物理上不一定是连续的
  • 现实中的结点一般都是从堆上申请出来的
  • 从堆上申请空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

3.2 带头双向循环链表的实现

链表有很多分类,带头或不带头;单向或双向;循环或非循环;三种组合有9种情况。

我们在这里主要学习的是带头双向循环链表,虽然它看起来复杂,但是实现起来并不复杂,而且相比于其他类型有很大的优势。

链表是按需申请空间的,申请的每个结点是一个结构体类型,所以要先定义出一个结构体类型。

typedef int DLLDataType;

typedef struct DLListNode
{
	DLLDataType val;
	struct DLListNode* next;
	struct DLListNode* prev;
}DLLNode;

 我们实现的链表存储的数据是int类型,所以对int进行重命名,如果存储的数据不是int类型,把int改成其他的类型即可。

3.2.1 DDLInit(初始化) 

链表的初始化和顺序表的初始化有所差别: 

链表的初始化需要创建一个结点,让它的prev和next都指向自己(循环),然后再返回这个结点的指针即可。

由于申请结点需要动态开辟,而且要频繁的使用,所以封装成了一个函数BuyNode()。

带头结点存储的值并不是固定的,这里给了一个-1。

DLLNode* BuyNode(DLLDataType x)
{
	DLLNode* newnode = (DLLNode*)malloc(sizeof(DLLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	newnode->val = x;
	newnode->next = NULL;
	newnode->prev = NULL;
	return newnode;
}

DLLNode* DLLInit()
{
	DLLNode* phead = BuyNode(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

3.2.2 DLLPushBack(尾插) &DLLPushFront(头插)

学习数据结构和我们学习语言有很大的不同,学习语言的时候很少借助画图,但是学习数据结构的话一定要画图,借助画图来转化为代码,不然很容易出现一些常见错误。

像尾插就需要先画图:

插入需要改变四个指针的指向:tail->next、newnode->prev、newnode->next、phead->prev。 

void DLLPushBack(DLLNode* phead, DLLDataType x)
{
	assert(phead);

	DLLNode* newnode = BuyNode(x);
	DLLNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;
}

头插也是一样的,下面先画图:

void DLLPushFront(DLLNode* phead, DLLDataType x)
{
	assert(phead);

	DLLNode* newnode = BuyNode(x);
	DLLNode* next = phead->next;
	newnode->next = next;
	next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

3.2.3 DLLPopBack(尾删) &DLLPopFront(头删)

链表的删除,需要先进行判空,判断链表除了头结点之外是否有其它结点;

bool DLLEmpty(DLLNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

DLLPopBack:

删除注意free结点,删除只需要修改两个指针。

void DLLPopBack(DLLNode* phead)
{
	assert(phead);
	assert(!DLLEmpty(phead));

	DLLNode* tail = phead->prev;
	DLLNode* tailPrev = tail->prev;

	free(tail);
	tail = NULL;

	tailPrev->next = phead;
	phead->prev = tailPrev;
}

DLLPopFront:

void DLLPopFront(DLLNode* phead)
{
	assert(phead);
	assert(!DLLEmpty(phead));

	DLLNode* head = phead->next;
	DLLNode* headnext = head->next;

	free(head);
	head = NULL;

	phead->next = headnext;
	headnext->prev = phead;
}

3.2.4 DLLPushInsert(任意位置之前的插入)

void DLLPushInsert(DLLNode* pos, DLLDataType x)
{
	assert(pos);

	DLLNode* posprev = pos->prev;
	DLLNode* newnode = BuyNode(x);
	posprev->next = newnode;
	newnode->prev = posprev;
	newnode->next = pos;
	pos->prev = newnode;
}

既然是任意位置的插入删除,那么头插和尾插自然而然的就可以复用:

void DLLPushBack(DLLNode* phead, DLLDataType x)
{
	assert(phead);

	DLLPushInsert(phead, x);
}

void DLLPushFront(DLLNode* phead, DLLDataType x)
{
	assert(phead);

	DLLPushInsert(phead->next, x);
}

3.2.5 DLLErase(任意位置的删除) 

void DLLPopErase(DLLNode* pos)
{
	assert(pos);

	DLLNode* posprev = pos->prev;
	DLLNode* posnext = pos->next;
	posprev->next = posnext;
	posnext->prev = posprev;

	free(pos);
	pos = NULL;
}

头删和尾删的复用:

void DLLPopBack(DLLNode* phead)
{
	assert(phead);
	assert(!DLLEmpty(phead));

	DLLPopErase(phead->prev);
}

void DLLPopFront(DLLNode* phead)
{
	assert(phead);
	assert(!DLLEmpty(phead));

	DLLPopErase(phead->next);
}

3.2.6 DLLFind(查找) & DLLModify(修改)

DLLNode* DLLFind(DLLNode* phead, DLLDataType x)//返回第一个值为val的节点
{
	assert(phead);
	assert(!DLLEmpty(phead));

	DLLNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->val == x)
			return cur;
	}
	return NULL;
}

void DLLModify(DLLNode* pos, DLLDataType x)
{
	assert(pos);

	pos->val = x;
}

3.2.7 DLLDestory(销毁) 

void DLLDestory(DLLNode* phead)
{
	assert(phead);
	assert(!DLLEmpty(phead));

	DLLNode* cur = phead->next;
	while (cur != phead)
	{
		DLLNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

顺序表和链表的实现到这里就结束了,阅读之后感觉有所收获的话可以给博主点个关注,后续会继续更新数据结构相关内容。

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

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

相关文章

C语言小游戏之扫雷(万字详解)

hello&#xff0c;大家好&#xff0c;今天我们继续为大家带来一个小游戏&#xff0c;扫雷。相信这个游戏又是很多人的童年&#xff0c;那么我们今天就来实现一下这个扫雷游戏。 目录 一、游戏简介 二、游戏的基本设计 1.游戏基本思路 2.游戏基本框架 3.如何设计布置雷与排查…

蓝牙耳机什么牌子好?口碑最好的蓝牙耳机品牌排行

在现代除了手机或智能手机&#xff0c;人们生活中离不开的另一件事就是“耳机”&#xff0c;尤其是对于心中有音乐的人。那么市面上的蓝牙耳机五花八门&#xff0c;尤其是陆续上新的新品&#xff0c;哪个牌子更好呢&#xff1f;以下是笔者整理的几款口碑好的蓝牙耳机&#xff0…

欧拉角与旋转矩阵

目录1. 欧拉角&#xff11;.&#xff11;欧拉角的表示&#xff11;.&#xff12;内旋和外旋1.3 欧拉角的缺点2 欧拉角到旋转矩阵的表示3 值得注意的点4. 非常感谢您的阅读&#xff01;1. 欧拉角 &#xff11;.&#xff11;欧拉角的表示 我们想描述刚体在现实世界的旋转时&…

chromedriver依赖安装失败 解决办法

1.问题描述 在使用npm下载chromedriver依赖时报错&#xff1a; chromedriver2.27.2 install: node install.js2.解决办法 第一步&#xff1a;根据报错信息中的地址&#xff0c;手动下载 chromedriver 依赖。 https://cdn.npmmirror.com/binaries/chromedriver/2.27/chromedr…

VOLTE呼叫流程介绍

VOLTE呼叫流程介绍&#xff1a; A和B均在IDLE模式&#xff0c;A用户&#xff08;主叫Caller&#xff09;呼叫B用户&#xff08;被叫Callee&#xff09;流程图&#xff1b; A、B均在MME附着&#xff0c;已在AS服务器注册&#xff1b; VOLTE呼叫业务流程 VOLTE呼叫业务流程 VOL…

[附源码]java毕业设计天悦酒店管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

java高级篇 Mybatis-Plus

目录 一、Mybatis-Plus概述 二、特性 三、快速搭建Mybatis-Plus框架 3.1 创建数据库以及表结构和数据 3.2 创建一个springboot工程并引入相关的依赖 3.3 修改配置文件 3.4 创建实体类 3.5 创建dao接口 3.6 为dao接口生成带来实现类 3.7 测试 四、使用mp完成crud操作 4.1 添加…

Linux 基础IO

目录 一、复习C文件IO相关操作 示例代码 fopen的打开模式 C标准库默认打开的三个输入输出流 理解当前路径 二、认识文件相关系统调用接口 示例代码 open函数简介 三、文件描述符 初步认识... 文件描述符的本质&#xff1a; 三个默认打开的文件 文件描述符的分配规则…

SSH客户端工具MobaXterm

前言 SSH客户端远程连接服务器的有xshell(xmanager套件下)&#xff0c;需要收费&#xff0c;也可以通过一些和谐的方式使用。 但是有时候&#xff0c;我们需要使用光明正大的软件SSH到远程服务器&#xff0c;MobaXterm家庭版可以正常的使用。 其他产品&#xff1a; SecureCRT&…

任意代码执行漏洞复现

漏洞简介 在 PostgreSQL 数据库的 jdbc 驱动程序中发现一个安全漏洞。当攻击者控制 jdbc url 或者属性时&#xff0c;使用 PostgreSQL 数据库的系统将受到攻击。 pgjdbc 根据通过 authenticationPluginClassName、sslhostnameverifier、socketFactory 、sslfactory、sslpasswo…

021_SSSS_Diffusion Models Already Have a Semantic Latent Space

Diffusion Models Already Have a Semantic Latent Space 1. Introduction 本文指出&#xff0c;现有的Diffusion模型可以在不同的领域有出色的表现&#xff0c;但是缺少可以控制其生成过程的语义隐空间&#xff08;Semantic Latent Sapce&#xff09;。本文提出了非对称的反…

C++模拟OpenGL库——图片处理及纹理系统(三):图片缩放操作:简单插值二次线性插值

目录 简单插值 二次线性插值 简单插值 如图&#xff0c;我们想把一张小图缩放成一张大图&#xff0c;自然的想法就是按照它们的长宽比例进行缩放&#xff08;zoomX&#xff09;。 但是问题也显而易见&#xff0c;在缩放的过程中&#xff0c;小图的像素并不能一一映射到大图的…

蜂巢能源冲刺科创板上市:拟募资150亿元,上半年收入37亿元

11月18日&#xff0c;蜂巢能源科技股份有限公司&#xff08;下称“蜂巢能源”&#xff09;在上海证券交易所递交招股书&#xff0c;准备在科创板上市。本次冲刺科创板上市&#xff0c;蜂巢能源计划募资150亿元&#xff0c;主要用于动力锂离子电池项目、研发中心建设项目等。 据…

Unity游戏Mod/插件制作教程02 - 开发环境准备

前言 虽然本教程的目标读者是有C#基础的玩家&#xff0c;但是作为流程&#xff0c;基础的开发软件部分我还是要记录一下。 安装VisualStudio VisualStudio是我们开发插件最重要的工具&#xff0c;也许你习惯其他开发.net的工具&#xff0c;但是免费的VisualStudio已经足够好用…

王道OS 1.1_1 操作系统的概念、功能和目标

王道OS 1.1_1 操作系统的概念、功能和目标 chap1 计算机系统概述 参考资料 B站王道考研操作系统概念 第9版 &#xff08;原书、译本&#xff09; 好久没有写博客总结整理和输出了&#xff0c;学习的惰性在一次次的考试周从零开始的经历中达到了巅峰&#xff0c;现在想重振旗鼓…

换工作有感

最近很长一段时间没有更新博客&#xff0c;更新关于vim相关的操作&#xff0c;主要是最近在忙于换工作的事情。其实本来我也没打算换工作的&#xff0c;主要是最近公司的一些骚操作让我觉得心里很不爽&#xff0c;所以一怒之下提出离职。 背景 先来说说这个事情的背景吧&#…

2022年 SecXOps 安全智能分析技术白皮书 附下载地址

近年来&#xff0c;互联网、大数据和人工智能 等技术都得到了飞速的发展&#xff0c;网络攻击的方法也越来越复杂&#xff0c;过去广泛、漫无目的的攻击威胁&#xff0c;在数年内迅速地转化为有目标、有组织、长期 潜伏的多阶段组合式高级可持续威胁&#xff08;Advanced Persi…

计算机网络——第五章网络层笔记(5)

网络地址翻译&#xff08;NAT&#xff09; Private IP address:不可路由的地址、也可用于广域网链路上 NAT&#xff1a;net address translate 私有IP地址和公有IP地址之间的转换。 PAT&#xff1a;port address translate 将多个私有IP地址影射到同一个公有IP地址的不同…

跑步时戴什么耳机好、分享五款最适合跑步的运动耳机排名清单

在进行户外跑步、骑行等运动&#xff0c;往往会感到枯燥乏味&#xff0c;很难坚持下去&#xff0c;就像我经常跑一圈就觉得没了动力&#xff0c;但是当我戴上耳机听音乐跑步时&#xff0c;不知不觉就结束了&#xff0c;就感觉时间过得很快。不过话有说回来&#xff0c;适合跑步…

【JVM】jvm的体系结构

JVM体系结构如下图所示&#xff1a; JVM大致可以分为五大模块&#xff1a; 类加载子系统&#xff08;Class Loader SubSystem&#xff09;运行时数据区&#xff08;Runtime Data Area&#xff09;执行引擎&#xff08;Execution Engine&#xff09;Java本地接口&#xff08;Ja…