八皇后/N皇后问题 C++代码+解析(回溯与暴力法对比)

八皇后/N皇后问题 C++代码+解析

目的:掌握回溯法算法思想,并能利用回溯法算法思想解决实际问题。

任务:在 8×8 的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处 在同一行、同一列或者同一对角斜线上。下图中的每个黑色格子表示一个皇后,这就是一种 符合条件的摆放方法。请求出总共有多少种不同的摆法。

**皇后的位置条件:**皇后可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。

判断皇后位置解决办法:
首先对于斜线上的元素满足:
①左上到右下对角线上xy差恒定
②右上到左下对角线上xy和恒定
所以我们可以设置两个数组,zhu和fu(取自主斜线副斜线线首字拼音)。分别用来保存已经放入皇后的坐标的差与和。
还有就是对当前列进行判断,该列上是否已经有皇后。这个判断比较简单,可以设置一个位置position数组,记录每个皇后的位置,扫描即可。

如何计算算法的执行时间:因为要观察算法执行完成的时间,故我使用了clock()函数(其被ctime头文件收录)用来对算法执行的一段时间进行测量。通过设置两个clock_t类型的变量,也就是long类型,将他们放在算法代码的上下两端,最后用末减初得出的结果输出算法时间。

解题流程:
暴力法

暴力法思想极为简单,由于棋盘为NN的,且不允许同一行有两个及以上皇后,所以,皇后应该为每行一个。我通过深度遍历枚举的方式,获得每行一个的所有皇后出现的可能性,然后对每一种可能进行判断是否为需要的结果。
容易看出,该算法时间复杂度极高。单纯的所有位置的组合就有N^N次方种。还有判断是否合适需要对每种可能进行判断,因为我使用了哈希表,所以判断的代价较低,对于每个的判断有O(1)的复杂度
回溯法:
大致思想为,设置一个N
N的vector,用于保存一个答案的结果。还有一个保存最终所有解的数据结构,使用vector<vector>保存所有解。
我们按照行优先遍历的方式。从[0][0]号位置开始,如果在某一行找到了一个可以放置皇后的位置,记录该行的位置,并且更新相应的三个位置数组(zhu,fu,position,以后用三个位置数组代替)那么进入下一行进行判断。如果在某行没有找到位置,那么意味着前面发生了错误,需要返回上一行进入该行的位置,将保存的上一行皇后的位置从三个位置数组中删除,接着判断上一行下一个未判断的空位。
如果在最后一行找到了位置,那么意味着找到了一个解,接着保存该解。并且删除三个位置数组的位置,回溯至上一级,返回上一行对未判断的位置继续进行求解。直至对第一行所有位置扫描一遍后,找到所有解为止。

定义的数据结构的功能:
在这里插入图片描述

函数的功能:
在这里插入图片描述

两种算法的伪代码:

//回溯法伪代码
bool  DFS(int x,int y,int n)if(x>=n||y>=n)    //越界退出
   return false
for i←0 to n-1 do
  if(!isok(x,i))     //如果(x,y)位置不能放皇后则判断下一个位置,如果可以isok函数会更新三个位置数组的信息
     continue
  if(x==n-1)     ///遍历到了最后一行,且最后一行也放上了皇后,成功找到
     return true
if(DFS(x+1,0,n))     //DFS返回true,找到一个答案,需要保存答案
   get_ans()       //保存答案
   pop_array()     //放弃最后一行的答案,回溯至上一行,看看还有没有其他答案。
pop_array()     //在某一行没有适当的位置时,需要回溯。或者已经找到最终答案,对倒数第二行及以上行没有判断的位子进行重新判断。需要弹出最后加入的元素
return false      //如果该行没有适合的,返回失败



//暴力法伪代码,获取全部可能方案
void DFS2(int x,int y,int n):
if(x>=n||y>=n)    //越界退出
   return
    position.push_back((x,y))    //保存当前(x,y)
if(x==n-1)     //找到了一组可能解
   if(isok_method2(position))    //如果该组解是符合题意的解,就保存该解
          result.push_back(position)      
       
   position.pop_back()//更新位置(最后一行返回的位置)
   return
row=x+1
for i←0 to n-1 do:    //从该行的第0个到n-1个开始
   DFS(row,i,n)
   if(row!=n-1)
      position.pop_back()     //删除position(x,y)

附录,源代码:


#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#include<ctime>
#include<map>
#include <utility>
using namespace std;
class NQueens {
public:
    vector<vector<string>> result;  //结果,皇后的位置用Q表示,其他用‘.’,每N个元素为一组答案
    vector<vector<pair<int, int>>> TEMP;
    vector<pair<int, int>> position;//皇后的位置
    vector<int> zhu;//左上到右下对角线上xy差恒定
    vector<int> fu;//右上到左下对角线上xy和恒定
    int N;//皇后数量

    void solveNQueens(int n) {

        N = n;

        if (n == 1)   //不需要进入其他行判断,直接返回结果
        {
            vector<string> temp;
            temp.push_back("Q");
            result.push_back(temp);
            return;
        }

        for (int i = 0; i < n; i++)  //第一行从左到右都试试
        {
            zhu.push_back(-i);
            fu.push_back(i);
            pair<int, int> temp(0, i);
            position.push_back(temp);
            DFS(1, 0);
            //清空三个数组
            pop_array(false);
        }

    }

    
    void method2_solveNQueens(int n)   //暴力解出全部可能排序,排列方法,所有结果存入result中
    {
        N = n;
        if (n == 1)  //n==1特殊的情况,直接返回结果即可
        {
            vector<string> temp;
            temp.push_back("Q");
            result.push_back(temp);
            return;
        }

        for (int i = 0; i < n; i++)//获得N个皇后所有可能的位置,每行1个。
        {
            DFS2(0, i);
            position.pop_back();
        }

        int count = 0;  //删除的时候result会变化,所以设置一个count用来同步TEMP和result
        vector<vector<string>>::iterator it;
        for (int i = 0; i < TEMP.size(); i++)  //对每种可能进行判断,看其是否为答案
        {
            if (!isok_method2(TEMP[i]))   //不是解就在得到的可能中删除
            {
                it = result.begin();
                it = i - count+it;    //TM的,it+i-count就不对,有可能先算到it+i就越界了,shabi编译器
                result.erase(it);
                count++;
            }
        }

        
    }




    bool DFS(int x, int y) {    //行优先遍历,在该行找到了皇后的位置便进入下一行

        if (x >= N || y >= N)//越界
            return false;

        for (int i = 0; i < N; i++)   //对该行遍历
        {
            if (!isok(x, i))//如果位于一列或者同一对角线则该位置不能放置,换个位置,如果可以的话,就更新三个数组
                continue;

            //进入这里证明在该行找到了合适的位置,判断是否完成

            if (x == N - 1)//遍历到了最后一行,且最后一行也放上了皇后,成功找到
                return true;

            //到这里表示在该行找到了合适位置,且未完成,进入下一行判断

            if (DFS(x + 1, 0))  //从下一行第一个开始,x行y列
            {
                //若到了最后一行结束时,会返回true,从而可以调用get_ans得到答案。(只有获得一个最终答案时才会进入到这里)
                get_ans();   //保存答案
                pop_array(true);  //放弃最后一行的答案,回溯至上一行,看看还有没有其他答案。
            }

            //在某一行没有适当的位置时,需要回溯。或者已经找到最终答案,对倒数第二行及以上行没有判断的位子进行重新判断。
            pop_array(true);
        }
        //如果该行没有适合的,返回失败
        return false;
    }

    //方法二的DFS
    void DFS2(int x, int y)   //找到所有可能位置
    {
        if (x >= N || y >= N)  //越界
            return;

        pair<int, int> temp(x, y);
        position.push_back(temp);

        if (x == N - 1)   //找到一种可能
        {
            get_ans();   //转为字符串方便显示
            TEMP.push_back(position);    //TEMP记录每个可能的位置
            position.pop_back();   //更新位置(最后一行返回的位置)
            return;
        }
        int row = x + 1;
        //从该行的第0个到n-1个开始
        for (int i = 0; i < N; i++)
        {
            DFS2(row, i);
            if (row != N - 1)
                position.pop_back();  //删除position(x,y)
        }

    }


    map<int, bool> hash1;
    map<int, bool> hash2;

    //对方法二找到每个结果进行判断,是否符合要求
    bool isok_method2(vector<pair<int, int>> &temp)
    {
        hash1.clear();  //先清空
        hash2.clear();
        for (int i = 0; i < N; i++)  //判断是否有在相同列上的
        {
            if(hash1.count(temp[i].second))
                return false;
            hash1.insert(map<int, int>::value_type(temp[i].second, 0));
        }
        hash1.clear();
        for (int i = 0; i < N; i++)  //判断斜线上的
        {
            if(hash1.count(temp[i].first-temp[i].second)|| hash2.count(temp[i].first + temp[i].second))  //找到了在对角线上已经存在的元素,则返回false
                return false;
            hash1.insert(map<int,int>::value_type(temp[i].first - temp[i].second,0));
            hash2.insert(map<int, int>::value_type(temp[i].first + temp[i].second,0));
        }


        return true;
    }



    //判断位置是否可以放置皇后,并且更新相应数组
    bool isok(int& x, int& y)
    {
        for (int i = 0; i < zhu.size(); i++)    //是否在两个方向的斜线上有皇后
            if ((x - y) == zhu[i] || (x + y) == fu[i])
                return false;

        for (int i = 0; i < position.size(); i++)   //判断一列上是否有
            if (y == position[i].second)
                return false;

        zhu.push_back(x - y); fu.push_back(x + y);    //该位置可以放置皇后,更新zhu fu和position数组
        pair<int, int> temp(x, y);
        position.push_back(temp);

        return true;
    }

    //获得答案,每一个position对应一个解,将其转为字符串可视化的形式
    void get_ans()
    {
        vector<string> temp;
        string tmp;
        for (int i = 0; i < position.size(); i++)
        {
            for (int i = 0; i < N; i++)
                tmp += '.';
            tmp[position[i].second] = 'Q';
            temp.push_back(tmp);
            tmp = "";
        }
        result.push_back(temp);
    }

    //对三个数组撤销操作
    void pop_array(bool flag)
    {
        if (flag==true) {    //为1表示撤销上一级操作
            position.pop_back();
            zhu.pop_back(); fu.pop_back();
        }
        else   //清空
        {
            zhu.clear();
            fu.clear();
            position.clear();
        }
    }
    //输出答案
    void print_result()
    {
          //输出结果一的答案
            cout << "**********************" << endl;
            cout << "答案共有" << result.size() << "个,如下:" << endl;
            cout << "**********************" << endl;

            for (int i = 0; i < result.size(); i++)
            {
                cout << "答案" << i + 1 << "如下:" << endl;
                for (int j = 0; j < N; j++) {
                    cout << result[i][j] << endl;
                }
                cout << "**********************" << endl;
            }
        
        cout << "输出结束!!!!"<<endl;
    }
};

int main()
{
    cout << "请输入皇后的个数:";
    int n;
    cin >> n;
    clock_t start, end;;
    NQueens queen,queentwo;   

    //回溯
    start = clock();
    queentwo.solveNQueens(n);// queentwo.print_result();
    end = clock();

    cout << "当皇后数量为" << n << "个时采用回溯解法时间为:" << (double)(end - start) / CLOCKS_PER_SEC << "S" << endl;

    //暴力
    start = clock();
    queen.method2_solveNQueens(n);
   // queen.print_result();
    end = clock();

    cout << "当皇后数量为" << n << "个时采用暴力解法时间为:" << (double)(end - start) / CLOCKS_PER_SEC << "s" << endl;

    cout << endl << "***************" << endl;
    cout << "回溯法的结果共有:" << queentwo.result.size() << "种" << endl<<"每种情况为:";
    queentwo.print_result();
    cout << endl <<"***************"<< endl;
    cout << "暴力法的结果共有:" << queen.result.size() << "种" << endl << "每种情况为:" << endl;
    queen.print_result();

    cout << endl << "***************" << endl;

    return 0;
}
  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值