【数据结构】贪心算法

一.贪心算法的定义

        贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,只做出在某种意义上的局部最优解

        贪心算法的结果是最优解的最好近似。

优点:简单,高效。

缺点:可能不是正确的或最优的解

二.引例

当一个问题具有最优子结构性质时,可以用动态规划求解。也可以用贪心算法来求解。

哈夫曼编码:每次选择集合中权值最小的两个子树构成一棵树。

思想:贪心选择思想。

三.贪心算法的基本步骤与实现

1.建立数学模型来描述问题;

2.把求解的问题分成若干个子问题;

3.对每一个子问题求解,得到子问题的局部最优解;

4.把子问题的局部最优解合成原来问题的解。

四.贪心算法的应用

1.活动安排问题

(1)问题描述

1)有n个活动;

2)活动j在$s_j$时刻开始,$f_j$时刻结束;

3)如果两个活动不重叠,则这两个活动是可以兼容的;

4)目标:找到相互兼容的最大活动子集

(2)活动相容性:

设有n个活动的集合E={1,2,……,n},其中,每个活动都要求使用同一资源,如会场等。

而在同一时间内,只有一个活动能够使用该资源。

每个活动i都有一个要求使用该资源的起始时间$s_i$和一个结束时间$f_i$,且$s_i$<$f_i$

如果选择了活动i,则它在半开时间区间[$s_i$,$f_i$)内占用资源。

若区间[$s_i$,$f_i$)与区间[$s_j$,$f_j$)不相交,则称活动i与活动j相容。

(3)贪心策略:

先安排结束时间最早的活动,可以使得剩余的时间极大化,从而安排更多的活动。

(4)伪代码:

1.将所有活动按照结束时间从小到大排列;

2.扫描所有活动的开始时间与结束时间,若扫描到活动i的开始时间早于最近安排的活动j的结束时间,则不安排活动i,否则,安排活动i。

(5)代码:

int greedySelector(int s[],int f[],bool a[],int n){
    //n代表事件个数,bool类型的数组a表示活动i是否被安排
    //各活动的起始时间和结束时间存储于数组s和f中且按结束时间的非减序排序
    int i=0,j=0,count=0;//i表示处理的第i个活动,j表示刚刚安排的活动,count记录安排活动的数量
    //初始化
    a[i]=true;//安排第一个事件
    j=0;
    count++;
    for(i=1;i<n;i++){
        if(s[i]>f[j]) {
            a[i]=true;
            j = i;
            count++;
        }
        else{
            a[i]=false;
        }
    }
    return count;
}

 

(6)其他贪心策略:

1)最早开始时间

按照开始时间$s_i$非递减排序

2)最早结束时间 

按照结束时间$s_i$非递减排序

3)最短间隔

按照间隔长度$f_i-s_i$非递减排序

4)最小冲突

对于每个活动,计算该活动的冲突活动数$c_j$,并按照$c_j$非递减排序

(7)证明贪心算法对于活动安排问题的求解是整体最优解:

对于活动安排问题来说:

最优解即为:使得相容集合的规模最大

数学归纳法进行证明)

证明:

设E={1,2,……,n}活动集合(集合有序)

且按照活动安排结束时间递增排序,即:活动1具有最早的完成时间

假设:活动安排中有一个包含贪心选择的最优解

设A$\subseteq$E是该问题的一个最优解,且集合A也有序,对于A中的活动,将其表示为{ $k_1,k_2,...,k_j$},则有活动的开始时间集合S={ $s[k_1],s[k_2],...s[k_j]$},以及结束时间集合F=$\{f[k_1],f[k-2],...,f[k_j]\}$

找到一个集合A,首先:A是问题最优解;其次,A是贪心选择出来的集合。

当k=1时,A即为所求。

当k!=1时,设$B=$$A-\{k_1\}\cup\{1\}=\{1,k_2,k_3,...,k_j\}$

由于,$f[1]\leqslant f[k]$,将集合A更改之后,整个集合仍然相容,元素个数也没有改变,集合B也是最优解。

同理,以及将A中的元素替换,由于贪心选择的原理,每次选择结束时间最早的事件,则替换之后,集合仍然是相容的,将所有元素均替换为贪心选择的元素,则集合既是贪心选择的结果,也是最优解,即:贪心选择出的集合是最优解,得证。#

五.贪心算法的基本要素

1.贪心选择性质

所求问题的整体最优解可以通过一系列局部最优的选择达到。

用数学归纳法证明,通过每步的贪心选择,最终可以得到问题的整体最优解。

2.最优子结构性质

当一个问题的最优解包含其子问题的最优解,称此问题具有最优子结构性质。

问题的最优子结构性质是该问题可以用动态规划算法或者贪心算法求解的关键特征。

3.贪心算法和动态规划差别

动态规划:自底向上

贪心算法:从开始逐次迭代

0-1背包问题:

问题描述:

给定n种物品和一个背包。物品i的重量是$w_i $,其价值为$v_i$,背包的容量为C。应如何选择装入背包的物品,使得装入背包中物品的总价值最大。

背包问题:

问题描述:

与0-1背包问题类似,所不同的是,在选择物品i装入背包时,可以选择物品的一部分,而不一定要全部装入背包中。

这两类问题都具有最优子结构性质,但是背包问题可以用贪心算法求解,而0-1背包问题却不能用贪心算法求解。

用贪心算法求解背包问题的基本步骤:

1.计算每种物品单位重量的价值$\frac{v_i}{w_i}$;

2.依据贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包; 

3.若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包;

4.依照此策略一直进行下去,直到背包装满为止。

代码实现:

#include <iostream>
using namespace std;

struct node{
    float weight;
    float value;
    int number;
};

int greedySelector(int s[],int f[],bool a[],int n);
float knapsack(float c,int n,float v[],float w[],float x[]);
int partition(struct node a[],int first,int end);
void quickSort(struct node a[],int first,int end);


int main() {
    int n=5;
    float v[]={10,40,20,30,25};
    float w[]={15,12,8,9,10};
    float x[5];
    float c=21.5;
    knapsack(c,n,v,w,x);
    for(int i=0;i<n;i++){
        cout<<x[i]<<" ";
    }
    cout<<endl;
    return 0;
}

int greedySelector(int s[],int f[],bool a[],int n){
    //n代表事件个数,bool类型的数组a表示活动i是否被安排
    //各活动的起始时间和结束时间存储于数组s和f中且按结束时间的非减序排序
    int i=0,j=0,count=0;//i表示处理的第i个活动,j表示刚刚安排的活动,count记录安排活动的数量
    //初始化
    a[i]=true;//安排第一个事件
    j=0;
    count++;
    for(i=1;i<n;i++){
        if(s[i]>f[j]) {
            a[i]=true;
            j = i;
            count++;
        }
        else{
            a[i]=false;
        }
    }
    return count;
}

float knapsack(float c,int n,float v[],float w[],float x[]){
    //函数的返回值表示总价值
    //c表示背包容量
    //n表示物品个数
    //数组v表示价值value,数组w表示重量weight,数组x表示每个物品放入背包的比例
    struct node *p=new struct node[n];
    int i,j;
    float sum=0;
    for(i=0;i<n;i++){
        p[i].number=i;
        p[i].value=v[i];
        p[i].weight=w[i];
        x[i]=0;
    }
    quickSort(p,0,n-1);

    for(i=0;i<n;i++){
        if(p[i].weight>c){
            break;
        }
        else{
            x[p[i].number]=1;
            sum+=p[i].value;
            c-=p[i].weight;
        }
    }
    if(i<n){
        x[p[i].number]=c/p[i].weight;
        sum+=p[i].value*c/p[i].weight;
    }
    delete []p;
    return sum;
}

int partition(struct node a[],int first,int end){
    int i=first,j=end;
    struct node temp;
    while(i<j){
        while(i<j&&(a[j].value/a[j].weight)<=(a[i].value/a[i].weight)){
            --j;
        }
        if(i<j){
            temp=a[i];
            a[i]=a[j];
            a[j]=temp;
        }
        while(i<j&&(a[j].value/a[j].weight)<=(a[i].value/a[i].weight)){
            ++i;
        }
        if(i<j){
            temp=a[i];
            a[i]=a[j];
            a[j]=temp;
        }
    }
    return i;//i==j时,返回轴值
}

void quickSort(struct node a[],int first,int end){
    if(first<end){
        int pos=partition(a,first,end);
        quickSort(a, first, pos-1);
        quickSort(a, pos+1, end);
    }
}

贪心算法不适用于0-1背包问题:

对于0-1背包问题,贪心算法得不到最优解。

无法保证最终能够将背包装满,部分闲置的背包空间使得单位重量的价值变低了。

在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再做出最好选择。由此导出了许多相互重叠的子问题,这正是利用动态规划求解的重要特征。

 

版权声明:本文为博主作者:Hsianus原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/Hsianus/article/details/134886464

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2024年1月11日
下一篇 2024年1月11日

相关推荐