【笔记】贪心(2)---哈夫曼树、排序不等式、绝对值不等式、推公式类


贪心问题

贪心问题很多都需要证明,但题目背后往往是那几个经典的类型。笔记用于积累,越做越有。


一、哈夫曼树

哈夫曼树的构造

给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。

哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。哈夫曼树是贪心算法的经典例子:总是从森林中选取两颗根节点权值最小的二叉树,作为一棵树新的左右子树,新二叉树的根节点的权值为左右子树根节点权值之和。从森林中删除原二叉树,并将新树加入到森林中。重复此过程直到森林中只有一颗二叉树。

算法实现

使用小根堆(优先队列)实现,这样总能选取到根节点权值最小的两颗二叉树。堆中存储的对象是权值。

#include<iostream>
#include<algorithm>
#include<queue>

using namespace std;

int main(){
    
    int n;
    scanf("%d",&n);
    
    priority_queue<int,vector<int>,greater<int>> heap;
    
    while(n--){
        int x;
        scanf("%d",&x);
        heap.push(x);
    }
    
    int res = 0;
    
    while(heap.size()>1){
        int a=heap.top();heap.pop();
        int b=heap.top();heap.pop();
        
        res+=a+b;
        heap.push(a+b);
        
    }
    
    printf("%d\n",res);
    

    return 0;
}

二、排序不等式

排序不等式分析

排队打水问题是经典的排序不等式问题:
要保证打水的总等待时间最短,则打水时间较短的人先打水。
类比短进程优先调度算法( SJF )可使进程的平均等待时间最短。

算法实现

思路1:打水时间最短的被等待的次数最多。
思路2:打水时间最长的要等待之前所有打水完成之后才开始。

#include<iostream>
#include<algorithm>

using namespace std;

typedef long long LL;

const int N=100010;
int n,a[N];

int main(){
    cin>>n;
    for(int i=0;i<n;i++)
        cin>>a[i];
    
    sort(a,a+n);
    
    LL res=0;
    for(int i=n-1,j=0;i>=0;i--,j++)
        res+=j*a[i];
            
    cout<<res<<endl;
    return 0;
}

绝对值不等式问题

绝对值不等式分析

货仓选址问题是经典的绝对值不等式问题:
对于两个点: ∣ x − a ∣ + ∣ x − b ∣ > = ∣ a − b ∣ |x-a|+|x-b|>=|a-b| xa+xb>=ab等号成立的条件是 x 位于两个点之间(包括端点)。

对于 n 个点则分组考虑:货仓在x坐标处,x 左侧的商店有 P 家,x 右侧的商店有 Q 家。当P == Q时为最优解(总是位于左右两个点之间)。n 为奇数时,x 在n个点的中位数上为最佳位置;n 为偶数时,x 最中间两个点之间任意位置。

该问题还可以推广到 2 维、3 维(三分)、n 维(模拟退火)等。

算法实现

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int n,a[N];

int main(){
    cin>>n;
    for(int i=0;i<n;i++)    cin>>a[i];
    sort(a,a+n);
    int res=0;
    for(int i=0;i<n;i++)
        res+=abs(a[i]-a[n/2]);
    cout<<res<<endl;
    return 0;
}

推公式类问题

此类问题需要结合具体题目分析,如国王游戏、耍杂技的牛等问题。
无论公式如何,贪心问题先排序

### 哈夫曼哈夫曼编码概述 哈夫曼是一种特殊的二叉,用于实现最优前缀编码方案。这种编码方式能够有效地压缩数据,在通信领域有着广泛的应用。 #### 构建哈夫曼的原则 构建哈夫曼遵循以下原则: - 将给定的一组具有不同权重(通常代表字符出现频率)的叶子节点组成森林; - 每次从未加入到当前中的最小两个权值结点创建一个新的父节点,并将其作为这两个子节点的父亲节点; - 把新建立起来的这个内部节点重新放回未处理集合中继续参与后续操作直到只剩下一个根节点为止[^1]。 #### 哈夫曼编码的特点 哈夫曼编码具备如下特性: - **唯一可解性**:由于采用的是前缀性质的编码规则,即任何一个字符对应的编码都不是其他任何字符编码串的前缀,因此可以确保译码过程不会产生歧义。 - **最短路径优先**:对于频繁使用的符号赋予较短长度的编码序列,而较少见的情况则分配较长位数表示,以此达到整体上减少传输量的目的[^2]。 #### PTA平台下的具体应用实例 在PTA平台上关于哈夫曼哈夫曼编码的任务描述指出,当构造此特殊形式的二叉查找表时需满足特定条件——左孩子结点权值不大于右孩子结点权值;如果遇到相同情况,则依据先进先出原则选取最先被移除的那个元素来进行组合形成新的双亲节点。此外还明确了左右分支分别对应'0''1'[^3]。 ```python from heapq import heappush, heappop def huffman_tree(frequencies): heap = [[weight, [symbol, ""]] for symbol, weight in frequencies.items()] while len(heap) > 1: lo = heappop(heap) hi = heappop(heap) for pair in lo[1:]: pair[1] = '0' + pair[1] for pair in hi[1:]: pair[1] = '1' + pair[1] heappush(heap, [lo[0]+hi[0]] + lo[1:] + hi[1:]) return sorted(heappop(heap)[1:], key=lambda p: (len(p[-1]), p)) frequencies = {'a': 45, 'b': 13, 'c': 12, 'd': 16, 'e': 9, 'f': 5} huff_code = dict(huffman_tree(frequencies)) print("Symbol".ljust(10) + "Frequency".ljust(10) + "Huffman Code") for symbl in frequencies.keys(): print(symbl.ljust(10), str(frequencies[symbl]).ljust(10), huff_code[symbl]) ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值