理解使用递归及堆栈的算法处理八皇后问题

八皇后算法,是大多数计算机编程教程讲解到递归部分,几乎必讲的一个经典算法题,甚至在许多程序员职位面试中,都出现了这道题目。知乎上有个主题就是用10行代码,解决八皇后问题。本人刚刚转行软件,目前研读到递归这一块内容。现在把自己的粗浅理解写下来。
首先是基础知识:

  • C语言,这个我想只要工科类的学生,肯定学过,有基本数据结构,三种控制流,输入,输出知识足够。现在有些人,使用C++的环境来写不包含对象的C程序,这会简化C语言在输入输出方面的繁琐学习,值得推荐。而C++的特点是,在C语言的基础上加入面向对象的支持,也就是“类”,说白了就是把原来的结构体里面加入与这些数据结构相关的函数。而把面向对象拆分开,不写成“类”,在某些情况下,可以更好地理解程序的结构。本文在网友分享的八皇后程序基础上,修改为比较好理解的结构化程序。
  • 函数。这个主要是理解函数调用的原理,重点是每次调用函数,都会开辟一片新的内存空间,函数参数都会被复制一次。
  • 递归。递归的调用可以用函数调用来理解,必须有一个“结束”条件,否则容易造成程序死循环。
  • 栈。主要是“后入先出”的思想。如何用数组或者链表来创建栈

有了基础知识就能来看看八皇后算法了。
首先是声明一个二维数组,保存棋盘信息,

#define StackSize 8 /*最多放8个皇后*/
int queen[StackSize][StackSize]={0}; /*棋盘*/

然后声明一个大小为8的一维数组,存储皇后的行(列)位置,这里是一个栈,top为栈顶指针。

int top=-1;                          /*栈顶指针*/ 
int data[StackSize];                  /*存储皇后位置*/ 

摆放皇后函数如下:

/*放N皇后的递归函数*/
void Place(int row)//摆放皇后
{
    bool Judgement();//位置是否合法
    void SeqStack();//关键点1:每一次摆放都会初始化空栈,即栈顶指针=-1
    void Output();//输出结果
    for (int col=0;col<StackSize;col++)//结束条件为col=8
    {
        Push(col);//将合法位置入栈
        if (Judgement()) //判断位置合法
        {
            if (row<StackSize-1) //若还没有放到第八个皇后,则递归进行下一个皇后的放置
                PlaceQueen(row+1);                                    
            else
            {             //摆放完8个皇后,输出结果
                ans++;  //解数加1
                Output(); //打印成功的棋盘
            }
        }
        Pop();//若不符合条件则出栈                                                                    
    }
}

以4皇后为例,分析main函数调用place摆放函数过程。place(0),也就是在第0行放一个皇后,data栈将“0”入栈。此时棋盘信息和栈内容如下:
这里写图片描述
这个位置是合法的,所以继续调用place(1),也就是在第1行放一个皇后,data栈将“0”入栈,判断发现(1,0)这个位置不合法(在皇后同一行),于是将“0”出栈。以此类推,(1,1)也不合法,(1,2)合法,将2入栈,继续调用place(2).经判断第2行没有合法的摆放位置。程序返回到place(1),并且出栈。返回到place(0),出栈。那此时col的值加1变为了1,也就是这次就要在(0,1)这个位置放第一个皇后.给出调用place(1)~place(3)之后的棋盘信息和栈空间
这里写图片描述

皇后已经摆了4个,就可以调用output函数,打印棋盘信息同时解变量值加1.出栈程序回到place(0),col加1变为2,继续判断。以此类推。

还有一个难点,是判断位置合法性的函数。八皇后游戏规定,皇后可以吃掉“同行、同列、对角线、斜对角线”上的棋子,那么8个皇后必然是不同行不同列的,这个可以用下面代码判断

data[top]==data[i]

就是说,假设现在place(0)将(0,4)入栈,递归调用place(1),如果将(1,4)入栈,那就是同一行同一列,必须舍弃,这里data[top]=4,就是刚入栈的(1,4)信息,data[0]=4就是原来栈中所存的皇后位置信息(0,4),二者相等,位置不合法。
这里写图片描述
而对角线的判断,可以用下面代码

abs(data[top]-data[i]))==(top-i)

top-i的值就是皇后之间的非法相对距离,值为0、1、2….,也就是同一条对角线或者斜对角线。data[top]-data[i]为2个皇后的实际的相对距离。比如假设现在place(0)将(0,3)入栈,递归调用place(1),如果将(1,2)入栈,那就是在右上角的对角线上,必须舍弃,这里data[top]=2,就是刚入栈的(1,2)信息,data[0]=3就是原来栈中所存的皇后位置信息(0,3),二者之差=1,就是相对距离为1,位置不合法。
这里写图片描述

如果上面递归调用place(1),将(1,0)入栈,那么相对距离变为2-0=2,不是相差一层(top-i=1),那就合法
这里写图片描述


附上完整八皇后C++代码

#include <iostream>
#include<cmath>
using namespace std;
#define StackSize 8 /*最多放8个皇后*/
int queen[StackSize][StackSize]={0}; /*棋盘*/
int ans=0;                           /*解数*/
int top=-1;                          /*栈顶指针*/ 
int data[StackSize];                  /*存储皇后位置*/ 

/*入栈*/ 
void Push(int x)                                          //入栈操作
{
    top++;                                                           //栈顶指针上移
    data[top]=x;
}
/*出栈*/ 
void Pop()                                              //出栈操作
{
    top--;                                                           //栈顶指针下移
}

void SeqStack(){top=-1;}                                              //构造函数,初始化空栈

/*放N皇后的递归函数*/
void PlaceQueen(int row)                                //在栈顶放置符合条件的值的操作,即摆放皇后
{
    bool Judgement();
    void SeqStack();                                    //关键点1:每一次摆放都会初始化空栈
    void Output();
    for (int col=0;col<StackSize;col++)                              //穷尽0~7,即穷尽列
    {
        Push(col);
        if (Judgement())                                             //判断摆放皇后的位置是否安全
        {
            if (row<StackSize-1)                                     //若还没有放到第八个皇后,则进行下一个皇后的放置
                PlaceQueen(row+1);                                    
            else
            {
                ans++;                                               //解数加1
                Output();                                            //打印成功的棋盘
            }
        }
        Pop();                                                       //若不符合条件则出栈        
    }
}

/*关键点2:判断合法性*/
bool Judgement()
{
    for(int i=0;i<top;i++)                                           //依次检查前面各行的皇后位置
        if(data[top]==data[i]||(abs(data[top]-data[i]))==(top-i))    //判断是否在同一列同一斜线
            return false;                                            /*第一个条件,使每次入栈的数字不同层(行)*/   
    return true;                                                     /*top-i的值就是皇后之间的非法相对距离,值为0、1、2....,也就是同一条对角线或者斜对角线。data[top]-data[i]为2个皇后的实际的相对距离。*/  
}

/*输出棋盘*/ 
void Output()                                          //将栈的数组形式打印成棋盘形式
{
    cout<<"NO."<<ans<<":"<<endl;                                    
    for(int i=0;i<StackSize;i++)
    {
        for(int j=0;j<data[i];j++)
            cout<<"- ";                                             //不放置处打印“-”
        cout<<"Q";                                                  //放置处打印“Q”
        for(int j=StackSize-1;j>data[i];j--)
            cout<<" -";
        cout<<endl;                                                 //换行
    }
    cout<<endl;
}
int main()
{
    PlaceQueen(0);                                        //从栈底开始赋值
    cout<<"the total number of solutions is:"<<ans<<endl;       //输出摆放方法的总数
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值