贪心算法

理解贪心算法

贪心算法在每一步都做出局部最优选择,希望找到全局最优解。它们简单高效,但不总是产生最优解。关键是识别贪心方法何时真正有效。

当问题具有贪心选择性质(通过做出局部最优选择可以达到全局最优)和最优子结构时,贪心算法有效。

活动选择问题

给定一组具有开始和结束时间的活动,选择不重叠的最大数量活动。贪心策略:总是选择最早结束的活动。

活动选择问题

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Activity {
    int start, finish, index;
    
    Activity(int s, int f, int i) : start(s), finish(f), index(i) {}
};

class ActivitySelection {
public:
    // Greedy approach: select activity that finishes earliest
    vector<int> selectActivities(vector<Activity>& activities) {
        // Sort activities by finish time
        sort(activities.begin(), activities.end(), 
             [](const Activity& a, const Activity& b) {
                 return a.finish < b.finish;
             });
        
        vector<int> result;
        result.push_back(activities[0].index);
        int lastFinish = activities[0].finish;
        
        cout << "Selected activities (in order of selection):" << endl;
        cout << "Activity " << activities[0].index 
             << ": [" << activities[0].start << ", " << activities[0].finish << "]" << endl;
        
        // Consider remaining activities
        for (int i = 1; i < activities.size(); i++) {
            // If this activity starts after the last selected activity finishes
            if (activities[i].start >= lastFinish) {
                result.push_back(activities[i].index);
                lastFinish = activities[i].finish;
                cout << "Activity " << activities[i].index 
                     << ": [" << activities[i].start << ", " << activities[i].finish << "]" << endl;
            }
        }
        
        return result;
    }
    
    // Recursive greedy approach
    void selectActivitiesRecursive(vector<Activity>& activities, int i, 
                                   vector<int>& result) {
        int j = i + 1;
        
        // Find next compatible activity
        while (j < activities.size() && activities[j].start < activities[i].finish) {
            j++;
        }
        
        if (j < activities.size()) {
            result.push_back(activities[j].index);
            selectActivitiesRecursive(activities, j, result);
        }
    }
    
    void printActivities(const vector<Activity>& activities) {
        cout << "Available activities:" << endl;
        for (const auto& activity : activities) {
            cout << "Activity " << activity.index 
                 << ": [" << activity.start << ", " << activity.finish << "]" << endl;
        }
        cout << endl;
    }
};

int main() {
    vector<Activity> activities = {
        Activity(1, 4, 1),
        Activity(3, 5, 2),
        Activity(0, 6, 3),
        Activity(5, 7, 4),
        Activity(8, 9, 5),
        Activity(5, 9, 6)
    };
    
    ActivitySelection as;
    as.printActivities(activities);
    
    vector<int> selected = as.selectActivities(activities);
    
    cout << "\nTotal activities selected: " << selected.size() << endl;
    cout << "Activity indices: ";
    for (int id : selected) {
        cout << id << " ";
    }
    cout << endl;
    
    return 0;
}

按结束时间排序确保我们总是为未来活动留出最大空间。

分数背包问题

与0/1背包不同,在分数背包中我们可以拿取物品的一部分。贪心策略:按价值重量比对物品排序,首先拿取比率最高的物品。

分数背包

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Item {
    int value, weight;
    double ratio;
    int index;
    
    Item(int v, int w, int i) : value(v), weight(w), index(i) {
        ratio = (double)v / w;
    }
};

class FractionalKnapsack {
public:
    double solve(vector<Item>& items, int capacity) {
        // Sort items by value-to-weight ratio in descending order
        sort(items.begin(), items.end(), 
             [](const Item& a, const Item& b) {
                 return a.ratio > b.ratio;
             });
        
        double totalValue = 0.0;
        int currentWeight = 0;
        
        cout << "Items sorted by value/weight ratio:" << endl;
        for (const auto& item : items) {
            cout << "Item " << item.index << ": value=" << item.value 
                 << ", weight=" << item.weight << ", ratio=" << item.ratio << endl;
        }
        cout << endl;
        
        cout << "Knapsack filling process:" << endl;
        
        for (auto& item : items) {
            if (currentWeight + item.weight <= capacity) {
                // Take the whole item
                currentWeight += item.weight;
                totalValue += item.value;
                cout << "Take whole Item " << item.index 
                     << " (weight: " << item.weight << ", value: " << item.value << ")" << endl;
            } else {
                // Take fraction of the item
                int remainingCapacity = capacity - currentWeight;
                if (remainingCapacity > 0) {
                    double fraction = (double)remainingCapacity / item.weight;
                    totalValue += item.value * fraction;
                    cout << "Take " << fraction << " of Item " << item.index 
                         << " (weight: " << remainingCapacity << ", value: " 
                         << item.value * fraction << ")" << endl;
                }
                break;
            }
        }
        
        return totalValue;
    }
    
    // Alternative implementation with detailed tracking
    struct Solution {
        double totalValue;
        vector<pair<int, double>> itemFractions; // (item_index, fraction_taken)
    };
    
    Solution solveDetailed(vector<Item>& items, int capacity) {
        sort(items.begin(), items.end(), 
             [](const Item& a, const Item& b) {
                 return a.ratio > b.ratio;
             });
        
        Solution solution;
        solution.totalValue = 0.0;
        int currentWeight = 0;
        
        for (auto& item : items) {
            if (currentWeight + item.weight <= capacity) {
                // Take whole item
                currentWeight += item.weight;
                solution.totalValue += item.value;
                solution.itemFractions.push_back({item.index, 1.0});
            } else {
                // Take fraction
                int remainingCapacity = capacity - currentWeight;
                if (remainingCapacity > 0) {
                    double fraction = (double)remainingCapacity / item.weight;
                    solution.totalValue += item.value * fraction;
                    solution.itemFractions.push_back({item.index, fraction});
                }
                break;
            }
        }
        
        return solution;
    }
    
    void printSolution(const Solution& solution) {
        cout << "\nDetailed solution:" << endl;
        cout << "Total value: " << solution.totalValue << endl;
        cout << "Items taken:" << endl;
        for (const auto& itemFraction : solution.itemFractions) {
            cout << "Item " << itemFraction.first << ": " 
                 << (itemFraction.second * 100) << "%" << endl;
        }
    }
};

int main() {
    vector<Item> items = {
        Item(60, 10, 1),
        Item(100, 20, 2),
        Item(120, 30, 3)
    };
    
    int capacity = 50;
    
    FractionalKnapsack fk;
    
    cout << "Knapsack capacity: " << capacity << endl;
    cout << "Available items:" << endl;
    for (const auto& item : items) {
        cout << "Item " << item.index << ": value=" << item.value 
             << ", weight=" << item.weight << endl;
    }
    cout << endl;
    
    double maxValue = fk.solve(items, capacity);
    cout << "\nMaximum value: " << maxValue << endl;
    
    // Reset items for detailed solution
    vector<Item> items2 = {
        Item(60, 10, 1),
        Item(100, 20, 2),
        Item(120, 30, 3)
    };
    
    auto solution = fk.solveDetailed(items2, capacity);
    fk.printSolution(solution);
    
    return 0;
}

基于价值重量比的贪心选择保证分数背包的最优解。

霍夫曼编码

霍夫曼编码为数据压缩创建最优前缀码。贪心策略:总是合并频率最低的两个节点。

霍夫曼编码算法

#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <string>
using namespace std;

struct Node {
    char character;
    int frequency;
    Node* left;
    Node* right;
    
    Node(char c, int f) : character(c), frequency(f), left(nullptr), right(nullptr) {}
    Node(int f) : character('\0'), frequency(f), left(nullptr), right(nullptr) {}
};

struct Compare {
    bool operator()(Node* a, Node* b) {
        if (a->frequency == b->frequency) {
            return a->character > b->character; // For deterministic results
        }
        return a->frequency > b->frequency;
    }
};

class HuffmanCoding {
public:
    Node* buildHuffmanTree(vector<pair<char, int>>& frequencies) {
        priority_queue<Node*, vector<Node*>, Compare> pq;
        
        // Create leaf nodes and add to priority queue
        for (auto& freq : frequencies) {
            pq.push(new Node(freq.first, freq.second));
        }
        
        cout << "Building Huffman Tree:" << endl;
        
        // Build the tree
        while (pq.size() > 1) {
            Node* right = pq.top(); pq.pop();
            Node* left = pq.top(); pq.pop();
            
            Node* merged = new Node(left->frequency + right->frequency);
            merged->left = left;
            merged->right = right;
            
            cout << "Merge nodes with frequencies " << left->frequency 
                 << " and " << right->frequency << " -> " << merged->frequency << endl;
            
            pq.push(merged);
        }
        
        return pq.top();
    }
    
    void generateCodes(Node* root, string code, unordered_map<char, string>& codes) {
        if (!root) return;
        
        // Leaf node
        if (!root->left && !root->right) {
            codes[root->character] = code.empty() ? "0" : code;
            return;
        }
        
        generateCodes(root->left, code + "0", codes);
        generateCodes(root->right, code + "1", codes);
    }
    
    void printCodes(const unordered_map<char, string>& codes, 
                    const vector<pair<char, int>>& frequencies) {
        cout << "\nHuffman Codes:" << endl;
        cout << "Character | Frequency | Code" << endl;
        cout << "----------|-----------|-----" << endl;
        
        int totalBits = 0;
        int totalOriginalBits = 0;
        
        for (const auto& freq : frequencies) {
            char c = freq.first;
            int f = freq.second;
            string code = codes.at(c);
            
            cout << "    " << c << "     |     " << f << "     | " << code << endl;
            totalBits += f * code.length();
            totalOriginalBits += f * 8; // Assuming 8 bits per character originally
        }
        
        cout << "\nCompression Analysis:" << endl;
        cout << "Original bits: " << totalOriginalBits << endl;
        cout << "Huffman bits: " << totalBits << endl;
        cout << "Compression ratio: " << (double)totalBits / totalOriginalBits << endl;
    }
    
    string encode(const string& text, const unordered_map<char, string>& codes) {
        string encoded = "";
        for (char c : text) {
            encoded += codes.at(c);
        }
        return encoded;
    }
    
    string decode(const string& encoded, Node* root) {
        string decoded = "";
        Node* current = root;
        
        for (char bit : encoded) {
            if (bit == '0') {
                current = current->left;
            } else {
                current = current->right;
            }
            
            // Reached leaf node
            if (!current->left && !current->right) {
                decoded += current->character;
                current = root;
            }
        }
        
        return decoded;
    }
};

int main() {
    vector<pair<char, int>> frequencies = {
        {'a', 5}, {'b', 9}, {'c', 12}, {'d', 13}, {'e', 16}, {'f', 45}
    };
    
    HuffmanCoding hc;
    
    cout << "Character frequencies:" << endl;
    for (auto& freq : frequencies) {
        cout << freq.first << ": " << freq.second << endl;
    }
    cout << endl;
    
    Node* root = hc.buildHuffmanTree(frequencies);
    
    unordered_map<char, string> codes;
    hc.generateCodes(root, "", codes);
    hc.printCodes(codes, frequencies);
    
    // Test encoding and decoding
    string text = "abcdef";
    string encoded = hc.encode(text, codes);
    string decoded = hc.decode(encoded, root);
    
    cout << "\nTest encoding/decoding:" << endl;
    cout << "Original: " << text << endl;
    cout << "Encoded: " << encoded << endl;
    cout << "Decoded: " << decoded << endl;
    
    return 0;
}

霍夫曼编码为更频繁的字符分配更短的码,最小化总编码长度。