【算法篇】贪心算法

目录

贪心算法

贪心算法实际应用

一,零钱找回问题

二,活动选择问题

三,分数背包问题

将数组和减半的最小操作次数

最大数


贪心算法

贪心算法,是一种在每一步选择中都采取当前状态下的最优策略,期望得到全局最优解的算法策略。也就是说,通过局部最优解,期望得到全局最优解。

贪心算法一般按如下步骤执行:

1,问题分解: 将原问题转化为一系列子问题。

2,贪心选择:对每个子问题求解的时候,都选择当前看起来"最优的"解法。

3,合并解:累计局部最优解形成全局解。

4,正确性证明:通过数学归纳或者替换论证验证。(因为贪心策略可能是错误的)

先简单理解一下贪心算法:

例一:找零问题

这个问题在我们日常生活中很普遍。

假设我们现在可以找的零钱面额为【20,10,5,1】,并且每个有无限张。当我们想找k的零钱时,怎样可以让钱的张数最少?

这里假设k=46,用 贪心算法的思想,每一步尽可能使用面值最大的纸币即可。

46 -> 选一张20 -> 26 -> 选一张20 -> 6 -> 选一张5->1->选一张1

例二:最小路径和

给你一个矩阵,求解:从矩阵的左上角出发,只能向下和向右两个方向走,求到达矩阵的右下角过程中,路径上所有元素和的最小值。

贪心算法实际应用

一,零钱找回问题

假设1元,5元,10元,20元,50元,100元的纸币分别有

c0,c1,c2,c3,c4,c5张 。现在用这些钱来找回k元,求最少使用的张数。

用贪心算法的思想,每一步找钱尽可能使用面值大的。

//单张钱的面额

int signle_money[7] = { 1,2,5,10,20,50,100 };

//对应前的个数

int number_money[7] = {2,5,1,3,4,0,4};

//存放结果

int total[7] = { 0 };

int tanxin(int money)

{

if (money >= 0)

{

//每一步尽量选最大的面额

for (int i = 6; i >=0; i--)

{

total[i] = min(number_money[i], money / signle_money[i]);

money = money - total[i] * signle_money[i];

}

return 0;

}

else

{

return money;

}

}

int main()

{

int k = 0;

cout << "输入 要找回的零钱" << endl;

cin >> k;

if (!tanxin(k))

{

cout << "贪心结果为:" << endl;

for (int i = 0; i < 7; i++)

cout << signle_money[i] << "元:" << total[i] << "张" << endl;

}

else

{

cout << "ops wrong number" << endl;

}

return 0;

}

二,活动选择问题

有n个活动,a1,a2,a3...an,这些活动需要在同一天使用同一个教室。每个活动都有一个开始时间si和结束时间fi,一旦被选择后,活动ai就占据半开时间[si,fi)。如果[si,fi)和[sj,fj)互不重叠,那么ai和aj两个活动可以被安排在同一天。该问题是安排这些活动,使尽量多的活动不冲突的举行。

贪心策略:想要使尽量多的活动不冲突,那我们在选择活动时,就尽量选择结束早的活动,为后续留出更多的时间。

//活动安排问题

#include <algorithm>

struct ACT

{

int start;

int end;

}activity[100];

//活动的个数

int N;

bool cmp(ACT a, ACT b)

{

return a.end < b.end;

}

int greedy()

{

int num = 0;

for (int i = 0, j = i + 1; i < N; j++)

{

if (activity[j].start > activity[i].end)

{

i = j;

num++;

}

}

return num;

}

int main()

{

cout << "活动的总数:";

cin >> N;

cout << "输入每个活动的开始和结束:" << endl;

for (int i = 0; i < N; i++)

{

cin >> activity[i].start;

cin >> activity[i].end;

}

//将活动按结束时间排序,升序

sort(activity, activity + N, cmp);

//统计结果

int res = greedy();

cout << "安排的活动个数:" << res << endl;

return 0;

}

三,分数背包问题

情景描述:给定背包容量和一些物品,每个物品有重量和价值 两个属性。允许只取一个物品的一部分加入背包,求问如何才能使背包装的物品价值最大。

与01背包不同,分数背包问题允许将物品的部分装入背包。这意味着我们可以将一个物品分割成任意大小,并根据其重量比例来计算其价值。

贪心策略:

核心思想:按性价比来排序。即每个物品的单位价值:价值/重量。

按照单位价值从高到低对物品进行排序,然后依次考虑每个物品 。

算法步骤:

1,将所有物品按照单位价值从高到低排序。

2,遍历排序后的物品。对于每个物品:

  • 如果物品重量小于等于背包剩余容量 ,就将物品装入背包。
  • 如果物品重量大于背包剩余容量,只装入能狗适应当前容量的部分。

//分数背包问题

#include <iostream>

#include <vector>

#include <algorithm>

using namespace std;

class Item

{

public:

int _w;

int _v;

Item() = default;

Item(int w,int v)

:_w(w)

,_v(v)

{}

};

bool cmp(Item a, Item b)

{

return a._v / a._w > b._v /a._w;

}

double greed(vector<int>& w, vector<int>& v, int cap)

{

vector<Item> items(w.size());

double res = 0;

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

{

items[i] = { w[i], v[i] };

}

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

for (auto& item : items)

{

if (item._w <= cap)

{

res += item._v;

cap -= item._w;

}

else

{

res += (double)item._v / item._w * cap;

break;

}

}

return res;

}

int main()

{

vector<int> w = { 10,20,30,40,50 };

vector<int> v = { 50,120,150,210,240 };

//背包容量

int cap = 50;

cout << "MAX_value:" << greed(w, v, cap) << endl;

return 0;

}

将数组和减半的最小操作次数

本题链接:2208. 将数组和减半的最少操作次数 - 力扣(LeetCode)

贪心策略:每次选出数组中的最大值,对该数减半,直到使数组和减小到一半。

算法步骤:

  • 计算出数组和的一半sum
  • 每次选数组 中最大的数a进行减半,sum-=a/2,找到sum为0结束。

在每次选最大数的时候,我们可以借助优先级队列,快速找的最大值。

复制代码
class Solution {
public:
    int halveArray(vector<int>& nums) {
        priority_queue<double> heap;
        double sum=0.0;
        for(int x:nums)
        {
             sum+=x;
             heap.push(x);
        }

        sum/=2.0;
        
        int count=0;
        while(sum>0)
        {
            double t=heap.top()/2.0;
            sum-=t;
            heap.pop();
            heap.push(t);
            count++;

        }
        return count;

    }
};

最大数

本题链接:179. 最大数 - 力扣(LeetCode)

贪心策略:本题可以理解为是对数组nums进行重新"排序",而传统的排序是根据大小排的,对于本题,则是按照题目要求。

对于nums数组中的两个元素a和b, 我们无法直接确定他们的先后关系,但我们可以从结果角度来看,如果拼接结果ab要比ba好,那么a就应该放在b的前面。

复制代码
class Solution {
public:
    string largestNumber(vector<int>& nums) {
        vector<string> strs;
        for(auto x:nums)
        strs.push_back(to_string(x));

        sort(strs.begin(),strs.end(),
        [](string s1,string s2)
        {
            return s1+s2>s2+s1;
        });

        string ret;
        for(auto& s:strs)
        ret+=s;
        //处理前导0
        if(ret[0]=='0') return "0";

        return ret;
    }
};
相关推荐
前端小L1 天前
贪心算法专题(十):维度权衡的艺术——「根据身高重建队列」
javascript·算法·贪心算法
方得一笔1 天前
自定义常用的字符串函数(strlen,strcpy,strcmp,strcat)
算法
YJlio1 天前
VolumeID 学习笔记(13.10):卷序列号修改与资产标识管理实战
windows·笔记·学习
小龙1 天前
【学习笔记】多标签交叉熵损失的原理
笔记·学习·多标签交叉熵损失
Xの哲學1 天前
Linux SMP 实现机制深度剖析
linux·服务器·网络·算法·边缘计算
wuk9981 天前
使用PCA算法进行故障诊断的MATLAB仿真
算法·matlab
知识分享小能手1 天前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04的Linux网络配置(14)
linux·学习·ubuntu
额呃呃1 天前
二分查找细节理解
数据结构·算法
无尽的罚坐人生1 天前
hot 100 283. 移动零
数据结构·算法·双指针
永远都不秃头的程序员(互关)1 天前
C++动态数组实战:从手写到vector优化
c++·算法