回溯问题

(1)幂集问题

问题:求含N个元素的集合的幂集。如对于集合A={1,2,3},则A的幂集为P(A)={{1,2,3},{1,2},{1,3},{1},{2,3},{2},{3},Ø}。

分析:幂集或为空集,或含A中1~N个元素。反之,集合A中的每一个元素,只有两种状态:属于幂集的元素集,不属于幂集的元素集。则求幂集P(A)的元素的过程可看成是依次对集合A中元素进行“取”和“舍”的过程,并且可以用一个状态树来表示。求幂集元素的过程即为先序遍历这颗状态树的过程。如图,先分为取“1”与不取“1”,再分为取“2”与不取“2”,最后分为取“2”与不取“3”。

状态树
状态树
#include <stdio.h>
#include <malloc.h>
#define ERROR 0
#define OK 1
typedef int ElemType;
typedef struct LNode {
	ElemType data;
	struct LNode *next;
} LNode,*LinkList;
//初始化
LinkList ListInit() {
	LNode *base=(LinkList)malloc(sizeof(LNode));
	base->data=0;
	base->next=NULL;
	return base;
}
//插入一个元素
int ListInsert(LinkList L,int i,ElemType e) {
	LNode *p,*s;
	int j=0;
	p=(LNode *)L;
	while(p&&j<i-1) {
		p=p->next;
		++j;
	}
	if(!p||j>i-1)
		return ERROR;
	s=(LNode *)malloc(sizeof(LNode));
	s->data=e;
	s->next=p->next;
	p->next=s;
	return OK;
}
//删除一个结点
int ListDelete(LinkList &L,int i,ElemType &e) {
	LinkList p=L,q;
	int j=0;
	while(p->next&&j<i-1) {
		p=p->next;
		++j;
	}
	if(!(p->next)||j>i-1)
		return ERROR;
	q=p->next;
	p->next=q->next;
	e=q->data;
	free(q);
}
//长度
int ListLength(LinkList L) {
	LinkList p=L;
	int j=0;
	if(!L)
		return ERROR;
	while(p->next) {
		p=p->next;
		++j;
	}
	return j;
}
//查找一个元素
int GetElem(LinkList L,int i,ElemType &e) {
	LNode *p=L;
	int j=0;
	while(p->next&&j<i) {
		p=p->next;
		++j;
	}
	if(!p||j>i)
		return ERROR;
	e=p->data;
	return OK;
}
//输出链表元素
void Display(LinkList L) {
	LNode *p=L;
	if(!(p->next)) {
		printf("NULL,");
		return;
	}
	else
		p=p->next;
	while(p) {
		printf("%d,",p->data);
		p=p->next;
	}
}
//求幂集
void PowerSet(int i,LinkList A,LinkList &B) {
	int k=0;
	ElemType e=0;
	if(i>ListLength(A)) {
		Display(B);
		printf("\n");
	}
	else {
		GetElem(A,i,e);
		k=ListLength(B);
		ListInsert(B,k+1,e);
		PowerSet(i+1,A,B);
		ListDelete(B,k+1,e);
		PowerSet(i+1,A,B);
	}
}
int main() {
	LinkList list=ListInit();//初始化
	LinkList list2=ListInit();//初始化
	ListInsert(list,1,1);//插入元素
	ListInsert(list,2,2);
	ListInsert(list,3,3);
	Display(list);//输出元素
	printf("\npower set is:\n");
	PowerSet(1,list,list2);//求幂集
}

(2)迷宫问题

分析:迷宫问题常用“试探和回溯”的方法,即从入口出发,顺某一方向向前探索,若能走通,则继续往前走;否则沿原路退回,换一个方向再继续探索,直至所有可能的通路都探索到为止,如果所有可能的通路都试探过,还是不能走到终点,那就说明该迷宫不存在从起点到终点的通道。

思路:1.从入口进入迷宫之后,不管在迷宫的哪一个位置上,都是先往东走,如果走得通就继续往东走,如果在某个位置上往东走不通的话,就依次试探往南、往西和往北方向,从一个走得通的方向继续往前直到出口为止;

            2.如果在某个位置上四个方向都走不通的话,就退回到前一个位置,换一个方向再试,如果这个位置已经没有方向可试了就再退一步,如果所有已经走过的位置的四个方向都试探过了,一直退到起始点都没有走通,那就说明这个迷宫根本不通;

            3.所谓"走不通"不单是指遇到"墙挡路",还有"已经走过的路不能重复走第二次",它包括"曾经走过而没有走通的路"。显然为了保证在任何位置上都能沿原路退回,需要用一个"后进先出"的结构即栈来保存从入口到当前位置的路径。并且在走出出口之后,栈中保存的正是一条从入口到出口的路径。

所以,若当前位置"可通",则纳入"当前路径",并继续朝"下一位置"探索;若当前位置"不可通",则应顺着"来的方向"退回到"前一通道块",然后朝着除"来向"之外的其他方向继续探索;若该通道块的四周四个方块均"不可通",则应从"当前路径"上删除该通道块。

设定当前位置的初值为入口位置;
do {
	若当前位置可通,
	则{
		将当前位置插入栈顶;// 纳入路径
		若该位置是出口位置,则算法结束;
		// 此时栈中存放的是一条从入口位置到出口位置的路径
		否则切换当前位置的东邻方块为新的当前位置;
	}
	否则 {
		若栈不空且栈顶位置尚有其他方向未被探索,
		则设定新的当前位置为: 沿顺时针方向旋转找到的栈顶位置的下一相邻块;
		若栈不空但栈顶位置的四周均不可通,
		则 {
			删去栈顶位置; // 从路径中删去该通道块
			若栈不空,则重新测试新的栈顶位置,
				直至找到一个可通的相邻块或出栈至栈空;
		}
	}
}while (栈不空);
#include <stdio.h>
#define WALL 0 //墙
#define CORRIDOR 1 //通道
#define PATH 9 //为路径上的一块
#define TRIED 2
#define ROW_NUM 7//迷宫数组行数
#define COL_NUM 13 //列数
#define TRUE 1
#define FALSE 0
#define MAXSIZE 50
typedef struct {
	int row;
	int col;
}PosType;
typedef struct {
	int ord; //通道块在路径上的"序号"
	PosType seat; //通道块在迷宫中的坐标
	int di; //当前通道块的方向
}SElemType;
typedef struct {
	SElemType S[MAXSIZE];
	int top;
}MazeType;
//迷宫
int grid[ROW_NUM][COL_NUM]={{1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1},
			    {1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1},
			    {1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1},
			    {1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1},
			    {1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0},
			    {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0},
			    {0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1}};
//当前位置是否可以通过
bool Valid(PosType pos) {
	if(pos.row>=0&&pos.row<=ROW_NUM&&pos.col>=0&&pos.col<=COL_NUM&&grid[pos.row][pos.col]==CORRIDOR)
		return TRUE;
	else
		return FALSE;
}
//留下足迹
void FootPrint(PosType pos) {
	grid[pos.row][pos.col]=PATH;
}
//留下不能通过的标识
void Undo(PosType pos) {
	grid[pos.row][pos.col]=TRIED;
}
//当前位置的下一个位置
PosType NextPos(PosType cur,int di) {
	PosType next;
	switch(di) {
		case 0: //东
			next.row=cur.row;
			next.col=cur.col+1;
			break;
		case 1: //南
			next.row=cur.row+1;
			next.col=cur.col;
			break;
		case 2: //西
			next.row=cur.row;
			next.col=cur.col-1;
			break;
		case 3: //北
			next.row=cur.row-1;
			next.col=cur.col;
			break;
	}
	return next;
}
//是否到达终点
bool Done(PosType cur,PosType end) {
	if(cur.row==end.row&&cur.col==end.col)
		return TRUE;
	else
		return FALSE;
}
//寻找迷宫路径
bool MazePath(MazeType &path,PosType start,PosType end) {
	SElemType e;
	path.top=-1;
	int step=1;
	PosType curpos=start;
	do {
		if(Valid(curpos)) {
			FootPrint(curpos);
			e.ord=step;
			e.di=0;
			e.seat=curpos;
			path.S[++path.top]=e;
			if(Done(curpos,end))
				return TRUE;
			curpos=NextPos(curpos,0);
			step++;
		}
		else {
			//棧不空
			if(path.top>-1) {
				e=path.S[path.top--];
				while(e.di==3&&path.top>-1) {
					Undo(e.seat);
					e=path.S[path.top--];
				}
				if(e.di<3) {
					e.di++;
					path.S[++path.top]=e;
					curpos=NextPos(e.seat,e.di);
				}
			}//if
		}//else
	}while(path.top>-1);
	return FALSE;
}
//输出路径
void PrintPath(MazeType path) {
	int i=0;
	while(i<=path.top) {
		printf("第%d步:(%d,%d)\n",path.S[i].ord,path.S[i].seat.row,path.S[i].seat.col);
		i++;
	}
}
//输出路径
void PrintPath2() {
	for(int i=0;i<ROW_NUM;i++)
		for(int j=0;j<COL_NUM;j++)
			if(grid[i][j]==PATH)
				printf("(%d,%d)\n",i,j);
}
int main() {
	MazeType path;
	PosType start={0,0},end={6,12};
	if(MazePath(path,start,end))
		PrintPath(path);
	else
		printf("not reachable!\n");
	PrintPath2();
}

(3)N皇后问题

问题:在一个N*N的棋盘上放置N个皇后,且使得每两个之间不能互相攻击,也就是使得每两个不在同一行,同一列和同一斜角线上。

分析:对于N=1,问题的解很简单,而且我们很容易看出对于N=2和N=3来说,这个问题无解。对于4皇后问题,可用回溯法对它求解。因为每个皇后都必须分别占据—行,需要做的不过是为图1棋盘上的每个皇后分配一列。

首先从空棋盘开始,然后把皇后1放到它所在行的第一个可能位置上,也就是第一行第—列。对于皇后2,在经过第一列和第二列的失败尝试之后,再把它放在第一个可能的位置格子上,这被证明是一个死胡同,因为皇后3将没有位置可放。所以,该算法进行回溯,把皇后2放在下一个可能位置(2,4)上。然后皇后3就可以放在(3,2),这被证明是另一个死胡同。该算法然后就回溯到底,把皇后1移到(1,2)。接着皇后2到(2,4),皇后3到(3,1),而皇后4到(4,3),这就是该问题的一个解。图2给出了这个查找的状态空间树。

#include <stdio.h>
#include <math.h>
#define N 4
int col[N+1];
//输出结果
void Output() {
	for(int i=1;i<=N;i++) {
		printf("(%d,%d)\n",i,col[i]);
	}
	printf("\n");
}
//求解函数
void Queen(int i,int n) {
	if(i>n)
		Output();
	else {
		for(int j=1;j<=n;++j) {
			int k=1;
			col[i]=j;
			while(k<i) {
				if((col[k]-col[i])*(fabs(col[k]-col[i])-fabs(k-i))!=0) {
					k++;
					if(k==i)
						Queen(i+1,n);
				}
				else {
					break;
				}
			}
		}
	}
}
int main() {
	printf("the answer is:\n");
	for(int i=1;i<=N;i++) {
		col[1]=i; //设置第一行
		Queen(2,N);
	}
}

猜你喜欢

转载自blog.csdn.net/u013228808/article/details/83057319
今日推荐