八皇后算法解析[通俗易懂]

八皇后算法解析[通俗易懂]今天研究力扣的一道题死活写不出来对应的算法,没办法自己算法基础太差。于是看了下答案,发现使用什么回溯算法,菜鸟表示平时开发期间写的最复杂的程序就是写了两层for循环,已经很牛逼了有木有?这个回溯算法什么鬼?于是乎百度了下,算是了解了回溯算法是什么玩意儿。这里分析一波八皇后算法来加深一下理解。https://blog.csdn.net/microopithecus/article/details/…

大家好,又见面了,我是你们的朋友全栈君。

今天研究力扣的一道题死活写不出来对应的算法,没办法自己算法基础太差。于是看了下答案,发现使用什么回溯算法,菜鸟表示平时开发期间写的最复杂的程序就是写了两层for循环,已经很牛逼了有木有?这个回溯算法什么鬼?于是乎百度了下,算是了解了回溯算法是什么玩意儿。这里分析一波八皇后算法来加深一下理解。

八皇后算法描述如下:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法!
在这里插入图片描述

下面来分析一波,假设此时我们想要在黑色方块位置放置一个皇后
在这里插入图片描述
如果一列一列的放置皇后的话,图中黑色位置能放置一个皇后的合法性条件为:
1、绿色线条经过的方格没有皇后 (不处于同一斜线)
2、红色线条经过的方格没有皇后 (不处于同一行)
3、紫色线条经过的方格没有皇后 (不处于同一斜线)

也就是说如果以黑色方块位置为参照原点:(0,0)坐标点,紫色和绿色两个线条分别是斜率为1和-1的两个函数,如下图:
紫色线所代表的函数是:y = -x;
绿色先所代表的函数是:y=x;
(横坐标是列,纵坐标为行,注意行从上到下递增)
在这里插入图片描述
凡是位于这两条函数线上的位置(点)以及横坐标(说明位于同一行)都不能有皇后

所以假设某一列皇后的位置用行来记录,比如queen[column] = row,意思是第column列的皇后的位置在第row行。
同行的逻辑很好判断,那么我们想要在黑色方块位置放置一个皇后,怎么判断前面几列是否在绿色线条和紫色线条上已经有了皇后呢?思路也很简单:
假设黑色方块的位置为n列,nRow行,假设位于m列的所在的行是否有皇后位于紫色线或者绿色上,那么就符合下面条件:

//假设此时即将在n列放置一个皇后,n>m

]//获取m列上皇后所在的行
int mRow = queen[m]
int nRow = queen[n];
//行的差值
int rowDiff = nRow - mRow;

//列的差值
int columnDiff = n-m;

上面代码中 rowDiff的绝对值等于columnDiff的绝对值的话,说明点位于y=x或者y=-x的函数线上:
在这里插入图片描述
就说明此时黑色方块的位置是不能放置皇后的,因为在紫色或者绿色线上已经有了皇后。

那么用代码来(currentColumn,curreentRow)是否可以放置皇后的方法如下

	/**
     * 判断当(currentRow,currentColumn)是否可以放置皇后
     * @param currentColumn 
     * @return
     */
    public boolean isLegal(int currentRow,int currentColumn) {
    	//遍历前面几列
    	for(int preColumn=0;preColumn<currentColumn;preColumn++) {
    		int row = queen[preColumn];
    		//说明在子preColumn的低currentRow已经有了皇后
    		if(row==currentRow) {
    			return false;
    		}
    		
    	    //行与行的差值
            int rowDiff= Math.abs(row -currentRow);
          
            //列于列的差值
            int columnDiff =  Math.abs(currentColumn-preColumn);
            //说明斜线上有皇后
            if(rowDiff==columnDiff ){
                return false;
            }
    	}//end for
    	
    	//说明(currentRow,currentColumn)可以摆放。
    	return true;
    }

}

因为博主是按照一列一列的方式来进行放置的,所以整体思路就是:在当前列逐步尝试每一行是否可以放置皇后,如果有一个可以放置皇后,就继续查看下一列的每一行是否可以放置皇后。所以代码如下:

	 int queen[] = new int[8];
    int count = 0;
    
	private void eightQueen(int currentColumn) {
		//这个for循环的目的是尝试讲皇后放在当前列的每一行
		for(int row=0;row<8;row++) {
			//判断当前列的row行是否能放置皇后
			if(isLegal(row,currentColumn)) {
		        //放置皇后
				queen[currentColumn] = row;
				if(currentColumn!=7) {
					//摆放下一列的皇后
					eightQueen(currentColumn+1);
				
				}else {
					//递归结束,此时row要++了
					count++;
				}
			}
		}//end for
	}

需要注意的是当currentColumn==7的时候,说明此时已经完成了一种摆放方法,然后for循环继续执行,去尝试其他摆放方法。
测试一波,一共有92种摆放方法:

   public static void main(String args[]) {
    	Queen queen = new Queen();
    	queen.eightQueen(0);
    	System.out.println("总共有 " +queen.count+ " 摆放方法");
    }

所以结合八皇后的实现来看看到底什么是回溯算法,看百度百科解释:回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法

比如八皇后算法来说,我们根据约束条件判断某一列的某一行是否可以放置皇后,如果不可以就继续判断当前列的下一行是否可以放置皇后.如果可以放置皇后,就继续探寻下一列中可以放置皇后的那个位置。完成一次摆放后。再重新挨个尝试下一个可能的摆放方法。

下面用一个力扣的题再次巩固下回溯算法的应用。该题描述如下:

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。

说明:所有数字(包括 target)都是正整数。解集不能包含重复的组合。 
示例 1:输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

示例 2:输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

做该题的重要条件是无重复的数组,那么问题就很好解了。
首先对数组从大到小排序。这是解题的关键。
然后为了减少不必要的遍历,我们要对原来的数组进行截取:

      List<List<Integer>> res = new ArrayList<>();
         //重要的要大小排列
        Arrays.sort(candidates);
        //说明原数组中就没有满足target的数
		if (candidates[0] > target) {
			return res;
		}

	  List<Integer> newCandidates= new ArrayList<Integer>();
        int len = candidates.length;
		// 取小于target的数 组成一个临时数组
		for (int i = 0; i < len; i++) {
			int num = candidates[i];
			if (num > target) {
				break;
			}

			newCandidates.add(num);
		} // end for
         

通过上面的步骤我们拿到了一个从小到大排列的无重复数组newCandidates,数组中的元素都<=target.
因为数组从小到大排列,所以我们有如下几种情况,以candidates = [2,3,5], target = 8为例:
符合条件的子数组满足条件如下
1、target循环减去一个数,如果能一直减到到差值等于0,那么这个数组成的数组就是一个解,比如[2,2,2,2];
2、target减去一个数,然后形成了一个新的newTarget=target-num[i],让这个newTarget减去下一个数num[i+1],然后执行步骤1,则又是一个解,比如[2,3,3];(其实步骤1是步骤2的一个特例)
3、target减去一个数,然后形成了一个新的newTarget=target-num[i],让这个newTarget减去下一个数num[i+1],如果能一直减到到差值等于0说明又是一个解.,比如[3,5];
如此得到了一个规律,只要是相减之后得到差值=0,就说明就得到一个解。
得到一个新的解之后继续循环数组中的下一个数字,继续执行1,2,3步骤即可。
所以完成的解法如下:

class Solution {
     public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
         //重要的要大小排列
        Arrays.sort(candidates);
         
		List<Integer> temp = new ArrayList<Integer>();

     
		if (candidates[0] > target) {
			return res;
		}

        int len = candidates.length;
         
		// 取小于target的数 足证一个临时数组
		for (int i = 0; i < len; i++) {
			int num = candidates[i];
			if (num > target) {
				break;
			}

			temp.add(num);
		} // end for
         
         //
        find(res, new ArrayList<>(), temp, target, 0);
         
        return res;
    }
    
    public void find(List<List<Integer>> res, List<Integer> tmp, List<Integer> candidates, int target, int start){
        //target==0.找到一个新的解
        if (target == 0) {
            res.add(new ArrayList<>(tmp));
        }else if(target>0){
          for (int i = start; i < candidates.size(); i++) {
             int num = candidates.get(i);
             if(num<=target){               
                  tmp.add(num);
                  //查找新的target
                  int newTarget = target-num;
                  find(res, tmp, candidates, newTarget, i);
                  tmp.remove(tmp.size() - 1);
             }
           
           }//end for
        }
    
       
    }
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/124813.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • oracle查询时使用case,Oracle查询语句中Casewhen的使用[通俗易懂]

    oracle查询时使用case,Oracle查询语句中Casewhen的使用[通俗易懂]casewhen和decode函数用法有一些相似,只是decode是枚举函数,而casewhen则更加灵活,同时casewhen相当于一个特殊的只有两个枚casewhen语句语法如下:casewhen表达式thenvalueAelsevalueBend;具体使用如下:select(casewhena.column1>=1then’成功’e…

  • 移动端开发绪论

    移动端开发绪论移动端开发基础通常移动端开发主流方案一共有两种一种是单独制作移动端页面,目前在市场上是主流方案还有一种是响应式页面兼容移动端这种虽然不是主流方案,但是应用这种开发方案的也不是没有.我们访问移动端页面可以通过以下两种方式进行访问:网址域名加m(mobile)就可以直接访问页面的移动端页面使用移动设备,进行访问,则可以直接跳转到移动端页面又到了我们最头疼的浏览器的兼容性问题移动端浏览器基本以webkit内核为主,因此我们只需要考虑webkit兼容性问题即可。移动端浏览器

  • 如何运行一个vue项目(github安装项目依赖)

    1.cd到package.json目录中,执行npmoutdatedPackageCurrentWantedLatestLocation包名当前版本满足semer版本的最高版本(及在兼容的前提下能更新的最高版本)当前最高的版本红色:可以立即更新黄色:需要进行兼容,慎重更新全部更新在已有项目中,不建议采用全部更新,推荐使用npmupdate按需更新安装ncu,执行npminstall-gnpm-check-updates执行ncu-u

  • Java面试宝典(2019版)

    Java面试宝典(2019版)附Java/C/C++/机器学习/算法与数据结构/前端/安卓/Python/程序员必读书籍书单大全:书单导航页(点击右侧极客侠栈即可打开个人博客):极客侠栈①【Java】学习之路吐血整理技术书从入门到进阶最全50+本(珍藏版)②【算法数据结构+acm】从入门到进阶吐血整理书单50+本(珍藏版)③【数据库】从入门到进阶必读18本技术书籍网盘吐血整理网盘(珍藏版)④【Web前端】从HT…

  • JavaScript之爆肝汇总【万字长文❤值得收藏】[通俗易懂]

    JavaScript之爆肝汇总【万字长文❤值得收藏】[通俗易懂]目录一、JavaScript简介1.1.一门客户端脚本语言1.2.JavaScript发展史1.3.JavaScript优势1.4.JavaScript引用一、JavaScript简介1.1.一门客户端脚本语言运行在客户端浏览器中的。每一个浏览器都有JavaScript的解析引擎脚本语言:不需要编译,直接就可以被浏览器解析执行了功能:可以来增强用户和html页面的交互过程,可以来控制html元素,让页面有一些动态的效果,增强用户的体验1.2.JavaScript发展史1992年,Nomba

  • linux下安装pycharm到桌面_Linux下载pycharm

    linux下安装pycharm到桌面_Linux下载pycharm工欲善其事,必先利其器。既然开始学习了,就得有好的工具嘛!这里lz选了个pycharm的编译工具。可能是看着比较舒服吧(其实就是感觉和idea一样),当时也想着用eclipse安装插件,后来也没有用。该干嘛的就是干嘛的,我可不想任务栏里一排排的eclipse。要是着急了,傻傻分不清。lz建议条件允许的话,就不要用激活成功教程版了,还是正版才是王道。社区版也能满足日常的一些开发。废话不多说,下来开始我们伟大…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号