参考文章:https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E6%90%9C%E7%B4%A2.md
广度优先搜索(BFS)
广度优先搜索是按层来处理顶点的,距离开始点最近的那些顶点首先被访问,而最远的那些顶点则最后被访问。BFS的代码使用了一个队列。搜索的步骤:
- 首先选择一个顶点作为起始顶点,将起始顶点放入队列中
- 从队列首部选出一个顶点,将与之相邻并且没有被访问的结点依次加入到队列的队尾,然后访问这些与之相邻并且没有被访问过的结点,将队列队首的结点删去。
- 按照步骤2处理队列中下一个结点,直到找到要找的结点或者队列中没有结点结束。
void BFS()
{
定义队列;
定义备忘录,用于记录已经访问的位置;
判断边界条件,是否能直接返回结果的。
将起始位置加入到队列中,同时更新备忘录。
while (队列不为空) {
获取当前队列中的元素个数。
for (元素个数) {
取出一个位置节点。
判断是否到达终点位置。
获取它对应的下一个所有的节点。
条件判断,过滤掉不符合条件的位置。
新位置重新加入队列。
}
}
}
我们用一道LeedCode上面的题目讲解,题目位置:
https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/ 这里我们需要注意三点:
- 需要一个队列,来记录下一次访问的结点,因为该队列是记录结点位置的(访问结点的下标),如果一维数组可以搞定,定义个
int queue[QUEUE_SIZE] ,如果是二维,定义int queue[QUEUW_SIZE][2] . - 需要一个备忘录,记录已经被访问的结点,备忘录用数组表示
- 每一层结点数就是可以访问的结点,这里题目是 8 个方向上的单元格
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define QUEUE_SIZE 10001
int BFS()
{
int grid[3][3] = {{0,0,0},{1,1,0},{1,1,0}};
int gridSize=3;
int ans=0;
if(grid[gridSize-1][gridSize-1]==1 || grid[0][0]==1)
return -1;
if(gridSize==1)
return 1;
int m=gridSize,n=m*m,front=0,rear=0,pre_rear=0,cnt=1;
int **cularr = (int **)malloc(sizeof(int)*n);
for(int i=0;i<n;i++)
{
cularr[i]=(int *)malloc(sizeof(int)*2);
}
cularr[rear][0]=0;
cularr[rear++][1]=0;
grid[0][0]=1;
int temp[8][2] = {{-1,-1},{0,-1},{1,-1},{1,0},{1,1},{0,1},{-1,1},{-1,0}};
while(front<rear)
{
pre_rear = rear;
while(front<pre_rear){
for(int i=0;i<8;i++)
{
int x = cularr[front][0]+temp[i][0];
int y = cularr[front][1]+temp[i][1];
if((m-1==x)&&(m-1==y))
{
return cnt+1;
}
if(x<m && x>=0 && y<m && y>=0 && grid[x][y]==0)
{
cularr[rear][0]=x;
cularr[rear++][1]=y;
grid[x][y]=1;
}
}front++;
}cnt++;
}
return -1;
}
int main(void)
{
int num = BFS();
printf("%d\n",num);
return 0;
}
深度优先遍历(DFS)
广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。
而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。
在程序实现 DFS 时需要考虑以下问题:
- 栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
- 标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
- 每个结点有多少个子树(也就是一个结点遍历完,深度优先遍历时,有几个可选的结点可以遍历,比如上图的0,可以有1、2、5、6四个结点可以选择)
我们用LeedCode上面的题目来讲解:
695. 岛屿的最大面积
#include <stdio.h>
#include <stdlib.h>
int dfs(int **grid,int gridSize,int *gridColSize,int row,int col)
{
if(row<0 || row>=gridSize || col<0 || col>=gridColSize[0] || grid[row][col]==0)
{
return 0;
}
grid[row][col]=0;
return 1+dfs(grid,gridSize,gridColSize,row,col+1)
+dfs(grid,gridSize,gridColSize,row,col-1)
+dfs(grid,gridSize,gridColSize,row+1,col)
+dfs(grid,gridSize,gridColSize,row-1,col);
}
int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize)
{
int area=0,max=0,i,j;
for(i=0;i<gridSize;i++)
{
for(j=0;j<gridColSize[0];j++)
{
area = dfs(grid,gridSize,gridColSize,i,j);
max = max>area?max:area;
}
}
return max;
}
int main(void)
{
int grid[][13] = {{0,0,1,0,0,0,0,1,0,0,0,0,0},{0,0,0,0,0,0,0,1,1,1,0,0,0},{0,1,1,0,1,0,0,0,0,0,0,0,0},{0,1,0,0,1,1,0,0,1,0,1,0,0},{0,1,0,0,1,1,0,0,1,1,1,0,0},{0,0,0,0,0,0,0,0,0,0,1,0,0},{0,0,0,0,0,0,0,1,1,1,0,0,0},{0,0,0,0,0,0,0,1,1,0,0,0,0}};
int gridColSize =13;
int **p = malloc(sizeof(int)*8);
for(int i=0;i<8;i++)
{
p[i]=grid[i];
}
int max = maxAreaOfIsland(p,8,&gridColSize);
printf("max=%d\n",max);
return 0;
}
求岛屿最大面积,也就是从一个结点出发,我们按前后左右方向走,可以走的最大格子数(可达最大区域)。
我们访问过一个位置后,使用深度优先遍历的话,我们可以有四个选择,也就是水平或者竖直的四个方向上,这对应深度优先遍历,就是每个结点都有四个子树。 对于可以访问的结点,访问过后,我们将其值1置为0,表示已经访问过了(0在这个题目当中不需要访问)。 对于栈,这题我们用递归,因为有四个选择,我们在递归时,需要加上四个dfs函数。 结果:
回溯法
Backtracking(回溯)属于 DFS。
- 普通 DFS 主要用在 可达性问题 ,这种问题只需要执行到特点的位置然后返回即可。
- 而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。
因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
- 在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;
- 但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
以上三种定义:
1、路径:也就是已经做出的选择
2、选择列表:也就是当前可以做的选择
3、结束条件:也就是到达决策树底层,无法再做选择的条件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
static char g_ch[10][4] = {
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz",
};
static int g_total[10] ={0,0,3,3,3,3,3,4,3,4};
void recursive(char *number, int *answer, int index, int depth, char **ans, int *pathIdx)
{
if(index == depth)
{
ans[*pathIdx] = (char *)malloc((depth+1)*sizeof(char));
for(int i=0;i<depth;i++)
{
ans[*pathIdx][i]=g_ch[number[i]-'0'][answer[i]];
}
ans[*pathIdx][depth]='\0';
(*pathIdx)++;
return;
}
for(answer[index]=0;answer[index]<g_total[number[index]-'0'];answer[index]++)
{
recursive(number,answer,index+1,depth,ans,pathIdx);
}
}
char ** letterCombinations(char * digits, int* returnSize){
int a[100] = {0};
int depth = strlen(digits);
int num = (int)pow(4,depth);
*returnSize = 0;
if(depth == 0)
return NULL;
char **ans = (char **)malloc(num*sizeof(char *));
recursive(digits,a,0,depth,ans,returnSize);
return ans;
}
int main(void)
{
char *digits = "23";
int returnSize =0;
char **ans = letterCombinations(digits,&returnSize);
printf("returnSize = %d\n",returnSize);
for(int i =0;i<returnSize;i++)
{
puts(ans[i]);
}
return 0;
}
|