八皇后问题的两个高效的算法(回溯与递归)

序言

八皇后问题是一个经典的问题,在一个N*N的棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一行、同一列、同一斜线上的皇后都会自动攻击)。

求解八皇后问题是算法中回溯法应用的一个经典案例

回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

在现实中,有很多问题往往需要我们把其所有可能穷举出来,然后从中找出满足某种要求的可能或最优的情况,从而得到整个问题的解。回溯算法就是解决这种问题的“通用算法”,有“万能算法”之称。N皇后问题在N增大时就是这样一个解空间很大的问题,所以比较适合用这种方法求解。这也是N皇后问题的传统解法,很经典。

下面是算法的高级伪码描述,这里用一个N*N的矩阵来存储棋盘:

1) 算法开始, 清空棋盘,当前行设为第一行,当前列设为第一列

2) 在当前行,当前列的位置上判断是否满足条件(即保证经过这一点的行,列与斜线上都没有两个皇后),若不满足,跳到第4步

3) 在当前位置上满足条件的情形:

在当前位置放一个皇后,若当前行是最后一行,记录一个解;

若当前行不是最后一行,当前行设为下一行, 当前列设为当前行的第一个待测位置;

若当前行是最后一行,当前列不是最后一列,当前列设为下一列;

若当前行是最后一行,当前列是最后一列,回溯,即清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置;

以上返回到第2步

4) 在当前位置上不满足条件的情形:

若当前列不是最后一列,当前列设为下一列,返回到第2步;

若当前列是最后一列了,回溯,即,若当前行已经是第一行了,算法退出,否则,清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置,返回到第2步;

算法的基本原理是上面这个样子,但不同的是用的数据结构不同,检查某个位置是否满足条件的方法也不同。为了提高效率,有各种优化策略,如多线程,多分配内存表示棋盘等。

使用递归时的核心算法

//放置皇后到棋盘上void place(int k, int n){    int j;    if (k > n)        print(n); //递归出口    else        for (j = 1; j <= n; j++)   //试探第k行的每一个列            if (find(k, j))            {                q[k] = j;   //保存位置                place(k + 1, n);  //接着下一行            }}

回溯算法的核心算法

void eight_queen(int line) {    //在数组中为0-7列    for (int list = 0; list < 8; list++) {        //对于固定的行列,检查是否和之前的皇后位置冲突        if (Check(line, list)) {            //不冲突,以行为下标的数组位置记录列数            Queenes[line] = list;            //如果最后一样也不冲突,证明为一个正确的摆法            if (line == 7) {                //统计摆法的Counts加1                Counts++;                //输出这个摆法                print();                //每次成功,都要将数组重归为0                Queenes[line] = 0;                return;            }            //继续判断下一样皇后的摆法,递归            eight_queen(line + 1);            //不管成功失败,该位置都要重新归0,以便重复使用。            Queenes[line] = 0;        }    }}

八个皇后在8x8棋盘上共有4,426,165,368(64C8)种摆放方法,但只有92个互不相同的解。如果将旋转和对称的解归为一种的话,则一共有12个独立解,具体如下:

完整代码:

/*递归法实现*/#include <stdio.h>#include <stdlib.h>?const int N = 20;   //最多放皇后的个数int q[N];         //i表示皇后所在的行号,                  //q[i]表示皇后所在的列号int cont = 0;     //统计解的个数//输出一个解void print(int n){    int i, j;    cont++;    printf("第%d个解:", cont);    for (i = 1; i <= n; i++)        printf("(%d,%d) ", i, q[i]);    printf("\n");    for (i = 1; i <= n; i++)        //行    {        for (j = 1; j <= n; j++)    //列        {            if (q[i] != j)                printf("x ");            else                printf("Q ");        }        printf("\n");    }}//检验第i行的k列上是否可以摆放皇后int find(int i, int k){    int j = 1;    while (j < i)  //j=1~i-1是已经放置了皇后的行    {        //第j行的皇后是否在k列或(j,q[j])与(i,k)是否在斜线上        if (q[j] == k || abs(j - i) == abs(q[j] - k))            return 0;        j++;    }    return 1;}//放置皇后到棋盘上void place(int k, int n){    int j;    if (k > n)        print(n); //递归出口    else        for (j = 1; j <= n; j++)   //试探第k行的每一个列            if (find(k, j))            {                q[k] = j;   //保存位置                place(k + 1, n);  //接着下一行            }}int main(void){    int n;    printf("请输入皇后的个数(n<=20),n=:");    scanf("%d", &n);    if (n > 20)        printf("n值太大,不能求解!\n");    else    {        printf("%d皇后问题求解如下(每列的皇后所在的行数):\n", n);        place(1, n);        //问题从最初状态解起        printf("\n");    }    system("pause");    return 0;}?
/*回溯法实现*/#include <stdio.h>int Queenes[8] = { 0 }, Counts = 0;int Check(int line, int list) {    //遍历该行之前的所有行    for (int index = 0; index < line; index++) {        //挨个取出前面行中皇后所在位置的列坐标        int data = Queenes[index];        //如果在同一列,该位置不能放        if (list == data) {            return 0;        }        //如果当前位置的斜上方有皇后,在一条斜线上,也不行        if ((index + data) == (line + list)) {            return 0;        }           //如果当前位置的斜下方有皇后,在一条斜线上,也不行        if ((index - data) == (line - list)) {            return 0;        }    }    //如果以上情况都不是,当前位置就可以放皇后    return 1;}//输出语句void print(){    for (int line = 0; line < 8; line++)    {        int list;        for (list = 0; list < Queenes[line]; list++)            printf("0");        printf("#");        for (list = Queenes[line] + 1; list < 8; list++) {            printf("0");        }        printf("\n");    }    printf("================\n");}void eight_queen(int line) {    //在数组中为0-7列    for (int list = 0; list < 8; list++) {        //对于固定的行列,检查是否和之前的皇后位置冲突        if (Check(line, list)) {            //不冲突,以行为下标的数组位置记录列数            Queenes[line] = list;            //如果最后一样也不冲突,证明为一个正确的摆法            if (line == 7) {                //统计摆法的Counts加1                Counts++;                //输出这个摆法                print();                //每次成功,都要将数组重归为0                Queenes[line] = 0;                return;            }            //继续判断下一样皇后的摆法,递归            eight_queen(line + 1);            //不管成功失败,该位置都要重新归0,以便重复使用。            Queenes[line] = 0;        }    }}int main() {    //调用回溯函数,参数0表示从棋盘的第一行开始判断    eight_queen(0);    printf("摆放的方式有%d种", Counts);    return 0;}

原文地址:https://www.cnblogs.com/Kanna/p/12327657.html

时间: 2024-10-27 04:43:26

八皇后问题的两个高效的算法(回溯与递归)的相关文章

C#中八皇后问题的递归解法——N皇后

百度测试部2015年10月份的面试题之——八皇后. 八皇后问题的介绍在此.以下是用递归思想实现八皇后-N皇后. 代码如下: using System;using System.Collections.Generic; namespace QueensSolution { class Program { static int count = 0; static void Main(string[] args) { int n = Int32.Parse(Console.ReadLine()); L

VC版八皇后

一.  功能需求: 1. 可以让玩家摆棋,并让电脑推断是否正确 2. 能让电脑给予帮助(给出全部可能结果) 3. 实现悔棋功能 4. 实现重置功能 5. 加入点按键音效果更佳 二.  整体设计计: 1.   核心算法: 递归实现(回溯算法): 思路:按行分别安排皇后(Q),Q数目眼下为8. Q1从第一行第一列開始到最后一列,先放在第一列: Q2从第二行第一列到最后一列,每次都检查冲突,不冲突才干够落子: 依次尝试Qm- 假设Qm没有可摆放的位置,则返回Qm-1,同一时候Qm-1放弃刚才的位置:

java实现八皇后问题(递归和循环两种方式)

循环方式: package EightQueens; public class EightQueensNotRecursive { private static final boolean AVAILABLE = true; private int squares = 8, norm = squares - 1; private int positionInRow[] = new int[squares]; private int p=-1; private boolean[] rows = n

解决八皇后问题,递归与非递归方式两种

回溯法理解,一般形式 void Bcktrack(int t) //参数t表示当前递归深度 { if(t>n)Output(x); //遍历到解,则将解输出或其他处理 else { //f(n,t)和g(n,t)表示当前节点(扩展节点)处未搜索过的子树的起始编号和中指编号 for(int i=f(n,t);i<=g(n,t);i++) { x[t]=h(i); //h(i)表示当前节点(扩展节点)处x[i]的第i个可选值 if(Constarint(t)&&Bound(t))

八皇后以及N皇后问题分析

八皇后是一个经典问题,在8*8的棋盘上放置8个皇后,每一行不能互相攻击. 因此 拓展出 N皇后问题. 下面慢慢了解解决这些问题的方法: 回溯法: 回溯算法也叫试探法,它是一种系统地搜索问题的解的方法. 回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试. 在现实中,有很多问题往往需要我们把其所有可能穷举出来,然后从中找出满足某种要求的可能或最优的情况,从而得到整个问题的解. 回溯算法就是解决这种问题的“通用算法”,有“万能算法”之称. N皇后问题在N增大时就是这样一个解

P1219 八皇后 含优化 1/5

题目描述 检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行.每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子. 上面的布局可以用序列2 4 6 1 3 5来描述,第i个数字表示在第i行的相应位置有一个棋子,如下: 行号 1 2 3 4 5 6 列号 2 4 6 1 3 5 这只是跳棋放置的一个解.请编一个程序找出所有跳棋放置的解.并把它们以上面的序列方法输出.解按字典顺序排列.请输出前3个解.最后一行是解的总个数. //以下的话来自usaco官方

python解决八皇后问题

经典回溯算法:八皇后问题 算法要求: 在国际象棋棋盘上(8*8)放置八个皇后,使得任意两个皇后之间不能在同一行,同一列,也不能位于同于对角线上. 国际象棋的棋盘如下图所示: 问共有多少种不同的方法,并且指出各种不同的放法. # -*- coding:utf-8 -*- __author__ = "tyomcat" print("******八皇后问题的解决方法******") def next_col(current, n=8): length = len(curr

用遗传算法解八皇后问题

此算法收敛速度还可以,基本在1万代之内就能找到解 主程序 clear; clc; %% %八皇后问题,8X8的棋盘上,放置8个皇后,使之两两都不能攻击 %初始的状态,随机在棋盘上放置8个皇后,每列放一个 n = 8; %8皇后 %% %用遗传算法计算 %先随机获得几个个体,形成一个种群 %这个种群有10个个体 No_of_people = 10; people = randi(n,[No_of_people,n]); %计算每个初始种群的h值 people_h = ones(No_of_peop

回溯算法解八皇后问题(java版)

八皇后问题是学习回溯算法时不得不提的一个问题,用回溯算法解决该问题逻辑比较简单. 下面用java版的回溯算法来解决八皇后问题. 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. 思路是按行来规定皇后,第一行放第一个皇后,第二行放第二个,然后通过遍历所有列,来判断下一个皇后能否放在该列.直到所有皇后都放完,或者放哪