《算法导论》第16章 贪心算法 个人笔记

第16章 贪心算法

16.1 活动选择问题

问题:假设有一个n个活动的集合 S=a1,a2,...,an ,这些活动使用同一个资源,而这个资源在某个时刻只能供一个活动使用。每个活动都有一个开始时间 si 和一个结束时间 fi 。若 sifi sjfi ,则 ai aj 是兼容的。在活动选择问题中,我们希望选出一个最大兼容活动集。假设活动已按结束时间的单调递增顺序排序。

1、动态规划法

Sij 表示在 ai 结束之后开始。且在 aj 开始之前结束的那些活动的集合,用 c[i,j] 表示集合 Sij 的最优解的大小,故有

c[i,j]=0,ifSij=ϕc[i,j]=maxakSijc[i,k]+c[h,j]+1,ifSijϕ

2、贪心选择

定理:考虑任意非空子问题 Sk ,令 am Sk 中结束时间最早的活动,则 am Sk 的某个最大兼容活动子集中。
证明:令 Ak Sk 的一个最大兼容子集,且 aj Ak 中结束时间最早的活动。
aj=am ,即证。
ajam ,将 Ak 中的 aj 替换为 am ,因为 fmfj ,则还是兼容的,数目不变,故替换后的也是 Sk 的一个最大兼容活动子集,且包含 am
- 递归贪心算法
为方便初始化,添加一个虚拟活动 a0 ,其结束时间 f0=0

public List<Integer> RECURSIV_ACTIVITY_SELECTOR(int[] s,int[] f,int k,int n){
    int m = k + 1;
    while(m<=n && s[m]<=f[k])
        m++;
    List<Integer> res = new ArrayList<>();
    if(m<=n)
        return res.add(m).addAll(RECURSIV_ACTIVITY_SELECTOR(int[] s,int []f,int k,int n))
    else
        return res;
}
  • 迭代贪心算法
public List<Integer> GREED_ACTIVITY_SELECTOR(int[] s,int[] f){
    int n = s.length - 1; //去掉虚拟活动
    List<Integer> res = new ArrayList<>();
    res.add(1);
    int k = 1;
    for(int i = 2; i<=n; i++){
        if(s[m]>=f[k]){
            res.add(m);
            k = m;
        }
    }
    return res;
}

16.2 贪心算法原理

一般地,我们按如下步骤设计贪心算法:
1. 将最优化问题转化为这样的形式:对其做出一次选择后,只剩下一个子问题需要求解。
2. 证明做出贪心选择后,原问题总是存在最优解,即贪心选择总是安全的。
3. 证明做出贪心选择后,剩余的子问题满足性质:其最优解与贪心选择组合即可得到原问题的最优解,这样就得到了最优子结构。

贪心对动态规划

0-1背包问题:动态规划

分数背包问题:贪心

/**
 * @param m 表示背包的最大容量
 * @param n 表示商品个数
 * @param w 表示商品重量数组
 * @param p 表示商品价值数组
 */
public static int[][] Package1(int m, int n, int[] w, int[] p) {
    //c[i][v]表示前i件物品恰放入一个重量为m的背包可以获得的最大价值
    int c[][] = new int[n + 1][m + 1];
    for (int i = 0; i < n + 1; i++)
        c[i][0] = 0;
    for (int j = 0; j < m + 1; j++)
        c[0][j] = 0;

    for (int i = 1; i < n + 1; i++) {
        for (int j = 1; j < m + 1; j++) {
            //当物品为i件重量为j时,如果第i件的重量(w[i-1])小于重量j时,c[i][j]为下列两种情况之一:
            //(1)物品i不放入背包中,所以c[i][j]为c[i-1][j]的值
            //(2)物品i放入背包中,则背包剩余重量为j-w[i-1],所以c[i][j]为c[i-1][j-w[i-1]]的值加上当前物品i的价值
            if (w[i - 1] <= j) {
                if (c[i - 1][j] < (c[i - 1][j - w[i - 1]] + p[i - 1]))
                    c[i][j] = c[i - 1][j - w[i - 1]] + p[i - 1];
                else
                    c[i][j] = c[i - 1][j];
            } else
                c[i][j] = c[i - 1][j];
        }
    }
    return c;
}
//用一维数组存储
public static int Package2(int m, int n, int[] w, int[] p) {
    int[] f = new int[m + 1];
    for (int i = 1; i < f.length; i++)    //必装满则f[0]=0,f[1...m]都初始化为无穷小
        f[i] = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
        for (int j = f.length - 1; j >= w[i]; j--) {
            f[j] = Math.max(f[j], f[j - w[i]] + p[i]);
        }
    }
    return f[m];
}

在具体运用时,可以首先假设全部物品装进背包是否超载,若不超载则直接输出最优解。有一次某公司一道笔试题就是这样,直接上dq就超时gg了=。=

16.3 赫夫曼编码

赫夫曼编码用于压缩数据,根据每个字符的出现频率构造最优二进制表示。
前缀码:没有任何码字是其他码字的前缀。
构造赫夫曼编码,使用一个以属性freq为关键字的最小优先队列Q。

HUFFMAN(C)
n = C.length
Q = C
for i =1 to n-1
    allocate a new node z
    z.left = x = EXTRACT-MIN(Q)
    z.right = y = EXTRACT-MIN(Q)
    z.freq = x.freq + y.freq
    INSERT(Q,z)
return EXTRACT-MIN(Q) //return the root of the tree

定理:过程HUFFMAN会生成一个最优前缀码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值