DS:顺序表的实现

news2025/1/11 20:45:39

感谢各位友友的支持!目前我的博客进行到了DS阶段,在此阶段首先会介绍一些数据结构相关的知识,然后再进行顺序表的学习。学习数据结构是为后面的通讯录项目打基础。

在学习数据结构之前,需要友友们掌握一些储备知识——结构体、指针、动态内存管理

一、数据结构相关概念

    什么是数据结构?从字面意义上讲,就是“数据”和“结构”。

1.1 什么是数据?

    常见的数值1、2、3、4.....、教务系统里保存的用户信息(姓名、性别、年龄、学历等
等)、网页里肉眼可以看到的信息(⽂字、图⽚、视频等等),这些都是数据。

1.2 什么是结构?

       当我们想要使用大量使用同⼀类型的数据时,通过手动定义⼤量的独立的变量对于程序来说,可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在⼀起,结构也可以理解为组织数据的方式。比如说,在一个山坡上寻找一头叫做贝蒂的羊,就很难查找,但是如果是从一号羊圈里寻找到某一头羊就很容易,这是因为羊圈将数据进行了有效的组织管理。这里的羊圈就相当于“结构”,羊圈里的羊就相当于“数据”。

简而言之,我们为了方便组织管理数据,使用了一个可以有效组织数据的方式——数据结构。

1.3 什么是数据结构?

        数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在⼀种或多种特定关系的数据元素的集合。数据结构反映数据的内部构成,即数据由哪部分构成,以什么方式构成,以及数据元素之间呈现的结构。

      就比如在一个生意兴隆的餐厅中,如果不借助排队的⽅式来管理客⼾,会导致客⼾就餐感受差、等餐时间⻓、餐厅营业混乱等情况。同理,程序中如果不对数据进⾏管理,可能会导致数据丢失、操作数据困难、野指针等情况。通过数据结构,能够有效将数据组织和管理在⼀起,按照我们的⽅式任意对数据进⾏增删改查等操作。

1.4 数据结构到底有什么用?

(1)能够存储数据(如顺序表、链表等结构);

(2)存储后的数据方便我们进行数据的增删查改操作。

1.5 数组

       在没接触数据结构之前,如果要创建100个数据,我们会选择使用数组的方式创建这100个变量。这样就将同类型的数据进行了有效的管理。因此,数组是最基础的数据结构。

既然有了数组,为什么还要学习其他的数据结构(如顺序表)?

       先看下面的情形:假定数组有100个空间,已经使用了5个,向数组中插⼊数据步骤:求数组的长度,求数组的有效数据个数,向下标为数据有效个数的位置插⼊数据(注意:这里是否要判断数组是否满了,满了还能继续插入吗)……假设数据量非常庞大,频繁的获取数组有效数据个数会影响程序执行效率。
结论:最基础的数据结构能够提供的操作已经不能完全满足复杂算法实现。

二、顺序表

2.1 线性表

        线性表( Linear list )是n个具有相同特性的数据元素的有限序列,或者是具有部分相同特性的⼀类数据结构的集合。

       线性表是⼀种在实际中⼴泛使⽤的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串……线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

举例:根据相同特性,蔬菜分为绿叶类、⽠类、菌菇类等;水果分为热带水果、亚热带水果、温带水果等。

2.2 如何理解逻辑结构和物理结构?

       前面说,线性表是具有相同特性的数据结构的集合。这种相同特性体现在:

逻辑结构: 在线性表中,逻辑结构是连续的。

物理结构:数据在内存中存储时,它的结构是物理结构。

       线性表是一种抽象的、逻辑结构上连续,物理结构上不一定连续的结构。

       数组在逻辑结构和物理结构上都是连续的(因为数组在内存中是连续存放的),因此 以数组为底层的 顺序表在物理结构和逻辑结构上都是连续的。

2.3 顺序表的分类

        对于一份素菜汤来说,他的底层就是青菜,但是在加入牛肉、水进行烹煮摆盘等一系列操作后,就变成了一份更加高级的西湖牛肉羹。

       对于顺序表来说,顺序表的底层是数组,但是它提供了很多现成的方法(增删查改等方法),但是它通过对数组进行一系列的增删查改等操作,就升级变成了一个新的数据结构,即线性表。

      数组申请空间有两种方式:1. 定长数组(如:int arr[100]={0};)2. 动态数组:动态内存开辟数组的空间,确定大小后再去动态申请内内存。

      由于顺序表的底层是数组,而数组又可以分为定长数组和动态数组,因此顺序表也可以分为两类: 静态顺序表和动态顺序表 。

       通过对数组进行封装,实现了常用的增删改查等接口操作,将数组升级为了所谓的顺序表。封装过程就需要用到结构体。

2.3.1 静态顺序表 

定义:使用定长数组存储元素

(1)静态顺序表的一般结构
#define N 1000  //定义一个宏,方便根据需要修改定长数组的大小,这样就不用频繁改动数组的大小
typedef int SLDataType;
//因为顺序表根据需要可能需要存储不同的数据类型,所以将其进行重命名
//如果想改成char和float,可以直接在这里修改,不需要去改动后面的内容
typedef struct Seqlist
{
	SLDataType a[N];//定长数组,可以通过修改#define定义的N来改变数组大小
	int size;
	//定长数组开辟了N个空间,但不代表里面有N个有效数个数,所以需要size来记录有效数个数。
}SL;//将名字修改得简短一点

1. 为什么命名为 SeqList和SLDataType?

       将类型用typedef定义为SLDataType,因为顺序表可以用来存储各种类型的数据如内置类型、自定义类型等,使用SLDataType可以灵活修改数据的类型,而不用大量的修改数据类型的代码。

SeqList是sequence (顺序)和List(表)的组合,即为顺序表。命名要求简洁明了。

2. 初始化要注意传值调用和传址调用

3. 使用size有什么作用?

       例如int  arr[10] = {1 , 2 , 3 , 4 , 5 } ; 这里的有效数据个数就是5,size用来记录空间中使用的有效数据的个数,虽然申请了这么多的空间,但不是所有空间都存储了有效的数据。 

4. 为什么要使用宏定义,而不直接使用arr[100]?

       便于后续对空间大小进行修改,如果代码写到中途返现数组大小不合适,就可以直接使用宏定义进行一键替换,而不需要,一个一个的去修改,这样也就不会出现误改漏改的情况,写代码的效率进一步提升!

(2) 静态顺序表的劣势

a. 如果给定的数组大小不合适会出现空间浪费和数据丢失的情况。

       给定的数组长度如果大了,那么空间分配就大了,造成空间的浪费;空间分配小 了,不够用,导致后续的数据保存失败,造成数据丢失。(例如,存储用户信息的空间如果分配小了,就会出现用户信息丢失的情况;用户信息丢失得小,面临失业;如果用户信息丢失大了,则会面临被整个行业通报的情况。总而言之,丢失用户信息,是一件非常严重的技术事故,同时这也体现了一位程序员的代码素养)

b. 此外,静态顺序表对于未来可能存储的数据量是未知的。因此,更加推荐使用顺序表,但不代表静态顺序表就没用了。

2.3.2 动态顺序表

       根据前面对静态顺序表的分析,我们为了减少问题的产生,选择使用动态顺序表,动态顺序表结构灵活,程序猿可以根据需要灵活地开辟空间。

定义:使用动态数组进行动态增容

typedef int SLDataType;
//顺序表可能需要存储不同的数据类型,将其命名为SLDataType,然后通过typedef根据需要修改数据类型
//而且,修改数据,不一定得一键替换,有时只需要改变一部分数据类型
//一键替换是宏定义,比如:#define SLDataType int
typedef struct Seqlist
{
	SLDataType* arr;//动态数组,一开始不确定大小,程序员可以根据过程中的需求去合理开辟
	int size;//开辟了相应的空间,用size来记录有效数个数。	
    int capacity;//空间容量,若有扩容,用其记录扩容后动态数组的大小。
}SL;//(名称修改方式1)

//typedef struct SeqList SL;//(名称修改方式2)

       与静态顺序表相比,动态顺序表不仅底层的数组不同,还增加了capacity这一变量,这是因为动态顺序表在最开始并不知道要申请多大的空间,且在为顺序表进行动态内存开辟时,顺序表的空间是在变化的。

三、动态顺序表的实现

        在前面我们就知道了相比动态顺序表来说静态顺序表的问题更多,因此更推荐使用动态顺序表,下面是动态顺序表的实现过程介绍。

准备工作:在 SeqList.h文件中进行顺序表的声明,但不实现;在 SeqList.c文件中写实现顺序表的方法;在test.c文件中进行代码的测试,一定要写一部分再测一部分。

3.1 初始化和销毁

3.1.1 初始化

代码1:

     在这段代码中出现了“未初始化的局部变量'sl' ”,是因为传值调用与传址调用的混淆。

1. 行参传值中,行参是实参的一份临时拷贝,如果我们创建的变量参数sl没有进行能初始化,那就不能进行值的传递的;

2. 即使sl初始化了,由于行参是实参的临时拷贝,sl的值就不会被改变。可行的办法就是传址调用,使用指针来调用。

3. 此外,  .  操作符要改为 -> 操作符

所以,正确的代码为:

3.1.2 销毁

代码如下:

注意:

1. 为什么要使用assert断言?首先,必须要保证是动态内存开辟的空间,才能释放,所以释放空间前要先判断ps是否为空;其次,如果传入的是NULL,说明没有对顺序表的动态数组开辟过空间,也就不需要通过free释放空间了;

2. 为什么ps->arr被置为NULL?a.  动态申请开辟的空间被释放了,但是这块空间只是失去了使用权限,该空间仍然存在;b.  ps指针指向的空间虽然被释放了,但是指针的值并没有被改变,因此我们无法通过指针自身来判断它指向的空间是否被释放了;c.  把指针置空可以提醒我们该指针指向的空间已经释放,因为程序猿在写了大量的代码后很容易忘记指针指向的空间是否释放,如果误用了已经被释放的空间,程序将会崩溃,而如果ps->arr是一个空指针,那么编译器会通过报错来提醒你。

3.2 增容

      在增加、插入数据等操作之前,都要先判断空间是否足够,如果空间不够,就需要动态申请空间。

增容代码如下:

void SLCheckCapacity(SL* ps)
{
	//插入数据之前先看空间够不够
	if (ps->capacity == ps->size)
	{
		//申请空间
		//malloc calloc realloc  int arr[100] --->增容realloc
		//三目表达式
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//要申请多大的空间
		if (tmp == NULL)
		{
			perror("realloc fail!");
			exit(1);//直接退出程序,不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}

1. 增容使用哪个函数?

       使用realloc,因为它有增容的概念,而且可以进行多次增容;malloc和calloc都可以用来申请 一段连续的空间,但是它们都没有增容的概念。//复习realloc的空间处理方法;

      值得注意的是:(1)realloc增容的第二个参数单位是字节,所以代码中的newCapacity需要乘以sizeof(SLDataType);(2)使用realloc申请空间可能会申请失败,realloc返回 EOF,但是不能用ps->arr接收返回值,因为arr数组空间变为NULL,会使得arr空间原本可能会有数据消失,出现数据丢失的情况,因此我们创建一个新的临时变量tmp来接收开辟空间返回的地址;(3)realloc的返回值类型是void*,因此需要tmp需要强制类型转换为SLDataType*。
2. 增容需要申请多大的空间?(增容的原则)

        增容规则:增容通常来说,成倍数增加,一般是2、3倍。这个规律涉及概率论(补充:为什么增容需要以倍数增加?)比如,插入数据如果 是一个一个进行插入的,每插入一个数据就申请一块空间,当需要插入的数据很多时,就会出现频繁增容的情况,造成程序性能低下。最好的解决办法就是空间一次增加许多,但又不能增加太大,避免空间浪费;也不能增加太小了,所以2、3倍增加。如:4-8-16-32-64-128-256-512-1T……(2倍增加的)
        如果插入的数据量不大,前期就能表现出来,因为数据个数和空间大小成正比。如果前期的数据量不确定,先少一点申请空间,若发现插入的数据比较多,就逐步扩大空间。

3. 使用三目操作符 int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;有什么作用?

      起初在对顺序表进行初始化的时候,对capacity赋的值就是0,0无论乘以多少倍的容量值都是0;此外 ,如果capacity不等于0,那么就给它赋值4,这样newCapacity就等于2 * ps->capacity;如果capacity等于0,说明,ps指向的空间的空间容量为0。

4. 为什么还要判断tmp==NULL?

判断tmp得到的返回值是否为NULL,也就是判断动态申请空间是否成功

5. exit(1)和return 1的区别?

(1)exit(): 关闭所有文件,终止正在执行的进程。

a.  exit是系统调用级别的 ,它表示了一个进程的结束,用于在程序运行过程中随时结束程序, exit的参数是返回给os操作系统的,exit是结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程;通常情况:在整个程序中,只要调用exit就结束(当前进程或者在main时候为整个程序)。

b.  exit 是一个函数,exit是操作系统提供的(系统函数库中给出的)。

c.  exit() 则会立即结束整个程序的执行,且不会返回到调用者。

(2)return()是返回函数值并退出当前函数

a.  return是语言级别的,它表示了调用堆栈的返回; return()是返回函数值并退出当前函数,当然如果是在主函数main, 自然也就结束当前进程了,如果不是,那就是退回上一层调用。在多个进程时。如果有时要检测上个进程是否正常退出。就要用到上个进程的返回值,依次类推。

b.  return返回函数值,是关键字 ,是C语言提供的。

c.  return 只会结束当前的函数,且如果是在子函数中使用,程序其余部分还会继续执行。 

总的来说,exit(1)和return 1在这里都是差不多的效果,只是exit会比return更加暴力一些

3.3 打印

       后续我们会实现很多个函数的封装,包括尾插、头插、在指定位置插入数据等,在写完这些代码后我们都会进行调试来改正代码中的错误,但是代码写完后的调试操作太麻烦,而且难度也加大了,因此,我们选择再封装一个函数,可以对每一个封装的函数进行验证,便于我们及时找到错误并改正,提高我们编程的效率。

3.4 增删查改

3.4.1 插入(增)

插入有两种情况:尾插和头插。

(1)尾插

(ps->size)指向最后一个数据结尾 ,在size的位置插入数据,size++,

正确代码如下::

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	//if (ps==NULL)//
	//{
	//	return;//直接退出
	//}//比较温柔的洁柔的解决方式
    //先判断空间够不够
	assert(ps);//相较于if比较暴力,需要在SeqList.h中添加assert.h
	SLCheckCapacity(ps);//判断空间不够,就会增容
    //开始插入数据
	ps->arr[ps->size] = x;
	ps->size++;
}// ps避免为NULL

 1. assert断言:为了判断ps是否为空。对指针解引用的前提是该指针不能为空指针(NULL),这也是为了避免该函数被滥用!不能是你想传什么就传什么,后面的很多函数接口也要考虑这个情况!

2. 写入异常:从图中可以看出,capacity空间容量为0,因此不能插入数据,在此之前要先判断空间够不够,如果不够就要申请空间。(因此代码中用到增容代码:SLCheckCapacity,可以先判断)

3. 只要是插入数据,一定不要忘记最后要size++ 

(2)头插

从下标为0的位置插入数据,移动的顺序是从后向前进行的,移动的方向是向后移动,结构是整体数据向后移一位。移动数据的顺序如果错了,就会覆盖原来的数据,造成数据丢失更改。

正确代码:

//头插
void SLPushFront(SL* ps,SLDataType x)
{
	assert(ps);//判断传入的指针是否为空
	//先判断空间够不够
	SLCheckCapacity(ps);
	//先让数据表中已有的数据整体往后移动
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//前面的数据向后挪,arr[1]=arr[0](最后一次),
		//得到结束条件i>0,而且arr[0]位置空出来,可以插入数据了
	}
	ps->arr[0] = x;//直接在arr[0]处插入数据
	ps->size++;
}

判断循环结束的条件方法:看最后一次循环是怎样的,然后根据它来进行调整。

3.4.2 删除(删)

(1)尾删

尾删有两种情况,

       ps->arr[ps->size-1]=-1;中-1是我为了举例随便写的, 其实无论这里是否有值,只要size--了,那么他就不是有效的元素了,即使后面又插入了新元素,插入的新元素都是直接覆盖掉原数据,所以没必要特意地去赋值。(只要不是访问size之外的数据,size有数据不影响对顺序表的增删查改操作)

       此外,顺序表不能为空,如果为空就不能执行删除操作。

正确代码:

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//判断顺序表是否为空
	//ps->arr[ps->size - 1] = 0;这段代码有没有都可以,加入它感觉还有点画蛇添足
	--ps->size;
}

 尾删测试:

(2)头删

代码如下:

//头删:
void SLPopFront(SL* ps)
{
	assert(ps);//判断指针是否为空
	assert(ps->size);//判断顺序表是否为空
	//数据整体往前挪动一位
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
		//最后一个循环arr[0] = arr[1];ps->arr[ps->size - 2] = ps->arr[ps->size - 1];
	}
	ps->size--;
}

头删测试:

由上图可知代码正确。

       删除了首元素之后,要将后面的元素依次往前挪,如果是从后往前挪,那么3一旦覆盖2,就找不到2了,所以必须从前往后挪。

3.4.3 指定位置的操作

(1)指定位置插入数据

代码如下:

//指定位置之前插⼊数据,即在pos之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)//在ps指向的顺序表里插入,插入位置的下标,插入的数据
{
	assert(ps);//判断指针是否为空
	assert(pos >= 0 && pos <= ps->size);//pos是指顺序表的数组下标,有范围,0到有效数据个数之间
	//开始插入数据,判断空间够不够
	SLCheckCapacity(ps);
	//让下标为pos的位置后面的数据整体向后移动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}

在这里综合了头插和尾插,测试如下:

(2)指定位置删除数据

指定位置删除之后,要把后面的元素往前挪!

 代码如下:

//指定位置之前删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);//size位置上并没有有效的数据,因此,pos不能等于size
	//整个顺序表的数据少了一个,size--
	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//循环条件判断:ps->a[i-2] = ps->a[i-1];
        //size处没有数据,所以i不能等于size-1
	}
	ps->size--;
}
//测试一定要注意边界上的数据,再加上对中间位置的数据的测试

(3)顺序表的查找
//查找数据
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);//判断指针是否为空
	for (int i = 0; i < ps->size; i++)//遍历数组
	{
		if (ps->arr[i] == x)
		{
			//找到了
			return i;//返回下标
		}
	}
	//没找到
	return -1;//返回无效的下标整数
}

四、顺序表的所有代码

       下面是我写的整个有关顺序表实现的代码,值得一提的是,代码不是只有一种写法,我的代码也不一定是非常好的代码,因此,下面的代码作为参考,希望友友们作为参考,适当发挥!

SeqList.h

#pragma once

#include <stdio.h>
#include <stdlib.h> 
#include<assert.h>

typedef int	SLDataType;//方便后续类型的统一替换

//动态顺序表
typedef struct SeqList
{
	SLDataType* arr;
	int size;//有效数据个数
	int capacity;
}SL;//重命名

//顺序表的初始化
void SLInit(SL* ps);//声明

//顺序表的销毁
void SLDestroy(SL* ps);

//顺序表的打印,不用传入指针了,只需传值
void  SLPrint(SL s);


//顺序表的插入
void SLPushBack(SL* ps, SLDataType x);//尾插
void SLPushFront(SL* ps, SLDataType x);//头插


//顺序表的删除
void SLPopBack(SL* ps);//尾删
void SLPopFront(SL* ps);//头删

//指定位置之前插⼊数据
void SLInsert(SL* ps, int pos, SLDataType x);
//指定位置之前删除数据
void SLErase(SL* ps, int pos);
//查找数据
int SLFind(SL* ps, SLDataType x);

SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
//次文件用于声明各种方法:

#include "SeqList.h"//可以引用到


void SLInit(SL* ps)//s是结构体变量
{
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

//顺序表的销毁
void SLDestroy(SL* ps)
{

	if (ps->arr)//先判断arr数组中是否有申请空间,等价于if(ps->arr!=null)
	{
		free(ps->arr);
	}
	ps->arr = NULL;//销毁空间后指针要及时置为空(NULL)
	ps->size = ps->capacity = 0;//避免他们变成随机值(变成其他值)
} 
void SLCheckCapacity(SL* ps)
{
	//插入数据之前先判断空间够不够
	if (ps->capacity == ps->size)
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;//如果capacity不等于0,那么newCapacity等于2 * ps->capacity
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));//但是,capacity之前就被初始化为0了
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(1);//exit作用用法:直接退出,程序不再继续执行
		}
		//空间申请成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}
//顺序表的打印
void  SLPrint(SL s)
{
	for (int i = 0; i < s.size; i++)
	{
		printf("%d ", s.arr[i]);
	}
	printf("\n");
}

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);//暴力,在SeqList.h中添加assert.h
	SLCheckCapacity(ps);

	ps->arr[ps->size] = x;//也可以这样写
	ps->size++;
}

//头插
void SLPushFront(SL* ps,SLDataType x)
{
	assert(ps);
	//先判断空间够不够
	SLCheckCapacity(ps);
	//先让数据表中已有的数据整体往后移动
	for (int i = ps->size; i > 0; i--)
	{
		ps->arr[i] = ps->arr[i - 1];//前面的数据向后挪,arr[1]=arr[0](最后一次),
		//得到结束条件i>0,而且arr[0]位置空出来,可以插入数据了
	}
	ps->arr[0] = x;//直接在arr[0]处插入数据
	ps->size++;
}

//尾删
void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size);//判断顺序表是否为空
	//ps->arr[ps->size - 1] = 0;这段代码有没有都可以
	--ps->size;
}

//头删:
void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size);
	//数据整体往前挪动一位
	for (int i = 0; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
		//最后一个循环arr[0] = arr[1];ps->arr[ps->size - 2] = ps->arr[ps->size - 1];
	}
	ps->size--;
}

//指定位置之前插⼊数据,即在pos之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)//在ps指向的顺序表里插入,插入位置的下标,插入的数据
{
	assert(ps);//判断指针是否为空
	assert(pos >= 0 && pos <= ps->size);//pos是指顺序表的数组下标,有范围,0到有效数据个数之间
	//开始插入数据,判断空间够不够
	SLCheckCapacity(ps);
	//让下标为pos的位置后面的数据整体向后移动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	ps->size++;
}


//指定位置之前删除数据
void SLErase(SL* ps, int pos)
{
	assert(ps);//加入一个断言:顺序表不能为空,这样代码更加健壮
	assert(pos >= 0 && pos < ps->size);//size位置上并没有有效的数据,因此,pos不能等于size
	assert(ps->size);//确保里面有元素,否则不执行 
	//整个顺序表的数据少了一个,size--
	for (int i = pos; i < ps->size-1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//边界判断:ps->a[i-2] = ps->a[i-1];
		//size处没有数据,所以i不能等于size-1
	}
	ps->size--;
}
//测试一定要注意边界上的数据,再加上对中间位置的数据的测试

//查找数据
int SLFind(SL* ps, SLDataType x)
{
	assert(ps);//判断指针是否为空
	for (int i = 0; i < ps->size; i++)//遍历数组
	{
		if (ps->arr[i] == x)
		{
			//找到了
			return i;//返回下标
		}
	}
	//没找到
	return -1;//返回无效的下标整数
}

友友们,码字不易哎,三连支持一波呗~ 


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

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

相关文章

锦瑟香也MYLOVE:音质与颜值俱佳,入坑HiFi的热门好物!

当下尽管无线耳机大行其道&#xff0c;但有线耳机依旧保有其独特的魅力&#xff0c;特别是在音质表现上&#xff0c;它们拥有无线耳机难以企及的优势。如果对音质要求很高的话&#xff0c;口袋里还是少不了一副有线耳机。国产品牌中就有许多性价比高的有线耳机&#xff0c;它们…

Llama 3 开源!手把手带你进行大模型推理,部署,微调和评估

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学&#xff0c;针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 基于大模…

Linux网络编程--网络传输

Linux网络编程--网络传输 Linux网络编程TCP/IP网络模型网络通信的过程局域网通信跨网络通信&#xff1a;问题总结&#xff1a; Linux网络编程 TCP/IP网络模型 发送方&#xff08;包装&#xff09;&#xff1a; 应用层&#xff1a;HTTP HTTPS SSH等 —> 包含数据&#xff0…

如何在Windows安装Ollama大语言模型工具并实现无公网IP异地远程使用

文章目录 前言1. 运行Ollama2. 安装Open WebUI2.1 在Windows系统安装Docker2.2 使用Docker部署Open WebUI 3. 安装内网穿透工具4. 创建固定公网地址 前言 本文主要介绍如何在Windows系统快速部署Ollama开源大语言模型运行工具&#xff0c;并安装Open WebUI结合cpolar内网穿透软…

pycharm已有项目增加pipenv

pycharm已有项目增加pipenv 第一步 第一步 python base 需要安装pipenv pip install pipenv在设置&#xff0c;project 之后 会自动查找项目下的pipfile 和pipfile.lock 进行pip配置 如果网络较慢&#xff0c;可以修复pipfile下的url 为国内的pip源 [[source]] name "…

centos7安装openGauss数据库企业版

本文章是在CentOS7虚拟机上安装openGauss企业版数据库流程 1.下载安装包: https://opengauss.org/zh/download/ openGauss-5.0.1-CentOS-64bit-all.tar.gz 2.安装python3.6.9 见我的另一篇文章 CentOS7安装Python3-CSDN博客 3.检查工具依赖&#xff1a; 分别检查以下工具是…

符文协议的演变历程:从挑战到创新

在比特币网络长期面临的挑战中&#xff0c;与主流去中心化金融功能的兼容性一直是一大难题。相比之下&#xff0c;以太坊通过ERC-721和ERC-1155代币标准&#xff0c;为NFT和去中心化金融应用提供了支持&#xff0c;而比特币的应用范围却相对有限。然而&#xff0c;近年来&#…

2024燃动智火-业务视角的中国企业AI+学习发展报告

来源&#xff1a;新华三 学习型组织的数字化转型是众多企业关注的焦点&#xff0c;数字战略需要人才升级&#xff0c;数字 化学习加速人才培养。AI 技术在学习中的运用&#xff0c;为企业学习型组织的数字化转型插 上了飞翔的翅膀。这份报告解码了AI 时代企业的学习发展&#…

Docker(七):容器监控工具(Portainer、CAdvisor)

一&#xff1a;轻量级可视化监控工具Portainer 可视化监控工具, 可以通过docker安装&#xff0c;用于管理和监控docker&#xff0c;基本上的docker命令都有对应的按钮来操作。 # always 表示docker重启了该容器也跟着重启 docker run -d --name portainer -p 8000:8000 -p 90…

torch.gather用法详解

torch.gather是PyTorch中的一个函数&#xff0c;用于从源张量中按照指定的索引张量来收集数据。 基本语法如下&#xff0c; torch.gather(input, dim, index, *, sparse_gradFalse, outNone) → Tensor input&#xff1a;输入源张量dim&#xff1a;要收集数据的维度index&am…

多头蜗杆的轴截面和端截面的关系

最近有一个点,之前没有注意,就是多头蜗杆的导程与齿距的关系,它们会影响蜗杆断截面的形状,是不是听的有点别扭,往下看: 上图是一个蜗杆的轴剖面齿形,看到这个图形,如果看不到蜗杆实物或者有明显的标准,我们是没办法判断这个蜗杆的头数是多少。 从下面几张图可以看到,…

A-1:树状数组

A-1:树状数组 1.介绍Q1:树状数组解决什么问题&#xff1f;Q2:树状数组的使用1.前置知识&#xff1a;lowbit(x)2.单点修改3.求[1,n]的和4.区间查询5.hh Q3:树状数组是否优化了Q4:上图上例子解释上面说的东西(Important) 2.习题练习 1.介绍 树状数组是一个比较难以理解的高级数据…

希亦、追觅、石头洗地机哪一款更实用?爆款产品性能全名测评

洗地机行业正在经历突飞式的发展&#xff0c;各大品牌商家纷纷推出功能丰富、性能卓越的产品&#xff0c;不断升级和优化洗地机的各个方面。如今&#xff0c;洗地机的功能配置已经变得十分完善&#xff0c;为用户提供了更多选择和更优质的清洁体验。那么&#xff0c;洗地机型号…

产品推荐 | 基于Lattice用于原型和FPGA设计和开发的Avant-E 评估板

01 产品概述 莱迪思半导体Avant-E评估板使设计人员能够快速进行原型设计和FPGA设计测试。它提供对所有 I/O 的访问&#xff0c;以及广泛的内存选项&#xff0c;以实现更快的原型设计和开发。 Avant-E评估板采用LFG1156封装的Avant-E FPGA。该板可以通过 FMC HPC、PMAD 和 Ras…

团队协作:如何利用 Gitee 实现多人合作项目的版本控制

文章目录 前言一、名词解释1、Git是什么&#xff1f;2、Gitee、GitHub和GitLab 二、操作步骤1.安装Git2.创建Gitee仓库3.用vscode连接仓库4. 克隆远程仓库 总结 前言 在软件开发中&#xff0c;有效地管理代码是至关重要的。Gitee 是一个功能强大的代码托管平台&#xff0c;提供…

Qt :设置应用的图标

应用不设置图标&#xff0c;怎么都是没灵魂的。 Qt如何设置应用程序图标&#xff0c;一句话搞定&#xff1a; win32: RC_ICONS app.ico本文&#xff0c;笔者 app.ico 文件与pro放到同一级目录。各位可以根据自己的实际情况~

Java -集合-知识点

本文详细介绍了Java中集合的基本概念、常用数据结构和核心特性。通过学习本文&#xff0c;读者可以了解到Java集合框架的核心接口和实现类&#xff0c;掌握各种数据结构在不同场景下的应用方法和优劣势&#xff0c;以及如何使用集合框架提供的方法进行数据操作和处理。同时&…

【MATLAB基础绘图第21棒】绘制比例弦图 (Chord Diagram)

MATLAB绘制比例弦图 Chord Diagram 1 简介1.1 弦图简介1.2 比例弦图简介 2 MATLAB绘制比例弦图2.1 数据准备2.2 基本绘制2.3 添加方向箭头2.4 添加绘图间隙2.5 添加刻度2.6 修改标签2.7 颜色设置2.8 弧块及弦属性设置2.8.1 弧块属性设置2.8.2 弦属性设置 2.9 字体设置 参考 1 简…

手写Java设计模式之工厂模式,附源码解读

工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常用的设计模式之一&#xff0c;这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 工厂模式提供了一种创建对象的方式&#xff0c;而无需指定要创建的具体类。 工厂模式属于创建型…

11.盛最多水的容器(Java,双指针)

目录 题目描述&#xff1a;输入&#xff1a;输出&#xff1a;代码实现&#xff1a; 题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同…