【回溯算法】总结回溯算法的写法套路

之前接了个新项目,忙了一段时间,这一篇拖了好久啊……月末就辞职啦!
今天复习了一下回溯算法,明明是很基础的东西,动手写的时候却感觉很困难,思路不清晰。翻看了一下以前写的全排列问题的代码,思路一下就有了。果然算法套路模板还是很重要的,模板在手什么变体都不怕了,而且必须及时复习,加强记忆。所以决定总结一下经典算法的套路,就从回溯开始吧。


前言

回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解。深度优先遍历有个特点:当发现已不满足求解条件时,就返回并尝试别的路径,我们通常称之为“剪枝”。此时对象类型变量就需要重置成为和之前一样,称为「状态重置」。


一、八皇后问题

八皇后问题可以说是回溯算法的经典例题了,我还记得大二的算法课有一次大作业,给了十几道题目自己选一道来做,我当时就选了八皇后问题。一开始不会写,后来还是参考了网上的代码(这就是经典题目的好处,不会也有得抄哈哈哈),看明白了网上的代码之后,我自己动手改成了两版,一版能画出棋盘的八皇后,另一版只统计数量的N皇后,一起交上去了。

八皇后问题(Eight queens):将 8 个皇后放置在 8×8 的棋盘上,并且使皇后彼此之间不能相互攻击。(国际象棋中皇后攻击的条件是位于同一行、同一列或同一斜线上)
图片来自百度百科
如果使用穷举法,需要尝试8^8种情况,这个时候回溯的特点“剪枝”就体现出优势来了。比如我们先把第一行的Queen放在第一列上,那么第二行的Queen显然不能放在第一列或者第二列了,于是我们把它放在第三列,接下来第三行的Queen就显然不能放在1、2、3、4列上,以此类推,在不符合要求时果断退回上一步,寻找新的路径,就是回溯的核心思想了。

来一个N皇后求解决数量的代码:

class Solution {
public:
	int result=0;
    int totalNQueens(int n) {
    	//初始化一个空棋盘
    	vector<int> board(n,-1);
    	backtrack(0,board);
    	return result;
    }
    void backtrack(int row,vector<int>& board){
    	//结束条件
    	if(row==board.size()) 
    	{
    		result++;
    		return;
    	}

    	for(int col=0;col<board.size();col++)
    	{
    		if(!isValid(board,row,col)) continue;
    		//做选择
    		board[row]=col;
    		//下一行
    		backtrack(row+1,board);
    		//重置
    		board[row]=-1;
    	}
    }
    bool isValid(vector<int> board,int row,int col){
    	//判断当前位置是否与前几行有冲突
    	for(int r=0;r<row;r++)
    	{
    		int c=board[r];
    		if(c==col||abs(r-row)==abs(c-col)) return false;
    	}
    	return true;
    }
};

二、全排列问题

全排列问题:给定一个不含重复数字的数组,返回其所有可能的全排列。
全排列问题可以说是回溯算法的标准模板了,很清晰的实现思路,就是选择下一状态 - 递归 - 状态重置。
全排列代码如下:

class Solution {
public:
	vector<vector<int>> result;
    vector<vector<int>> permute(vector<int>& nums) {
    	vector<int> line;
    	backtrack(line,nums);
    	return result;
    }
    void backtrack(vector<int>& line,vector<int> nums){
    	if(line.size()==nums.size())
    	{
    		result.push_back(line);
    		return;
    	}
    	for(int i=0;i<nums.size();i++)
    	{
    		bool flag=true;
    		for(int j=0;j<line.size();j++)
    			if(line[j]==nums[i]){
    				flag=false;
    				break;
    			}
    		if(!flag) continue;
    		line.push_back(nums[i]);
    		backtrack(line,nums);
    		line.pop_back();
    	}
    }
};

三、回溯算法模板

代码如下(示例):

//回溯算法的套路
class Solution {
public:
    //定义全局变量,例如结果要返回的数量、或者结果集
    //(根据需要)定义visited[]
    vector<vector<int>> result;
    vector<vector<int>> permute(vector<int>& nums) {
        //对全局变量做初始化
        //定义track(表示当前状态)
        vector<int> line;
        //执行回溯算法,使用初始参数
        backtrack(line,nums);
        //返回结果
        return result;
    }
    void backtrack(vector<int>& line,vector<int> nums){
        //定义结束状态,回溯到哪里停止
        if(line.size()==nums.size())
        {
            result.push_back(line);
            return;
        }
        //遍历所有可能的下一状态
        for(int i=0;i<nums.size();i++)
        {
            //排除不符合条件的下一状态(剪枝)
            bool flag=true;
            for(int j=0;j<line.size();j++)
                if(line[j]==nums[i]){
                    flag=false;
                    break;
                }
            if(!flag) continue;
            //修改track为新状态,修改visited[]
            line.push_back(nums[i]);
            //对新状态执行回溯,注意修改参数
            backtrack(line,nums);
            //退回原状态
            line.pop_back();
        }
    }
};

总结

总之回溯算法就是带剪枝的穷举吧,列举所有可能性肯定是非常耗时的,剪枝条件写好了能大幅降低时间复杂度。
有了模板,做题目就轻松多啦,思路也清晰了,就往里套就行了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dijkstra最短路径算法的矩阵写法主要有两种方式:邻接矩阵和距离矩阵。 邻接矩阵写法: 首先,定义一个二维矩阵adj[n][n]表示图中每个节点i与节点j之间是否有连接,若有则adj[i][j]=1,否则adj[i][j]=0。此外,再定义一个一维矩阵dis[n],表示源点s到每个节点i的最短距离。 然后,初始化dis[n]数组,将源点s到每个节点i的初始距离都赋值为无穷大,除了源点s到自身的距离为0。接下来,遍历整张图,每次从未被访问的节点中选择一个距离源点s最近的节点u,更新其邻接的节点v的最短距离。具体地,如果dis[v]>dis[u]+adj[u][v],则更新dis[v]=dis[u]+adj[u][v]。 最后,遍历完整张图后,dis数组中的所有元素即为源点s到所有节点的最短距离。 距离矩阵写法: 首先,定义一个二维矩阵dist[n][n]表示图中每个节点i与节点j之间的距离。然后,同样定义一个一维矩阵dis[n],表示源点s到每个节点i的最短距离。 接下来,初始化dis[n]数组,将源点s到每个节点i的初始距离都赋值为dist[s][i],除了源点s到自身的距离为0。然后,遍历整张图,每次从未被访问的节点中选择一个距离源点s最近的节点u,更新其邻接的节点v的最短距离。具体地,如果dis[v]>dis[u]+dist[u][v],则更新dis[v]=dis[u]+dist[u][v]。 最后,遍历完整张图后,dis数组中的所有元素即为源点s到所有节点的最短距离。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值