算法设计与分析------蛮力法

一、蛮力法(穷举法 枚举法)

1、定义

蛮力法是一种简单直接地解决问题的方法,通常直接基于问题的描述和所涉及的概念定义,找出所有可能的解。

然后选择其中的一种或多种解,若该解不可行则试探下一种可能的解。

2、蛮力法使用情况

  1. 索所有的解空间:问题的解存在于规模不大的解空间中。
  2. 索所有的路径:这类问题中不同的路径对应不同的解。
  3. 直接计算:按照基于问题的描述和所涉及的概念定义,直接进行计算。往往是一些简单的题,不需要算法技巧的。
  4. 拟和仿真:按照求解问题的要求直接模拟或仿真即可。

3、蛮力法的优点

  1. 逻辑清晰,编写程序简单且简洁的
  2. 可以解决生活很多问题(具有普遍性)
  3. 对于一些重要的问题,用它可以产生一些合理的算法
  4. 可以解决一些小规模的问题
  5. 可以作为其他高效算法的衡量标准【做比较的对象】

4、蛮力法的缺点

  1. 它所设计的算法大多数效率都不高
  2. 主要适合问题规模比较小的问题的求解
  3. 蛮力法依赖的是常见的基本技术:扫描技术。

5、采用蛮力法设计算法的2类:

  1. 采用基本穷举的思路【简单,直接】
  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}0011
{2}0102
{1,2}0113
{3}1004
{1,3}1015
{2,3}1106
{1,2,3}1117

对于含有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时的所有解和最佳解。

物品编号重量价值
154
234
323
411

​ 【问题求解】对于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
19278
26437
35818
47694

代码如下:

#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<<"种兑法";
}
  • 8
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
最大子段和问题是指在一个数列中找到一个子序列,使得该子序列中所有元素的和最大。以下是三种常见的算法实现: 1. 蛮力 蛮力是最朴素的解,它的间复杂度为 $O(n^2)$。具体实现如下: ```c++ int maxSubArray(int nums[], int n) { int ans = INT_MIN; for (int i = 0; i < n; i++) { int sum = 0; for (int j = i; j < n; j++) { sum += nums[j]; ans = max(ans, sum); } } return ans; } ``` 2. 分治 分治间复杂度为 $O(n\log n)$,它将问题分成三个部分:求解左半部分的最大子段和、求解右半部分的最大子段和、求解跨越中点的最大子段和。具体实现如下: ```c++ int maxSubArray(int nums[], int left, int right) { if (left == right) return nums[left]; int mid = left + (right - left) / 2; int leftMax = maxSubArray(nums, left, mid); int rightMax = maxSubArray(nums, mid + 1, right); int crossMax = nums[mid]; int sum = nums[mid]; for (int i = mid - 1; i >= left; i--) { sum += nums[i]; crossMax = max(crossMax, sum); } sum = crossMax; for (int i = mid + 1; i <= right; i++) { sum += nums[i]; crossMax = max(crossMax, sum); } return max(leftMax, max(rightMax, crossMax)); } ``` 3. 动态规划 动态规划间复杂度为 $O(n)$,它定义了一个状态数组 $dp$,其中 $dp[i]$ 表示以 $i$ 结尾的最大子段和。具体实现如下: ```c++ int maxSubArray(int nums[], int n) { int dp[n]; dp[0] = nums[0]; int ans = nums[0]; for (int i = 1; i < n; i++) { dp[i] = max(dp[i - 1] + nums[i], nums[i]); ans = max(ans, dp[i]); } return ans; } ``` 以上是三种常见的算法实现,需要注意的是,在实际应用中,我们还可以使用其他优化方,如前缀和、后缀和、单调栈等,以进一步提高算法效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

拾亿-唯一

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值