贪心算法介绍及应用(贪心算法)

一、贪心算法概述

(一)定义

贪心算法(Greedy Algorithm)是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,它不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解。贪心算法采用自顶向下、以迭代的方法做出相继的贪心选择,每做一次贪心选择,就将问题简化为一个规模更小的子问题,通过每一步的局部最优选择,期望最终得到全局最优解。

(二)核心思想

贪心算法的核心在于根据题目给定的条件,选择当前状态下的最优策略,从而逐步逼近目标。其关键在于贪心策略的选择,即如何定义 "当前最好" 的选择。不同的问题可能需要不同的贪心策略,而一个正确的贪心策略需要能够保证通过局部最优选择最终得到全局最优解。

(三)适用条件

  1. 最优子结构性质:问题的最优解包含其子问题的最优解。即当一个问题的最优解包含其子问题的最优解时,称该问题具有最优子结构性质。
  1. 贪心选择性质:可以通过局部最优的选择来达到全局最优解。即所求问题的整体最优解可以通过一系列局部最优的选择,也就是贪心选择来达到。

二、贪心算法的步骤

(一)分析问题,确定贪心策略

首先需要明确问题的目标和约束条件,然后根据问题的特点选择合适的贪心策略。例如,在活动选择问题中,选择结束时间最早的活动作为当前最优选择;在背包问题中,根据物品的单位价值进行选择等。

(二)证明贪心策略的正确性

这是贪心算法中较为关键的一步,需要证明所选择的贪心策略能够保证最终得到全局最优解。常用的证明方法包括数学归纳法、反证法等。

(三)根据贪心策略设计算法

使用 C++ 语言实现算法时,需要根据贪心策略选择合适的数据结构来存储和处理数据,以提高算法的效率。

(四)实现算法并进行测试

编写代码实现算法,并通过测试用例验证算法的正确性和效率。

三、经典贪心算法问题及 C++ 实现

(一)活动选择问题

问题描述:设有 n 个活动的集合 E={1,2,...,n},其中每个活动都要求使用同一资源(如演讲厅),且同一时间只能有一个活动使用该资源。每个活动 i 都有一个开始时间 s_i 和结束时间 f_i,其中 s_i < f_i。要求选择尽可能多的活动,使得这些活动之间互不冲突。

贪心策略:选择结束时间最早的活动,然后在剩余的活动中选择结束时间最早且与已选活动不冲突的活动,依次类推。

C++ 实现

 

#include <iostream>

#include <vector>

#include <algorithm>

using namespace std;

// 定义活动结构体

struct Activity {

int start;

int end;

};

// 比较函数,按结束时间升序排序

bool compare(Activity a, Activity b) {

return a.end < b.end;

}

vector<Activity> greedyActivitySelection(vector<Activity> activities) {

sort(activities.begin(), activities.end(), compare);

vector<Activity> selected;

selected.push_back(activities[0]);

int lastEnd = activities[0].end;

for (int i = 1; i < activities.size(); i++) {

if (activities[i].start >= lastEnd) {

selected.push_back(activities[i]);

lastEnd = activities[i].end;

}

}

return selected;

}

int main() {

vector<Activity> activities = {

{1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 9}, {5, 9}, {6, 10}, {8, 11}, {8, 12}, {2, 14}, {12, 16}

};

vector<Activity> selected = greedyActivitySelection(activities);

cout << "Selected activities count: " << selected.size() << endl;

return 0;

}

(二)背包问题(部分背包问题)

问题描述:有一个容量为 W 的背包,有 n 个物品,每个物品有重量 w_i 和价值 v_i。每个物品可以取部分放入背包,求背包能装入物品的最大价值。

贪心策略:计算每个物品的单位价值(v_i/w_i),按照单位价值从高到低的顺序选择物品,直到背包装满。

C++ 实现

 

#include <iostream>

#include <vector>

#include <algorithm>

using namespace std;

struct Item {

int weight;

int value;

double unitValue;

};

bool compare(Item a, Item b) {

return a.unitValue > b.unitValue;

}

double fractionalKnapsack(int capacity, vector<Item> items) {

sort(items.begin(), items.end(), compare);

double totalValue = 0.0;

for (int i = 0; i < items.size(); i++) {

if (items[i].weight <= capacity) {

totalValue += items[i].value;

capacity -= items[i].weight;

} else {

totalValue += items[i].unitValue * capacity;

break;

}

}

return totalValue;

}

int main() {

vector<Item> items = {

{10, 60, 6.0}, {20, 100, 5.0}, {30, 120, 4.0}

};

int capacity = 50;

double maxValue = fractionalKnapsack(capacity, items);

cout << "Max value: " << maxValue << endl;

return 0;

}

(三)Dijkstra 算法(单源最短路径问题)

问题描述:给定一个带权有向图 G=(V,E),其中每条边的权值为非负实数,求从某个源点 s 到其余各顶点的最短路径。

贪心策略:每次选择当前距离源点最近的顶点,并更新其邻接顶点的距离。使用优先队列来维护当前各顶点的距离,每次取出距离最小的顶点进行处理。

C++ 实现

 

#include <iostream>

#include <vector>

#include <queue>

#include <climits>

using namespace std;

struct Edge {

int to;

int weight;

};

struct Node {

int vertex;

int distance;

bool operator<(const Node& other) const {

return distance > other.distance;

}

};

vector<int> dijkstra(int n, vector<vector<Edge>>& graph, int source) {

vector<int> dist(n + 1, INT_MAX);

dist[source] = 0;

priority_queue<Node> pq;

pq.push({source, 0});

while (!pq.empty()) {

Node current = pq.top();

pq.pop();

if (current.distance > dist[current.vertex]) {

continue;

}

for (Edge edge : graph[current.vertex]) {

int newDistance = dist[current.vertex] + edge.weight;

if (newDistance < dist[edge.to]) {

dist[edge.to] = newDistance;

pq.push({edge.to, newDistance});

}

}

}

return dist;

}

int main() {

int n = 5; // 顶点数

vector<vector<Edge>> graph(n + 1);

graph[1].push_back({2, 4});

graph[1].push_back({3, 2});

graph[2].push_back({4, 5});

graph[3].push_back({2, 3});

graph[3].push_back({4, 4});

graph[4].push_back({5, 1});

vector<int> dist = dijkstra(n, graph, 1);

cout << "Shortest distances from source 1:" << endl;

for (int i = 2; i <= n; i++) {

cout << "To vertex " << i << ": " << dist[i] << endl;

}

return 0;

}

四、贪心算法的优缺点

(一)优点

  1. 简单高效:贪心算法通常思路简单,实现起来相对容易,时间复杂度较低,能够在较短时间内得到问题的解。
  1. 适用于某些特定问题:对于具有贪心选择性质和最优子结构性质的问题,贪心算法能够高效地得到最优解。

(二)缺点

  1. 不能保证得到全局最优解:贪心算法只关注当前的局部最优选择,而忽略了整体的情况,因此在一些问题中可能无法得到全局最优解。例如,在 0-1 背包问题中,贪心算法可能无法得到最优解。
  1. 贪心策略的选择困难:不同的问题需要不同的贪心策略,而正确的贪心策略需要经过严格的证明,这对于一些复杂问题来说具有一定的难度。

五、总结

贪心算法是一种重要的算法设计策略,在解决许多实际问题中具有广泛的应用。它的核心在于选择合适的贪心策略,通过局部最优选择来逐步逼近全局最优解。在使用贪心算法时,需要注意问题是否满足贪心选择性质和最优子结构性质,并通过严格的证明来确保算法的正确性。同时,也要认识到贪心算法的局限性,在无法保证得到全局最优解的情况下,需要考虑其他算法如动态规划等。通过学习和掌握贪心算法,能够提高我们解决问题的能力,为复杂问题的求解提供更多的思路和方法。

我写的肯定会有不足所以我找到了AI写贪心算法的介绍大家可以理解一下:

C++ 贪心算法的实现与应用

1. 贪心算法的核心概念

贪心算法是一种通过逐步选择局部最优解来构建全局最优解的方法。其核心在于每次决策都基于当前状态下的最佳选择,而不考虑未来的可能影响4

2. 应用场景分析

贪心算法适用于具有 贪心选择性质最优子结构 的问题。以下是几个经典的 C++ 中贪心算法的应用实例:


3. 经典案例解析
(1) 礼物分组问题

此问题是关于如何将多个物品分配到若干组中,使总组数最少。具体实现如下:

 

C++

#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 9; int a[N]; int main() { int w, n; cin >> w >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; } sort(a + 1, a + n + 1); int ans = 0, l = 1, r = n; while (l <= r) { ans++; if (l == r) break; if (a[l] + a[r] <= w) { // 尝试组合最轻和最重的礼物 l++, r--; } else { r--; // 如果无法组合,则单独放置较重的礼物 } } cout << ans << endl; }

这段代码实现了礼品分组逻辑,其中 sort 函数用于排序输入数据,从而便于后续处理1


(2) 数字删除问题

给定一个字符串表示的大整数以及允许删除的最大字符数量 ( k ),目标是最小化剩余数字组成的数值大小。其实现方式如下:

 

C++

#include <bits/stdc++.h> using namespace std; #define N 300 char s[N], a[N]; int main() { int k, an = 0, ct = 0; cin >> s >> k; int len = strlen(s); for (int i = 0; i < len && s[i] == '0'; ++i); // 去除前导零 for (int i = 0; i < len; ++i) { while (an > 0 && a[an] > s[i] && ct < k) { // 删除较大的数字 an--, ct++; } a[++an] = s[i]; // 添加当前数字至结果序列 } while (ct < k) { // 若仍有可删次数,则移除末尾多余部分 an--; ct++; } for (int i = 1; i <= an && a[i] == '0'; ++i); // 再次去除前导零 if (i > an) cout << "0"; else while (i <= an) cout << a[i++]; return 0; }

这里采用了栈的思想模拟删除过程,并利用循环迭代完成优化操作2


(3) 接水问题

假设有多个人排队等待接水,每个人所需时间不同,问按何种顺序安排才能让所有人总的等待时间最小?解决方案如下所示:

 

C++

#include <bits/stdc++.h> using namespace std; struct Person { int id, time; }; bool cmp(const Person& p1, const Person& p2) { return p1.time < p2.time; // 按照耗时从小到大排列 } int main() { int n; cin >> n; vector<Person> people(n); long long total_time = 0, current_sum = 0; for (int i = 0; i < n; ++i) { cin >> people[i].time; people[i].id = i + 1; } sort(people.begin(), people.end(), cmp); for (auto person : people) { current_sum += person.time; total_time += current_sum; } cout << total_time << "\n"; for (auto person : people) { cout << person.id << " "; } return 0; }

在此程序里,我们先按照每个人的用水需求升序整理队列;随后依次累加每位用户的实际消耗并统计总体等候时长2


4. 局限性和注意事项

尽管贪心算法简单高效,但它并非万能工具。例如,在背包问题中的 分数型背包(Fractional Knapsack) 可以借助贪心法求得精确解答,然而对于 0/1 背包 则无能为力3。因此,在设计算法之前需仔细验证是否具备必要的特性——即是否存在合理的贪心策略及其对应的理论依据。


性能评估

上述各例子均展示了良好的时空复杂度表现,尤其适合大规模数据集上的快速计算任务。不过需要注意的是,某些特定条件下可能会因错误估计而导致非优解情况发生,故应谨慎选用。

建议收藏,有问题可以评论区问,我会一一回答

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值