贪心算法(greedy algorithm)就是这样的算法,它在每一步都做出在当时看起来最佳的选择。也就是说,它总是做出局部最优的选择,寄希望这样的选择能导致全局最优解。
但需要注意的是:
贪心算法并不保证得到最优解。
当将贪心算法应用于特殊的一个问题时,需要特别注意贪心性质的正确性:它是否能保证得到最优解。
所有的算法其实并不是书上描述的那样简单,需要对问题的理解、算法的分析、算法的证明。The idea inside algorithm is more important。
问题描述见算法导论,这里直接给出代码:
/*!
* @param s a pointer point to an array containing task begin times
* @param f a pointer point to an array containing task end times
* @param k an array index indicates the beginning of sub-question
* @param n an number indicates the scale of question(namely how much elements inside former array)
* @return vector a vector containing task index of answers
*/
vector RECURSIVE_ACTIVITY_SELECTOR (int *s, int*f, int k, int n)
{
int m = k + 1;
//find the first activity in Sk to finish
while (m <= n && s[m] < f[k])
++m;
auto result = vector ();
if (m <= n)
{
result.push_back (m);
for (auto const& it: RECURSIVE_ACTIVITY_SELECTOR (s, f, m, n))
result.push_back (it);
}
//return null if none
return result;
}
书中已证明贪心算法的可行性,这里不多做介绍。该版本的迭代版本也不在这里给出,思路是一样的,只是将尾递归化为迭代。
以下是简介:
赫夫曼编码可以很有效的压缩数据:通常可以节省20% ~ 90%的空间,具体压缩率依赖于数据的特性。我们将待压缩数据看作字节序列。根据每个字符的出现频率,赫夫曼贪心算法构造出字符的最优二进制表示。
表达信息的方式有很多种,这里涉及到二进制字符编码, 即将字符用二进制码来表示,产生一个从字符到二进制的映射。如图所述,采用变长编码 可能比 定长编码会有更好的压缩率。实际上,该种编码方式依赖于字符的已知信息—-出现频率。在频率已知的情况下有较好的压缩率。
我们这里只考虑所谓的前缀码,即没有任何码字是其它码字的前缀。
前缀码确实可以保证达到最优数据压缩率.
前缀码的作用是简化解码过程由于没有码字是其它码字的前缀,编码文件的开始字码是无歧义的。我们可以简单的识别出开始字码,将其转换回原字符,然后对编码文件剩余部分重复这种解码过程。
这里简单解释,若不是前缀码,在编码时不会有多余的工作,但在解码时必须处理前缀问题。即一个码字可能是一个字符的码字,也可能是另一个字符的前缀,因而在解码时必须处理这两种情况。
解码过程需要前缀码的一种方便的表示形式,以便我们可以容易的截取开始码字。一种二叉树表示可以满足这种需求,其中叶节点为给定的字符。字符的二进制码字用从根结点到该字符叶节点的简单路径表示,其中0意味着转向“左孩子”,1意味着转向“右孩子”.
可以看到,变长编码主要是利用字符出现频率已知的信息,将频率高的字符放在二叉树高处,减少二进制码字的长度,相对的,频率低的字符的二进制码字长度变大 。所以说,采用变长编码只是可能比定长编码有较好的压缩率。
以下为代码:
/*node containing frequent number, there omit character,
please compare the freq num to recognize*/
typedef struct node_tag
{
node_tag *left,
*right;
int freq;
}Node, *PNode;
//extract node pointer containing minimun frequent number
PNode EXTRACT_MIN (vector& Q)
{
//assume not empty
PNode ptr = Q.front ();
auto iterator = Q.begin ();
//pick minimun
for (auto it = Q.begin (); it != Q.end (); ++it)
if ((*it)->freq < ptr->freq)
ptr = *it,
iterator = it;
//erase the one picked
Q.erase (iterator);
return ptr;
}
VOID INSERT (vector& Q, PNode z)
{
Q.push_back (z);
}
PNode Huffman (int *array, int num)
{
//initial
vector Q;
for (int i = 0; i < num; ++i)
{
PNode z = new Node ();
z->left = nullptr;
z->right = nullptr;
z->freq = array[i];
Q.push_back (z);
}
//core body
for (int i = 1; i < num; ++i)
{
PNode z = new Node ();
z->left = EXTRACT_MIN (Q);
z->right = EXTRACT_MIN (Q);
z->freq = z->left->freq + z->right->freq;
INSERT (Q, z);
}
return EXTRACT_MIN (Q);
}
//!!!!!!!! 代码仅作示范,未释放堆内存 !!!!!!!!!//////
因为频率低的字符在二叉树地处,所以Huffman(...)
函数自底向上构建二叉树.代码不再赘述。
Fuffman(...)
采用贪心算法,其正确性在算法导论中给出了证明。
详见算法导论。