数据结构(6)栈和队列->栈的应用之八皇后问题

  • 整理数据结构学习笔记
  • 基于C++

一、八皇后问题

经典的八皇后问题,即

  • 在一个8*8的棋盘上放8个皇后
  • 任意2个皇后不能处于同一行
  • 任意2个皇后不能处于同一列
  • 任意2个皇后不能处于同一对角线上
  • 输出所有可能的摆放情况

此问题也可推广为N*N大小棋盘上的N皇后问题

二、回溯法

回溯法是解八皇后问题的一个常用算法,和深度优先搜索很类似,摘录其定义如下:

  • 回溯法(探索与回溯法):
    是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
  • 基本思想:
    在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束

根据以上描述,可以看出回溯法适合用递归实现

三、一个小例子

下面看一个递归回溯法的例子,理解了这个之后八皇后问题就很简单了。

1、问题

输入正整数n,输出1到n的全排列

2、求解方法

  • 设全局标记flag数组,flag[i]标记数值(i+1)有没有被计入当前全排列
  • 递归函数定义为void dfs(int num),num记录当前计入全排列的数个数,递归边界条件为num==n。在递归函数中遍历1到n,判断其对应的flag标志,若其没有加入全排列则加入并修改flag值,调用dfs(num+1)。调用后紧接着恢复num和flag值,以保证回溯到这里时各变量情况和第一次到达此处时一样
  • 若递归到边界条件,输出当前全排列,然后直接return

3、递归图示

以输出3的全排列为例,递归流程如下
在这里插入图片描述
输出如下
在这里插入图片描述
代码如下,请结合流程图仔细分析递归流程

//输出1 2 3 ... n的全排列 
#include<iostream>
#include<cstdlib>
using namespace std;


int *a;		//a[]用来存排列
int *flag;	//flag[0]~flag[n-1]表示1~n有没有被排进a[] 
int n;

//可以看做回溯,也可看做深度优先搜索
void dfs(int num)
{
	//达到边界状态(填满),递归结束 
	if(num==n)	
	{
		for(int i=0;i<n;i++)
			cout<<a[i]<<" ";
		cout<<endl;
		return; 
	}	
	
	else
	{
		for(int i=0;i<n;i++)
		{
			if(flag[i])
			{
				a[num++]=i+1,flag[i]=0;	//遍历搜索没有排入a[]的加入a[]中 
				dfs(num);             	//找下一个数 
				num--,flag[i]=1;        //回溯时还原标记,这样才能把i+1填入后面的位 
			}	
		}
	}	
} 

int main()
{
	cin>>n;
	a=(int*)malloc(n*sizeof(int));		
	flag=(int*)malloc(n*sizeof(int));	
	
	//标志初始化
	for(int i=0;i<n;i++)
		flag[i]=1;
		
	//递归开始				
	dfs(0);	
} 

四、八皇后问题的求解

1、数据结构设计

用栈的结构来存储棋盘,可以进行如下转换:
将每一行中皇后位置存入栈中,棋盘第一行对应栈顶,第八行对应栈底
在这里插入图片描述
把栈中元素看做一个排列,问题转换为:在1~8的全排列中,选择符合八皇后要求的排列,这一转换十分重要

//栈结构
typedef struct
{
	int *base;//指向最下面元素 
	int *top;//指向真正顶部再上一个 
	int stacksize;
}SqStack;

2、判断条件

每往栈中压入一个新数(代表当前最上一行皇后位置)使其成为栈顶元素后,进行如下判断:

  • 在同一行:
    根据上述栈的建立方式,不可能在同一行出现两个皇后
  • 在同一列:
    从栈底遍历至栈顶 for(int i=0;i<StackLength-1;i++),判断 *(s.base+i)==top,或者利用上面全排列例子中的方法,设全局flag数组标记
  • 在同一主对角线:
    从栈底遍历至栈顶 for(int i=0;i<StackLength-1;i++),判断*(s.base+i)+i+1==top+L棋盘中表现为行号+列号相等
  • 在同一副对角线:
    从栈底遍历至栈顶 for(int i=0;i<StackLength-1;i++),判断*(s.base+i)-i-1==top-L棋盘中表现为行号-列号相等

若上述任意情况出现,判断为当前行皇后位置不可行,需进行回溯

3、程序代码

//避免同列错误,可以给每个列设标志,也可在错误判断时一并拒绝
//此程序中两种都写了,注释掉一种也可正常工作 

#include<iostream>
#include<cstdlib>
using namespace std;

#define STACK_INIT_SIZE 10

#define  OK true
#define  ERROR false 
#define OVERFLOW -1

int flag[8]={0}; 
int cnt=0;
typedef struct
{
	int *base;//指向最下面元素 
	int *top;//指向真正顶部再上一个 
	int stacksize;
}SqStack;


bool InitStack(SqStack &s)
{
	s.base = (int *)malloc(STACK_INIT_SIZE*sizeof(int));
	if(!s.base)
		exit(OVERFLOW);		
	s.top=s.base;
	s.stacksize=STACK_INIT_SIZE;
	return OK;
}


bool StackEmpty(SqStack s)
{
	if(s.top==s.base)
		return true;
	else
		return false;
}


int StackLength(SqStack s)
{
	return s.top-s.base;
}

bool GetTop(SqStack s,int &e)
{
	if(StackEmpty(s))
		return ERROR;
	e=*(s.top-1);
	return OK;
}

bool push(SqStack &s,int e)
{
	if(s.top-s.base>=s.stacksize)
	{
		s.base=(int *)realloc(s.base,(s.stacksize+STACKINCREMENT)*sizeof(int));
		if(!s.base)
			exit(OVERFLOW);	
		
		s.top=s.base+s.stacksize;
		s.stacksize+=STACKINCREMENT;	
	}
	
	*s.top++=e;
	return OK;
}

bool pop(SqStack &s,int &e)
{
	if(s.top==s.base)
		return ERROR;
	e=*(s.top-1);
	s.top--;
	return OK;
}


//----------------------------------------------
void ShowElem(SqStack s)
{
	int *p=s.base;
	while(p<s.top)
		cout<<*(p++)<<" ";
	cout<<endl;
}

void ShowStack(SqStack s)
{
	if(StackEmpty(s))
		cout<<"空栈,长度"<<StackLength(s)<<endl;
	else
		cout<<"皇后列位置(第8行到第1行):", ShowElem(s);
	cout<<endl;
}

bool avalible(SqStack s)
{
	int L=StackLength(s);
	int top;
	GetTop(s,top);
	for(int i=0;i<L-1;i++) 
		if(*(s.base+i)+i+1==top+L || *(s.base+i)-i-1==top-L || *(s.base+i)==top )//最后一个是通过用栈存储棋盘的特性来避免同列错误 
			return ERROR;
	return OK;
}


void insert(SqStack &s,int PuttedNum)
{
	int p;
	if(PuttedNum==8)
	{
		cnt++;
		ShowStack(s);
		return;
	}
	else
	{
		for(int j=0;j<8;j++)
		{
			if(flag[j]!=0)//用标记避免同列错误 
				continue;
			else
			{
				push(s,j+1);
				if(avalible(s))
				{
					PuttedNum++;
					flag[j]=1;
					insert(s,PuttedNum);		
					flag[j]=0;
					pop(s,p);
					PuttedNum--;
				}
				else
					pop(s,p);
			}
		}	
	}
}

int main()
{
	int num=0;
	SqStack Stack;
	//初始化 
	InitStack(Stack);
	//输出提示语 
	cout<<"从前往后表示第8行到第1行中每行皇后所在列"<<endl; 
	//递归开始 
	insert(Stack,num);
	cout<<"共"<<cnt<<"种"<<endl;	
	return 0; 
}

五、N皇后问题

算法思想和八皇后完全类似,把所有长度可能变的数组之类都改动态申请就行了

#include<iostream>
#include<cstdlib>
#include<cstring>
using namespace std;

//#define STACK_INIT_SIZE 10
#define STACKINCREMENT 10

#define  OK true
#define  ERROR false 
#define OVERFLOW -1

int NumOfQueen;
int cnt=0;
typedef struct
{
	int *base;//指向最下面元素 
	int *top;//指向真正顶部再上一个 
	int stacksize;
}SqStack;

//1 
bool InitStack(SqStack &s)
{
	s.base = (int *)malloc(NumOfQueen*sizeof(int));
	if(!s.base)
		exit(OVERFLOW);		
	s.top=s.base;
	s.stacksize=NumOfQueen;
	return OK;
}

//4 
bool StackEmpty(SqStack s)
{
	if(s.top==s.base)
		return true;
	else
		return false;
}
//5 
int StackLength(SqStack s)
{
	return s.top-s.base;
}
//6 
bool GetTop(SqStack s,int &e)
{
	if(StackEmpty(s))
		return ERROR;
	e=*(s.top-1);
	return OK;
}
//7 
bool push(SqStack &s,int e)
{
	if(s.top-s.base>=s.stacksize)
	{
		s.base=(int *)realloc(s.base,(s.stacksize+STACKINCREMENT)*sizeof(int));
		if(!s.base)
			exit(OVERFLOW);	
		
		s.top=s.base+s.stacksize;
		s.stacksize+=STACKINCREMENT;	
	}
	
	*s.top++=e;
	return OK;
}
//8 
bool pop(SqStack &s,int &e)
{
	if(s.top==s.base)
		return ERROR;
	e=*(s.top-1);
	s.top--;
	return OK;
}


//----------------------------------------------
void ShowElem(SqStack s)
{
	int *p=s.base;
	while(p<s.top)
		cout<<*(p++)<<" ";
	cout<<endl;
}

void ShowStack(SqStack s)
{
	if(StackEmpty(s))
		cout<<"空栈,长度"<<StackLength(s)<<endl;
	else
		cout<<"皇后列位置(最后行到第1行):", ShowElem(s);
	cout<<endl;
}

bool avalible(SqStack s)
{
	int L=StackLength(s);
	int top;
	GetTop(s,top);
	for(int i=0;i<L-1;i++) 
		if(*(s.base+i)+i+1==top+L || *(s.base+i)-i-1==top-L || *(s.base+i)==top ) 
			return ERROR;
	return OK;
}


void insert(SqStack &s,int PuttedNum,int flag[])
{
	int p;
	if(PuttedNum==NumOfQueen)
	{
		cnt++;
		ShowStack(s);
		return;
	}
	else
	{
		for(int j=0;j<NumOfQueen;j++)
		{
			if(flag[j]!=0)
				continue;
			else
			{
				push(s,j+1);
				if(avalible(s))
				{
					PuttedNum++;
					flag[j]=1;
					insert(s,PuttedNum,flag);		
					flag[j]=0;
					pop(s,p);
					PuttedNum--;
				}
				else
					pop(s,p);
			}
		}	
	}
}


int main()
{
	int num=0;	
	SqStack Stack;
	//初始化 
	InitStack(Stack);
	
	cout<<"皇后个数:"<<endl;
	cin>>NumOfQueen;
	int *flag;
	flag=(int *)malloc(NumOfQueen*sizeof(int));
	for(int i=0;i<NumOfQueen;i++)
		flag[i]=0;	
	
	cout<<"从前往后表示最后一行到第一行中每行皇后所在列"<<endl; 
	insert(Stack,num,flag);
	cout<<"共"<<cnt<<"种"<<endl;	
	return 0; 
}
  • 3
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云端FFF

所有博文免费阅读,求打赏鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值