贪心算法

贪心算法

求解最优化问题的算法通常需要经过一系列步骤,在每个步骤都面临多种选择。比如使用动态规划。
动态规划算法的一般步骤有如下:

  1. 刻画一个最优解的结构特征
  2. 递归地定义最优解的值
  3. 计算最优解的值
  4. 利用计算出的信息构造一个最优解

贪心算法并并不保证得到最优解。但是对于很多问题可以得到最优解。
常见的贪心算法的应用有Huffman编码,最小生成树算法,单源最短路径的Dijkstra算法。

活动选择问题

假定一个有n个活动的集合 S = { a 1 , a 2 , … , a n } S=\{a_1,a_2,…,a_n\} S={a1,a2,,an},这些活动使用同一个资源(例如同一个阶梯教室),这个资源在某个时刻只能供一个活动使用。每个活动 a i a_i ai 都有一个开始时间 s i s_i si和一个结束时间 f i f_i fi,其中 0 ≤ s i &lt; f i &lt; ∞ 0 \le s_i &lt;f_i&lt;∞ 0si<fi<。如果任务被选中,任务ai发生在搬开区间 [ s i , f i ) [s_i,f_i) [si,fi)期间。如果二个活动 a i a_i ai a j a_j aj满足 [ s i , f i ) [s_i,f_i) [si,fi) [ s j , f j ) [s_j,f_j) [sj,fj)不重叠,则它们是兼容的。

目标:希望选择出一个最大兼容活动集。
假定活动已经按照结束时间的单调递增顺序排序:
在这里插入图片描述
可以看到子集 { a 3 , a 9 , a 11 } \{a_3,a_9,a_{11}\} {a3,a9,a11}是相互兼容的活动,但不是一个最大子集。
子集 { a 1 , a 4 , a 8 , a 11 } \{a_1,a_4,a_8,a_{11}\} {a1,a4,a8,a11}是一个最大子集,同样另外一个 { a 2 , a 4 , a 9 , a 11 } \{a_2,a_4,a_9,a_{11}\} {a2,a4,a9,a11}最大子集

动态规划,把问题分成二个子问题,将二个子问题的最优解整合成原问题的一个最优解。确定哪些子问题用于最优解时,要考虑几种选择。
贪心算法只需要考虑一种选择(贪心的选择)

验证最优子结构性质

S i j S_{ij} Sij表示在 a i a_i ai结束后开始,在 a j a_j aj开始之前结束的那些活动的集合。求 S i j S_{ij} Sij的一个最大的相互兼容的活动子集,假定这个子集为 A i j A_{ij} Aij,包含活动 a k a_{k} ak。由于最优解包含 a k a_k ak,得到二个子问题
S i k S_{ik} Sik( a i a_i ai结束后开始切 a k a_k ak开始之前结束的那些活动)
S k j S_{kj} Skj( a k a_{k} ak结束后开始切 a j a_{j} aj开始之前结束的那些活动)
A i j = A i k ∪ a k ∪ A k j A_{ij}=A_{ik}∪{a_k} ∪A_{kj} Aij=AikakAkj,活动数目 ∣ A i j ∣ = ∣ A i k ∣ + ∣ A k j ∣ + 1 |A_{ij}|=|A_{ik}|+|A_{kj}|+1 Aij=Aik+Akj+1

动态规划的递归式

c [ i , j ] = { 0 , if  S i j = ∅ m a x { c [ i , k ] + c [ k , j ] + 1 } , if  S i j   ≠   ∅ c[i,j]=\begin{cases} 0, &amp; \text{if $S_{ij}$=$\varnothing$}\\ max\{c[i,k]+c[k,j]+1\}, &amp; \text{if $S_{ij}$ $\neq$ $\varnothing$} \end{cases} c[i,j]={0,max{c[i,k]+c[k,j]+1},if Sij=if Sij ̸= 
C [ i , j ] C[i,j] C[i,j]表示集合大小

动态规划每一次分解为子问题,会考虑所有的活动。如果无需考虑所有子问题就可以选着出一个活动加入到最优解。
贪心选择:只考虑一个选择。
一种贪心策略是:尽可能选择最早结束的那一个,把剩下的资源提供给它之后的尽量多的活动。

证明最早结束的活动总是最优解的一部分

证明非空子问题 S k S_k Sk,令 a m a_m am S k S_k Sk中结束时间最早的活动,则 a m a_m am S k S_k Sk的某个最大兼容活动子集中。
证:
A k A_k Ak S k S_k Sk的一个最大兼容活动子集。 A j A_j Aj A k A_k Ak中结束时间最早的活动。
1.若 a j = a m a_j=a_m aj=am,已近证明am在Sk的某个最大兼容活动子集。
2.若 a j ≠ a m a_j≠a_m aj̸=am,令集合 S k ′ = ( A k − a j ) ∪ a m S_k^′=(A_k−{a_j })∪{a_m} Sk=(Akaj)am,将 A k A_k Ak中的 a j a_j aj替换为 a m a_m am ∣ A k ′ ∣ = ∣ A k ∣ |A_k^′ |=|A_k| Ak=Ak ,可以得出结论 A k ′ A_k^′ Ak 也是 S k S_k Sk的一个最大兼容活动子集,并且它包含 a m a_m am

递归贪心算法

活动选择算法不必使用递归算法,可以自顶向下计算,选择一个活动放入最优解,然后对剩余子问题进行求解。

在这里插入图片描述
s开始时间,f结束时间,k求解得子问题,n问题的规模
一开始调用RECURSIVE-ACTIVITY-SELECTOR(s,f,0,n)

迭代贪心算法

在这里插入图片描述

时间复杂度为O(n)

贪心算法原理

贪心算法通过做出一系列选择来求出问题的最优解。在每个决策点,它做出在当时来看最佳的选择。这种策略并不能保证总能找到最优解。
基本步骤

  1. 确定问题的最优子结构
  2. 设计一个递归算法(活动选择问题,给出了递归式)
  3. 证明如果我们做出一个贪心选择,则只剩下一个子问题
  4. 证明贪心选择总是安全的
  5. 设计一个递归算法实现贪心策略
  6. 把递归算法转换为迭代算法

更加一般步骤:

  1. 最优化转化成:对其做出一次选择后,只剩下一个子问题求解。
  2. 证明做出贪心选择后,原问题总是存在最优解,即贪心选择总是安全的。
  3. 证明做出贪心选择后,剩余子问题满足性质:其最优解与贪心选择组合即可以得到原问题的最优解。

如何证明一个贪心算法时候能够求解一个最优化问题?

并没有适合所有情况的方法。
一般情况下如果条件有贪心选择和最优子结构这二个特征,就可以向贪心算法靠拢了。

使用整数规划求解最优解问题

首先对每个活动构造0,1变量,x1,x2,…,xn
目标函数为:
Max(x1 + x2 + … + xn)
约束为f(i)<=s(j),i < j
在这里插入图片描述
lpsolve代码

int SolveTest2(vector<int>& s, vector<int> f)
{
	lprec *lp;
	int Ncol, *colno = NULL, j, ret = 0;
	REAL *row = NULL;
	Ncol = s.size();
	lp = make_lp(0, Ncol);
	if (lp == nullptr)
	{
		ret = 1;
	}

	if (ret == 0)
	{
		char name[20];
		for (int i = 1; i <= s.size(); ++i)
		{
			string s = "x" + std::to_string(i);
			strcpy_s(name, s.c_str());
			set_col_name(lp, i, name);
		}
		colno = (int*)malloc((Ncol+1) * sizeof(*colno));
		row = (REAL*)malloc((Ncol+1) * sizeof(*row));
		if (colno == nullptr || row == nullptr)
		{
			ret = 2;
		}
	}
	// max:x1+x2+..xn
	int count = 0;
	for (int i = 1; i <= Ncol; ++i)
	{
		colno[count] = i;
		row[count] = 1;
		count++;
	}

	set_obj_fnex(lp, count, row, colno);
	set_maxim(lp);
	//x1,x2,..xn 零一变量
	for (int i = 1; i <= Ncol; ++i)
	{
		set_binary(lp, i, true);
	}

	int ssize = s.size();
	//添加约束
	for (int i = 0; i < ssize; ++i)
	{
		for (int j = i + 1; j < ssize; ++j)
		{
			if (f[i] > s[j])
			{
				count = 0;
				colno[count] = i + 1;
				row[count++] = 1;
				colno[count] = j + 1;
				row[count++] = 1;
				add_constraintex(lp, count, row, colno, LE, 1);
			}
		}
	}

	set_add_rowmode(lp, FALSE);

	write_LP(lp, stdout);
	/* write_lp(lp, "model.lp"); */

	/* I only want to see important messages on screen while solving */
	set_verbose(lp, IMPORTANT);

	/* Now let lpsolve calculate a solution */
	ret = solve(lp);
	if (ret == OPTIMAL)
		ret = 0;
	else
		ret = 5;

	if (ret == 0) {
		/* a solution is calculated, now lets get some results */

		/* objective value */
		printf("Objective value: %f\n", get_objective(lp));

		/* variable values */
		get_variables(lp, row);
		for (j = 0; j < Ncol; j++)
			printf("%s: %f\n", get_col_name(lp, j + 1), row[j]);

		/* we are done now */
	}

	/* free allocated memory */
	if (row != NULL)
		free(row);
	if (colno != NULL)
		free(colno);

	if (lp != NULL) {
		delete_lp(lp);
	}

	return(ret);

}

结果为

/* Objective function */
max: +x1 +x2 +x3 +x4 +x5 +x6 +x7 +x8 +x9 +x10 +x11;

/* Constraints */
+x1 +x2 <= 1;
+x1 +x3 <= 1;
+x1 +x5 <= 1;
+x1 +x10 <= 1;
+x2 +x3 <= 1;
+x2 +x5 <= 1;
+x2 +x10 <= 1;
+x3 +x4 <= 1;
+x3 +x5 <= 1;
+x3 +x6 <= 1;
+x3 +x10 <= 1;
+x4 +x5 <= 1;
+x4 +x6 <= 1;
+x4 +x7 <= 1;
+x4 +x10 <= 1;
+x5 +x6 <= 1;
+x5 +x7 <= 1;
+x5 +x8 <= 1;
+x5 +x9 <= 1;
+x5 +x10 <= 1;
+x6 +x7 <= 1;
+x6 +x8 <= 1;
+x6 +x9 <= 1;
+x6 +x10 <= 1;
+x7 +x8 <= 1;
+x7 +x9 <= 1;
+x7 +x10 <= 1;
+x8 +x9 <= 1;
+x8 +x10 <= 1;
+x9 +x10 <= 1;
+x10 +x11 <= 1;

/* Variable bounds */
x1 <= 1;
x2 <= 1;
x3 <= 1;
x4 <= 1;
x5 <= 1;
x6 <= 1;
x7 <= 1;
x8 <= 1;
x9 <= 1;
x10 <= 1;
x11 <= 1;

/* Integer definitions */
int x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11;
Objective value: 4.000000
x1: 1.000000
x2: 0.000000
x3: 0.000000
x4: 1.000000
x5: 0.000000
x6: 0.000000
x7: 0.000000
x8: 0.000000
x9: 1.000000
x10: 0.000000
x11: 1.000000
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值