C++步步为营

GREEDY ALGORITHM

2019-08-01  本文已影响0人  世界上的一道风

贪心算法原理

  1. 贪心选择性质:可以通过做出局部最优选择来构造全局最优选择。这也是与动态规划不同的地方,动态规划中每一个步骤都要进行选择,选择依赖于子问题的解。贪心算法总是选择当前最佳的选择,然后求解剩下的唯一的子问题。因此,贪心算法通常是自顶向下的,进行一次又一次选择,将给定的问题实例变得更小。
  1. 最优子结构:最优子结构的定义是,一个问题的最优解包含了其子问题的最优解。这个性质也是能否运用贪心算法和动态规划的关键。因此一个问题是否能应用贪心算法,需要论证每个步骤的贪心选择是否会生成原问题的最优解。(数学归纳)

数学归纳

具体问题

1. 活动选择问题

其中开始时间s_i,结束时间为f_i,具体实例如:

image.png

这个例子里有两个最大兼容任务活动子集,{(a_1,a_4,a_8,a_{11})}还有{(a_2,a_4,a_9,a_{11})}的数目最多且元素不重叠。
为了后面应用贪心算法,先按结束时间对f_i做了排序。

因此S_{i,j}中最大兼容任务活动子集A_{i,j}的个数为:(3)|A_{i,k}| + |A_{k,j}| + 1

在式子(2)里我们假设了A_{i,j}里包含了两个子问题S_{i,k}S_{k,j}的最优解。可以用书上说的验证方法,设S_{k,j}的一个兼容活动子集为\hat A_{k,j},其大小满足|\hat A_{k,j}| \ge | A_{k,j}|,将\hat A_{k,j}作为S_{i,j}的最优解一部分,构造出一个兼容活动子集,其大小大于(3),这与A_{i,j}为最优解矛盾,因此,证明A_{i,j}里包含了两个子问题S_{i,k}S_{k,j}的最优解。

上面假设最优解包含了a_k,实际并不知道,需要寻找k
c_{i,j} = 0 , \ if : S_{i,j} = \emptyset\\ c_{i,j} = \max_{a_{k} \in S_{i,j}}{(c_{i,k} + c_{k,j} +1)}, \ if : S_{i,j} \neq\emptyset

c++代码:

#include <iostream>
#include<vector>
#include<climits>
using namespace std;

int DP_Activity(const vector<int> &start, const vector<int> &end)
{
  const int n = start.size();
  vector<vector<int>> table(n, vector<int>(n, 0));
  vector<int> path;
  //第一个位置为虚拟活动a_0
  for(int i=0; i<n; ++i)
  {
    for (int j=i+1; j<n; ++j)
    {
      for (int k=i+1;k<j;++k)
      {  
        if(start[k]>end[i] && end[k] < start[j])
        {
          int tmp = table[i][k] + table[k][j] + 1;
          if (tmp > table[i][j])
          {
            table[i][j] = tmp;
            path.push_back(k);
          }
        }
      } 
    }
  }
  for (int i=0; i<table.size(); ++i)
  {
    for(int j=0; j<table.size(); ++j)
    {
      cout << table[i][j] << " ";
    }
  cout<< endl;
  }
  return table[0][n-1];
}
int main() {
  //第一个位置和最后一个位置是虚拟位
  vector<int> s={0,1,3,0,5,3,5,6,8,8,2,12,INT_MAX};
  vector<int> f={0,4,5,6,7,9,9,10,11,12,14,16, INT_MAX};
  int ans = DP_Activity(s,f);
  cout << "Answer is : "<< ans << endl;
}


 ./main
0 0 0 0 1 0 1 1 2 2 0 3 4
0 0 0 0 0 0 0 0 1 1 0 2 3
0 0 0 0 0 0 0 0 0 0 0 1 2
0 0 0 0 0 0 0 0 0 0 0 1 2
0 0 0 0 0 0 0 0 0 0 0 1 2
0 0 0 0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0
Answer is : 4

参考下别人的,多个角度看一个问题收获更多:https://www.cnblogs.com/hapjin/p/5573419.html

先确定所做的贪心选择方法:直觉上,先选最早结束的活动,那就可以剩下更多的资源供其他活动选择,即选择某一最早结束的活动a_k之后,剩下的问题规模变小了,活动集合为S_k=\{s_i \ge f_k\}

书上证明了这样一种思路可以得到最优解。主要思路是a_mS_k中最早结束的活动,假设有a_jA_k的最早结束活动,替换a_ja_k,得到结论a_k也是属于一个最大兼容活动集合。

c++代码,运行时间为\Theta(n)

#include <iostream>
#include<vector>
#include<climits>
using namespace std;

void Greedy_Recursive_Activity(const vector<int> &start, const vector<int> &end, const int k, const int n, vector<int> &ans)
/*
Params: 
  start: 活动开始时间
  end: 活动结束时间(已经升序排列)
  k: S活动集合区间的左边界
  n:S活动集合区间的右边界
Return:
  void: 使用ans返回S[k,n]内的最大的兼容区间大小
*/
{
  int m=k+1;
  while(m<=n && start[m]<end[k]) //找到最早结束的活动
  {
    m += 1;
  }
  if(m<=n)
  {
    ans.push_back(m);
    Greedy_Recursive_Activity(start,end,m,n,ans);
  }
  else
    return;
}
int main() {
  //第一个位置和最后一个位置是虚拟位
  vector<int> s={0,1,3,0,5,3,5,6,8,8,2,12,INT_MAX};
  vector<int> f={0,4,5,6,7,9,9,10,11,12,14,16, INT_MAX};
  vector<int> ans;
  int n = s.size();
  Greedy_Recursive_Activity(s,f,0,n-2,ans);
  for (auto i:ans)
    cout << i << " ";
  
  cout << endl << "Answer is : "<< ans.size() << endl;
}

1 4 8 11
Answer is : 4

迭代版本:

#include <iostream>
#include<vector>
#include<climits>
using namespace std;

vector<int> Greedy_Iter_Activity(const vector<int> &start, const vector<int> &end)
/*
Params: 
  start: 活动开始时间
  end: 活动结束时间(已经升序排列)
Return:
  vector<int>: 使用ans返回S[k,n]内的最大的兼容区间大小
*/
{
  vector<int> ans;
  int n = start.size();
  ans.push_back(1);
  int k=1;
  for (int m=2; m<n-1; ++m)
  {
    if(start[m] > end[k])
    {

      ans.push_back(m);
      k = m;
    }
  }
  return ans;
}
int main() {
  //第一个位置和最后一个位置是虚拟位
  vector<int> s={0,1,3,0,5,3,5,6,8,8,2,12,INT_MAX};
  vector<int> f={0,4,5,6,7,9,9,10,11,12,14,16, INT_MAX};
  vector<int> ans;
  int n = s.size();
  ans = Greedy_Iter_Activity(s,f);
  for (auto i:ans)
    cout << i << " ";
  
  cout << endl << "Answer is : "<< ans.size() << endl;
}

背包问题9讲学习记录

1. 01背包
#include <iostream>
#include<vector>
#include<climits>
using namespace std;

void Zero_One_knapsack(const vector<int> &weight, const vector<int> &value, const int capacity, vector<vector<int>> &ans, vector<vector<int>> &path)
/*
Params:
  weight: weight list of objects.
  value: corresponding valus of each object.
  capacity: total size of knapsack.
  ans: return answer table.
  path: return backtracking table.
Return:
  None.
*/
{
  const int n = weight.size();
  vector< vector<int>> _ans(n, vector<int>(capacity+1, 0));
  vector< vector<int>> _path(n, vector<int>(capacity+1, 0));
  for(int i=1; i<n;++i)
  {
    for(int j=1; j<=capacity; ++j)
    {
      if(j < weight[i])  // whether backpack size enough for weight
      {
        _ans[i][j] = _ans[i-1][j]; 
      }else{
        if (_ans[i-1][j] > _ans[i-1][j-weight[i]] + value[i])
        {
          _ans[i][j] = _ans[i-1][j];
        }
        else{
          _ans[i][j] = _ans[i-1][j-weight[i]] + value[i];
          _path[i][j] = i;
        }
      }
    }
  }

  ans = _ans;
  path = _path; 
}

void Backtracking(const vector<vector<int>> &ans, const vector<int> &weight, const vector<int> &value, const int i, const int j)
/*
Params:
  weight: weight list of objects.
  value: corresponding valus of each object.
  capacity: total size of knapsack.
  i: size of objects (without zero visual item).
  j: size of backpack.
Return:
  None.
*/
{
  if (i > 0)
  {
    if (ans[i][j] == ans[i-1][j])
    {
      Backtracking(ans, weight, value, i-1, j);
    }
    else
    {
      if (j-weight[i]>=0 && ans[i][j] == ans[i-1][j-weight[i]] + value[i])
      {
        cout<< i << " ";
        Backtracking(ans, weight, value, i-1, j-weight[i]);
      }

    }
  }
}

int main() {
  int mark = 0;
  vector<int> weight={mark,1,2,5,6,7};
  vector<int> value={mark,1,6,18,22,28};
  const int capacity = 11;
  vector<vector<int>> ans;
  vector<vector<int>> path;
  Zero_One_knapsack(weight, value, capacity, ans, path);

  for (int i=0; i< ans.size(); ++i)
  {
    for(int j=0; j < ans[0].size(); ++j)
        cout << ans[i][j] << " ";
    cout << endl;
  }  
  cout << endl << "Answer is : "<< ans[ans.size()-1][capacity] << endl;
  Backtracking(ans,weight, value, 5, 11);
}

0 0 0 0 0 0 0 0 0 0 0 0
0 1 1 1 1 1 1 1 1 1 1 1
0 1 6 7 7 7 7 7 7 7 7 7
0 1 6 7 7 18 19 24 25 25 25 25
0 1 6 7 7 18 22 24 28 29 29 40
0 1 6 7 7 18 22 28 29 34 35 40

Answer is : 40
4 3
2.分数背包
上一篇下一篇

猜你喜欢

热点阅读