顺序表讲解

news2024/11/24 12:53:13

一.数据结构

在学习顺序表之前,我们先需要了解什么是数据结构。

1.什么是数据结构呢?

数据结构是由“数据”和结构两词组合而来。

什么是数据呢?

你的游戏账号,身份信息,网页里的信息(文字,视频,图片),手机app存储的信息等等这些都是数据。

什么是结构呢?

在生活中,我们是有很多数据的,当我们想要使用大量的同一类型的数据的时候,通过手动定义大量的独立变量对于程序员的消耗是很大的,并且可读性非常的查,无法轻易的实现查找数据,排列数据等基本功能。可读性非常差,我们可以借助数组这样的数据结构将大量的数据组织在一起,结构也可以理解为组织这样结构的方式。

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

总结:1.能够存储数据(比如顺序表,链表等结构)2.存储的数据方便查找。

2.为什么需要数据结构?

假设在餐馆中,如果我们不使用排队的方式来管理客户点餐,会导致客户就餐感受差,等餐时间长,餐厅营业混乱等情况。同理在程序中,如果不对数据进行管理,可能会导致数据丢失,数据管理困难等问题。所以我们需要通过数据结构,能够将有效数据组织和管理在一起。按照我们的方式任意对数据进行增删改查等操作。

3.最基础的数据结构:数组。

可是数组有一个极大的缺陷,就是在我们声明数组的时候,数组的大小需要固定,可是我们的数据的多少却是无法确定的,数组过大,会导致空间浪费,数组过小,会导致数据丢失。

也就是最基础的数据结构能够提供的操作已经不能完全满足复杂算法的实现了。
 

二.顺序表 

1.顺序表的概念以及结构

1.1线性表

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

线性表在逻辑上是一定是线性结构,也就是连续的一条直线。但是在物理结构上不一定是连续的。

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

案例:蔬菜分为绿叶类,瓜类,菌菇类。线性表指的是具有部分相同特性的一类数据结构的集合。

2.顺序表分类

2.1顺序表和数组的区别

顺序表的底层结构式数组,对数组的封装,实现了常用的增删改查等接口

2.2分类

1.静态顺序表

概念:使用定长数组存储元素

typedef int SLDataType;
#define N 7
struct SeqList {
	SLDataType arr[N];//定长数组
	int size;//存储的有效数据个数
};

typedef struct SeqList SL;

这样的顺序表其实和数组类似:空间少了不够使用,多了会造成空间浪费

2.动态顺序表
struct SeqList {
	SLDataType *arr;//按需求申请空间
	int size;//存储的有效数据个数
	int capacity;//顺序表实际空间容量
};

typedef struct SeqList SL;

什么是按需申请空间呢?

这时候就需要用到我们的malloc函数申请空间用于我们存储数据,如果空间不够了,我们再使用realloc函数对空间进行扩容。 

这样的顺序表就非常的方便了

3.顺序表的实现

#pragma once
#define INIT_CAPACITY 4
typedef int SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
	SLDataType* a;
	int size; // 有效数据个数
	int capacity; // 空间容量
}SL;
//初始化和销毁
void SLInit(SL* ps);//初始化
void SLDestroy(SL* ps);//销毁
void SLPrint(SL* ps);//打印数据
//扩容
void SLCheckCapacity(SL* ps);
//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);//尾插
void SLPopBack(SL* ps);//尾删
void SLPushFront(SL* ps, SLDataType x);//头插
void SLPopFront(SL* ps);//头删
//指定位置之前插⼊/删除数据
void SLInsert(SL* ps, int pos, SLDataType x);//插入
void SLErase(SL* ps, int pos);//删除
int SLFind(SL* ps, SLDataType x);//查找

我们自己实现顺序表的基础就是完成以上所有函数 。

 1.首先我们先完成初始化函数。

void SLInit(SL* ps);//初始化

	SL ps;
	SLInit(&ps);

首先我们来思考一个问题:为什么在初始化函数中我们使用的是SL*类型的指针作为参数?

这是因为如果是使用SL作为参数的话,我们在函数中的任何操作并不会影响实参,形参只是实参的临时拷贝,改变形参并不会影响实参,所以为了改变实参,我们就需要使用一个指向实参的指针。这里我们为了改变这个结构体ps,我们就需要使用一个指向ps的指针。

然后我们再来思考这个初始化函数应该怎么写。

顺序表这个结构体有三个成员变量,对结构体进行初始化,无非就是对这个三个成员变量进行初始化。

顺序表开始是没有存储任何数据的,所以很明显这里的size就是0了.

观察上面的头文件,有着这样一句代码

typedef int SLDataType;

顺序表是可以储存不同种类的数据的,所以我们在使用的时候不能将数据的类型定死了,这是需要根据情况进行改变的,但是我们在实际开发中是不可能对每一个类型一个一个改变的,所以为了方便,我们将我们要存储的类型重命名为SLDataType这样如果我们需要存储其他类型的数据直接进行改变重命名的对象即可。

这也是为什么结构体的指针是SLDatatype*类型的原因了。

我们此时的顺序表是没有储存任何数据的,我们还需要为其开辟空间吗?如果要,那么开辟多大的空间合适呢?

实际上,开不开辟都可以,这个看自己即可,如果初始就开辟会更加方便,这样不用在后续使用的过程中判断空间是否为0了。

第二个问题开辟多大的空间合适呢?

这个是要看具体的实际情况的,这里我们是自己写,不会用到太多的数据,我们就开辟4个元素大小空间即可。

#define INIT_CAPACITY 4

如果觉得太小直接将4进行更改即可。

因为我们要开辟4个空间,所以此时顺序表的容量就是4了。这时候我们就完成了顺序表的初始化了.

void SLInit(SL* ps) {//初始化
	ps->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);
	ps->size = 0;
	ps->capacity = 4;
};

2.完成插入数据函数 

 接下来就是如何插入元素了

这里解释一下头插尾插什么意思,因为顺序表的本质上是一个数组,只不过这个数组大小是可变的,头插就是在这个顺序表的最前面插入一个数据,尾插就是在顺序表的末尾插入一个数据.头删尾删同理就是删除数据了.

本质上这四种函数其实都是一样的,为了本文的简便性,我们先完成指定位置插入函数和指定位置删除函数,然后再使用这两个函数完成其他函数,也是为了方便大家理解.

void SLInsert(SL* ps, int pos, SLDataType x);

第一个参数就是顺序表结构体的指针了,第二个参数就是指定位置的下表了(position),第三个参数就是我们需要插入的元素.

但是我们需要考虑一个问题,如果顺序表容量不够了怎么办,此时我们是不能进行插入的,否则就造成了数据的丢失,这是非常重大的事故.

所以我们在完成插入函数之前我们需要判断容量是否够 .所以我们可单独再定义一个函数完成此功能.

void SLCheckCapacity(SL* ps) ;

 什么时候顺序表的空间不够了呢?

我们要存储的数据大于空间容量就不够了,也就是ps->size > ps->capacity的时候就空间不足了,但是我们存储数据的时候是一个一个进行存储的,也就是说其实两者相等的时候就已经不够了,因为此时我们已经没有办法存储数据了,所以需要realloc函数进行扩容处理.

此时我们又遇到了一个问题:这个空间扩大到多大合适呢?是加1还是加2呢?

如果我们扩容的空间较小,这时候我们就会反复调用这个函数,此时函数压栈会浪费很多性能和时间

程序效率就会低下,所以最好是扩大2倍或者3倍(这个是有数学推导出来的).

void SLCheckCapacity(SL* ps) {//检测空间大小
	assert(ps);
	if (ps->capacity == ps->size) {
        ps->a = realloc(ps->a,sizeof(SLDataType) * ps->capacity * 2);
	}
};

此时我们就完成了扩容操作.

但是

这样写对不对呢?

我们知道realloc函数,如果开辟失败是会返回NULL的,如果发生这样的情况,ps->a就变成了NULL,我们不仅开辟失败了,还丢失了访问原来开辟空间的权力,造成了内存泄漏的情况,同时我们也丢失了所有的数据.

所以我们需要定义一个临时变量,来储存realloc函数的返回值,如果返回值不为NULL,我们再把返回值传给ps->a.这样就避免了这样的情况.

以为这样就完了吗?

其实还有一个错误.我们进行了扩容,但是我们并没有改变容量大小,这样也会造成数据丢失的情况,所以容量也要*2.

完整版:

void SLCheckCapacity(SL* ps) {//检测空间大小
	assert(ps);
	if (ps->capacity == ps->size) {
		SLDataType* tmp = realloc(ps->a,sizeof(SLDataType) * ps->capacity * 2);
		if (tmp == NULL) {
			perror("realloc");
			exit(1);
		}
			ps->a = tmp;
			ps->capacity *= 2;
	}
};

完成了这些我们再来看插入函数.

假设这里有这样一个顺序表,储存着这样的数据1 2 3 4 5 ... 9

如果我们要在下标为6的位置之前插入一个数据10,也就是在数据6前面插入一个数据10,我们需要怎么完成呢?

首先我们需要对原有的数据进行移动,避免数据丢失了.依次将后面的数据向后移动一位.然后再在下标为5的位置写入数字10, 

 	for (int i = ps->size; i > pos; i--){
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;

我们是从下标为size的位置开始的,所以i从ps->size开始,直到pos的后一位结束.

再移动完了后直接更改即可.

但是这里还有一个错误,也是一个很容易犯错的地方.

我们在插入一个数据后,有效数据的个数其实是变化了的,所以要加1,同理删除要减1,这个一定不能忘了.

完整版:

void SLInsert(SL* ps, int pos, SLDataType x) {//在指定位置之前插入
	assert(ps);
	assert(pos <= ps->size && pos >= 0);
	SLCheckCapacity(ps);
 	for (int i = ps->size; i > pos; i--){
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
};

3.完成删除数据函数

 还是上图的顺序表,如果我们要删除下标为5位置上的数据,我们需要将后面的数据向前挪动一位.

void SLErase(SL* ps, int pos) {//删除指定位置的数据
	assert(ps);
	assert(ps->size > pos && pos >= 0);
	while (pos < ps->size) {
		ps->a[pos] = ps->a[pos + 1];
		pos++;
	}
	ps->size--;
};

这里之所以要断言是因为要删除的下标必须要小于有效数据的个数,并且大于等于0,不然我们删除的就不是有效数据,这是没有意义的,而且可能会非法访问,出现越界的情况.

不要忘了结束要size--;

4.查找函数

int SLFind(SL* ps, SLDataType x);

x就是我们需要查找的数据,如果找到了函数返回下标,否则返回-1;

我们只需要遍历一遍顺序表即可完成。非常简单。

int SLFind(SL* ps, SLDataType x) {//查找
	assert(ps);
	for (int i = 0; i < ps->size; i++) { 
		if (ps->a[i] == x) {
			return i;
		}
	}
	return -1;
};

5.头插/删,尾插/删 

这个是基于我们前面写的指定位置插入和删除函数写的,这里就不过多赘述。 

void SLPushBack(SL* ps, SLDataType x) {
	SLInsert(ps,ps->size,x);
};
void SLPopBack(SL* ps) {
	SLErase(ps,ps->size-1);
};
void SLPushFront(SL* ps, SLDataType x) {
	SLInsert(ps,0,x);
};
void SLPopFront(SL* ps) {
	SLErase(ps,0);
};

这样写其实是有好处的,更加便捷,而且不用考虑顺序表是否为空等其他情况。建议读者自行完成这四个函数,不使用指定位置的函数做跳板,可以更加理解顺序表。 

6.最后就是销毁顺序表

void SLDestroy(SL* ps) {//销毁顺序表
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
};

ps->a是我们动态开辟的内存,所以最后需要释放掉,并且将指针置为NULL,避免非法访问的情况,最后再将有效数据个数和顺序表容量置为0即可。

4.通讯录

学完了顺序表,我们就可尝试使用顺序表完成一个简单的通讯录了,这个我并没有讲解,因为比较简单,会了顺序表的同学都可以看懂。 以下是代码实现:

//SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include"contact.h"
#define INIT_CAPACITY 4
typedef PeoInfo SLDataType;
// 动态顺序表 -- 按需申请
typedef struct SeqList
{
	SLDataType* a;
	int size; // 有效数据个数
	int capacity; // 空间容量
}SL;
//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
//void SLPrint(SL* ps);
//扩容
void SLCheckCapacity(SL* ps);
//头部插⼊删除 / 尾部插⼊删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
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 
#include"SeqList.h"
//typedef struct SeqList
//{
//	SLDataType* a;
//	int size; // 有效数据个数
//	int capacity; // 空间容量
//}SL;
void SLInit(SL* ps) {//初始化
	ps->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);
	ps->size = 0;
	ps->capacity = 4;
};
void SLDestroy(SL* ps) {//销毁顺序表
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->size = 0;
};
//void SLPrint(SL* ps) {//打印顺序表
//	assert(ps);
//	int i = 0;
//	for (i = 0; i < ps->size; i++) {
//		printf("%d->",ps->a[i]);
//	}
//	printf("\n");
//};
void SLCheckCapacity(SL* ps) {//检测空间大小
	assert(ps);
	if (ps->capacity == ps->size) {
		SLDataType* tmp = realloc(ps->a,sizeof(SLDataType) * ps->capacity * 2);
		if (tmp == NULL) {
			perror("realloc");
			exit(1);
		}
			ps->a = tmp;
			ps->capacity *= 2;
	}
};
void SLInsert(SL* ps, int pos, SLDataType x) {//在指定位置之前插入
	assert(ps);
	assert(pos <= ps->size && pos >= 0);
	SLCheckCapacity(ps);
 	for (int i = ps->size; i>pos; i--){
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
};

void SLErase(SL* ps, int pos) {//删除指定位置的数据
	assert(ps);
	assert(ps->size > pos);
	while (pos < ps->size) {
		ps->a[pos] = ps->a[pos + 1];
		pos++;
	}
	ps->size--;
};
//int SLFind(SL* ps, SLDataType x) {//查找
//	assert(ps);
//	for (int i = 0; i < ps->size; i++) { 
//		if (ps->a->name == x.name) {
//			return i;
//		}
//	}
//};

void SLPushBack(SL* ps, SLDataType x) {
	SLInsert(ps,ps->size,x);
};
void SLPopBack(SL* ps) {
	SLErase(ps,ps->size-1);
};
void SLPushFront(SL* ps, SLDataType x) {
	SLInsert(ps,0,x);
};
void SLPopFront(SL* ps) {
	SLErase(ps,0);
};

 

//contact.h

#define _CRT_SECURE_NO_WARNINGS 

#pragma once
#define NAME_MAX 20
#define SEX_MAX 4
#define TEL_MAX 20
#define ADDR_MAX 100

typedef struct SeqList contact;
typedef struct PersonInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}PeoInfo;

//初始化通讯录
void InitContact(contact* con);
//添加通讯录数据
void AddContact(contact* con);
//删除通讯录数据
void DelContact(contact* con);
//展⽰通讯录数据
void ShowContact(contact* con);
//查找通讯录数据
void FindContact(contact* con);
//修改通讯录数据
void ModifyContact(contact* con);
//销毁通讯录数据
void DestroyContact(contact* con);
//contact.c
#define _CRT_SECURE_NO_WARNINGS 
#include"SeqList.h"
#include<string.h>
//初始化通讯录
//typedef struct PersonInfo
//{
//	char name[NAME_MAX];
//	char sex[SEX_MAX];
//	int age;
//	char tel[TEL_MAX];
//	char addr[ADDR_MAX];
//}PeoInfo;
void loadContact(contact*con) {
	FILE* pf = fopen("contact.txt","rb");
	if (pf == NULL) {
		perror("fopen");
		return;
	}
	PeoInfo p;
	while (fread(&p, sizeof(p), 1, pf)) {
		SLPushBack(con,p);
	}
};


void InitContact(contact* con) {//初始化通讯录

	SLInit(con);
	loadContact(con);
};
//添加通讯录数据
void AddContact(contact* con) {
	PeoInfo person;
	printf("请输入联系人的姓名:\n");
	scanf("%s", person.name);

	printf("请输入联系人的性别:\n");
	scanf("%s", person.sex);

	printf("请输入联系人的年龄:\n");
	scanf("%d",&(person.age));

	printf("请输入联系人的电话:\n");
	scanf("%s", person.tel);

	printf("请输入联系人的地址:\n");
	scanf("%s", person.addr);

	SLPushBack(con, person);
	printf("添加成功\n");
};

//查找联系人是否存在
int findbyname(contact *con, char* name) {
	for (int i = 0; i < con->size; i++) {
		if (0 == strcmp(con->a[i].name, name)) {
			return i;
		}
	}
	return -1;
}

//删除通讯录数据
void DelContact(contact* con) {
	char name[NAME_MAX];
	printf("请输入要删除的联系人的名字:\n");
	scanf("%s",name);
	int pos = findbyname(con, name);
	if (pos >= 0) {
		SLErase(con,pos);
		printf("删除成功!!!\n");
	}
	else {
		printf("要删除的联系人不存在!!!\n");
		return;
	}
};
//展⽰通讯录数据
void ShowContact(contact* con) {
	for (int i = 0; i < con->size; i++) {
		printf("%-20s %-4s %-3d %-20s %-100s\n",con->a[i].name,\
			con->a[i].sex,\
			con->a[i].age,\
			con->a[i].tel,\
			con->a[i].addr);\
	}
};
//查找通讯录数据
void FindContact(contact* con) {
	char name[NAME_MAX];
	printf("请输入要查找的联系人的名字:\n");
	scanf("%s", name);
	int pos = findbyname(con, name);
	if (pos >= 0) {
		printf("查找成功!!!\n");
		printf("%-20s %-4s %-3d %-20s %-100s\n", con->a[pos].name, \
			con->a[pos].sex, \
			con->a[pos].age, \
			con->a[pos].tel, \
			con->a[pos].addr); \
	}
	else {
		printf("该联系人不存在!!!\n");
	}
}
//修改通讯录数据
void ModifyContact(contact* con) {
	char name[NAME_MAX];
	printf("请输入要修改的联系人的名字:\n");
	scanf("%s", name);
	int pos = findbyname(con, name);
	if (pos >= 0) {
		printf("请输入联系人的姓名:\n");
		scanf("%s", con->a[pos].name);

		printf("请输入联系人的性别:\n");
		scanf("%s", con->a[pos].sex);

		printf("请输入联系人的年龄:\n");
		scanf("%d", &(con->a[pos].age));

		printf("请输入联系人的电话:\n");
		scanf("%s", con->a[pos].tel);

		printf("请输入联系人的地址:\n");
		scanf("%s", con->a[pos].addr);
		printf("修改成功!\n");
	}
	else {
		printf("该联系人不存在!!!,修改失败!!!\n");
		
	}
};

void SaveContact(contact* con) {
	FILE* pf = fopen("contact.txt","wb");
	if (pf == NULL) {
		perror("fopen\n");
		return;
	}
	//可以一起性读取完
	fwrite(con->a, sizeof(PeoInfo), con->size, pf);
	//for (int i = 0; i < con->size; i++) {
	//	fwrite(con->a + i,sizeof(PeoInfo),1,pf);
	//}
	printf("数据保存成功!!!\n");
}

//销毁通讯录数据
void DestroyContact(contact* con) {
	SaveContact(con);
	SLDestroy(con);
};

 

//test.c
#define _CRT_SECURE_NO_WARNINGS 
#include"SeqList.h"
#include<windows.h>
//void test1() {//测试
//	SL ps;
//	SLInit(&ps);
//	SLPushBack(&ps,1);
//	SLPushBack(&ps,2);
//	SLPushBack(&ps,3);
//
//
//	SLPushFront(&ps,4);
//	SLPushFront(&ps,5);
//	SLPushFront(&ps, 2); 
//	SLPrint(&ps);
//	int find = SLFind(&ps,2);
//	SLErase(&ps,find);
//	SLPrint(&ps);
//};
void menu()
{
	contact con;
	InitContact(&con);
	int choice = -1;
	do {
		printf("---------------------------------------------\n");
		printf("--------1.添加联系人 2.删除联系人------------\n");
		printf("--------3.查找联系人 4.修改联系人------------\n");
		printf("-----------5.展示所有联系人信息--------------\n");
		printf("----------------0.退出-----------------------\n");
		printf("---------------------------------------------\n");
		printf("输入选项:\n");
		scanf("%d",&choice);
		system("cls");
		switch(choice){
	
		case 1:
			AddContact(&con);
			break;
		case 2:
			DelContact(&con);
			break;
		case 3:
			FindContact(&con);
			break;
		case 4:
			ModifyContact(&con);
			break;
		case 5:
			ShowContact(&con);
			break;
		default:
			printf("输入有误,请重新输入!!!\n");
			break;
		};
		
	} while (choice);
	DestroyContact(&con);

};

int main()
{
	//test1();
	menu();

	return 0;
}

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

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

相关文章

体验Docker快速部署微信机器人

找到一个 Docker 部署微信机器人的镜像&#xff0c;简单测试一下。 1 使用 Docker 部署 1.1 拉取最新镜像 docker pull dannicool/docker-wechatbot-webhook1.2 Docker 部署 docker run -d --name wxBotWebhook -p 3001:3001 \ -v ~/wxBot_logs:/app/log \ dannicool/docker…

『VUE』11. 操作数组的方法(详细图文注释)

目录 vue中操作数组的方法会修改原数组的 会进行渲染更新不修改原数组的 不会进行渲染更新 push自动渲染concat 赋值渲染总结 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 欢迎关注 『VUE』 专栏&#xff0c;持续更新中 vue中操作数组的方法 vue中数组数据呈现在网页,只检测…

【数据结构】-- 单链表 vs 双向链表

&#x1f308; 个人主页&#xff1a;白子寰 &#x1f525; 分类专栏&#xff1a;python从入门到精通&#xff0c;魔法指针&#xff0c;进阶C&#xff0c;C语言&#xff0c;C语言题集&#xff0c;C语言实现游戏&#x1f448; 希望得到您的订阅和支持~ &#x1f4a1; 坚持创作博文…

珠海华发实业股份有限公司副总毛冰清莅临天府锋巢直播产业基地考察调研

3月19日&#xff0c;珠海华发实业股份有限公司副总毛冰清拜访天府锋巢直播产业基地&#xff08;以下简称天府锋巢&#xff09;&#xff0c;由产业招商总负责人姜国东进行接待。 基地建设情况 姜国东负责人介绍到&#xff0c;天府锋巢是由德商产投携手无锋科技于兴隆湖落地的成都…

从误差分解看Few-shot的核心问题

FSL训练过程一般都是最小化经验误差ERM。 同时&#xff0c;由于现实任务的实际数据分布 是未知的&#xff0c;因此无法找到一个最优的参数组合 &#xff0c;能最小化期望损失&#xff08;最小值多少也是未知的&#xff09;&#xff0c;我们能做的实际上是尽可能的去找一个参数…

基于SpringBoot + Vue实现的在线答疑系统设计与实现+毕业论文+答辩PPT

介绍 学生角色&#xff1a; 1.注册、登录功能&#xff1a;学生可以通过系统完成注册和登录操作&#xff0c;进入学生专属界面。 2.个人信息修改功能&#xff1a;学生可以查看和修改自己的个人信息&#xff0c;包括姓名、联系方式等。 3.问题发布功能&#xff1a;学生可以在线发…

功能测试如何到自动化测试,看这篇就够了。

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 关注公众号&#xff1a;互联网杂货铺&#xff0c;回复1 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;薪资嘎嘎涨 本帖不仅给大家介绍自动化测试&#xff0c;更会提供大…

MySQL-10. 存储引擎、视图、mysql管理

10.1 存储引擎 存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。因为在关系数据库中数据的存储是以表的形式存储的&#xff0c;所以存储引擎也可以称为表类型&#xff08;即存储和操作此表的类型&#xff09;。 存储引擎(Stor…

使用MobaXterm/winTerm之类的工具连接虚拟机时报错Permission denied或者Access denied的解决办法

一、虚拟机设置&#xff1a; 1.将点击【编辑】---> 【虚拟网络编辑器】 2.添加VMnet6 1&#xff09;点击【DHCP设置】--->添加网络--->选中【仅主机模式】【将主机虚拟适配器连接到此网络】 2&#xff09;子网ip写“192.168.66.0” 3&#xff09;点击右下角【应用…

前端实现打开新标签页后,再次定位到该标签页

需求 A 页面中点击按钮可以打开新的标签页 B 并且向 B 页面发送消息数据。 当新的标签页 B 未关闭且符合同源策略时&#xff0c;再次点击按钮&#xff0c;可以自动跳转到标签页 B 并且发生消息数据。 B.html <script>window.onmessage evt > {console.log(evt.d…

xss跨站脚本攻击笔记

1 XSS跨站脚本攻击 1.1 xss跨站脚本攻击介绍 跨站脚本攻击英文全称为(Cross site Script)缩写为CSS&#xff0c;但是为了和层叠样式表(CascadingStyle Sheet)CSS区分开来&#xff0c;所以在安全领域跨站脚本攻击叫做XSS 1.2 xss跨战脚本攻击分类 第一种类型:反射型XSS 反射…

14.java openCV4.x 入门-Core之图像融合

专栏简介 &#x1f492;个人主页 &#x1f4f0;专栏目录 点击上方查看更多内容 &#x1f4d6;心灵鸡汤&#x1f4d6;我们唯一拥有的就是今天&#xff0c;唯一能把握的也是今天建议把本文当作笔记来看&#xff0c;据说专栏目录里面有相应视频&#x1f92b; &#x1f9ed;文…

每日一题(leetcode2529):正整数和负整数的最大计数--二分法

因为需要O&#xff08;logn&#xff09;的复杂度&#xff0c;所以考虑使用二分法&#xff0c;先找到负数里面的最大下标&#xff08;初始值定为-1&#xff09;&#xff0c;再找到第一个正数的下标&#xff08;初始值定为数组长度值&#xff09;。最后求出个数并进行比较即可。 …

说说你对算法中时间复杂度,空间复杂度的理解?如何计算?

一、前言 算法&#xff08;Algorithm&#xff09;是指用来操作数据、解决程序问题的一组方法。对于同一个问题&#xff0c;使用不同的算法&#xff0c;也许最终得到的结果是一样的&#xff0c;但在过程中消耗的资源和时间却会有很大的区别 衡量不同算法之间的优劣主要是通过时…

深度学习图像处理04:图像分类模型训练实战——动物分类

这篇博文不涉及理论知识&#xff0c;主要通过一个完整的深度学习模型训练流程&#xff0c;直观地了解深度学习图像分类任务。有关理论的部分&#xff0c;之前几篇博文已经涉及基础部分&#xff0c;之后也会对一些理论进行补充。 本文将结合代码&#xff0c;主要介绍三部分内容…

嵌入式应会的模电数电基础

AC/DC交直流 电压 欧姆定律 常见元器件 电阻器 并联电阻&#xff0c;增加通路&#xff0c;电阻更小&#xff0c;电流更大 串联电阻&#xff0c;电阻更大&#xff0c;电流越小 相同阻值的电阻&#xff0c;个头大小不同主要区别在功率容量、耐压能力和散热性能方面。 功率容量…

产品开发流程

产品开发流程 时间&#xff1a;2024年04月10日 作者&#xff1a;小蒋聊技术 邮箱&#xff1a;wei_wei10163.com 微信&#xff1a;wei_wei10 产品开发流程_小蒋聊技术_免费在线阅读收听下载 - 喜马拉雅欢迎收听小蒋聊技术的类最新章节声音“产品开发流程”。时间&#xff1a;…

MQ死信队列:面试题

所谓的死信队列只不过是我们自己定义的一个队列&#xff0c;注意对于这个队列只能人工干预 面试题&#xff1a;你们是如何保证消息不会丢失的 1&#xff0c;什么是死信 在RabitMQ中充当主角的就是消息&#xff0c;在不同场景下&#xff0c;消息会有不同地表现。 死信就是在…

Hugo 配置个人博客

下载 Hugo 下载地址: https://www.gohugo.org/ 或者点击百度云链接下载安装包: 链接&#xff1a;https://pan.baidu.com/s/1s2GuNuUKDjEXRzXgCvEYHQ?pwd6666 提取码&#xff1a;6666 安装 Hugo 在文件夹内建立新的文件夹, 在再内部建立两个文件夹, 分别命名为 bin 和 site.…

设计模式——2_8 策略(Strategy)

文章目录 定义图纸一个例子&#xff1a;如何切换坦克的攻击方式GameElement&#xff08;游戏元素&#xff09;TankFactory&#xff08;坦克工厂&#xff09;Tank&#xff08;坦克&#xff09; 医疗车和飞行车策略模式Behavior(行为)TankTankFactory 碎碎念策略和状态为什么我们…