C++算法之贪心算法

贪心算法是一种求解最优解问题的算法,它的核心思想是每一步都采取当前状态下最优的选择,从而最终得到全局最优解。它是C++重要的一种算法。下面会介绍贪心算法。


目录

1.步骤

1.2 例

2.框架

3.例题

3.1 删数问题

13

 3.2 接水问题


1.步骤

(1)确定问题的最优子结构:问题的最优子结构指的是原问题的最优解可以通过其子问题的最优解得到。这一步通常需要根据问题的特性进行分析。

(2)制定贪心策略:贪心策略是贪心算法的核心,它指的是每一步的最优选择方式。贪心策略通常需要满足贪心选择性质,即每一步的最优选择不依赖于之前所做的选择。

(3)实现贪心策略:贪心策略的实现通常涉及到对问题的数据结构和算法的选择,例如贪心策略是基于数值大小的,那么需要使用合适的数据结构(例如优先队列)来存储和获取当前状态下的最优选择。

(4)分析算法的正确性:贪心算法的正确性通常需要通过数学证明来证明它的贪心选择性质以及最终得到的解一定是全局最优解。

(5)分析算法的复杂度:贪心算法的复杂度通常取决于贪心策略的实现以及问题的特性。

需要注意的是:贪心算法只能用在局部最优解能导致全局最优解的问题上。

1.2 例

下面以一个例子来详解贪心算法的应用过程:

有一些区间,在这些区间中选择尽可能多的不重叠的区间。 

步骤:

  1. 确定问题的最优子结构:这个问题具有最优子结构,即在原问题中,如果我们知道了前n个区间的最优解,则可以构造出前n+1个区间的最优解。

  2. 制定贪心策略:对于每一次选择最优区间,我们选择区间结束时间最早的一个。

  3. 实现贪心策略:我们可以使用优先队列来存储区间,并根据结束时间来排序,然后依次选择结束时间最早的区间。

  4. 分析算法的正确性:我们可以通过反证法来证明该贪心策略是正确的。假设存在一个最优解,其中包含了不止一个结束时间比当前选择的区间早的区间,那么我们可以将其中一个区间换成当前选择的区间,得到的解一定不会更劣,因为当前选择的区间是在所有结束时间比它早的区间中,结束时间最早的一个。

  5. 分析算法的复杂度:由于需要将区间按照结束时间排序,因此时间复杂度为O(nlogn)。

2.框架

从问题的某一初始解出发;
while(能朝给定总目标前进一步)
{
    利用可行的决策,求出可行解的一个解元素;
}
由所有解元素组成成问题的一个可行解;

3.例题

3.1 删数问题

删数问题(Noip1994)

【题目描述】

输入一个高精度的正整数n,去掉其中任意s个数字后剩下的数字按原左右次序组成一个新的正整数。编程对给定的n和s,寻找一种方案使得剩下的数字组成的新数最小。

输出新的正整数。(n不超过240位)

输入数据均不需判错。

【输入】

n

s

【输出】

最后剩下的最小数。

【输入样例】

175438
4

【输出样例】

13

#include<bits/stdc++.h>
using namespace std;
#define N 300
int main()
{
    int k, an = 0; 
    char s[N], a[N];
    cin >> s >> k;
    int len = strlen(s), i, ct = 0;
    for(i = 0; i < len && s[i] == '0'; ++i);//去除前导0 
    for(; i < len; ++i)
    {
        while(an > 0 && a[an] > s[i] && ct < k)//a[an]为前一个数 s[i]为后一个数,当a[an]>s[i]时,删除a[an]
        {
            an--;
            ct++;//删除数字个数加1
        }
        a[++an] = s[i];//无论如何s[i]都会填充进数组a
    }
    while(ct < k)//删除末尾的k-ct个元素
    {
        an--;
        ct++;
    }
    for(i = 1; i <= an && a[i] == '0'; ++i);//去除前导0 
    if(i > an)//如果前导0去完,就没有可以输出的了,说明原来的结果是0 
        cout << "0";
    else
        while(i <= an)
            cout << a[i++];
    return 0;
}

 3.2 接水问题

接水问题

【题目描述】

学校里有一个水房,水房里一共装有m个龙头可供同学们打开水,每个龙头每秒钟的供水量相等,均为1。

现在有n名同学准备接水,他们的初始接水顺序已经确定。将这些同学按接水顺序从1到n编号,i号同学的接水量为wi。接水开始时,1到m号同学各占一个水龙头,并同时打开水龙头接水。当其中某名同学j完成其接水量要求wj后,下一名排队等候接水的同学k马上接替j同学的位置开始接水。这个换人的过程是瞬间完成的,且没有任何水的浪费。即j同学第x秒结束时完成接水,则k同学第x+1 秒立刻开始接水。 若当前接水人数n’不足m,则只有n’个龙头供水,其它m-n’个龙头关闭。

现在给出n名同学的接水量,按照上述接水规则,问所有同学都接完水需要多少秒。

【输入】

第1行2个整数n和m,用一个空格隔开,分别表示接水人数和龙头个数。

第2 行n个整数 w1、w2、……、wn,每两个整数之间用一个空格隔开,wi表示 i 号同学的接水量。

【输出】

输出只有一行,1个整数,表示接水所需的总时间。

【输入样例】

5 3
4 4 1 2 1

【输出样例】

4

【样例输入#2】

8 4

23 71 87 32 70 93 80 76

【样例输出#2】

163

【提示】

输入输出样例1解释:

第1秒,3人接水。第1秒结束时,1、2、3号同学每人的已接水量为1,3号同学接完水,4号同学接替3号同学开始接水。

第2秒,3人接水。第2秒结束时,1、2号同学每人的已接水量为2,4号同学的已接水量为1。

第3秒,3人接水。第3秒结束时,1、2号同学每人的已接水量为3,4号同学的已接水量为2。4号同学接完水,5号同学接替4号同学开始接水。

第4秒,3人接水。第4秒结束时,1、2号同学每人的已接水量为4,5号同学的已接水量为1。1、2、5号同学接完水,即所有人完成接水。

总接水时间为4秒。

#include <bits/stdc++.h>
using namespace std;
struct Pair
{
    int n, t;//n:水龙头编号 t:结束时间
    Pair(){}
    Pair(int a, int b):n(a),t(b){}
    bool operator < (const Pair &b) const
    {
        return b.t < t;//结束时间早更优先 
    } 
};
int main()
{
    priority_queue<Pair> pq;
    int n, m, w[10005], mni, mx = 0;
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        cin >> w[i];
    for(int i = 1; i <= m; ++i)
        pq.push(Pair(i, w[i]));
    for(int i = m+1; i <= n; ++i)//已知m<=n 
    {
        int mni = pq.top().n, t = pq.top().t;
        pq.pop();
        pq.push(Pair(mni, t + w[i]));//第mni水龙头接水结束时间增加w[i]
    }
    while(pq.empty() == false)
    {
        mx = max(mx, pq.top().t);
        pq.pop();
    }
    cout << mx;
    return 0;
}

创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,如果喜欢我的文章,给个关注吧!

冰焰狼 | 文

如果本篇博客有任何错误,请批评指教,不胜感激 !

  • 34
    点赞
  • 89
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值