栈、队列、链表

基于《啊哈!算法》和《数据结构》(人民邮电出版社)

本博客篇幅较多,读者根据目录选择,不理解的可留言和私信。

栈、队列、链表都是线性结构。

三者都不是结构体、数组这种数据类型,我认为更像是一种算法。

针对问题:

1.相较于数组的优点

2.模板,结合结构体

3.栈、队列、链表之间的结合

4.STL库容器

一、队列

原则:先进先出

        队列,顾名思义就像是买东西排队先到的人先出,我们先看一道《啊哈!算法》上的例子:

解密QQ号:

        “6 3 1 7 5 8 9 2 4”是小哈给小哼的加密数字,规则是:首先将第一个数删除,紧接着将第二个数放到这串数的末尾,再将第三个数删除并将第四个数放到这串数的末尾,再将第五个数删除……直到剩下最后一个数。将最后一个数删除。按照刚才删除的顺序,把这些删除的数连在一起就是小哈的QQ号。

        对于这道题,这些加密数字就满足“先进先出”的原则。解题思路如图:

非队列思路 

         在不知道队列的时候,我的C解题思路如下:(其实也有队列的影子)

#include <stdio.h>int main()
{int q[100]={0,6,3,1,7,5,8,9,2,4};int m[10]={0};int len=10;int i=0;for ( i = 1; q[i] != 0; i++)//条件最后一个数被删除{// printf("%d\n",q[i]);if(i%2==1)//删除{printf("%d",q[i]);}else if(i%2 == 0)//放到数组的末尾{q[len]=q[i];len++;}}// for(i=0;i<len-1;i++)// printf("%d",m[i]);return 0;
}

        当i为奇数时,则删除就,为偶数时就移到末尾,直到删除到最后一个,及q[i]==0。

        引入队列的定义:队列主体,队首,队尾;队首出队即删除,队尾进队即插入。

队列思路

我认为队列可以更加优雅的完成一些问题。队列的解题思路如下:

#include<stdio.h>
int main()
{int q[102]={0,6,3,1,7,5,8,9,2,4};int head = 1;int tail =10;while(head<tail){//打印队首并将队首出队printf("%d ",q[head]);head++;//先将新队首的数添加到队尾q[tail]=q[head];tail++;//再将队首出队head++;}return 0;
}

现在有九个数,9个数全部放入队列之后,head=1,tail=10(队尾指向的是尾元素的后一位,1-9为有效元素,这样写防止了“假溢出”),此时head和tail之间的数就是队列的有效数,如果要删除一个数的话。就将head++就行了,这样可以保证head和tail之间的数为有效数,及“排队的数”,这样做虽然浪费了一个空间,却节省了大量的空间,这是非常划算的,新增一个数也很简单,把需要增加的数放到队尾即q[tail]之后再tail++就行了。 

定义一个结构体,即可认定为一个新的数据类型:队列,如下:

struct queue
{int data[100];    //队列的主体,用来排队int head;        //队首      int tail;         //队尾
};

结构体模板

 下面这段代码就是使用结构体来实现队列操作:

#include<stdio.h>struct queue
{int data[100];    //队列的主体,用来排队int head;        //队首      int tail;         //队尾
};int main()
{struct queue q;int i=0;q.head = 1;q.tail = 1;for ( i = 1; i <= 9; i++){scanf("%d",&q.data[q.tail]);q.tail++;}while(q.head<q.tail)    //当队列不为空的时候执行循环{//打印队首并将队首出队printf("%d ",q.data[q.head]);q.head++;//先将新队首的数添加到队尾q.data[q.tail]=q.data[q.head];q.tail++;//再将队首出队q.head++;}return 0;
}

 另外C++的STL库已经有队列的实现。

队列的基本操作
q.push(x):将元素 x 放入队尾。
q.pop():将队头元素出队。(只能删除,不能返回队首的值)
q.empty():判断队列是否为空,为空返回 t r u e,否则返回 f a l s e 。(头尾队列重合为空)
q.size():返回队列长度。
q.front():返回队头元素,不出队。(返回的是队首元素的值)
q.back():返回队尾元素。

STL库队列容器

使用STL库的queue容器实现:

#include <bits/stdc++.h>
using namespace std;int main()
{queue<int> q;for (int i = 1; i <= 9; i++){int x=0;scanf("%d",&x);q.push(x);}while(q.empty() == 0)    //当队列不为空的时候执行循环{//打印队首并将队首出队printf("%d ",q.front());q.pop();//先将新队首的数添加到队尾int x=q.front();q.push(x);//再将队首出队q.pop();}return 0;
}

其它还有:优先队列,双向队列。 

扩展: 

       (一)循环队列

        通过取“模”实现。

        假设当前队列分配的最大空间为6,则当队列处于如图(d)所示(队首经过删除得到)的状态时不可再继续插入新的队尾元素,否则会出现溢出现象。但实际上,队列的实际可用空间只用了两个,这称为“假溢出”。是由“队尾入队,队头出队”这种限制造成的。

        一个巧妙的办法是将顺序队列变为一个环状的空间,成为循环队列。如图所示3.13:

        头、尾指针以及队列元素之间的关系不变,只是在循环队列中,头、尾指针“依环状增1”的操作通过模实现。通过取模,头尾指针可以在顺序表空间内以头尾衔接的方式循环移动。

        如图3.14(a)队头位置是q[5](从1-6共六个数),队尾位置是q[5](队头队尾重合),队尾插入一位,通过模运算,q[(5+1)%6]==q[0],即实现循环,又多“开辟”了6-1-2-3-4这五个空间,并且实现了循环。

                那么我们如何判断队列空间是满还是空呢?

        (1)少用一个元素,即当队列空间大小为m时,有m-1个元素就认为是队满。这样判断对空的条件不变,即当头尾指针的值相同时;而当尾指针在循环意义上加1后等于头指针时,则认为队满。如下:

队空:Q.front == Q.rear(一定要记得Q。rear指向的是队尾元素的后一个)

队满:Q.(Q.rear + 1)%m == Q.front

        (2)另设一个标记位,队列本来就是类似算法,灵活性高,大家可以自己定义。

                求循环队列的长度

        (Q.rear-Q.front+m)%m;(前面经过了取模,所以Q.rear的范围为1-m)

        情况d来举例,(4-5+6)%6=5;是J5-J9。

        (二)链队列

        顾名思义,相比前面的线性结构,这里是链式结构实现的队列。(具体实现了解链表便可领会)

                特点:

(1)不需要判断队满(不受最大空间容量的限制)

(2)可以插队

(3)队首为链表的头部

二、栈

原则:后进先出

        栈限定为只能在一端进行插入和删除工作,就像是一个小桶,直径只能放一个球,先放进去的球最后取,后放进去的球先取。如图(来自《啊哈!算法》)

          栈的实现只需要一个一维数组和一个指向栈顶的变量top就行了。我们通过top来对栈进行插入和删除

        我们来看一道例题:解密回文。

        “xyzyx”是一个回文字符串,所谓回文字符串就是指正读和反读均相同的字符序列,如“席主席”、“记书记”、“aha”和“ahaha”均是回文,但“ahah”不是回文,通过栈这个数据结构我们将很容易的判断一个字符串是否为回文。

        栈思路

        假设判断“ahyha”

        先求出字符串长度,如果一个字符串为回文的,那么必将中心对称,中心点‘y’左面的从左向右依次入栈入栈为“a h”,栈顶指向‘h’,然后从栈顶依次出栈与中心点右边的“h a”比较。

#include <stdio.h>
#include <string.h>int main()
{char str[100]={'\0'},s[100]={'\0'};int len=0,i=0,next=0,top=0,mid=0;gets(str);len=strlen(str);// 读取字符串和长度mid=len/2-1;// 如果回文则必将从中间对称,我们取中心点for ( i = 0; i <= mid; i++)// 加等于号,预防输入的是两个字符{s[++top]=str[i];}// 将mid之前的字符存入栈if (len%2==0) // 判断回文为奇数还是偶数{next=mid+1; //偶数}elsenext=mid+2; //奇数for ( i = next; i <= len-1; i++){if (str[i]!=s[top])// 判断是否为中心对称{break;}top--;// 不能简写s[top--],应该先判断为1后再--}// 如果top为0,则说明栈内所有的字符都被一一匹配了if(top == 0)printf("YES");elseprintf("NO");return 0;
}

结构体模板 

        栈也有结构体模板,解密回文具体思路不再展示。

struct stack
{int data[100];    //栈的主体int top;        //栈顶      
};

 STL栈容器

使用STL库的stack容器实现:       

#include <bits/stdc++.h>
using namespace std;int main()
{stack<int> s;char str[100]={'\0'};int len=0,next=0,mid=0;gets(str);len=strlen(str);// 读取字符串和长度mid=len/2-1;// 如果回文则必将从中间对称,我们取中心点for (int i = 0; i <= mid; i++)// 加等于号,预防输入的是两个字符{s.push(str[i]);}// 将mid之前的字符存入栈if (len%2==0) // 判断回文为奇数还是偶数next=mid+1; //偶数elsenext=mid+2; //奇数for (int i = next; i <= len-1; i++){if (str[i]!=s.top())// 判断是否为中心对称{break;}s.pop();// 不能简写s[top--],应该先判断为1后再--}// 如果top为0,则说明栈内所有的字符都被一一匹配了if(s.size() == 0)printf("YES");elseprintf("NO");return 0;
}

stack容器和queue容器 成员函数差不多,两种容器均自动管理内存,无容量限制;均不允许遍历操作。

1、s.emplace();                在栈顶构造元素,避免复制或者移动等操作影响性能

2、s.empty();                      判断栈是否为空

3、s.pop();                          从栈顶出栈

4、s.push();                      从栈顶入栈

5、s.size();                         返回栈元素数量

6、s.swap();                       交换堆栈

7、s.top();          访问栈顶元素,返回引用

扩展:

(一)链栈

        链表与栈结合,链式结构的栈。

        特点:

(1)不需要判断栈满(不受最大空间容量的限制)

(2)栈顶为链表的头部        

(二)栈与递归

        栈有一个重要的应用是在程序设计语言中实现递归。递归与栈有很多相似之处

        所谓递归是指,若在一个函数、过程或者数据结构定义的内部又直接(或间接)出现定义本身的应用,则称其是递归的,或者是递归定义的。在以下三种情况,常常使用递归的方法:

1.定义是递归的

 阶乘函数

求解代码(  n!)

long Fact(long n)
{if( n == 0) return 1;    // 递归终止条件else return n*Fact(n-1); // 递归步骤
}

二阶斐波那契数列

求解代码

long Fib(long n)
{if(n==1 || n==2) return 1;      //递归终止条件else return Fib(n-1)+Fib(n-2);  //递归步骤
}

计算4!时先计算3!,再进一步分解求解进行求解,这种分解求解的策略叫做“分治法

采取分治法的条件:

(1) 能将一个问题转变成一个新问题,并且新问题与原问题解法相同或相似。不同的仅是处理对象,并且其处理对象更小且变化规律。

(2)可以通过上述转化而使问题简化。

(3)必须有一个明确的递归出口,或者称递归的边界。

“分治法”求解递归问题算法的形式如下:

void p(参数表)
{if(递归结束条件成立) 可直接求解;    // 递归终止的条件else p(较小的参数);                // 递归步骤
}
2.数据结构是递归的

        链表

3.问题的解法是递归的

        有一类问题,问题本身没有明显的递归结构,但用递归求解比迭代求解更简单,经典如Hanoi(汉诺)塔问题、八皇后问题、迷宫问题等。

        

Hanoi塔问题的递归算法(问题不再赘述)

【算法步骤】

1.如果 n = 1, 则直接将编号为1的圆盘从塔座A移到塔座C,递归结束。

2.否则:

  • 递归,将塔座A上编号为1至n-1的圆盘移到塔座B,塔座C作为辅助塔座;
  • 直接将编号为n的圆盘从塔座A移到塔座C,塔座A为辅助塔座。
  • 递归,将塔座B上编号为1至n-1的圆盘移到塔座C,塔座A作为辅助塔座。
#include <stdio.h>int m=0;
void move(char A,int n,char C)
{printf("%d,%d,%c,%c\n",++m,n,A,C);
}void Hanoi(int n, char A,char B,char C)
{//将塔座A上的n个圆盘按规格搬到塔座C上,塔座B作为辅助塔座if(n == 1) move(A,1,C);else{Hanoi(n-1,A,C,B); //将塔座A上编号为1至n-1的圆盘移到塔座B,塔座C作为辅助塔座;move(A,n,C);      //直接将编号为n的圆盘从塔座A移到塔座C,塔座A为辅助塔座。Hanoi(n-1,B,A,C); //递归,将塔座B上编号为1至n-1的圆盘移到塔座C,塔座A作为辅助塔座。}}
int main()
{char A='A',B='B',C='C';Hanoi(2,A,B,C);return 0;
}
递归过程和递归工作站

        我们可以发现,当有多个函数构成嵌套调用时,按照“先调用后返回”的原则,上述函数之间的信息传递和控制必须通过“栈”来实现,系统将整个程序运行所需的数据空间安排在一个栈中,没调用一个函数就在栈顶为它分配一个储存空间,每从一个函数退出,就释放它的储存区。如此,当前正运行的函数的数据区必在栈顶。

递归算法的效率分析
        时间复杂度分析

        当一个算法中包含递归调用时,其时间复杂度的分析可以转化为一个递归求解方程,就是数学上求解渐近阶的问题。

        迭代法时求解递归方程的一种常用方法,其基本步骤是迭代地展开递归方程的右端,使之成为一个非递归的和式,然后通过对和式的估计来达到对方程左端的估计。

例如求阶乘函数Fact(4):(来自《数据结构》)

        设Fact(n)的执行时间是T(n)。此递归函数中语句: if( n == 0) return 1 的执行时间是o(1),递归调用Fact(n-1)的执行时间是T(n-1),所以else return n*Fact(n-1)的执行时间是O(1)+T(n-1)。其中,设两数相乘和赋值操作的时间为O(1),则对某常数C、D有如下递归方程:

 采用这种方法计算斐波那契数列和Hanoi塔问题递归算法的时间复杂度,可得到结果均为O(2^n)。

        空间复杂度分析

        S(n) = O(f(n))

        其中,f(n)为“递归工作栈”中工作记录的个数与问题规模n的函数关系。

        斐波那契数列和Hanoi塔问题递归算法的空间复杂度,可得到结果均为O(n)。

  

利用栈将递归转换为非递归的方法

        递归程序在执行时需要系统提供隐式栈这种数据结构来实现,对于一般的递归过程,仿照递归算法执行过程中递归栈的状态变换可直接写出相应的非递归算法,具体实例不再叙述,想要了解的直接可以去试试。

三、链表 

链表是一种递归的数据结构,学会链表的前提是你要懂得指针和结构体。

        有一串已经排好大小的数字“2 3 5 8 9 10 18 26 32”,现在要向这段数字中插入‘6’,使其新的序列仍符合从大到小排列,如果我们用数组来实现这一操作,则需要将8和8后面的数都依次往后挪一位,而对于链表,只需要将‘5’的指针指向‘6’,再将‘6’的指向‘8’就完成了插入。如下

       

        也正因为如此,链表相邻元素的地址可以是不连续的,就像发邮件,我不需要和你挨在一起,只需知道你的邮箱就行。所以链表中,我认为最重要的就是地址

        代码示例

        废话不多说,我们先来理解一个简单点的的链表代码:(大家可以结合注释看一下代码。代码实现的功能是将n个数字存入链表)

#include<stdio.h>
#include<stdlib.h>struct node //定义链表
{int data;struct node *next;
};int main()
{struct node *head, *p, *q, *t;int i,n,a;scanf("%d",&n);head =NULL;for ( i = 0; i < n; i++)  //将数据存入链表{scanf("%d",&a);p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址p ->data=a;                                    //将a存入这个地址p->next=NULL;                                  //分配下一个节点if (head ==NULL)        //如果这是创建的第一个节点,则将头指针指向这个节点,理解为火车头{head=p;printf("head=%d",head->data);}else                    //不是第一个节点,就与上一个节点连接,就是连接车厢,如果上节车厢为A,这节为B,那么就是把B连接到Aq->next=p;q=p;                    //保证下一个车厢连接的是B不是A// printf("%d \n",q->data);// t=q->next;// printf("%d\n",t->data);}t=head;while (t!=NULL){printf("%d ",t->data);//从火车头向后跑,把连接的车厢依次输出。t=t->next;}free(p);p=NULL;return 0;
}

         把这个理解了,链表的“增 删 改 查”就没有什么问题了。要理解最重要的是地址,这些步骤都是围绕将数字存入地址进行的,这几个结构体变量都是只能存储一个节点。链表是通过地址连接的。

结构体模板

struct node //定义链表
{int data;struct node *next;
};

         结构体只包含两个变量,一个是存储的数据data,另一个就是该节点的地址。多个节点相连接,便构成了链表。

头节点、首元节点、头指针

        这是链表中比较容易混淆的三个概念。

        (1)首元节点是指链表中储存第一个数据元素的结点,

        (2)头结点是首元结点之前附设的一个结点,其指针域指向首元结点。头结点的数据域可以不储存任何信息,也可储存与数据元素类型相同的其它附加信息。

        (3)头指针是指向链表中第一个节点的指针。若链表设有头结点,这头指针指向它,若没有则指向首元结点。

        本代码中,head为头指针,head指向的是首元结点,没有头结点。

前插法与后插法

        同样得到“1 2 3 4”,前插法需要输入“4 3 2 1”,后插法输入“1 2 3 4”.

        如果我们把第一个创建的结点称为第一结点。

        那么前插法便是将结点插入到第一结点的前面,而后插法就是将结点插入到第一结点的后面。

        

        本套代码便是后插法,多了一个尾指针q。

        正确代码

        下面我们展示一套代码往链表中插入‘6’。

#include<stdio.h>
#include<stdlib.h>struct node //定义链表
{int data;struct node *next;
};int main()
{struct node *head=NULL, *p=NULL, *q=NULL, *t=NULL;int i,n,a;scanf("%d",&n);head =NULL;for ( i = 0; i < n; i++)  //将数据存入链表{scanf("%d",&a);p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址p ->data=a;                                    //将a存入这个地址p->next=NULL;                                  //分配下一个节点if (head ==NULL)        //如果这是创建的第一个节点,则将头指针指向这个节点,理解为火车头{head=p;}else                    //不是第一个节点,就与上一个节点连接,就是连接车厢,如果上节车厢为A,这节为B,那么就是把B连接到Aq->next=p;q=p;                    //保证下一个车厢连接的是B不是A}t=head;                              while(t!=NULL)  //将数据存入链表{if (t->next == NULL || t->next->data > 6 )         //此节点是结尾或下一个节点大于6      {p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址p ->data=6;                                    //将6存入这个地址//******************* p->next=t->next;                               t->next=p;//*******************将存储6的节点插入链表break;}      t=t->next;  }t=head;while (t!=NULL){printf("%d ",t->data);//从火车头向后跑,把连接的车厢依次输出。t=t->next;}free(p);p=NULL;return 0;
}

这段代码与上一段代码就差了一段:(看注释理解)

    t=head;                              while(t!=NULL)  //将数据存入链表{if (t->next == NULL || t->next->data > 6 )         //此节点是结尾或下一个节点大于6      {p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址p ->data=6;                                    //将6存入这个地址//******************* p->next=t->next;                               t->next=p;//*******************将存储6的节点插入链表break;}      t=t->next;  }

 野指针问题

        再写代码的时候出现了一个问题:(大家注意这段代码与前面的差别)

 t=head;                              while(t!=NULL)  //将数据存入链表{if ( t->next->data > 6 || t->next == NULL )         //此节点是结尾或下一个节点大于6      {p=(struct node *)malloc(sizeof(struct node));  //分配动态内存,给p分配一个地址p ->data=6;                                    //将6存入这个地址//******************* p->next=t->next;                               t->next=p;//*******************将存储6的节点插入链表break;}      t=t->next;  }

        没错!就是if条件的改变,把||两端的条件调换了位置。

        如果大家不能看出来什么问题,不妨试试输入:

情况一:

3

1 2 3 

情况二: 

3

1 2 8 

        看看这两套代码有什么区别。情况一中,两套代码结果一样。 而情况二,第一套代码中正常输入结果,而第二套代码中就没有了结果,代码‘崩’了。

        在解决问题之前,大家首先要知道‘||’这个符号的作用:(A || B),如果A为真,便不会检查B了,情况二中,把“t->next->data > 6”放在了后面,运行中发现“t->next == NULL”为真,便不会执行“t->next->data > 6”了,而第二套代码会先执行“t->next->data > 6”。

        这就会导致野指针的问题出现。与下面这套代码相似:

#include<stdio.h>
#include<stdlib.h>int main()
{int *p;printf("%d",p);printf("这个不会输出!");return 0;
}

 造成这个原因就是*p是野指针,它指的是无效内存!具体原因参考博客:【C语言基础】野指针与空指针_野指针和空指针-CSDN博客

        删除6

    t=head; while (t!=NULL){if (t->next->data == 6){p=t->next;t->next=t->next->next;free(p);//释放删除节点空间}t=t->next;}

        将6改为7

    t=head; while (t!=NULL) //改{if (t->data == 6){t->data = 7;}t=t->next;}

        查找6在链表的哪一个节点。

    int s=1;t=head; while (t!=NULL) //查{if (t->data == 6){printf("在链表的第%d个节点\n",s);break;}s++;t=t->next;}

拓展

        模拟链表

        使用数组来实现链表,叫做模拟链表。通过两个数组实现,数值data[]存每个数,另一个right[]我认为可以理解为存储的下一个节点的地址,不过是是用序号来模拟地址。

        

        例如,data[1]地址是1,他的right是right[ 1 ]为2,即下一个节点的地址是2;

        我们把“6”插入,只需达到data[10]=6,'6'的地址是10,在5的右边现在的地址right[3]是4,改为10即可,将right[10]存为8的地址‘4’。

示例代码 

#include <stdio.h>int main()
{int data[101]={0},right[101]={0};int i=0,n=0,t=0,len=0;scanf("%d",&n);  //初始链表的个数for ( i = 1; i <= n; i++){scanf("%d",&data[i]); // 存入链表每个节点的数据}len=n;for (i = 0; i <=n; i++) //存入链表每个节点的模拟地址{if (i!= n){right[i]=i+1;}elseright[i]=0;}// 直接在数组data末尾增加一个数len++;scanf("%d",&data[len]);// 从链表头部开始遍历t=1;while (t!=0){if (data[right[t]]>data[len])//找到要插入的地方{right[len]=right[t];right[t]=len;break;}t=right[t]; //不能写t++}t=1;while (t!=0){printf("%d ",data[t]);t=right[t];}return 0;
}

循环链表

        与循环队列相似,整个链表形成一个环,由此从表中的任一结点出发均可找到表中其它结点。

        循环链表的操作与单链表一致,差别仅在于:当链表遍历时判别当前指针p是否向表尾结点的终止条件不同。在单链表中,判别条件为p!=NULL或 P->next !=NULL,而循环链表的为p

!=NULL或p->next !=NULL。

       我们还可以将两个或者多个链表连接在一起。将第一个表的尾指针指向第二个表的第一个结点,将第二个尾指针连接在第一链表的第一个结点就构成了一个循环链表。

        p= B->next->next;//B的第一个结点

        B->next = A->next;

        A -> next =p;

        时间复杂度为

双向链表 

        双向链表就是一个结点具有两个指针域,一个指向前结点,一个指向后结点。

struct node //定义链表
{int data;struct node *next;struct node *prior;
};

        双向链表的插入和删除需同时修改两个方向的指针。 

参考文章

C++——STL标准模板库——容器详解——stack+queue_stl栈-CSDN博客

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

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

相关文章

基于单片机的远程无线控制系统设计

摘 要 &#xff1a; 主要介绍了一种以单片机 AT89C2051 、 无线模块 APC200A-43 和继电器为核心的远程智能控制系统。 该系统通过对单片机功能的研究 &#xff0c; 使用单片机的输入输出口和中断实现对控制信号的处理&#xff0c; 通过调试无线通讯模块 &#xff0c; 控制…

Mybatis 快速入门(maven)

文章目录 需求建表新建了数据库但是navicat界面没有显示 新建maven项目 注意导入依赖 总结 黑马学习笔记 需求 建表 注意&#xff1a;设置字符集 减少出错 drop database mybatis; create database mybatis charset utf8; use mybatis;drop table if exists tb_user;create…

汇总区间计算

给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,b] 应该按…

深入理解Unreal中的AI感知系统

StimuliSource和PerceptionListener 感知作为一种信号&#xff0c;整个场景中存在这个信号的生产者和消费者。这种信号在Unreal中被叫做刺激Stimuli 生产者就是StimuliSource&#xff0c;通过挂载StimuliSourceComponent并RegisterForSense来注册成为哪些类型刺激的刺激源 消费…

Sui主网升级至V1.33.2

其他升级要点如下所示&#xff1a; #19404&#xff1a; 启用对等节点的共识轮次探测。 #19119&#xff1a; 无符号整数现在支持.to_string()方法&#xff0c;例如10u8.to_string()等同于 b”10".to_string()。 GraphQL #18774&#xff1a; 为GraphQL引入.move名称解…

【Python常用模块】_PyMySQL模块详解

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

9.深入线性回归推导出MSE——不容小觑的线性回归算法

引言 通过8.机器学习从线性回归开始——不容小觑的线性回归算法-CSDN博客的学习&#xff0c;我们已经掌握了线性回归模型的概念和基本原理。均方误差(MSE&#xff0c;Mean Squared Error)是评价线性回归模型性能的一个核心指标。 通过阅读本篇博客&#xff0c;你可以&#xf…

[NSSCTF 2022 Spring Recruit]ezgame

打开题目环境是一个游戏: 直接F12开始审计源代码&#xff1a; 这里说拿到65分以后可以得到flag&#xff0c;这里因为游戏太菜选择直接篡改分数&#xff1a; 在控制台输入scorePoin10000就可以改变当次得分&#xff0c;等到游戏结束就能得到flag。

基于springboot的驾校预约管理系统的设计与实现 (含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于springboot的驾校预约管理系统5拥有三种角色&#xff1a;管理员、教练、学员 管理员&#xff1a;学员管理、教练管理、车辆管理、车辆关联、维修管理等 教练&#xff1a;学员查看/毕…

阿里通义千问开源Qwen2.5系列模型:Qwen2-VL-72B媲美GPT-4

通义千问团队近日重磅宣布&#xff0c;继其Qwen2模型发布仅三个月后&#xff0c;再次推出Qwen家族的全新成员——Qwen2.5系列语言模型&#xff0c;并已全面开源。这次开源行动堪称史上规模之最&#xff0c;涵盖了通用型Qwen2.5&#xff0c;以及专为编程和数学领域打造的Qwen2.5…

消息队列-Kafka(概念篇)

1 为什么需要消息队列&#xff1f; 消息队列是一种基于消息的异步通信机制&#xff0c;用于在分布式系统中不同组件或服务之间传递数据和通知。实际上可以将消息队列看作为存放消息的容器&#xff0c;参与消息传递的分别称为生产者&#xff08;发送消息&#xff09;和消费者&am…

【网络】高级IO——epoll版本TCP服务器初阶

目录 前言 一&#xff0c;epoll的三个系统调用接口 1.1.epoll_create函数 1.1.1.epoll_create函数干了什么 1.2. epoll_ctl函数 1.2.1.epoll_ctl函数函数干了什么 1.3.epoll_wait函数 1.3.1.epoll_wait到底干了什么 1.4.epoll的工作过程中内核在干什么 二&#xff0c;…

北斗盒子TD20——水域巡检的安全卫士,落水报警保平安

随着我国水域经济的快速发展&#xff0c;水域巡检工作在保障水上安全、维护水域秩序方面发挥着越来越重要的作用。水域巡检是一项复杂且充满挑战的任务&#xff0c;无论是河流湖泊的日常巡逻&#xff0c;还是海洋环境的监测与保护&#xff0c;都需要高度的警惕和专业的技术支持…

目前最好的数据可视化项目!一个拖拽编辑的图表工具,上手快,酷炫十足,好用得尖叫!(带私活)

数据可视化在当下的工作当中被我们频繁提到或者是利用到&#xff0c;也是因为视觉仍然是目前人们从外界获取信息的主要来源途径。通过图表的处理&#xff0c;我们能够更快速的获取数据要传达的信息。 今天给大家推荐一款目前我见过最好的数据可视化项目-- AJ-Report。 AJ-Rep…

数据结构之二叉树遍历

二叉树的遍历 先序遍历 先输入父节点&#xff0c;再遍历左子树和右子树&#xff1a;A、B、D、E、C、F、G 中序遍历 先遍历左子树&#xff0c;再输出父节点&#xff0c;再遍历右子树&#xff1a;D、B、E、A、F、C、G 后序遍历 先遍历左子树&#xff0c;再遍历右子树&#xff0c;…

攻防世界--->debug

前言&#xff1a; 我的下载。 ILSpy&#xff1a;&#xff08;静调&#xff09; ILSpy下载 ILSpy反编译软件 v9.0.0.7618 中文绿色免费版 下载-脚本之家 (jb51.net)https://www.jb51.net/softs/137337.html dnSpy&#xff1a;&#xff08;动调&#xff09; dnspy v6.5.1 (2…

【JSrpc破解前端加密问题】

目录 一、背景 二、项目介绍 三、JSrpc 处理前端加密步骤 一、背景 解决日常渗透测试、红蓝对抗中的前端密码加密问题&#xff0c;让你的爆破更加丝滑&#xff1b;降低js逆向加密的难度&#xff0c;降低前端加密逻辑分析工作量和难度。 二、项目介绍 运行服务器程序和js脚本…

TC8:SOMEIP_ETS_009-019

SOMEIP_ETS_009: echoENUM 目的 检查枚举类型的参数及其顺序能够被顺利地发送和接收 测试步骤 1、Tester:创建SOME/IP消息 2、Tester:使用method echoENUM发送SOME/IP消息 3、DUT:返回method响应消息,其数据(值和顺序)与请求中的相同 期望结果 3、DUT:返回method响应消…

为什么说开放式比入耳式耳机更受欢迎?精选开放式耳机推荐

在音频设备不断推陈出新的今天&#xff0c;开放式耳机以其别样的魅力走进人们的视野。与传统入耳式耳机不同&#xff0c;开放式耳机在多个方面独具优势。它能带来清晰自然的音质&#xff0c;让音乐的每一个细节都生动呈现。佩戴上&#xff0c;它给予耳朵充分的自由空间&#xf…

ABAP-Logger ABAP 日志记录与任何其他语言一样轻松

ABAP-Logger ABAP 日志记录与任何其他语言一样轻松 ABAP Logger SAP Logging as painless as any other language. ABAP Version: 702 or higher See the mission statement Features Record message in Application Log(BC-SRV-BAL)Display message Installation Insta…