男人三妻四妾不得不說的故事(又名 N皇后問題)

在這裏插入圖片描述
問題描述:
  N皇后問題是把N個皇后放到NNN*N的棋盤中,使它們不會相互攻擊。根據國際象棋規定,皇后可以喫掉和它同行、同列或同一斜線上的任意一個棋子。設計算法給出所有解。

在這裏插入圖片描述

如下圖:如果一個皇后在圖中的位置,那麼,在她的行,列,對角線上都不能有其他皇后在這裏插入圖片描述

算法分析:
  解決這個問題最簡單粗暴的方法就是枚舉,枚舉所有的可能性,最後得出解,如:求 4 皇后問題時,我們可以直接使用 4 層循環,枚舉所有的可能性。雖然這樣算法的時間複雜度非常高,但是簡單粗暴啊。但是呢,我們這裏要求 N 皇后問題,皇后的個數是未知的,我們就不能通過寫多層循環的方法來解決。那該怎麼辦呢?
  在這裏插入圖片描述
  我們可以採用遞歸,使用遞歸來代替多層的循環。
  在這裏插入圖片描述
核心算法詳解:
  我們設定一個數組 queenPos[M],用來存放對於N皇后每一種方案的結果。比如:queenPos[0]=3,就表示第 0 行的皇后放在第 3 列上。
  寫一個函數NQueen(int k),它表示,在第 0 行到第 k1k-1 行的皇后都已經擺好的情況下,我們開始擺放第 kk 行的皇后,直到把 NN 個皇后都擺完。

  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);
    在這裏插入圖片描述
  2. 在第一行擺放第 1 個皇后。在該行中尋找和第 0 個皇后不衝突的位置,最終將該皇后放入該行第 2 列的位置。然後繼續擺放下一個皇后。
    在這裏插入圖片描述
  3. 擺放第二個皇后(從0開始)。在第 2 行尋找與前面兩個皇后不發生衝突的位置。我們發現,這時,不論將該皇后放入該行的哪個位置,都會與前面的兩個皇后發生衝突。這時,我們返回重新擺放第 0 個皇后。
  4. 重新擺放第 0 個皇后,這時,我們已經得知不能將第 0 個皇后放進第 0 行第 0 列的位置。所以,我們將該皇后放在第 0 行第 1 列的位置。然後重複第二步和第三步,直到 4 個皇后全部放完。
  5. 經過多次嘗試與檢測,我們得到一種方案:
    在這裏插入圖片描述
  6. 我們繼續尋找另外一種方案。直到沒有滿足條件的方案,程序結束。

C++實現:

#include<iostream>
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;
}

輸出:
在這裏插入圖片描述

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揹包問題)
葉天帝對戰黑暗至尊,多次瀕臨死境,卻創出 分治算法(分治算法解決歸併排序),小白也能看懂的歸併
兩行代碼帶你體驗算法極致的美
深入理解動態規劃

博主

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章