男人三妻四妾不得不说的故事(又名 N皇后问题)

男人三妻四妾不得不说的故事(又名 N皇后问题)_第1张图片
问题描述:
  N皇后问题是把N个皇后放到 N ∗ N N*N NN的棋盘中,使它们不会相互攻击。根据国际象棋规定,皇后可以吃掉和它同行、同列或同一斜线上的任意一个棋子。设计算法给出所有解。

男人三妻四妾不得不说的故事(又名 N皇后问题)_第2张图片

如下图:如果一个皇后在图中的位置,那么,在她的行,列,对角线上都不能有其他皇后男人三妻四妾不得不说的故事(又名 N皇后问题)_第3张图片

算法分析:
  解决这个问题最简单粗暴的方法就是枚举,枚举所有的可能性,最后得出解,如:求 4 皇后问题时,我们可以直接使用 4 层循环,枚举所有的可能性。虽然这样算法的时间复杂度非常高,但是简单粗暴啊。但是呢,我们这里要求 N 皇后问题,皇后的个数是未知的,我们就不能通过写多层循环的方法来解决。那该怎么办呢?
  男人三妻四妾不得不说的故事(又名 N皇后问题)_第4张图片
  我们可以采用递归,使用递归来代替多层的循环。
  男人三妻四妾不得不说的故事(又名 N皇后问题)_第5张图片
核心算法详解:
  我们设定一个数组 queenPos[M],用来存放对于N皇后每一种方案的结果。比如:queenPos[0]=3,就表示第 0 行的皇后放在第 3 列上。
  写一个函数NQueen(int k),它表示,在第 0 行到第 k − 1 k-1 k1 行的皇后都已经摆好的情况下,我们开始摆放第 k k k 行的皇后,直到把 N N N 个皇后都摆完。

  1. 在主函数中,我们从摆放第 0 行的皇后开始。即:NQueen(0);
  2. 递归出口:k == N,表示 N 个皇后已经摆好。为什么要queenPos[i] + 1呢?当然是为了程序的友好性了,只有计算机行业的人,数东西的时候才是从 0 开始的。
  3. 如果 k != N,我们就开始摆放第 k 行的皇后以及后面的皇后。
    1. 逐个尝试第 k 个皇后摆放的位置,一共有 N 个位置,所以循环从 0 到 N-1
    2. 对每个位置进行判断,看它是否和前面摆放好的皇后有冲突。冲突有两种情况:
      ①. 当该皇后要摆放的位置的同一列中已经有了其他的皇后。即:queenPos[j] == i
      ②. 当该皇后要摆放的位置的对角线上已经有了其他的皇后。即:abs(queenPos[j] - i) == abs(k - j)。(为什么是这样判断呢,大家画个图,一目了然)
      ③. 当这两种冲突满足其一时,该位置就不能摆放皇后。则测试下一个位置。
    3. 如果检测到某个位置并不和前面已经摆放好的皇后有冲突,即:if (j == k);则在该位置摆放第 k 个皇后,即:queenPos[k] = i;,表示将第 k 个皇后摆放在位置 i ;后继续摆放第 k+1 个皇后,即:NQueen(k + 1);
  4. 为什么我们只调用了NQueen(0);,它最后会输入所有的解呢?
    1. 因为我们对每一行的皇后的所有可能的位置我们都去进行了检测。
    2. 它归根到底就是一个 N 重循环。

程序比较复杂,我们举个例子,看一个它的执行过程。

例:现在要求 4 皇后的问题:

  1. 执行NQueen(0);,在第 0 行摆放第0个皇后。进入循环for (i = 0; i < N; i++),显然,这时前面并没有摆放其他的皇后,所以这个第 0 个皇后就摆放在第 0 行第 0 列的位置,即:queenPos[k] = i;,进而继续摆放第一个皇后,NQueen(k + 1);
    男人三妻四妾不得不说的故事(又名 N皇后问题)_第6张图片
  2. 在第一行摆放第 1 个皇后。在该行中寻找和第 0 个皇后不冲突的位置,最终将该皇后放入该行第 2 列的位置。然后继续摆放下一个皇后。
    男人三妻四妾不得不说的故事(又名 N皇后问题)_第7张图片
  3. 摆放第二个皇后(从0开始)。在第 2 行寻找与前面两个皇后不发生冲突的位置。我们发现,这时,不论将该皇后放入该行的哪个位置,都会与前面的两个皇后发生冲突。这时,我们返回重新摆放第 0 个皇后。
  4. 重新摆放第 0 个皇后,这时,我们已经得知不能将第 0 个皇后放进第 0 行第 0 列的位置。所以,我们将该皇后放在第 0 行第 1 列的位置。然后重复第二步和第三步,直到 4 个皇后全部放完。
  5. 经过多次尝试与检测,我们得到一种方案:
    男人三妻四妾不得不说的故事(又名 N皇后问题)_第8张图片
  6. 我们继续寻找另外一种方案。直到没有满足条件的方案,程序结束。

C++实现:

#include
using namespace std;
const int M = 100;
int N;
int queenPos[M];//存放皇后的摆放位置
int sum = 0;//记一共有多少种解决方案
void display()//用来图形化输出结果,@表示皇后
{
     
	int i, j;
	int k;
	cout << endl;
	sum++;
	for (i = 0; i < N; i++)
	{
     
		cout << " ";
		for (k = 0; k < N; k++)
		{
     
			cout << "---";
		}
		cout << endl;
		for (j = 0; j < N; j++)
		{
     
			if (j == queenPos[i])
			{
     
				cout << "| ";
				cout << "@";
			}
				
				
			else
			{
     
				cout << "| ";
				cout << ".";
			}
		}
		cout << " |"<<endl;
	
	}
	cout << " ";
	for (i = 0; i < N; i++)
	{
     
		cout << "---";
	}
	cout << "\n"<<endl;
}

void NQueen(int k)
{
     
	int i;

	if (k == N)//N个皇后已经全部摆好
	{
     
		cout << N << "皇后的摆放位置是:";
		for (i = 0; i < N; i++)
		{
     
			cout << queenPos[i] + 1 << " ";
		}
		cout << endl;
		cout << "图解如下:" << endl;
		display();
		return;
	}
	for (i = 0; i < N; i++)//在一行中逐个检测每个位置
	{
     
		int j;
		for (j = 0; j < k; j++)//和语句摆好的前几个皇后进行冲突检测
		{
     
			if (queenPos[j] == i || abs(queenPos[j] - i) == abs(k - j))
			{
     
				break;//发生冲突,则检测下一个位置
			}
		}
		if (j == k)//该位置不与前面的皇后发生冲突
		{
     
			queenPos[k] = i;//将第k个皇后放在第i的位置上
			NQueen(k + 1);
		}
	}
}
int main()
{
     
	cin >> N;
	NQueen(0);//摆放第0个皇后
	cout <<N<<" 皇后的解决方案有 "<< sum << " 种"<<endl;;
	return 0;
}

输出:
男人三妻四妾不得不说的故事(又名 N皇后问题)_第9张图片

Java实现:

package lu;

import java.util.Scanner;

public class Demo2 {
     
    public static int N;
    public static int sum=0;
    public static void main(String[] args) {
     
        Scanner in = new Scanner(System.in);
        int []queenPos=new int[100];
        N=in.nextInt();
        NQueen(0,queenPos);
        System.out.println(N+" 皇后的解决方案有 "+sum+" 种");
    }
    public static void NQueen(int k,int []queenPos)
    {
     
        int i;

        if (k == N)
        {
     
            System.out.print(N+"皇后的摆放位置是:");
            for (i = 0; i < N; i++)
            {
     
                int a=queenPos[i] + 1;
                System.out.print(a+" ");
            }
            System.out.println();
            System.out.println("图解如下:");

            display(queenPos);

            return;
        }
        for (i = 0; i < N; i++)
        {
     
            int j;
            for (j = 0; j < k; j++)
            {
     
                if (queenPos[j] == i || Math.abs(queenPos[j] - i) == Math.abs(k - j))
                {
     
                    break;
                }
            }
            if (j == k)
            {
     
                queenPos[k] = i;
                NQueen(k + 1,queenPos);
            }
        }
    }
    public static void display(int []queenPos)
    {
     
        int i, j;
        int k;
        System.out.println();
        sum++;
        for (i = 0; i < N; i++)
        {
     
            System.out.print(" ");
            for (k = 0; k < N; k++)
            {
     
                System.out.print("---");
            }
            System.out.println();
            for (j = 0; j < N; j++)
            {
     
                if (j == queenPos[i])
                {
     
                    System.out.print("| "+"@");

                }
                else
                {
     
                    System.out.print("| "+".");
                }
            }
            System.out.println(" |");

        }
        System.out.print(" ");
        for (i = 0; i < N; i++)
        {
     
            System.out.print("---");
        }
        System.out.println();
    }
}

输出:

更多有趣算法:

叶天帝为天下苍生血战黑暗至尊(又名0-1背包问题)
叶天帝对战黑暗至尊,多次濒临死境,却创出 分治算法(分治算法解决归并排序),小白也能看懂的归并
两行代码带你体验算法极致的美
深入理解动态规划

博主

你可能感兴趣的:(算法,Java语言,C/C++语言,算法)