算法-回溯法初探-n皇后问题

问题描述:

这周的数据结构作业要求写一个程序判断输入为n的所有皇后的情况, 皇后大致就是在一个n*n的棋盘上所有不同行列及不同对角线的格子排列

提示用书本上求解迷宫时用到的回溯法,也就是用到一个栈来保存当前满足的皇后,若进行不下去则回溯

采用C语言实现

代码:

1,文件 BetterQueen.h

里面主要定义了一些程序要用到的数据结构和函数接口

#ifndef BETTERQUEEN_H_INCLUDED
#define BETTERQUEEN_H_INCLUDED
#include <stdio.h>
#include "BetterQueen.h"
#define MAXSIZE 21  /* 因为题目中要求输入不超过20,因在一开始进栈一个不存在棋盘上的皇后(作为循环结束的标志),故栈的最大长度为21 */
/*******************************************************
定义"皇后"放置的格子类型
row标示了在一个棋盘中行的坐标
column标示了列的坐标(在一个4*4的棋盘中,行、列均从0开始计数)
next_column标示了下一个可能放置的皇后的列坐标(在一个4*4的棋盘中(next_column>=0 && next_column<=n-1))
*******************************************************/
typedef struct
{
    int row;
    int column;
    int next_column;
}Cell;

/*******************************************************
定义了存放"皇后"的栈
它是求解这个问题的一个关键数据结构
当且仅当当前栈顶元素的next_column坐标有效时(即棋盘对应的二维数组中该格子的列坐标不为-1时),next_column标示的下一个皇后才可以进入此栈
*******************************************************/
typedef struct
{
    Cell queen[MAXSIZE];
    int top;
}QueenStack;

/*******************************************************
定义了存放棋盘状态(即二维数组中所有元素的值)的链栈
当且仅当需要修改棋盘二维数组值前将当前二维数组值存入链栈
(需要修改棋盘状态是指在皇后进栈(皇后栈)后,需要修改当前二维数组的值,以确保下一个皇后不和当前进栈(皇后栈)皇后不同行、列以及不同对角线)
当且仅当有皇后退栈(皇后栈)时,将棋盘状态栈中栈顶元素同时退栈返还给二维数组
*******************************************************/
typedef struct ChessBoardStack
{
    int* data;
    struct ChessBoardStack* next;
}ChessBoardNode;

/*******************************************************
初始化皇后栈函数
*******************************************************/
QueenStack* init_queen_stack()
{
    QueenStack* qs=(QueenStack*)malloc(sizeof(QueenStack));
    qs->top=-1;
    return qs;
}

/*******************************************************
初始化棋盘状态栈函数
*******************************************************/
ChessBoardNode* init_chessboard_stack()
{
    ChessBoardNode* cbs=(ChessBoardNode*)malloc(sizeof(ChessBoardNode));
    cbs->data=NULL;
    cbs->next=NULL;
    return cbs;
}

/*******************************************************
皇后进栈函数
进栈后next_column值置为-1(表示下一个皇后从头开始找)
*******************************************************/
void push_queen(QueenStack* qs,int r,int c)
{
    ++(qs->top);
    qs->queen[qs->top].row=r;
    qs->queen[qs->top].column=c;
    qs->queen[qs->top].next_column=-1;
}

/*******************************************************
判断皇后栈是否为空
*******************************************************/
int queen_stack_is_empty(QueenStack* qs)
{
    return qs->top==-1;
}

/*******************************************************
打印一种皇后的排列
*******************************************************/
void print_result(QueenStack* qs,int n)
{
    int i,j;
    for(i=1;i<=n;i++)
    {
        for(j=0;j<n;j++)
        {
            if(j!=qs->queen[i].column)
                printf("%d ",j+1);
            else
                printf("  ");
        }
        printf("\n");
    }
     printf("\n");
}

/*******************************************************
出栈一个皇后
*******************************************************/
void pop_queen(QueenStack* qs)
{
    qs->top--;
}

/*******************************************************
当前棋盘状态进栈
*******************************************************/
void push_chessboard(ChessBoardNode* cbs,int* cb,int n)
{
    ChessBoardNode* new_node=(ChessBoardNode*)malloc(sizeof(ChessBoardNode));
    new_node->data=(int*)malloc(sizeof(int)*(n*n));
    int* p=new_node->data;
    while(p<new_node->data+n*n)
        *p++=*cb++;
    new_node->next=cbs->next;
    cbs->next=new_node;
}

/*******************************************************
棋盘状态出栈
恢复上一个皇后时的状态
*******************************************************/
void pop_chessboard(ChessBoardNode* cbs,int* cb,int n)
{
    if(cbs->next==NULL)
        return;
    int* p=cbs->next->data;
    while(p<cbs->next->data+n*n)
        *cb++=*p++;
    ChessBoardNode* dn=cbs->next;
    cbs->next=dn->next;
    free(dn);
}

/*******************************************************
修改棋盘状态,使下一个皇后满足同先前皇后不同行列及对角线
*******************************************************/
void updata_chessboard(int n,int (*chessboard)[n],int r,int c)
{
    int i,j;
    chessboard[r][c]=-1;
    for(i=0;i<n;i++)
    {
        chessboard[r][i]=-1;
        chessboard[i][c]=-1;
    }
    for(i=0; i<n; i++)
        for(j=0; j<n; j++)
        {
            if(r!=i && c!=j)
                if((double)(r-i)/(double)(c-j)==-1 || (double)(r-i)/(double)(c-j)==1)
                    chessboard[i][j]=-1;
        }
}

/*******************************************************
销毁皇后栈
*******************************************************/
void destroy_queen_stack(QueenStack* qs)
{
    free(qs);
}

/*******************************************************
销毁棋盘状态栈
*******************************************************/
void destroy_chessboard_stack(ChessBoardNode* cbs)
{
    ChessBoardNode* p;
    while(cbs->next!=NULL)
    {
        p=cbs->next;
        free(cbs);
        cbs=p;
    }
    free(cbs);
}
#endif // BETTERQUEEN_H_INCLUDED

2,文件 main.c

实现对输入的n求解对应所有的皇后排列

/*
* Copyright (c) 2014, 烟台大学计算机与控制工程学院
* All rights reserved.
* 文件名称:main.cpp && BetterQueen.h
* 作    者:何小乐
* 完成日期:2016年 4 月 16 日
* 版 本 号:v1.0
*
* 问题描述:求解输入为n的皇后数
            皇后大致就是在一个n*n的棋盘上所有不同行列及不同对角线的格子排列
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include "BetterQueen.h"
void queen(int n);
int main()
{
    printf("Please input the number of Queens : ");
    int numOfQueens;
    scanf("%d",&numOfQueens);
    /* 不存在皇后数为2、3的情况,值为负数更为扯淡 */
    if((numOfQueens>1 && numOfQueens<4) || numOfQueens<0)
    {
        printf("input error!");
        exit(0);
    }
    queen(numOfQueens);
    return 0;
}

/*******************************************************
求解"皇后"可能排列的函数
大致思路:
1,先将一个不在棋盘的"皇后"压入皇后栈中,目的是为了中止循环
2,当栈不空时执行循环:
(1)  若栈顶元素的row值为n-1时(row从0~n-1,总共n个皇后),说明已经找到一个可能的皇后排列,
则输出这组结果。并且在输出完之后出栈一个皇后,为了遍历之后的可能情况
(2)  若(1)不成立则表示还没有找到一个可能的皇后排列,则从当前栈顶元素开始定位下一个皇后所在的行、列值,
找到行列值后对照棋盘当前状态(二维数组中各元素值)决定是否将此皇后压入栈。

当一个新皇后的行列值符合要求,在压入栈(皇后栈)前,先修改当前皇后栈顶皇后中指向下一个皇后的next_column值,然后将皇后压入栈中;
在修改棋盘状态前(修改棋盘状态是使二维数组中某些值为-1,为了让下一个皇后能满足与前面的皇后不同行列及对角线),先将当前棋盘状态压入栈中,
然后修改棋盘状态。

若新皇后所有可能的行列值都不满足要求,则表示前面的皇后不满足要求,执行退栈(皇后栈)操作,并且恢复修改前棋盘的状态。

(栈将在步骤1中压入栈的皇后的next_column值为n时中止循环,意味着已经将所有可能的情况遍历了)
*******************************************************/
void queen(const int n)
{
    int chessBoard[n][n]; /* 建立对应于皇后数的棋盘二维数组 */
    int *p=*chessBoard;
    /* 初始化棋盘,使所有值都为0(0表示此位置可以放置皇后,-1表示不可以放置) */
    while(p<*chessBoard+n*n)
        *p++=0;
    QueenStack* qs=init_queen_stack();
    ChessBoardNode* cbs=init_chessboard_stack();
    push_queen(qs,-1,-1);   /* 一开始进栈一个不存在棋盘上的皇后(作为循环结束的标志) */
    int flag,count_of_queen=0;
    int next_queen_row,next_queen_column,current_queen_next_column;
    while(!queen_stack_is_empty(qs))
    {
        if(qs->queen[qs->top].row==n-1)
         {
             count_of_queen++;
             print_result(qs,n);
             pop_queen(qs);
             pop_chessboard(cbs,*chessBoard,n);
         }
         flag=0;
         current_queen_next_column=qs->queen[qs->top].next_column;
         next_queen_row=qs->queen[qs->top].row+1;
         while(current_queen_next_column<n-1 && flag==0)
         {
             next_queen_column=++current_queen_next_column;
             if(chessBoard[next_queen_row][next_queen_column]==0)
                flag=1;
         }
         if(flag==1)
         {
             qs->queen[qs->top].next_column=current_queen_next_column;
             push_queen(qs,next_queen_row,next_queen_column);
             push_chessboard(cbs,*chessBoard,n);
             updata_chessboard(n,chessBoard,next_queen_row,next_queen_column);
         }
         else
         {
             pop_queen(qs);
             pop_chessboard(cbs,*chessBoard,n);
         }
    }
    printf("the count of Queens is : %d\n",count_of_queen);
    destroy_queen_stack(qs);
    destroy_chessboard_stack(cbs);
}

运行结果:

1,输入为4是皇后的排列

2,输入为8时皇后的排列(只显示了部分)

小结:

做出来了

在开始写了一个c++实现的版本,但是实在很疲劳的时候完成的,思路完全不是很清晰,第二天调试了两个小时才能运行

然后在看程序,完全没有耐心读下去,太杂了,类的定义也乱七八糟,然后想用C写一个干净点的,把一些操作封装在函数中,不想用类了

重新写的时候,思路很清晰,一点一点边测试边写,没像第一次写时一口气写完,最后很快就调试过了

回溯法,听起来不错,但是感觉还是没有掌握内涵,因为是参照书本上求解迷宫思路写的,所以很多想法还是在书本基础上进行改进

刚拿到问题的时候第一个想法就是,这怎么做的出来?,中间在还没有开始自主思考的时候一直想直接去网上看一个现成的算了,但是最后还是不允许自己这么做

在一点一点思考之后,想法越来越可以在自己能力范围内实现,挺好

前几天看到一句话,开始慌了,“算法是程序的灵魂”,因为自己懂的算法太少了

既然知道了,就努力吧

时间: 2024-11-13 16:48:25

算法-回溯法初探-n皇后问题的相关文章

ACM:回溯法,八皇后问题,素数环

(一)八皇后问题 (1)回溯法 #include <iostream> #include <string> #define MAXN 100 using namespace std; int tot = 0, n = 8; int C[MAXN]; void search(int cur) { if(cur == n) ++tot; //递归边界,只要走到了这里,所有皇后必然不冲突 else for(int i = 0; i < n; ++i) { int ok = 1; C

回溯法求解N皇后问题

问题描述: 在n*n格的棋盘上放置彼此不受攻击的n个皇后(按照国际象棋的规则),即任意两个皇后不能处在同一行或同一列或同一斜线上. 实现: /* *回溯法,N皇后问题 *author: [email protected] */ #include <iostream> #include <vector> #include <cmath> using namespace std; struct Point{ Point(int _x, int _y): x(_x), y(_

【基础算法】回溯法与八皇后问题

在国际象棋中,皇后是最强大的一枚棋子,可以吃掉与其在同一行.列和斜线的敌方棋子.比中国象棋里的车强几百倍,比她那没用的老公更是强的飞起(国王只能前后左右斜线走一格).上图右边高大的棋子即为皇后. 八皇后问题是这样一个问题:将八个皇后摆在一张8*8的国际象棋棋盘上,使每个皇后都无法吃掉别的皇后,一共有多少种摆法?此问题在1848年由棋手马克斯·贝瑟尔提出,岂止是有年头,简直就是有年头,82年的拉菲分分钟被秒的渣都不剩. 八皇后问题是典型的回溯法解决的问题,我们以这个问题为例介绍回溯法. 所谓回溯法

每天刷个算法题20160519:回溯法解八皇后

版权所有.所有权利保留. 欢迎转载,转载时请注明出处: http://blog.csdn.net/xiaofei_it/article/details/51502622 为了防止思维僵化,每天刷个算法题.已经刷了几天了,现在发点代码. 我已经建了一个开源项目,每天的题目都在里面: https://github.com/Xiaofei-it/Algorithms 绝大部分算法都是我自己写的,没有参考网上通用代码.读者可能会觉得有的代码晦涩难懂,因为那是我自己的理解. 最近几天都是在写一些原来的东西

回溯法求解八皇后问题---(初步、未优化)

首先介绍一下回溯算法: 定义来自<百度百科>......名字着很高大上,实际上就是试探法,逐步试错找到最终的可行解. 重要的一点是解空间通常是在搜索可行解过程中动态产生的,所以程序中通常利用到递归的算法,如后面介绍的八皇后问题.这点区别与于前段时间所写的模拟退火算法,模拟退火是首先确定解空间,然后以一定的概率接受当前发现的次优解,从而有更大的可能避免局部最优而得到全局最优. 简单介绍一下八皇后问题: 在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或

【回溯法】八皇后问题(递归和非递归)

先贴代码,分递归回溯法和非递归回溯法 递归回溯法,代码如下: // test.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> #include <stdio.h> using namespace std; int a[9] = {0}; int n = 8; int count = 0; bool check

暴力回溯法 解八皇后

国际象棋 八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行.同一列或同一斜线上,问有多少种摆法. public class _8Queen { //回溯法,暴力解8皇后 private static int ways = 0; //返回解法个数 public static int f8queen() { int[][] board = new int

回溯法与八皇后问题

tail recursion 函数在调用的时候,会提前创建一个栈空间,给传递的参数也分配空间,当函数结束返回上层函数的时候,一些局部变量需要从栈中弹出并恢复到调用子函数之前的值,返回到上一个函数调用子函数之前的现场.如果是尾递归,从子函数返回的时候这个函数同时也会结束了,所以没有必要恢复一些局部变量,直接把局部变量的栈空间删除.因此一个尾递归的函数根本不需要使用栈来给子函数变量空间,可以直接使用当前变量的值为新参数的值. backtracking 八皇后问题 用一个类Queens表示棋盘上现在的

回溯法:八皇后问题

八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行.纵行或斜线上.八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n.当且仅当 n = 1 或 n ≥ 4 时问题有解. 这个问题简化描述就是:在8x8的棋盘上放8颗子,要求它们[不在同一行][不在同一列][不在同一斜线]上. 我们可以定义一个数组position[8],positon