算法设计与分析------蛮力法(c语言)
一、蛮力法(穷举法 枚举法)
1、定义
蛮力法是一种简单直接地解决问题的方法,通常直接基于问题的描述和所涉及的概念定义,找出所有可能的解。
然后选择其中的一种或多种解,若该解不可行则试探下一种可能的解。
2、蛮力法使用情况
- 索所有的解空间:问题的解存在于规模不大的解空间中。
- 索所有的路径:这类问题中不同的路径对应不同的解。
- 直接计算:按照基于问题的描述和所涉及的概念定义,直接进行计算。往往是一些简单的题,不需要算法技巧的。
- 拟和仿真:按照求解问题的要求直接模拟或仿真即可。
3、蛮力法的优点
- 逻辑清晰,编写程序简单且简洁的
- 可以解决生活很多问题(具有普遍性)
- 对于一些重要的问题,用它可以产生一些合理的算法
- 可以解决一些小规模的问题
- 可以作为其他高效算法的衡量标准【做比较的对象】
4、蛮力法的缺点
- 它所设计的算法大多数效率都不高
- 主要适合问题规模比较小的问题的求解
- 蛮力法依赖的是常见的基本技术:扫描技术。
5、采用蛮力法设计算法的2类:
- 采用基本穷举的思路【简单,直接】
- 在穷举中应用递归算法,即采用递归方法穷举搜索解空间【相对复杂】
6、简单选择排序和冒泡排序
【问题描述】对于给定的含有n个元素的数组a,对其按元素值递增排序。
1. 简单选择排序
例如,i=3的一趟简单选择排序过程,其中a[0…2]是有序的,从a[3…9]中挑选最小元素a[5],将其与a[3]进行交换,从而扩大有序区,减小无序区。
代码如下:
#include <stdio.h>
void swap(int &x,int &y) //交换x和y
{
int tmp=x;
x=y; y=tmp;
}
void SelectSort(int a[],int n) //对a[0..n-1]元素进行递增简单选择排序
{ int i,j,k;
for (i=0;i<n-1;i++) //进行n-1趟排序
{ k=i; //用k记录每趟无序区中最小元素的位置
for (j=i+1;j<n;j++) //在a[i+1..n-1]中采用穷举法找最小元素a[k]
if (a[j]<a[k])
k=j;
if (k!=i) //若a[k]不是最小元素,将a[k]与a[i]交换
swap(a[i],a[k]);
}
}
void disp(int a[],int n) //输出a中所有元素
{ int i;
for (i=0;i<n;i++)
printf("%d ",a[i]);
printf("\n");
}
int main()
{ int n=10;
int a[]={2,5,1,7,10,6,9,4,3,8};
printf("排序前:"); disp(a,n);
SelectSort(a,n);
printf("排序后:"); disp(a,n);
}
2. 冒泡排序
例如,i=3的一趟冒泡排序过程,其中a[0…2]是有序的,从a[3…9]中通过交换将最小元素放在a[5]处,从而扩大有序区,减小无序区。
代码如下:
#include <stdio.h>
void swap(int &x,int &y) //交换x和y
{ int tmp=x;
x=y; y=tmp;
}
void BubbleSort(int a[],int n) //对a[0..n-1]按递增有序进行冒泡排序
{ int i,j;
bool exchange;
for (i=0;i<n-1;i++) //进行n-1趟排序
{ exchange=false; //本趟排序前置exchange为false
for (j=n-1;j>i;j--) //无序区元素比较,找出最小元素
if (a[j]<a[j-1]) //当相邻元素反序时
{ swap(a[j],a[j-1]); //a[j]与a[j-1]进行交换
exchange=true;//本趟排序发生交换置exchange为true
}
if (exchange==false) //本趟未发生交换时结束算法
return;
}
}
void disp(int a[],int n) //输出a中所有元素
{ int i;
for (i=0;i<n;i++)
printf("%d ",a[i]);
printf("\n");
}
int main()
{ int n=10;
int a[]={2,5,1,7,10,6,9,4,3,8};
printf("排序前:"); disp(a,n);
BubbleSort(a,n);
printf("排序后:"); disp(a,n);
}
7、字符串匹配
【问题描述】对于字符串s和t,若t是s子串,返回t在s中的位置(t的首字符在s中对应的下标),否则返回-1。
【分析思路】采用直接穷举法求解,称为BF算法。该算法从s的每一个字符开始查找,看t是否会出现。例如,s=“aababcde”,t=“abcd”:
代码如下:
#include <stdio.h>
#include <string>
using namespace std;
int BF(string s,string t) //字符串匹配
{
int i=0,j=0;
while (i<s.length() && j<t.length())
{
if (s[i]==t[j]) //比较的两个字符相同时
{
i++; j++;
}
else //比较的两个字符不相同时
{
i=i-j+1; //i回退
j=0; //j从0开始
}
}
if (j==t.length()) //t的字符比较完毕
return i-j; //t是s的子串,返回位置
else //t不是s的子串
return -1; //返回-1
}
int main()
{
string s="aababcabcde";
string t="abcd";
printf("index=%d\n",BF(s,t));
}
8、求解最大连续子序列和问题
【问题描述】给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如:
序列(-2,11,-4,13,-5,-2)的最大子序列和为20
序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
规定一个序列最大连续子序列和至少是0(看成0个元素构成的子序列),如果小于0,其结果为0。
解法1:
设含有n个整数的序列a[0…n-1],其中任何连续子序列a[i…j](i≤j,0≤i≤n-1,i≤j≤n-1)求出它的所有元素之和thisSum。
通过比较将最大值存放在maxSum中,最后返回maxSum。
代码如下:
#include <stdio.h>
int maxSubSum1(int a[],int n) //求a的最大连续子序列和
{ int i,j,k;
int maxSum=0,thisSum;
for (i=0;i<n;i++) //两重循环穷举所有的连续子序列
{ for (j=i;j<n;j++)
{ thisSum=0;
for (k=i;k<=j;k++)
thisSum+=a[k];
if (thisSum>maxSum) //通过比较求最大连续子序列之和
maxSum=thisSum;
}
}
return maxSum;
}
int main()
{
int a[]={-2,11,-4,13,-5,-2};
int n=sizeof(a)/sizeof(a[0]);
int b[]={-6,2,4,-7,5,3,2,-1,6,-9,10,-2};
int m=sizeof(b)/sizeof(b[0]);
printf("a序列的最大连续子序列的和:%ld\n",maxSubSum1(a,n));
printf("b序列的最大连续子序列的和:%ld\n",maxSubSum1(b,m));
}
解法2:
改进前面的解法,在求两个相邻子序列和时,它们之间是关联的。
例如a[0…3]子序列和=a[0]+a[1]+a[2]+a[3],a[0…4]子序列和=a[0]+a[1]+a[2]+a[3]+a[4],在前者计算出来后,求后者时只需在前者基础上加以a[4]即可,没有必须每次都重复计算。从而提高了算法效率。
代码如下:
#include <stdio.h>
int maxSubSum2(int a[],int n) //求a的最大连续子序列和
{ int i,j;
int maxSum=0,thisSum;
for (i=0;i<n;i++)
{ thisSum=0;
for (j=i;j<n;j++)
{ thisSum+=a[j];
if (thisSum>maxSum)
maxSum=thisSum;
}
}
return maxSum;
}
int main()
{
int a[]={-2,11,-4,13,-5,-2};
int n=sizeof(a)/sizeof(a[0]);
int b[]={-6,2,4,-7,5,3,2,-1,6,-9,10,-2};
int m=sizeof(b)/sizeof(b[0]);
printf("a序列的最大连续子序列的和:%ld\n",maxSubSum2(a,n)); //输出:20
printf("b序列的最大连续子序列的和:%ld\n",maxSubSum2(b,m)); //输出:16
}
解法3:
更一步改进解法2。
如果扫描中遇到负数,当前子序列和thisSum将会减小,若thisSum为负数,表明前面已经扫描的那个子序列可以抛弃了,则放弃这个子序列,重新开始下一个子序列的分析,并置thisSum为0。
若这个子序列和thisSum不断增加,那么最大子序列和maxSum也不断增加。
代码如下:
#include <stdio.h>
int maxSubSum3(int a[],int n) //求a的最大连续子序列和
{ int i,maxSum=0,thisSum=0;
for (i=0;i<n;i++)
{ thisSum+=a[i];
if (thisSum<0) //若当前子序列和为负数,则重新开始下一个子序列
thisSum=0;
if (maxSum<thisSum) //比较求最大连续子序列和
maxSum=thisSum;
}
return maxSum;
}
int main()
{
int a[]={-2,11,-4,13,-5,-2};
int n=sizeof(a)/sizeof(a[0]);
int b[]={-6,2,4,-7,5,3,2,-1,6,-9,10,-2};
int m=sizeof(b)/sizeof(b[0]);
printf("a序列的最大连续子序列的和:%ld\n",maxSubSum3(a,n)); //输出:20
printf("b序列的最大连续子序列的和:%ld\n",maxSubSum3(b,m)); //输出:16
}
9、图的深度优先和广度优先遍历
1.图的存储结构
- 邻接矩阵存储方法
邻接矩阵的类型定义如下:
#define MAXV <最大顶点个数>
typedef struct
{ int no; //顶点编号
char data[MAXL]; //顶点其他信息
} VertexType; //顶点类型
typedef struct
{ int edges[MAXV][MAXV]; //邻接矩阵的边数组
int n,e; //顶点数,边数
VertexType vexs[MAXV]; //存放顶点信息
} MGraph;
- 邻接表存储方法
typedef struct ANode
{ int adjvex; //该边的终点编号
int weight; //该边的权值
struct ANode *nextarc; //指向下一条边的指针
} ArcNode; //边结点类型
typedef struct Vnode
{ char data[MAXL]; //顶点其他信息
ArcNode *firstarc; //指向第一条边
} VNode; //邻接表头结点类型
typedef VNode AdjList[MAXV]; //AdjList是邻接表类型
typedef struct
{ AdjList adjlist; //邻接表
int n,e; //图中顶点数n和边数e
} ALGraph;
2.深度优先遍历
从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次,这个过程称为图的遍历。
为了避免同一个顶点被重复访问,必须记住每个被访问过的顶点。为此,设置一个访问标志数组visited[],当顶点i被访问过时,数组中元素visited[i]置为1;否则置为0。
深度优先搜索的过程是:
(1)从图中某个初始顶点v出发,首先访问初始顶点v。
(2)然后选择一个与顶点v相邻且没被访问过的顶点w为初始顶点,再从w出发进行深度优先搜索。
(3)重复直到图中与当前顶点v邻接的所有顶点都被访问过为止。显然,这个搜索过程是个递归过程。
3.广度优先遍历
广度优先搜索的过程是:
(1)首先访问初始顶点v。
(2)接着访问顶点v的所有未被访问过的邻接点v1,v2,…,vt。
(3)然后再按照v1,v2,…,vt的次序,访问每一个顶点的所有未被访问过的邻接点,依次类推,直到图中所有和初始顶点v有路径相通的顶点都被访问过为止。
二、案例
1、求解幂集问题
【问题描述】对于给定的正整数n(n≥1),求1~n构成的集合的所有子集(幂集)。
解法1:采用直接蛮力法求解,将1~n的存放在数组a中,求解问题变为构造集合a的所有子集。设集合a[0…2]={1,2,3},其所有子集对应的二进制位及其十进制数如下。
子集 | 对应的二进制位 | 对应的十进制数 |
---|---|---|
{} | — | — |
{1} | 001 | 1 |
{2} | 010 | 2 |
{1,2} | 011 | 3 |
{3} | 100 | 4 |
{1,3} | 101 | 5 |
{2,3} | 110 | 6 |
{1,2,3} | 111 | 7 |
对于含有n(n≥1)个元素的集合a,求幂集的过程如下:
for (i=0;i<2n;i++) //穷举a的所有子集并输出
{ 将i转换为二进制数b;
输出b中为1的位对应的a元素构成一个子集;
}
显然该算法的时间复杂度为O(n×2n),属于指数级的算法。
代码如下:
#include <stdio.h>
#include <math.h>
#define MaxN 10
void inc(int b[], int n) //将b表示的二进制数增1
{ for(int i=0;i<n;i++) //遍历数组b
{ if(b[i]) //将元素1改为0
b[i]=0;
else //将元素0改为1并退出for循环
{ b[i]=1;
break;
}
}
}
void PSet(int a[],int b[],int n) //求幂集
{ int i,k;
int pw=(int)pow(2,n); //求2^n
printf("1~%d的幂集:\n ",n);
for(i=0;i<pw;i++) //执行2^n次
{ printf("{ ");
for (k=0;k<n;k++) //执行n次
if(b[k])
printf("%d ",a[k]);
printf("} ");
inc(b,n); //b表示的二进制数增1
}
printf("\n");
}
int main()
{ int n=3;
int a[MaxN],b[MaxN];
for (int i=0;i<n;i++)
{ a[i]=i+1; //a初始化为{1,2,3}
b[i]=0; //b初始化为{0,0,0}
}
PSet(a,b,n);
}
解法2:采用增量蛮力法求解1~n的幂集,当n=3时的求解过程。
这种思路也是蛮力法方法:即穷举1~n的所有子集。先建立一个空子集,对于i(1≤i≤n),每次都是在前面已建立的子集上添加元素i而构成若干个子集,对应的过程如下:
void f(int n) //求1~n的幂集ps
{ 置ps={{}}; //在ps中加入一个空子集元素
for (i=1;i<=n;i++)
在ps的每个元素中添加i而构成一个新子集;
}
在实现算法时,用一个vector容器表示一个集合元素,用vector<vector >容器存放幂集(即集合的集合)。
代码如下:
#include <stdio.h>
#include <vector>
using namespace std;
vector<vector<int> > ps; //存放幂集
void PSet(int n) //求1~n的幂集ps
{
vector<vector<int> > ps1; //子幂集
vector<vector<int> >::iterator it; //幂集迭代器
vector<int> s,s1;
ps.push_back(s); //添加{}
for (int i=1;i<=n;i++) //循环添加1~n
{
ps1=ps;
for (it=ps1.begin();it!=ps1.end();++it)
(*it).push_back(i); //在ps1的每个集合元素末尾添加i
for (it=ps1.begin();it!=ps1.end();++it)
ps.push_back(*it); //将ps1的每个集合元素添加到ps中
}
}
void dispps() //输出幂集ps
{
vector<vector<int> >::iterator it; //幂集迭代器
vector<int>::iterator sit; //幂集集合元素迭代器
for (it=ps.begin();it!=ps.end();++it)
{
printf("{ ");
for (sit=(*it).begin();sit!=(*it).end();++sit)
printf("%d ",*sit);
printf("} ");
}
printf("\n");
}
int main()
{ int n=3;
PSet(n);
printf("1~%d的幂集:\n ",n);
dispps();
}
算法分析:对于给定的n,每一个集合元素都要处理,有2n个,所以上述算法的时间复杂度为O(2n)。
2、求解0/1背包问题
【问题描述】有n个重量分别为{w1,w2,…,wn}的物品,它们的价值分别为{v1,v2,…,vn},给定一个容量为W的背包。设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且具有最大的价值。
并对下表所示的4个物品求出W=6时的所有解和最佳解。
物品编号 | 重量 | 价值 |
---|---|---|
1 | 5 | 4 |
2 | 3 | 4 |
3 | 2 | 3 |
4 | 1 | 1 |
【问题求解】对于n个物品、容量为W的背包问题,采用前面求幂集的方法求出所有的物品组合。
对于每一种组合,计算其总重量sumw和总价值sumv,当sumw小于等于W时,该组合是一种解,并通过比较将最佳方案保存在maxsumw和maxsumv中,最后输出所有的解和最佳解。
#include <stdio.h>
#include <vector>
using namespace std;
vector<vector<int> > ps; //存放幂集
void PSet(int n) //求1~n的幂集ps
{ vector<vector<int> > ps1; //子幂集
vector<vector<int> >::iterator it;//幂集迭代器
vector<int> s;
ps.push_back(s); //添加{}空集合元素
for (int i=1;i<=n;i++) //循环添加1~n
{ ps1=ps; //ps1存放上一步得到的幂集
for (it=ps1.begin();it!=ps1.end();++it)
(*it).push_back(i); //在ps1的每个集合元素末尾添加i
for (it=ps1.begin();it!=ps1.end();++it)
ps.push_back(*it); //将ps1的每个集合元素添加到ps中
}
}
void Knap(int w[],int v[],int W) //求所有的方案和最佳方案
{ int count=0; //方案编号
int sumw,sumv; //当前方案的总重量和总价值
int maxi,maxsumw=0,maxsumv=0; //最佳方案的编号、总重量和总价值
vector<vector<int> >::iterator it; //幂集迭代器
vector<int>::iterator sit; //幂集集合元素迭代器
printf(" 序号\t选中物品\t总重量\t总价值\t能否装入\n");
for (it=ps.begin();it!=ps.end();++it) //扫描ps中每一个集合元素
{ printf(" %d\t",count+1);
sumw=sumv=0;
printf("{");
for (sit=(*it).begin();sit!=(*it).end();++sit)
{ printf("%d ",*sit);
sumw+=w[*sit-1]; //w数组下标从0开始
sumv+=v[*sit-1]; //v数组下标从0开始
}
printf("}\t\t%d\t%d ",sumw,sumv);
if (sumw<=W)
{ printf("能\n");
if (sumv>maxsumv) //比较求最优方案
{ maxsumw=sumw;
maxsumv=sumv;
maxi=count;
}
}
else printf("否\n");
count++; //方案编号增加1
}
printf("最佳方案为: ");
printf("选中物品");
printf("{ ");
for (sit=ps[maxi].begin();sit!=ps[maxi].end();++sit)
printf("%d ",*sit);
printf("},");
printf("总重量:%d,总价值:%d\n",maxsumw,maxsumv);
}
int main()
{ int n=4,W=6;
int w[]={5,3,2,1};
int v[]={4,4,3,1};
PSet(n);
printf("0/1背包的求解方案\n",n);
Knap(w,v,W);
}
3、求解任务分配问题
【问题描述】有n(n≥1)个任务需要分配给n个人执行,每个任务只能分配给一个人,每个人只能执行一个任务。
第i个人执行第j个任务的成本是ci(1≤i,j≤n)。求出总成本最小的分配方案。
【问题求解】所谓一种分配方案就是由第i个人执行第j个任务,用(a1,a2,…,an)表示,即第1个人执行第a1个任务,第2个人执行第a2个任务,以此类推。全部的分配方案恰好是1~n的全排列。
这里采用增量穷举法求出所有的分配方案ps(全排列),再计算出每种方案的成本,比较求出最小成本的方案,即最优方案。以n=4,成本如下表所示为例讨论。
人员 | 任务1 | 任务2 | 任务3 | 任务4 |
---|---|---|---|---|
1 | 9 | 2 | 7 | 8 |
2 | 6 | 4 | 3 | 7 |
3 | 5 | 8 | 1 | 8 |
4 | 7 | 6 | 9 | 4 |
代码如下:
#include <stdio.h>
#include <vector>
using namespace std;
#define INF 99999 //最大成本值
#define MAXN 21
//问题表示
int n=4;
int c[MAXN][MAXN]={ {9,2,7,8},{6,4,3,7},{5,8,1,8},{7,6,9,4} };
vector<vector<int> > ps; //存放全排列
void Insert(vector<int> s,int i,vector<vector<int> > &ps1)
//在每个集合元素中间插入i得到ps1
{ vector<int> s1;
vector<int>::iterator it;
for (int j=0;j<i;j++) //在s(含i-1个整数)的每个位置插入i
{ s1=s;
it=s1.begin()+j; //求出插入位置
s1.insert(it,i); //插入整数i
ps1.push_back(s1); //添加到ps1中
}
}
void Perm(int n) //求1~n的所有全排列
{ vector<vector<int> > ps1; //临时存放子排列
vector<vector<int> >::iterator it; //全排列迭代器
vector<int> s,s1;
s.push_back(1);
ps.push_back(s); //添加{1}集合元素
for (int i=2;i<=n;i++) //循环添加1~n
{ ps1.clear(); //ps1存放插入i的结果
for (it=ps.begin();it!=ps.end();++it)
Insert(*it,i,ps1); //在每个集合元素中间插入i得到ps1
ps=ps1;
}
}
void Allocate(int n,int &mini,int &mincost) //求任务分配问题的最优方案
{
Perm(n); //求出全排列ps
for (int i=0;i<ps.size();i++) //求每个方案的成本
{
int cost=0;
for (int j=0;j<ps[i].size();j++)
cost+=c[j][ps[i][j]-1];
if (cost<mincost) //比较求最小成本的方案
{
mincost=cost;
mini=i;
}
}
}
int main()
{
int mincost=INF,mini; //mincost为最小成本,mini为ps中最优方案编号
Allocate(n,mini,mincost);
printf("最优方案:\n");
for (int k=0;k<ps[mini].size();k++)
printf(" 第%d个人安排任务%d\n",k+1,ps[mini][k]);
printf(" 总成本=%d\n",mincost);
}
三、蛮力法实验
1、实验一 求解迷宫问题
【问题描述】有如下8*8的迷宫图:
OXXXXXXX
OOOOOXXX
XOXXOOOX
XOXXOXXO
XOXXXXXX
XOXXOOOX
XOOOOXOO
XXXXXXXO
其中,O表示通路方块,X表示障碍方块。假设入口是位置(0,0),出口为右下角方块位置(7,7)。设计一个程序采用递归方法求指定入口到出口的一条迷宫路径。
【问题求解】用n表示迷宫大小,二维数组Maze存放迷宫,从(x,y)方块可以试探上下左右4个方位,假设总是从方位0到方位3的顺序试探,各方位对应的水平方向偏移量H[4] = {0,1,0,-1},垂直偏移量V[4] = {-1,0,1,0}。
解法1:
采用深度优先遍历方式,从(x,y)出发(初始为入口)搜索目标(出口)。
对于当前方块(x,y):
- 需要试探4个相邻的方块。
- 为了避免重复,每走过一个方块,将对应的迷宫值由’O’改为’ ‘(空字符),当回过来时将其迷宫值恢复为’O’。
代码如下:
#include <stdio.h>
#define MAxN 10 //最大迷宫大小
int n=8; //迷宫大小
char Maze[MAxN][MAxN]=
{ {'O','X','X','X','X','X','X','X'},
{'O','O','O','O','O','X','X','X'},
{'X','O','X','X','O','O','O','X'},
{'X','O','X','X','O','X','X','O'},
{'X','O','X','X','X','X','X','X'},
{'X','O','X','X','O','O','O','X'},
{'X','O','O','O','O','X','O','O'},
{'X','X','X','X','X','X','X','O'}
};
int H[4] = {0, 1, 0, -1}; //水平偏移量,下标对应方位号0~3
int V[4] = {-1, 0, 1, 0}; //垂直偏移量
void disppath() //输出一条迷宫路径
{
for (int i=0; i<n;i++)
{
printf(" ");
for(int j=0; j<n;j++)
printf("%c",Maze[i][j]);
printf("\n");
}
}
void DFS(int x,int y) //求从(x,y)出发的一条迷宫路径
{
if (x==n-1 && y==n-1) //找到一条路径,输出
{
Maze[n-1][n-1]=' ';
disppath();
return;
}
else
{ for (int k=0;k<4;k++) //试探每一个方位
if(x>=0 && y>=0 && x<n && y<n && Maze[x][y]=='O')
{ //若(x,y)方块的可走的
Maze[x][y]=' '; //将该方块设置为空字符
DFS(x+V[k],y+H[k]); //查找(x,y)周围的每一个相邻方块
Maze[x][y]='O'; //若从该相邻方块出发没有找到路径,恢复(x,y)
}
}
}
int main()
{
int x=0,y=0; //指定入口,出口默认为(n-1,n-1)
printf("一条迷宫路径:\n");
DFS(x,y); //求(0,0)->(7,7)的一条迷宫路径
}
解法2:
采用广度优先遍历方式,从(x,y)出发(初始为入口)搜索目标(出口)。由于STL中queue不能顺序遍历,这里采用一个数组作为非循环队列,front和rear分别为队头和队尾(初始时均设置为-1),每个进队元素有唯一的下标。
队列元素类型声明如下:
struct Position //队列元素类型
{ int x,y; //当前方块位置
int pre; //前驱方块的下标
};
首先将根入口方块(其pre置为-1)进队,队列不空循环:
出队方块p1作为当前方块(在队列数组中的下标为front):
- 若为p1出口:通过队列数组qu反向推出迷宫路径并输出。
- 否则:查找p1的每一个相邻方块p2,若p2位置有效(即p2.x>=0 && p2.y>=0 && p2.x<n && p2.y<n)并且可走(Mazep2.x=‘O’),则置p2.pre=front(表示p2的前驱方块是p1)并将p2方块进队。
代码如下:
#include <stdio.h>
#define MAXQ 100 //队列大小
#define MAxN 10 //最大迷宫大小
int n=8; //迷宫大小
char Maze[MAxN][MAxN]=
{ {'O','X','X','X','X','X','X','X'},
{'O','O','O','O','O','X','X','X'},
{'X','O','X','X','O','O','O','X'},
{'X','O','X','X','O','X','X','O'},
{'X','O','X','X','X','X','X','X'},
{'X','O','X','X','O','O','O','X'},
{'X','O','O','O','O','X','O','O'},
{'X','X','X','X','X','X','X','O'}
};
int H[4] = {0, 1, 0, -1}; //水平偏移量,下标对应方位号0~3
int V[4] = {-1, 0, 1, 0}; //垂直偏移量
struct Position //队列元素类型
{
int x,y; //当前方块位置
int pre; //前驱方块的下标
};
Position qu[MAXQ]; //定义一个队列qu
int front=-1,rear=-1; //定义队头和队尾
void disppath(int front) //输出一条迷宫路径
{
int i,j;
for (i=0; i<n;i++) //将所有'*'改为'O'
for (j=0;j<n;j++)
if (Maze[i][j]=='*')
Maze[i][j]='O';
int k=front;
while (k!=-1) //即路径上的方块改为' '
{
Maze[qu[k].x][qu[k].y]=' ';
k=qu[k].pre;
}
for (i=0; i<n;i++) //输出迷宫路径
{ printf(" ");
for(int j=0; j<n;j++)
printf("%c",Maze[i][j]);
printf("\n");
}
}
void BFS(int x,int y) //求从(x,y)出发的一条迷宫路径
{
Position p,p1,p2;
p.x=x; p.y=y; p.pre=-1; //建立入口结点
Maze[p.x][p.y]='*'; //改为'*'避免重复查找
rear++; qu[rear]=p; //入口方块进队
while (front!=rear) //队不空循环
{
front++; p1=qu[front]; //出队方块p1;
if (p1.x==n-1 && p1.y==n-1) //找到出口
{
disppath(front); //输出路径
return;
}
for (int k=0;k<4;k++) //试探p1的每个相邻方位
{
p2.x=p1.x+V[k]; //找到p1的相邻方块p2
p2.y=p1.y+H[k];
if (p2.x>=0 && p2.y>=0 && p2.x<n && p2.y<n && Maze[p2.x][p2.y]=='O')
{ //方块p2有效并且可走
Maze[p2.x][p2.y]='*'; //改为'*'避免重复查找
p2.pre=front;
rear++; qu[rear]=p2; //方块p2进队
}
}
}
}
int main()
{
int x=0,y=0; //指定入口,出口默认为(n-1,n-1)
printf("一条迷宫路径:\n");
BFS(x,y); //求(0,0)->(7,7)的一条迷宫路径
}
2、实验二 求解钱币兑换问题
【问题描述】某个国家仅有1分.2分和5分硬币,将钱n(n≥5)兑换成硬币有很多种兑法。编写一个实验程序计算出10分钱有多少种兑法,并列出每种兑换方式。
#include<iostream>
using namespace std;
int main()
{
int i,j,k,sum=0;
for(i=0;i<100;i++)
for(j=0;j<50;j++)
for(k=0;k<20;k++)
{
if(i+2*j+k*5==10)
{ sum=sum+1;
cout<<"兑法"<<sum<<";一分钱硬币"<<i<<"个,二分钱硬币 "<<j<<"个,五分钱硬币 "<<k<<"个"<<endl;
}
}
cout<<"共有"<<sum<<"种兑法";
}