uva - 10954 - Add All(优先队列、哈夫曼编码思想)

题意:题目名称就是你要做的任务:把一些数加起来。但是这对你来说一定是太简单了,所以让我们加一些东西在里面。

做加法要付出的代价(cost) 定义为这2个数的总和,所以要加1 和10 所需付出的代价为11 。假如你想要加1, 2 和3,那么有以下几种方法:


1 + 2 = 3, cost = 3 
3 + 3 = 6, cost = 6 
Total = 9

1 + 3 = 4, cost = 4 
2 + 4 = 6, cost = 6 
Total = 10

2 + 3 = 5, cost = 5 
1 + 5 = 6, cost = 6 
Total = 11


任务就是把几个数加起来使得代价最小。

方法1:优先队列 priority_queue。参考 http://blog.youkuaiyun.com/frankiller/article/details/7913697

优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素。但是它有一个特性,就是队列中最大的元素总是位于队首,所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大的元素出队。这点类似于给队列里的元素进行了由大互小的顺序排序。元素的比较规则默认按元素值由大到小排序,可以重载“<”操作符来重新定义比较规则。

#include <iostream>  
#include <iomanip>  
#include <string>  
#include <cstring>  
#include <cstdio>  
#include <queue>  
#include <stack>  
#include <algorithm>  
#include <cmath>  
#include <ctime>

using namespace std;

struct cmp
{
	bool operator() (const int &a, const int &b)
	{
		return a > b;
	}
};//重载

priority_queue<int, vector<int>,cmp>q;//定义

int main() 
{
#ifdef Local    
	freopen("a.in", "r", stdin);    
#endif
	int n = 0, i = 0;
	while(cin >> n && n)
	{
		int x = 0, y = 0, cost = 0;
		for (i = 1; i <= n; i++)
		{
			cin >> x;
			q.push(x);
		}
		while(q.size() > 1)
		{
			y = q.top();
			q.pop();
			y += q.top();
			q.pop();
			cost += y;
			q.push(y);
		}
		q.pop();//注意,必须有这个,要保证在下一个test case的时候q为空,不然会影响下一个case的结果。
		cout << cost << endl;
	}
	return 0;
}
STL的都不太懂,太在做题中积累啊!


方法2:“哈尔曼编码的算法。实质就是每次两个最小的相邻数相加,直到只剩一个数为止,然后把过程中所有的和加起来,较难处理的点就是每次加完了要保证下次相加的必须是最小的两个数。
在这里因为看见数据量比较小(5000个),因此用的O(n*n)的办法做的,就是每次做完和都找到相应位置将数字插进去,前面的数依次向前移动一位,以此保证整个序列始终有序;本来想用排序做,但想想如果每加一次qsort排一次序的话,复杂度为(n*n*logn),会更加费时。” ------ http://blog.youkuaiyun.com/goomaple/article/details/7823607

和我下面TLE的思想一样,但是我是每次都快排,所以超时间了,可以按照上边说的,求和后找位置插入,比快排要快些。

附上别人的代码

#include<iostream>  
#include<cstdlib>  
#include<cstring>  
#include<cstdio>  
#include<cmath>  
using namespace std;  
const int maxn = 5002;  
int cmp(const void *a, const void *b)  
{  
    return *(int *)a - *(int *)b;  
}  
int main()  
{  
#ifdef test  
    freopen("in.txt", "r", stdin);  
#endif  
    int num;  
    int a[maxn];  
    while(scanf("%d", &num), num)  
    {  
        long long sum = 0;  
        for(int i = 0; i < num; i++)  
            scanf("%d", &a[i]);  
        qsort(a, num, sizeof(a[0]), cmp);  
        for(int i = 0; i < num - 1; i++)  
        {  
            int ans = a[i] + a[i + 1];  
            sum += ans;  
            a[i + 1] = ans;  
            for(int j = i + 1; j < num; j++)  
                if(ans < a[j])   
                {  
                    for(int k = i + 1; k < j - 1; k++) // 找到元素位置将其插入,前面的元素依次向前移一位  
                        a[k] = a[k + 1];  
                    a[j - 1] = ans;  
                    break;  
                }  
                else if(j == num - 1)  
                {  
                    for(int k = i + 1; k < num - 1; k++)  
                        a[k] = a[k + 1];  
                    a[num - 1] = ans;  
                    break;  
                }  
        }  
        printf("%lld\n", sum);  
    }  
    return 0;  
}  


其他:在所有的数中不断选最小相加。结果TLE了!

附TLE代码:

#include <iostream>  
#include <iomanip>  
#include <string>  
#include <cstring>  
#include <cstdio>  
#include <queue>  
#include <stack>  
#include <algorithm>  
#include <cmath>  
#include <ctime>

using namespace std;

const int maxn = 5000+10;
int num[maxn];

int cmp(const void *a, const void *b)
{
	return *(int *)b - *(int *)a;
}

int main() 
{
#ifdef Local    
	freopen("a.in", "r", stdin);    
#endif
	int n = 0;
	while(cin >> n && n)
	{
		memset(num, 0, sizeof(num));
		int i = 0, sum = 0, k = 1, t = 0;
		for(i = 0; i < n; i++)
			cin >> num[i];
		qsort(num, n, sizeof(num[0]), cmp);
		for (i = 0, t = n; i < t-1; i++)
		{
			sum += (num[n-1]+num[n-2]);
			num[n-2] += num[n-1];
			n--;
			qsort(num, n, sizeof(num[0]), cmp);
		}
		cout << sum << endl;
	}
}


<think>首先,用户的问题是:“我想了解优先队列哈夫曼编码中的应用 请问优先队列哈夫曼编码中的作用及实现方法”。系统指令要求我使用中文回答,并遵循特定的格式:行内数学表达式用$...$,独立公式用$$...$$,语法正确,生成相关问题,并在引用末尾添加引用标识。 用户提供了两个引用: - 引用[1]:描述了一个优先队列的实现,用于哈夫曼编码,基于权重建立小顶堆。 - 引用[2]:解释了哈夫曼编码的基本概念,并提到用优先队列实现。 我的回答需要结构清晰,逐步解决问题,保证真实可靠。系统指令还要求生成相关问题。 关键点: - **哈夫曼编码**:一种用于数据压缩的编码方式,基于字符频率构建最优二叉树(哈夫曼树),使频率高的字符有较短的编码。 - **优先队列**:一种数据结构,支持高效地插入和删除最小元素提取(小顶堆)。在哈夫曼编码中,优先队列用于选择频率最小的节点进行合并。 **作用**: - 在构建哈夫曼树时,优先队列用于存储所有字符节点(叶子节点)。 - 算法步骤:从队列中提取两个频率最小的节点,合并它们(创建一个新节点,频率为两者之和),并将新节点放回队列。 - 重复此过程,直到队列中只剩一个节点,即哈夫曼树的根节点。 - 优先队列确保每次都能高效地找到最小频率节点,使得整个构建过程高效(时间复杂度O(n log n),其中n是字符种类数)。 **实现方法**: - 使用优先队列(如Java中的PriorityQueue),基于节点的频率(权重)建立小顶堆。 - 引用[1]给出了Java实现的示例:使用PriorityQueue,并自定义比较器以基于节点的weight属性排序。 - 基本步骤: 1. 为每个字符创建一个节点,包含字符和频率。 2. 将所有节点加入优先队列。 3. 循环:提取两个最小节点,合并它们(创建父节点,频率为子节点之和),将新节点加入队列。 4. 当队列大小为1时停止,得到哈夫曼树。 - 然后,基于哈夫曼树生成编码:从根到叶子,左分支为0,右分支为1。 在回答中,我应该自然地引用提供的引用。引用[1]直接相关,引用[2]提供背景。 **回答结构**: 1. **介绍哈夫曼编码**:简要解释是什么,为什么用优先队列。 2. **优先队列的作用**:详细说明在构建过程中的作用。 3. **实现方法**:描述步骤,并引用代码示例(如引用[1])。 4. **代码示例**:如果需要,可以添加一个简单的代码片段(如Python或Java),但要确保格式正确(用```...```)。 5. **生成相关问题**:在末尾添加相关问题。 系统指令要求生成相关问题,如示例所示。 **数学表达式**: - 如果有频率或概率,可以用行内数学表达式,如频率$f_i$。 - 哈夫曼编码的目标是最小化平均编码长度:$L = \sum p_i l_i$,其中$p_i$是概率,$l_i$是编码长度。 - 在构建树时,节点权重是频率或概率。 **引用标识**: - 在引用末尾添加[^1]或[^2],如“...实现高效排序[^1]”。 基于引用[1],我可以用它作为例子。 最终回答大纲: 1. 解释哈夫曼编码的基本概念。 2. 描述优先队列在其中的作用。 3. 提供实现方法,包括代码示例(参考引用[1])。 4. 生成相关问题。</think>### 优先队列哈夫曼编码中的应用 哈夫曼编码是一种高效的数据压缩算法,由David Huffman于1952年提出,它基于字符出现的频率构建最优二叉树(哈夫曼树),使高频字符的编码更短,低频字符的编码更长,从而最小化平均编码长度$L = \sum p_i l_i$(其中$p_i$是字符频率,$l_i$是编码长度)[^2]。优先队列(通常实现为小顶堆)在哈夫曼树构建过程中扮演核心角色,确保算法高效运行。下面我将逐步解释其作用及实现方法。 #### 1. **优先队列哈夫曼编码中的作用** - **核心作用**:在构建哈夫曼树时,优先队列用于动态管理和选择频率最小的节点。算法反复从队列中提取两个最小权重的节点(代表字符或子树),合并它们为一个新节点(权重为子节点之和),并将新节点放回队列。这一过程持续到队列中只剩一个节点,即哈夫曼树的根节点。优先队列的$O(\log n)$插入和提取操作保证了整个构建过程的时间复杂度为$O(n \log n)$(其中$n$是字符种类数),远优于朴素排序方法。 - **优势**: - **高效性**:优先队列(小顶堆)确保每次提取最小元素的操作高效,避免了全局排序的开销。 - **动态管理**:队列在合并过程中动态更新,适应哈夫曼树的增量构建。 - **最优性保障**:通过贪心策略,优先队列帮助生成平均编码长度最短的树,符合哈夫曼编码的“最佳编码”特性[^2]。 #### 2. **优先队列的实现方法** 在哈夫曼编码中,优先队列通常基于节点的权重(频率)实现小顶堆(min-heap)。以下是通用的实现步骤,结合代码示例(以Java为例,参考引用[1]): - **步骤说明**: 1. **初始化节点**:为每个字符创建叶子节点,包含字符值和权重(频率)。 2. **构建优先队列**:将所有节点加入优先队列,队列根据节点权重升序排列(小顶堆)。 3. **合并节点**:循环执行以下操作,直到队列大小为1: - 提取两个权重最小的节点(`queue.poll()`)。 - 创建新父节点,权重为子节点权重之和,并将子节点作为左右子树。 - 将新节点加入队列。 4. **生成编码**:最后剩余的节点即为哈夫曼树的根节点。从根节点遍历树,分配编码(左分支为0,右分支为1)。 - **代码示例**(Java实现,基于引用[1]): ```java import java.util.PriorityQueue; import java.util.Comparator; // 定义哈夫曼树节点 class TreeNode { char data; // 字符(叶子节点使用) int weight; // 权重(频率) TreeNode left, right; // 左右子树 public TreeNode(char data, int weight) { this.data = data; this.weight = weight; this.left = this.right = null; } } public class HuffmanCoding { public static TreeNode buildHuffmanTree(char[] chars, int[] freqs) { // 创建优先队列,基于权重建立小顶堆(参考引用[1]) PriorityQueue<TreeNode> queue = new PriorityQueue<>(chars.length, new Comparator<TreeNode>() { @Override public int compare(TreeNode o1, TreeNode o2) { return Integer.compare(o1.weight, o2.weight); // 小顶堆:权重小的优先 } }); // 初始化队列:添加所有叶子节点 for (int i = 0; i < chars.length; i++) { queue.add(new TreeNode(chars[i], freqs[i])); } // 构建哈夫曼树:合并节点 while (queue.size() > 1) { TreeNode left = queue.poll(); // 提取最小权重节点 TreeNode right = queue.poll(); // 提取次小权重节点 TreeNode parent = new TreeNode('\0', left.weight + right.weight); // 创建父节点 parent.left = left; parent.right = right; queue.add(parent); // 新节点加入队列 } return queue.poll(); // 返回根节点 } // 生成编码的方法(此处省略,需递归遍历树) public static void main(String[] args) { char[] chars = {'a', 'b', 'c', 'd'}; int[] freqs = {5, 9, 12, 13}; // 示例频率 TreeNode root = buildHuffmanTree(chars, freqs); // 后续可调用函数生成编码 } } ``` - **关键点解析**: - **优先队列配置**:使用`PriorityQueue`并自定义`Comparator`,确保队列按节点权重升序排列(小顶堆),每次`poll()`提取最小元素[^1]。 - **效率**:插入和提取操作的平均时间复杂度为$O(\log n)$,整体$O(n \log n)$。 - **语言无关性**:类似实现可在Python(使用`heapq`模块)、C++(使用`priority_queue`)等语言中完成。 #### 3. **实际应用与注意事项** - **应用场景**:哈夫曼编码广泛用于文件压缩(如ZIP、JPEG)、数据传输优化,以及任何需要减少冗余数据的场景。 - **注意事项**: - 优先队列的选择影响性能:小顶堆实现优于排序列表。 - 权重相等时的处理:需定义稳定比较规则(如按字符值排序)。 - 内存优化:对于大数据集,可使用懒加载或外部存储。 通过优先队列哈夫曼编码高效实现了无损压缩,其核心在于贪心算法与数据结构的结合。如果您有具体代码语言需求,我可以提供更多实现细节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值