【编织时空一:探究顺序表与链表的数据之旅】

news2024/11/23 20:38:28

本章重点

  1. 线性表

  2. 顺序表

  3. 顺序表OJ题

1.线性表

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

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

2.顺序表

        顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。 顺序表中的元素在内存中是相邻存储的,每个元素都有一个对应的索引来标识其在顺序表中的位置。顺序表支持随机访问,可以通过索引直接访问任意位置的元素。

        顺序表一般可以分为:

1. 静态顺序表:使用定长数组存储元素。

 静态顺序表的缺点:

  1. 固定大小: 静态顺序表的大小在创建时被固定下来,无法动态地扩展或缩小。这意味着一旦初始化了静态顺序表,其容量就无法改变。如果需要存储的元素数量超过初始分配的容量,可能需要重新分配更大的空间并手动进行数据迁移。

  2. 内存浪费: 如果静态顺序表的容量设置过大,但实际存储的元素数量较少,会导致内存浪费。因为未使用的部分空间也会被保留,占用了额外的内存。

  3. 不灵活: 由于容量固定,静态顺序表可能无法适应动态变化的数据需求。当需要的容量超过初始设置时,可能需要重新设计或重写代码。

  4. 初始化成本: 静态顺序表在初始化时需要分配固定大小的内存空间,而如果无法预估元素的最终数量,就需要进行适当的空间规划,这可能增加设计和开发的成本。

2. 动态顺序表:使用动态开辟的数组存储。

3.顺序表接口实现 

typedef int SLDataType;
// 顺序表的动态存储
typedef struct SeqList
{
	SLDataType* a;  // 指向动态开辟的数组
	int size;       // 有效数据个数
	int capicity;   // 容量空间的大小
}SeqList;

// 基本增删查改接口
// 
// 顺序表初始化
void SeqListInit(SeqList * s);
// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* s);
// 顺序表尾插
void SeqListPushBack(SeqList* s, SLDataType x);
// 顺序表尾删
void SeqListPopBack(SeqList* s);
// 顺序表头插
void SeqListPushFront(SeqList* s, SLDataType x);
// 顺序表头删
void SeqListPopFront(SeqList* s);
// 顺序表查找
int SeqListFind(SeqList* s, SLDataType x);
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* s, size_t pos, SLDataType x);
// 顺序表删除pos位置的值
void SeqListErase(SeqList* s, size_t pos);
// 顺序表销毁
void SeqListDestory(SeqList* s);
// 顺序表打印
void SeqListPrint(SeqList* s);

顺序表初始化:void SeqListInit(SeqList * s);

        代码中存在一些问题,主要是关于C语言中函数参数传递方式的错误理解。C语言中的参数传递是值传递,意味着函数接收的是参数的副本,而不是原始的数据。因此,当你在 SeqListInit 函数中修改 s 的值时,实际上只是修改了传递进来的副本,而不会影响原始的 s。为了解决这个问题,需要通过指针传递来修改原始数据。

// 顺序表初始化
void SeqListInit(SeqList* s)
{
	s->a = NULL;
	s->size = 0;
	s->capicity = 0;
}

但是,我们需要在初始的时候就开辟一些空间,这样更方便数据存储,不用一上来就扩容。

// 顺序表初始化
void SeqListInit(SeqList* s)
{
	s->a = (SeqList*)malloc(sizeof(SLDataType) * 4);
	if (s->a == NULL)
	{
		perror("malloc");
		//return;只是该函数退出,程序依然运行
		exit(-1);//程序退出为-1
	}
	s->size = 0;
	s->capicity = 4;
}

        malloc函数:malloc函数的返回值是void *,使用malloc函数要在返回的时候转化为我们需要的类型。malloc(sizeof(SLDataType) * 4)这代表的是申请了4个SLDataType类型大小的空间。        

        malloc函数的使用要引头文件#include<stdlib.h>。
        分配成功则返回指向被分配内存的指针,分配失败则返回空指针NULL。

检查空间,如果满了,进行增容:void CheckCapacity(SeqList* s);

// 检查空间,如果满了,进行增容
void CheckCapacity(SeqList* s)
{
	//满了需要扩容
	if (s->size == s->capicity)
	{
		//realloc,第二个参数是新空间的大小,不是要扩多少
		SLDataType* temp = (SLDataType*)realloc(s->a, s->capicity * 2 * sizeof(SLDataType));//扩容2倍
		if (temp == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		s->a = temp;
		s->capicity *= 2;
	}
}

        realloc函数:realloc函数的返回值是void *,使用realloc函数要在返回的时候转化为我们需要的类型。realloc(s->a, s->capicity * 2 * sizeof(SLDataType))这代表的是申请了s->capicity * 2 个SLDataTypet型大小的空间。 我这里是扩充2倍空间。同时realloc函数,第二个参数是新空间的大小,不是要扩多少 。    

        realloc函数的使用要引头文件#include<stdlib.h>。
        分配成功则返回指向被分配内存的指针,分配失败则返回空指针NULL。

顺序表尾插:void SeqListPushBack(SeqList* s, SLDataType x);

// 顺序表尾插
void SeqListPushBack(SeqList* s, SLDataType x)
{
	CheckCapacity(s);
	s->a[s->size] = x;//尾插数字
	s->size++;//尾插后有效数字+1
}


顺序表尾删:void SeqListPopBack(SeqList* s);

// 顺序表尾删
void SeqListPopBack(SeqList* s)
{
	s->a[s->size - 1] = 0;
	s->size--;
}

这样写有一个问题,万一要删除的数字就是0,你还把size-1的位置设置为0,就没有意义了,我们发现只用将size--就行,有效数字就会少一个,打印数据自然会少一个,下次再尾插原本的数据就会被覆盖,原数字也自然就被删除了。

// 顺序表尾删
void SeqListPopBack(SeqList* s)
{
	//s->a[s->size - 1] = 0;
	s->size--;
}

这样写是不是还有问题,假设删掉的数字比原本数字多怎么办?程序直接崩了,这样会出现越界访问的问题

// 顺序表尾删
void SeqListPopBack(SeqList* s)
{
	//方法一:
	if(s->size == 0)//保证不会被越界
	{
		return;
	}
	//s->a[s->size - 1] = 0;
	s->size--;

	//方法二:
	assert(s->size > 0);
	//s->a[s->size - 1] = 0;
	s->size--;
}

顺序表头插:void SeqListPushFront(SeqList* s, SLDataType x);

// 顺序表头插
void SeqListPushFront(SeqList* s, SLDataType x)
{
	CheckCapacity(s);
	//挪动数据
	int end = s->size - 1;//最后一个数据
	while (end >= 0)
	{
		s->a[end + 1] = s->a[end];
		end--;
	}
	s->a[0] = x;
	s->size++;
}


顺序表头删:void SeqListPopFront(SeqList* s);

// 顺序表头删
void SeqListPopFront(SeqList* s)
{
	assert(s->size > 0);//防止越界
	int begin = 0;
	while (begin < s->size - 1)
	{
		s->a[begin] = s->a[begin + 1];
		begin++;
	}
	s->size--;
}

顺序表查找:int SeqListFind(SeqList* s, SLDataType x);

顺序表有顺序存取的功能,因此按位查找元素可以直接通过数组下标定位取得。

// 顺序表查找
int SeqListFind(SeqList* s, SLDataType x)
{
	for (int i = 0; i < s->size; i++)
	{
		if (s->a[i] == x)
			return i;
	}
	return -1;
}


顺序表在pos位置插入x:void SeqListInsert(SeqList* s, size_t pos, SLDataType x);

        顺序表的元素插入和插队是一个意思的。想象一下,有一个人要插队,他要插到第3个位置去,那么他前面的两个人不用动,而他后面的人都得动。具体步骤是:最后面的那个人后退一个位置,倒数第二个人后退到原来最后一个人的位置,这样子后面的每个人依次后退,最后就空出来了一个位置,这个人就插队进去了。顺序表也是这么插入的。在插入操作完成后表长+1(多了一个人)。

元素插入有一些要求:

  1. 元素下标是否越界(有没有插队到奇怪的位置)
  2. 顺序表存储空间是否满了(有没有位置让你插队)
// 顺序表在pos位置插入x
void SeqListInsert(SeqList* s, size_t pos, SLDataType x)
{
	//检查pos是否合法
	assert(pos >= 0 && pos <= s->size);
	//检查是否需要扩容
	CheckCapacity(s);
	int end = s->size - 1;
	while (end >= pos)
	{
		s->a[end + 1] = s->a[end];
		end--;
	}
	s->a[pos] = x;
	s->size++;
}


顺序表删除pos位置的值:void SeqListErase(SeqList* s, size_t pos);

        删除和插入的操作类型,这里借用插队的例子说明。一群人在排队,有一个人有事临时走了,那么这个人的位置就空出来了,后面的人就一个个往前一步,补上这个空位。在删除操作完成后表长-1(少了一个人)。

元素删除有一些要求:

  1. 元素下标是否越界(走的人是不是这个排队里面的人)
  2. 顺序表存储空间是否为空(有没有人可以走)
// 顺序表删除pos位置的值
void SeqListErase(SeqList* s, size_t pos)
{
	//检查pos是否合法
	assert(pos >= 0 && pos < s->size);
	int begin = pos;
	while (begin < s->size - 1)
	{
		s->a[begin] = s->a[begin + 1];
		begin++;
	}
	s->size--;
}


顺序表销毁:void SeqListDestory(SeqList* s);

        顺序表初始化的时候是用malloc函数向系统申请的空间,malloc函数申请的空间是在内存的堆区,堆区的空间不会被系统自动回收,还需要用free函数释放空间。与malloc一样,要引头文件#include<stdlib.h>。

// 顺序表销毁
void SeqListDestory(SeqList* s)
{
	free(s->a);//释放开辟的空间
	s->a = NULL;
	s->size = 0;
	s->capicity = 0;
}

顺序表打印:void SeqListPrint(SeqList* s);

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

修改顺序表pos位置的值

// 修改顺序表pos位置的值
void SeqListModify(SeqList* s, size_t pos, SLDataType x)
{
    assert(pos >= 0 && pos < s->size);
    //修改pos位置的值
    s->a[pos] = x;
}

3.顺序表OJ题

1. 原地移除数组中所有的元素val,OJ链接:

https://leetcode.cn/problems/remove-element/

思路一:暴力解法

这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

删除过程如下:

int removeElement(int* nums, int numsSize, int val) 
{
	int size = numsSize;
	for (int i = 0; i < size; i++) 
	{
		if (nums[i] == val) 
		{ // 发现需要移除的元素,就将数组集体向前移动一位
			for (int j = i + 1; j < size; j++) 
			{
				nums[j - 1] = nums[j];
			}
			i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
			size--; // 此时数组的大小-1
		}
	}
	return size;
}
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

思路二:双指针法(快慢指针法): 通过一个快指针和慢指针在一个whiler循环下完成工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

删除过程如下:

int removeElement(int* nums, int numsSize, int val)
{
	int fastindex = 0;
	int slowindex = 0;
	while (fastindex < numsSize)
	{
		if (nums[fastindex] != val)
		{
			nums[slowindex++] = nums[fastindex++];//赋值
		}
		else
		{
			fastindex++;//发现需要移除的元素,就将指针向后移动一位
		}
	}
	return slowindex;
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

2. 删除排序数组中的重复项,OJ链接:https://leetcode.cn/problems/remove-duplicates-from-sorted-array/

3. 合并两个有序数组,OJ链接:https://leetcode.cn/problems/merge-sorted-array/

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) 
{
	int end1 = m;
	int end2 = n;
	int end = m + n;
	while (end1 >= 0 && end2 >= 0)
	{
		if (nums1[end1] < nums2[end2])
			nums1[end--] = nums2[end2--];
		else
			nums1[end--] = nums1[end1--];
	}
	while (end2 >= 0)
		nums1[end--] = nums2[end2--];
}

 本节结束啦!

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

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

相关文章

Linux命令 -- vim

Linux命令 -- vim 前言一般模式光标移动复制粘贴内容查找 底线命令行模式 前言 用vim指令进入文件。 刚进入时是命令行模式&#xff0c;也叫一般模式。 按i或者insert进入编辑模式&#xff0c;此时可以编辑文件内容。 按esc可从编辑模式退回到一般模式&#xff0c;输入冒号进…

[C++ 网络协议编程] TCP/IP协议

目录 1. TCP/IP协议栈 2. TCP原理 2.1 TCP套接字中的I/O缓冲 2.2 TCP工作原理 2.2.1 三次握手&#xff08;连接&#xff09; 2.2.2 与对方主机的数据交换 2.2.3 四次握手&#xff08;断开与套接字的连接&#xff09; TCP&#xff08;Transmission Control Protocol传输控…

【Apollo】阿波罗自动驾驶:塑造自动驾驶技术的未来

前言 Apollo (阿波罗)是一个开放的、完整的、安全的平台&#xff0c;将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统&#xff0c;快速搭建一套属于自己的自动驾驶系统。 开放能力、共享资源、加速创新、持续共赢是 Apollo 开放平台的口号。百度把自己所拥有的强大、…

前后端分离------后端创建笔记(03)前后端对接(上)

本文章转载于【SpringBootVue】全网最简单但实用的前后端分离项目实战笔记 - 前端_大菜007的博客-CSDN博客 仅用于学习和讨论&#xff0c;如有侵权请联系 源码&#xff1a;https://gitee.com/green_vegetables/x-admin-project.git 素材&#xff1a;https://pan.baidu.com/s/…

Reinforcement Learning with Code【Code 6. Advantage Actor-Critic(A2C)】

Reinforcement Learning with Code【Code 6. Advantage Actor-Critic&#xff08;A2C&#xff09;】 This note records how the author begin to learn RL. Both theoretical understanding and code practice are presented. Many material are referenced such as ZhaoShiy…

你不得不懂的IT知识-《敏捷项目管理》

国林哥在IBM时&#xff0c;几乎每天都会收到关于“敏捷”相关的邮件&#xff0c;公司鼓励我们去学习邮件里的知识&#xff0c;参加敏捷相关的认证和培训。刚开始我和大多数同事一样不管不顾&#xff0c;后来随着PBC里要求加上成长目标&#xff0c;比如要获得一个认证&#xff0…

为什么要试用CRM系统?有什么优点?

对于那些正在进行CRM选型的企业来说&#xff0c;想要了解一款CRM系统是否好用&#xff0c;亲自试用无疑是最好的方法。那么&#xff0c;有没有可以免费试用的在线CRM系统 CRM系统免费试用的好处 体验产品功能&#xff1a;您可以亲自操作和测试CRM系统的各项功能&#xff0c;如…

从一个GPU到多个GPU

在多GPU运行应用程序时&#xff0c;需要正确设计GPU之间的通信&#xff0c;GPU间数据传输的效率取决于GPU是如何连接在一个节点上并跨集群的 在多GPU系统里有两种连接方式 多GPU通过单个节点连接到PCIe总线上 多GPU连接到集群中的网络交换机上 /* * 本示例演示了如何使用 Open…

【Vue-Router】路由模式

1. WebHashHistory index.ts import { createRouter, createWebHistory, RouteRecordRaw, createWebHashHistory } from "vue-router";// 路由模式 //vue2 mode history -> vue3 createWebHistory //vue2 mode hash -> vue3 createWebHashHistory //vue2 m…

电池的正极是带正电?

首先说明结论&#xff1a;电池正极带正电&#xff0c;负极带负电。 一个错误的实例&#xff1a; 如果说电流是从电池正极流动到电池负极&#xff0c;那么电子就是从负极流动到正极&#xff0c;那么正极就是带负电。----这个说法是错误的。这是因为&#xff0c;根据那么很出名…

verilog学习笔记5——进制和码制、原码/反码/补码

文章目录 前言一、进制转换1、十进制转二进制2、二进制转十进制3、二进制乘除法 二、原码、反码、补码1、由补码计算十进制数2、计算某个负数的补码 前言 2023.8.13 天气晴 一、进制转换 1、十进制转二进制 整数&#xff1a;除以2&#xff0c;余数倒着写 小数&#xff1a;乘…

难解的bug

android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord 【Android TimeCat】 解决 context.startforegroundservice() did not then call service.startforeground() | XiChens Blog http://www…

【Linux从入门到精通】文件I/O操作(C语言vs系统调用)

文章目录 一、C语言的文件IO相关函数操作 1、1 fopen与fclose 1、2 fwrite 1、3 fprintf与fscanf 1、4 fgets与fputs 二、系统调用相关接口 2、1 open与close 2、2 write和read 三、简易模拟实现cat指令 四、总结 &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &#x1f64b;‍…

JAVA多线程和并发基础面试问答(翻译)

JAVA多线程和并发基础面试问答(翻译) java多线程面试问题 1. 进程和线程之间有什么不同&#xff1f; 一个进程是一个独立(self contained)的运行环境&#xff0c;它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序…

Shell编程之条件测试、if语句、case语句

条件语句 一、条件测试1.1 测试命令1.1 文件测试1.2 整数比较1.3 字符串比较1.4 逻辑测试1.4.1 逻辑与 &&1.4.2 逻辑或 || 1.4.3 组合应用1.5 多个命令组合执行 ( ) { } 二、if语句2.1单分支结构2.2 多分支结构2.4 if语句练习2.4.1 单分支2.4.2 简单的交互式分数反馈 三…

Shell编程之正则表达式(非常详细)

正则表达式 1.通配符和正则表达式的区别2.基本正则表达式2.1 元字符 &#xff08;字符匹配)2.2 表示匹配次数2.4 位置锚定2.5 分组 和 或者 3.扩展正则表达式4.部分文本处理工具4.1 tr 命令4.2 cut命令4.3 sort命令4.4 uniq命令 1.通配符和正则表达式的区别 通配符一般用于文件…

部署Springboot项目注意事项

步骤 步骤 1&#xff1a;将数据库内容在云服务器上的数据库部署一份 我使用mariadb&#xff1b;会出现一些不兼容现象&#xff1b;我们需要把默认值删掉 2&#xff1a;配置文件你得修改地方 a&#xff1a;linux是磁盘区分(像我自己项目用来储存验证码的文件我们得换这个配置;…

DoIP诊断入门

简介 DoIP&#xff08;Diagnosis over Internet Protocol&#xff09;是一种用于车辆诊断的网络通信协议。它基于现代互联网技术&#xff0c;允许通过以太网或IP网络进行车辆诊断和通信。 DoIP的背景是现代车辆中使用的电子控制单元&#xff08;ECU&#xff09;数量不断增加&…

利用OpenSSL实现私有 CA 搭建和证书颁发

利用OpenSSL实现私有 CA 搭建和证书颁发 一、私有 CA 搭建1. 安装openssl2. 配置 openssl3. 生成 CA 自己的私钥4. 生成 CA 自己的自签证书5. 验证自签证书 二、向私有CA申请证书流程1. 生成应用私钥文件2. 根据应用私钥生成证书申请文件3. 向CA请求颁发证书4. 验证应用证书5. …

PS/LR2024专用智能磨皮插件Portraiture提高P图效率

Portraiture 4智能磨皮插件支持Photoshop和Lightroom&#xff01;Portraiture是一款智能磨皮插件&#xff0c;为Photoshop和Lightroom添加一键磨皮美化功能&#xff0c;快速对照片中皮肤、头发、眉毛等部位进行美化&#xff0c;无需手动调整&#xff0c;大大提高P图效率。全新4…