一、基本概念:
所谓贪心算法是指,在对问题求解时,总是做出在 当前看来是最好的选择 。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的 局部最优解 。
贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。
二、贪心算法的基本思路:
1.建立数学模型来描述问题。
2.把求解的问题分成若干个子问题。
3.对每一子问题求解,得到子问题的局部最优解。
4.把子问题的解局部最优解合成原来解问题的一个解。
三、贪心算法适用的问题
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
实际上,贪心算法适用的情况很少。一般,对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可做出判断。
四、贪心算法的实现框架
从问题的某一初始解出发;
while (能朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组合成问题的一个可行解;
五、示例
(1)背包问题:
与0-1背包问题类似,所不同的是在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部装入背包,1 <= i <= n。
这2类问题都具有最优子结构性质,极为相似,但背包问题可以用贪心算法求解,而0-1背包问题却不能用贪心算法求解。
用贪心算法解背包问题的基本步骤:
首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。
伪代码:
void Knapsack(int n,float M,float v[],float w[],float x[])
{
Sort(n,v,w);
int i;
for (i = 1 ; i <= n ; i++)
x[i] = 0;
float c=M;
for (i=1;i<=n;i++) {
if (w[i] > c) break;
x[i]=1;
c-=w[i];
}
if (i <= n)
x[i]=c / w[i];
}
算法knapsack的主要计算时间在于将各种物品依其单位重量的价值从大到小排序。因此,算法的计算时间上界为 O(nlogn)。
为了证明算法的正确性,还必须证明背包问题具有贪心选择性质。
对于0-1背包问题,贪心选择之所以不能得到最优解是因为在这种情况下,它无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择。由此就导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。实际上也是如此,动态规划算法的确可以有效地解0-1背包问题。
(2) 哈夫曼算法
代码
/* 主题: Haffman编码 * 作者: chinazhangjie * 邮箱: chinajiezhang@gmail.com * 开发环境 : Microsoft Visual Studio 2008 * 时间 : 2010.11.21 */ #include <iostream> #include <vector> #include <queue> using namespace std ; class HaffmanNode { public: HaffmanNode (int nKeyValue, HaffmanNode* pLeft = NULL, HaffmanNode* pRight = NULL) { m_nKeyValue = nKeyValue ; m_pLeft = pLeft ; m_pRight = pRight ; } friend bool operator < (const HaffmanNode& lth, const HaffmanNode& rth) { return lth.m_nKeyValue < rth.m_nKeyValue ; } public: int m_nKeyValue ; HaffmanNode* m_pLeft ; HaffmanNode* m_pRight ; } ; class HaffmanCoding { public: typedef priority_queue<HaffmanNode*> MinHeap ; typedef HaffmanNode* HaffmanTree ; public: HaffmanCoding (const vector<int>& weight) : m_pTree(NULL) { m_stCount = weight.size () ; for (size_t i = 0; i < weight.size() ; ++ i) { m_minheap.push (new HaffmanNode(weight[i], NULL, NULL)) ; } } ~ HaffmanCoding() { __destroy (m_pTree) ; } // 按照左1右0编码 void doHaffmanCoding () { vector<int> vnCode(m_stCount-1) ; __constructTree () ; __traverse (m_pTree, 0, vnCode) ; } private: void __destroy(HaffmanTree& ht) { if (ht->m_pLeft != NULL) { __destroy (ht->m_pLeft) ; } if (ht->m_pRight != NULL) { __destroy (ht->m_pRight) ; } if (ht->m_pLeft == NULL && ht->m_pRight == NULL) { // cout << "delete" << endl ; delete ht ; ht = NULL ; } } void __traverse (HaffmanTree ht,int layers, vector<int>& vnCode) { if (ht->m_pLeft != NULL) { vnCode[layers] = 1 ; __traverse (ht->m_pLeft, ++ layers, vnCode) ; -- layers ; } if (ht->m_pRight != NULL) { vnCode[layers] = 0 ; __traverse (ht->m_pRight, ++ layers, vnCode) ; -- layers ; } if (ht->m_pLeft == NULL && ht->m_pRight == NULL) { cout << ht->m_nKeyValue << " coding: " ; for (int i = 0; i < layers; ++ i) { cout << vnCode[i] << " " ; } cout << endl ; } } void __constructTree () { size_t i = 1 ; while (i < m_stCount) { HaffmanNode* lchild = m_minheap.top () ; m_minheap.pop () ; HaffmanNode* rchild = m_minheap.top () ; m_minheap.pop () ; // 确保左子树的键值大于有子树的键值 if (lchild->m_nKeyValue < rchild->m_nKeyValue) { HaffmanNode* temp = lchild ; lchild = rchild ; rchild = temp ; } // 构造新结点 HaffmanNode* pNewNode = new HaffmanNode (lchild->m_nKeyValue + rchild->m_nKeyValue, lchild, rchild ) ; m_minheap.push (pNewNode) ; ++ i ; } m_pTree = m_minheap.top () ; m_minheap.pop () ; } private: vector<int> m_vnWeight ; // 权值 HaffmanTree m_pTree ; MinHeap m_minheap ; size_t m_stCount ; // 叶结点个数 } ; int main() { vector<int> vnWeight ; vnWeight.push_back (45) ; vnWeight.push_back (13) ; vnWeight.push_back (12) ; vnWeight.push_back (16) ; vnWeight.push_back (9) ; vnWeight.push_back (5) ; HaffmanCoding hc (vnWeight) ; hc.doHaffmanCoding () ; return 0 ; }