1算法思想

回溯

1.1含义

以深度优先方式搜索问题解的算法称为回溯法。

1.2思想

按照深度优先搜索策略,从根节点出发搜索解空间树,如果某结点不包含问题解,则逐层向祖先结点回溯;否则进入子树。

1.3特点

深度优先搜索+递归前设置变量,递归后清除变量设置

1.4适用

适合求取问题所有解类型的题目。例如求出问题的多少种组合。

1.5通用解法

回溯算法:

如果当前是问题的解,打印解;

设置变量;

递归;

清除变量设置。

1.6经典例题讲解

n皇后问题

在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

代码如下:

void place(int pos , vector<int>& result , vector< vector<int> >& results , int n)

{

if(pos == n)     //如果当前是问题的解,打印解

{

results.push_back(result);

return;

}

//从中选出任意一个i值作为result[pos]摆放,作为第pos行的列

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

{

bool isOk = true;

for(int j = 0 ; j < pos ; j++)

{

//如果在同一列,说明不符合,

if(result.at(j) == i)

{

isOk = false;

break;

}

//如果在同一对角线

if( abs( result.at(j) - i ) == abs(j - pos) )

{

isOk = false;

break;

}

}

if(isOk)

{

result.push_back(i);                 //设置变量

place(pos + 1 , result , results , n);    //递归

result.pop_back();                 //清除变量设置

}

}

}

1.7回溯与递归的区别

回溯通常也用到递归,递归往往求取的是一个解,回溯通常求取的是多个解。回溯的过程中需要对标记量进行设置,递归完成后,需要对标记量清除设置。

2回溯系列

类别-编号

题目

遁去的1

1

Prime ring problem

在给定的1到n的数字中,将数字填入环中,使得环中任意2个相邻的数字和为素数。按字典序输出所有符合条件的解

计算机考研—机试指南

https://blog.csdn.net/qingyuanluofeng/article/details/47186279

关键:

1 对于其余情况,首先要判断当前的数字是否在环中,若不在环中,先标记为已用,并尝试设置第num+1个数字在ans数组中,然后继续尝试放入下一个数字,最后再将该数字

重新标记为未使用

2 注意剪枝的基本过程:初始化剪枝标记,初始化节点,放入循环中

//用于判定两个数的和是否为素数。

int prime[13] = {2,3,5,7,11,13,17,19,23,29,31,37,41};

//用于存放所有数的和

int ans[N];

bool mark[N];//剪枝标记

//判断一个数是否为素数

bool judge(int x)

{

//判断一个数是否为素数

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

{

if(x==prime[i])

{

return true;

}

}

return false;

}

//判断最后一个数和第一个数是之和是否为质数,若是,输出答案

bool check(int n)

{

if(judge(ans[n] + ans[1])==false)

{

return false;

}

else

{

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

{

if(i!=1)

{

printf(" %d",ans[i]);

}

else

{

printf("%d",ans[i]);

}

}

printf("\n");

return true;

}

}

//num:ans数组中已经存入的数字,n表示总共输入的数字个数

void DFS(int num,int n)

{

//如果已经存在大于一个数字,就开始检查

if(num > 1)

{

if(judge(ans[num] + ans[num-1])==false)

{

return;

}

}

//如果所有数字全部确定完毕,需要检查第一个数和第n个数的和是否为质数

if(num==n)

{

check(n);

//检查结束之后就返回

return;

}

//对第2个数到第n个数进行判定,递归判定

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

{

//如果当前数未被尝试

if(false==mark[i])

{

mark[i] = true;//标记该数已经被尝试

ans[num+1] = i;//将新的数字加入到数组中

DFS(num+1,n);//尝试新的数字

mark[i] = false;//如果尝试失败,将当前数字重新设置为未被访问

}

}

}

2

货郎担问题

四个顶点的货郎担问题。求从顶点1出发,最后回到顶点1的最短路线。

输入:

4

0 0  1 7

8 0      5 1

7 2  0 1

2 5  3 0

输出:

1 3 2 4 1

6

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189381

v1                 v2                 v3                 v4

v1       无穷   无穷   1                   7

v2       8                   无穷   5                   1

v3       7                   2                   无穷   1

v4       2                   5                   3                   无穷

算法分析:

因为是采用回溯法来做,肯定是递归,然后还需要现场清理。

要设置一个二维数组来标识矩阵内容,然后回溯还需要设计

一个二维标记数组来剪枝,设定一个目标变量,初始为无穷大,

后续如果有比目标变量值小的就更新。剪枝的条件就是如果走到当前节点的耗费值>=目标变量,就直接不再往下面走,

向上走。

深度优先 = 递归

递归基:如果到达叶子节点的上一个节点,那么就进行是否更新的判断

递归步:如果没有到达叶子节点,就进行剪枝操作,判断能否进入下一个节点,如果能,更新最优值

关键:

1        //递归基:如果已经遍历到叶子节点的上一层节点,i标识递归深度

if(i == g_n)

{

//判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印

if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0)  &&

(g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult ))

{

g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1];

//用当前最优路径去更新最优路径,防止下一次没有

for(int k = 1 ; k <= g_n ; k++)

{

g_iBestPath[k] = pArr[k];

2        //递归步:判断能否进入子树,需要尝试每一个节点

else

{

//尝试不同的组合

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

{

//判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass

if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) )

3                                       //交换i与j,则i为当前可以尝试的范围

//为完成后面k个元素的排列,逐一对数组第n-k~n个元素互换。数组第一个元素为1,生成后面n-1个元素的排列

//数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列...

swap(&pArr[i],&pArr[j]);

//更新当前累加值,是i-1与i的

g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ];

//递归

backTrace(i+1,pArr);

//回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退

g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ];

swap(&pArr[i],&pArr[j]);

代码:

const int MAXSIZE = 100;

const int MAX = 1000000000;

int g_iArr[MAXSIZE][MAXSIZE];//邻接矩阵

int g_iResult;//存放最优解

int g_iPath[MAXSIZE];//存放最优路径上

int g_n;//元素个数

int g_iCurResult;//当前累加路径和

int g_iBestPath[MAXSIZE];//还需要设置一个数组,用来保存最优解

//可以做成字符串全排列的性质track(int i,int* pArr,int* pResult),其中pArr是用于存放最优解的路径

void backTrace(int i,int* pArr)

{

//递归基:如果已经遍历到叶子节点的上一层节点

if(i == g_n)

{

//判断累加和是否超过最大值,如果有0,应该排除;满足这个条件,才打印

if((g_iArr[pArr[i-1]][pArr[i]] != 0) && (g_iArr[pArr[g_n]][1] != 0)  &&

(g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1] < g_iResult ))

{

g_iResult = g_iCurResult + g_iArr[pArr[i-1]][pArr[i]] + g_iArr[pArr[g_n]][1];

//用当前最优路径去更新最优路径,防止下一次没有

for(int k = 1 ; k <= g_n ; k++)

{

g_iBestPath[k] = pArr[k];

}

}

}

//递归步:判断能否进入子树,需要尝试每一个节点

else

{

//尝试不同的组合

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

{

//判断能否进入子树:如果当前值+下一个连线值的和 < 最优值,就进入,0要pass

if( (g_iArr[pArr[i-1]][pArr[j]] != 0) && (g_iCurResult + g_iArr[ pArr[i-1] ][ pArr[j] ] < g_iResult) )

{

//交换i与j,则i为当前可以尝试的范围

//为完成后面k个元素的排列,逐一对数组第n-k~n个元素互换。数组第一个元素为1,生成后面n-1个元素的排列

//数组第一个元素与第二个元素互换,第一个元素为2,第2个元素为1,生成后面的n-1个元素的排列...

swap(&pArr[i],&pArr[j]);

//更新当前累加值,是i-1与i的

g_iCurResult += g_iArr[ pArr[i-1] ][ pArr[i] ];

//递归

backTrace(i+1,pArr);

//回溯,清空累加值;能够走到这里,说明上述结果不是最优解,需要向求解树上一层回退

g_iCurResult -= g_iArr[pArr[i-1]][ pArr[i] ];

swap(&pArr[i],&pArr[j]);

}

}

}

}

3

n皇后问题

在n×n格的国际象棋上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189389

//判断当前列能否摆放成功。判断存不存在相同列或者在同一个斜线上

bool place(int x[] , int k)

{

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

{

if(x[i] == x[k] || abs(x[i] - x[k]) == abs(i - k))

{

return false;

}

}

return true;

}

int backTrace(int x[], int n)

{

int k = 1 ;

x[1] = 0;

while(k > 0)

{

x[k] = x[k] + 1;

//如果不能摆放,尝试下一个列

while(x[k] <= n && !place(x , k))

{

x[k] = x[k] + 1;

}

//如果存在满足条件的列

if(x[k] <= n)

{

//如果所有皇后摆放完毕,输出结果

if(k == n)

{

break;

}

//继续处理下一个皇后,当前列摆放正确。处理下一个皇后,把其放在0列作为初始化

else

{

k++;

x[k] = 0;

}

}

//如果不满足,回溯。先置当前列的值为初始值,回溯到前一行

else

{

x[k] = 0;

k--;

}

}

}

4

图的着色问题

给定无向图G(V,E),用m种颜色为图中每个顶点着色,要求每个顶点着一种颜色,并且使得相邻两个顶点之间的颜色不同

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189399

分析:

用一个n元组描述图的一种颜色(x1,x2,...,xn),xi属于[1,m],1<=i<=n

为了用m种颜色对n个顶点着色,就有m^n中可能颜色组合,状态空间树是高度为n

的完全m叉树

约束方程:

x[i] != x[j],若顶点i与顶点j相邻 <=>a[i][j] = 1

算法分析:

使用一个n元的一维数组

1判断当前元素是否与前面的元素存在重复

2如果颜色个数超出限制,那么使颜色的使用颜色为0,然后同时

关键:

1        //为颜色赋初始值,后面会累加到1,表示采用1号颜色

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

{

pResult[i] = 0;

2                  //检测当前节点是否与之前出现的节点颜色相同;若相同,则累加颜色数,并且是作为循环判断的,因为不行的话,肯定要与下面的其他颜色进行比较,

//直至与其他颜色没有冲突为止

while(pResult[k] <= m && isSameColor(k,iArr,pResult))

{

pResult[k]++;

3                  //判断是否到循环出口,如果颜色数没有超过,说明是因为两个颜色不同而退出

if(pResult[k] <= m)

{

//如果k=n-1表示全部遍历结束,退出

if(k == n-1)

{

break;

}

//否则遍历下一个节点

else

{

k++;

4                  //如果是由于颜色数超过最大值

else

{

//因为当前节点所有颜色均试过不通过后,重新置为0,清理现场,并回溯到其上一个节点,让其颜色进行累加,经典

pResult[k] = 0;

//回到其父节点,通过改变父节点的颜色,来使子节点的颜色可以调整

k--;

代码:

bool isSameColor(int k,int iArr[MAXSIZE][MAXSIZE],int* pResult)

{

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

{

//检测当前节点是否相邻且颜色相同

if(iArr[i][k] && pResult[i] == pResult[k])

{

return true;

}

}

return false;

}

void dfs(int n,int m,int iArr[MAXSIZE][MAXSIZE],int* pResult)

{

//为颜色付初始值,后面会累加到1,表示采用1号颜色

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

{

pResult[i] = 0;

}

//如果颜色pResult[i]:表示第i号节点使用何种颜色

int k = 0;

while(k >= 0)

{

//使颜色标识累加

pResult[k]++;

//检测当前节点是否与之前出现的节点颜色相同;若相同,则累加颜色数,并且是作为循环判断的,因为不行的话,肯定要与下面的其他颜色进行比较,

//直至与其他颜色没有冲突为止

while(pResult[k] <= m && isSameColor(k,iArr,pResult))

{

pResult[k]++;

}

//判断是否到循环出口,如果颜色数没有超过,说明是因为两个颜色不同而退出

if(pResult[k] <= m)

{

//如果k=n-1表示全部遍历结束,退出

if(k == n-1)

{

break;

}

//否则遍历下一个节点

else

{

k++;

}

}

//如果是由于颜色数超过最大值

else

{

//因为当前节点所有颜色均试过不通过后,重新置为0,清理现场,并回溯到其上一个节点,让其颜色进行累加,经典

pResult[k] = 0;

//回到其父节点,通过改变父节点的颜色,来使子节点的颜色可以调整

k--;

}

}

}

5

马的遍历问题

在n*m的棋盘中,马只能走“日” 字。马从位置(x,y)处出发,把棋盘的每一格都走一次,且只走一次。找出所有路径。

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189427

问题分析:

1问题解的搜索空间?

棋盘的规模是n*m,是指行有n条边,列有m条边

马在棋盘的点上走,所以搜索控件是整个棋盘上的n*m个点

用n*m的二维数组记录马行走的过程,初值为0标识未经过。

2在寻找路径过程中,活结点的扩展规则?

对于棋盘上任意一点A(x,y),有8个扩展方向:

A(x+1,y+2),A(x+1,y-2)

A(x+2,y+1),A(x+2,y-1)

A(x-1,y-2),A(x-1,y+2)

A(x-2,y-1),A(x-2,y+1)

用数组fx[8] = {1,1,2,2,-1,-1,-2,-2},

fy[8]={2,-2,1,-1,-2,2,-1,1}来模拟马走日时下表的变化过程

问题3:扩展的约束条件?

1)不出边界

2)每个点只经过一次

棋盘点对应的数组元素初值为0,对走过的棋盘点的值置为所有步数

起点存储1,重点存储n*m

函数check,检查当前状态是否合理

问题4:搜索解空间?

搜索过程是从任一点(x,y)出发,按照深度优先原则,从8个方向

尝试一个可以走的棋盘点,直到走过棋盘上所有n*m个点。递归算法

注意要求找出全部可能的解,注意回溯过程的清理现场工作,也就是

置当前位置为未经过

数据结构设计:

1)用一个变量dep记录递归深度=走过的点数,当dep=n*m时,找到一族解

2)用n*m的二维数组记录马行走的过程,初始值为0标识未经过。搜索完毕后,

起点存储的是1,终点存储的是n*m

const int MAXSIZE = 100;

int g_count;

//方向数组

//int g_x[8] = {1,1,2,2,-1,-1,-2,-2};

//int g_y[8] = {2,-2,1,-1,-2,2,-1,1};

int g_x[8]={1,2,2,1,-1,-2,-2,-1};

int g_y[8]= {2,1,-1,-2,-2,-1,1,2};

//记录所走的顺序

int g_path[MAXSIZE][MAXSIZE];

//已经拥有深度,同时兼顾了计数的功能,因此,无需再进行计数

void dfs(int x,int y,int iDepth,int n,int m)

{

int xx,yy;

//迷宫算法典型步骤,一上来就是方向的扩展

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

{

//获取新方向

xx = x + g_x[i];

yy = y + g_y[i];

//剪枝:是否越界

if(xx < 0 || xx >= n || yy < 0 || yy >= m)

{

continue;

}

//剪枝:是否访问过

if(g_path[xx][yy] != 0)

{

continue;

}

//设定新方向的存储值

g_path[xx][yy] = iDepth;

//判断是否达到递归出口

if(iDepth == n*m)

{

//return ;

//注意,这里要走出所有路径,因此不能返回,而是输出

print(n,m);

}

//如果没有到达递归出口

else

{

//递归

dfs(xx,yy,iDepth+1,n,m);

//置已访问标记为未访问

//g_path[xx][yy] = 0;

}

}

//置已访问标记为未访问

g_path[xx][yy] = 0;

//iDepth--;

}

6

找n个数中r个数的组合

例如:当 n=5, r=3 时 , 所有组合为:

1 2 3

1 2 4

1 2 5

1 3 4

1 3 5

1 4 5

2 3 4

2 3 5

2 4 5

3 4 5 total=10 { 组合数 }

输入:

5 3

输出:

10

5 4 3

5 4 2

5 4 1

5 3 2

5 3 1

5 2 1

4 3 2

4 3 1

4 2 1

3 2 1

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189433

分析1:

每组3个数的特点:

1)互不相同;

2)前面的数小于后面的数;

将上述两条作为约束条件。

3) 当 r =3时,可用三重循环对

每组中的3个数进行 枚举 。

用递归法设计该问题:

每个组合中的数据必须从大到小排列,因为递归算法设计是要找出大规模问题与

小规模问题之间的关系

分析2:

分析n=5,r=3时10个组合数

1)首先固定第一个数5,然后就是n=4,r=2的组合数,共6个组合

2)其次固定第一个数4,其后就是n=3,r=2的组合数,共3个

3)最够固定第一个数3,后面就是n=2,r=2的组合数,共1个

至此,找到了“5个数中3个数的组合”与"4个数中2个数的组合"

,3个数中2个数的组合,2个数中2个数的组合的递归关系

递归算法的三个步骤:

1)n个数中r个数组合递推到n-1个数r-1个数有组合,n-2个数中r-1个数有组合

,...,r-1个数中有r-1个数有组合,共n-(r-1)次递归

2)递归地边界条件是r=1

3)函数主要操作是输出,每当递归到r=1时,就有一个新的组合产生,输出他们和

一个换行符,

先固定5,然后进行多次递归,数字5要多次输出,所以要用数组存储以备每次

递归到r=l时输出。

同样每次向下递归都要用到数组,所以将数组设置为全局变量

const int MAXSIZE = 100;

int g_iArr[MAXSIZE];

bool isOk()

{

int iTemp;

int iNext;

int iCur = 100000;

for(int j = g_iArr[0]; j >= 1; j--)

{

iNext = g_iArr[j];//第一个数

//判断前面的数是否比自己大,正常的顺序应该是前面大,后面小,如果不符合就是前面<=后面。现在拿到的第一个是最前面的

if( iCur <= iNext)

{

return false;

}

iCur = iNext;

}

return true;

}

void dfs(int n,int r)

{

for(int i = n ; i >= r ; i--)

{

//设定当前选中的元素为第一个,因为元素的选取是从大到小,因此为i

g_iArr[r] = i;

//递归基,当r减为1,说明成功了

if(r == 1)

{

if(isOk())

{

for(int j = g_iArr[0]; j >= 1; j--)

{

cout << g_iArr[j];

}

cout << endl;

}

}

//递归步,从剩余n-1个数中挑选r-1个数

else

{

//添加限制条件,后面的大于前面,不能重复

dfs(n-1,r-1);

}

}

}

7

构造高精度数据:

构造一个尽可能大的数,使其从高到低前一位能被一整除,前2位能被2整除,...,前n位能被n整除

输出:

447204888644483284

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189449

分析:

解空间:记高精度数据为a1a2...an,

则(a1*10^(n-1) + a2*10^(n-2) + ... + an)整除n

如何搜索?

从高位到低维逐位尝试,失败回溯的算法。算法的首位从1开始枚举,以后各位从0开始枚举。生成的高精度数据用数组从高位到低位

存储,一号元素存储最高位。

如何确定和保留最优解?

用数组A作为当前求解的高精度数据的暂存处。数组B为当前最大的满足条件的数。求解出的满足条件的数之间只需要比较位数就能确定大小。

n为当前满足条件的最大数据的位数,当i > n时,位数多数据一定大;i = n时,由于搜索是由小到大进行的,位数相等时后来满足条件的

数据一定比前面的大。

关键:

1 设置数组首位为1 A[1] = 1;

2 循环退出条件为A[1] > 9 ,while(A[1] <= 9)//因为最后会回溯到A[1],而A[1]的值最多为9

3 当 i >= n时,更新数据

if(i >= n)//寻找到更大的数据,将临时数组中的数据保存到最终数据中

{

n = i ;//更新n

4 检查第i位是否符合条件

r = r % i;//获取第i位的余数

if(r != 0)//第i位失败

5 构造第i位可能的解

//构造第i位的可能解,也就是加上i-r,构成i缺少的部分

A[i] = A[i] + i - r;

6 确定第i位是否需要回溯,是当其值大于9,并且位数大于1

while(A[i] > 9 && i > 1)//搜索到第i位的解,回溯到前一位

7 回溯的做法是:另当前位为0,A[i] = 0 , 再减小位数;i--,另上一位可能解为:A[i] = A[i] + i;

A[i] = 0;//置当前位为0

i = i - 1;

A[i] = A[i] + i;//?尝试前一位,加i的原因是,在前一位的基础上加i,上一位必定能被i整除。

代码:

void num()

{

//设置数组,用于暂存

int A[101] , B[101];

//初始化数组

memset(A, 0 , sizeof(A));

memset(B, -1 , sizeof(B));

//设置暂存数组的首位为1

A[1] = 1;

int i = 1;

int n = 1;

while(A[1] <= 9)//因为最后会回溯到A[1],而A[1]的值最多为9

{

if(i >= n)//寻找到更大的数据,将临时数组中的数据保存到最终数据中

{

n = i ;//更新n

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

{

B[k] = A[k];

}

}

i = i + 1;

int r = 0;

for(int j = 1 ; j <= i ; j++)//检查第i位是否符合条件

{

r = r * 10 + A[j];

}

r = r % i;//获取第i位的余数

if(r != 0)//第i位失败

{

//构造第i位的可能解,也就是加上i-r,构成i缺少的部分

A[i] = A[i] + i - r;

while(A[i] > 9 && i > 1)//搜索到第i位的解,回溯到前一位

{

A[i] = 0;//置当前位为0

i = i - 1;

A[i] = A[i] + i;//?尝试前一位,加i的原因是,在前一位的基础上加i,上一位必定能被i整除。

}

}

}

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

{

if(B[i] == -1)

{

break;

}

cout << B[i] ;

}

cout << endl;

}

8

流水作业车间调度:

n个作业要在两台机器M1和M2组成的流水线上完成加工。每个作业加工的顺序都是现在M1上加工,然后在

M2上加工。M1和M2加工作业i所需的时间分别为ai和bi。流水作业调度问题要求确定这n个作业的最优

加工顺序,使得从第一个作业在机器M1上开始加工,到最后一个作业在机器M2上加工完成所需的时间

最少。作业在机器M1和M2的加工顺序相同。

输入:

3(作业数)

2 1

3 1

2 3

输出:

8

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189475

分析:

类似字符串全排列的回溯算法结构。固定第一位,生成后续排列,

然后交换第一位与后面某一位,固定第一位,生成后续排列。

解空间:是排列树,用数组x[](初值为1,2,3,...,n)模拟不同排列

如何计算完成时间?

对机器M1进行加工,加工时间f1是固定的。f1[i] = f1[i-1] + M1[x[i]]

对机器M2加工分为两种情况

1)有空闲

##a1........##a2...........##

##b1....       ##b2.........##

空闲时,f2[i] = f1[i] + M2[x[i]]

2)积压时

##a1........##a2...........##

##b1..................##b2.........##

空闲时,f2[i] = f2[i-1] + M2[x[i]]

如何获取最优解?

最优调度应使及其M1没有空闲时间,机器M2空闲时间最少。

在搜索排列树的同时,不断更新最优解,最后找到问题的最优解。

搜索过程中,当某一排列的前几步的加工时间已经大于当前的最小值,就无需搜索计算。

数据结构设计:

二维数组job[100][2]存储作业在M1,M2的加工时间。

由于f1在计算中,只需当前值,用变量存储即可;f2在计算时,还依赖前一个作业的数据,

所以用数组存储。

变量f存储当前加工需要的全部时间。

算法步骤:

1 如果搜索到叶节点(必然是最优的),得到调度方案,更新

2 对当前作业直到最后作业做迭代

3 计算机器1工作时间 , 判断机器2处于积压或者空闲 并更新f2

4 更新直到当前结点的累加值

5 若累加值小于最优解,则进入该结点的叶子结点,交换两个作业,回溯下一个结点,再交换回来

6 将f1设置为回溯前的值,将累加值还原为回溯前的值

7 这是排列树问题,初始化应该给与一个排列

//这是回溯中的排列树问题:需要赋予一个初始排列。排列树:

//选取的n个元素满足某种排列性质,时间复杂度为O(n!)

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

{

//设置一个初始调度为调度作业1,2,...,n的顺序

x[k] = k;

}

8 初始最优值应该置为无穷大

bestf = 1000000000;//易错,最优值应该初始化为无穷大

代码:

int n;//作业数

int bestf;//存放最优调度时间

int f1;//机器1的调用时间

int f2[100];//机器2的调用时间

int f;//直至当前结点的机器调用累加时间

int job[100][3];//作业的运行时间

int x[100];//易错,当前调度

int bestx[100];//易错,存放当前作业最佳调度

void backTrace(int i)

{

if(i == n + 1)//如果到达叶子结点,则说明产生了调度方案,更新

{

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

{

bestx[j] = x[j];

}

bestf = f;

}

else

{

//回溯的标准格式

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

{

f1 = f1 + job[ x[j] ][1];

//如果机器2存在积压

if(f2[i-1] > f1)

{

f2[i] = f2[i-1] + job[ x[j] ][2];

}

else

{

f2[i] = f1 + job[ x[j] ][2];

}

f = f + f2[i];

//判断累加和是否小于最优调度值,是就进行继续尝试下一个结点

if(f < bestf)

{

swap(&x[i] , &x[j]);

backTrace(i+1);

swap(&x[i] , &x[j]);

}

//清理现场

f1 = f1 - job[ x[j] ][1];

f = f - f2[i];

}

}

}

void process()

{

while(cin >> n)

{

//输入

memset(job , 0 , sizeof(job));

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

{

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

{

cin >> job[i][j];

}

}

//计算

f = 0;

f1 = 0;

bestf = 1000000000;//易错,最优值应该初始化为无穷大

memset(f2 , 0 , sizeof(f2));

//易错,这是回溯中的排列树问题:需要赋予一个初始排列。排列树:选取的n个元素满足某种排列性质,时间复杂度为O(n!)

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

{

//设置一个初始调度为调度作业1,2,...,n的顺序

x[k] = k;

}

backTrace(1);

cout << f2[n] << endl;

}

}

9

连续邮资问题:

假设国家发行了n种不同面值的邮票,并且规定每张信封上最多只允许贴m张邮票。连续邮资问题要求对于给定的n和m的值,给出邮票面值的最佳设计,在一张信封上可以贴出从邮资1开始,增量为1的最大连续邮资区间。

输入:

2(邮票的种类数) 3(允许张贴的邮票数)

2 3

5 4

输出:

7

1 3

70

1 3 11 15 32

算法设计与分析

https://blog.csdn.net/qingyuanluofeng/article/details/47189483

举例分析:

当n=2,m=3时,如果面值分别为1和4,则可以获得的邮资范围为1~6 加上 8 , 9 , 12

如果过面值为1,3,则可以获得1~7之间的每个邮资值,并且7就是可以得到的连续的邮资最大值

问题分析:

寻找子集就是子集树

寻找元素排列就是排列树

这是子集树问题,因为要求一个集合S,集合S中的元素满足某种性质。是从n个元素的集合S中,找出S满足某种性质的子集

思路:搜索可行解

解向量:用n元组x[1:n]表示n种不同邮票面值,从小到大排列

约束函数:若选定x[1:i-1],并且取值范围为:1~r,那么x[i]取值范围为x[i-1]+1~r+1

如何确定r的值?

计算x[1:1]的最大连续邮资区间时,直接递归复杂度较高。

尝试计算用不超过m张面值为x[1:i]贴出邮资k所需的最少邮票数为y[k],通过y[k]可以推算出r(最大连续邮资)的值,

y[k]可以通过递推在O(n)时间内解决

不懂

算法步骤:

1 初始化数组

2 计算任意邮资需要的最少张数

3 判断是否越界,并更新最优值

4 拷贝数组,对x[i]范围的x[i-1]+1 到 r,更新当前解,递归调用 , 回溯

代码

int n ,m ;     //邮票种类数,邮票允许的张帖数

int x[100];    //当前解

int bestx[100];//当前最优解

int y[10000];  //贴出各种邮资所需要的最少邮票数

int maxint;    //大整数

int maxl;      //邮资上界

int maxvalue;  //当前最优值

void backTrace(int i ,int r)

{

//递推求解任意邮资所需要的最少邮资数

int z[10000];

for(int j = 0 ; j <= x[i-2]*(m-1) ; j++)//这里是x-2

{

//如果小于最多邮资数

if(y[j] < m)

{

for(int k = 1 ; k <= m - y[j] ; k++)

{

//更新邮资所需的最少张数

if(y[j] + k < y[j + x[i-1] * k] )

{

y[j + x[i-1]* k ] = y[j] + k;

}

}

}

}

//更新最大连续邮资值

while(y[r] < maxint )

{

r++;

}

//判断是否搜索到最优解

if(i > n)

{

//判断是否超过最优解

if(r - 1 > maxvalue)//?r - 1

{

maxvalue = r - 1;

//更新最优解

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

{

bestx[p] = x[p];

}

return ;//易错,直接退出

}

}

//拷贝邮资最少张数数组,用于回溯,对于每个x[i]的可能值进行赋值并求解

for(int k = 1 ; k <= maxl; k++)

{

z[k] = y[k];

}

for(int j = x[i-1] + 1 ; j <= r ; j++)

{

x[i] = j ;

backTrace( i+1 , r);//递归下一个编号

//回溯

for(int k = 1 ; k <= maxl; k++)

{

y[k] = z[k];

}

}

}

void process()

{

while(cin >> n >> m)

{

maxint = 32767;

maxl = 1500;

maxvalue = 0;

//初始化解

memset(x , 0 , sizeof(x));

//初始化邮资所需要的最少张数

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

{

y[i] = maxint;

}

y[0] = 0;

x[1] = 1;//第一个邮票面值必须为1

backTrace(2,1);//第一个参数是邮票编号,第二个参数是最大连续邮资

cout << maxvalue;

}

}

10

24点游戏:

给玩家4张牌,每张牌的面值在1~13之间,允许其中有数值相同的牌。采用加减乘除,允许中间运算存在小数,并且可以使用括号,但每张牌只能使用一次,尝试构造表达式,使其运算结果为24.

  1. 输入:n1,n2,n3,n4

输出:若能得到运算结果为24,输出对应表达式

输入:

11,8,3,5

输出:

(11-8)*(3+5)=24

编程之美

https://blog.csdn.net/qingyuanluofeng/article/details/47186763

解法2:

定义要计算的初始数据,放于集合A中,定义函数f(A)为对集合A中的元素进行所有可能的四则运算所得到的值,采用分治思想,先将A划分为两个子集A1和A-A1,

其中A1为A的非空真自己,分别计算A1和A-A1中的元素进行四则运算得到的结果集合,即f(A1)和f(A-A1),然后对f(A1)和f(A-A1)这两个集合中的元素进行加减

乘除运算,最后得到的所有集合的并集就是f(A)。

分治:划分,递归求解,合并

给定两个多重集合A和B,定义两个集合中的元素如下:

Fork(A,B) = 并{a+b,a-b,b-a,a*b,a/b(b!=0),b/a(a!=0)},(a,b)属于A*B,假设A1中有n个元素,A2中有m个元素,那么将有n*m个(a,b),而每对值需要进行6个计算,

Fork(A1,A2) = 2*n*m个元素,需要去重。假设集合A中有n个元素,那么集合A的所有非空真子集个数为2^n-2,则f(A)第一层递推式中共有(2^n-2)/2个Fork函数

可以用二进制数来表示集合和子集,由于只有4个元素,可以采用4位的二进制数来表示集合A及其真子集,设A{a0,a1,a2,a3},当且仅当ai在某一个真子集中时,

该真子集所代表的二进制数对应的第i位才为1,如A1 = {a1,a2,a3}则1110表示A1,若A2 = {a0,a3},那么1001表示A2,所以A的真子集范围为1到14(1到2^n-2),

再用一个大小为2^n-1的数组S来保存f(i)(1<=i<=15),数组S中的每一个元素S[i]都是一个集合(f(i)),其中S[2^n-1]即为集合A中的所有元素通过四则运算和加括号得到

的全部结果,通过检查S[2^n-1],可得知某个输入是否有解。

24Game(Array)//Array为初始输入集合

{

for(int i = 1 ; i <= 2^n-1 ; i++)

{

S[i] = 空集;//初始化将S中的各个集合置为空集,n为集合Array的元素个数,在24点中即为4,

}

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

{

S[2^i] = {ai};//先对每个只有一个元素的真子集赋值,即为该元素本身

}

for(int i = 1 ;i <= 2^n - 1 ; i++)//对每个i都代表着Array的一个真子集

{

S[i] = f(i);//

}

Check(S[2^n-1]);//检查S[2^n-1]中是否有值为24的元素,并返回

}

f(int i)//i的二进制表示可代表着集合的一个真子集

{

if(S[i] != 空集)

{

return S[i];

}

for(int x = 1 ; x < i ; i++)//只有小于i的x才可能称为i的真子集

{

if((x & i) == x)//&为与运算,只有当x & i == x成立时,x才为i的子集,此时i-x为i的另一个真子集,x与i-x共同构成i的一个划分

{

S[i] U= Fork(f(x),f(i-x));//U为集合的bing yunsuan ,Fork的过程中,去除重复中间结果

}

}

}

代码

const double Threshould = 1E-6;//浮点数的误差值

const int CardsNumber = 4;

const int ResultValue = 24;

double number[CardsNumber];

string result[CardsNumber];

bool dot(int n)

{

if(n == 1)//递归出口

{

if(fabs(number[0] - ResultValue) < Threshould)

{

cout << result[0] <<endl;

}

else

{

return false;

}

}

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

{

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

{

double a,b;

string expa,expb;

a = number[i];

b = number[j];

number[j] = number[n-1];//?

expa = result[i];

expb = result[j];

result[j] = result[n-1];//?

result[i] = '(' + expa + '+' + expb + ')';

number[i] = a + b;

if(dot(n-1))

{

return true;

}

result[i] = '(' + expa + '-' + expb + ')';

number[i] = a - b;

if(dot(n-1))

{

return true;

}

result[i] = '(' + expa + '-'+ expb + ')';

number[i] = b - a ;

if(dot(n-1))

{

return true;

}

result[i] = '(' + expa + '*'+ expb + ')';

number[i] = a * b;

if(dot(n-1))

{

return true;

}

if(b != 0)

{

result[i] = '(' + expa + '/'+ expb + ')';

number[i] = a / b;

if(dot(n-1))

{

return true;

}

}

if(a != 0)

{

result[i] = '(' + expa + '/'+ expb + ')';

number[i] = b / a;

if(dot(n-1))

{

return true;

}

}

number[i] =a ;

number[j] = b;

result[i] = expa;

result[j] = expb;

}

}

return false;

}

void process()

{

int x;

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

{

char buffer[20];

cout << "the" << i << "th number:";

cin >> x;

number[i] = x;

itoa(x,buffer,10);

result[i] = buffer;

}

if(dot(CardsNumber))

{

cout<< "成功了" <<endl;

}

else

{

cout<< "失败了" << endl;

}

}

11

八皇后问题

设计一种算法,打印八皇后在8*8棋盘上的各种摆法,其中每个皇后都不同行、不同列,也不在对角线上。这里的“对角线”指的是所有的对角线,不只是平分整个棋盘的那两条对角线。

输入:

4(n皇后中的n)

输出:

0 1 0 0

0 0 0 1

1 0 0 0

0 0 1 0

0 0 1 0

1 0 0 0

0 0 0 1

0 1 0 0

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54293895

关键:

1 如何判定不在对角线:

countXY[9]  ,存储每个棋子的x-y + 7的值,对于任意元素countXY[i] <= 1,这个没有用,

参见:(1,4),(2,3)是对角线,但是用:|x-y|,或x-y来做判断都不行,因为题目指明了不是中间那条对角线

即任意两点的斜率的绝对值不为1,如果两点x相同或y相同直接排除,这样需要把8个点找出来,进行任意两点比较

2 另一种简单方法,将columns[i]表示横坐标为i对应的点的纵坐标

//判断待摆放位置是否和前面的棋子斜率相同或处于同一竖直线,不需要任意两个点两两比较

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

{

//牛逼,斜率比较方式, c[i] - c[j] 与 i-j的绝对值比较

if( columns[i] == columns[count] || abs( columns[i] - columns[count] ) == abs( i - count ) )

{

return false;

}

}

代码

const int MAXSIZE = 10000;

int g_matrix[MAXSIZE][MAXSIZE];

bool isOK_simple(int n , int count , int* columns)

{

if(NULL == columns)

{

return false;

}

//判断待摆放位置是否和前面的棋子斜率相同或处于同一竖直线,不需要任意两个点两两比较

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

{

//牛逼,斜率比较方式, c[i] - c[j] 与 i-j的绝对值比较

if( columns[i] == columns[count] || abs( columns[i] - columns[count] ) == abs( i - count ) )

{

return false;

}

}

return true;

}

//更简单的方法,我用x[i]表示第i行上的棋子纵坐标y,而不需要用二维数组

void setQueen_Simple(int n , int count , int* columns)

{

if(count > n)

{

//输出结果

printResult_simple(columns , n);

}

else

{

//确定列的摆放

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

{

//如果当前位置没有访问过

if( 0 == columns[count] )

{

columns[count] = j;

//如果可以

if(isOK_simple(n , count , columns ))

{

setQueen_Simple(n , count + 1 , columns);

}

//便于回溯

columns[count] = 0;

}

}

}

}

12

给定两个字典里的单词,长度相等。编写一个方法,将一个单词变换成另一个单词,一次只改动一个字母。在变换过程中,每一步得到的新单词都必须是字典里里存在的。

程序员面试金典

https://blog.csdn.net/qingyuanluofeng/article/details/54632381

关键:

1我卡在了寻找中间替代字符。书上结果用了回溯,果然想法是一样的。

用广度优先,设定原始字符串压入队列,设定每次从队列中弹出字符串和目标字符串比较,

如果相同,则寻找到上一个可以经过修改一个字符等于目标字符串的字符串,再根据该字符串重复上述操作,得到结果;

如果不相同,则寻找变成当前字符串只需改动一个字符的字符串集合,遍历其中每个字符串,如果

没有访问过就加入队列,并设置该字符串可以变成当前字符串。

2 一定要生成当前字符串经过一次字符变换后的字符串集合;先建立字符串指向回溯关系,再判断是否找到

while(!queueStr.empty())

{

string value = queueStr.front();

queueStr.pop();

//寻找经过一次字符变换就可以生成的单词v

newWords = getOneEditWords(value , words);

for(set<string>::iterator it = newWords.begin() ; it != newWords.end() ; it++)

{

string newWord = *it;

//应该先判断,如果新单词在字典中存在,就建立映射,否则映射还没建立,就找到结果,回溯的时候,就无法回溯

if( words.find(newWord) != words.end() )

{

//如果新单词没有访问过,就压入队列

if( visitedMap.find(newWord) == visitedMap.end() )

{

queueStr.push(newWord);

visitedMap[newWord] = true;

resultMap[newWord] = value;//设置指向,这个要放到前面去做

}

}

//说明找到了,直接寻找结果

if(newWord == dst)

{

resultVector.push_back(newWord);

map<string , string>::iterator itFind = resultMap.find(newWord);

while(itFind != resultMap.end())

{

string previousStr = itFind->second;

resultVector.push_back(previousStr);

itFind = resultMap.find(previousStr);

}

return resultVector;

}

}

}

代码:

set<string> getOneEditWords(string& word , hash_map<string , int>& words)

{

set<string> resultSet;

if(word.empty())

{

return resultSet;

}

int length = word.length();

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

{

char value = word.at(i);

//注意这里要留取待截取字符串前面的位置i,不是i+1

string frontStr = word.substr(0 , i);

string backStr = word.substr(i+1);

for(char ch = 'a' ; ch <= 'z' ; ch++)

{

if(ch != value)

{

//尝试组成新的单词

stringstream stream;

stream << frontStr << ch << backStr;

string newWord = stream.str();

//如果该单词存在于字典中,则加入结果集

if(words.find(newWord) != words.end())

{

resultSet.insert(newWord);

}

}

}

}

return resultSet;

}

vector<string> getTransformStrings(string& src, string& dst ,hash_map<string ,int>& words )

{

vector<string> resultVector;

if(src.empty() || dst.empty() || words.empty())

{

return resultVector;

}

//注意,一定要将字符串全部改成小写(因为我的字典中单词是小写的)

transform(src.begin() , src.end() , src.begin() , tolower);

transform(dst.begin() , dst.end() , dst.begin() , tolower);

queue<string> queueStr;

hash_map<string , bool> visitedMap;//是否访问的标记,true表示已经访问过

visitedMap[src] = true;

queueStr.push(src);

map<string ,string> resultMap;

set<string> newWords;

while(!queueStr.empty())

{

string value = queueStr.front();

queueStr.pop();

//寻找经过一次字符变换就可以生成的单词v

newWords = getOneEditWords(value , words);

for(set<string>::iterator it = newWords.begin() ; it != newWords.end() ; it++)

{

string newWord = *it;

//应该先判断,如果新单词在字典中存在,就建立映射,否则映射还没建立,就找到结果,回溯的时候,就无法回溯

if( words.find(newWord) != words.end() )

{

//如果新单词没有访问过,就压入队列

if( visitedMap.find(newWord) == visitedMap.end() )

{

queueStr.push(newWord);

visitedMap[newWord] = true;

resultMap[newWord] = value;//设置指向,这个要放到前面去做

}

}

//说明找到了,直接寻找结果

if(newWord == dst)

{

resultVector.push_back(newWord);

map<string , string>::iterator itFind = resultMap.find(newWord);

while(itFind != resultMap.end())

{

string previousStr = itFind->second;

resultVector.push_back(previousStr);

itFind = resultMap.find(previousStr);

}

return resultVector;

}

}

}

return resultVector;

}

13

Combination Sum

Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

Note:

All numbers (including target) will be positive integers.

The solution set must not contain duplicate combinations.

For example, given candidate set [2, 3, 6, 7] and target 7,

分析:

这是程序员面试金典的一道题目。求组成数的所有组合。

输入:

4(数组元素个数) 7(目标值)

2 3 6 7

4 6

2 3 6 7

2 7

2 6

输出:

7,2 2 3

6,3 3,2 2 2

no result

Leecode

https://blog.csdn.net/qingyuanluofeng/article/details/54934859

关键:

1 前半部分求解结果,前半部分 与 后半部分结果 进行笛卡尔积 即为最终结果

int curCandidate = candidates.at(curCandidateIndex);

vector<vector<int>> results;//后半部分递归求解结果

vector<int> result;//前半部分求解结果,前半部分 与 后半部分结果 进行笛卡尔积 即为最终结果

for(int i = 0 ; i * curCandidate <= target ; i++)

{

result.clear();//清空上一次结果

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

{

result.push_back(curCandidate);

}

results = combineSum(candidates , target -  i * curCandidate , curCandidateIndex + 1 );//得到多个结果需要和当前结果进行笛卡尔积拼接

int size = results.size();

//进行笛卡尔积拼接

for(int k = 0 ; k < size ; k++)

{

results.at(k).insert(results.at(k).end() , result.begin() , result.end());//插入到后面,前面是最小部分

totalResults.push_back(results.at(k));

}

//如果当前直接等于结果集i * curCandidate = target,直接压入结果中

if(i * curCandidate == target)

{

totalResults.push_back(result);

}

}

return totalResults;

代码

bool compare(int a, int b)

{

return a > b;

}

class Solution {

public:

vector<vector<int>> combineSum(vector<int>& candidates, int target , int curCandidateIndex)

{

vector< vector<int> > totalResults;

if(candidates.empty() || target <= 0 || curCandidateIndex < 0 || curCandidateIndex >= candidates.size())

{

return totalResults;

}

int curCandidate = candidates.at(curCandidateIndex);

vector<vector<int>> results;//后半部分递归求解结果

vector<int> result;//前半部分求解结果,前半部分 与 后半部分结果 进行笛卡尔积 即为最终结果

for(int i = 0 ; i * curCandidate <= target ; i++)

{

result.clear();//清空上一次结果

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

{

result.push_back(curCandidate);

}

results = combineSum(candidates , target -  i * curCandidate , curCandidateIndex + 1 );//得到多个结果需要和当前结果进行笛卡尔积拼接

int size = results.size();

//进行笛卡尔积拼接

for(int k = 0 ; k < size ; k++)

{

results.at(k).insert(results.at(k).end() , result.begin() , result.end());//插入到后面,前面是最小部分

totalResults.push_back(results.at(k));

}

//如果当前直接等于结果集i * curCandidate = target,直接压入结果中

if(i * curCandidate == target)

{

totalResults.push_back(result);

}

}

return totalResults;

}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {

//必须确保候选值从大到小排序

sort(candidates.begin() , candidates.end() , compare );

int curCandidateIndex = 0;

vector<vector<int>> results = combineSum(candidates, target , curCandidateIndex);

return results;

}

};

参考:
[1]计算机考研--机试指南,王道论坛 组编
[2]剑指offer
[3]算法设计与分析
[4]编程之美
[5]程序员面试金典
[6]leecode
[7]Python程序员面试算法宝典
[8]刘汝佳算法竞赛入门经典
[9]算法导论
[10]编程珠玑

算法 64式 4、回溯算法整理__第1部分_1到13题相关推荐

  1. 算法 64式 7、搜索算法整理_第1部分_1到15题

    1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...

  2. 算法 64式 7、搜索算法整理_第4部分_46到60题

    1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...

  3. 算法 64式 7、搜索算法整理_第3部分_31到45题

    1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...

  4. 算法 64式 7、搜索算法整理_第2部分_16到30题

    1 算法思想 算法分类 搜索算法主要分为: 暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等 1.1枚举 枚举: 最直白的搜索方式, ...

  5. (十五)算法设计思想之“回溯算法”

    算法设计思想之"回溯算法" 回溯算法是什么? 什么问题适合用回溯算法解决? 适合回溯算法解决的问题 全排列 LeetCode:46.全排列 LeetCode:78.子集 思考题 回 ...

  6. 极客时间——数据结构与算法(39) 回溯算法:从电影《蝴蝶效应》中学习回溯算法的核心思想

    转载地址:https://time.geekbang.org/column/article/74287 我们在第 31 节提到,深度优先搜索算法利用的是回溯算法思想.这个算法思想非常简单,但是应用却非 ...

  7. JAVA算法:走迷宫回溯算法设计(JAVA版本)

    JAVA算法:走迷宫回溯算法设计(JAVA版本) 迷宫数组 int[][] maze = {                 {0, 1, 0, 0, 0},                 {0, ...

  8. 算法 64式 8、动态规划算法整理_第1部分_1到15题

    1 算法思想 动态规划 1.1含义 把问题分解成多阶段或多个子问题,顺序求解各个子问题,最后一个子问题就是初始问题的解. 概念 阶段: 问题分成的顺序的几个环节.例如最长递增子序列中每个字符就是一个阶 ...

  9. 算法 64式 19、数学算法整理

    1 算法思想 2 数学系列 类别-编号 题目 遁去的一 1 特殊乘法 写个算法,对2个小于1000000000的输入,求结果. 特殊乘法举例: 123 * 45 = 1*4 + 1*5 + 2*4 + ...

最新文章

  1. 用Spring Web Flow和Terracotta搭建Web应用
  2. 高清视频实时对讲SDK源码
  3. 直接定址表03 - 零基础入门学习汇编语言74
  4. python匿名函数调用_python3笔记十六:python匿名函数和高阶函数
  5. GDCM:gdcm::ImageHelper的测试程序
  6. python导入同一文件夹下的类_python自定义模块
  7. Gitter - 高颜值GitHub小程序客户端诞生记
  8. jhat命令 Java Heap Analyse Tool
  9. CRNN+CTC (基于CTPN 的end-to-end OCR)
  10. 【图像检索】基于matlab GUI Hu不变矩图像检索【含Matlab源码 1508期】
  11. 【百度网盘】老罗android开发视频教程[压缩后3.63G]
  12. vector的初始化和使用
  13. java 打包加密_java打包、加密、发布(源代码保护)
  14. Paper和陈丹琦撞车是一种怎样的体验
  15. 评:10月PMI指数新高, 带动大盘逆转, 跨年度业绩行情展开
  16. TalkingData三大产品创新,引领2022数字营销技术新格局
  17. MM们必败潮物。。。。大眼睛的小秘密哦```````
  18. 22考研杭师管理科学与工程专业368分经验贴(Python141+政治80+日语71,含数政专书籍与免费课程资源推荐)
  19. EF 之 System.InvalidOperationException
  20. Java导出excel中response.setHeader()参数设置

热门文章

  1. jquery ajax java二级联动_使用Ajax和Jquery配合数据库实现下拉框的二级联动的示例...
  2. Vim 分屏功能+无插件Vim编程技巧
  3. 1,10-Phen|邻菲啰啉|邻二氮杂菲|1,10-菲啰啉有机配体-66-71-7
  4. JS中的数组转变成JSON格式字符串的方法
  5. 【改进模糊神经网络】基于粒子群-万有引力算法PSOGSA 改进的前馈神经网络研究(Matlab代码实现)
  6. C#毕业设计——基于C#+asp.net+sqlserver基于C2C模式的网上购物系统设计与实现(毕业论文+程序源码)——网上购物系统
  7. Mac上的终端Shell命令总结(初级)
  8. PostgreSQL 源码解读(212)- 后台进程#11(checkpointer-SyncOneBuffer)
  9. 全新型号,戴尔(Dell) EMC PowerEdge R760机架式服务器产品特性及详细技术参数
  10. 将ES6代码转换为ES5代码