Day_28,贪心算法

书写思路
什么是贪心算法
贪心算法: 在每一步都做出 当时 看起来是最优的选择,并寄希望该选择能够得到全局最优解。
贪心算法不能保证得到最优解,但是对许多问题确实能得到最优解,接下去我会用两篇博客来说明。

这篇文章将会按照下面几个部分来写

1、动态规划求解活动选择问题
2、抛出贪心算法求解活动选择问题
3、推导一下时间复杂度为
4、比较两者区别及优劣
5、一些想法:“如果是要在最少天数内将这些活动全部进行完,这个贪心算法是不是不可行?是否有别的贪心思想?”
在这里插入图片描述
问题如下: 在一天时间内,安排尽可能多的活动,并且保证每项活动的时间不发生冲突,活动列表如下,其中si是活动开始时间,fi是活动结束时间。
在这里插入图片描述

动态规划求解

我们发现这个问题可以使用动态规划问题解决:
1、最优解结构特征
我们令 Sij 为在 ai 结束之后, aj 开始之前的活动,现在我们假 设Sij是最大活动列表 ,该列表包含活动 ak ,那么 SikSkj 也是各自时间段内的一个最大活动列表。
(可以用反证法证明:如果 Sik 不是最大活动列表,则可以找到一个更大的列表替代 Sik ,那么 Sij 就不是最大活动列表,与假设矛盾。 Skj 同理)
2、定义递归解
从上面的定义我们可以得到如下定义,其中c[i,j]表示在 ai 结束之后, aj 开始之前的活动数量,当 Sij≠∅ 的时候, |Sij| = |Sik|+|Skj| +|ak| ,也就是第二个式子所表达的
在这里插入图片描述
3、4自底向上计算最优解及构造最优解

伪代码

MAX-ACTIVITY(s,f,len)
1	c[0..len] be new tables	// c 存放区间活动数量 
2	for i=0 to len  
3	   if f[i]<=s[i+1]     			
4	   c[i,i+1]=2					// 初始化 [0,1] [1,2]这种间隔1的
5	for l=2 to len                	// l子串长度比如 [0,2] l为2
6	   for i=0 to len-l				// i 结束活动序号
7	 	  j = i+l					// j 开始活动序号, 两个组合在一起就是i活动结束之后、 j活动开始前
8	      for k=i+1 to j			// k遍历j之后的所有活动查看是否有满足的
9	         if s[k]>=f[i] and f[k]<=s[j]
10	         	q = c[i,k]+c[k,j]+1
11	   			if q>c[i,j]
12	 				c[i,j]=q
13	for i=0 to len                	
14	   for j=i+1 to len				
15	     if f[i]<=s[j] 
16	       c[i,j]= c[i,j] +2			// i活动结束之后 j活动开始之前,如果ij两个活动满足下面条件则自身也属于这个区间
17	return c

运行代码

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <vector>
using namespace std;

//采用自底向上的方式,c用来存储已经计算出来的结果
vector<vector<int>> MAX_ACTIVITY(int *S,int *F,int len)
{
    vector<vector<int>> c(len,vector<int>(len));//存放区间内活动数量
    for (int i=0;i<len-1;i++) //初始化[0,1] [1,2]这种间隔1的
        if (F[i]<=S[i+1])
            c[i][i+1]=2;
    //用这个逻辑就剩下 [0,1] [1,2]这种间隔1的没求到
    //并且这个方法没有把头尾两个点包括进去
    for (int l=2;l<len;l++) //子串长度
        for (int i=0;i<len-l;i++)
        {
            int j = i+l;
            for (int k=i+1;k<j;k++)
            {
                cout<<"["<<i<<","<<j<<"]"<<" = ";
                cout<<"["<<i<<","<<k<<"]"<<" + ";
                cout<<"["<<k<<","<<j<<"]"<<" = ";
                if (S[k]>=F[i] && F[k]<=S[j])
                {
                    int q = c[i][k]+c[k][j]+1;
                    if (q>c[i][j])
                    {
                        c[i][j]=q;
                    }
                }
                cout<<c[i][j]<<endl;
            }
        }
    for (int i=0;i<len;i++) //i活动结束之后 j活动开始之前,如果ij两个活动满足下面条件则自身也属于这个区间
        for (int j=i+1;j<len;j++)
            if (F[i]<=S[j])
                c[i][j]=c[i][j] + 2;
    return c;
}

void OutPutMaxList(vector<vector<int>> activeList, int len)
{
    int maxCount = 0;
    for (int i=0;i<len;i++)
        for (int j=i+1;j<len;j++)
            if (activeList[i][j]>maxCount)
                maxCount = activeList[i][j];
    for (int i=0;i<len;i++)
    {
        int curCount = 0;
        if (activeList[i][len-1]==maxCount)
        {
            cout<<i+1<<" ";
            for (int j=i+1;j<len;j++)
            {
                if(activeList[i][j]>curCount)
                {
                    cout<<j+1<<" ";
                    curCount = activeList[i][j];
                }
            }
        }
        cout<<endl;
    }

}

int main()
{
    //活动是按照结束时间递增排好序的
    int s[] = {1,3,0,5,3,5,6,8,8,2,12};
    int f[] = {4,5,6,7,9,9,10,11,12,14,16};
    int len = sizeof(s) / sizeof(*s);
    vector<vector<int>> result = MAX_ACTIVITY(s,f,len);
    for (int i=0;i<len;i++)
    {
        for (int j=0;j<len;j++)
            cout<<result[i][j]<<" ";
        cout<<endl;
    }
    OutPutMaxList(result,len);
}

运算结果如下
在这里插入图片描述
中间省略了一些
在这里插入图片描述
从上面的打印结果可以看出后面的每一个步骤都是建立在之前计算出结果的基础上计算的,举个例子 [2,5] = [2,3] + [3,5],[2,3] 和 [3,5] 在之前都已经计算过了。

贪心算法求解

想法: 贪心算法是做出局部最优解,那对于已经按照结束时间排序好的活动,局部最优就是 每次都选出当前最早结束的活动,这样可以保证留下的时间最多 ,设其为 am
易证明: 如果一个活动集是最大活动集Sk,并且不包含am,则am 在Sk里某个最大兼容活动子集中
在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <vector>
using namespace std;

vector<int> Greedy_Activity(int *S,int *F,int len)
{
    vector<int> result;
    result.push_back(1);
    int curActivityIndex = 1;
    for (int i=2;i<len;i++)
    {
        if (F[curActivityIndex]<=S[i])
        {
            result.push_back(i);
            curActivityIndex = i;
        }
    }
    return result;
}

int main()
{
    //活动是按照结束时间递增排好序的
    int s[] = {1,3,0,5,3,5,6,8,8,2,12};
    int f[] = {4,5,6,7,9,9,10,11,12,14,16};
    int len = sizeof(s) / sizeof(*s);
    vector<int> result = Greedy_Activity(s,f,len);
    int reLen = result.size();
    for (int i=0;i<reLen;i++)
        cout<<result[i]<<" ";
    cout<<endl;
}

时间复杂度比较

动态规划:
通过观察冬天规划伪代码 5~12行可以看出,运行时间为 O(n3,因为嵌套了3层循环
贪心算法:
贪心算法观察可知,只执行了一次循环嵌套,因此运行时间为 O(n)

两者优缺点

动态规划:
优点:更为准确,并且可以得到所有情况(只要将上面的OutPutMaxList方法稍微改进一下就可以输出所有最长活动解)
缺点:运行时间长
贪心算法:
优点:运行时间短
缺点:得到的仅是一个最优解

拓展

如果是要在最少天数内将这些活动全部进行完,这个贪心算法是不是不可行?是否有别的贪心思想?
1、首先原本的贪心算法是不可行的,因为活动数量最多并不能确保他的时间使用率最高。
现在有两个想法:
1、每一天都安排尽量满的时间,让时间利用率变高
2、每天都安排尽量多的活动
明显第一种思想得到的才会是正确的,但是如何构造他的最优子结构?
首先想是从活动入手,怎么样安排才能让当天的时间利用率最高?
首先想到的是枚举每一种组合,找到组合起来活动时间最长的组成一天,然后再在剩下的活动中继续枚举找到组合时间最长的一天
按照上述思想如何构造最优子结构?

无果

然后又有了新的想法,如果每天安排的活动数量最多,则使用的天数也是最少的,得到的一定是其中一个最优解
按照这个想法,我们就可以继续沿用之前的贪心算法,并且只要在每次选剩下的活动中再次选择最多的活动集,就可以得到最短的天数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值