速通数据结构与算法第五站 栈队列

系列文章目录

速通数据结构与算法系列

1   速通数据结构与算法第一站 复杂度          http://t.csdnimg.cn/sxEGF

2   速通数据结构与算法第二站 顺序表          http://t.csdnimg.cn/WVyDb

3   速通数据结构与算法第三站 单链表          http://t.csdnimg.cn/cDpcC

4   速通数据结构与算法第四站 双链表          http://t.csdnimg.cn/LCO0A

感谢佬们支持!


目录

系列文章目录

  • 前言
  • 一、栈
  •     1 结构体
  •     2 接口声明
  •     3
  • 二、队列
  • 三、OJ题
  •     1 有效的括号
  •     2 栈实现队列
  •     3 队列实现栈
  •     4 设计循环队列
  • 总结

前言

   前几篇博客我们学习了顺序表和链表,这篇博客我们要学习的是栈和队列,按底层来说,他们并不算是一种真正的新的容器,而是以底层容器完成其所有工作,具有"修改某物接口,形成另一种风貌"之性质者,称为adapter(配接器) (以上摘自《STL源码剖析》),换言之,栈和队列的

底层是顺序表或链表等容器。(虽然但是,deque由于其O(1)的头插头删尾插尾删使得其在SGI版本中成为了栈和队列的默认底层容器)


一、栈

只允许在一端进行插入和删除操作的特殊的线性表。进行插入和删除的一端叫栈顶,另一端叫栈底。栈中的元素满足FILO(first in last out)模式

有趣的是,根据出栈顺序的不同 ,我们可以得到不同的序列

比如入栈顺序是1,2,3,4,5

1 我们可以得到出栈顺序为5,4,3,2,1 即一次全部入栈后再全部出栈

2 我们可以得到出栈顺序为1,2,3,4,5 即每个元素入栈后立刻出栈

3 ……剩下的序列大家可以自己试试~


显然,由于我们进栈出栈在一端,我们可以用数组的尾插尾删模拟

先给一个结构体

结构体
typedef int STDataType;

typedef struct stack
{
	STDataType* a;
	int capacity;
	int top;//栈顶的下一个位置

}ST;

此处的top指向的是栈顶的下一个位置,为什么呢?

如果我们开始top给0,而此时栈里是没有元素的此时我们就不能说0是栈顶元素所在位置了


接口声明

栈的接口就相对少了

//初始化
void STInit(ST* ps);

//销毁
void STDestroy(ST* ps);

//入栈
void STPush(ST* ps, STDataType x);

//出栈
void STPop(ST* ps);

//判空
bool STEmpty(ST* ps);

//栈的大小
int STSize(ST* ps);

//返回栈顶元素
STDataType STTop(ST* ps);

初始化

开始给4个空间即可

//初始化
void STInit(ST* ps)
{
	assert(ps);

	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);//刚开始给4个
	if (ps->a == NULL)
	{
		perror("malloc\n");
		exit(-1);
	}
	ps->capacity = 4;
	ps->top = 0;//top给0表示他是栈顶元素的下一个位置
}

销毁
//销毁
void STDestroy(ST* ps)
{
	ps->capacity = ps->top = 0;

	free(ps->a);
	ps->a = NULL;
}

下来是入栈的工作了,我们直接用数组的尾插模拟即可

入栈
//入栈
void STPush(ST* ps, STDataType x)
{
	//考虑扩容
	if (ps->capacity == ps->top && (!STEmpty(ps)))
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * 2 * ps->capacity);
		if (tmp == NULL)
		{
			perror("reolloc\n");
			exit(-2);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->top++] = x;
}

(考虑扩容即可)


出栈
//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//空了就不能出栈了

	ps->top--;
}

判空
//判空
bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}

size

由于我们的top是指向栈顶元素的下一个,所以占栈的大小我们直接返回top即可

//栈的大小
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

返回栈顶

由于我们的top是指向栈顶元素的下一个,所以返回时要减1

//返回栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));

	return ps->a[ps->top - 1];
}

我们可以简单的做波测试

注意:由于栈不支持遍历,我们只能通过拿到栈顶再pop的做法遍历

ST ps;
	STInit(&ps);
	STPush(&ps, 1);
	STPush(&ps, 2);
	STPush(&ps, 3);
	STPush(&ps, 4);
	STPush(&ps, 8);


	while (!STEmpty(&ps))
	{
		int top = STTop(&ps);
		printf("%d ", top);
		STPop(&ps);
	}

(非常奈斯)


完整代码

stack.h

#pragma once 

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

typedef int STDataType;

typedef struct stack
{
	STDataType* a;
	int capacity;
	int top;//栈顶的下一个位置

}ST;

//初始化
void STInit(ST* ps);

//销毁
void STDestroy(ST* ps);

//入栈
void STPush(ST* ps, STDataType x);

//出栈
void STPop(ST* ps);

//判空
bool STEmpty(ST* ps);

//栈的大小
int STSize(ST* ps);

//返回栈顶元素
STDataType STTop(ST* ps);

stack.c

#include"stack.h"

//初始化
void STInit(ST* ps)
{
	assert(ps);

	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);//刚开始给4个
	if (ps->a == NULL)
	{
		perror("malloc\n");
		exit(-1);
	}
	ps->capacity = 4;
	ps->top = 0;//top给0表示他是栈顶元素的下一个位置
}

//销毁
void STDestroy(ST* ps)
{
	ps->capacity = ps->top = 0;

	free(ps->a);
	ps->a = NULL;
}

//入栈
void STPush(ST* ps, STDataType x)
{
	//考虑扩容
	if (ps->capacity == ps->top && (!STEmpty(ps)))
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * 2 * ps->capacity);
		if (tmp == NULL)
		{
			perror("reolloc\n");
			exit(-2);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}
	ps->a[ps->top++] = x;
}


//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//空了就不能出栈了

	ps->top--;
}

//判空
bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}


//栈的大小
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

//返回栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));

	return ps->a[ps->top - 1];
}

test.c

#include"stack.h"
int main()
{
	ST ps;
	STInit(&ps);
	STPush(&ps, 1);
	STPush(&ps, 2);
	STPush(&ps, 3);
	STPush(&ps, 4);
	STPush(&ps, 8);


	while (!STEmpty(&ps))
	{
		int top = STTop(&ps);
		printf("%d ", top);
		STPop(&ps);
	}

	return 0;
}

二、队列

只允许在一端插入,另一端删除的特殊线性结构,具有FIFO(first in first out)的性质
入队列的一端叫队头

出队列的一端叫队尾

与栈不同,栈在边入边出时顺序会有所变化,队列没有变化


由于顺序表的头插不那么方便,我们可以用单链表实现,为了方便找尾,我们可以增加一个尾指针作为成员,就像之前OJ题我们做的那样

队列的结构体
typedef struct Queue
{
	QNode* head;
	QNode* tail;//为了方便找尾定义的
	int size;
}Queue;
队列节点的结构体

队列的每个节点其实就是单链表的节点

typedef struct QueueNode
{
	QDatatype data;
	struct QueueNode* next;
}QNode;

接口声明

下来看看我们的接口

//初始化
void QueueInit(Queue* pq);

//销毁
void QueueDestroy(Queue* pq);

//入栈
void QueuePush(Queue* pq, QDatatype x);

//出栈
void QueuePop(Queue* pq);

//大小
int QueueSize(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);

//返回队头
QDatatype QueueFront(Queue* pq);

//返回队尾
QDatatype QueueBack(Queue* pq);


初始化
// 初始化
void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

销毁

和链表的销毁一样,我们只需提前保存下一个,再销毁即可,最后给头尾置空即可

//销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

入队列

入队列实际上就是单链表的尾插,由于我们有尾指针,尾插其实还是很方便的

//入队列
void QueuePush(Queue* pq, QDatatype x)
{
	assert(pq);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//第一次插入
	if (pq->head==NULL)
	{
		assert(pq->tail == NULL);//如果head==NULL但tail!=空说明出事了

		pq->head=pq->tail=newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

出队列

出队列是单链表的头删,我们只需额外考虑只有一个节点的时候即可

//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	QNode* cur = pq->head;

	if (pq->head->next == NULL)//考虑还剩一个的情况
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

size
//大小
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

判空
//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->head == NULL;
}

返回队头

显然,当队列为空时我们不能返回队首,队尾

//返回队头
QDatatype QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}
返回队尾
//返回队尾
QDatatype QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

我们再简单的做一波测试

Queue q;
	QueueInit(&q);

	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	
	int back = QueueFront(&q);
	int front = QueueBack(&q);

	printf("%d ", back);
	QueueDestroy(&q);


完整代码

queue.h

#pragma once

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

typedef int QDatatype;

typedef struct QueueNode
{
	QDatatype data;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;//为了方便找尾定义的
	int size;
}Queue;

//初始化
void QueueInit(Queue* pq);

//销毁
void QueueDestroy(Queue* pq);

//入栈
void QueuePush(Queue* pq, QDatatype x);

//出栈
void QueuePop(Queue* pq);

//大小
int QueueSize(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);

//返回队头
QDatatype QueueFront(Queue* pq);

//返回队尾
QDatatype QueueBack(Queue* pq);

queue.c

#include"queue.h"

// 初始化
void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

//销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

//入队列
void QueuePush(Queue* pq, QDatatype x)
{
	assert(pq);

	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//第一次插入
	if (pq->head==NULL)
	{
		assert(pq->tail == NULL);//如果head==NULL但tail!=空说明出事了

		pq->head=pq->tail=newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

//出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);

	QNode* cur = pq->head;

	if (pq->head->next == NULL)//考虑还剩一个的情况
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

//大小
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->head == NULL;
}

//返回队头
QDatatype QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

//返回队尾
QDatatype QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

test.c

#include"queue.h"

void test1()
{
	Queue q;
	QueueInit(&q);

	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	
	int back = QueueFront(&q);
	int front = QueueBack(&q);

	printf("front:%d back:%d", front,back);
	QueueDestroy(&q);
}

int main()
{
	test1();
}

三、OJ题

有关栈和队列的几道OJ题并不难,只是为了让大家巩固所学的性质


有效的括号

题目链接:. - 力扣(LeetCode)

这里有的同学可能想到了数括号个数的方式,但是这没法保证左括号能以正确的顺序闭合

最好的方式其实是用栈,如果是左括号,我们就入栈,如果是右括号,我们就出栈匹配有一个不匹配说明这个s寄了

力扣的用例是很恶心的,我们可能会碰到只有左括号或只有又括号的用例

所以最后我们判断的标准是栈是否为空,如果为空,说明是有效的

代码还是很简单的,首先把我们上面写好的栈拷一份过去,然后再操作

bool isValid(char* s)
{
	ST st;
	STInit(&st);
	while (*s)
	{
		//左括号入栈,右括号匹配
		if (*s == '(' || *s == '[' || *s == '{')
		{
			STPush(&st, *s);
		}
		else//右括号就出栈匹配
		{
			if (!(STEmpty(&st)))
			{
				char top = STTop(&st);
				STPop(&st);
				if (top == '(' && *s != ')'
					|| top == '[' && *s != ']'
					|| top == '{' && *s != '}')
				{
					STDestroy(&st);
					return false;
				}
			}
			else
			{
				return false;
			}
		}
		s++;
	}
	int ret = STEmpty(&st);
	STDestroy(&st);
	return (ret);//最后不为空就是false
}

最后在返回的时候注意销毁就好啦~


队列实现栈

题目链接: . - 力扣(LeetCode)

队列实现栈,我们可以先模拟一下,假设在队列1push了1,2,3,4;现在我们想pop,由于是栈的行为,所以我们要pop的是4;但是现在队列1只能pop1怎么办?

可以将1,2,3都导至队列2,此时就可以pop1啦~

-》

现在我要再pop3怎么办?将1,2导至另一个队列

那我要push7,8,往哪个队列里push?显然是往非空的队列里push

另外:如何判空?显然当两个队列都为空时,我们模拟的栈为空

模拟出来以后就好办了,我们就能上手写代码啦~(别忘了将我们写的队列拷过来)
 

typedef struct
{
	Queue q1, q2;//成员:两个队列
} MyStack;


MyStack* myStackCreate()
{
	MyStack* pst = (MyStack*)malloc(sizeof(MyStack));

	QueueInit(&pst->q1);
	QueueInit(&pst->q2);

	return pst;
}

void myStackPush(MyStack* obj, int x)
{
	if (QueueEmpty(&obj->q1))
	{
		QueuePush(&obj->q2, x);
	}
	else
	{
		QueuePush(&obj->q1, x);
	}
}

int myStackPop(MyStack* obj)
{
	//杭の经典操作
	Queue* emptyq = &obj->q1;
	Queue* noemptyq = &obj->q2;
	if (QueueEmpty(&obj->q2))
	{
		emptyq = &obj->q2;
		noemptyq = &obj->q1;
	}

	//倒数据
	while (QueueSize(noemptyq) > 1)
	{
		int val = QueueFront(noemptyq);
		QueuePop(noemptyq);
		QueuePush(emptyq, val);
	}
	int ret = QueueFront(noemptyq);
	QueuePop(noemptyq);
	return ret;
}

int myStackTop(MyStack* obj)
{
	if (QueueEmpty(&obj->q1))
	{
		return QueueBack(&obj->q2);
	}
	else
	{
		return QueueBack(&obj->q1);
	}
}

bool myStackEmpty(MyStack* obj)
{
	return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj)
{
	QueueDestroy(&obj->q1);
	QueueDestroy(&obj->q2);

	free(obj);
}

/**
 * Your MyStack struct will be instantiated and called as such:
 * MyStack* obj = myStackCreate();
 * myStackPush(obj, x);

 * int param_2 = myStackPop(obj);

 * int param_3 = myStackTop(obj);

 * bool param_4 = myStackEmpty(obj);

 * myStackFree(obj);
*/

栈实现队列

题目链接:. - 力扣(LeetCode)

我们还是采用模拟的思路画图

push个1,2,3,4先,此时我们要pop1,所以我们还是老样子,把1,2,3,4拷过去

-》

此时我们再pop,就是pop2了,然后我们会发现,数据倒过来后我们pop2就不用再往过倒了

这确实挺不错的。

那再插往哪插呢?直接往已经空了的栈1插就行。

如果连续pop后,栈二空了,我们再从栈一倒就行。

最后我们发现:左边的栈纯纯用来push,而右边的纯纯用来pop,所以我们直接起名为popS和pushS

代码启动!

typedef struct
{
	ST pushS;
	ST popS;
} MyQueue;


MyQueue* myQueueCreate()
{
	MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));

	STInit(&obj->pushS);
	STInit(&obj->popS);

	return obj;
}

void myQueuePush(MyQueue* obj, int x)
{
	STPush(&obj->pushS, x);
}

int myQueuePeek(MyQueue* obj)
{
	if (STEmpty(&obj->popS))//pop栈为空就往过导
	{
		while (!STEmpty(&obj->pushS))
		{
			STPush(&obj->popS, (STTop(&obj->pushS)));
			STPop(&obj->pushS);
		}
	}
	int ret = STTop(&obj->popS);
	return ret;
}

int myQueuePop(MyQueue* obj)
{
	int ret = myQueuePeek(obj);
	STPop(&obj->popS);
	return ret;
}



bool myQueueEmpty(MyQueue* obj)
{
	return STEmpty(&obj->pushS) && STEmpty(&obj->popS);
}

void myQueueFree(MyQueue* obj)
{
	STDestroy(&obj->pushS);
	STDestroy(&obj->popS);

	free(obj);
	obj = NULL;
}

/**
 * Your MyQueue struct will be instantiated and called as such:
 * MyQueue* obj = myQueueCreate();
 * myQueuePush(obj, x);

 * int param_2 = myQueuePop(obj);

 * int param_3 = myQueuePeek(obj);

 * bool param_4 = myQueueEmpty(obj);

 * myQueueFree(obj);
*/

设计循环队列

题目链接: . - 力扣(LexetCode)

循环队列的核心是循环,也就是当他走到了尾,他会再回到头

如果不用循环链表,其实我们可以用数组+%capacity的做法实现循环。

由于要支持取队首和队尾,所以我们需要一个头指针front以及一个尾指针back

头指针指向首个元素而尾指针指向尾部元素的下一个

这个时候有意思的就来了

显然刚开始front和back均指向同一个位置0,但是如果队列满了,他们指向的还是同一个位置

就像下图这样~


此时我们有两个办法

1 我们增加一个成员size,让size来判断是否为满

//size法

class MyCircularQueue {
public:
    MyCircularQueue(int k) : capacity(k) { nums.resize(k); }

    bool enQueue(int value) {
        if (isFull())
            return false;

        nums[back] = value;
        back = (back + 1) % capacity;
        size++;
        return true;
    }

    bool deQueue() {
        if (isEmpty())
            return false;

        front = (front + 1) % capacity;
        size--;
        return true;
    }

    int Front() {
        if (isEmpty())
        {
            return -1;
        }
        return nums[front];

    }

    int Rear() {
        if (isEmpty()) {
            return -1;
        }
        return nums[(back + capacity - 1) % capacity];
    }

    bool isEmpty() { return size == 0; }

    bool isFull()
    {
        return size == capacity;
    }

    vector<int> nums;
    int capacity;
    int size = 0;//size
    int front = 0;
    int back = 0;
};

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue* obj = new MyCircularQueue(k);
 * bool param_1 = obj->enQueue(value);
 * bool param_2 = obj->deQueue();
 * int param_3 = obj->Front();
 * int param_4 = obj->Rear();
 * bool param_5 = obj->isEmpty();
 * bool param_6 = obj->isFull();
 */

2 我们可以浪费一个空间,即让nums多开一个不存数据的空间,这样front和back就错开了

而判满的核心逻辑就是(back+1)%capacity是否等于front,其他都一样

可以,我们直接上代码


//浪费空间法

class MyCircularQueue {
public:
    MyCircularQueue(int k)
        :capacity(k + 1)
    {
        nums.resize(capacity);
    }

    bool enQueue(int value)
    {
        if (isFull())
            return false;

        nums[back] = value;
        back = (back + 1) % capacity;
        return true;
    }

    bool deQueue()
    {
        if (isEmpty())
            return false;

        front = (front + 1) % capacity;

        return true;
    }

    int Front()
    {
        if (isEmpty())
            return -1;

        return nums[front];
    }

    int Rear()
    {
        if (isEmpty())
            return -1;

        return nums[(back - 1 + capacity) % capacity];
    }

    bool isEmpty()
    {
        return back == front;
    }

    bool isFull()
    {
        return ((back + 1) % capacity) == front;
    }
    int front = 0;
    int back = 0;
    int capacity;//多留一个位置!
    vector<int> nums;
};

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue* obj = new MyCircularQueue(k);
 * bool param_1 = obj->enQueue(value);
 * bool param_2 = obj->deQueue();
 * int param_3 = obj->Front();
 * int param_4 = obj->Rear();
 * bool param_5 = obj->isEmpty();
 * bool param_6 = obj->isFull();
 */

总结

 做总结,这篇博客讲了栈和队列和一些相关的题目,其中循环队列将在后面大家学习生产者消费者模型中用到。下篇博客将带大家学习二叉树以及堆,这部分又是一个硬骨头,我们要面临一大堆概念,画很多图,而且还要用到递归。

水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。

每日gitee侠:今天你交gitee了嘛

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

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

相关文章

unity学习(82)——profiler 限制帧率

实际测试发现当玩家个数增加时&#xff0c;客户端明显变的很卡&#xff0c;想知道为什么变卡了&#xff01; 1.只有玩家自己的时候 2.两个时候感觉脚本的工作量增大了 拖了一会直接炸了&#xff01;&#xff08;数据包积压把内存搞炸&#xff0c;我第一次见&#xff09; 3.我觉…

Flutter 画笔(Paint)、绘制直线(drawLine)

override bool shouldRepaint(CustomPainter oldDelegate) > true; } class MyPainter extends CustomPainter { override void paint(Canvas canvas, Size size) { //画背景 var paint Paint() …isAntiAlias false …strokeWidth30.0 …color Colors.red; c…

Loss【1】:Focal Loss

系列文章目录 文章目录 系列文章目录前言1. 什么是 Focal Loss2. 逐过程解析 Focal Loss3. Focal Loss 的 PyTorch 实现总结 前言 类别不平衡是一个在目标检测领域被广泛讨论的问题&#xff0c;因为目标数量的多少在数据集中能很直观的体现。同时&#xff0c;在分割中这也是一…

【C++STL详解 —— vector的模拟实现】

CSTL详解 —— vector的模拟实现 vector各函数接口总览vector当中的成员变量介绍默认成员函数**构造函数1&#xff1a;****构造函数2****构造函数3****拷贝构造函数**赋值运算符重载函数 迭代器相关函数begin和end 容量和大小相关函数size和capacityreserveresizeempty 修改容器…

spring boot后端controller中接收表单参数校验

校验分为两部分&#xff0c;一部分是前端的输入时就校验&#xff0c;一部分时后端接收参数时的校验。本文提到的是后端接收参数时的校验。这个后端校验的存在有什么意义呢&#xff1f; 比如我们设置前端在输入参数时限制输入不能为空&#xff0c;应该为3-20位非空字符&#xf…

ENSP华为防火墙WEB登录操作指南

ENSP华为防火墙WEB登录操作指南 华为防火墙登录WEB 1、华为防火墙配置&#xff1a;&#xff08;需要在互联接口下放通https和ping&#xff09; int g0/0/0 service-manage https permit service-manage ping permit 2、电脑需要配置虚拟网卡 3、虚拟网卡与云和防火墙配置的IP地…

JDK类加载器剖析

0.前言 我之所以深入研究 Java 类加载器&#xff0c;是为了解决一个奇怪的问题。流行出版物&#xff0c;也就是人们所认为的 Java 世界的灯塔&#xff0c;充斥着关于这个主题的相互矛盾和过时的信息。这种矛盾引发了我的调查 — — 在 Java 类加载器的迷宫中寻求清晰的答案。 …

音视频开发之旅(81)- 图片视频“黑边”检测与去除

目录 1.“黑边“的场景 2. 二值化--单一颜色边缘的图像 3. canny边缘检测霍夫直线变换--处理负责的边缘图像 4. 性能优化 5. 资料 在页面展示中&#xff0c;如果图片/视频有黑边&#xff0c;比较影响体验&#xff0c;我我们今天实现下对图片/视频进行黑边检测。检测到黑边…

校招说明书

3400字的详细说明&#xff0c;介绍了程序员类岗位校招的整体时间节点和招聘流程。还对一些常见的问题进行讨论&#xff0c;例如内推、offer和三方、实习等。 第一章介绍基本的术语&#xff0c;第二章介绍整个校招的重要流程及时间点&#xff0c;然后第三章介绍每次招聘要经过的…

网络:HTTP协议

目录 序列化与反序列化 守护进程 网络计算器的实现 HTTP协议 http的代码演示 HTTPS 初步理解三次握手&#xff0c;四次挥手 ①tcp是面向连接的通信协议&#xff0c;在通信之前&#xff0c;需要进行3次握手&#xff0c;来进行连接的建立(谁connect谁握手) ②当tcp在断开…

《软件方法》剖析“提灯定损”,金将军和南丁格尔

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 这几天&#xff0c;江西出了“提灯定损”事件&#xff0c;成为“周公子”、“指鼠为鸭”之后的又一个大IP。 我也蹭一下这个IP&#xff0c;谈一谈软件开发中的业务建模和需求。 图1 “…

git上传到本地仓库

摘要&#xff1a;本地初始化init仓库&#xff0c;进行pull和push&#xff1b;好处是便于利用存储设备进行git备份 git init --bare test.git 随便到一个空的目录下git clone 然后使用git上传 把git仓库删除之后再clone一次验证一下是否上传成功&#xff1a; 如果在ubantu上面没…

实用技巧:如何取消app的截屏禁用

因为我想要在小鹅通App做笔记,但是被小鹅通App禁用截屏了,这真是一个很糟糕的使用体验,虽然可能是为了保护商家权益…… 方法1 可以让商家设置课程可以截屏 方法2 手机root,安装Xposed框架,利用Xposed框架上面的插件我们可以对手机进行高度定制化,而安装Xposed框架的…

Emacs之实现复制当前已打开文件buffer(一百三十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【嵌入式智能产品开发实战】(十六)—— 政安晨:通过ARM-Linux掌握基本技能【Linux shell揭秘】

目录 简述 开始 深入探究Linux内核 1.系统内存管理 2.软件程序管理 3.硬件设备管理 4.文件系统管理 GNU实用工具 1.核心GNU实用工具 2.shell Linux桌面环境 1.X Window软件 2.KDE Plasma桌面 3.GNOME桌面 4.其他桌面 Linux发行版 核心Linux发行版 特定用途的…

卷积篇 | YOLOv8改进之引入全维度动态卷积ODConv | 即插即用

前言:Hello大家好,我是小哥谈。ODConv是一种关注了空域、输入通道、输出通道等维度上的动态性的卷积方法,一定程度上讲,ODConv可以视作CondConv的延续,将CondConv中一个维度上的动态特性进行了扩展,同时了考虑了空域、输入通道、输出通道等维度上的动态性,故称之为全维度…

在一套Dockerfile中完成编译和运行环境部署

大纲 解释型语言编译环境解释环境编译型语言编译环境运行环境 方法编译环境安装系统安装编译依赖下载代码特殊处理&#xff08;可以忽略&#xff09;编译准备&#xff08;可以忽略&#xff09;编译打包依赖&#xff08;编译结果&#xff09; 运行环境安装操作系统安装运行时依赖…

RabbitMQ高级-应用问题、集群搭建

1.消息补偿 消息可靠性保障&#xff1a;——消息补偿机制 需求&#xff1a;100%确保消息发送成功 2.幂等性保障 幂等性指一次和多次请求某一资源&#xff0c;对于资源本身应该具有同样的结果。也就是说&#xff0c;其任意多次执行对资源本身所产生的影响均与第一次执行的影响…

AI编程005/ 逆向生成mysql的建表语句

1/ 通过insert into 语句生成建表语句 有些时候我们能获取到表的insert语句&#xff0c;但是没有表结构。我们可以借助AI工具&#xff0c;让其逆向生成mysql的建表语句。 提示词如下&#xff1a; 根据下面的SQL语句&#xff0c;逆向生存mysql的建表语句&#xff0c;每个字段…

Redis配置与优化

目录 引言 一、关系型数据库与非关系型数据库 1、关系型数据库 2、非关系型数据库 3、关系型数据库和非关系型数据库的区别 1.数据存储方式不同 2.扩展方式不同 3.对事物性的支持不同 4、非关系型数据库产生背景 二、Redis简介 1、Redis优点 2、Redis为什么这麽快&…