【算法篇】贪心算法

目录

贪心算法

贪心算法实际应用

一,零钱找回问题

二,活动选择问题

三,分数背包问题

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

最大数


贪心算法

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

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

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;
    }
};
相关推荐
望云山1905 分钟前
4.回归与聚类算法 4.1线性回归
算法·回归·聚类
秋意钟42 分钟前
数据结构(AVL树、B-Tree、B+Tree)
数据结构·算法
希望奇迹很安静1 小时前
CTFSHOW-WEB入门-PHP特性89-100
学习·web安全·php·ctf
hurrycry_小亦1 小时前
洛谷网站: P3029 [USACO11NOV] Cow Lineup S 题解
算法
计算机-秋大田2 小时前
基于微信小程序的绘画学习平台的设计与开发
spring boot·后端·学习·微信小程序·小程序·课程设计
shimly1234562 小时前
(undone) MIT6.S081 2023 学习笔记 (Day8: LAB7 networking)
笔记·学习
谢道韫6662 小时前
我的鸿蒙学习之旅:探索万物互联的新宇宙
学习·华为·harmonyos
m0_748234902 小时前
Django框架丨从零开始的Django入门学习
学习·django·sqlite
fc&&fl2 小时前
R语言速通
开发语言·学习·r语言