1 算法思想
算法分类
搜索算法主要分为:
暴力搜索+剪枝,枚举,广度优先搜索,深度优先搜索,二分查找,哈希查找, A*算法,两边向中间逼近,从中间向两边扩散等
1.1枚举
枚举: 最直白的搜索方式,依次尝试搜索空间中的所有解。可以在搜索过程中通过加强条件约束来减少搜索范围图。
例如: 百鸡问题
1.2广度优先搜索(BFS)
含义:遍历解答树时使每次状态转移时扩展出尽可能多的状态,并按照各状态出现顺序依次扩展它们。
表现:在解答树上表现为树的层次遍历。
适用:可用于求解最优问题。因为其搜索到的状态总是按照某个关键字递增(例如时间,倒杯次数等)。一旦问题中出现最少,最短,最优等关键字,
就需要考虑是否使用广度优先搜索。
实现: 一般用队列实现,用结构体保存每个状态,用标记数组防止无效搜索。
实现过程:
1)定义结构体用于保存每个状态,定义标记数组防止无效搜索
2)初始化第一个元素,并将该元素塞入队列中,设置第一个元素为已经访问
3)只要队列非空,得到并弹出队头元素,扩展得到新的元素,
对每个新元素,判断其如果满足约束条件并且是未遍历过的,
则更新新元素的状态,并将新元素塞入队列,设置新元素为已经访问过,
如果新元素是所求解,则直接返回
剪枝: 剪去解答树上不可能存在答案的子树。
1.3深度优先搜索(DFS)
含义: 优先遍历层次更深的状态,直到遇到一个状态节点不再拥有子树,则返回上一层,
访问其未被访问过的子树,直到解答树中所有状态被遍历完成。
适用: 深度优先搜索缺少广度优先搜索按层次递增顺序遍历的特性,深度优先搜索到的状态不再具有最优特性,深度优先搜索更多求解的是有解或者无解的问题。
实现: 通常使用递归实现。
实现过程:
1)扩展得到新元素,如果新元素不符合约束条件,则过滤该新元素;
2)基于当前状态更新得到新元素状态,判断新元素状态是否等于所求状态,如果是,设置结果标记为成功并直接返回;
3)否则,设置新元素为已经访问,递归处理,设置新元素为未访问(因为后续状态全部遍历完成,需要退回上层状态),如果结果标记为成功,则停止搜索
1.2 特点
1.3适用
1.4通用解法
广度优先搜索(BFS)算法:
1)定义结构体用于保存每个状态,定义标记数组防止无效搜索 2)初始化第一个元素,并将该元素塞入队列中,设置第一个元素为已经访问 3)只要队列非空,得到并弹出队头元素,扩展得到新的元素, 对每个新元素,判断其如果满足约束条件并且是未遍历过的, 则更新新元素的状态,并将新元素塞入队列,设置新元素为已经访问过, 如果新元素是所求解,则直接返回
|
深度优先搜索(DFS)算法:
1)扩展得到新元素,如果新元素不符合约束条件,则过滤该新元素; 2)基于当前状态更新得到新元素状态,判断新元素状态是否等于所求状态,如果是,设置结果标记为成功并直接返回; 3)否则,设置新元素为已经访问,递归处理,设置新元素为未访问(因为后续状态全部遍历完成,需要退回上层状态),如果结果标记为成功,则停止搜索 |
1.5经典例题讲解
广度优先搜索(BFS):
胜利大逃亡
我被魔王抓走,城堡是A*B*C的立方体,即A个B*C的矩阵,我被关在(0,0,0)位置,出口在(A-1,B-1,C-1),魔王在T分钟内回到城堡,
我每分钟能移动1个坐标。若走到出口恰遇魔王,也算成功。请输出多少分钟可以离开,不能则输出-1
代码:
typedef struct Stat //定义结构体用于保存每个状态 { int x,y,z;//坐标 int t;//从(0,0,0)到达该坐标的时间 }Stat;//保存当前节点的状态
int maze[N][N][N];//用于标识每一个坐标是0:路,1:墙 bool mark[N][N][N];// 定义标记数组防止无效搜索,用于标识该坐标是否已经搜索过,false:未搜索过,true:搜索过,便于剪枝 int goNext[][3] = {1,0,0, -1,0,0, 0,1,0, 0,-1,0, 0,0,1, 0,0,-1};//用于进行下一次6个可达状态的遍历
queue
//进行深度遍历,无需判断超时 int BFS(int a,int b,int c) { //只要队列不空,说明仍有状态需要遍历 while(!queueStat.empty()) { //弹出当前状态,进行状态迁移 Stat stat = queueStat.front(); queueStat.pop();
//遍历6种状态,扩展得到新的元素 int x,y,z; for(int i = 0 ; i < 6 ; i++) { x = stat.x + goNext[i][0]; y = stat.y + goNext[i][1]; z = stat.z + goNext[i][2];
//对每个新元素,判断其如果满足约束条件并且是未遍历过 // 判断是否仍在围墙中 //易错,这里城堡的位置是不能为a,因为这是数组,这样做就超过了 //if(x < 0 || x > a || y < 0 || y > b || z < 0 || z > c) if(x <0 || x >= a || y < 0 || y >=b || z < 0 || z >= c ) { continue; }
//如果已经遍历过,则跳过 if(true==mark[x][y][z]) { continue; } //如果下一个是墙,则跳过 if(1==maze[x][y][z]) { continue; }
//更新新元素的状态,并将新元素塞入队列,设置新元素为已经访问过 Stat statTemp; statTemp.x = x; statTemp.y = y; statTemp.z = z; statTemp.t = stat.t + 1;//所耗时间进行累加
//易错,更新剪枝状态 mark[x][y][z] = true;
//将新状态放入队列 queueStat.push(statTemp);
//如果新元素是所求解,则直接返回 //判断是否已经到达终点,则返回其所耗时间 if(a-1==x && b-1==y && c-1==z) { return statTemp.t; } }//for }//while return -1;//如果一直没有找到,返回-1 } |
深度优先搜索(DFS)
Temple of the bone
有一个N*M的迷宫,起点S,终点D,墙X和地面,'.'表示路,0秒时,我从S出发,
每秒能走到4个与其相邻位置的任意一个,行走之后不能再次走入。
问:是否存在一条路径使主人公刚好在T秒走到D
代码
typedef struct Stat { int x,y;//横纵坐标 int t;//耗费时间 }Stat;
char maze[N][N];//保存迷宫元素 bool success;//设置是否找到的成功标记
//走下一个位置的数组 int goNext[][2] = {0,1, -1,0, 1,0, 0,-1 };
//深度优先搜索 void DFS(int x,int y,int t,int n,int m,int tLimit) { int i ; // 扩展得到新元素 for(i = 0 ; i < 4 ; i ++) { int iXNext = x + goNext[i][0]; int iYNext = y + goNext[i][1];
// 如果新元素不符合约束条件,则过滤该新元素 //判定有无超过迷宫位置 if(iXNext < 1 || iXNext > n || iYNext < 1 || iYNext > m) { continue; } //判定是否是墙 if('X'==maze[iXNext][iYNext]) { continue; } // 基于当前状态更新得到新元素状态,判断新元素状态是否等于所求状态,如果是,设置结果标记为成功并直接返回 //判定是否到达终点,并且时间要符合 if('D'==maze[iXNext][iYNext] && tLimit==(t + 1)) { //易错,需要设置成功标记 success = true; return; }
//设置新元素为已经访问,递归处理,设置新元素为未访问(因为后续状态全部遍历完成,需要退回上层状态),如果结果标记为成功,则停止搜索 maze[iXNext][iYNext] = 'X'; //递归调用 DFS(iXNext,iYNext,t+1,n,m,tLimit); //若其后续状态全部遍历完毕,返回上层状态,因为要搜索后续状态,因此再将墙改为普通状态 maze[iXNext][iYNext] = '.';
//易错,判断是否搜索成功 if(true==success) { return; } }//for //如果一直遍历不到,则返回-1 return; } |
2 搜索系列
类别-编号 |
题目 |
遁去的1 |
61 |
如何在不能使用库函数的条件下计算n的平方根 给定一个数n,求出它的平方根,比如16的平方根是4.要求不能使用库函数。 |
Python程序员面试算法宝典 https://blog.csdn.net/qingyuanluofeng/article/details/96723712 关键: 1 二分查找尤其需要注意一种情况 while low < high: low = high - 1 此时会造成无限循环, 当low = high - 1 需要跳出循环再计算 2 书上解法 通过近似值获取。第一个近似值为1,接下来的近似值通过 Ai+1 = (Ai + n/Ai)/2 为什么? 3 没有想到 没有想到公式: Ai+1 = (Ai + n/Ai)/2
代码: # 其中e是精度 def squareRoot(n, e): newOne = n lastOne = 1.0 # 第一个近似值 while newOne - lastOne > e: newOne = (newOne + lastOne) / 2 lastOne = n / newOne return newOne
def process(): # for i in range(0, 27): # result = rootOfN(i) # print results e = 0.000001 for i in range(0, 27): result = squareRoot(i, e) print result |
62 |
黑白图像 输入一个n*n的黑白图像(1表示黑色,0表示白色),任务是统计其中八连块的个数。如果两个黑盒子有公共边或者公共顶点,就说它们属于同一个八连块。 如图所示,有3个八连块 输入: 6 100100 001010 000000 110000 111000 010100 输出: 3 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47730863 #define MAXSIZE 50 int iPic[MAXSIZE][MAXSIZE]; int iMark[MAXSIZE][MAXSIZE]; int go[][2] = { {-1,1}, {0,1}, {1,1}, {-1,0}, {1,0}, {-1.-1}, {0,-1}, {1,-1} };
void dfs(int x,int y,int n) { int iNextX,iNextY; for(int i = 0; i < 8;i++)//八个方向 { iNextX = x + go[i][0]; iNextY = y + go[i][1]; if(iNextX < 1 || iNextX > n || iNextY < 1 || iNextY > n)//越界 { continue; } if(iMark[iNextX][iNextY])//已经访问过 { continue; } if(!iPic[x][y])//如果是白色 { continue; } //先置当前节点为已经访问过 iMark[iNextX][iNextY] = 1; dfs(iNextX,iNextY,n); //如果达到要求
//iMark[iNextX][iNextY] = 0;//由于是深度访问,还要将已访问还原。不需要还原,因为这个问题不是那种不成功需要回溯的题目 } }
void process() { int n; scanf("%d",&n); memset(iMark,0,sizeof(iMark));//初始化未被访问 for(int i = 1; i <= n;i++) { for(int j =1 ; j <= n;j++) { scanf("%d",&iPic[i][j]); } } int iCount = 0; for(int k = 1; k <= n; k++) { for(int p = 1; p <= n; p++) { if(!iMark[k][p] && iPic[k][p])//如果未访问和是黑色就开始访问 { dfs(k,p,n); iCount++; } } } printf("%d\n",iCount); } |
63 |
迷宫 表示往上、下、左、右移动到相邻单元格。任何时候都不能在障碍格中,也不能走到迷宫之外。起点和终点保证是空地。n,m<=100。 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47730893 关键: 1 在迷宫问题中不管使用bfs还是dfs算法,都不需要再递归后,将剪枝状态进行回溯,因为一旦遇到墙,说明这条路是不通的,因此标记这个路是已经访问过,后序 不需要再访问,而对于回溯问题,由于若采用当前状态不成功的话,要回溯到上一个状态,而当前状态后续中还有可能被用到,因此需要改变状态 2 bfs一定是最优问题,所以不要考虑是不是最优解,输出的一定是最优解,因为无效解已经被排除了 3 if(iNextX == iEx && iNextY == iEy)//如果抵达终点,直接返回,放在最后判断,不影响递归 { return newStat.time; }
代码: #define MAXSIZE 50 using namespace std; typedef struct Stat { Stat(int _x,int _y,int _time):x(_x),y(_y),time(_time){} int x,y; int time; }Stat;
int go[][2] = { {0,1}, {1,0}, {0,-1}, {-1,0} };
queue
int iMap[MAXSIZE][MAXSIZE]; int iMark[MAXSIZE][MAXSIZE]; int iBx,iBy; int iEx,iEy; int N,M; bool isFind = false; int bfs()//返回所耗时间 { while(!queueStat.empty()) { Stat stat = queueStat.front(); queueStat.pop(); for(int i = 0 ; i < 4 ; i++) { int iNextX = stat.x + go[i][0]; int iNextY = stat.y + go[i][1]; if(iNextX < 0 || iNextX >= N || iNextY < 0 || iNextY >= M)//越界 { continue; } if(iMark[iNextX][iNextY])//已经被访问过 { continue; } if(!iMap[iNextX][iNextY])//如果是墙,1表示路,0表示墙,最后的终点必须是路为1 { continue; } Stat newStat(iNextX,iNextY,stat.time+1); iMark[iNextX][iNextY] = 1;//置已经访问标记 queueStat.push(newStat); if(iNextX == iEx && iNextY == iEy)//如果抵达终点,直接返回,放在最后判断,不影响递归 { return newStat.time; } } } return -1;//如果一直没有找到 } |
64 |
迷宫路径 表示往上、下、左、右移动到相邻单元格。任何时候都不能在障碍格中,也不能走到迷宫之外。起点和终点保证是空地。n,m<=100。 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47730937 关键: 1 要设置父节点和从父节点到当前节点的走向,两个数组
代码:
#define MAXSIZE 50 int queue[MAXSIZE*MAXSIZE]; //int father[MAXSIZE*MAXSIZE]; int father[MAXSIZE][MAXSIZE]; int dist[MAXSIZE][MAXSIZE]; int last_dir[MAXSIZE][MAXSIZE];//记录从父节点到子节点的移动序列 int dir[MAXSIZE*MAXSIZE];//方向序列 char name[MAXSIZE] = "RDLU";//方向解释 int iMark[MAXSIZE][MAXSIZE]; int iMaze[MAXSIZE][MAXSIZE]; int go[][2] = { {1,0},//0表示右 {0,-1},//1表示往下 {-1,0},//2表示往左 {0,1}//3表示往上 }; int m;//列数 int n;//行数 int iEx,iEy;
void bfs(int x,int y) { memset(iMark,0,sizeof(iMark)); int front,rear,pos; front = rear = 0; pos = x*m + y; //置已经访问标记和父节点为本身,这个是后面递归的终止条件,距离为0表示,同时将队列中的首元素进行设置 iMark[x][y] = 1; father[x][y] = pos; dist[x][y] = 0; queue[rear++] = pos;//这个元素,这里其实起到的作用是stat,因为这里无法保存状态,不过写得很搓,程序可读性差 while(front < rear) { pos = queue[front++]; x = pos/m; y = pos%m; for(int i = 0 ; i < 4 ;i++) { int iNextX = x + go[i][0]; int iNextY = y + go[i][1]; if(iNextX < n && iNextX >= 0 && iNextY < m && iNextY >= 0 && !iMark[iNextX][iNextY] && iMaze[iNextX][iNextY])//这里直接用成立的条件,进行操作,是改进 { father[iNextX][iNextY] = pos; pos = iNextX*m + iNextY; queue[rear++] = pos; dist[iNextX][iNextY] = dist[x][y] + 1; last_dir[iNextX][iNextY] = i; if(iNextX == iEx && iNextY == iEy)//找到重点 { return; } } } } }
void printPath(int x,int y)//这里传入的x和y必须是最后一次走的节点,然后逆向推出其父节点 { int c = 0; for( ; ; ) { int fx = father[x][y]/m; int fy = father[x][y]%m; if(x == fx && y == fy) { break;//如果没有父节点就退出,是退出条件 } dir[c++] = last_dir[x][y]; x = fx; y = fy; } while(c--) { printf("%c ",name[dir[c]]); } } |
65 |
除法输入正整数,按从小到大的顺序输出所有形如abcde/fghij=n的表达式,其中a~j恰好为数字0~9的一个排列,2<=n<=79. 输入: 62 输出: 79546/01283=62 94736/01528=62 |
算法竞赛入门经典https://blog.csdn.net/qingyuanluofeng/article/details/47731045 关键: 1 暴力解决:罗列所有可能性,一一实验 2 枚举原则:枚举简单的东西:整数,子串 3 应该枚举除数,5个数字,那么被除数算出来之后,看是否有重复的即可,不要傻不拉几的枚举10!种
代码: void division(int n) { if( n <2 || n >79) { printf("您输入的n不符合要求(2<=n<=79),请重新输入!\n"); return; } for(int i = 98765/79; i <= 98765/2; i++)//因为被枚举的数最多不会超过最大数的一半 { int iMark[10] = {0};//用于标记是否有重复元素 //将数i进行分解 //这里要加判断,如果被除数小于10000,默认iMark[0] = 1; if(i < 10000) { iMark[0] = 1; } int k = i; int iProduct; bool isOk = true; do{ int temp = k % 10;//分解个位 if(iMark[temp] == 1) { isOk = false; break; } else { iMark[temp] = 1; } k /= 10; }while(k); if(isOk == false) { continue; } else { iProduct = n * i; int p = iProduct; do{ int temp = p % 10; if(1 == iMark[temp]) { isOk = false; break; } else { iMark[temp] = 1; } p /= 10; }while(p); } if(isOk) { //这里要对除数若小于10000,要前置添加0 if(i < 10000) { printf("%d/0%d = %d\n",iProduct,i,n); } else { printf("%d/%d = %d\n",iProduct,i,n); } } } } |
66 |
最大乘积 输入n个元素组成的序列S,你需要找出一个乘积最大的连续子序列。如果这个最大的成绩不是整数,应输出-1(表示无解)。1<=n<=18,-10<=Si<=10。 输入: 3 2 4 -3 5 2 5 -1 2 -1 输出: 8 20 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47731081 关键: 1 枚举起点和终点,注意大数要用long long表示 long long iMax = iArr[0]*iArr[1]; 2 求最大子段和 max[i] = {max[i-1]*iArr[i],max[i-1] > 0 {iArr[i],max[i-1] <= 0,这个只能用于加法,乘法似乎不好用 for(int i = 0;i < ;i++) { if(b > 0) { b += a[i]; } else { b = a[i] } if(b > sum) { sum = b; } }
代码: #define MAXSIZE 1024
void maxProduct() { int n; while(EOF != scanf("%d",&n)) { int iArr[MAXSIZE]; int i; for(i = 0;i < n;i++) { scanf("%d",&iArr[i]); } long long iMax = iArr[0]*iArr[1]; for(int iBegin = 0 ; iBegin { for(int iEnd = iBegin+1;iEnd { //int iProduct = 1; long long iProduct = 1; for(int j = iBegin ; j <= iEnd;j++) { iProduct *= iArr[j]; } if(iProduct > iMax) { iMax = iProduct; } } } if(iMax < 0) { printf("%d\n",-1); } else { printf("%d\n",iMax); } } } |
67 |
分数拆分 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47731123 关键: 1 根据小范围枚举和减法确定另一个数的枚举范围。因为x >= y,有1/x <= 1/y,所以1/k - 1/y <= 1/y解方程组,所以k < y <= 2*k,根据这个,然后将x枚举出来 2 关键是如何求出x,直接减肯定不行1/x = (y-k)/(y*k),所以x = (y*k)/(y-k) 3 float x = 1.0*(y*n)/(y-n);//注意凡是浮点数相乘,不要忘记乘以1.0 4 if(x == temp)//需要判断x是否为整数,先float x一下,再int一下,看是否相等。
代码: void fractionDivision(int n) { int iCount = 0; char str[MAXSIZE][50]; for(int y = n + 1;y <= 2*n;y++) { float x = 1.0*(y*n)/(y-n);//注意凡是浮点数相乘,不要忘记乘以1.0 int temp = (int)x; if(x == temp)//需要判断x是否为整数,先float x一下,再int一下,看是否相等。 { sprintf(str[iCount++],"1/%d = 1/%d + 1/%d",n,temp,y); } } printf("%d\n",iCount); for(int i = 0; i < iCount;i++) { puts(str[i]); } } |
68 |
双基回文数 如果一个正整数n至少在两个不同的进位制b1和b2下都是回文数(2<=b1,b2<=10),则称n是双基回文数(注意,回文数不能包含前导0)。输入正整数S<10^6,输出比 S大的最小双基回文数> 输入:1600000(1632994) 输出:1632995 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47731141 思路: 1对比S大每个数进行2到10进制的罗列,这8种中但凡有两种进制回文数相同,就输出 2判断回文数做成一个函数,将一个数先分解(从个位到最高位)存放在一个数组,对这个数组的前一半与后一半进行比较,相同,则认为是回文数 关键: 1 int iMark[11] = {0};//默认标记数组iMark[i]=1表示i进制是回文数,这里进制最大为10,因此iMark必须为11 2 for(p = 0,q = j-1; p < j/2;)//注意这里的j是数组的最后一位,q必须从j-1开始
代码: bool isEchoNum(int n,int *iMark) { for(int b = 2 ; b <= 10; b++) { int iArr[MAXSIZE]; int k = n; int j = 0; //分解这个数 do{ iArr[j++] = k % b; k /= b; }while(k); //判断回文 int p,q; bool isEcho = true; for(p = 0,q = j-1; p < j/2;)//注意这里的j是数组的最后一位,q必须从j-1开始 //for(p = 0,q = j;p <= (j-1)/2;) { if(iArr[p++] != iArr[q--]) { isEcho = false; } } if(isEcho == true) { iMark[b] = 1; } } //判断是不是达到至少两次进制回文 int iCount = 0; for(int m = 2; m <= 10; m++) { if(iMark[m]) { iCount++; } } if(iCount >= 2) { return true; } else { return false; } }
void doubleBaseEchoNum(int n) { if(n >= 1e7) { return; } for(int i = n + 1; ; i++) { int iMark[11] = {0};//默认标记数组iMark[i]=1表示i进制是回文数,这里进制最大为10,因此iMark必须为11 memset(iMark,0,sizeof(iMark)); if(isEchoNum(i,iMark)) { printf("%d\n",i); break; } } } |
69 |
倒水问题 有装满水的6升杯子、空的3升杯子和一升杯子,3个杯子中都没有刻度。在不使用其他道具的情况下,是否可以量出4升的水呢? 输入: 6(满杯水所在的刻度) 3 1 输出: (6,0,0)->(3,3,0)->(3,2,1)->(4,2,0) |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47746957 思路: 这与倒可乐是一个问题,关键在与状态的搜索。 1 采用广度优先搜索算法 2 当前状态的下一状态的方法为:(a,b,c) 状态:全部分
关键: 1 小杯子倒向大杯子,只能全部倒入。大杯子倒向小杯子,把大杯子加满。 2 需要使用倾倒函数。量出4升水的方法是:3升杯装中装2升,1升杯0升,6升杯中为4升。 3 如何打印状态,需要在状态中设置访问标记。设置一个父节点指针,到时候逆向打印即可 4 迷宫是无向图,倒水是有向状态图
typedef struct Stat { Stat(int a,int b,int c,int d,Stat* s):x(a),y(b),z(c),t(d),par(s){} int x;//6升杯中容量 int y;//3升杯中容量 int z;//1升杯中容量 int t;//倾倒次数 Stat* par; //int v;//访问标记,1表示访问 }Stat;
queue queue Stat endStat(0,0,0,0,NULL); //int iCapA,iCapB,iCapC;//3个容器的容量
void dump(int iConA,int& iVa,int iConB,int& iVb)//A容器中的水倒向B容器中的水 { if(iVa > iConA || iVb > iConB || iVa < 0 || iVb <0)//合法性检验 { return; } if(iVa + iVb < iConB)//A容器小于B容器,应该加满B { iVb += iVa; iVa = 0; } else { iVa -= iConB - iVb; iVb = iConB; } }
int dfs(int iCapA,int iCapB,int iCapC)//当前3个杯子中水的容量 { int x,y,z,t; while(!queueStat.empty()) { Stat stat = queueStat.front(); queueStat.pop();
x = stat.x; y = stat.y; z = stat.z; t = stat.t; //a向b倾倒 dump(iCapA,x,iCapB,y); if(x < 0 || x > iCapA || y < 0 || y > iCapB || z < 0 || z > iCapC ) { continue; } Stat newStat(x,y,z,t+1,&stat);//如果是这样的话,应该保存的是new出来的节点,而且应该保存最后一个状态 queueStat.push(newStat); printStat.push(newStat); if(x == 4 && y == 2 && z == 0)//如果找到,就直接退出 { endStat = newStat; return(t+1); } else//没找到,生成新状态 { //Stat newStat(x,y,z,t+1); //queueStat.push(newStat); //printStat.push(newStat); }
//a向c倾倒 x = stat.x; y = stat.y; z = stat.z; t = stat.t; dump(iCapA,x,iCapC,z); if(x < 0 || x > iCapA || y < 0 || y > iCapB || z < 0 || z > iCapC ) { continue; } Stat newStat2(x,y,z,t+1,&stat); queueStat.push(newStat2); printStat.push(newStat2); if(x == 4 && y == 2 && z == 0)//如果找到,就直接退出 { endStat = newStat2; return(t+1); }
x = stat.x; y = stat.y; z = stat.z; t = stat.t; //b向a倾倒 dump(iCapB,y,iCapA,x); if(x < 0 || x > iCapA || y < 0 || y > iCapB || z < 0 || z > iCapC ) { continue; } Stat newStat3(x,y,z,t+1,&stat); queueStat.push(newStat3); printStat.push(newStat3); if(x == 4 && y == 2 && z == 0)//如果找到,就直接退出 { endStat = newStat3; return(t+1); }
x = stat.x; y = stat.y; z = stat.z; t = stat.t; //b向c倾倒 dump(iCapB,y,iCapC,z); if(x < 0 || x > iCapA || y < 0 || y > iCapB || z < 0 || z > iCapC ) { continue; } Stat newStat4(x,y,z,t+1,&stat); queueStat.push(newStat4); printStat.push(newStat4); if(x == 4 && y == 2 && z == 0)//如果找到,就直接退出 { endStat = newStat4; return(t+1); }
//c向a倾倒 x = stat.x; y = stat.y; z = stat.z; t = stat.t; dump(iCapC,z,iCapA,x); if(x < 0 || x > iCapA || y < 0 || y > iCapB || z < 0 || z > iCapC ) { continue; } Stat newStat5(x,y,z,t+1,&stat); queueStat.push(newStat5); printStat.push(newStat5); if(x == 4 && y == 2 && z == 0)//如果找到,就直接退出 { endStat = newStat5; return(t+1); }
//c向b倾倒 x = stat.x; y = stat.y; z = stat.z; t = stat.t; dump(iCapC,z,iCapB,y); if(x < 0 || x > iCapA || y < 0 || y > iCapB || z < 0 || z > iCapC ) { continue; } Stat newStat6(x,y,z,t+1,&stat); queueStat.push(newStat6); printStat.push(newStat6); if(x == 4 && y == 2 && z == 0)//如果找到,就直接退出 { endStat = newStat6; return(t+1); } } return -1; } |
70 |
八数码问题 编号为1~8的8个正方形滑块被摆成3行3列(有一个格子空留),如图所示。每次可以把与空格相邻的滑块(有公共边才算相邻)移到空格中,而它原来的位置就称为了 新的空格。给定初始局面和目标局面(用0表示空格),你的任务是计算出最少的移动步数。如果无法达到目标局面,则输-1. 2 6 4 8 1 5 1 3 7 7 3 6 5 8 4 2 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47746983 |
71 |
八数码问题之哈希去重 输入: 2 6 4 1 3 7 0 5 8 8 1 5 7 3 6 4 0 2 输出: 31 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47746995 |
72 |
八数码问题之stl 1set 问题:并不是所有类型的State都可以作为set中的元素类型。set的元素必须定义"<"运算符,C语言原生的数组(包括字符数组)却不行。 2如果数组不能转化为整数,自己声明结构体,重载函数调用符比较状态。下面中,整数a和b分别是两个状态在状态数组st中的下标,在比较时直接使用memcpy来比较整个 内存块 输入: 2 6 4 1 3 7 0 5 8 8 1 5 7 3 6 4 0 2 输出: 31 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47747019 |
73 |
二分查找 本质:有序表中使用二分查找,log2(1000) 深入: 如果数组中多个元素都是v,上面的函数返回的是中间的一个。能不能求出值等于v的完整区间呢? 下面的程序当v存在时返回它出现的第一个位置。如果不存在,返回这样一个下标i:在此处插入v(原来的元素A[i],A[i+1],..全部往后移动一个位置)后序列仍然有序 思路 排序后: 0 1 3 4 6 7 9 9 输入: 8 1 9 6 3 4 7 9 0 3 8 1 9 6 3 4 7 9 0 5 输出: 2 -1 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47747225 |
74 |
二分查找之lowerBound 注意:对于二分查找的一个系列,high都是用数组长度来计算,真正是取不到的 如果数组中多个元素都是v,上面的函数返回的是中间的一个。能不能求出值等于v的完整区间呢? 下面的程序当v存在时返回它出现的第一个位置。如果不存在,返回这样一个下标i:在此处插入v(原来的元素A[i],A[i+1],..全部往后移动一个位置)后序列仍然有序 输入: 8 0 1 3 4 6 7 9 9 5
8 0 1 4 4 4 4 9 9 4 输出: 4 2 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47747247 重复 int lowerBound(int* iArr,int low,int high,int iVal)// { //while(low <= high) while(low < high)//=不能取,陷入死循环,low = high = mid { int mid = low + (high - low) / 2; if(iArr[mid] < iVal)//在右区间 { low = mid + 1; } else if(iArr[mid] > iVal)//左区间 { high = mid; } else { return mid; } } return low;//如果改成low,就是iVal应该在的位置 } |
75 |
二分查找之upperBound
往后移动一个位置)后序列仍然有序。
输入: 8 0 1 3 4 6 7 9 9 5
8 0 1 3 4 4 4 9 9 4
8 0 3 3 4 4 4 9 9 3 输出: 4 6 3 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47747257
int upperBound(int* iArr,int low,int high,int iVal) { while(low < high) { int mid = low + (high - low)/2; if(iArr[mid] <= iVal) { low = mid + 1; } else { high = mid; } } return high; } |
76 |
二分查找之范围统计 给出n个整数xi和m个询问,对于每个询问(a,b),输出闭区间[a,b]内的整数xi的个数。 输入: 8 0 1 3 4 6 7 9 9 3 9 输出: 6 |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47747273 关键: 1 大于等于a的第一个元素的下标是L。如果所有元素小于a,则L=n 小于等于b的最后一个元素的下一个元素的下标是什么,如果所有元素都大于b,则相当R=0,相当于A[0]前面还有一个A[-1],A[-1]下一个位置为0 答案即为[R-L]的长度,即R-L 2 iterator upper_bound(const key_type& key)返回第一个指向>key的迭代器 3 iterator lower_bound(const key_type& key)返回第一个指向>=key的迭代器 4 return upper_bound(iArr,iArr+n,b) - lower_bound(iArr,iArr+n,a);调用方法是数组首地址和数组元素个数,已经标记值
int lowerBound(int* iArr,int low,int high,int iVal) { while(low < high) { int mid = low + (high - low)/2; if(iArr[mid] >= iVal )//在左半区间时,不断向前 { high = mid; } else { low = mid + 1; } } return low; }
int upperBound(int* iArr,int low,int high,int iVal) { while(low < high) { int mid = low + (high - low)/2; if(iArr[mid] <= iVal) { low = mid + 1; } else { high = mid; } } return low; }
int rangeStat_stl(int* iArr,int n,int a,int b) { return upper_bound(iArr,iArr+n,b) - lower_bound(iArr,iArr+n,a); }
int ranggeStat_my(int* iArr,int n,int a,int b) { return upperBound(iArr,0,n,b) - lowerBound(iArr,0,n,a); } |
77 |
非线性方程求根 一次向银行借a元钱,分b月还清。如果需要每月还c元,月利率是多少(按复利率计算)?例如借2000元,分4个月每月还510,则月利率为0.797%。答案应不超过100%。 2000 4 510 输出: 0.797% |
算法竞赛入门经典 https://blog.csdn.net/qingyuanluofeng/article/details/47775495 关键: 1 使用猜数字方法,对于百分数,我们把%100提取出来,分子为[0,100]之间的数字,本质上是二分法 2 while(high - low > 1e-5)//利用high-low与精度值的比较来确定循环退出条件 3 本题上下限的更新是根据f(x)的单调性确定,本题单调增,因此,sum>0表明mid选大了, if(sum > 0)//说明mid选大了,在小区间 { high = mid; } 4 更新复利的计算表达式为a(1+x)-c,即sum += sum*mid/100.0 - c;//更新剩余所需要还的钱,是加上本月的利息sum*mid/100.0,再减去本月还的钱
//采用[0,100]区间缩小的方法 void nonlinearEquation(float a,int b,float c) { float low = 0,high = 100; while(high - low > 1e-5)//利用high-low与精度值的比较来确定循环退出条件 { float mid = low + (high - low)/2; float sum = a; for(int i = 0 ; i < b ; i++) { sum += sum*mid/100.0 - c;//更新剩余所需要还的钱,是加上本月的利息sum*mid/100.0,再减去本月还的钱 } if(sum > 0)//说明mid选大了,在小区间 { high = mid; } else { low = mid; } } printf("%.3lf%%\n",low);//要打印出“%”必须连续写两个%% //printf("%%\n"); //printf("%\n"); } |
78 |
统计书中的单词及出现次数,实现一个数据结构进行存储 |
编程珠玑 https://blog.csdn.net/qingyuanluofeng/article/details/54647029 int getPrimeNumber(int num) { if(num <= 0) { return -1; } int* primeArr = new int[num + 1];//用于判定是否为素数的数组,初始化为0,表示都是质数 // sizeof(指针)都是4,strlen只是用来计算字符串长度,整形指针不行 int* visitArr = new int[num + 1];//初始化为0,表示都没有访问过 memset(primeArr , 0 , sizeof(primeArr) * (num + 1)); memset(visitArr , 0 , sizeof(visitArr) * (num + 1)); int k; for(int i = 2 ; i <= num ; i++ ) { k = i;//k是倍数,从i开始算,不要从2开始, i*i与2*i的比较,起始2*i已经在第一轮计算过了 while(k * i <= num) { //如果没有访问过 if(0 == visitArr[i]) { visitArr[k * i] = 1;//是k*i不是i primeArr[k * i] = 1;//表示是合数 } //如果访问过,就不再处理 k++; } } int i; //到这里,已经通过素数筛选法,获得了所有质数,凡是primeArr[i]为0都是质数,我们只需要从num向前搜索最接近的质数即可 for(i = num - 1; i >= 2 ; i--) { if(0 == primeArr[i]) { break; } } delete[] primeArr; delete[] visitArr; return i; }
/* 接下来是要建立散列表,散列表的长度,散列中的乘数为31 散列值计算公式:设一个字符串val共有n个字符,则计算的哈希值为 h = 31 ^ (n-1) * val[0] + 31 ^ (n-2) * val[1] + 31 ^ (n-3) * val[2] + ...+ val[n-1] 选用31作为乘数的原因是: 1】对于任意数i, 31 * i = (i << 5) - i,可以用移位和减法代替乘法,可以优化 2】31是质数,只能被1和自身整除,既要保证31乘以字符串不能溢出,又要保证哈希地址较大,来减少冲突, 综合来说:31是个不错的乘数 */ const int MULT = 31; typedef struct Node { Node():_next(NULL),_word(""),_count(0),_isNULL(true){} void setNULL(bool flag) { _isNULL = flag; } Node* _next;//指向下一个结点 //char* _word;//字符串,用字符指针会出现乱码 string _word; int _count;//该字符串出现次数 bool _isNULL;//初始化建立结点的时候设置结点默认为空,以后每次实例化的结点都必须设置该空为false }Node;
//对字符串进行哈希,返回在哈希表中的下标 int getHash(char* str , int primeNum) { unsigned int hashValue = 0; if(NULL == str || primeNum < 2) { return hashValue; } for( ; *str ; str++) { char ch = *str; hashValue = MULT * hashValue + ch; } return (hashValue % primeNum); }
void countWords(Node* hashTable, int primeNum , vector { if(NULL == hashTable || primeNum < 2 || words.empty()) { return; } //对每个单词生成链表 int size = words.size(); string sWord; char* word; int hashIndex; Node* node; for(int i = 0 ; i < size ; i++) { sWord = words.at(i); word = (char*)sWord.c_str(); hashIndex = getHash(word , primeNum); //如果哈希表中对应位置的结点为空,则需要新建结点 if(hashTable[hashIndex]._isNULL) { Node* newNode = new Node(); newNode->_word = sWord; newNode->_isNULL = false; newNode->_count = 1;//设置计数器为1 hashTable[hashIndex] = *newNode; } else { //判断对应结点如果不为空,则找到该单词(),并累加计数 bool isFind = false; Node* previouNode = NULL; for(node = &hashTable[hashIndex] ; node != NULL ; ) { //如果找到该单词 if( node->_word == sWord) { node->_count++; isFind = true; break; } previouNode = node; node = node->_next; } //如果一直找不到,说明对应同一个哈希下标,出现了不同的字符串,需要将当前结点加入到首部(发现死循环),插入到尾部 if(!isFind) { Node* newNode = new Node(); newNode->_word = sWord; newNode->_isNULL = false; newNode->_count = 1;//设置计数器为1 previouNode->_next = newNode; //newNode->_next = &hashTable[hashIndex];//插入到首部需要获取地址,似乎陷入了死循环 //hashTable[hashIndex] = *newNode; } } } } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
参考:
[1]计算机考研--机试指南,王道论坛 组编
[2]剑指offer
[3]算法设计与分析
[4]编程之美
[5]程序员面试金典
[6]leecode
[7]Python程序员面试算法宝典
[8]刘汝佳算法竞赛入门经典
[9]算法导论
[10]编程珠玑