算法学习:搜索

暴力美学

枚举每一个可能解,如8-puzzle,Hamiltonian环问题

  • 确定一个搜索的顺序使得每一种方案都能被枚举到
  • 确定搜索结束的条件
数字排列问题
#include <iostream>
using namespace std;
const int N=8;
int n;
int path[N];
bool st[N];
void  dfs(int u)
{
    if(u==n)
    {
        for(int i=0;i<n;i++)
        {printf("%d ",path[i]);}
        puts("");
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(st[i]==false)
        {path[u]=i;
        st[i]=true;
        dfs(u+1);
        st[i]=false;
        }
    }
    
}
int  main()
{
    cin >>n;
    dfs(0);   
    return 0;   
}

深度优先和广度优先

BFS

在这里插入图片描述

DFS

在这里插入图片描述
递归算法的实际实现中,不需要我们写栈,系统内部已经帮我们实现了栈
回溯时要注意恢复现场

搜索的优化

爬山策略(局部贪心优化DFS)

问题的引入

DFS时遇到多个节点可以扩展时先扩展谁?
爬山策略使用贪心方法来确定先扩展谁,是一种局部优化观念,不一定得到全局优化解
爬山策略运用启发式测度函数来进行局部优化选择
在这里插入图片描述

Best-First策略(全局优化BFS )

构造一个评价函数,在当前产生的所有节点中选择具有最小评价函数值的节点进行扩展,得到的解是全局优化解
注:若当前产生的节点中有评价函数相同的,则选择先产生的节点,因为使用的数据结构是用堆实现的优先队列
在这里插入图片描述

分支界限

使用爬山法或者BF算法得到一个可行解,作为优化解的一个界限,利用此界限对超过界限的即没必要继续搜索下去的分支进行剪枝

剪枝的运用:人员安排问题

  1. 符号的定义
    在这里插入图片描述
  2. 补充知识
    工作的偏序集:表示工作的安排有先后顺序
    由偏序集求拓扑排序的算法:Kahn算法,依次删掉入度为0的节点
    每一个对工作的拓扑排序对应了一种人员安排方案(人有先后顺序,即第一个安排的工作只能由P1来完成),即问题的一个可能解
    问题的树表示:用树来表示所有可能的拓扑序列
    在这里插入图片描述
  3. 问题的求解
    - 计算解的代价的下界
    e.g在这里插入图片描述

Q:第一步的目的?A:为了更快地剪枝

剪枝的运用:TSP问题(带权哈密顿回路)

  • 问题的定义
    在这里插入图片描述
    代价矩阵C[i,j]代表i号节点与j号节点的权值
    在这里插入图片描述

  • 问题的求解

  1. 构造根节点,通过使代价矩阵一些行或列同时减去同一个值计算根节点的代价下界
    在这里插入图片描述
    在这里插入图片描述

  2. 利用爬山法,寻找使路权值增加最小的边,即处理后代价矩阵的权值为0的点,但注意到有多个权值为0的边,为了方便剪枝,我们寻找一个选取后能让左右子树差距最大的边
    e.g 以边(4,6)为例,选取边(4,6)后,左子树为包含边(4,6)的回路,右子树为不包含边(4,6)的回路,而回路中如果不包含边(4,6),则必有一条从4出发的边和进入6的边,又用爬山法选取这两条边,选到的权值分别为32和0,此时选取边(4,6)作为DFS的边会使得左右子树的差距增加32+0=32,对其他矩阵中的权值为0的边同理分析,得到一个选取后能使左右子树差距最大的边作为下一个DFS选择的边,这样有利于更快地剪枝
    在这里插入图片描述

  3. 递归地构造左子树:左子树选取了边(4,6),因此删除权值矩阵的第4行和第6列,又从6出去的边不能再是4,因此将矩阵的(6,4)设置为无穷,再回到第一步,直到矩阵所有列和行全部删除

  4. 递归地构造右子树:右子树为不包含边(4,6)的解,因此把矩阵的(4,6)设置为无穷,再回到第二步

A*算法(BFS利用BF策略进行优化)

  1. 算法的适用范围:搜索空间十分庞大
  2. 算法的执行过程
  • 将BFS中的队列换成优先队列(最小堆)
  • 当队列不空时循环
  • 每次取优先队列的队头,当取的点是目标点时break
  • 队列中每个元素存储从起点到当前点的真实距离g(t)(不一定是最短)和从当前点到终点的估价距离h(t),将两个距离相加f(t)=h(t)+g(t)作为关键字来排序队头的所有邻边,取估计距离最小的点来扩展
  • 对队头的所有邻边,将邻边入队
  1. e.gA*算法求最短路径问题
    定义h(v)为节点v权值最小的出度的权,算法采用优先队列,f(t)小的先入队
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
此时虽然算法已经找出了一条最短路径,但节点T还没有被扩展,所以算法并没有结束
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
此时目标节点T被扩展,表明已经找到一条最短路径,算法结束

A*算法与分支界限算法的比较

A*的核心是告诉我们在某些情况下我们得到的解一定是优化解,所以算法可以停止,当算法选择的节点是目标节点时,表示算法结束
在这里插入图片描述

大礼包问题

问题的定义

在 LeetCode 商店中, 有 n 件在售的物品。每件物品都有对应的价格。然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。

给你一个整数数组 price 表示物品价格,其中 price[i] 是第 i 件物品的价格。另有一个整数数组 needs 表示购物清单,其中 needs[i] 是需要购买第 i 件物品的数量。

还有一个数组 special 表示大礼包,special[i] 的长度为 n + 1 ,其中 special[i][j] 表示第 i 个大礼包中内含第 j 件物品的数量,且 special[i][n] (也就是数组中的最后一个整数)为第 i 个大礼包的价格。

返回 确切 满足购物清单所需花费的最低价格,你可以充分利用大礼包的优惠活动。你不能购买超出购物清单指定数量的物品,即使那样会降低整体价格。任意大礼包可无限次购买。

问题的伪代码

ShoppingOffers(price,special,needs)
	n<-len(price);
	filter_special<-[];
	for sp in special do//过滤掉不满足条件的大礼包
		if sum (sp[i] for i<-0 to n-1)>0&&sum(sp[i]*price[i] for i<-0 to n-1)>sp[-1] do
			filter_special.append(sp);
	return dfs(price,filter_special,needs);//记忆化搜索 
dfs(price,filter_special,cur_needs)
	//不买大礼包原价购买物品
	min_price<-0;
	for i<-0 to n-1 do
		min_price<min_price+cur_needs[i]*price[i];
	//购买满足要求的大礼包
	for cur_special in filter_special do
		special_price<-cur_special[-1];
		rem_needs<-[];//剩余物品清单
		for i<-0 to n-1 do
			if cur_special[i]>cur_needs[i] do
				break;
			rem_needs.append(cur_needs[i]-cur_special[i]);
		if len(rem_needs)==n do//该大礼包可以购买
			min_price=min(min_price,dfs(price,filter_special,cur_needs)+special+price);
	return min_price;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值