225. 用队列实现栈、232. 用栈实现队列、622. 设计循环队列

news2025/7/12 7:45:44

LeetCode题解

  • 前言
  • 用队列实现栈
  • 用栈实现队列
  • 循环队列
  • 总结

前言

这三道题都是比较经典的一道题,主要想要考察我们对于栈、队列的性质的应用,也是笔试题的常客!!!接下来就让我们一起来手撕它!!!
在这里插入图片描述

用队列实现栈

题目描述:
在这里插入图片描述
➡️挑战链接⬅️
分析:

由于我们当前用的是C语言来刷的题,而C语言刷题最大的缺点就是得自己造轮子!!!因此我们在这里得把我们之前写的栈copy过来,不然没办法做;
进入主题:
我们的目的是实现栈,而且是利用队列去实现栈:
首先我们想到的是栈的性质是先进后出,队列的性质是先进先出;
在这里插入图片描述
我们先按照一样的顺序给数据入栈和入队列,那么现在我们要实现的是栈,栈的基本擦做就是出栈入栈,那么我们想要出栈的话,那么对应上图我们就像出掉4,那么栈最后所剩的数据还剩下1、2、3,但是我们对应队列的操作的话,队列是不能之间删除队尾的数据的,只能删除队头的数据,为此我们就要想办法既要保留队尾前面的数据,又要删除队尾的数据!!题目给了我们一点提示,题目允许我们使用两个队列:
在这里插入图片描述
那么我们就可以利用另一个队列用来先存储前一个队列队尾的数据,也就是将q1的队尾以前的数据全部放入q2这个队列里面去,最后只将最后一个队尾数据留在q1中:
在这里插入图片描述
那么这样的话是不是q1中原来队尾的数据就变到了队头的位置,我们是不是就能对其进行出队操作(删除该数据),同时我们也还是保留了队尾以前的数据,是不是就相当于完成了一次出栈操作!!!
在这里插入图片描述
那么好,既然完成了一次出栈操作,那么q1的队列是不是就变为了一个空队列;那么我们接着来完成入栈操作:由于刚才我们完成了出栈操作,所以现在我们的栈里面只剩下1、2、3这3个有效数据:
在这里插入图片描述
现在我想要入栈的话,是不是就是在原来的数据后面插入一个数据,比如我想插入一个数据5:那么原来的数据在哪里?是不是在q2里面,为此我们只需对q2队列完成一次入队操作就相当于完成了一次入栈操作
在这里插入图片描述
那么现在我们如果再来一次出栈操作,那么按照上面的意思:我们是不是要把q2里面除了队尾的元素全部放入q1里面去啊!然后在队q2里面的最后一个元素完成一次出队操作,就相当于完成了一次出栈操作:
在这里插入图片描述
这是q2又变为空队了,然后我们又在原有的基础上再插入数据我们就会在q1的后面插入,不会再q2里面插入;
不知道读者们又没又注意到在整个出栈入栈的过程中q1、q2两个队列是不可能同时为空的,一定是有一个队列为空零一个队列不为空的时候我们才能完成一次出栈操作,当两个队列都为空时,就表示这个也就为空了!!!为此我们在这里总结一下上面的出栈操作
1、 将不为空的队列中的元素(除了队尾元素)全部倒转到为空的队列中去;
2、 那么现在这个原来不为空的队列就只剩下一个元素了,以栈的方式来看的话,就是栈顶元素,我们就只需对其完成出栈操作就可以了;
再来总结一下上面的入栈操作:
1、 我们对不为空的队列进行掺入数据,也就相当于完成了一次入栈,不要对空的那个队列插入数据,否则会打乱我们出栈操作!!一定要保证一个队列为空!!!;
2、 如果两个队列都为空的话,那么就表示这个栈是个空栈,那么我们只需要随便找个队列入数据就可以了,保证另一个数据始终为空!!!
那么 如何获得栈顶元素
在这里插入图片描述
我们的队列有一个操作叫做获取队尾元素,我们只需要对不为空的队列执行获取队尾的操作就能完成一次获取栈顶元素的操作了!!!

好了现在我们已经解决了栈的基本操作,下面就是模拟实现了:

// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType val;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
    int size;
}Queue;
 
//初始化队列
void QueueInit(Queue*q);
//销毁队列
void QueueDestroy(Queue*q);
//入队列
void QueuePush(Queue*q,QDataType x);
//出队列
void QueuePop(Queue*q);
//获取队头元素
QDataType QueueFront(Queue*q);
//获取队尾元素
QDataType QueueBack(Queue* q);
//获取队列元素个数
size_t QueueSize(Queue*q);
//队列判空
bool QueueEmpty(Queue*q);
//初始化队列
void QueueInit(Queue* q)
{
	assert(q);
	q->front = NULL;
	q->rear = NULL;
    q->size=0;
}
//销毁队列
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->front = NULL;
	q->rear = NULL;
    q->size=0;
}

//开辟新节点
static QNode* BuyQNode(QDataType x)
{
	QNode* NewNode = (QNode*)malloc(sizeof(QNode));
	if (!NewNode)
	{
		printf("malloc fail!\n");
		exit(EXIT_FAILURE);
	}
	NewNode->next = NULL;
	NewNode->val = x;
	return NewNode;
}
//入队列
void QueuePush(Queue* q,QDataType x)
{
	assert(q);
	QNode* NewNode = BuyQNode(x);
	if (q->rear == NULL)
	{
		q->front = q->rear = NewNode;
	}
	else
	{
		q->rear->next = NewNode;
		q->rear = NewNode;
	}
    q->size++;
}
//出队列
void QueuePop(Queue* q)
{
	assert(q);
	assert(QueueEmpty(q)==false);//判空
	QNode* next = q->front->next;
	free(q->front);
	q->front = next;
	if (q->front == NULL)//删除节点过后front指针指向NULL,表示无节点可删
		q->rear = q->front;
        q->size--;
}
//队列判空
bool QueueEmpty(Queue* q)
{
	assert(q);
	return (q->front == q->rear && q->front == NULL);
}
//获取队头元素
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->front->val;
}
//获取队尾元素
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->rear->val;
}
//获取队列元素个数
size_t QueueSize(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	
	return q->size;
}


typedef struct {
       Queue q1;
       Queue q2;//我们得需两个队列才能完成模拟操作
} MyStack;

bool myStackEmpty(MyStack* obj);
MyStack* myStackCreate() {
         //该结构体用于控制两个队列,并且完成结构体的创建和初始化工作
         MyStack*NewStack=(MyStack*)malloc(sizeof(MyStack));
         QueueInit(&NewStack->q1);
         QueueInit(&NewStack->q2);//完成对于两个队列的初始化工作
         //也就相当于完成了对于这个结构体的初始化工作
         return NewStack;//由于该结构体是在堆上开辟的不会随着改函数栈帧的销毁而销毁
         //只有当我们手动去销毁时,才会被销毁;
}

void myStackPush(MyStack* obj, int x) {
         //完成入栈操作
         assert(obj);//养成一个好的代码习惯,反之用户乱穿null指针
         Queue *empty=&obj->q1;
         Queue *nonempty=&obj->q2;//我们需要对不为空的队列完成插入操作,为此我们得需要知道那个是非空队;
         //假设q2是非空队,但是假设是会出错的啊,这就需要我们纠正
         if(QueueEmpty(&obj->q2))//如果q2是空队,则纠正错误
         {
             empty=&obj->q2;
             nonempty=&obj->q1;
         }
         //到这里我们只需完成队非空队列的插入操作
         QueuePush(nonempty,x);
}

int myStackPop(MyStack* obj) {
        //出栈操作
        assert(obj);//好习惯
        assert(myStackEmpty(obj)==false);//当栈为空的时候就不需要在删除了
        //老样子先判断那个队列是非空
        Queue *empty=&obj->q1;
         Queue *nonempty=&obj->q2;
         if(QueueEmpty(&obj->q2))
         {
             empty=&obj->q2;
             nonempty=&obj->q1;
         }
         //将非空队列的元素(除队尾外)全部搬运到空队;
         while(QueueSize(nonempty)>1)
         {
             QueuePush(empty,QueueFront(nonempty));
             QueuePop(nonempty);
         }
         int top=QueueFront(nonempty);
         QueuePop(nonempty);//完成出栈操作
         return top;
}

int myStackTop(MyStack* obj) {
         //获取栈顶元素
         assert(obj);
         assert(myStackEmpty(obj)==false);//当栈为空的时候就不要在进行此操作了
          //老样子先判断那个队列是非空
         Queue *empty=&obj->q1;
         Queue *nonempty=&obj->q2;
         if(QueueEmpty(&obj->q2))
         {
             empty=&obj->q2;
             nonempty=&obj->q1;
         }
         //非空队列的队尾就相当于栈顶
         int top=QueueBack(nonempty);
         return top;
}

bool myStackEmpty(MyStack* obj) {
   assert(obj);
   return (QueueEmpty(&obj->q1)&&(QueueEmpty(&obj->q2)));//只有两个队列都为空,整个栈才为空;
}

void myStackFree(MyStack* obj) {
        assert(obj);
        QueueDestroy(&obj->q1);
        QueueDestroy(&obj->q2);
        free(obj);//注意这里不要一上来就释放obj,这样会造成内存泄漏,因为obj之开辟存放q1和q2的空间没有开辟具体的队列空间,对于具体的队列空间是通过q1、q2去完成的,为此我们需要先释放队列,在释放存储队列的结构体(obj);
}

在这里插入图片描述
最后还是画图解释一下最后的销毁操作吧:
在这里插入图片描述

用栈实现队列

题目描述:
在这里插入图片描述
➡️挑战链接⬅️

分析:
大致思路呢,其实和上面一道题是一模一样的:
在这里插入图片描述
对于队列来说的话我们想要完成出队操作只需删掉队头元素就可以了,这里的队头元素相当于栈底元素,但是现在我们的数据是放在栈里面的,无法直接去删除,我们只有将栈底以前的元素全部全部抽离出来(并不是删除)才能直接接触到栈底的元素,至此我们只需要再对其进行出栈操作就算是完成了一次出队操作:当然根据上面的思路,这些栈底以前的数据我们可以抽离出来放在另一个栈里面:
在这里插入图片描述
进行完删除操作后,那么我们是不是还得将s2里面的数据倒回s1里面去,不然的在插入数据的时候就会照成顺序错误!!当然如果我们真的这么做的话效率可就太低了,你有没有注意到我们s1在完成出队的操作之后,就变为了空栈,我们直接在s1里面插入数据,在连接着s2一起看,你又没又注意到s2栈似乎扮演者队头的身份,s1扮演者队尾的身份,你看哈我们如果需要再次进行出队操作的话,是不是就只对s2进行出栈操作就好了,我们如果想要在删除数据的话是不是就直接在s1里面插入数据:也就是说原来在s1里面是栈底(以队列眼光来看就是队头),经过倒转到s2以后,原本在s1是栈底的数据就在s2中变为了栈顶的数据(以队列的眼光来看,原本是属于队头的数据处于栈底,无法完成出队操作,但是现在变为了s2的栈顶数据,相当于我们打通了封闭队头数据出去的大门,在s2中阻止队头数据出去的大门就不在了,我们就可以顺利的完成出队操作);
总结上面的言论
1、我们将一个栈当作“入栈”数据的插入就放在这个栈里面;
2、我们将另一个栈当作“出栈”数据的删除就在这个栈里面进行;
3、当“出栈”里面没有数据时,就需要我们将“入栈”里面的原素全部倒进“出栈”;
就有点类似于黑洞和白洞的关系,一个只进东西,一个只出东西:
在这里插入图片描述

理论分析完了,下面就是代码实现:

typedef int DataType;
typedef struct Stack
{
	DataType* nums;
	int capcity;
	int top;
}ST;
//初始化栈
void InitStack(ST* ps);
//销毁栈
void DestroyStack(ST* ps);
//入栈
void StackPush(ST* ps,DataType x);
//出栈
void StackPop(ST*ps);
//判断栈是否为NULL
bool StackEmpty(ST* ps);
//统计栈的元素
size_t StackSize(ST* ps);
//获取栈顶元素个数
DataType StackTop(ST*ps);
//初始化栈
void InitStack(ST* ps)
{
	assert(ps);//防止乱传
	ps->capcity = 0;
	ps->top = 0;
	ps->nums = NULL;
}
//销毁栈
void DestroyStack(ST*ps)
{
	assert(ps);
	ps->capcity = 0;
	ps->top = 0;
	free(ps->nums);
}
//检查扩容,不提供给用户,由程序自己完成
static void Check_Capcity(ST* ps)
{
	assert(ps);
	if (ps->capcity == ps->top)//需要扩容
	{
		int len = (ps->capcity == 0) ? 4 : ps->capcity * 2;
		DataType* tmp = (DataType*)realloc(ps->nums,len*sizeof(DataType));
		if (!tmp)
		{
			printf("realloc fail!\n");
			exit(EXIT_FAILURE);
		}
		ps->nums = tmp;
		ps->capcity = len;
	}
}
//入栈
void StackPush(ST* ps,DataType x)
{
	assert(ps);
	Check_Capcity(ps);
	ps->nums[ps->top] = x;
	ps->top++;
}
//出栈
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//判空
	ps->top--;
}
//判断栈是否为NULL
bool StackEmpty(ST* ps)
{
	assert(ps);
	return !ps->top;
}
//统计栈的元素
size_t StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}
	//获取栈顶元素
	DataType StackTop(ST* ps)
	{
		assert(ps);
		assert(!StackEmpty(ps));//栈不为空,我们才有元素获取;
		return ps->nums[ps->top - 1];
	}


typedef struct {
        ST PushSt;//这个栈专门用来插入数据(入栈)
        ST PopSt;//这个栈专门用来删除数据  (出栈)
} MyQueue;

bool myQueueEmpty(MyQueue* obj);
MyQueue* myQueueCreate() {
       //创建结构体
       MyQueue*q=(MyQueue*)malloc(sizeof(MyQueue));
       InitStack(&q->PushSt);
       InitStack(&q->PopSt);//初始化两个栈
       return q;
}

void myQueuePush(MyQueue* obj, int x) {
           //入队
           assert(obj);//好习惯
           //我们只需要在PushSt栈里面插入数据
           StackPush(&obj->PushSt,x);
}

int myQueuePop(MyQueue* obj) {
            //出队
            assert(obj);
            assert(myQueueEmpty(obj)==false);//判空
            //只需要对PopSt栈进行操作即可
            //当然如果PopSt栈里面没有元素了,我们就需要将PushSt里面的元素全部倒过来
            /*if(StackEmpty(&obj->PopSt))
            {
                while(StackEmpty(&obj->PushSt)==false)
                {
                    StackPush(&obj->PopSt,StackTop(&obj->PushSt));
                    StackPop(&obj->PushSt);
                }
            }
            int top=StackTop(&obj->PopSt);
            StackPop(&obj->PopSt);
            return top;*/
            //代码复用
            int top= myQueuePeek(obj);
           StackPop(&obj->PopSt);
           return top;
}

int myQueuePeek(MyQueue* obj) {
            assert(obj);
            assert(myQueueEmpty(obj)==false);
            //当然如果PopSt栈里面没有元素了,我们就需要将PushSt里面的元素全部倒过来
            if(StackEmpty(&obj->PopSt))
            {
                while(StackEmpty(&obj->PushSt)==false)
                {
                    StackPush(&obj->PopSt,StackTop(&obj->PushSt));
                    StackPop(&obj->PushSt);
                }
            }
            //PopSt的栈顶元素就是队头元素
             int top=StackTop(&obj->PopSt);
             return top;
}

bool myQueueEmpty(MyQueue* obj) {
         assert(obj);
         return ((StackEmpty(&obj->PopSt))&&(StackEmpty(&obj->PushSt)));//两个栈同时为空队列才为空
}

void myQueueFree(MyQueue* obj) {
     assert(obj);
     DestroyStack(&obj->PopSt);
     DestroyStack(&obj->PushSt);
     free(obj);//与前一道题一样,必选先释放所开辟的栈,在释放当前结构体,否则会照成内存泄漏;
}

在这里插入图片描述

循环队列

题目描述:
在这里插入图片描述
➡️挑战链接⬅️

分析:
嗯,循环队列,很好!我可以用循环链表来实现,嗯想法是不错的,下面我们来画图分析一下:
在这里插入图片描述
由于环形链表是固定大小的,所以链表的大小是开好的;
首先队列为空的条件是front==rear,然后呢rear指向的是待插入位置,这没问题吧,好那我现在不断插入数据,将这个环形链表插满:
在这里插入图片描述
我们可以发现此时队列满了的条件也是:front==rear, 这似乎就与我们判断队列为空的条件就似乎有点冲突了,我front==rear到底是表示队列已满嘞,还是队列此时为空嘞?这会引发歧义!!
那如何解决嘞?
1、第一种办法:我们在定义队列结构的时候,多定义一个size,专门用来记录队列长度的,入锅size==k那就说明队列满了,如果size==0,就说明时空队;这种解决办法比较简单,读者可以自行下去写一下相关代码;
2、第二种办法:
有大佬就提出我们在生成环形链表的时候在多开一个节点,需要k个节点,实际上我们开了k+1个节点;
在这里插入图片描述

此时队列为空的条件还是:front==rear;
队列已满的条件就是rear->next==front
那么我们再来看看这样的结构是否满足其他操作的要求;
1、 出队:只要不为空队,front往后走就行,不需要释放节点,可以满足;
2、 入队:只要不为满队,在rear所指位置处填上数据,rear往后走就行,可以满足;
3、 获取队头元素:只要不为空队,直接返回front->val;即可,可以满足;
4、 获取队尾元素:我的rear指针指向的不是尾节点,而是尾节点的下一个节点,又是单链表结构,我们无法直接获取尾节点的元素,如果去遍历寻找的话,效率就很低,那该怎么办?我们可以在定义环形队列的结构时,多定义一个指针专门用来记录rear指针的前一个节点;
在这里插入图片描述
这样的话我们就把获取尾节点的元素的时间复杂度降到了O(1);这种办法呢?读者也可以自行实现一下:在初始化的时候可能代码会多一点(其他还好);!!!

上面链表给我们的最大麻烦就是无法直接获取尾元素地址,虽然上面的解决方法将获取尾元素
的时间复杂度降到了O(1),但是呢我认为还不够好!;
我们可不可以不加那个前指针直接就找到了尾指针!!
当然可以我们既然可以用链表构成循环队列,我们为什么不能用数组去构成循环队列呢?
当然我们的数组也需要开辟K+1个长度,不然判空和判满的条件会冲突!!
在这里插入图片描述
那么很自然的我队空的条件就是front==rear;
那么队满嘞?
在这里插入图片描述
通过大量的尝试和代入得出队满的条件
(rear+1)%(k+1)==front;
现在我们来看一看是否能满足队列的基本操作:
1、出队:只要队不为空,front++;但是想这副图这样需要我们注意:
在这里插入图片描述
很明显我们向这种情况在出队的话,front明显越界了,理论上我们是希望front指向0号下标的:
在这里插入图片描述

因此我们在每一次front加加过后都应该判断一下front与k+1的关系,如果front==\k+1,front指针越界,需要我们修正,以此让其循环起来;若不等的话,则不需要修正;
2、入队,只要队未满,那么我们就只需在rear位置插入就行了,同时我们的rear也需要注意向上面的front一样的越界问题:
在这里插入图片描述
为此在rear加加结束过后我们也需要对其进行判断是否越界,以便及时修正rear,让队列循环起来;
3、获取队头元素:只要队不为空,直接返回front所指的元素;
4、获取队尾元素,只要队不为空,直接返回rear-1所指元素,应为我的rear始终指向的是我队尾元素的下一个:但是我们同样的rear-1越界的问题,比如下图:
在这里插入图片描述
很明显我们在返回队尾元素之前,需要对rear做一番检查,如果rear==0,则返回下标为k的元素,如果不等于,则就返回rear-1所指的元素!!;

好了以上是对循环队列的所有分析了,下面我们就利用代码来实现一下:

typedef struct {
      int *nums;
      int front;
      int rear;
      int k;//必须把这个带着,不然后期无法判满和循环
} MyCircularQueue;

void myCircularQueueFree(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value);
bool myCircularQueueDeQueue(MyCircularQueue* obj);
int myCircularQueueFront(MyCircularQueue* obj);
int myCircularQueueRear(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);

MyCircularQueue* myCircularQueueCreate(int k) {
                           
           MyCircularQueue*q=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
           q->nums=(int*)malloc(sizeof(int)*(k+1));//实际开k+1个空间
           q->front=0;
           q->rear=0;
           q->k=k;
           return q;
}

bool myCircularQueueEnQueue(MyCircularQueue* q, int value) {
          assert(q);//好习惯
          if(myCircularQueueIsFull(q))//如果队列已满,则返回false,表示插入失败;
           return false;
           else
           {
               q->nums[q->rear]=value;
               q->rear++;
               q->rear=(q->rear==q->k+1)?0:q->rear;//判断rear指针是否越界
           }
           return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* q) {
          assert(q);
          if(myCircularQueueIsEmpty(q))//如果队列为空就会删除失败
          return false;
          else
          {
              q->front++;
              q->front=(q->front==q->k+1)?0:q->front;//判断一下front是否越界
          }
          return true;
}

int myCircularQueueFront(MyCircularQueue* q) {
         assert(q);
         if(myCircularQueueIsEmpty(q))//判空
         return -1;
         return q->nums[q->front];
}

int myCircularQueueRear(MyCircularQueue* q) {
     assert(q);
     if(myCircularQueueIsEmpty(q))//判空
     return -1;
     int index=(q->rear)?q->rear-1:q->k;
     return q->nums[index];
}

bool myCircularQueueIsEmpty(MyCircularQueue* q) {
      assert(q);
      return ((q->front==q->rear));
}

bool myCircularQueueIsFull(MyCircularQueue* q) {
   assert(q);
   return ((q->rear+1)%(q->k+1)==q->front);
}

void myCircularQueueFree(MyCircularQueue* q) {
     assert(q);
     free(q->nums);
     free(q);
}

总结

以上便是博主对于三个经典例题的解释和说明了,感谢大家阅读!!

在这里插入图片描述

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

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

相关文章

【C】程序环境和预处理

🙈个人主页:阿伟t 👉系列专栏:【C语言–大佬之路】 🎈今日心语:你所看到的惊艳,都曾被平庸所历练。 本章重点: 程序的翻译环境程序的执行环境详解:C语言程序的编译链接…

next-key lock案例

1. 加锁规则 在默认的可重复读的隔离级别下,加锁规则可以总结为:两个原则,两个优化和一个bug。 原则1:加锁的基本单位是next-key lock。next-key lock是前开后闭的区间原则2:查询过程中访问到的对象才会加锁优化1&am…

计算机网络-数据交换技术

数据交换:实现在大规模网络核心上进行数据传输的技术基础。 常见的数据交换技术包括:电路交换、报文交换和分组交换。 一、电路交换 电路:通过中间交换结点为两台主机之间建立一条专用的通信线路。电路交换:是最早出现的一种交换…

这个神器,让我的 Python 代码运行速度快了100倍

Python 已经得到了全球程序员的喜爱,连续多期稳坐编程语言排行榜第一把交椅。但是还是遭到一些人的诟病,原因之一就是认为它运行缓慢。 要是有一款能够自动优化我们代码的神器该有多好啊! 于是,大家都在想尽各种办法来提高 Pytho…

弘扬企业家精神!闪马智能创始人兼CEO彭垚再获殊荣

随着新业态、新活力涌现,诞生了一批又一批新的优秀企业家。11月8日,闪马智能创始人兼CEO彭垚荣膺胡润百富“2022青年产业领袖”。 自2016年起,彭垚连续创业,7年内成功培育并壮大了两家行业独角兽企业,2019年4月成立闪…

创龙AD+全志T3 ad_display 开发案例 (2)

上一篇:创龙AD全志T3 ad_display 开发案例(1) 前 言 本文主要介绍基于全志科技T3(ARM Cortex-A7)处理器的8/16通道AD采集开发案例,使用核芯互联CL1606/CL1616AD芯片,亦适用于ADI AD7606/AD7616。CL1606/CL1616与AD7606/AD7616软硬件兼容。 …

OpenCV图像处理——图像梯度

总目录 图像处理总目录←点击这里 七、图像梯度 7.1、图像梯度-Sobel算子 原图 直接计算 不建议这么算 sobelxycv2.Sobel(img,cv2.CV_64F,1,1,ksize3) sobelxy cv2.convertScaleAbs(sobelxy) cv_show(sobelxy,sobelxy)分别计算——合并 当前位置的像素值等于sobel算…

spring security调用过程;及自定义改造

认证/授权概述 一般系统都有登录接口来校验用户是否存在,密码是否正确,然后会颁发一个token给客户端,后续客户端就可以带着这个token来请求,代表自己是合法请求。 spring security责任链 请求->UsernamePasswordAuthenticati…

【MFC】打砖块小游戏(上)(5)

创建WIN32项目的时候,可以去掉勾选【空项目】可以减少工作量。 创建项目 文件-》新建-》 项目-》WIN32项目-》取消勾选空项目,完成创建 创建完成后,多出了很多文件,当然很多代码是前面已经手动写过了的: stdafx.h …

聚醚羰基铑功能化离子液体{[CH3O(CH2CH2O)nmim][Rhx(CO)y]}

聚醚羰基铑功能化离子液体{[CH3O(CH2CH2O)nmim][Rhx(CO)y]} 离子液体种类 目前研究较多的离子液体阳离子,根据有机母体的不同主要可分四种,即咪唑类离子[R1R3Im]、吡啶类离子[RPy]、烷基季铵类离子[NRxH4-x]以及烷基季膦类离子[PRxH4-x]。这四类阳离子…

【Designing ML Systems】第 9 章 :生产中的持续学习和测试

🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎 📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃 🎁欢迎各位→点赞…

MySQL数据库的约束

文章目录一、约束是什么?二、约束的具体操作Not NULLUNIQUE约束的组合使用PRIMARY KEYDEFAULTFOREIGN KEY一、约束是什么? 约束就是,在创建表的时候,对表设置一些规则,只有满足这些规则,才可以插入数据&am…

【微服务】Nacos通知客户端服务变更以及重试机制

💖Spring家族源码解析及微服务系列 ✨【微服务】Nacos服务发现源码分析 ✨【微服务】SpringBoot监听器机制以及在Nacos中的应用 ✨【微服务】Nacos客户端微服务注册原理流程 ✨【微服务】SpringCloud中使用Ribbon实现负载均衡的原理 ✨【微服务】SpringBoot启动流程…

字节一面后,我又看了一遍ThreadLocal核心原理

前言:上周在面试字节的时候,问到了ThreadLocal的核心原理,由于这个知识点当时有些淡忘,因此作此篇文章进行知识的记录,同时希望能够帮助到其他的小伙伴儿们。 本篇文章记录的基础知识,适合在学Java的小白&a…

动态 SQL

文章目录一、学习目的二、动态 SQL 中的元素三、条件查询操作四、更新操作五、复杂查询操作1.foreach 元素中的属性2.foreach 元素迭代数组3.foreach 元素迭代 List4.foreach 元素迭代 Map一、学习目的 在实际项目的开发中,开发人员在使用 JDBC 或其他持久层框架进…

【汇编 C++】多态底层---虚表、__vfptr指针

前言:如果对多态不太了解的话,可以看我的这篇文章《C多态》,另外本文中出现到的汇编代码,我都会予以解释,看不懂没关系,知道大概意思就行,能不讲汇编的地方我就不讲; 本文使用到的工…

networkx学习记录

networkx学习记录networkx学习记录1. 创建图表2. 节点3. 边4.检查图的元素5.从图中删除元素6.使用图构造函数7.访问边和邻居8.向图、节点和边添加属性9.有向图10. 绘制图形networkx学习记录 1. 创建图表 创建一个空图 import networkx as nx G nx.Graph()此时如果报以下错误…

HTML网页设计结课作业——11张精美网页 html+css+javascript+bootstarp

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置,有div的样式格局,这个实例比较全面,有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 精彩专栏推荐&#x1f4…

学姐突然问我键盘怎么选?原来是为了这个...

前言: 上个星期学姐来问我该买啥键盘,说是自己用的笔记本的键盘实在是不太好用,很喜欢机械键盘的手感,但是常规的机械键盘有太大了而且声音十分大,对她们女生来说并不是很友好。于是我给她推荐了我现在正在用的这款键盘…

头歌-信息安全技术-Java生成验证码

头歌-信息安全技术-Java生成验证码一、第1关:使用Servlet生成验证码1、任务描述2、编程要求3、评测代码二、第2关:用户登录时校验验证码是否正确1、任务描述2、编程要求3、评测代码三、第3关:使用Kaptcha组件生成验证码1、任务描述2、编程要求…