算法设计技巧一 :贪心算法(Greedy Algorithm)
在第9章曾多次遇到贪心算法的应用,如解决单源最短路径的Dijkstra算法,最小生成树的Prim算法,最小生成树的Kruskal算法。贪心算法分阶段进行。在每一阶段可以认为所做的决定是最好的,而不考虑将来的结果。一般来说,这意味着选择是某个局部优的。这种“眼下能够拿到的就拿”的策略即是这类算法名称的来源。当算法结束时,我们希望局部最优就是全局最优。如果真是这样,算法就是正确的;否则,得到的是一个次最优解。如果不要求绝对的最佳答案,那么使用简单的贪心算法生成近似答案。
贪心算法的典型事例为找零钱问题(用最少数量的纸币凑出零钱总数)。更复杂的应用包括:非抢占式任务调度、霍夫曼编码、近似装箱问题、背包问题等。每一类问题又将产生相似的NP完整性问题,灵活多变,留下来极大地思考空间。
霍夫曼编码(Huffman Coding):
使用贪心算法思维的霍夫曼编码算法是一个很好的压缩编码/解码算法,在各类压缩算法中得到广泛应用。其基本思想是出现最多频率的字符用最少的编码位,从而节省资源。具体算法如下:
维护一个由树组成的森林。一棵树的权等于它的叶子的频率和。任意选取最小权的两棵树T1和T2,并任意形成以T1和T2为子树的新树,重复该过程C-1次(其中C为字符总数)。算法结束时的一棵树即为最优霍夫曼编码树。
行为描述(Action Description):
对于一组字符[a, b, c,e, f],其对应的频率统计为][45, 13, 12, 16, 9, 5],对字符按照频率升序顺序排列,实现霍夫曼编码过程如下图(自下而上生成霍夫曼树):
编码过程对应的霍夫曼树如下图:
霍夫曼编码算法编程实现:
//main.cpp
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
struct Tree{
char key;
int frequency;
Tree *left;
Tree *right;
//Tree constructor
Tree(char k = ' ', int f = 0, Tree *l = NULL, Tree *r = NULL):key(k), frequency(f), left(l), right(r){
};
};
struct LargerThan{
bool operator()(Tree *first, Tree *second){
return first -> frequency > second -> frequency;
}
};
priority_queue<Tree*, vector<Tree*>, LargerThan> priQueue;
//print the result of Huffman coding
void printCode(Tree *root, string str);
//delete the tree
void deleteTree(Tree *root);
//huffman algorithm
void huffman(vector<char> &kArray, vector<int> &fArray);
int main(){
ifstream fin("input.txt");
char k;
int f;
vector<char> kArray;
vector<int> fArray;
while(fin>>k){
kArray.push_back(k);
fin >> f;
fArray.push_back(f);
}
fin.close();
cout << "The info as follows : " << endl;
for(int i = 0; i < kArray.size(); i++){
cout << kArray[i] << " : " << fArray[i] << endl;
}
huffman(kArray, fArray);
cout << "done ." << endl;
return 0;
}
void huffman(vector<char> &kArray, vector<int> &fArray){
//create sigle tree group as froest
for(int i = 0; i < kArray.size(); i++){
Tree *root = new Tree(kArray[i], fArray[i]);
priQueue.push(root);
}
//merge the forest as sigle tree
while(priQueue.size() > 1){
Tree *l;
Tree *r;
l = priQueue.top();
priQueue.pop();
r = priQueue.top();
priQueue.pop();
Tree *root = new Tree('\0', l -> frequency + r -> frequency, l, r);
priQueue.push(root);
}
string str = "";
printCode(priQueue.top(), str);
deleteTree(priQueue.top());
}
void deleteTree(Tree *root){
if(root == NULL){
return;
}
deleteTree(root -> left);
deleteTree(root -> right);
delete root;
}
void printCode(Tree *root, string str){
if(root == NULL){
return;
}
//assign the left child coding
if(root -> left){
str += '0';
}
printCode(root -> left, str);
//print the leaf coding
if(!root -> left && !root -> right){
cout << root -> key << " 's Huffman code is : " << str << endl;
}
str.pop_back();
//assign the right child coding
if(root -> right){
str += '1';
}
printCode(root -> right, str);
}
//Note: compile with : g++ -std=c++11 -o main main.cpp
//input.txt
a 45
b 13
c 12
d 16
e 9
f 5
实验结果:
practice makes perfect !