Bootstrap

4.贪心算法

贪心

贪心算法(Greedy Algorithms)是 C++ 等编程语言中常用的一种算法策略。

定义

贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。虽然贪心算法不能对所有问题都得到整体最优解,但对许多问题它能产生整体最优解,如单源最短路径问题、最小生成树问题等。

特点

  • 局部最优选择:每一步都做出当时看起来最佳的选择,它并不从整体最优的角度去考虑问题,而是只关注眼前的利益。
  • 无后效性:即某个状态以前的过程不会影响以后的状态,只与当前状态有关。这使得算法在每一步的决策都相对简单,只依赖于当前的信息。

一般步骤

  1. 分解问题:把求解的问题分成若干个子问题。
  2. 局部最优决策:对每个子问题求解,得到子问题的局部最优解。
  3. 合并解:把子问题的局部最优解合成原来问题的一个解。

示例:活动选择问题(用 C++ 实现)

假设有一系列活动,每个活动都有开始时间和结束时间,目标是选择出最多的活动,使得这些活动之间没有时间冲突。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 定义活动结构体
struct Activity {
    int start;
    int end;
};

// 比较函数,用于按活动结束时间排序
bool compareEndTime(Activity a, Activity b) {
    return a.end < b.end;
}

// 贪心算法实现活动选择
int activitySelection(vector<Activity> activities) {
    // 按活动结束时间排序
    sort(activities.begin(), activities.end(), compareEndTime);

    int count = 1;  // 选择的活动数量,初始选第一个活动
    int lastEnd = activities[0].end;  // 记录最后一个被选择活动的结束时间

    for (int i = 1; i < activities.size(); i++) {
        if (activities[i].start >= lastEnd) {  // 如果当前活动开始时间晚于上一个活动结束时间
            count++;
            lastEnd = activities[i].end;
        }
    }
    return count;
}

你可以使用以下方式调用这个函数:

int main() {
    vector<Activity> activities = { {1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 8}, {5, 9}, {6, 10}, {8, 11}, {8, 12}, {2, 13}, {12, 14} };
    int result = activitySelection(activities);
    cout << "最多可以选择的活动数量: " << result << endl;
    return 0;
}

上述代码中,compareEndTime 函数用于对活动按结束时间进行排序,activitySelection 函数实现了贪心算法,通过不断选择结束时间最早且与已选活动无冲突的活动,最终得到最多可选择的活动数量。

实例:哈夫曼编码问题

哈夫曼编码是一种用于数据压缩的算法,贪心算法可以很好地应用于构建哈夫曼树来实现哈夫曼编码。以下从原理、步骤和 C++ 代码示例等方面进行讲解:

原理

哈夫曼编码基于字符出现的频率,频率越高的字符被赋予越短的编码,频率越低的字符则被赋予越长的编码,从而达到数据压缩的目的。贪心算法在构建哈夫曼树的过程中,每次都选择频率最小的两个节点来构建新的父节点,直到所有节点合并成一棵完整的哈夫曼树。

步骤
  1. 统计字符频率:遍历待编码的数据,统计每个字符出现的频率。

  2. 创建节点:为每个字符创建一个节点,节点包含字符本身和其频率。

  3. 构建哈夫曼树

    • 将所有节点放入一个优先队列(通常按频率升序排列)。
    • 从优先队列中取出频率最小的两个节点,创建一个新的父节点,其频率为这两个节点频率之和。
    • 将新的父节点重新放入优先队列。
    • 重复上述步骤,直到优先队列中只剩下一个节点,该节点即为哈夫曼树的根节点。
  4. 生成编码:从哈夫曼树的根节点开始,通过遍历左右子树为每个字符生成对应的编码(通常左子树对应编码为 0,右子树对应编码为 1)。

  5. 编码数据:根据生成的编码表,对待编码的数据进行编码。

C++ 代码示例
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <string>

// 哈夫曼树节点结构体
struct HuffmanNode {
    char data;
    int frequency;
    HuffmanNode* left;
    HuffmanNode* right;
    HuffmanNode(char c, int f) : data(c), frequency(f), left(nullptr), right(nullptr) {}
};

// 比较函数,用于优先队列按频率升序排列
struct Compare {
    bool operator()(HuffmanNode* a, HuffmanNode* b) {
        return a->frequency > b->frequency;
    }
};

// 构建哈夫曼树
HuffmanNode* buildHuffmanTree(const std::unordered_map<char, int>& frequencyMap) {
    std::priority_queue<HuffmanNode*, std::vector<HuffmanNode*>, Compare> pq;
    for (const auto& pair : frequencyMap) {
        pq.push(new HuffmanNode(pair.first, pair.second));
    }
    while (pq.size() > 1) {
        HuffmanNode* left = pq.top();
        pq.pop();
        HuffmanNode* right = pq.top();
        pq.pop();
        HuffmanNode* parent = new HuffmanNode('\0', left->frequency + right->frequency);
        parent->left = left;
        parent->right = right;
        pq.push(parent);
    }
    return pq.top();
}

// 生成哈夫曼编码表
void generateCodes(HuffmanNode* root, const std::string& code, std::unordered_map<char, std::string>& codeMap) {
    if (root == nullptr) {
        return;
    }
    if (root->data != '\0') {
        codeMap[root->data] = code;
    }
    generateCodes(root->left, code + "0", codeMap);
    generateCodes(root->right, code + "1", codeMap);
}

// 对数据进行编码
std::string encodeData(const std::string& data, const std::unordered_map<char, std::string>& codeMap) {
    std::string encodedData;
    for (char c : data) {
        encodedData += codeMap[c];
    }
    return encodedData;
}

int main() {
    std::string data = "hello world";
    std::unordered_map<char, int> frequencyMap;
    for (char c : data) {
        frequencyMap[c]++;
    }
    HuffmanNode* root = buildHuffmanTree(frequencyMap);
    std::unordered_map<char, std::string> codeMap;
    generateCodes(root, "", codeMap);
    std::string encodedData = encodeData(data, codeMap);
    std::cout << "原始数据: " << data << std::endl;
    std::cout << "哈夫曼编码: " << encodedData << std::endl;
    return 0;
}
代码说明
  1. 节点结构体:定义了 HuffmanNode 结构体来表示哈夫曼树的节点,包含字符、频率以及左右子节点指针。
  2. 构建哈夫曼树buildHuffmanTree 函数根据字符频率构建哈夫曼树,使用优先队列来选择频率最小的节点。
  3. 生成编码表generateCodes 函数通过遍历哈夫曼树为每个字符生成对应的编码,并存储在 codeMap 中。
  4. 编码数据encodeData 函数根据生成的编码表对待编码的数据进行编码。

通过上述步骤和代码,就可以使用贪心算法解决哈夫曼编码问题,实现对数据的压缩编码。

;