贪心算法介绍与使用

贪心算法思想:

   贪心算法不是从整体来考虑, 而是在某种意义上在局部做出最优选择. 对于有些问题局部最优不能代表整体最优选择,是不能使用贪心算法,例如01揹包问题.

贪心算法是自顶向下的方式分解各子问题, 用迭代的方式相继做出贪心选择, 每一次的选择的子问题再简化成规模更小的问题来选择。

由于贪心算法与动态规划很类似, 在解决问题时需要确定问题是否具有贪心选择的性质.


贪心算法思路:

  1.   从最初问题的初始解开始

  2.   while  (将问题分解) do

          求出分解的问题的一个解

  3.   然后由所有解组合成问题的一个可行的解。


为什么对与01揹包问题不能使用贪心算法来解决问题?

假设现在有一个揹包重30,有A,B,C三种物品且重量与价值分别如下:

物品: A   B   C

重量:29 13 13

价值:32 19 19

根据价值最大的贪心策略,那么答案为A,但是B+C才是最优解。

对于根据单位重量价格最大的贪心策略的反例:

物品:A B C

重量:29 22 10

价值:29 22 10

所以贪心算法并不适合01揹包问题, 应该使用动态规划来解决这个问题。

对于01揹包问题不能用贪心算法解是因为:贪心算法将问题分解成子问题时是用的单一的条件,如果此条件与其他因素关联的话,那么贪心策略就会

失败。从01揹包问题来看,按照价值最大来划分子问题时受到物品重量条件约束,所以贪心算法解决的问题是讲问题划分成子问题的条件是独立的。



贪心算法例子1

设n为一自然数,n可以分解成若干个不同的自然数的和,这样的分法有很多种,比如n=10, 10可以分解为:10=5+4+1; 10=5+3+2; 10+9+1; 10=8+2; 10=7+3; 10=6+4; 10=7+2+1; 10=6+3+1;…。在所有这些分法中,各加数乘积最大的为30, (10=5+3+2中加数的乘积为5*3*2=30)。试编写程序,求各种分解方法中各加数乘积的最大值。

输入要求:输入只有1行,自然数n。

输出要求:输出也只有1行,所有分解方法中各加数乘积的最大值


这是一个具有贪心性质的问题, 将10分解成若干数,找到符合条件的组合数,然后再从组合数中挑出乘积最大的值。符合贪心算法的思路。将问题分为若干解, 再由所有局部最优解得到整体的解。

参考代码:

#include<iostream>
#include<vector>
using namespace std;
class MaxProductNumber {
public:
    MaxProductNumber() :
            maxNum(0) {
    }
    ~MaxProductNumber() {
    }
    int getTmpMaxNum();
    int getMaxProductNumber() {
        return maxNum;
    }
    bool isExistAtVec(int val);
    void Solution(int n, int curValue, int num);
public:
    int maxNum;           //保存最大乘积
    vector<int> vec;     //保存每次获取的n的拆分数
};
bool MaxProductNumber::isExistAtVec(int val) {
    if (vec.size() <= 0) {
        return false;
    }
    for (int i = 0; i < vec.size(); ++i) {
        if (val == vec.at(i)) {
            return true;
        }
    }
    return false;
}
int MaxProductNumber::getTmpMaxNum() {
    int tmpMax = 1;
    for (int i = 0; i < vec.size(); i++) {
        tmpMax *= vec.at(i);
        cout << vec.at(i) << " ";
    }
    cout << endl;
    return tmpMax;
}
void MaxProductNumber::Solution(int n, int curValue, int num) {
    if (num == n) {
        int tmp = getTmpMaxNum();
        if (tmp > maxNum) {
            maxNum = tmp;
        }
        return;
    }
    for (int i = curValue; i < n; i++) {
        if (!isExistAtVec(i)) {
            num += i;
            vec.push_back(i);
            Solution(n, curValue + 1, num);
            vec.pop_back();
            num -= i;
        }
    }
}
int main() {
    int n = 0;
    cin >> n;
    MaxProductNumber maxProduct;
    maxProduct.Solution(n, 1, 0);
    cout << maxProduct.getMaxProductNumber() << endl;
    return 0;
}

贪心算法例子2

有n个人参加一个马拉松接力游戏,游戏规定每个人可以根据自己的情况随时终止游戏并由下一个人继续接力。由于每个人的情况不同,即使同一个人也不可能在整 个游戏过程中永远保持很好的状态。因此要求他们在比赛前根据每个人的情况需要制定一个接力规则,使整个比赛的时间越少越好。请编写程序帮助他们制定这样的 接力方案。 
输入要求:输入的第1行有三个整数n,k和m,分别表示参加接力的人的个数,每个人最多可以跑的公里数以及接力赛的距离(以公里为单位)。其后的n行,每 行有k个整数,分别表示每个人跑整数1公里,2公里,….,K公里所花费的时间(以秒为单位,整数)。游戏要求每个人都必须参加比赛,且每次只能跑到整数 公里后才能换人。 
输出要求:输出1个整数,表示这些人跑完整个接力赛最少要花多少时间

这个问题也是具有贪心性质,和例子1是一样的,只不过解的规则复杂了一点但是解题思路是一样的。

解题思路:

(1)将m拆成若干自然数的和,m =1+1+1+..., m=1+2+2.., m=1+2+3+...

(2)找到m拆成的若干自然数的个数p满足 p == n, 并且 若干自然数都<=k, 即 p[i] <=k  (0<i<n)

(3)然后根据获得的p,从每个人k公里跑的时间得到这组p的最短时间t

(4)汇总获得的t, 找到最少的t,即为最终结果。

#include<iostream>
#include<vector>
using namespace std;
class Result {
public:
    Result() :
        minTime(-1) {
    }
    ~Result() {
    }
    void setMinTime(int n, int k);
    int getMinTime() {
        return minTime;
    }
    void Solution(int m, int n, int k, int num);
    void setVecTime(int n, int k);
    void showVecTime(int n, int k);
    int getAllVecValue();
public:
    long minTime;           //保存最少消耗时间
    vector<int> vec;     //保存符合条件的拆分数
    vector<vector<long> > vecTime;  //保存每个人k公里跑的时间
};
void Result::showVecTime(int n, int k) {
    for (int i = 0; i < n; i++) {
        vector<long> vv = vecTime.at(i);
        for (int j = 0; j < k; j++) {
            cout<<vv.at(j)<<" ";
        }
        cout<<endl;
    }
}
void Result::setVecTime(int n, int k) {
    for (int i = 0; i < n; i++) {
        vector<long> vecc;
        int val;
        for (int j = 0; j < k; j++) {
            cin >> val;
            vecc.push_back(val);
        }
        vecTime.push_back(vecc);
    }
}
void Result::setMinTime(int n, int k) {
    //先判断所有的拆分数都小于k
    for(int i = 0; i < vec.size(); i++) {
        if (vec.at(i) > k) {
            return;
        }
    }
    int tmpTime = 0;
    vector<int> indexVec(n);
    for (int i = 0; i < vec.size(); i++) {
        long tmp = 1000000; 
        int index = 0;
        for (int j = 0; j < n; j++) {
            if (indexVec.at(j) <= 0 && vecTime[j].at(vec.at(i) - 1) < tmp) {
                tmp = vecTime[j].at(vec.at(i) - 1);
                index = j;
            }
        }
        indexVec.at(index) = 1;
        index = 0;
        tmpTime += tmp;
    }
    if (minTime == -1 || minTime > tmpTime) {
        minTime = tmpTime;
    }
}
int Result::getAllVecValue() {
    int tmp = 0;
    for (int i = 0; i < vec.size(); i++) {
        tmp += vec.at(i);
    }
    return tmp;
}
void Result::Solution(int m, int n, int k, int num) {
    if (num >= m) {
        if (vec.size() == n && getAllVecValue() == m) {
            setMinTime(n, k);
        }
        return;
    }
    for (int i = 1; i <= m; i++) {
            num += i;
            vec.push_back(i);
            Solution(m, n, k, num);
            vec.pop_back();
            num -= i;
    }
}

int main() {
    int m, n, k;
    vector<vector<long> > vecTime;
    Result result;
    cin>>m>>n>>k;
    result.setVecTime(n, k);
    result.Solution(m, n, k, 0);
    cout <<result.getMinTime()<< endl;
    return 0;
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章