当前位置: 首页 > news >正文

c++笔记2

目录

2.2 栈底(bottom)

}

大数乘大数

节点:包含一个数据元素及若干指向子树分支的信息 。

节点的度:一个节点拥有子树的数目称为节点的度 。

叶子节点:也称为终端节点,没有子树的节点或者度为零的节点。

分支节点:也称为非终端节点,度不为零的节点称为非终端节点 。

树的度:树中所有节点的度的最大值。

节点的层次:从根节点开始,假设根节点为第1层,根节点的子节点为第2层,依此类推,如果某一个节点位于第L层,则其子节点位于第L+1层 。

树的深度:也称为树的高度,树中所有节点的层次最大值称为树的深度 。

有序树:如果树中各棵子树的次序是有先后次序,则称该树为有序树。

无序树:如果树中各棵子树的次序没有先后次序,则称该树为无序树。

森林:由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根节点删除,则该树就变成了一片森林,森林中的树由原来根节点的各棵子树构成。

二叉树的节点数量

二叉树的遍历

先序遍历

中序遍历

二叉树遍历的总结

特殊的二叉树

二叉搜索树

平衡二叉树

二分查找

二分查找也称折半查找( Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

二分查找的基本思想

STL与二分查找

upper_bound

lower_bound

二分查找的适用条件

二分答案的思想

二分答案的技巧

二分答案适用的条件

链表的分类

单向链表

链表的常用操作

查找操作

插入操作 链表与数组

数组的插入

数组的删除

链表的应用

Vector

遍历vector

队列

map的功能

使用map

定义

set的定义

添加元素

正序遍历

set s;

set::iterator it;

for (it = s.begin(); it != s.end(); it++)

cout << *it << endl;

反序遍历

容器与迭代器

vector,stack,queue,map,set都是stl提供的标准数据结构,他们共同的特点是都是容器,用来存储元素集合。为了统一他们的使用方法,stl提供了迭代器。

康托展开

康托展开运算

康托展开逆运算

康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在给出61可以算出起排列组合为34152。由上述的计算过程逆推回来,具体过程如下:

位运算

“按位与”运算符(&)

左移运算符(<<)

右移运算符(>>)

顾名思义,枚举便是依次列举出所有可能产生的结果,根据题中的条件对所得的结果进行逐一的判断,过滤掉那些不符合要求的,保留那些符合要求的,也可以称之为暴力算法。

枚举法的优缺点

时间复杂度

空间复杂度

常见算法复杂度

复杂度分析基础

复杂度分析进阶

空间换时间

常用的空间换时间方法

前缀和优化

ST表

XOR的特性

尺取法

概念   尺取法也常被称为双指针,是很常用的一种优化算法。通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。尺取法比直接暴力枚举区间效率高很多。

适用条件

贪心算法

贪心算法进阶

贪心的适用条件


stack的基本操作

C++的标准库中, 有封装好的栈 stack , stack 是一个模板类,定义stack的示例代码如下:

stack<类型> 对象:

stack<int>  s;

一种常见的数据结构,遵循后进先出,先进后出的原则。有一个连续的内存区域组成,栈顶(top)

线性表允许插入和删除的那一段。值得注意的是,栈顶指针top的指向是有些两种方式的,一种是指向栈顶当前元素,一种是指向栈顶的下一位置。两种指向方式对栈的操作影响不大,只是在判断栈顶位置的时候略有差异,本文以指向当前栈顶元素为例。另一种指向方式读者可以自行实践。

2.2 栈底(bottom)

固定的,不允许进行插入和删除的另一端。

进栈 push top>=n; Top++; 退栈pop

Top<=n; Top--;

#define n 100

int top=0,x;

void push(int 2[],x){

if(top==n)

else top++;

s[top]=x;

}

void pop(int s[]){

if(top==0) {

cout<<"underflow";

}

Else{top--;

} }

int stackArray[1000], index = -1;

void push(int x) {

    index++;

    stackArray[index] = x;

}

int pop() {

    int value = stackArray[index];

    index--;

    return value;

}

int top( ) {

    return stackArray[index];

}

int size( ) {

    return index + 1;

}

int main( ) {

    push(1);

    push(2);

    push(3);

    cout << "顶:" << top() << endl;

    cout << "大小:" << size() << endl;

    pop();

    cout << "顶:" << top() << endl;

    cout << "大小:" << size() << endl;

    pop();

    cout << "顶:" << top() << endl;

    cout << "大小:" << size() << endl;

    pop();

    return 0;

}

操作

代码

解释

入栈

s.push(x)

将x元素入栈

出栈

s.pop()

弹出栈的第一个元素,并不会返回元素的值

栈顶元素

s.top()

获取栈的第一个元素

元素个数

s.size()

获取栈中的元素个数,返回int

判空

s.empty()

栈是否为空,返回bool,相当于s.size() == 0

汉诺塔

void hanoi(int n,char a,char b,char c){

if(n==1){

cout<<a<<"-->"<<c<<endl;

}else{

hanoi(n-1,a,c,b);

cout<<a<<"-->"<<c<<endl;

hanoi(n-1,b,a,c); }}

int main(){

int n; cin>>n;

hanoi(n,'A','B','C');

return 0;}

高精度数(大数)

分类

整形数组

字符数组

储存方式

按字符串输入,通过循环语句,将字符转换位数字

直接输入,以字符方式存储

获取位数

计算每个数组的元素位数再相加

strlen( )函数

输出

采用循环语句打印数组元素

字符串输出,方便

运算

直接计算

转化为数字后计算

Strlen    Strrev

高精度加法

#include <bits/stdc++.h>

using namespace std;

int main() {

char a1[5005], b1[5005];

int a[5005], b[5005], c[5005];

int lena, lenb, lenc = 1, x, i;

memset(a, 0, sizeof(a));

memset(b, 0, sizeof(b));

memset(c, 0, sizeof(c));

cin>>a1>>b1;

lena = strlen(a1);

lenb = strlen(b1);

for(i=0; i<lena; i++) a[lena - i] = a1[i] - '0';

for(i=0; i<lenb; i++) b[lenb - i] = b1[i] - '0';

x = 0;

while(lenc <= lena || lenc <= lenb) {

c[lenc] = a[lenc] + b[lenc] + x;

x = c[lenc] / 10;

c[lenc] %= 10;

lenc++;

}

c[lenc] = x;

if(c[lenc] == 0) {

lenc--;

}

for(int i=lenc; i>=1; i--) {

cout<< c[i];

}

return 0;

}高精度减法

strcmp 比较compare

Strcpy 复制copy

char a1[1001]={},b1[1001]={},c1[1001]={};

int a[1001]={},b[1001]={},c[1001]={};

int lena,lenb,lenc=0,i;

cin>>a1>>b1;

lena=strlen(a1);

lenb=strlen(b1);

if(strlen(a1)<strlen(b1)||(strlen(a1)==strlen(b1)&&strcmp(a1,b1)<0)){

strcpy(c1,a1);

strcpy(a1,b1);

strcpy(b1,c1);

cout<<"-";

}

lena=strlen(a1);

lenb=strlen(b1);

for(i=0;i<lena;i++){

a[lena-i-1]=a1[i]-48;

}

for(i=0;i<lenb;i++){

b[lenb-i-1]=b1[i]-48;

}

i=0;

while(i<lena||i<lenb){

if(a[i]<b[i]){

a[i]+=10;

a[i+1]--;

}

c[i]=a[i]-b[i];

i++;

}

lenc=i;

while((c[lenc]==0)&&(lenc>1)){

lenc--;

}

for(i=lenc;i>=0;i--){

cout<<c[i];

}

cout<<endl;

高精度乘法

  1. 输入被乘数和乘数。
  2. 将char类型的被乘数转化为int 类型。
  3. 将乘数与被乘数各个位相乘计算,并处理进位。
  4. 输出乘积。

(多位乘一位)

char a[201]={};

int a1[201],b1[202],b,x=0,s;

cin>>a>>b;

if(b==0){

cout<<0;

return 0;

}

int len=strlen(a);

for(int i=0;i<len;i++){

a1[i+1]=a[i]-48;

}

for(int i=len;i>=0;i--){

s=a1[i]*b+x;

b1[i]=s%10;

x=s/10;

}

int i=0;

while(b1[i]==0 &&i<len){

i++;

}

for(i;i<=len;i++){

cout<<b1[i];

}

大数乘大数

当乘数和被乘数都是大数时,套用上面大数乘 intint 的方法,逐个计算并进位,最终合并相加。

例如: 6789854321238763×77198343712321636789854321238763×7719834371232163

转换后

6789854321238763

6789|8543|2123|8763

7719834371232163

7719|8343|7123|2163

8763

67641597|73109709|62418849|18954369

进位

6764|8908|5951|0744|4369

2123

16387437|17712189|15122129|4592049

进位

1638|9208|3701|2588|2049

8543

65943417|71274249|60851789|18478509

进位

6595|0545|0334|3636|8509

6789

52404291|56640627|48358047|14684607

进位

5240|9955|5462|9515|4607

最后进行位移相加:

6764

8908

5951

0744

4369

1638

9208

3701

2588

2049

6595

0545

0334

3636

8509

++

5240

9955

5462

9515

4607

5241

6550

7647

5823

0853

7048

2793

4369

多位乘多位

int main(){

char a1[1001]={},b1[1001]={};

int a[1001]={},b[1001]={},c[2002]={};

cin>>a1>>b1;

int lena=strlen(a1);

int lenb=strlen(b1);

for(int i=0;i<lena;i++){

a[lena-i]=a1[i]-48;

}

for(int i=0;i<lenb;i++){

b[lenb-i]=b1[i]-48;

}

for(int i=1;i<=lenb;i++){

int x=0;

for(int j=1;j<=lena;j++){

c[j+i-1]=a[j]*b[i]+x+c[i+j-1];

x=c[i+j-1]/10;

c[i+j-1]%=10;

}

c[i+lena]=x;

}

int lenc=lena+lenb;

while(c[lenc]==0&&lenc>1){

lenc--;

}

for(int i=lenc;i>0;i--){

cout<<c[i];

}

return 0;

}

两个数相乘积不可能超过两数位数之和。 a1[1001]={ },b1[1001]={ },c[2002]={}

高精度除法

(高精除高精)

#include<bits/stdc++.h>

using namespace std;

int a[101],b[101],c[101],d,i;

void init(int a[]){

string s;

cin>>s;

a[0]=s.length();

for(i=1;i<=a[0];i++)

a[i]=s[a[0]-i]-'0';

}

void print(int a[]){

if(a[0]==0) {

cout<<0<<endl;

return ;

}

for(int i=a[0];i>0;i--) cout<<a[i];

cout<<endl;

return ;

}

int compare(int a[],int b[]){

if(a[0]>b[0]) return 1;

if(a[0]<b[0]) return -1;

for(int i=a[0];i>0;i--){

if(a[i]>b[i]) return 1;

if(a[i]<b[i]) return -1;

}

return 0;

}

void jian(int a[],int b[]){

int flag,i;

flag=compare(a,b);

if(flag==0) {a[0]=0;return;}

if(flag==1){

for(i=1;i<=a[0];i++){

if(a[i]<b[i]){

a[i+1]--;

a[i]+=10;

}

a[i]-=b[i];

}

while(a[0]>0&&a[a[0]]==0) a[0]--;

return;

}

}

void numcpy(int p[],int q[],int det){

for(int i=1;i<=p[0];i++)q[i+det-1]=p[i];

q[0]=p[0]+det-1;

}

void chu(int a[],int b[],int c[]){

int i,tmp[101];

c[0]=a[0]-b[0]+1;

for(i=c[0];i>0;i--){

memset(tmp,0,sizeof(tmp));

numcpy(b,tmp,i);

while(compare(a,tmp)>=0){

c[i]++;

jian(a,tmp);

}

}

while(c[0]>0&&c[c[0]]==0) c[0]--;

return ;

}

int main(){

memset(a,0,sizeof(a));

memset(b,0,sizeof(b));

memset(c,0,sizeof(c));

init(a);

init(b);

chu(a,b,c);

print(c);

print(a);

return 0;

}排序

冒泡排序 快速排序 选择排序 二叉树排序

插入排序 堆排排序

选择排序

每一趟从待排序的数据元素中选出最大(或最小)的一个元素,顺序放在待排序的数列的最前面,直到全部待排序的数据元素排完。

  1. 读入数据存放在a数组中。
  2. 在a[1]~a[n]中选择值最小的元素,与第1位置元素进行交换,则把最小值元素放入a[1]中。
  3. 在a[2]~a[n]中选择值最小的元素,与第2位置元素进行交换,则把最小元素放入a[2]中。
  4. 知道第n-1个元素与第n 个元素比较排序为止。

int n,k,i,j;

int a[1001];

cin>>n;

for(i=0;i<n;i++){

cin>>a[i];

}

for(i=0;i<n;i++){

k=i;

for(j=i+1;j<n;j++){

if(a[j]<a[k]){

k=j;

}

}

if(k!=i){

swap(a[i],a[k]);

}

}

for(i=0;i<n;i++){

cout<<a[i]<<" ";

}

冒泡排序

  1. 读入数据存放在a数组中。
  2. 比较相邻的前后两个数据,如果前面数据大于后面的数据,就将两个数据交换。
  3. 对数组的第0个数据到n-1个数据进行一次遍历后,最大的一个数据就“冒”到数组第n-1个位置。
  4. 重复前面第二步,至道排序完成。

int n,k,i,j;

int a[1001];

cin>>n;

for(i=0;i<n;i++){

cin>>a[i];

}

for(i=n-1;i>=1;i--){

for(j=0;j<i;j++){

if(a[j]>a[j+1]){

swap(a[j],a[j+1]);

}}}

for(i=0;i<n;i++){

cout<<a[i]<<" ";

}

6

5

3

4

1

2

5

6

3

4

1

2

5

3

6

4

1

2

5

3

4

6

1

2

5

3

4

1

6

2

5

3

4

1

2

6

邻接数组表示法

图:是一种非线性的数据结构,有多点连接在一起。图是由有限个数的顶点和顶点之间边的组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。

无向图:在途中任一顶点上的边都是没有方向性的。

无向边的表示:用圆括号将两端的顶点括起来(Vi,Vj).

有向图:在图中任意顶点上的边都是有方向的。

有向边的表示:用尖括号将边两边的顶点括起来<Vi,Vj>

有向完全图:有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧。含有n个顶点的有向完全图有n*(n-1)条边。

无向完全图:无向图中,任意两个顶点之间都存在边。含有n个顶点的无向完全图有n*(n-1)/2条边。

子图图形中取出的部分集合。

:顶点V的度是和V相关联的边的数目。

入度:有向图中以顶点V为终点的有向图边的数目。

出度:有向图中以顶点V为起点的有向边的数目。

权值:边的“费用”,可以形象的理解为边的长度。

回路起点和终点相同的路径,称为回路。或“环”

连通图形:在无向图中,任意两个顶点皆连通,则称为连通图形。即任意两个顶点皆存在一条路径可到达。

强连通图形:在有向图中,任意两个顶点间皆存在一条路径可到对方,则称为强连通图形。

强连通分量有向图中任意两点都连通的最大子图包括单个顶点。

邻接数组

邻接数组表示法是以一个n*n的数组来表示一个具有n个顶点的图形,我们以数组的索引值来表示顶点,以数组的内容值来表示顶点间的边是否存在。如图所示:

0

1

2

3

4

0

0

1

1

0

0

1

1

0

1

1

1

2

1

1

0

1

0

3

0

1

1

0

0

4

0

1

0

0

0

遍历:从图中某一顶点出发系统的访问图中的所有顶点,是每个顶点恰好被访问一边。

由n(>0)个元素组成的有限集合,每个元素称为结点有一个特定的结点,称为根结点

除了根节点外,其余节点称为子树一个结点的子树个数称为这个结点的。度为0的结点称为叶节点.

二叉树度为。二叉树的每个结点最多有两个结点。

在二叉树的第i层上最多有(2i-1)个结点。(i>=1)

深度为k的二叉数至多有(2k-1)个结点。(k>=1)

满二叉树:除最后一层无结点外,每一层上的所有结点都有两个子结点。在满二叉树中,每一层上的结点数都达到最大值。

完全二叉树:除最后一层外,每一层上的结点数均达最大值,在最后一层上只缺少右边的若干结点。


二叉树

在计算机科学中,二叉树是每个节点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”。

节点

左子树

右子树

0

子树 1

子树 7

1

子树 2

子树 6

2

链表也可以看作是一棵特殊的二叉树,因为链表每个结点只有一个子节点,满足二叉树的定义。二叉树与普通树的差别在于,普通树可以有 22 个以上的子节点,而二叉树的子节点不能超过 22 个。

链表

二叉树

后继节点

1个

不超过 2个

任意个

定义关系

链表属于二叉树

二叉树属于树

节点:包含一个数据元素及若干指向子树分支的信息 。

节点的度:一个节点拥有子树的数目称为节点的度 。

叶子节点:也称为终端节点,没有子树的节点或者度为零的节点。

分支节点:也称为非终端节点,度不为零的节点称为非终端节点 。

树的度:树中所有节点的度的最大值。

节点的层次:从根节点开始,假设根节点为第1层,根节点的子节点为第2层,依此类推,如果某一个节点位于第L层,则其子节点位于第L+1层 。

树的深度:也称为树的高度,树中所有节点的层次最大值称为树的深度 。

有序树:如果树中各棵子树的次序是有先后次序,则称该树为有序树。

无序树:如果树中各棵子树的次序没有先后次序,则称该树为无序树。

森林:由m(m≥0)棵互不相交的树构成一片森林。如果把一棵非空的树的根节点删除,则该树就变成了一片森林,森林中的树由原来根节点的各棵子树构成。

二叉树的节点数量

一棵高度为n的二叉树,最多包括2n−1个节点。树高为n,共有n 层,第一层有1 个根节点,根据二叉树的定义,后面每层节点的数量最多为上一层的2 倍,因此最多有:1+2+4+....+2n−1=2n−1个节点。

上图为一棵高度为3的二叉树,共有7个节点。

那么一棵高度为n的二叉树,最少包括多少个节点呢?最少有n个节点,是一个链表。

二叉树的存储

可以继续沿用存储树的方法,来存储二叉树。

struct node{

    int value;

    vector<int> childs; //用来记录所有子节点的编号

}nodes[10000];

int root;

//root为根节点

也可以利用二叉树的特性,用左右子树的方式,来存储二叉树。

用结构体来表示二叉树的节点, 每个节点存储了当前节点的值, 以及左子树和右子树的编号。

struct node{

    int value;

    int left;

    int right;

}nodes[10000];

int root; //root为根节点

二叉树的遍历

遍历二叉树的方法可以沿用遍历树的方法——递归。

DFS(当前节点u){

    DFS(u.left);

    DFS(u.right);

}

在此基础上,二叉树的遍历又分为以下三种:

顺序

先序遍历

根左右

27,14,10,19,35,31,42

中序遍历

左根右

10,14,19,27,31,35,42

后序遍历

左右根

10,19,14,31,42,35,27

先序遍历

遍历顺序规则为:根左右,遍历方法:

(1)访问根节点

(2)采用先序递归遍历左子树

(3)采用先序递归遍历右子树

先序遍历结果:ABDFECGHI

中序遍历

遍历顺序规则为:左根右,遍历方法:

(1)采用中序遍历左子树

(2)访问根节点

(3)采用中序遍历右子树

中序遍历结果: DBEF A GHCI

二叉树遍历的总结

三种方法遍历过程中经过节点的路线一样,只是访问各个节点的顺序不同。

二叉树的先序、中序、后序遍历我们已经学会了, 那么给定其中任意两种遍历, 我们能否推出唯一的第三种遍历么?

答案:给定先序+中序可以推出后序。后序+中序可以推出先序。但是先序+后序是无法推出中序的。

这里我们只是以:知道先序遍历和中序遍历,推断后序遍历作为例子,其他组合方式原理是一样的。

要完成这个任务,我们首先要利用以下几个特性:

特性A,对于先序遍历,第一个肯定是根节点

特性B,对于后序遍历,最后一个肯定是根节点

特性C,利用先序或后序遍历,确定根节点,在中序遍历中,根节点的两边就可以分出左子树和右子树;

特性D,对左子树和右子树分别做前面3点的分析和拆分,相当于做递归,我们就可以重建出完整的二叉树。

所以我们可以靠保存先序+中序,或者后序+中序2个顺序,来保存整个二叉树的结构。

以下为先序遍历结果为1,2,3的所有二叉树。

编号

中序

后序

1

3,2,1

3,2,1

2

1,2,3

3,2,1

3

2,3,1

3,2,1

4

2,1,3

2,3,1

5

1,3,2

3,2,1

上面的表格中,所有的中序遍历结果都不相同,因此给出中序就可以确定是哪棵二叉树。

但后序遍历的结果有重复的情况,因此如果给出后序遍历的结果,并不能确定对应的是哪一棵二叉树。

特殊的二叉树

二叉搜索树

二叉搜索树(Binary Search Tree),(又:二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若它的右子树不空,则右子树上所有节点的值均大于它的根节点的值; 它的左、右子树也分别为二叉搜索树。

上图为二叉搜索树,以子树20为例,左子树的节点包括:3,14。均小于20,右子树的节点包括:35。大于20。二叉搜索树是特殊的二叉树,因为除了满足二叉的条件之外,还满足节点有序。二叉搜索树有许多扩展,被用于各类数据结构。因为在二叉查找树中,找一个符合条件的值,时间复杂度同这个树的深度相关。而我们知道,假如这个二叉搜索树的每一个节点都是满的,其所有叶子节点的深度都是 d ,那么这棵树的节点数量可以达到O(2d)。这时查找的复杂度为O(log(n))。与我们之前学习的二分查找复杂度相同。能够快速查找数据也是二叉搜索树被用于各类数据结构的原因,但这种快速是有限制的,即不会出现退化,假如二叉搜索树退化为链表,则无法做到快速查找数据的要求,所以又出现了平衡二叉树。

平衡二叉树

平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

平衡二叉树有效的限制了树高,使得二叉树不会退化为一个链表。

平衡二叉树如果同时也是一棵二叉搜索树,那么查找某个节点的效率是O(log(n))的。

很多数据结构,不能完美的做到平衡,但也能有效的控制树高,例如:红黑树。也被经常用于数据的快速查找。

  1. 根据后序找出根节点e。
  2. 根据中序确定左右子树。
  3. 确定左右子树根节点

满二叉数有2k-1个结点

struct node{

char value;

int left,right;

}data[101];

int root=0,cnt;

char ch;

int buildTree(int bt){

cin>>ch;

if(ch=='.'){

bt==0;

return bt;

}else{

bt=++cnt;

data[bt].value=ch;

data[bt].left=data[bt].right=0;

data[bt].left=buildTree(bt);

data[bt].right=buildTree(bt);

}

return bt;

}

void postorder(int bt){

if(bt){

postorder(data[bt].left);

postorder(data[bt].right);

cout<<data[bt].value;

}

}

int main(){

root=0;

cnt=0;

root=buildTree(0);

postorder(root);

return 0;

}

共同特点

  1. 最优子结构

母问题的最优解包含其子问题的最优解,我们就称此问题具有最优子结构

  1. 子问题重叠

子问题本质上是和母问题一样的,只是问题的输入参数不一样,就可以称之为子问题重叠。  这类问题的解决途径------【动态规划】

背包问题

int w[200],c[200],f[200][200];

int m,n;

cin>>m>>n;

for(int i=1;i<=n;i++){

cin>>w[i]>>c[i];

}

for(int i=1;i<=n;i++){

for(int j=1;j<=m;j++){

if(j>=w[i]){

f[i][j]=max(f[i-1][j-w[i]]+c[i],f[i-1][j]);

}else{

f[i][j]=f[i-1][j];

}

}

}

cout<<f[n][m];

算法

算法是对特定问题求解步骤的一种描述,他是指令的有限序列,其中那个每一条指令表示一个或多个操作。简单来说,算法就是解决问题的操作步骤。

1有穷性 2确定性 3输入 4输出 5可行性

时间复杂度 :计算工作量

空间复杂度 :内存空间

结构:顺序结构 选择结构 循环结构

算法

  1. 划分阶段:按照问题的特征,把问题分为若干个阶段。
  2. 确定状态和状态变量将问题发展到各个阶段所处的各种情况用不同的状态表示出来。
  3. 确定决策并写出状态转移方程:根据相邻状态之间的关系确定决策。
  4. 寻找边界条件:递推式的边界条件。

特征

具体描述

有穷性

对于任意一组合法的输入值,算法的每个操做步骤都蒙在有限时间内完成

确定性

算法中的每一步都必须是有明确的定义,不允许歧义性和多义性

输入

一个算法应该有0个或多个输入,以刻画预算对象的初始情况。

输出

一个算法应该有一个或多个输出,以反映对输入数据加工后的结果

可行性

在特定的接替规则中允许使用的,可执行的并可以通过执行有限次来实现。

二分法

每次找一半 优先级:括号>非>与>或、异或。

int maxline=200,kz;

int reverse(char s[]){

int i,j,t;

for(i=0,j=strlen(s)-1;i<j;i++,j--){

t=s[i];s[i]=s[j];s[j]=t;

}

return 0;}

int main(){

char line[100];

cin>>kz;

while(kz!=-1){

cin>>line;

reverse(line);

cout<<line<<endl;

cin>>kz; }

return 0;}

二分查找

二分查找也称折半查找( Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

给一个不重复有序的数组, 要找到某一个数出现的位置, 最朴素的做法就是遍历一遍数组, 复杂度是O(n),但是通过二分查找, 可以在O(log(n))的复杂度下完成查找。如数组1,2,3,4,5,6,7,8,9 ,查找元素6,用二分查找的算法执行的话,其顺序为:第一步查找中间元素,即5,由于5<6,则 6 必然在5之后的数组元素中,那么就在6,7,8,9中查找,寻找6,7,8,9 的中位数,为 7 , 7>6 ,则6应该在7左边的数组元素中,那么只剩下6,即找到了。

二分查找的基本思想

A想好一个100以内的自然数,由B来猜。每次A都会告诉B猜大了还是小了,或者猜对了。B的最优策略就是先猜50,如果大了再猜25,如果小了则猜75。这就用到了二分查找的思想。设 R[low..high]是当前的查找区间首先确定该区间的中点位置:mid=⌊(low+high)/2⌋。然后将待查的K值与 a[mid] 比较:若相等,则查找成功并返回此位置,否则须确定新的查找区间,继续二分查找,具体方法如下:若a[mid]>K,则由表的有序性可知a[mid..n]均大于K,因此若表中存在关键字等于K的结点,则该结点必定是在位置 mid 左边的子表a[low..mid−1]中,故新的查找区间是左子表R[low..mid−1]。若a[mid]<K,则要查找的K必在mid 的右子表R[mid+1..high]中,即新的查找区间是右子表 R[mid+1..high]。下一次查找是针对新的查找区间进行的因此,从初始的查找区间R[1..n]开始,每经过一次与当前查找区间的中点位置上的结点关键字的比较,就可确定查找是否成功,不成功则当前的查找区间就缩小一半。这一过程重复直至找到关键字为 KK 的结点,或者直至当前的查找区间为空(即查找失败)时为止。 因为每次都可以将区间大小缩小一半,所以最多只需log(n)log(n) 次就可完成查找,所以二分查找的复杂度是O(log(n))

int Binary_Search(int a[], int n, int key){

    int low, high, mid;

    low = 1;     /*定义最底下标为记录首位*/

    high = n;    /*定义最高下标为记录末位*/

    while (low <= high)

    {

        mid = (low + high) / 2;    /*折半*/

        if (key < a[mid])

            high = mid - 1;

        else if (key > a[mid])

            low = mid + 1;

        else

            return mid;

    }

    return -1; // 没找到  

}

STL与二分查找

upper_bound

upper_bound() 是 C++C++ 标准库提供的二分查找函数,可以在一个排好序的数组中,对指定数值进行查找。

upper_bound(begin,end,num)会从数组的 begin 位置到 end−1 位置二分查找第一个大于num的数字。同排序sort函数一样,upper_bound()提供了一个重载函数,允许我们自己定义什么是大于,upper_bound(begin,end,num,cmp)。通过cmp函数定于大于或小于。需要注意:如果原数组不是通过 cmp 进行排序的,则不符合二分的条件,因为二分查找过程中,会认为原数组是无序的。upper_bound(),最终返回的是一个地址,即第一个大于num 的数字的位置,如果不存在则返回end 。通过返回的地址减去起始地址begin ,得到找到数字在数组中的下标。

int main(){

    int num[6]={1,2,4,7,15,34};

    sort(num, num + 6);                           //按从小到大排序

    int pos = upper_bound(num, num + 6, 7) - num;//返回数组中第一个大于被查数的值

    cout << pos << " " << num[pos] << endl;

    return 0;    

}

输出结果如下:4 15

使用自定义的比较函数 cmpcmp 。

int cmp(int a,int b){

    return a > b;

}

int main(){

    int num[6]={34, 15, 7, 4, 2, 1};

    sort(num, num + 6, cmp);                           //按从大到小排序

    int pos=upper_bound(num, num+6, 7, cmp) - num; //返回数组中第一个小于被查数的值

    cout << pos << " " << num[pos] << endl;

    return 0;    

}

输出结果:3 4

lower_bound

lower_bound()C++标准库提供的另一个二分查找函数,与upper_bound() ,lower_bound(begin,end,num)会从数组的begin位置到end−1 位置二分查找第一个大于或等于num的数字。

lower_bound()也提供了一个重载函数,允许我们自己定义什么是大于,lower_bound(begin,end,num,cmp) 。

数组

返回

upper_bound()

必须有序

大于 numnum 的数

lower_bound()

必须有序

大于或等于 numnum 的数

int main(){

    int num[6]={1,2,4,7,15,34};

    sort(num,num+6);                           //按从小到大排序

    int pos=lower_bound(num, num+6,7) - num;  //返回数组中第一个大于或等于被查数的值

    cout<<pos<<" "<< num[pos]<<endl;

    return 0;    

}

输出结果:3 7

使用自定义的比较函数 cmpcmp 。

int cmp(int a,int b){

    return a > b;

}

int main(){

    int num[6]={34, 15, 7, 4, 2, 1};

    sort(num, num + 6, cmp);                           //按从大到小排序

    int pos = lower_bound(num, num + 6, 7, cmp) - num; //返回数组中第一个小于或等于被查数的值

    cout << pos << " " << num[pos] << endl;

    return 0;    

}

输出结果:2 7

二分查找的适用条件

虽然二分查找的效率高,但是要将表按关键字排序。而排序本身是一种很费时的运算。如果所给的数组无序的,则需要先用的复杂度将数组进行排序,才能完成二分查找。

二分答案的思想

二分答案就是对最终答案的值进行二分,具体做法就是每次取一个中间答案, 把答案作为已知条件, 判断根据当前答案是否满足题意, 根据判断结果再将区间缩小。写法和二分查找基本相同, 只不过要判断的东西比较复杂, 一般写法是通过写一个\(check\) 函数来实现。

二分答案的技巧

二分答案通常是在寻找“合法”与“不合法”的边界,进一步说,是在寻找“刚好合法”的那个位置。二分离不开单调性,我们做这类题目最重要的就是要找到题目中的某个与“合法性”有单调关系的量。单调关系通常可以表现为:A越大,越合法,A越小,越不合法。在题目中总结出这种单调关系,十分有助于进一步思考。

二分答案适用的条件

一个问题如果可以用二分答案的方法来解决,一定要满足某种单调性。这样通过二分答案,我们可以将一个本来需要复杂计算的问题,转为一个判定性问题,即成立或不成立,没有严格的单调性保障,二分答案可能会给出错误的答案。

链表

链表是一种物理存储单元上非连续,非顺序的储存结构,数据元素的逻辑顺序是通过链表中的指针连续实现的。

单向链表:数据以节点来表示(只能一个方向)

双向链表:每个数据节点中都有两个指针,(可以两个方向)

循环链表:表中最后一个结点的指针域指向头节点,整个链表形成一个环。

 数组是在内存中一段连续的存储空间,

 可以在常数时间内访问任意位置的元素, 但是数组也有缺点, 无法做到快速的插入和删除, 因为空间是连续且固定的, 想要在 p 位置插入/删除一个元素, 则 p 之后的位置的元素都需要移动。

为了能够在常数时间内实现元素的插入和删除, 我们引入 链表 这种数据结构。

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

那么非连续、非线性有什么含义呢?这表明链表的内存是不连续的,前一个元素存储地址的下一个地址中存储的不一定是下一个元素。链表通过一个指向下一个元素地址的引用将链表中的元素串起来。

链表的分类

分类

描述

单向链表

每一个节点包含了数据块和指向下一个节点的指针

双向链表

每一个节点包含了数据块和指向下一个节点的指针以及指向前一个节点的指针

单向链表

单向链表是最简单的链表形式。我们将链表中最基本的数据称为节点(node),每一个节点包含了数据块(data)和指向下一个节点的指针(next),链表有一个头节点,图中以head表示。可以看出,head指向第一个元素,第一个元素的next又指向第二个元素……直到最后一个元素,该元素不再指向其他元素,它称为表尾,它的 next 为空(NULL),链表到此结束。

可以看到,要找链表中的某一元素,必须先找到上一个元素,根据它提供的下一个元素的地址才能找到下一个元素。如果不提供头指针,则整个链表都无法访问。链表如同一条铁链一样,一环扣一环,中间是不能断开的。

我们使用数组模拟的方法来学习链表。我们创建一个结构体, 这个结构体有两个int型的变量,分别是data和next,data就是链表当前节点存储的数据,next指向下一个节点的节点编号(在数组中的编号,这样可以避免使用指针)。

struct node{    //next即后面节点的编号,data就是需要维护的数据

    int next,data;

}a[10000];

int head;    //head即头指针

//head节点是头结点, 不存储数据, 作用只是用来找到链表的第一个节点。

链表的常用操作

操作

描述

查找

找到符合条件的节点

插入

添加一个新节点

删除

删除一个存在的节点

查找操作

如何根据给出的数据,找到链表中符合条件的节点呢?需要从头节点开始,逐个向后遍历节点,比较 p 的 data 同待查找的数据是否相同,相同则返回当前节点。

struct node{    //next即后面节点的编号,data就是需要维护的数据

    int next,data;

}a[10000];

int head;    //head即头节点的编号

node find(int value){

    int p = head; //从

    while(a[p] != NULL)    {

       if(value==a[p].data)

            return a[p];

        p = a[p].next;

    }

    return NULL;

}

插入操作 链表与数组

操作

链表

数组

查找

从头节点开始查找

从下标0开始查找

插入

很快,因为只需要修改pre以及新节点的next编号

所有后面的节点都要向后移1位

删除

很快,因为只需要修改pre的next编号

所有后面的节点都要向前移1位

数组的插入

数组的删除

链表的应用

由于链表存储不连续,一般来讲,访问效率低于数组。但链表的删除插入效率高。

欧拉回路:奇数点可各遍历一遍。

捆绑法,插空法,排除

STL 标准模板库

obj.pop_back()删除数组中的最后一个数据

typede起别名 Insert 添加函数

Vector:只关心随机存取,不在乎插入和删除

List 只关心插入删除,不在乎随机存取。

动态规划

int main(){

int a[101][101];

for(int i=0;i<=8;i++){

for(int j=1;i<=8;j++){

if(i==1&&j==1){

a[i][j]=1;

}else{

a[i][j]=a[i-1][j]+a[i][j-1];

}

}

}

cout<<a[8][8];

深搜:能深则深,不能深就退。

Vector

基本操作

vector<int> c

c.clear()            //移除容器中所有数据。

c.empty()            //判断容器是否为空。

c.erase(pos)         //删除pos位置的数据

c.erase(beg,end)     //删除[beg,end)区间的数据

c.front()            //传回第一个数据。

c.insert(pos,elem)   //在pos位置插入一个elem拷贝

c.pop_back()         //删除最后一个数据。

c.push_back(elem)    //在尾部加入一个数据。

c.resize(num)        //重新设置该容器的大小

c.size()             //回容器中实际数据的个数。

c.begin()            //返回指向容器第一个元素的迭代器

c.end()              //返回指向容器最后一个元素的迭代器

遍历vector

访问 vector 的方法有两种:

方法一:直接访问

方法二:迭代器访问

int main(){    //顺序访问

    vector<int>obj;

    for (int i = 0; i < 10; i++)

        obj.push_back(i);

    cout << "直接利用下标:";

    for (int i = 0; i < obj.size(); i++)//方法一

        cout << obj[i] << " ";

    cout << endl;

    cout << "利用迭代器:"; //声明一个迭代器,来访问vector容器,作用:遍历或者指向vector容器的元素

    vector<int>::iterator it;

for(it = obj.begin();it != obj.end(); it++)

       cout << *it << " ";

   return 0;}

队列

队列是一种特殊的线性表。队列的原则是先进先出(FIFO — first in first out)。先进先出来,后进后出

队列在队头做删除操作,在队尾做插入操作:

队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFOfirst in first out)线性表。

操作

函数

解释

入队

push(x)

将x元素入队

出队

pop()

弹出队首元素,返回值为队首元素值

元素个数

size()

获取队列中的元素个数,返回int

获取队首元素

front()

获取队列第一个元素

在 C++的标准库中, 有封装好的队列 queue,queue是一个模板类,定义 queue 对象的示例代码如下: queue<int> q1; queue<double> q2;

操作

代码

解释

入队

q.push(x)

将x元素放到队列的末端

出队

q.pop()

弹出队列的第一个元素,并不会返回元素的值

队首元素

q.front()

获取队列的第一个元素

队尾元素

q.back()

获取队列的最后一个元素

元素个数

q.size()

获取队中的元素个数,返回int

判空

q.empty()

队列是否为空,返回bool,相当于q.size() == 0

int main() {

    queue<int> q; //定义队列q

    q.push(1); //放入元素3

    q.push(2);

    q.push(3);

    cout << "头:" << q.front( ) << endl;

    cout << "尾:" << q.back( ) << endl;

    cout << "大小:" << q.size( ) << endl;

    q.pop(); //弹出队列的第一个元素

    cout << "头:" << q.front( ) << endl;

    cout << "尾:" << q.back( ) << endl;

    cout << "大小:" << q.size( ) << endl;

    q.pop( );

    cout << "头:" << q.front( ) << endl;

    cout << "尾:" << q.back( ) << endl;

    cout << "大小:" << q.size( ) << endl;

    q.pop( );

    return 0;

}

MAP

mapC++标准库的一个关联容器,是一个模板类。

它提供一对一(其中第一个可以称为关键字,每个关键字只能在 map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。map就是从键( key)到值(value)的映射。因为重载了[ ][ ]运算符,map像是数组的“高级版”。

例如可以用一个 map<string, int>month_name 来表示“月份名字到月份编号”的映射,然后用 monthname["July"]=7 这样的方式来赋值。

这里说下map 内部数据的组织,map 内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map 内部所有的数据都是有序的,后边我们会见识到有序的好处。map 是一类关联式容器。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响

如果没有map利用结构体数组查找某个键对应的值,就需要遍历整个数组,这样很低效。map的内部实现是一种名为红黑树的数据结构,当将不同的键值放入 map,这种数据结构会自动将所有键值变为有序的(按照键key排序)。

如放入:8[A],1[F],7[Y],4[L],10[X]后(中括号内为值),map中保存的数据顺序为 1[F],4[L],7[Y],8[A],10[X] 。

因为有序,当我们查找某个值的时候,就无须遍历整个数组,而是可以用二分的方法快速定位。还是以上面的数据为例,我们想要查找4对应的值,查找过程会先找到中间的7,发现74大,因而只在前一半中进行查找,这样每次查找的范围会缩小一半,关于二分的知识,我们会在后续课程中讲解,这里不展开介绍。

map并不是唯一支持从 key 找 value的数据结构,还有一种更快的以空间换时间的方法叫做 hash

map的功能

自动建立Keyvalue 的对应。keyvalue可以是任意你需要的类型。

根据key值快速查找记录,查找的复杂度基本是Log(N)Log(N)

1.快速插入Key−Value记录

2.快速删除记录。

3.根据Key修改

.value记录

4.遍历所有记录。

使用map

定义

map<string,int>  my_Map;

SET关联式容器

set作为一个容器也是用来存储同一数据类型的数据类型,并且能从一个数据集合中取出数据,在 set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。应该注意的是 set 中数元素的值不能直接被改变。与 map的使用方法大致都相同。

set的定义

set<类型> 对象名; 如:set<int> s;

添加元素

set<int> s;

s.insert(8);

s.insert(10);

s.insert(6);

s.insert(8); //重复元素不会插入

set遍历

setset 的遍历也是使用迭代器进行遍历, 可以正序遍历也可以反序遍历。

正序遍历

set<int> s;

set<int>::iterator it;

for (it = s.begin(); it != s.end(); it++)

cout << *it << endl;

反序遍历

set<int>::reverse_iterator it;

for (it = s.rbegin(); it != s.rend(); it++)

    cout << *it << endl

容器与迭代器

vectorstack,queuemapset都是stl提供的标准数据结构,他们共同的特点是都是容器,用来存储元素集合。为了统一他们的使用方法,stl提供了迭代器。

容器

迭代器功能

解释

vector

随机访问

deque

随机访问

双端队列,可以从前后push,pop 元素

list

双向

链表

set/multiset

双向

multiset是允许有重复元素的set,元素保持有序

map/multimap

双向

multimap是允许有重复元素的map,元素是pair保持有序

stack

不支持迭代器

queue

不支持迭代器

队列

priority_queue

不支持迭代器

优先队列,每次可以从栈顶取出当前最小

容器

添加元素的效率

删除元素的效率

查找元素

访问中间的元素

递归

在函数内部,可以调用其他函数。如果一个函数在内部调用自己本身,这个函数就是递归函数。如果一个函数在调用自己,则被调用的”自己”再调用“自己”想象一下,这将会无限循环下去,所以递归函数中肯定有办法在某种情况下停止调用自己,即停止递归,一般来说使用if语句实现在满足特定的条件下终止递归。

函数不再递归的情况称作基本情形(base case,也称基本情况)。函数调用自身来执行子任务的情况就称作递归情形(recursive case)。

操作格式

递归函数格式如下:

函数类型 函数名(形参1){

    if(条件1)

        该条件下的函数值;

    else if(条件2)

         另一种条件下的函数值;

    ......

     执行操作并进行递归调用;

}

递归与辗转相除

辗转相除法,又名欧几里德算法是求最大公约数的一种方法。它的具体做法是:用较大数除以较小数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数

int gcd(int a, int b) {

    if (b == 0)

        return a;

    return gcd(b, a % b);

}

递归替代循环

void printHello(int n) {           // 传入一个参数n

    if(n == 0)

        return;

    cout << "Hello" << endl;

    printHello(n - 1);

}

int main() {                    // 主函数

 printHello(5);             // 调用函数

}

循环替代递归

斐波那契数列

int Fib(int n){

    int a = 1, b = 1;

    for (int i = 2; i < n; i++){

        int t = b;        b = a;        a += t;

    }

    return a;

}

int main(){

    cout << Fib(10);

return 0;

}

康托展开

康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的。

康托展开运算

X=an(n−1)!+an−1(n−2)!+...a10!其中ai为整数,并且满足0≤ai<i,1≤i≤nai表示原数的第i位在当前未出现的元素中排在第几。

例:在12345的排列组合中,计算34152的康托展开值。

未出现且小于当前值的数

展开值

33

1,2=>a5=2

a5×4!

4848

44

1,2=>a4=2

a4×3!

1212

11

无 =>a3=0=>a3=0

a3×2!a3×2!

00

55

2=>a2=12=>a2=1

a2×1!a2×1!

11

22

无 =>a1=0=>a1=0 ​

a1×0!a1×0!

00

所以比34152小的组合有61个,即34152是排第62

康托展开逆运算

康托展开是一个全排列到一个自然数的双射,因此是可逆的。即对于上述例子,在给出61可以算出起排列组合为34152。由上述的计算过程逆推回来,具体过程如下:

61/4!=213,说明,说明比首位小的数有2个,所以首位为3 。

13/3!=211,说明,说明在第二位之后小于第二位的数有2 个,所以第二位为4

1/2!=01,说明,说明在第三位之后没有小于第三位的数,所以第三位为1

1/1!=10,说明,说明在第四位之后小于第四位的数有1个,所以第四位为5

最后一位自然就是剩下的数2。通过以上分析,所求排列组合为34152 。

位运算

位运算是指按二进制进行的运算。在系统软件中,常常需要处理二进制位的问题。C语言提供了6 个位操作运算符。这些运算符只能用于整型操作数,即只能用于带符号或无符号的 char,short,intlonglong 类型。

运算符

&

按位与

如果两个相应的二进制位都为1,则该位的结果值为1,否则为0

|

按位或

两个相应的二进制位中只要有一个为1,该位的结果值为1

^

按位异或

若参加运算的两个二进制位值相同则为0,否则为1

~

取反

~是一元运算符,用来对一个二进制数按位取反,将0变1,将1变0

<<

左移

用来将一个数的各二进制位全部左移N位,右补0

>>

右移

将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0

“按位与”运算符(&)

参加运算的两个数据,按二进制位进行“与”运算。如果两个相应的二进制位都为 1 ,则该位的结果值为 1 。否则为 0 。

按位与(and)

1&1=1

1&0=0

0&1=0

0&0=0

00101& 11100

以前我们判断一个数n是奇数还是偶数通常使用n%2的结果来判断,1为奇数0为偶数,现在我们也可以使用 &操作来判断奇偶,n&1的结果就是取n的二进制的最末位。二进制的最末位为0表示n为偶数,最末位为1表示n为奇数。

位运算应用

左移运算符(<<)

左移运算符是用来将一个数的各二进制位左移若干位,移动的位数由右操作数指定(右操作数必须是非负值),其右边空出的位用 00 填补,高位左移溢出则舍弃该高位。

例如:将a的二进制数左移2位,右边空出的位补0,左边溢出的位舍弃。若a=15 即(00001111)2,左移2位得(00111100) 。

左移1位相当于该数乘以2,左移2位相当于该数乘以2×2=4 。

15<<2=60,即乘了4。但此结论只适用于该数左移时被溢出舍弃的高位中不包含 1的情况。

右移运算符(>>)

右移一位相当于除以2,右移n位相当于除以2n

在右移时,需要注意符号位问题。对无符号数,右移时左边高位移入0。对于有符号的值,如果原来符号位为0(该数为正),则左边也是移入0,如果上例表示的那样,如果符号位原来为1(该数为负),则左边移入的0 还是1,要取决于所用的计算机系统。移入0称为 逻辑右移,即简单右移。移入1称为算术右移。最后一位直接去掉。

简单枚举

顾名思义,枚举便是依次列举出所有可能产生的结果,根据题中的条件对所得的结果进行逐一的判断,过滤掉那些不符合要求的,保留那些符合要求的,也可以称之为暴力算法。

枚举结构:循环+判断语句。

应用场合

在竞赛中,并不是所有问题都可以使用枚举算法来解决(事实上,只有少数),只有当问题的所有解的个数不太多时,并在我们题目中可以接受的时间内得到问题的解,才可以使用枚举。

枚举法的优缺点

枚举法的优点:

由于枚举算法一般是现实生活中问题的“直译”,因此比较直观,易于理解;

由于枚举算法建立在考察大量状态、甚至是穷举所有状态的基础上,所以算法的正确性比较容易证明。

枚举法的缺点:

枚举算法的效率取决于枚举状态的数量以及单个状态枚举的代价,因此效率比较低。

直译”枚举:直接根据题意设定枚举对象、范围和约束条件。

算法复杂度是指算法在编写成可执行程序后,运行时所需要的资源,资源包括时间资源和内存资源。通常我们用时间复杂度和空间复杂度,来描述算法所需要的时间资源和内存资源。

时间复杂度

时间复杂度表示程序运行占用时间的多少,是评估算法效率的重要指标。一般表示为关于n的某个函数。其中n往往对应输入数据的规模,用T(n)表示。

若有某个辅助函数f(n),存在一个正常数c使得f(n)×c>=T(n) 恒成立。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度,简称时间复杂度。

时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。

例如:如果一个算法对于任何大小为n(必须比n0大)的输入,它至多需要5n3+3n的时间运行完毕,那么它的渐进时间复杂度是O(n3)

我们知道常数项对函数的增长速度影响并不大,所以当 T(n)=cc为一个常数的时候,我们说这个算法的时间复杂度为O(1)。如果 T(n) 不等于一个常数项时,直接将常数项省略。比如:cout << "hello world" << endl;

就是O(1)的。

一个函数中最高次项对函数的影响是最大,所以在计算时间复杂度时刻直接忽略其他较低次项。

比如T(n)=n3+n2+1,则对应的时间复杂度为O(n3)

综合起来:如果一个算法的执行次数是T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数f(n),此时算法的时间复杂度就是O(f(n))。在第二节中将介绍如何分析时间复杂度。

按数量级递增排列,常见的时间复杂度有:

常数阶O(1),对数阶O(log2n)(以2为底n的对数,下同),线性阶O(n) ,线性对数阶O(nlog2n,平方阶O(n2),立方阶O(n3),...k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。时间复杂度也是我们在做题时首先就要考虑的问题。

从运行效率来看:

别名

效率从高到低

多项式复杂度

常数阶

O(1)

多项式复杂度

对数阶

O(log(log(n)))

多项式复杂度

对数阶

O(log(n))

多项式复杂度

对数阶

O((logn)2)

多项式复杂度

O(n−−√)O(n)

多项式复杂度

O(n2/3logn)

多项式复杂度

线性阶

O(n)

多项式复杂度

O(nlog(n))

多项式复杂度

平方阶

O(n2)

多项式复杂度

立方阶

O(n3)

多项式复杂度

O(nk)

指数复杂度

斐波那契

O(Fib(n))

指数复杂度

指数阶

O(2n)

指数复杂度

指数阶

O(3n)

指数复杂度

指数阶

O(n!)

指数复杂度

指数阶

O(nn)

注意:log2n=log210×log10n 所以O(log2n)=O(log10n),所以在谈论对数复杂度的算法时,我们忽略是以2为底还是以10为底,统称为O(log(n)) 。

对于所有复杂度为:O(nk)的算法,不论k有多大,我们都称其为多项式时间的算法。所以O(nklogn)也是多项式时间的,因为O(nklogn)≤O(nk+1) 。

对于所有复杂度为:O(kn) 的算法k>1),我们都可以称其为非多项式时间的算法,经常会简化为指数级算法。

空间复杂度

空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。

比如 int a[100]; 所占用的内存空间大小 就是 100 ∗ 4 ∗ 8 bit 。一个长度为n的数组对应的空间复杂度为 O(n) 。

需要注意的是,一段程序运行的时间复杂度,不可能低于他的空间复杂度。

一个算法的优劣主要从算法的执行时间和所需要占用的存储空间两个方面衡量。当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间。反之,当追求一个较好的空间复杂度时,可能会使时间复杂度的性能变差,即可能导致占用较长的运行时间。

常见算法复杂度

复杂度

遍历

O(n)

冒牌排序

O(n2)

快速排序

O(nlog(n))

全排列

O(n!)

复杂度分析基础

下面通过一些代码具体分析一下时间复杂度。

void aFunc(int n) {  

for(int i = 0; i < n; i++) {         // 循环次数为 n  

       cout << "Hello, World!" << endl;     // 循环体时间复杂度为 O(1)  

}

这段程序的循环运行次数为n次,每次输出看作O(1),因此时间复杂度为O(n)

void aFunc(int n) {  

    for(int i = 0; i < n; i++) {         // 循环次数为 n  

        for(int j = 0; j < n; j++) {       // 循环次数为 n  

           cout << "Hello, World!" << endl;     // 循环体时间复杂度为 O(1)  

        }  

    }  

}

这段程序的外层循环运行次数为n次,内层循环运行次数也是n次,每次输出看作O(1),总共输出了n2行,因此时间复杂度为O(n2) 。

void aFunc(int n) {     // 第一部分时间复杂度为 O(n2)  

    for(int i = 0; i < n; i++) {  

        for(int j = 0; j < n; j+=2) {  

           cout << "Hello, World!" << endl;

        }  

    }     // 第二部分时间复杂度为 O(n)  

    for(int j = 0; j < n; j++) {  

       cout << "Hello, World!" << endl;  

    }  

}

第一段程序会执行n2/2次,第二段程序会执行n次,共执行n2/2+n 次,因此时间复杂度为O(n2)

void aFunc(int n) {

    for (int i = 1; i * i <= n; i++)

       cout << "Hello, World!" << endl;

}

当 i>n−−√i>n 时,会退出循环。复杂度 O(n−−√)O(n) 。

void aFunc(int n) {

    for (int i = 1; i <= n; i += i)

       cout << "Hello, World!" << endl;

}

i的取值每次会翻倍,因此程序只会执行log(n),复杂度为O(log(n))。对数复杂度的程序执行效率很高,哪怕n=1018,也只会执60多次。

时间复杂度分析的基本策略:从内向外分析,从最深层开始分析。如果遇到函数调用,要深入函数进行分析。

在学会了分析算法的复杂度后, 我们要学会根据对应的数据范围设计出题目时间要求的时间复杂度。一般我们做题系统的评测机1s可运行百万级别, 即每秒进行大约107次基础运算。设输入的数据量为n,当n<1000000时,运行一段时间复杂度为O(n)的程序,大约需要不到1s。而如果时间复杂度为O(n2)则可能需要运行几天。

时间复杂度分析

复杂度分析进阶

void aFunc(int n) {

    for (int i = 2; i <= n; i *= i)

       cout << "Hello, World!" << endl;

}

这个i=i×i函数的增长是很快的,复杂度为O(log(log(n)))。在 n=21024时,也只会执行10 次左右。

void aFunc(int n) {

    for (int i = 1; i <= n; i++)

    {

        for (int j = 1; j <= i; j += j)

           cout << "Hello, World!" << endl;

    }

}

外层循环n次,内层循环log(i)次,因此总共循环的次数是:

log(1)+log(2)+log(3)+....log(n)=∑i=1nlog(i)

那么这个复杂度究竟是多少呢?我们来进行一下推导:

nlog(n)≥∑i=1nlog(i)≥∑i=n/2

将所有log(i)换为log(n),所以有nlog(n)≥∑ni=1log(i)

去掉∑ni=2log(i) 中小于 n/2的项,所以有∑ni=1log(i)≥∑ni=n/2log(i)把所有∑ni=n/2log(i)(i)中的i都换成 n/2,所以有∑i=n/2nlog(i)≥(n/2)log(n/2)

有了这几个式子的比较,我们看,第1个式子对应的复杂度是O(nlog(n)),最后一个式子对应的复杂度也是O(nlog(n)),因此 ∑ni=1log(i)∑i=1nlog(i) 对应的复杂度是O(nlog(n))

void aFunc(int n) {

    for (int i = 1; i <= n; i+=i)    {

        for (int j = 1; j <= i; j++)

           cout << "Hello, World!" << endl;

    }

}

外层循环共log(n)次,内层每次循环到i。这个看起来似乎和上面的问题很像。但还需要仔细分析。我们先看n为2的幂的情况。总共循环的次数为:

因此这个程序的复杂度为O(n)。下面的程序也是O(n)的。

空间换时间

在介绍复杂度的概念时曾讲过,当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间。很多时候优化时间复杂度的方法都是基于空间换时间的方法。

常用的空间换时间方法

在计算中,有些数值会反复被用到,如果每次都重新计算,则会影响算法效率。我们可以通过一些手段,提前处理好这些计算结果,在计算过程中直接调用已经算出的部分。

方法

数组前缀和

O(1)求区间和

矩阵前缀和

容斥O(1) 求子矩阵和

质数表

加快分解质因数等等

打表

预先处理好需要重复计算的值

前缀和优化

给一个长度为n的数组,有m次询问,每一次询问区间[l,r]的和。最简单的做法是每一次询问都用for循环遍历数组[l,r]求一次和。这样复杂度是O(m×n) 。

我们可以预先处理一个数组 presum, presum[i]就是[0,i] 的区间和, 那么每当询问一个区间[l,r]时, 答案就是presum[r]−presum[l−1]。可以在O(1)时间得到答案。

总的复杂度就是O(m+n) 。例如:

原数组为:{1,5,9,4,3,3} 。

前缀和数组为:{0,1,6,15,19,22,25} 。

除了前缀和,常用的其实还有后缀和、前缀积、后缀积。核心思想都是经典的预处理思想。通过预处理将O(n×m)的复杂度降到O(n+m),只是额外的使用了O(n)的空间,这就是空间换时间的思想。

前缀和的逆运算是差分,与差分相关的内容暂时不展开。

ST表

RMQRange Maximum Query)问题是指:对于长度为n的数列A,回答若干询问Q(A,i,j)(i,j<=n),返回数列A中下标在(i−>j) 里的最大值。RMQRMQ 问题是指求区间最值的问题。

RMQ问题有多种解决方式,不同方法的复杂度不尽相同。这里只讲解最简单的 STST 表的方法。

XOR的特性

XOR运算具有一些独特的性质满足:

  1. 交换律,a xor b=b xor a
  2. 结合律,(a xor b)xor c=a xor (b xor c)
  3. 实际应用中,我们还可以将 XOR其看作不进位的二进制加法。

尺取法

概念   尺取法也常被称为双指针,是很常用的一种优化算法。通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。尺取法比直接暴力枚举区间效率高很多。

这么说可能有些抽象,我们来举一个具体的例子。

在一个有序数组a 里,所有数都大于0,我希望为每个ai找到最小的>=ai×2 的数。

这里需要注意一个点,就是数组a是有序的,这是能够适用尺取法的前提。

对于a0,我们可以从a1开始暴力找到第一个符合条件的ai,对于a1我们就没有必要从a2开始找了,因为a1>=a0,所以-

+2a1>=2a0>=ai,所以我们可以从ai 开始找即可,以此类推直到处理完an−1

这个算法的复杂度怎么分析呢?左端点从a0变换为a1,右端点也许直接从ai变为an。左端点走一步,右端点走多少步并不确定,最坏情况下要走O(n)步,那么会不会这是一个最坏情况下O(n2)的算法呢?

这时我们需要从总量上进行分析,我们可以想象,区间的左端点从0−(n−1),右端点也是从0−(n−1),并且不会向回走,因此总的复杂度为O(n) 。

提到有序,我们会联想到之前学习过的二分查找,对于上面这个问题,应用二分查找也是可以的。但我们来计算一下复杂度,二分查找单次的复杂度为O(log(n)),共需要执行n次,因此总的复杂度为O(nlog(n)),而运用尺取法,复杂度优化为O(n)

适用条件

尺取法通常适用于具备单调性的情况,比如上面例子中,a为有序数组,所以右端点可以一直向右移动。如果没有这个条件,则无法适用尺取法。这时我们可以先对a排序,然后再使用尺取法。

谈到单调性,我们容易联想到二分。二分在查找具体一个元素时,可以达到O(log(n))的复杂度,但如果处理 n个数,则总的复杂度会变为O(nlog(n)),这时如果可以使用尺取法,可以将总的复杂度降为O(n) 。

贪心算法

贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。

贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。举例来说:用2,4,6,8组成最大的4位数。我们很自然的会想到8642,因为在选择千位的数字时,我们只需要考虑让这一位最大,而后面的不论如何选,都不会影响这一位的选择,因此我们会选择8,后面的处理也是一样。

贪心算法进阶

贪心的适用条件

贪心算法在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。因此利用贪心算法来求最优解,一定要满足当前最优即整体最优这个条件。

相关文章:

c++笔记2

目录 2.2 栈底&#xff08;bottom&#xff09; } 大数乘大数 节点&#xff1a;包含一个数据元素及若干指向子树分支的信息 。 节点的度&#xff1a;一个节点拥有子树的数目称为节点的度 。 叶子节点&#xff1a;也称为终端节点&#xff0c;没有子树的节点或者度为零的节点…...

通过Lua脚本手写redis分布式锁

1、手写 Redis 分布式锁&#xff0c;包括上锁、解锁、自动续期。 此功能实现采用 Lua脚本实现&#xff0c;Lua脚本可以保证原子性。 setnx可以实现分布式锁&#xff0c;但是无法实现可重入锁&#xff0c;所以用hset来代替setnx实现可重入的分布式锁。 -- lock if redis.call…...

解析银行个人征信系统

银行个人征信系统&#xff0c;也被称为个人信用信息基础数据库或金融信用信息基础数据库&#xff0c;是我国社会信用体系的重要基础设施。该系统由中国人民银行组织国内相关金融机构建立&#xff0c;旨在依法采集、整理、保存、加工自然人&#xff08;法人&#xff09;及其他组…...

AttributeError: ‘list‘ object has no attribute ‘text‘

AttributeError: ‘list‘ object has no attribute ‘text‘ 目录 AttributeError: ‘list‘ object has no attribute ‘text‘ 【常见模块错误】 【解决方案】 示例代码 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英…...

Codeforces Round 874 (Div. 3)(A~D题)

A. Musical Puzzle 思路: 用最少的长度为2的字符串按一定规则拼出s。规则是&#xff1a;前一个字符串的尾与后一个字符串的首相同。统计s中长度为2的不同字符串数量。 代码: #include<bits/stdc.h> #include <unordered_map> using namespace std; #define N 20…...

[Python][基础语法]详细讲解

目录 1.顺序语句2.条件语句3.缩进和代码块4.空语句 pass5.循环语句1.while2.for3.continue4.break ∞.积累 1.顺序语句 默认情况下&#xff0c;Python的代码执行顺序是按照从上到下的顺序&#xff0c;依次执行# 输出结果&#xff1a;"123" print("1") pri…...

Layui---输入事件

输入实时监听 //监听表单单选框复选框选择 form.on(radio, function (data) {console.log(data.value); //得到被选中的值 });//监听表单下拉菜单选择form.on(select, function (data) //监听表单下拉菜单选择form.on(select, function (data) ​ //监听表单复选框选择form.…...

甄选范文“论软件测试中缺陷管理及其应用”软考高级论文,系统架构设计师论文

论文真题 软件缺陷指的是计算机软件或程序中存在的某种破坏正常运行能力的问题、错误,或者隐藏的功能缺陷。缺陷的存在会导致软件产品在某种程度上不能满足用户的需要。在目前的软件开发过程中,缺陷是不可避免的。软件测试是发现缺陷的主要手段,其核心目标就是尽可能多地找…...

spring框架实现滑动验证码功能

spring框架实现滑动验证码功能 1. 整体描述2. 具体实现2.1 滑动验证码实体类2.2 滑动验证码登录VO2.3 滑动验证码接口返回类2.4 滑动验证码工具类2.5 滑动验证码Service2.6 滑动验证码Controller 3 工程源码4 总结 1. 整体描述 之前项目需要在验证码模块&#xff0c;增加滑动验…...

Pytorch使用教学8-张量的科学运算

在介绍完PyTorch中的广播运算后&#xff0c;继续为大家介绍PyTorch的内置数学运算&#xff1a; 首先对内置函数有一个功能印象&#xff0c;知道它的存在&#xff0c;使用时再查具体怎么用其次&#xff0c;我还会介绍PyTorch科学运算的注意事项与一些实用小技巧 1 基本数学运算…...

[Spring Boot]登录密码三种加密方式

简述 介绍其三种密码加密方法 1.SM2加密与验签 2.随机密码盐加密 3.MD5加密 推荐使用方法1&#xff0c;其次使用方法2&#xff0c;最不推荐的是方法3。方法3极其容易被密码字典破解&#xff0c;如果项目进行安全测试&#xff0c;通常是不允许的加密方式。 SM2加密与验签 引入…...

前端面试项目细节重难点分享(十三)

面试题提问&#xff1a;分享你最近做的这个项目&#xff0c;并讲讲该项目的重难点&#xff1f; 答&#xff1a;最近这个项目是一个二次迭代开发项目&#xff0c;迭代周期一年&#xff0c;在做这些任务需求时&#xff0c;确实有很多值得分享的印象深刻的点&#xff0c;我讲讲下面…...

每天五分钟深度学习:向量化方式完成逻辑回归m个样本的前向传播

本文重点 我们已经知道了向量化可以明显的加速程序的运行速度,本节课程将使用向量化来完成逻辑回归的前向传播,不使用一个for循环。 逻辑回归的前向传播 我们先来回忆一下逻辑回归的前向传播,如果我们有m个训练样本,首先对第一个样本进行预测,我们需要计算z,然后计算预…...

以线程完成并发的UDP服务端

网络(九)并发的UDP服务端 以线程完成功能 客户端 // todo UDP发送端 #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/types.h> #include <stdlib.h> #include <string.h…...

linux c 特殊字符分割

/* * brief: 根据split_symbol分割字符串 * param: str为要分割的字符串&#xff0c;split_symbol是分隔符 * return&#xff1a;返回garray的指针数组&#xff0c;如果返回非空需要自己处理释放 */ GPtrArray_autoptr char_sz_spilt(pchar* str, pchar split_symbol) {if (NUL…...

搭建本地私有知识问答系统:MaxKB + Ollama + Llama3 (wsl网络代理配置、MaxKB-API访问配置)

目录 搭建本地私有知识问答系统:MaxKB、Ollama 和 Llama3 实现指南引言MaxKB+Ollama+Llama 3 Start buildingMaxKB 简介:1.1、docker部署 MaxKB(方法一)1.1.1、启用wls或是开启Hyper使用 WSL 2 的优势1.1.2、安装docker1.1.3、docker部署 MaxKB (Max Knowledge Base)MaxKB …...

谷粒商城实战笔记-65-商品服务-API-品牌管理-表单校验自定义校验器

文章目录 1&#xff0c;el-form品牌logo图片自定义显示2&#xff0c;重新导入和注册element-ui组件3&#xff0c;修改brand-add-or-update.vue控件的表单校验规则firstLetter 校验规则sort 校验规则 1&#xff0c;el-form品牌logo图片自定义显示 为了在品牌列表中自定义显示品…...

学好C++之——命名空间

c开始学习之时&#xff0c;你不可避免会遇到一个新朋友&#xff0c;那就是——namespace&#xff08;命名空间&#xff09;。 那么这篇文章就来为你解决这个小麻烦喽~ 目录 1.namespace存在的意义 2.namespace的定义 3.namespace的使用 1.namespace存在的意义 在C中&#…...

pytorch lightning报错all tensors to be on the same device

RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu! 修改指定为gpu trainer pl.Trainer(max_epochstrain_params.iterations, loggertb_logger,acceleratorgpu, devices1)...

Redis中的哨兵(Sentinel)

上篇文章我们讲述了Redis中的主从复制&#xff08;Redis分布式系统中的主从复制-CSDN博客&#xff09;&#xff0c;本篇文章针对主从复制中的问题引出Redis中的哨兵&#xff0c;希望本篇文章会对你有所帮助。 文章目录 一、引入哨兵机制 二、基本概念 三、主从复制的问题 四、哨…...

产业创新研究杂志产业创新研究杂志社产业创新研究编辑部2024年第12期目录

高质量发展 如何在新一轮产业链变革中平稳应对挑战 王宏利; 1-3《产业创新研究》投稿&#xff1a;cnqikantg126.com 基于ERGM的城市间绿色低碳技术专利转让网络结构及演化研究 吕彦朋;姜军;张宁; 4-6 数字基础设施建设对城市FDI的影响——基于“宽带中国”试点政策…...

网闸(Network Gatekeeper或Security Gateway)

本心、输入输出、结果 文章目录 网闸(Network Gatekeeper或Security Gateway)前言网闸主要功能网闸工作原理网闸使用场景网闸网闸(Network Gatekeeper或Security Gateway) 编辑 | 简简单单 Online zuozuo 地址 | https://blog.csdn.net/qq_15071263 如果觉得本文对你有帮助…...

C#中的字符串

String 在实例方法中string虽然传入的是引用类型 但是修改string 并不是修改原来堆里面的值 而是又重新创建一个堆值 用来然后用方法内的变量指向新的堆值 C# 中的字符串&#xff08;string 类型&#xff09;提供了许多有用的方法来处理字符串数据。以下是一些常用的字符…...

docker安装部署elasticsearch7.15.2

docker安装部署elasticsearch7.15.2 1.拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.2如果不想下载或者镜像拉去太慢可以直接下载文章上面的镜像压缩包 使用镜像解压命令 docker load -i elasticsearch-7-15-2.tar如下图所示就表示镜像解压成…...

Symfony 入门指南:快速安装与基础配置

Symfony 入门指南&#xff1a;快速安装与基础配置 Symfony 是一个强大而灵活的 PHP 框架&#xff0c;广泛应用于构建现代 Web 应用程序。本指南将带您一步一步地了解如何快速安装 Symfony&#xff0c;并完成基本配置&#xff0c;以便您能够开始使用这个强大的框架。 目录 引…...

3.3V升压至5V的AH6922芯片:高效能的SOP8封装解决方案

# 3.3V升压至5V的AH6922芯片&#xff1a;高效能的SOP8封装解决方案 在当今快速发展的电子设备领域&#xff0c;对于电源管理的需求日益增长。特别是对于便携式产品和手持设备&#xff0c;一个高效、稳定且体积小巧的升压解决方案变得至关重要。本文将介绍一款专为这些需求设计…...

赋能未来教育,3DCAT助力深圳鹏程技师学院打造5G+XR实训室

随着国家对教育行业的重视&#xff0c;实训室建设已成为推动教育现代化的关键。《教育信息化2.0行动计划》、《职业教育示范性虚拟仿真实训基地建设指南》等政策文件&#xff0c;明确指出了加强虚拟仿真实训教学环境建设的重要性。 在这一大背景下&#xff0c;教育行业对于实训…...

力扣141环形链表问题|快慢指针算法详细推理,判断链表是否有环|龟兔赛跑算法

做题链接 目录 前言&#xff1a; 一、算法推导&#xff1a; 1.假设有环并且一定会相遇&#xff0c;那么一定是在环内相遇&#xff0c;且是快指针追上慢指针。 2.有环就一定会相遇吗&#xff1f;快指针是每次跳两步&#xff0c;有没有可能把慢指针跳过去&#xff1f; 3.那一定…...

React 常见的报错及解决方法

1、Warning: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons&#xff08;无效的钩子调用。钩子只能在函数组件的内部调用。这可能是由于以下原因之一&#xff09; 原因&#x…...

更新服务器nginx 1.26.1版本

今天在官网下载了nginx1的1.26.1版本&#xff0c;使用gpt的脚本想直接覆盖安装&#xff0c;脚本如下 #!/bin/bash# 设置变量 NGINX_VERSION"1.26.1" TAR_FILE"nginx-$NGINX_VERSION.tar.gz" SRC_DIR"nginx-$NGINX_VERSION"# 检查是否存在tar包 …...

JAVA代码审计JAVA0基础学习(需要WEB基础知识)DAY2

JAVA 在 SQL执行当中 分为3种写法&#xff1a; JDBC注入分析 Mybatis注入分析 Hibernate注入分析 JDBC 模式不安全JAVA代码示例部分特征 定义了一个 sql 参数 直接让用户填入id的内容 一个最简单的SQL语句就被执行了 使用安全语句却并没有被执行 Mybatis&#xff1a; #…...

SpringBoot整合elasticsearch-java

一、依赖 系统使用的是ElasticSearch8.2.0 <dependency><groupId>co.elastic.clients</groupId><artifactId>elasticsearch-java</artifactId><version>8.1.0</version> </dependency> 二、配置 1、yml文件配置 elastics…...

网络服务与应用

一、 文件传输 FTP 1、FTP采用典型的C/S架构&#xff08;即服务器端和客户端模型&#xff09;&#xff0c;客户端与服务器端建立TCP连接之后即可实现文件的上传、下载。 2、FTP传输过程 1&#xff09;、主动模式&#xff08;POST&#xff09;&#xff1a;入站连接 2&#x…...

Git项目如何配置,如何上传至GitHub

Git项目配置并上传至GitHub的详细步骤如下&#xff1a; 一、准备工作 创建GitHub账号&#xff1a; 访问GitHub官网&#xff0c;点击“Sign up”注册新账号。填写相关信息&#xff0c;包括用户名、邮箱和密码&#xff0c;完成账号创建。安装Git客户端&#xff1a; 访问Git官网…...

Python教程(一):环境搭建及PyCharm安装

目录 引言1. Python简介1.1 编译型语言 VS 解释型语言 2. Python的独特之处3. Python应用全览4. Python版本及区别5. 环境搭建5.1 安装Python&#xff1a; 6. 开发工具&#xff08;IDE&#xff09;6.1 PyCharm安装教程6.2 永久使用教程 7. 编写第一个Hello World结语 引言 在当…...

神经网络与注意力机制的权重学习对比:公式探索

神经网络与注意力机制的权重学习对比&#xff1a;公式探索 注意力机制与神经网络权重学习的核心差异 在探讨神经网络与注意力机制的权重学习时&#xff0c;一个核心差异在于它们如何处理输入数据的权重。神经网络通常通过反向传播算法学习权重&#xff0c;而注意力机制则通过学…...

C语言------指针讲解(3)

一、字符指针 在指针中&#xff0c;我们知道有一类指针类型为字符指针char*; int main() {char ch w;char* pc &ch;*pc w;return 0; } 还有一种使用方式如下&#xff1a; 上述代码中&#xff0c;本质是把hello的首字符的地址放到了pstr中。即把一个常量字符串的首字符…...

博客建站 - 常用的公共DNS服务器

国内公共DNS服务 服务器名称首选DNS服务备用DNS服务114 DNS114.114.114.114114.114.115.115阿里 DNS223.5.5.5223.6.6.6腾讯云公共DNS119.29.29.29182.254.116.116百度公共DNS180.76.76.76110.242.68.68 国外公共DNS服务 服务器名称首选DNS服务备用DNS服务备注Google DNS8.8…...

用Redisson的RMap做一个简单的购物车示例

RMap是Redisson提供的一个高级数据结构&#xff0c;它封装了Redis中的Hash数据类型&#xff0c;提供了一个类似Java HashMap的接口。RMap非常适合在需要分布式共享的键值对集合场景中使用&#xff0c;以下是一些典型的应用场景&#xff1a; 分布式缓存&#xff1a; RMap可以用作…...

「12月·长沙」第四届机器人、自动化与智能控制国际会议(ICRAIC 2024)

随着科技的飞速发展&#xff0c;智能机器人在当今社会的重要性愈发凸显。从制造业的自动化生产线&#xff0c;到医疗领域的手术机器人&#xff0c;再到家庭生活中的智能助手&#xff0c;机器人与人工智能的融合正在改变着我们的生产和生活方式。第四届机器人、自动化与智能控制…...

传神社区|数据集合集第7期|法律NLP数据集合集

自从ChatGPT等大型语言模型&#xff08;Large Language Model, LLM&#xff09;出现以来&#xff0c;其类通用人工智能&#xff08;AGI&#xff09;能力引发了自然语言处理&#xff08;NLP&#xff09;领域的新一轮研究和应用浪潮。尤其是ChatGLM、LLaMA等普通开发者都能运行的…...

完美解决Ubuntu的MySQL临时文件夹修改调整

打开终端&#xff0c;输入以下命令 $ sudo -i # 切换root用户 $ systemctl stop mysql.service $ mkdir /home/tmp $ chown root:root /home/tmp $ chmod 1777 /home/tmp $ gedit /etc/mysql/mysql.conf.d/mysqld.cnf以上最后一条命令执行完后&#xff0c;在打开的mysqld.cnf文…...

shell基础编程

初始shell 程序 语言 编程 ---------------------------------- 语言 自然语言:汉语、英语 计算机语言:c语言、c、(java php python go shell) 编译型语言 c c java 解释型语言 php python bash ​ 编译型语言:编译型语言的首先将源代码编译生成机器语言&#xff0c;再由机…...

近期代码报错解决笔记

1.TypeError: ‘bool’ object is not callable 想print("Type of head:", type(entity_emb[head]))&#xff0c;结果报如下错误&#xff1a; 源代码&#xff1a; 因为 print 仍然被当作一个布尔值处理&#xff0c;而不是作为函数调用。这个问题的根源在于 print …...

apache设置ssl代理

<VirtualHost *:8082> ServerName localhost DocumentRoot D:\xampp\htdocs\somgl\dist #证书 SSLProtocol all -SSLv2 SSLCipherSuite DEFAULT:!EXP:!SSLv2:!DES:!IDEA:!SEED:3DES SSLEngine on SSLProxyEngine on SSLProxyVerify…...

数据库中单表的查询(select)

单表查询 所有的查找都会得到一张虚拟表 一、 最简单的查询 SELECT 123; SELECT asd; SELECT 11;二、 从表中获取数据 select 字段名,字段名 from 表名 2.1 全字段查询 SELECT sid,sname,birthday,ssex,classid FROM student; SELECT * FROM student; -- 使用*不利于s…...

Spring源码-BeanFactory类关系层级

BeanFactory 访问Spring bean容器的根接口。 这是bean容器的基本客户端视图;例如{link ListableBeanFactory}和{link org.springframework.beans.factory.config。ConfigurableBeanFactory}可用于特定目的。 这个接口是由包含许多bean定义的对象实现的&#xff0c;每个bean定义…...

Electron 结合 Selenium + chromedriver 驱动服务实现浏览器多开

背景 在调研浏览器多开的过程中&#xff0c;electron 有自带的 browserview&#xff0c;webview&#xff0c;但是上面两个受制于 electron 内核版本限制&#xff0c;升级不够灵活&#xff0c;对新版的网页支持可能不及时&#xff0c;甚至不兼容&#xff0c;必须通过发布新的客…...

手持式气象检测设备:便携科技,气象探测

一、手持式气象检测设备&#xff1a;小巧身躯&#xff0c;大能量 手持式气象检测设备&#xff0c;顾名思义&#xff0c;是一种可以手持操作的气象监测工具。它集成了温度、湿度、气压、风速风向等多种传感器&#xff0c;能够实时获取气象数据&#xff0c;并通过显示屏或手机APP…...

shell 发送邮件脚本(免密)

#!/bin/bash ENV$1 TARGET_VERSION$2 TO$3 # SMTP服务器设置 SMTP_SERVER"邮箱服务地址" SMTP_PORT"25"# 邮件信息 FROM"jenkinsy.com" SUBJECT"Deployment Status Notification" BODY$ENV"发布完成&#xff0c;版本 &#xff1a…...