C++学习笔记——搜索算法

一、深度优先搜索(DFS)

深度优先搜索是一种用于遍历或搜索树或图的算法。在深度优先搜索中,从起始顶点开始,沿着路径直到不能再继续为止,然后回溯并探索下一个分支。

深度优先搜索研究的是有没有一条路径能从A到B的问题,它不考虑这个过程的长与短,只考虑有没有。

1、递归回溯算法框架

基本思想

为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,然后继续向前探索,如此反复执行,直到得到解或证明无解。

框架

int search(int t)
{
	for(i=1;i<=算符种数;i++)
	{
		if(满足条件)
		{
			保存结果;
			if(到目的地) 输出解;
			else search(t+1);
			恢复:保存结果之前的状态(回溯) 
		}
	 } 
}
int search(int t)
{
	if(到目的地) 输出解;
	else
	{
		for(i=1;i<=算符种数;i++)
		if(满足条件)
		{
			保存结果;
			search(t+1);
			恢复:保存结果之前的状态(回溯) 
		 } 
	 } 
 } 

2、算法应用

【例题1】

素数环:从1到20,将这20个数摆成一个环,要求相邻的两个数的和是一个素数。

 

#include <bits/stdc++.h>
using namespace std;

int a[21],b[21],total;

bool pd(int x,int y)         //判断素数 
{
	int i=x+y;
	for(int j=2;j*j<=i;j++)
	{
		if(i%j==0)
		    return false;
	}
	return true;
}

void print()
{
	total++;         //total没有定义的话默认初始值为0 
	cout<<"<方案"<<total<<">";
	for(int j=1;j<=20;j++) cout<<a[j]<<" ";
	cout<<endl;
 } 

void search(int t)          //t代表第t个位置 
{
	int i;
	for(i=1;i<=20;i++)       //每个位置有20种可能 
	{
		if(pd(a[t-1],i)&&b[i]==0)     //b[i]==0表示i可用 
		{
			a[t]=i;         //将结果保存到数组a[] 
			b[i]=1;         //标记结果已被占用 
			if(t==20)         //第20个要跟第1个进行判断 
		    {
		 	    if(pd(a[20],a[1]))  print();
		    }
		    else search(t+1);   //没到第20个就往下走 
		    b[i]=0;       //都不满足就回溯 
		} 
	}
}

int main()
{
	search(1);   //从第1个开始搜索 
	cout<<total<<endl;
	return 0;
}

【例题2】

设有n个整数集合{1,2,3...n},从中任意取出r个数进行排列(r<n),试列出所有的排列。

#include<bits/stdc++.h>
using namespace std;

int n,r,a[10001],b[10001],num;

void print()
{
	num++;
	for(int i=1;i<=r;i++)
	{
		cout<<" "<<a[i];
	}
	cout<<endl;
}

void search(int t)
{
	int i;
	for(i=1;i<=n;i++)
	{
		if(b[i]==0)
		{
			a[t]=i;
			b[i]=1;
			if(t==r) print();
			else search(t+1);
			b[i]=0;
		}
	}
}

int main()
{
	cout<<"请输入两个数n和r:";
	cin>>n>>r;
	search(1);
	cout<<"number="<<num<<endl;
	return 0; 
}

【例题3】 

任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。当n=7,共有14种拆分方法:

#include<bits/stdc++.h>
using namespace std;

int a[10001]={1},n,total;

int print(int t)
{
	cout<<n<<"=";
	for(int i=1;i<=t-1;i++)
	{
		cout<<a[i]<<"+";
	}
	cout<<a[t]<<endl;
	total++;
}

int search(int s,int t)
{
	int i;
	for(i=a[t-1];i<=s;i++)
	{
		if(i<n)
		{
			a[t]=i;
			s=s-i;
			if(s==0) print(t);
			else search(s,t+1);
			s=s+i;
		}
	}
}

int main()
{
	cin>>n;
	search(n,1);
	cout<<"total="<<total<<endl;
	return 0;
}

【例题4】 

八皇后问题:要在国际象棋棋盘中放八个皇后,使得任意两个皇后都不能互相吃(皇后只能吃同一行、同一列、同一对角线的任意棋子)。

#include <bits/stdc++.h>
using namespace std;

int a[9],b[9],c[17],d[17],sum=0;

void print()
{
	int i;
	sum++;
	cout<<"sum="<<sum<<endl;
	for(i=1;i<=8;i++)
	{
		cout<<" "<<a[i];
	}
	cout<<endl;
}

void search(int i)     //i表示行 
{
	int j;             //j表示列 
	for(j=1;j<=8;j++)
	{
		if((b[j]==0)&&(c[i+j]==0)&&(d[i-j+7]==0))   //右上-左下的对角线行与列加起来是定值 ,左上-右下的对角线行与列相减是定值 
		{
			a[i]=j;       //保存这一行的皇后放在了哪一列 
			b[j]=1;       //列被占用 
			c[i+j]=1;     //右上-左下的对角线被占用 
			d[i-j+7]=1;   //左上-右下的对角线被占用 
			if(i==8) print();
			else search(i+1);   //继续放下一行的皇后 
			b[j]=0;        //列释放 
			c[i+j]=0;      //对角线释放 
			d[i-j+7]=0;
		}
	}
}
int main()
{
	search(1);
	return 0;
}

二、广度优先搜索(BFS)

广度优先搜索算法(又称宽度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。

1、基本思想

广度优先算法的核心思想是,从初始节点开始,应用算符生成第一层节点,检查目标节点是否在这些后继节点中,若没有,再用产生式规则将所有第一层的节点注意扩展,得到第二层节点,并逐一检查第二层节点中是否含有目标节点。若没有,再用算符逐一扩展第二层的所有节点,如此一次扩展,检查下去,直到发现目标节点为止。即:

  • 从图中的某一顶点V0开始,先访问V0;
  • 访问所有与V0相邻接的节点V1,V2,...,Vt;
  • 依次访问与V1,V2,...,Vt相邻接的所有未曾访问过的节点;
  • 循此以往,直至所有的顶点都被访问过为止。

广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有往回退的情况,因此它不是一个递归的算法。 

2、算法应用

【例题1】

解救A同学:迷宫由n行m列的单元格组成(n和m都小于等于50),每个单元格要么是空地,要么是障碍物。你的任务是找到一条从迷宫的起点通往A同学所在位置的最短路径,最终需要几步。注意障碍物是不能走的,当然也不能走到迷宫之外。

输入与输出:

分析:我们用一个二维数组来存储这个迷宫,最开始的时候我们在迷宫(1,1)处,可以往右或者往下走。“一层一层”扩展的方法来找到A同学。扩展时每发现一个点就将这个点加入到队列中,直至走到A同学的位置(p,q)时为止。

方法一:广搜+队列 
#include<iostream> 
#include<cstdio>
#include<queue>
using namespace std;

struct Node     //经过每一个节点的时候都要记录步数,所以用结构体来实现 
{
	int x,y,step;   //step代表走到(x,y)所需最小步数 
};

int a[101][101];   //数组存储迷宫的原始数据 
bool book[101][101];  //布尔类型表示迷宫是否被访问过了
int n,m,start_x,start_y,end_x,end_y;
bool flag=false;   //先假定不能到达A同学的位置 
int np[4][2]={{0,1},{1,0},{0,-1},{-1,0}};  //代表方向,向下右上左走
queue<Node> que;   //初始化一个队列,队列里是Node节点

void bfs()
{
	Node startnode;    //存储当前坐标 
	startnode.x=start_x;
	startnode.y=start_y;
	startnode.step=0;
	book[start_x][start_y]=true;   //当前坐标已经被访问过了 
	que.push(startnode);       //存储到队列 
	while(!que.empty())        
	{
		for(int i=0;i<4;i++)   //四个方向都走一走 
		{
			int nx=que.front().x+np[i][0];
			int ny=que.front().y+np[i][1];
			//判断是否在迷宫内、有没有障碍物、有没有被走过 
			if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]==0&&!book[nx][ny])
			{
				book[nx][ny]=true;   //代表新节点被走过了
				Node newnode;
				newnode.x=nx;newnode.y=ny;newnode.step=que.front().step+1;   //存储新节点 
				que.push(newnode);    //新节点加入队列 
			} 
			if(nx==end_x&&ny==end_y)   //走到目的地了 
			{
				flag=true;   //可以走到A同学 
				return;
			}
		}
		que.pop();  //否则移出队列 
	}
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)  
		{
			cin>>a[i][j];
		}
		
	}
	cin>>start_x>>start_y>>end_x>>end_y;
	bfs();
	if(flag) cout<<que.back().step;   //能走到A同学输出队列 
	else cout<<-1;   //不能走到A同学 
	return 0;
}
方法二:深搜+递归
#include<bits/stdc++.h>
using namespace std;

int a[101][101];   //数组存储迷宫的原始数据 
bool book[101][101];  //布尔类型表示迷宫是否被访问过了
int n,m,start_x,start_y,end_x,end_y,step=0,mins=100;
bool flag=false;   //先假定不能到达A同学的位置 
int np[4][2]={{0,1},{1,0},{0,-1},{-1,0}};  //代表方向,向下右上左走

void search(int i,int j)
{
	for(int k=0;k<4;k++)
	{
		int nx=i+np[k][0],ny=j+np[k][1];
		if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]==0&&!book[nx][ny])
		{
			step++;
			book[nx][ny]=1;
			if(nx==end_x&&ny==end_y)
			{
				flag=true;
				if(step<mins) mins=step;
			}
			else search(nx,ny);
			step--;
			book[nx][ny]=0;
		}
	}
}

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)  
		{
			cin>>a[i][j];
		}	
	}
	cin>>start_x>>start_y>>end_x>>end_y;
	search(1,1);
	cout<<mins<<endl;
	return 0;
}
方法三:深搜+栈
#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;

struct Node{
	int x,y,step;
};
int a[101][101];   
bool book[101][101];  
int n,m,start_x,start_y,end_x,end_y,minx=100;
bool flag=false;  
int np[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
stack<Node> stk;   //栈 
void dfs();  //深搜+栈 

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)  
		{
			cin>>a[i][j];
		}	
	}
	cin>>start_x>>start_y>>end_x>>end_y;
	dfs();
	cout<<minx;
	return 0;
}

void dfs()
{
	Node startnode;   //新节点 
	startnode.x=start_x;
	startnode.y=start_y;
	startnode.step=0;
	book[start_x][start_y]=true;
	stk.push(startnode);   //压入栈中 
	while(!stk.empty())   //如果栈不为空就循环 
	{
		if(stk.top().x==end_x&&stk.top().y==end_y)  //到达目的地 
		{
			if(stk.top().step<minx)
			{
				minx=stk.top().step;   //最小步数 
			}
			while(stk.size()>1) stk.pop();   //出栈,从头再找 
			break;
		}
		else   //没到终点 
		{
			int d=0;
			for(int i=0;i<4;i++)   //四个方向都走 
			{
				int nx=stk.top().x+np[i][0];
				int ny=stk.top().y+np[i][1];
				if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]==0&&!book[nx][ny])
				{
					d++;  //如果d一次都没有增加过,那就是走到死胡同了 
					book[nx][ny]=1;
					Node newnode;   //新节点 
					newnode.x=nx;newnode.y=ny;newnode.step=stk.top().step+1;
					stk.push(newnode);   //入栈 
					break;   //深搜,一条路走到底,所以不会再去找相邻的节点了,跳出循环 
				}
			} 
			if(d==0)
			{
				stk.pop();  //走到死胡同了,踢出栈,从下一个节点继续搜索 
			 } 
		}
	}
}

【例题2】

交通图:如下图表示的是从城市A到城市H的交通图。从图中可以看出,从城市A到城市H要经过若干个城市。相邻城市之间的距离为1,现要找出一条经过城市最少的一条路线。(H-F-A)

算法分析:用邻接矩阵来表示,0表示能走,1表示不能走。

方法:广搜+队列 
#include<iostream>
#include<queue>  //用到队列 
using namespace std;

int map[9][9]={
	{0,0,0,0,0,0,0,0,0},
	{0,1,0,0,0,1,0,1,1},
	{0,0,1,1,1,1,0,1,1},
	{0,0,1,1,0,0,1,1,1},
	{0,0,1,0,1,1,1,0,1},
	{0,1,1,0,1,1,1,0,0},
	{0,0,0,1,1,1,1,1,0},
	{0,1,1,1,0,0,1,1,0},
	{0,1,1,1,1,0,0,0,1}
};

bool book[101];  //标识某个城市是否走过 
int pre[101];    //表示当前城市的前一个城市 
queue<int> que;
void bfs();

int main()
{
	bfs();
	int k=8;   //八个城市 
	cout<<char(k+64);  //输出一个H 
	while(pre[k])
	{
		cout<<"-"<<char(pre[k]+64);  //整型要转变成字符型 
		k=pre[k];
	}
	return 0;
}

void bfs()
{
	que.push(1);   //第一个城市入队 
	book[1]=true;  //第一个城市被访问过了 
	while(!que.empty())
	{
		int cur=que.front();   //队列最前面的节点 
		for(int i=1;i<=8;i++)  //找跟队列最前面的节点相连的节点 
		{
			if(map[cur][i]==0&&!book[i])  //相连,并且没被访问过 
			{
				book[i]=true;  //标识为访问过了 
				que.push(i);   //入列 
				pre[i]=cur;    //pre存储前一个城市 
			}
		}
		que.pop();   //走到死胡同了就pop出来 
	}
}

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈夫曼算法是一种经典的无损数据压缩算法,它通过将频率较高的字符用较短的编码表示,而频率较低的字符用较长的编码表示,从而实现文件的压缩。 首先,对于要压缩的文件,我们需要统计各个字符的出现频率。然后,根据字符频率构建哈夫曼树。构建过程中,我们选择频率最低的两个节点,合并为一个新的节点,并调整树的结构,直到所有节点都合并为一个根节点。 接下来,我们通过遍历哈夫曼树,给每个字符生成对应的编码。规定向左走为0,向右走为1,当遍历到叶子节点时,就得到了字符的编码。 在压缩文件时,我们将每个字符的编码按顺序写入输出文件中。同时,为了解压缩方便,我们需要在文件开头添加字符与编码的映射表,用于解压缩时查找。 解压缩文件时,我们根据映射表将编码逐个翻译为字符,恢复出原始的文件内容。 哈夫曼算法通过合理地利用字符的频率信息,将出现频率高的字符用较短的编码表示,从而实现了对文件的压缩。经典的例子是英文文本,其中出现频率较高的字符如空格、换行符等可以用极短的编码表示,而出现频率较低的字符如字母J、Z等则可以用更长的编码表示。 用哈夫曼算法进行文件压缩解压缩可以有效地减小文件的存储空间,提高文件传输效率,并且无损地还原出原始文件内容。因此,哈夫曼算法在实际应用中被广泛使用,如在文件传输、音频、视频等领域中。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值