算法基础-->图论(BFS,DFS)
来源:互联网 发布:前端怎么获取后端数据 编辑:程序博客网 时间:2024/06/05 17:16
本篇博文将总结和图论里面很重要的BFS,DFS算法,关于最小生成树,最短路径等一些其他图相关算法在贪心和动态规划部分详细总结。
图的表示:
邻接矩阵
n∗n 的矩阵,有边是1 ,无边是0 ,n 表示结点个数。邻接表
为每个点建立一个链表(数组)存放与之连接的点。
搜索:
BFS(Breadth−First−Search) 广(宽)度优先DFS(Depth−First−Search) 深度优先
广度优先搜索
最简单、直接的图搜索算法
从起点开始层层扩展
第一层是离起点距离为1的
第二层是离起点距离为2的
…..本质就是按层(距离) 扩展,无回退
BFS分析
给定某起点
过程:假定某时刻缓冲区内结点为
- 辅助数据结构:队列
- 先进先出
- 从队尾入队,从队首出队
- 只有队首元素可见
结点判重:
如果在扩展中发现某结点在前期已经访问过,则本次不再访问该结点;显然,第一次访问到该结点时,是访问次数最少的:最少、最短;
路径记录:
- 一个结点可能扩展出多个结点:多后继
a1,a2,ax , - 但是任意一个结点最多只可能有1个前驱(起始结点没有前驱):单前驱
- 用结点数目等长的数组
pre[0…N−1]:pre[i]=j 第i 个结点的前一个结点是j
注:再次用到“存索引,不存数据本身”的思路。
BFS算法框架
辅助数据结构
- 队列
q ; - 结点是第几次被访问到的
d[0…N−1] :简称步数; - 结点的前驱
pre[0…N−1] ;
算法描述
起点
start 入队q
记录步数d[start]=0 ;
记录start 的前驱pre[start]=−1 ;如果队列
q 非空,则队首结点x 出队,尝试扩展x (也就是找出所有与结点x 直接相连的结点),找到x 的邻接点集合y|(x,y)∈E 进行遍历。
对每个新扩展结点y 判重(查看是否已经访问过),并且检查是否已经到达终点。如果y 是新结点并且不是终点,则入队q ;
同时,记录步数d[y]=d[x]+1 ;前驱pre[y]=x ;我们以下图为例,在一个图中
a 是起点,b 是终点,这里假设为四邻域,也即是图中任意点可沿着上下左右四个方向向外扩展(前提没有越界),并且扩展的顺序为顺时针(也即是上右下左),求起点到终点的最短路径,我们从a 开始不断的向外层按顺时针做四邻域扩展,每一层的扩展都是按顺时针方向,那么个时候就应该用队列来存储(先进先出) 上一层访问的结点(上一次先访问上方向结点,那么下次出队列时,也是首先访问下一层的上方向结点),每扩展一层step+1 (同一层的结点step 相等),因此这样到达终点的step肯定是最短的。
BFS算法的思考
隐式图:实践中,往往乍看不是关于图的问题,但如果是给定一个起始状态和一些规则,求解决方案的问题:往往可以根据这些规则,将各个状态(动态的)建立连接边,然后使用
树的层序遍历,是按照从根到结点的距离遍历,可以看做是图的
树的先序/后序/中序遍历,是从根搜索到树的叶子结点,然后回溯,可以看做是图的
对BFS的改进——双向BFS:
- 从起点和终点分别走,直到相遇;
- 将树形搜索结构变成纺锤形;
- 经典
BFS 中,树形搜索结构若树的高度非常高时,叶子非常多(树的宽度大),而把一棵高度为h 的树,用两个近似为h/2 的树代替,宽度相对较小。
单词变换问题Word ladder
问题描述
给定字典和一个起点单词、一个终点单词,每次只能变换一个字母,问从起点单词是否可以到达终点单词?最短多少步?
如:
- start= “hit”
- end = “cog”
- dict = [“hot”,”dot”,”dog”,”lot”,”log”]
- “hit” -> “hot” -> “dot” -> “dog” -> “cog”
起点单词和终点单词不在字典里面。
问题分析
使用邻接表,建立单词间的联系
单词为图的结点,若能够变换,则两个单词存在无向边;
这里这样定义:若单词A和B只有1个字母不同,则(A-B)存在边;建图:
⑴ 预处理:对字典中的所有单词建立map、hash或者trie结构,利于后续的查找。
⑵ 对于某单词w ,单词中的第i 位记为β ,则将β 替换 为[β+1,′Z′] ,查找新的串nw 是否在字典中。如果在,将(w−nw) 添加到邻接表项w 和nw 中(无向边)。
⑶ 循环处理第二步;
⑷ 若使用map,串在字典中的查找认为是O(logN) 的,那么,整体时间复杂度为O(N∗len∗13∗logN) ,即O(N∗logN) 。若使用hash 或者trie ,整体复杂度为O(N) 。从起始单词开始,广度优先搜索,看能否到达终点单词。若可以到达,则这条路径上的变化是最快的。
是否需要事先计算图本身?图可以记录路径
代码实现:
#include<stdio.h>#include <stdlib.h>#include<iostream>#include<vector>#include<set>#include<queue>using namespace std;//搜索cur的孩子结点,存入children中void Extends(const string& cur, vector<string>& children, const set<string>& dict, const string& end, set<string>& visit){ string child = cur; children.clear(); int i; char c, t; for (i = 0; i < (int)cur.size(); i++) { t = child[i]; for (c = 'a'; c != 'z'; c++)//每次循环都选择其中一个单词进行替换 { if (c == t) continue; child[i] = c; if (((child == end) || (dict.find(child) != dict.end())) && (visit.find(child) == visit.end())) //child同时满足以下两个条件才会压入children //一:child等于终点单词或者字典中含有child //二:没有访问过该单词child { children.push_back(child); visit.insert(child); } } child[i] = t;//单词恢复到原始状态 }}int CalcLadderLength(const string& start, const string& end, const set<string>& dict){ //以下是广度优先遍历的典型步骤 queue<string> q;//队列先进先出,队头进队尾出 q.push(start); vector<string> children;//从当前结点可以扩展得到的新结点集合 set<string> visit; int step = 0; string cur; while (!q.empty()) { cur = q.front();//获取队尾元素 q.pop();//队尾元素出队 Extends(cur, children, dict, end, visit);//获取与cur结点直接相连的结点,存入children for (vector<string>::const_iterator it = children.begin(); it != children.end(); it++)//遍历所有与cur结点直接相连的结点 { if (*it == end)//如果该元素和终点单词一致,那么返回步长即可 return step; q.push(*it);//反之将其压入队列 } step++;//步长加一 } return 0;}int main(){ set<string> dict; dict.insert("hot"); dict.insert("dot"); dict.insert("dog"); dict.insert("lot"); dict.insert("log"); string start = "hit"; string end = "cog"; cout << CalcLadderLength(start, end, dict)<<endl; return 0;}
周围区域问题
问题描述
给定二维平面,格点处要么是
找到这样的(多个)区域后,将所有的
问题分析
我们把X理解为SOIL(土)结点,O理解为WATER(水)结点。
反向思索最简单:哪些
- 从上下左右四个边界往里走,凡是能碰到的
‘O′ ,都是跟边界接壤的,应该保留(非内陆的water结点保留)。 - 内陆的water结点直接填土。
可把上述任务理解为把内陆的water填土。
思路:
- 对于每一个边界上的
‘O′ (边界water结点)作为起点,做若干次广度优先搜索,对于碰到的‘O′ (water结点),标记为其他某字符Y (Ocean结点,表示海水非内陆); - 最后遍历一遍整个地图,把所有的
Y (Ocean结点)恢复成‘O′ (water),把所有现有的‘O′ (原来的water结点)都改成‘X′ (填土)。
代码实现:
#include<stdio.h>#include <stdlib.h>#include<iostream>#include<vector>#include<set>#include<queue>using namespace std;//检查某个结点是否为waterbool IsWater(vector<vector<int>>& land, int M, int N, int i, int j){ if ((i < 0 || i >= M) || (j < 0 || j >= N))//检查是否越界 return false; return land[i][j] == WATER;}//从边缘的water结点开始广度优先遍历,依次搜索出非内陆的water结点void Ocean(vector<vector<int>>& land, int M, int N, int i, int j){ queue <pair<int, int>> q; q.push(make_pair(i, j)); //广度优先搜索的方向,这里是四领域,四个方向(-1,0),(1,0),(0,-1),(0,1) int iDirect[] = { -1, 1, 0, 0 }; int jDirect[] = { 0, 0, -1, 1 }; int iCur, jCur; int k; while (!q.empty()) { i = q.front().first; j = q.front().second; q.pop(); for (k = 0; k < 4; k++) { iCur = i + iDirect[k]; jCur = j + jDirect[k]; if (!IsWater(land, M, N, iCur, jCur))//检查该结点是否为water结点 { q.push(make_pair(iCur, jCur)); land[iCur][jCur] = OCEAN;//所以非内陆的water结点全部变为Ocean } } }}void FillLake(vector<vector<int>>& land, int M, int N){ int i, j; //不断查找边界为water的结点,并且将其代入Ocean方法中,通过广度优先搜索到所有的非内陆的water结点 for (i = 0; i < M; i++) { if (land[i][0] == WATER) Ocean(land, M, N, i, 0); if (land[i][N - 1] == WATER) Ocean(land, M, N, i, N-1); } for (j = 1; j < N - 1; j++) { if (land[0][j] == WATER) Ocean(land, M, N, 0, j); if (land[M - 1][j] == WATER) Ocean(land, M, N, M - 1, j); } for (i = 0; i < M; i++) { for (j = 0; j < N; j++) { if (land[i][j] == OCEAN)//之前操作已经把所有非内陆的water结点变为Ocean,这时把Ocean结点变为water land[i][j] = WATER; else if (land[i][j] == WATER)//而还没有改变为Ocean结点的water结点肯定是内陆的water结点,直接填土 land[i][j] = SOIL; } }}
深度优先搜索DFS
相比广度优先搜索,深度优先搜索的应用远远多于广度优先搜索。也比
选择一个方向进行递归的不断深入,“走到头”,然后回退(恢复原始状态),选择另外一个方向再次进行搜索递归(回溯的话就是求所有解,如果不回溯只是选择一个方向,并且沿着这个方向能够递归的不断深入直到满足条件,则是求一个解)。这样就把所有解空间遍历了一遍。这也是
DFS 的核心思想。(回溯思想) (BFS 无回退。);总而言之一句话:深度搜索加回溯就是DFS 。例如这段全排列核心代码:
void Permutation(int* a, int size, int n){ if (n == size - 1){ Print(a, size); return; } for (int i = n; i < size; i++){ swap(a[i], a[n]); Permutation(a, size, n + 1);//选择其中一个方向进行递归深入 swap(a[i], a[n]);//,当上一个方向走到头后,开始回溯。这一步就是回溯,恢复到原始的状态再选择其他方向进行遍历。 }}
我们以下图为例,
a 点为起点,b 点为终点,从起点a 有两条不同的路径可以到达终点b ,第一条路径“a−>A1−>A2−>A3−>A4−>A5−>b” ,第二条路径a−>A1−>A6−>A7−>A8−>A9−>b ,那么DFS 是如何搜索到这两条路径呢?图中粉红色箭头a−>−−−>−>b 表示在点A1 处选择第一个可选方向A1−>A2 一直深入到b ,然后绿色箭头b−>−−−>−>A1 表示在A1 处沿着第一个可选方向深入到b 后再回溯 到A1 ,然后再选择A1 的第二个可选方向A1−>A6 ,再选择A6 的第一个可选方向A6−>A10 再递归深入,发现到不了b ,此时再回溯到A6 在选择第二个可选方向A6−>A7 递归深入到b 。 其实每一个箭头都是一个方法,每个箭头都是一次递归。每次都可以回溯 到任意一点,然后看看该点有没有其他可选方向,如有选择该方向深入递归。然后再回溯再递归深入直到遍历了所有解空间。这种回溯可以搜索解空间里面的所有可能解。从图中的回溯方式可以看出DFS 可能会用带堆栈 (后进先出,比如A1,A2,A3,A4,A5,b 压入堆栈,那么在回溯时b 先弹栈,其次是A5,A4,A3,A2 )
- 一般所谓“暴力枚举(因为把解空间全部遍历了一遍)”搜索都是指
DFS ,例如之前说的N−sum 问题。 - 一般使用堆栈,或者递归。
八皇后问题
问题描述
在
问题分析
解法一:可以利用全排列知识解决:
显然任意一行有且仅有
对于
这是一个非常重要的思想,务必掌握
实现代码:
#include<stdio.h>#include <stdlib.h>#include<iostream>#include<vector>#include<set>#include<queue>using namespace std;void Print(int* a, int size){ for (int i = 0; i < size; i++) { cout << a[i] << " "; } cout << endl;}//判断是否有任意两个或多个结点在同一斜线bool isValid(int *a, int size){ for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (abs(j - i) == abs(a[j] - a[i])) return false; } } return true;}void Permutation(int* a, int size, int n,int& count)//变量使用地址符号才会改变变量值内容{ if (n == size - 1){ if (isValid(a, size)) { count += 1; Print(a, size); return; } } for (int i = n; i < size; i++){ swap(a[i], a[n]); Permutation(a, size, n + 1,count);//已固定前n+1个数。 swap(a[i], a[n]);//这一步就是回溯,恢复到原始的状态再选择其他方向进行遍历。 }}int main(){ int a[] = { 0,1, 2, 3, 4, 5, 6, 7 }; int count = 0; Permutation(a, sizeof(a) / sizeof(int), 0, count);//当前已经有0个数已经固定 cout << "解的个数为 "<<count << endl; return 0;}
解法二:深度优先搜索
深度优先搜索:将第
分析对角线:
主对角线上
(i−j) 为定值,并且不同的主对角线(i−j) 值不同。由此在主对角方向上,确定了(i−j) 就确定了这条主对角线。取值范围是−(N−1)≤(i−j)≤N−1 ,从而:0≤(i−j+N−1)≤2∗N−2 ;次对角线同理,只不过
(i+j) 为定值,取值范围是0≤(i+j)≤2∗N−2 ;由此,如果确定了一个皇后的位置坐标
(i,j) ,我们计算(i−j) 和(i+j) 就能确定该皇后所在列,所在主次对角线。使用布尔数组
m1[0…2N−2]、m2[0…2N−2] 记录皇后占据的对角线
上述数据结构与剪枝过程适用于
代码实现:
#include<stdio.h>#include <stdlib.h>#include<iostream>#include<vector>#include<set>#include<queue>using namespace std;class CQueen{private: int m_nQueue; vector<bool> m_Colomn;//某皇后已占据的列 vector<bool> m_MainDiagonal;//某皇后已占据的某条主对角线 vector<bool> m_MinorDiagonal;//某皇后已占据的某条次对角线 vector<vector<int>> m_Answer;//最终解public: CQueen(int N) :m_nQueue(N) { m_Colomn.resize(N, false); m_MainDiagonal.resize(2 * N - 1, false); m_MinorDiagonal.resize(2 * N - 1, false); } void Queue() { int *path = new int[m_nQueue];//长度为m_nQueue的数组,path[0]表示第1个皇后所在列,path[1]表示第2个皇后所在列等等 CalcNQueue(path, 0); delete[] path; }private: // 将皇后(i,j)所在的列,主对角线和次对角线上的点全设为true,表示已被占用 bool CanLay(int row, int col) const { return !m_Colomn[col] && !m_MinorDiagonal[row + col] && !m_MainDiagonal[m_nQueue - 1 + row - col]; } void CalcNQueue(int* path, int row)//row表示第row个皇后在row行,接下来就是确定第row个皇后所在列的问题 { if (row == m_nQueue)//row是从0开始计,所以当row==m_nQueue时,表面所有行的皇后所在列都已经确定 { m_Answer.push_back(vector<int>(path, path + m_nQueue));//此时将结果存入m_Answer中即可 return; } for (int col = 0; col < m_nQueue; col++) { if (CanLay(row, col))//确定[row,col]该点所在列,所在主次对角线是否已经被占。 { path[row] = col;//如果没有被占用,即将第row个皇后放在该坐标上 //然后将所在列,主次对角线上全部设为true,表示已被占用 m_Colomn[col] = true; m_MinorDiagonal[row + col] = true; m_MainDiagonal[m_nQueue - 1 + row - col] = true; CalcNQueue(path, row + 1);//在这个皇后位置确定的前提下,也就是沿着这个方向递归的不断深入再去确定其他皇后的位置。 //如果不执行下面代码则不回溯,那么只是找一个解 //上面的深入到头开始不断回溯,恢复到当前步骤的初始状态,换其他的方向重新深入,然后深入到头再回溯 //不断深入回溯再深入回溯,就可以找到所有解 m_Colomn[col] = false; m_MinorDiagonal[row + col] = false; m_MainDiagonal[m_nQueue - 1 + row - col] = false; } } }public: void Print() const { cout << "所有解的个数:" << (int)m_Answer.size() << endl; for (vector<vector<int>>::const_iterator it = m_Answer.begin(); it != m_Answer.end(); it++) { PrintOne(*it); } } void PrintOne(const vector<int>& v)const { for (vector<int>::const_iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl; }};int main(){ CQueen queue(8); queue.Queue(); queue.Print(); return 0;}
数独Sudoku
问题描述
要求每行、每列、每个九宫内,都是
问题分析
此题和上面的八皇后问题是一个套路,都是可以用深度优先搜索解决。若当前位置是空格,则尝试从
代码实现:
#include<stdio.h>#include <stdlib.h>#include<iostream>#include<vector>#include<set>#include<queue>using namespace std;class CSudou{private: int m_chess[9][9]; int m_result[9][9]; bool m_bSolve;public: CSudou(int chess[9][9])//构造函数,初始化 { memcpy(m_chess, chess, sizeof(m_chess)); m_bSolve = false; } bool isValid(int i, int j) { int t = m_chess[i][j]; int k; for (k = 0; k < 9; k++) { if ((j != k) && (t == m_chess[i][k]))//查看坐标(i,j)所在行有无重复 return false; if ((i != k) && (t == m_chess[k][j]))//查看坐标(i,j)所在列有无重复 return false; } int iGrid = (i / 3) * 3;//坐标(i,j)所在九宫格左上角横坐标 int jGrid = (j / 3) * 3;//坐标(i,j)所在九宫格左上角纵坐标 int k1, k2; //查看坐标(i,j)所在九宫格有无重复元素 for (k1 = iGrid; k1 < iGrid + 3; k1++) { for (k2 = jGrid; k2 < jGrid + 3; k2++) { if ((k2 == j) && (k1 == j)) continue; if (t == m_chess[k1][k2]) return false; } } return true; } bool Sudouku() { int i, j, k; for (i = 0; i < 9; i++) { for (j = 0; j < 9; j++) { if (m_chess[i][j] == 0)//只有为0时才填充 { for (k = 1; k < 10; k++) { m_chess[i][j] = k;//尝试填充为k if (isValid(i, j) && Sudouku())//如果所在行所在列,九宫格无重复,并且在这个位置填充为k的前提下, //就是沿着这个方向深入递归可以到达满足条件状态 { if (!m_bSolve) memcpy(m_result, m_chess, sizeof(m_chess));//把结果存入到m_result m_bSolve = true; return true; } m_chess[i][j] = 0;//如果不满足条件该坐标位置恢复为0,如果满足条件深入到头再回溯,恢复当前初始状态, //另选其他方向进行递归深入 } return false; } } } return true;//说明所有位置都有值了 }};
马踏棋盘
问题描述
给定
问题分析
显然,如果从
若当前位置为
对这个“八邻域”的理解很重要。,在一些隐式图中,准确的找出几个邻域,然后进行深度或者广度搜索很重要。
显然此题可以用深度优先搜索解决。
实现代码:
#include<stdio.h>#include <stdlib.h>#include<iostream>#include<vector>#include<set>#include<queue>using namespace std;//本题中马处于任意位置都有八个方向可跳(暂不考虑越界和已跳的问题),下面八个坐标就是相对于当前坐标的八个方向int iHorse[] = { -2, -2, -1, +1, +2, +2, +1, -1 };int jHorse[] = { -1, +1, +2, +2, +1, -1, -2, -2 };int m = 8;int n = 9;//考察是否越界并且是否为0,即是否跳到过该坐标bool CanJump(const vector<vector<int>>& chese, int i, int j){ if ((i < 0) || (i >= m) || (j < 0) || (j >= n)) return false; return (chese[i][j] == 0);}bool jump(vector<vector<int>>& chese, int i, int j, int step){ if (step == m*n) return true; int iCur, jCur; for (int k = 0; k < 8; k++)//八个方向可跳 { iCur = i + iHorse[k]; jCur = j + jHorse[k]; if (CanJump(chese, iCur, jCur))//考察下一跳位置是否越界并且是否为0 { chese[iCur][jCur] = step + 1; if (jump(chese, iCur, jCur, step + 1))//深入递归 return true; chese[iCur][jCur] = 0;//上一个方向深入到头,则回溯到当前初始状态,另选方向继续深入 } } return false;}
所有括号匹配的字符串问题
问题描述
如
1:
2:
3:
4:
5:
问题分析
任何一个括号序列,都可以写成形式
A、B 都是若干括号对 形成的合法串(可为空串)- 若
N=0 ,括号序列为空。 - 若
N=1 ,括号序列只能是() 这一种。
算法描述:
- 计算
i 对括号的可行序列A - 计算
N−i−1 对括号的可行序列B ; - 组合得到
A(B) 。
注:加上额外一对括号
实现代码:
#include<stdio.h>#include <stdlib.h>#include<iostream>#include<vector>#include<set>#include<queue>using namespace std;void Unit(vector<string>& res, const vector<string>& prefix, const vector<string>& suffix){ vector<string>::const_iterator ip, is;//两个迭代器,分别迭代prefix,suffix for (ip = prefix.begin(); ip != prefix.end(); ip++) { for (is = suffix.begin(); is != suffix.end(); is++) { res.push_back("");//压入 string& r = res.back();//返回vector中最后一个元素 r += "("; r += *ip; r += ")"; r += *is; } } //返回的res=(prefix)suffix}vector<string> AllParentheses(int n){ if (n == 0) return vector<string>(1, "");//大小为1,初值为空 if (n == 1) return vector<string>(1, "()");//大小为1,初值为() vector<string> prefix, suffix, res; for (int i = 0; i < n; i++) { prefix = AllParentheses(i);//i对括号的可行情况,在这种情况下递归结果res每个字符串为i对括号 suffix = AllParentheses(n - i - 1);//n-i-1对括号的可行情况,这里面还需要不断的递归,res每个字符串为n-i-1对括号 Unit(res, prefix, suffix); } return res;}void Print(vector<string> res){ vector<string>::iterator ip; for (ip = res.begin(); ip != res.end(); ip++) { cout << ip->c_str();//注意这一步不能是cout<<*ip cout << endl; }}int main(){ vector<string> res = AllParentheses(5);//有五对括号,这里面返回的res中每个字符串为5对括号 Print(res);}
如果只是计算可行括号串的数目,如何计算?事实上,数组
Catalan数计算
void GetCatalan(int *pCatalan, int N){ pCatalan[0] = 1; pCatalan[1] = 1; int i, j; int c=0; for (i = 2; i <= N; i++) { for (j = 0; j < i; j++) { c += pCatalan[j] * pCatalan[i - j - 1]; } pCatalan[i] = c; }}
最小平方划分
问题描述:
一个正整数可以由若干个正整数的平方和表示,求整数
如
这种平方划分是一定存在的。如:将任意正整数
问题分析:
贪心并不能解决这类问题。贪心能找到平方划分数目,但却不一定是最小的。
如
如果已经求出了
若
算法描述:
已知
记
1…n−1 的最小划分为数组split[n] ,其中split[i] 表示数i的最小划分数目。令n 的最小划分数目为x ,初始化为n 。记
k :遍历1……K ,令t=n−k∗k ,其中,K=n√ ,根号向下取整。其中split[t] 已经计算过。
若split[t]+1<x ,则将x 更新为split[t]+1 。这样不断迭代更新下去,split[n] 肯定取最小。x 即为n 的最小划分。
赋值split[n]=x ,为计算更大的n 做准备。
如何判断完全平方数:
给定正整数
通常在c++中, 调用系统函数
这里我们使用使用牛顿迭代法来判断一个数是否可以完全开方。
使用牛顿迭代法:
这里我们求数
得到
代码实现:
// 计算a的平方根的近似值float GetSquare(float a){ if (a < 1e-6)//负数或者0直接返回0 return 0; float x = a / 2;//设置x0初值 float t = a; while (fabs(x-t)>1e-6)//判断是否收敛 { t = x; x = (x + a / x) / 2; } return x;}// 计算N平方划分最小数目void SquareCut2(int N, int *pre, int *minCut){ int n, k, K,t; for (n = 1; n <= N; n++) { K = GetSquare(n);//n的平方根取整 if (K*K == n) { minCut[n] = 1; pre[n] = 0; continue; } minCut[n] = n;//默认分成n份 pre[n] = n - 1;//n若分为n份,那么其前驱为n-1 for (k = 1; k <= K; k++) { t = n - k*k; if (minCut[n] > minCut[t] + 1) { minCut[n] = minCut[t] + 1; pre[n] = t;//n的前驱为t } } }}void Print(int a, const int* pre){ while (a!=0) { cout << a - pre[a] << '-' << GetSquare(a - pre[a]) << '\t'; a = pre[a]; } cout << endl;}int main(){ int N = 201314; int* pre = new int[N + 1];//0未用 int* minCut = new int[N + 1];//0未用 SquareCut2(N, pre, minCut); Print(N, pre); delete[] pre; delete[] minCut; return 0;}
DFS 和 BFS 总结
对于较典型的
DFS 和BFS ,首先要确定题中给出了 “几邻域”,这个非常重要。通常涉及到 最短路径 问题,往往是一种广度优先搜索的应用。
BFS 一般用到队列,将开始结点压入队列,然后遍历该结点所有邻域,符合条件的邻域结点依次 压入队列。这是一层层的搜索。直到遇到满足终止条件的结点则退出。BFS 无递归无回溯。通常涉及到求多解 的题,往往需要使用深度优先搜索。因为
DFS 有回溯,这种回溯就能遍历解空间里面的所有可能解。DFS 中,从起点开始,遍历它的所有邻域结点,沿着第一个方向递归深入,如果该邻域结点不满足题给条件 或者沿着这个方向深入过程中发现不满足题给条件或者已经深入到头时,再回溯到当前的初始状态,再沿着第二个方向递归深入做同样操作,不断的递归再回溯,再递归在回溯。把所有可行解都搜一遍。
- 算法基础-->图论(BFS,DFS)
- 基础算法 之 BFS & DFS
- 算法学习基础篇(一):搜索(DFS、BFS)
- BFS,DFS,DIJKSTRA算法基础练习
- 图论算法基础-BFS与DFS
- ACM基础算法复习(STL + DFS + BFS + 并查集 + 快速幂 + 欧几里得算法)
- 基础BFS+DFS poj3083
- poj1979【基础bfs/dfs】
- DFS BFS 搜索基础
- 【bfs与dfs】基础
- DFS&BFS算法总结(1)
- 一些算法实现(bfs、dfs、Dijkstra)
- BFS和DFS算法
- BFS和DFS算法
- DFS,BFS算法总结
- dfs,bfs算法探究
- 算法-DFS和BFS
- A*算法(A星算法)DFS BFS
- GeoServer基础教程(四):空间数据互操作的接口规范WMS、WFS和WCS
- 关于shiro的.ini文件配置
- volatile的工作机制代码测试之socket学习笔记
- Bundle Plugin for Maven
- iOS开发 iOS 10及其以上,UITextField输入时文字往下偏移问题
- 算法基础-->图论(BFS,DFS)
- jstl自定义函数
- [bzoj2163]复杂的大门 最大流
- SQL Like通配符使用
- display:Flex-CSS3布局
- 文章标题
- Eclipse启动时发生An internal error occurred during: "Initializing Java Tooling".错误的解决方法
- Excel操作
- 强类型语言和弱类型语言