Finding the longest Increasing subsequence

我们可以使用动态规划(dynamic programming)解决这个寻找最长递增子序列问题。  

最长递增子序列问题是各个互联网公司都喜欢使用的一道经典面试题。 下面给出这个问题的定义。

(1)何为subsequence?

首先, 我们要知道sequence 元素之前有前后的顺序关系。 例如, 

1 3 4 5 和1 4 3 5 是两个不同的sequence, 尽管二者有相同的元素组成的。

对于subsequence, 当然就是从一个给定的sequence中按照从左往右 的顺序抽取出数据出来,不打乱顺序,  组成新的一个sequence, 那么我们称这个sequence是原sequence的subsequence(子序列)。


(2)何为increasing?

就是我们抽取的元素不是随便抽取的。 而是要保证每一个抽取的元素都大于前一个抽取的元素。 这样我们得到的这个新的序列就是increasing subsequence 了。


longest increasing subsequence 问题:

就是从给定的sequnce中, 能够抽取出一个最长的increasing subsequence.

例如, 对于给定的sequence: 1, 3, 2, 5, 9, 这个sequence的长度为5。

不难看出这个sequence的最长的increasing subsequence为: 1,3,  5, 9. (1 < 3 < 5 < 9, 所以当然是increasing(递增的)的)长度为4。 但是一个sequence的LIS(longest increasing subsequence)可能并不是唯一的。

例如上个sequence, 另一个LIS为1, 2, 5, 9。


通常对于LIS问题, 只会要求我们sequence其中一个LIS即可。


要解决LIS问题, 主要有两种算法。 

一个算法是动态规划(dynamic programming)算法, 该算法的时间复杂度为O(n^2), 比较简单易懂, 一个是贪婪算法(greedy Algorithm), 时间复杂度为O(n log(n))。


LIS 问题的解法。 

我们的问题:

对于一个size 为N的vector D, 找到其LIS。

solution: 只要我们找到D的所有的subsequence, 抽出这些subsequence 中满足increasing的, 然后我们就可以找打LIS了。

所以我们的naive算法如下:


代码如下:

//Problem:
//  For a sequence of data: a[0], a[1], ..., a[N], find longest increasing subsequence.
//Example: a[] = {3, 2, 6, 4, 5, 1}
//   Increasing Subsequcne: {3, 6}, {2,6}, {2, 4, 5}, {1}
//
//Simple Algorithm:
//for i=[N..1]:
//   1. Find all combination of i numbers;   (N!)/(i!)(N-i)!    O(exp(N))
//   2. See if there is one sorted increasingly.
//
//
//Algorithm 1:
//   for i=[0..N], calculate LIS[i], which is the longest increasing subsequence that ends with a[i]

//LIS[0]:  3
//LIS[1]:  2
//LIS[2]:  2,6
//LIS[3]:  2,4
//LIS[4]:  2,4,5
//LIS[5]:  1

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void prt(vector<int>& arr, string msg = "") {
	cout << msg << " ";
	for  (auto i: arr) {
		cout << i << " ";
	}
	cout << endl;
}


void calc_LIS(vector<int>& D) {
	vector< vector<int> > L(D.size());  // The longest increasing subsequence ends with D[i]

   L[0].push_back(D[0]);

	for (int i=1; i<D.size(); i++) {
		for(int j=0; j<i; j++) {
			if ( (D[j] < D[i]) && ( L[i].size() < L[j].size() ) ) {
				L[i] = L[j];
			}
		}
      L[i].push_back(D[i]);
	}

	for (auto x: L) {
		prt(x);
	}
}

int main() {
	int a[] = {3, 2, 6, 4, 5, 1};
	vector<int> arr(a, a + sizeof(a)/sizeof(a[0]));

	prt(arr, "Data In:");
	calc_LIS(arr);

	return 0;
}






上述算法, 从长度为N的vector 开始所有, 如果子序列长度为N 的D的的subsequence 中有一个increasing subsequence, 然后就退出(break)。 如果没有找到, 找D的长度为N-1 的所有子序列, 只要其中有一个increasing的, 就退出。 一次迭代下去, 最终我们得到的当然是LIS了。

这个算法比较simple, 但是时间复杂度不是很好, 最坏情况下(worst case)为(for loop 循环到底, 搜索到底):



时间复杂度达到指数量级了, 所以可以说该算法效果很差。


所以, 我们需要一个更好的算法。这就要求助于dynamic programming(动态规划)。


所谓的动态规划, 就是将一个复杂的问题break down 为一系列的小的subproblems,  动态规划通过对每个子子问题只求解一次, 将其解保存在一个表格中, 从而无需对每个子子问题都重新计算, 避免了这种不必要的计算工作。 动态规划方法通常用来求解最优化问题。 此类问题有许多可行解, 但是只有一个最优解。 这也是我们需要苦苦寻找的的解。


对于上述的LIS问题, 我们的问题是:

给定一个size 为N的 vector D, 找到其LIS。

上述问题的子问题是(subproblem):



也就是说定义一个vector L, L[i] 也是一个vector。 L[i]  is the longest increasing subsequence of D that ends with D[i]。 换句话说, D的所有increasing subsequence that ends with D[i], L[i]  is the longest。

不难看出, L[0] = {D[0]}

举个例子:

如下为D:



运算过程为:



解释:

L[5]是 {1}, 因为末尾为D[5](即1)的LIS(当然是increasing)为{1}。 


所以只要我们找到所有的L, 我们就很容易找到L最长的为{2, 4, 5}。 这是D的LIS。


同过对问题的分析, 我们有如下的公式:



也就是说对于所有的j, 找到L[0], L[1], ... L[j]中最长的那一个, L[j] 的最后一个元素为D[j], 只要满足j < i, D[j] < D[i] , 我们就选择最长的LIS, 然后将D[j] 加到L[i] 的后面。

也就是说, 如下图:



例如, 当i = 4 时候, 即

L[4] =  MAX(...) + "D[4]"

我们从L[4] 看时, L[4] 的末尾为D[4]。   然后我们从L[0], L[1], ...L[3]中选择最长的一个, 即L[3] . 为 {2, 4}, 由于满足D[3] < D[4], 所以我们将D[4] 接在L[3]的后面。 于是我们得到{2, 4, 5}.

上述算法的时间复杂度为O(N^2)

程序如下:

#include <iostream>
#include <vector>
#include <algorithm>


using namespace std;

// print a vector
void prt(vector<int>& arr, string msg = "") {
	cout << msg << " ";
	for  (auto i: arr) {
		cout << i << " ";
	}
	cout << endl;
}

void calc_LIS(vector<int>& din, vector<int>& dout) {
	vector<int> M;  // M[j] store the index of the smallest tail of all LIS of length (j+1)
	M.push_back(0);  // M[0]
	vector<int> P(din.size());  // Predecessor of M[j]

	for (int i=0; i < din.size()- 1; i++) {  // Calculate M for {din[0]..din[i]}
		if (din[M.back()] < din[i+1]) {
			M.push_back(i+1);
			P[i+1] = M[M.size()-2];
			prt(M, "M: ");
			continue;
		}

		vector<int>::iterator pos = lower_bound(M.begin(), M.end(), din[i+1], [&](int m, int val){return (din[m] < val);});
		if ( din[*pos] > din[i+1] ) {
			*pos = i+1;
			if (pos != M.begin())
				P[i+1] = *(pos - 1);
		}

		prt(M, "M: ");
	}

	int v = M.back();
	dout.resize(M.size());
	for (int u = M.size(); u!=0; u--) {
		dout[u-1] = din[v];
		v = P[v];
	}
}

int main() {

	int a[] = {3, 2, 6, 4, 5, 1};
	vector<int> arr(a, a + sizeof(a)/sizeof(a[0]));
	vector<int> res;

	prt(arr, "Data In:");
	calc_LIS(arr, res);
	prt(res, "Data out:");

	return 0;
}

运行结果为:




 

内容概要:本文详细介绍了如何使用Matlab对地表水源热泵系统进行建模,并采用粒子群算法来优化每小时的制冷量和制热量。首先,文章解释了地表水源热泵的工作原理及其重要性,随后展示了如何设定基本参数并构建热泵机组的基础模型。接着,文章深入探讨了粒子群算法的具体实现步骤,包括参数设置、粒子初始化、适应度评估以及粒子位置和速度的更新规则。为了确保优化的有效性和实用性,文中还讨论了如何处理实际应用中的约束条件,如设备的最大能力和制冷/制热模式之间的互斥关系。此外,作者分享了一些实用技巧,例如引入混合优化方法以加快收敛速度,以及在目标函数中加入额外的惩罚项来减少不必要的模式切换。最终,通过对优化结果的可视化分析,验证了所提出的方法能够显著降低能耗并提高系统的运行效率。 适用人群:从事暖通空调系统设计、优化及相关领域的工程师和技术人员,尤其是那些希望深入了解地表水源热泵系统特性和优化方法的专业人士。 使用场景及目标:适用于需要对地表水源热泵系统进行精确建模和优化的情景,旨在找到既满足建筑负荷需求又能使机组运行在最高效率点的制冷/制热量组合。主要目标是在保证室内舒适度的前提下,最大限度地节约能源并延长设备使用寿命。 其他说明:文中提供的Matlab代码片段可以帮助读者更好地理解和复现整个建模和优化过程。同时,作者强调了在实际工程项目中灵活调整相关参数的重要性,以便获得更好的优化效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值