初级数据结构:栈和队列

一、栈

(一)、栈的定义

栈是一种遵循后进先出(LIFO,Last In First Out)原则的数据结构。栈的主要操作包括入栈(Push)和出栈(Pop)。入栈操作是将元素添加到栈顶,这一过程中,栈顶指针上移,新元素被放置在栈顶位置;出栈操作则是移除栈顶元素,同时栈顶指针下移。此外,还可以通过获取栈顶元素(Top)操作来查看栈顶元素但不将其移除。

形象的来说,压栈操作就像堆叠盘子,一个盘子放在另一个盘子上。当你想取出盘子时,你必定会从顶部取出。如果从中间取盘子,那就有盘子打烂的风险,这也就是出栈操作。

栈也是一种线性表,在对于表达式求值和符号匹配等方面有很大用途。

如果你已经学完了顺序表,那么栈的实现对你来说轻而易举。

(二)、栈的功能

1、压栈(Push):将一个元素添加到栈顶。比如我们往弹夹里装子弹的动作就相当于入栈操作。

2、出栈(Pop):从栈顶移除一个元素。对应弹夹射击时弹出子弹的过程。

3、获取栈顶元素(Peek):获取栈顶元素,但不将其从栈中移除。这就像是我们查看弹夹最上面的子弹是什么类型,但不把它射出。

4、获取栈中有效元素个数(Size):就像我们查看弹夹中的子弹数目。

5、判断栈是否为空(IsEmpty):检查栈中是否没有元素。当弹夹里没有子弹时,就可以说栈为空。

(三)、栈的实现

对于栈来说,我们可以使用数组或者链表来实现栈。相对而言,使用数组来实现栈比用链表来实现更优。因为进行栈的压栈操作时,数组尾部插入数据的代价更小。而如果使用双向链表又过于麻烦。因此,我们对于栈的结构体定义如下:

typedef struct Stack
{
	int* data;//动态数组
	int top;//指向栈顶元素,在后续的初始化中,将其初始化为0还是-1,决定着top指向栈顶元素还是指向栈顶元素后一位
	int capicity;//容量大小

}Stack;

1.栈的初始化

和顺序表一样,我们需要先进行动态内存申请一定的空间代码如下:

void StackInit(Stack* ps)
{
    //动态内存申请4个整形空间,空间申请小一些方便检查后续扩容是否正确
	int* ptr = (int*)malloc(sizeof(int) * 4);
	if (ptr == NULL)
	{
        //判断空间是否申请成功,失败则打印错误信息
    	perror("StackInit::malloc");
		return;
	}
	ps->data = ptr;
	ps->capicity = 4;
    //我这里是让top指向栈顶元素的后一位,看自己的想法
    //这里top的指向如何会影响后续获取栈顶元素功能实现的代码
	ps->top = 0;
}

2.动态扩容

这个动态扩容由于我们使用数组来实现栈,因此动态扩容函数与顺序表基本一致,代码如下:

void Expansion(Stack* ps)
{
	assert(ps);
	if (ps->top == ps->capicity)
	{
    	printf("空间不足\n");
		int* ptr = (int*)realloc(ps->data, sizeof(int) * (ps->capicity * 2));
		if (ptr == NULL)
		{
			perror("Expansion::realloc");
			return;
		}
		ps->data = ptr;
        capicity*=2;
	}
}

3.压栈操作

实质上就是顺序表的尾插。代码如下:

void StackPush(Stack* ps)
{
	assert(ps);
    //判断是否需要扩容
	Expansion(ps);
	printf("请输入数字\n");
    //如果你的top初始化为-1,那么这里就需要先top++,再赋值
	scanf("%d", &ps->data[ps->top]);
	printf("入栈成功\n");
	ps->top++;
}

4.出栈操作

实质上就是顺序表的尾删,直接使top指针往前移一步,等下次压栈操作后,数据覆盖即可达到出栈作用,代码如下:

void StackPop(Stack* ps)
{
	assert(ps);
	if (ps->top > 0)
	{
		ps->top--;
		printf("出栈成功\n");
	}
	else
	{
		printf("栈中无元素\n");
	}
}

5.获取栈顶元素

因为我们这里top初始化为0,所以top一直指向栈顶元素后一位,如果我们想要获取栈顶元素就需要使top减一,代码如下:

int StackTop(Stack* ps)
{
	assert(ps);
	return ps->data[ps->top-1];
}

6.获取栈顶元素的有效个数

直接返回top即可,代码如下:

int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

7.检查栈是否为空

当top与初始化的top数相等时,栈就为空,代码如下:

int StackEmpty(Stack* ps)
{
	assert(ps);
	if (ps->top == 0)
	{
		return 0;
	}
	else
	{
		return ps->top;
	}
}

8.栈的销毁

和顺序表的销毁一致,先释放栈的空间,然后将指针data置空,容量也即capicity和top都置为0。代码如下:

void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->data);
	ps->data = NULL;
	ps->capicity = 0;
	ps->top = 0;
	printf("销毁成功\n");
}

至此,一个基础的栈就实现了,完整代码如下。

9.完整代码

stack.h中: 

#pragma once
#pragma warning(disable : 4996)
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef struct Stack
{
	int* data;
	int top;
	int capicity;
}Stack;
// 初始化栈 
void StackInit(Stack* ps);
//动态扩容
void Expansion(Stack* ps);
// 入栈 
void StackPush(Stack* ps);
// 出栈 
void StackPop(Stack* ps);
// 获取栈顶元素 
int StackTop(Stack* ps);
// 获取栈中有效元素个数 
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);

Fstack.c中:

#include"stack.h"
void StackInit(Stack* ps)
{
	int* ptr = (int*)malloc(sizeof(int) * 4);
	if (ptr == NULL)
	{
		perror("StackInit::malloc");
		return;
	}
	ps->data = ptr;
	ps->capicity = 4;
	ps->top = 0;
}

void Expansion(Stack* ps)
{
	assert(ps);
	if (ps->top == ps->capicity)
	{
		printf("空间不足\n");
		int* ptr = (int*)realloc(ps->data, sizeof(int) * (ps->capicity * 2));
		if (ptr == NULL)
		{
			perror("Expansion::realloc");
			return;
		}
		ps->data = ptr;
		ps->capicity*=2; 
	}
}

void StackPush(Stack* ps)
{
	assert(ps);
	Expansion(ps);
	printf("请输入数字\n");
	scanf("%d", &ps->data[ps->top]);
	printf("入栈成功\n");
	ps->top++;
}

void StackPop(Stack* ps)
{
	assert(ps);
	if (ps->top > 0)
	{
		ps->top--;
		printf("出栈成功\n");
	}
	else
	{
		printf("栈中无元素\n");
	}
}

int StackTop(Stack* ps)
{
	assert(ps);
	return ps->data[ps->top-1];
}

int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

int StackEmpty(Stack* ps)
{
	assert(ps);
	if (ps->top == 0)
	{
		return 0;
	}
	else
	{
		return ps->top;
	}
}

void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->data);
	ps->data = NULL;
	ps->capicity = 0;
	ps->top = 0;
	printf("销毁成功\n");
}

stack.h:

#include"stack.h"
Stack ps;
int main()
{
	// 初始化栈 
	StackInit(&ps);
	int a,b;
	do
	{
		printf("请输入数字\n");
		scanf("%d", &a);
		switch (a)
		{
		case 1:
			// 入栈 
			StackPush(&ps);
			break;
		case 2:
			// 出栈 
			StackPop(&ps);
			break;
		case 3:
			// 获取栈顶元素 
			b = StackTop(&ps);
			printf("栈顶元素:%d\n", b);
			break;
		case 4:
			// 获取栈中有效元素个数 
			printf("%d\n", StackSize(&ps));
			break;
		case 5:
			// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
			printf("%d\n", StackEmpty(&ps));
			break;
		case 0:
			// 销毁栈 
			StackDestroy(&ps);
			printf("退出\n");
			break;
		}
	} while (a);
	return 0;
}

二、队列

(一)、队列的定义

在数据结构的大家庭中,队列是一位独特而重要的成员。想象一下,在超市结账的队伍里,先来的顾客排在前面先结账离开,后来的顾客则依次在队尾加入等待,这就是典型的 “先进先出(FIFO,First In First Out)原则,而队列正是这种原则在计算机领域的完美体现。

队列作为一种特殊的线性表,它的操作被严格限定在两端进行。一端被称为队尾(rear),专门用于插入新元素,就像新顾客加入结账队伍的末尾;另一端是队头(front),负责删除元素,恰似排在队伍最前面的顾客完成结账后离开。这种操作受限的特性,赋予了队列先进先出的独特性质,也使得它在众多算法和实际应用中发挥着关键作用 。无论是操作系统中的任务调度、网络通信中的数据包处理,还是广度优先搜索算法中的节点遍历,队列都扮演着至关重要的角色

和栈一样,它的实现同样可以使用数组和链表来实现。因为上述我们使用了数组来实现栈,故此次队列的实现我们采用链表来实现,也即链式队列。

在链式队列中,每个节点包含数据域和指针域,数据域用于存储队列元素,指针域则指向下一个节点。队列通过两个指针来管理:头指针(front)指向链表的头节点,代表队头;尾指针(rear)指向链表的尾节点,代表队尾 。

入队操作时,创建一个新节点存储新元素,然后将其插入到链表的尾部,同时更新尾指针指向新节点;出队操作则是删除链表头部的节点,返回该节点的数据,并更新头指针指向下一个节点。比如,有一个初始为空的链式队列,当元素 3 入队时,创建一个新节点存储 3,此时头指针和尾指针都指向这个新节点 。接着元素 4 入队,创建新节点并插入到链表尾部,尾指针更新指向新节点。当出队时,删除头指针指向的节点(包含元素 3),头指针移动到下一个节点(包含元素 4) 。

链式队列的优点是不需要预先知道队列的最大容量,因为链表可以动态地分配内存,避免了顺序队列可能出现的溢出问题;而且在进行插入和删除操作时,只需要修改指针,不需要移动大量元素,效率较高。然而,链式队列也有缺点,由于每个节点都需要额外的指针域来指向下一个节点,这会占用更多的内存空间;并且链表不支持随机访问,访问特定位置的元素需要从头开始遍历链表,时间复杂度较高 。

(二)、队列的功能

1、队尾入队列

2、队头出队列

3、获取队列头部元素

4、获取队列尾部元素

5、获取队列有效元素个数

6、检查队列是否为空

(三)、队列的实现

因为链式队列需要头指针和尾指针,因此我们不能只像链表那样只用一个结构体。我们需要再使用一个结构体以使进行函数传参时更方便,故结构体的构造如下:

//节点
typedef struct QueueNode
{
	int data;
	struct QueueNode* next;
}Qnode;
typedef struct Queue
{
	//头指针
	Qnode* head;
	//尾指针
	Qnode* tail;
	//计数存储数据个数
	int size;
}Queue;

1、队列初始化

void QueueInit(Queue* q)
{
	assert(q);
	q->head = NULL;
	q->tail = NULL;
	q->size = 0;
}

因为还未存储数据,故头指针和尾指针都置空,以防止野指针出现。不要忘记将size也初始化为0;

2、队尾入队列

先看图:

如果我们是往队列中插入第一个节点,此时头指针和尾指针都指向空。我们就需要将头指针和尾指针都指向新节点,再将节点的next指针指向NULL;

而如果我们插入新节点时,队列中已有数据存储,也即有节点存在,将上述两图结合起来看,第一张代表插入新节点前,第二张代表插入新节点后。我们需要先使尾指针指向的节点的next指针指向新节点,再让尾指针指向新节点,最后再使新节点置空即可。代码如下:

void QueuePush(Queue* q)
{
	assert(q);
	//申请新节点
	Qnode* ptr = (Qnode*)malloc(sizeof(Qnode));
	if (ptr == NULL)
	{
		perror("QueuePush::malloc");
		return;
	}
	printf("请输入数字\n");
	scanf("%d", &ptr->data);
	//提前将新节点next指针置空
	ptr->next = NULL;
	//判断队列是否为空
	if (q->head == NULL && q->tail == NULL)
	{
		q->head = q->tail = ptr;
	}
	else
	{
		q->tail->next = ptr;
		q->tail = ptr;
	}
	q->size++;
}

3、队头出队列

先看代码:

void QueuePop(Queue* q)
{
	assert(q);
	//判断头指针是否为空,为空那还出什么队列
	assert(q->head);
	//先存储头指针指向的节点的下一个节点的位置
	Qnode* headnext = q->head->next;
	//释放头指针指向的节点空间
	free(q->head);
	//再让头指针指向之前存储的节点
	q->head = headnext;
	//如果队列中只有一个节点,那释放空间后,头指针是空,但
	//尾指针没有被置为空,而是处于野指针状态,因此也要将
	//尾指针置空
	if (q->head == NULL)
	{
		q->tail = NULL;
	}
	q->size--;
	printf("出队列成功\n");
}

4、获取队列头部元素

我们知道在链式队列中,链表头即是队头,链表尾即是队尾。获取队列头部元素即可以直接通过头指针获取。代码如下:

int QueueFront(Queue* q)
{
	assert(q);
	if (q->head == NULL)
	{
		printf("队列无元素\n");
		return NULL;
	}
	return q->head->data;
}

5、获取队列尾部元素

和获取队列头部元素一致,更改指针即可。代码如下:

int QueueBack(Queue* q)
{
	assert(q);
	if (q->tail == NULL)
	{
		printf("队列无元素\n");
		return NULL;
	}
	return q->tail->data;
}

6、获取队列有效元素个数


int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}

7、检查队列是否为空

为空返回0,不为空返回非零结果。

int QueueEmpty(Queue* q)
{
	assert(q);
	return q->size;
}

8、队列的销毁

队列的销毁和链表的销毁一致,遍历一遍,在遍历中存储下一个节点的位置,销毁当前节点,更新条件即可。代码如下:

void QueueDestroy(Queue* q)
{
	assert(q);
	Qnode* ptr = q->head;
	if (q->head == NULL)
	{
		return;
	}
	while (ptr)
	{
		Qnode* ptrnext = ptr->next;
		free(ptr);
		ptr = ptrnext;
	}
	q->head = q->tail = NULL;
	printf("队列销毁成功\n");
	q->size = 0;
}

至此,一个基础的队列就完成了,完整代码如下:

9、完整代码

queue.h:

#pragma once
#pragma warning(disable : 4996)
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//节点
typedef struct QueueNode
{
	int data;
	struct QueueNode* next;
}Qnode;
typedef struct Queue
{
	//头指针
	Qnode* head;
	//尾指针
	Qnode* tail;
	//计数存储数据个数
	int size;
}Queue;
// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
int QueueFront(Queue* q);
// 获取队列队尾元素 
int QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

Fqueue.c:

#include"queue.h"
void QueueInit(Queue* q)
{
	assert(q);
	q->head = NULL;
	q->tail = NULL;
	q->size = 0;
}

void QueuePush(Queue* q)
{
	assert(q);
	//申请新节点
	Qnode* ptr = (Qnode*)malloc(sizeof(Qnode));
	if (ptr == NULL)
	{
		perror("QueuePush::malloc");
		return;
	}
	printf("请输入数字\n");
	scanf("%d", &ptr->data);
	//提前将新节点next指针置空
	ptr->next = NULL;
	//判断队列是否为空
	if (q->head == NULL && q->tail == NULL)
	{
		q->head = q->tail = ptr;
	}
	else
	{
		q->tail->next = ptr;
		q->tail = ptr;
	}
	q->size++;
}

void QueuePop(Queue* q)
{
	assert(q);
	//判断头指针是否为空,为空那还出什么队列
	assert(q->head);
	//先存储头指针指向的节点的下一个节点的位置
	Qnode* headnext = q->head->next;
	//释放头指针指向的节点空间
	free(q->head);
	//再让头指针指向之前存储的节点
	q->head = headnext;
	//如果队列中只有一个节点,那释放空间后,头指针是空,但
	//尾指针没有被置为空,而是处于野指针状态,因此也要将
	//尾指针置空
	if (q->head == NULL)
	{
		q->tail = NULL;
	}
	q->size--;
	printf("出队列成功\n");
}

int QueueFront(Queue* q)
{
	assert(q);
	if (q->head == NULL)
	{
		printf("队列无元素\n");
		return NULL;
	}
	return q->head->data;
}

int QueueBack(Queue* q)
{
	assert(q);
	if (q->tail == NULL)
	{
		printf("队列无元素\n");
		return NULL;
	}
	return q->tail->data;
}

int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}

int QueueEmpty(Queue* q)
{
	assert(q);
	return q->size;
}

void QueueDestroy(Queue* q)
{
	assert(q);
	Qnode* ptr = q->head;
	if (q->head == NULL)
	{
		return;
	}
	while (ptr)
	{
		Qnode* ptrnext = ptr->next;
		free(ptr);
		ptr = ptrnext;
	}
	q->head = q->tail = NULL;
	printf("队列销毁成功\n");
	q->size = 0;
}

queue.c:

#include"queue.h"
Queue Que;
int main()
{
	int a;
	// 初始化队列 
	QueueInit(&Que);
	do
	{
		printf("输入数字\n");
		scanf("%d", &a);
		switch (a)
		{
		case 1:
			// 队尾入队列 
			QueuePush(&Que);
			break;
		case 2:
			// 队头出队列 
			QueuePop(&Que);
			break;
		case 3:
			// 获取队列头部元素 
			printf("%d\n",QueueFront(&Que));
			break;
		case 4:
			// 获取队列队尾元素 
			printf("%d\n",QueueBack(&Que));
			break;
		case 5:
			// 获取队列中有效元素个数 
			printf("%d\n",QueueSize(&Que));
			break;
		case 6:
			// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
			printf("%d\n",QueueEmpty(&Que));
			break;
		case 0:
			// 销毁队列 
			QueueDestroy(&Que);
			break;
		}
	} while (a);
	return 0;
}

(四)、循环队列

在了解顺序队列时,我们提到了假溢出问题,而循环队列就是为了解决这个问题而引入的。循环队列是一种特殊的顺序队列,它将顺序队列的首尾相连,把存储队列元素的表从逻辑上看成一个环。

在循环队列中,我们仍然使用front指针指示队头位置,rear指针指示队尾位置。当rear指针到达数组末尾时,如果数组前面还有空闲空间,它可以重新回到数组开头,继续利用前面的空闲空间。例如,假设我们有一个大小为 5 的循环队列数组,初始时front和rear都为 0 。当进行入队操作时,rear指针依次移动到 1、2、3、4 位置。当rear到达 4 时,如果再进行入队操作,rear就会回到 0 位置(通过取模运算实现)。

判断循环队列是否为空和满的条件与普通顺序队列有所不同。在循环队列中,当front等于rear时,表示队列为空;而当(rear + 1) % 队列容量 == front时,表示队列已满。这里的取模运算保证了rear指针在到达数组末尾时能够回到开头,实现循环的效果。

三、栈和队列的比较

栈和队列虽然都是线性数据结构,但它们在很多方面存在差异:

  1. 进出顺序:栈遵循后进先出(LIFO)原则,最后进入栈的元素最先出栈;而队列遵循先进先出(FIFO)原则,最先进入队列的元素最先出队 。例如,在程序调用栈中,函数调用是按照后进先出的顺序进行的,而在网络请求队列中,请求是按照先进先出的顺序被处理的。
  2. 插入删除操作:栈的插入(入栈)和删除(出栈)操作都在栈顶进行;队列的插入(入队)操作在队尾进行,删除(出队)操作在队头进行 。比如,往栈中添加元素就像往一摞盘子上放盘子,只能放在最上面,从栈中取出元素也只能从最上面取;而队列中添加元素就像排队买票,新来的人站在队伍末尾,离开的人从队伍最前面离开。
  3. 遍历数据速度:栈只能从栈顶开始遍历,若要访问栈底元素,需要依次弹出栈顶元素,遍历过程中需要开辟临时空间来保存数据状态,以确保遍历前后数据的一致性;队列基于地址指针进行遍历,可以从队头或队尾开始遍历,但不能同时进行双向遍历,遍历过程中不会改变数据结构,所以无需开辟额外空间,遍历速度相对较快 。例如,在遍历一个包含 100 个元素的栈时,如果要获取栈底元素,需要将栈顶的 99 个元素依次弹出并保存,然后才能访问栈底元素,最后再将弹出的元素依次压回栈中;而遍历一个包含 100 个元素的队列时,从队头开始遍历,直接按照顺序访问每个元素即可。
  4. 限定条件:栈只允许在一端进行插入和删除操作;队列允许在一端插入,在另一端删除操作 。这是它们最基本的操作限制,决定了它们在不同场景下的适用性。例如,在实现表达式求值时,利用栈的特性可以方便地处理运算符优先级;而在实现任务调度时,利用队列的特性可以保证任务按照提交顺序依次执行。
  5. 应用场景:栈常用于处理具有后进先出特性的问题,如函数调用、表达式求值、括号匹配等;队列常用于处理具有先进先出特性的问题,如网络请求处理、消息队列、任务调度等 。例如,在编译器中,使用栈来处理函数调用和递归,确保函数的正确返回和局部变量的正确管理;在分布式系统中,使用消息队列来异步处理消息,提高系统的吞吐量和响应速度。

如果你认为你已经对栈和队列掌握完全,那你可以尝试做一下下面的题目看看:

1.有效的括号

2.用队列实现栈

3.用栈实现队列

4.设计循环队列

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构 前缀中缀后缀

目录 前言 一&#xff0c;前缀中缀后缀的基本概念 二&#xff0c;前缀与后缀表达式 三&#xff0c;使用栈实现后缀 四&#xff0c;由中缀到后缀 总结 前言 这里学习前缀中缀后缀为我们学习树和图做准备&#xff0c;这个主题主要是对于算术和逻辑表达式求值&#xff0c;这…

笔灵ai写作技术浅析(三):深度学习

笔灵AI写作的深度学习技术主要基于Transformer架构,尤其是GPT(Generative Pre-trained Transformer)系列模型。 1. Transformer架构 Transformer架构由Vaswani等人在2017年提出,是GPT系列模型的基础。它摒弃了传统的循环神经网络(RNN)和卷积神经网络(CNN),完全依赖自…

专业的定制版软件,一键操作,无限使用

今天给大家介绍一个专业的PDF转word的小软件&#xff0c;软件只有5.5M。非常小&#xff0c;而且没有文档大小的限制&#xff0c;可以随意使用。 PDFtu PDF转word 软件第一次使用需要安装一下。 安装好之后&#xff0c;我们就能在桌面找到对应的图标&#xff0c;打开就能直接使…

QGIS系列22-如何提取不规则多边形的中心经纬度

今天我们来学习一下啊如何通过QGIS提取不规则多边形的中心经纬度 1、首先我们把不规则的多边形图形导入进QGIS里面去 2、现在打开的图层是不可以编辑的&#xff0c;因此我们还需要转换成可编辑状态&#xff0c;具体是选择图层&#xff0c;右键点击&#xff0c;选择切换编辑模式…

word2vec 实战应用介绍

Word2Vec 是一种由 Google 在 2013 年推出的重要词嵌入模型,通过将单词映射为低维向量,实现了对自然语言处理任务的高效支持。其核心思想是利用深度学习技术,通过训练大量文本数据,将单词表示为稠密的向量形式,从而捕捉单词之间的语义和语法关系。以下是关于 Word2Vec 实战…

数据库安全管理中的权限控制:保护数据资产的关键措施

title: 数据库安全管理中的权限控制:保护数据资产的关键措施 date: 2025/2/2 updated: 2025/2/2 author: cmdragon excerpt: 在信息化迅速发展的今天,数据库作为关键的数据存储和管理中心,已经成为了企业营运和决策的核心所在。然而,伴随着数据规模的不断扩大和数据价值…

【漫话机器学习系列】076.合页损失函数(Hinge Loss)

Hinge Loss损失函数 Hinge Loss&#xff08;合页损失&#xff09;&#xff0c;也叫做合页损失函数&#xff0c;广泛用于支持向量机&#xff08;SVM&#xff09;等分类模型的训练过程中。它主要用于二分类问题&#xff0c;尤其是支持向量机中的优化目标函数。 定义与公式 对于…

openmv的端口被拆分为两个 导致电脑无法访问openmv文件系统解决办法 openmv USB功能改动 openmv驱动被更改如何修复

我之前误打误撞遇到一次&#xff0c;直接把openmv的全部端口删除卸载然后重新插上就会自动重新装上一个openmv端口修复成功&#xff0c;大家可以先试试不行再用下面的方法 全部卸载再重新插拔openmv 要解决OpenMV IDE中出现的两个端口问题&#xff0c;可以尝试以下步骤&#x…

洛谷P1403 [AHOI2005] 约数研究

题目链接&#xff1a;P1403 [AHOI2005] 约数研究 - 洛谷 | 计算机科学教育新生态 题目难度&#xff1a;普及一 题目分析&#xff1a;本题很明显是要你求从i到n的质因数个数之和&#xff0c;如果采用暴力肯定是超时的&#xff0c;故我的想法是采用埃氏筛法来求时间复杂度为&…

elasticsearch8.15 高可用集群搭建(含认证Kibana)

文章目录 1.资源配置2.系统参数优化3.JDK17安装4.下载&安装ES 8.155.生成ES的证书(用于ES节点之间进行安全数据传输)6.修改ES 相关配置文件7.创建es用户并启动8.配置ES的账号和密码(用于ES服务端和客户端)9.下载和安装Kibana10.编辑Kibana配置文件11.启动Kiabana12.访问Kia…

MATLAB中的IIR滤波器设计

在数字信号处理中&#xff0c;滤波器是消除噪声、提取特征或调整信号频率的核心工具。其中&#xff0c;无限脉冲响应&#xff08;IIR&#xff09;滤波器因其低阶数实现陡峭滚降的特性&#xff0c;被广泛应用于音频处理、通信系统和生物医学工程等领域。借助MATLAB强大的工具箱&…

数据结构:优先级队列—堆

一、优先级队列 1、优先级队列概念 优先级队列&#xff0c;听名字我们就知道他是一种队列&#xff0c;队列在前面我们已经学习过了&#xff0c;它是一种先进先出的数据结构&#xff0c;但是在特殊的情况下&#xff0c;我们我们队列中元素是带有一定优先级的&#xff0c;它需要…

北大:三阶段学习优化多模态推理问答

&#x1f4d6;标题&#xff1a;ReasVQA: Advancing VideoQA with Imperfect Reasoning Process &#x1f310;来源&#xff1a;arXiv, 2501.13536 &#x1f31f;摘要 &#x1f538;视频问答&#xff08;VideoQA&#xff09;是一项具有挑战性的任务&#xff0c;需要理解视频中…

从零开始:用Qt开发一个功能强大的文本编辑器——WPS项目全解析

文章目录 引言项目功能介绍1. **文件操作**2. **文本编辑功能**3. **撤销与重做**4. **剪切、复制与粘贴**5. **文本查找与替换**6. **打印功能**7. **打印预览**8. **设置字体颜色**9. **设置字号**10. **设置字体**11. **左对齐**12. **右对齐**13. **居中对齐**14. **两侧对…

Jason配置环境变量

jason官网 https://jason-lang.github.io/ https://github.com/jason-lang/jason/releases 步骤 安装 Java 21 或更高版本 安装 Visual Studio Code 根据操作系统&#xff0c;请按照以下具体步骤操作 视窗 下载 Jason 的最新版本&#xff0c;选择“jason-bin-3.3.0.zip”…

机器学习--概览

一、机器学习基础概念 1. 定义 机器学习&#xff08;Machine Learning, ML&#xff09;&#xff1a;通过算法让计算机从数据中自动学习规律&#xff0c;并利用学习到的模型进行预测或决策&#xff0c;而无需显式编程。 2. 与编程的区别 传统编程机器学习输入&#xff1a;规…

如何使用SliverGrid组件

文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了SliverList组件相关的内容&#xff0c;本章回中将介绍SliverGrid组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 概念介绍 我们在本章回中介绍的SliverGrid组件是一种网格类组件&#xff0c;主要用来…

大模型培训讲师老师叶梓分享:DeepSeek多模态大模型janus初探

以下视频内容为叶梓分享DeepSeek多模态大模型janus的部署&#xff0c;并验证其实际效果&#xff0c;包括图生文和文生图两部分。 叶梓老师人工智能培训分享DeepSeek多模态大模型janus初探 DeepSeek 的多模态大模型 Janus 是一款强大的 AI 模型&#xff0c;专注于图像和文本的多…

一文掌握ADB的安装及使用

文章目录 一、什么是ADB&#xff1f;二、 安装ADB2.1 下载ADB2.2 配置环境变量 三、连接Android设备四、 常用ADB命令五、ADB高级功能5.1 屏幕截图和录制5.2 模拟按键输入5.3 文件管理5.4 系统设置管理5.5 系统操作指令5.6 日志操作指令5.7 APK操作指令5.8 设备重启和恢复 六、…

【机器学习与数据挖掘实战】案例11:基于灰色预测和SVR的企业所得税预测分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈机器学习与数据挖掘实战 ⌋ ⌋ ⌋ 机器学习是人工智能的一个分支,专注于让计算机系统通过数据学习和改进。它利用统计和计算方法,使模型能够从数据中自动提取特征并做出预测或决策。数据挖掘则是从大型数据集中发现模式、关联…