【算法分析与设计】第六章-贪心法

一、知识铺垫

最优化问题
       最优化问题是指这样一类问题:问题给定某些约束条件,满足这些约束条件的问题的解称为可行解。如果可行解只有一个,那么该解就是最优解;否则,引入某个数值函数,称为目标函数,用来衡量可行解的好坏。使得目标函数取极值(最大、最小)的可行解称为最优解

二、什么是贪心法

      贪心法是一种求解最优化问题的算法设计策略,通过分步决策来求解问题。贪心法在求解问题的每一步都要依靠一种策略进行决策,这种策略称为最优量度标准(贪心选择性质、贪心准则)。通过局部最优推出全局最优。

三、贪心法的使用场景

    求解最优化问题。问题具备两种特性:最优量度标准最优子结构。例如:部分背包问题、活动选择问题、带时限的作业排序问题、最佳合并模式(Haffman)、最小生成树MST(Prim、Kruskal)、单源最短路径(Dijkstra)。

四、贪心法的解题步骤

SolutionType Greedy(SType a[], int n){
	SolutionType solution = NULL:
	for(int i = 0; i < n; i ++){
		SType x = select(a);	// 通过贪心选择性质挑选该步的解分量
		if(Feasilble(solution, x)	//加入分量后,如果满足约束条件
			solution = Union(solution, x);	// 将分量x加入解向量
	}
}

五、典例

  1. 部分背包问题
    最优量度标准:每次挑选p/w最大的物品放入背包。
int n, m;
double  x[N];
void knapsack(){
	// 所以物品已经按p/w非递增排序
	double u = m;	// 剩余背包体积
	int i;
	for( i = 0; i < n; i ++){
		if(u >= w[i]){
			u -= w[i];
			x[i] = 1;
		}
	}
	if(i < n) x[i] = u/w[i];	//  如果没装满
}
  1. 带时限作业排序问题
    最优量度标准:每次挑选价值最高的作业加入。然后进行可行性检测。
int x[N], n;
void JS(){
	int k = 0;
	x[0] = 0;
	for(int i = 1; i < n; i ++){	// 枚举每一个作业 
		int r = k;
		auto t = job[i];
		while(r >= 0 && job[x[r]].d > t.d && job[x[r]].d > r + 1) r --;	//寻找插入位置
		if((r < 0 || job[x[r]].d <= t.d) && t.d > r + 1){	// 判断是否超限,可以插入 
			for(int j = k; j >= r + 1; j --)	// 插入位置及以后的作业右移
				x[j + 1] = x[j];
			x[r + 1] = i;	// 插入作业
			k ++;
		}
	}
}
  1. 最小生成树问题
    最优量度标准:每次挑选距离增量最小的边加入边集S,对于其不同解释,产生了Prim、Kruskal两种算法。
  • Prim
    原题链接:https://www.acwing.com/problem/content/description/860/
#include 
using namespace std;
const int N = 510;
int g[N][N];
bool st[N];
int dist[N];
int n, m;
long long ans;
bool prim(){
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for(int i = 0; i < n; i ++){
        int t = -1;
        for(int j = 1; j <= n; j ++){
            if(!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        }
        if(dist[t] == 0x3f3f3f3f) return false; 
        st[t] = true;
        ans += dist[t];
        for(int j = 1; j <= n; j ++)
            dist[j] = min(g[t][j], dist[j]);
    }
    return true;
}
int main(){
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);
    while(m --){
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    if(prim()) cout << ans;
    else puts("impossible");
    return 0;
}

  • Kruskal
    原题链接:https://www.acwing.com/problem/content/861/
#include 
#include 
#include 
#include 
using namespace std;
const int N = 1e5 + 10;
const int M = 2e5 + 10;
int p[N];
struct Edge{
    int a, b, c;
    bool operator<(const Edge &e){
        return c < e.c;
    }
};
long long cnt = 0, sum;
int find(int x){
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}
int main(){
    int n, m;
    cin >> n >> m;
    vector v;
    for(int i = 1; i <= m; i ++){
        int a, b, c;
        cin >> a >> b >> c;
        v.push_back({a,b,c});
    }
    for(int i = 1; i <= n; i ++) p[i] = i;
    sort(v.begin(),v.end());
    for(auto t:v){
        int pa = find(t.a);
        int pb = find(t.b);
        if(pa == pb) continue;
        p[pa] = pb;
        cnt ++;
        sum += t.c;
        if(cnt == n - 1) break;
    }
    if(cnt == n - 1) cout << sum;
    else puts("impossible");
}

  1. 最短路径问题
    Dijkstra
    最优量度标准:使得从s(源点)到其他所有点的代价增量之和最小。也就是求距离s第一短的路径
    、除去之前的点第一短的…
    原题链接:https://www.acwing.com/problem/content/description/851/
#include 
#include 
#include 
#define INF 0x3f3f3f3f
using namespace std;
const int N = 510;
int g[N][N], dist[N];
bool vis[N];
int n, m;
typedef long long LL;

void dijkstra(){
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for(int i = 0; i < n; i ++){
        int t = -1;
        for(int j = 1; j <= n; j ++)
            if(!vis[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
        vis[t] = true;
        for(int j = 1; j <= n; j ++)
            dist[j] = min(dist[j], g[t][j] + dist[t]);
    }
}
int main(){
    memset(g, 0x3f, sizeof g);
    cin >> n >> m;
    while (m -- ) {
        int a, b , c;
        cin >> a >> b >> c;
        g[a][b]  = min(g[a][b], c);
    }
    dijkstra();
    if(dist[n] == INF) cout << -1;
    else cout << dist[n];
    return 0;
}

六、总结

        不同的贪心选择性质可能会导致不同的解。最优贪心选择性质可能不存在,因为贪心法是一种短视的算法,每步只考虑当前的最佳选择,不一定总能产生最优解。如果找不到最优量度标准,可以考虑使用DP求解。

另外:贪心法的正确性需要证明,通常通过归纳法或者 cut & paste 方法。

你可能感兴趣的:(算法分析与设计,算法,c++,图论)