算法-回溯法初探-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是皇后的排列

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

2,输入为8时皇后的排列(只显示了部分)
《算法-回溯法初探-n皇后问题》

小结:

做出来了

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

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

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

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

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

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

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

既然知道了,就努力吧

    原文作者:回溯法
    原文地址: https://blog.csdn.net/hexiaole1994/article/details/51166073
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞