说明:仅仅分享思想和代码设计方法,代码是用于验证和对照(对照是为了方便你检查你写的哪里有问题),我强烈建议你自己动手实现,我明确反对你直接用我的代码去写报告或其它目的。
补充说明:基于了电路布线的思想,可参考电路布线。
- 最长升序子序列问题
1.1原问题的解:对于一个N个数的升序子序列,假设它为集合B,那B里的元素应该满足是升序的且属于N,且不存在长度比B更长的序列。
1.2 算法:因为要求最长升序子序列,既要找一个子序列及其在序列中的位置,采用动态规划的算法。空间换取时间,但不能明换。必须先对序列进行一个排序找出其相对大小。目的是减少空间的消耗。
假定用size(i,j)进行动态规划。i代表当前出发位置,也表示当前已经最大升序子序列已经考虑到的位置。j代表考虑序列规模。它的规模肯是小于原序列的。也就是size(i,j)只看序列前j个位置,然后从第0个位置到第i个位置所加入的最大升序子序列的边数。
我们的目标就是判断第i个位置的元素能否加入子序列,因为自底向下,当我们考虑到size(n,n)时规模最大,所有位置也都判断了,再回溯原问题的解就出来了。
将序列分为如图两部分,刚开始从位置0出发,根据0在原序列中的排名,因为是升序的,找寻数组,所以找如果它的排名比位置0大,就对相应位置+1。表示只考虑到第0个位置时,问题规模j所能找到的最大子序列长度。接着考虑第一个元素,判断第一个元素相对大小,对于第一个元素前面的元素,size(i,j)=size(i-1,j),因为i对应的排名位置在这后面,所以考虑这个元素时,和考虑上一个元素时,结果是一样的。对于第1个元素排名后面的元素,因为规模已经大到,可以包含这个元素了,这时候要考虑一个问题,这个元素到底能否加入已找到最大升序子序列中,因为规模已经可以包括它了,所以这时候就要假设它加入和它不加入。
如果它加入则后面,则对于当前规模j来说,你需要考虑的时如果它前面有元素,且排名在它后面,且已经在最大升序子序列里的情况。它跟当前元素不兼容,怎么办。很简单,如果冲突的只有一个元素,表示冲突那个元素可以和当前元素二择一,因为最大升序
子序列解不止一个。如果前面有多个元素已经加入当前规模的升序子序列,那么这个元素铁定是不能加入的,因为如果加入,前面的就不能加入了,最大升序子序列就变短了。
最后将名次排名映射回原序列得到结果。
回溯:由size矩阵开始回溯。回溯的过程就是从原序列自右向左找最大升序子序列的过程。先找到最右边第一条加入的数。判断的方法是size(i-1,j),size(i,j)不一样,这说明在同等问题规模下,i这条边是构成最大升序子序列的一个可行解,接着由于你找到了i这条边,你就必须找i这条边名次左边的位置,因为右边的位置那个数,必然就和这个数冲突了。冲突是有可能加入最大升序子序列的,因为可能当前冲突序列是1,即二择一的情况。但比较难找,所以采用优先选择最右边的数的方法。
1.3分析
解决这个问题有一些贪心算法,或者回溯法的解法。但从动态规划这个角度来说,
这个算法从本质上来说和课本的电路布线没有区别,都是用动态规划的方法求一个子集。然后这个子集是升序的问题。现在以升序数组的重新描述了这个算法。复杂度主要体现在两部分。一部分是对原序列排序找相对大小,这个方法的复杂度和简单排序是一样的复杂度渐进确界O(N^2)。然后是找最长升序子序列的问题。这从数列第一个开始找起,每次都与更新size的一行。每次更行一行用时位O(N),总复杂度位O(N^2)。回溯:操作size矩阵复杂度位O(N^2),映射复杂度为O(N),故算法的总时间复杂度为O(N^2).
1.4实现(采用一个规模为20的数列验证)
void msn()
{
cout << "所测试数据为规模大小为20(为方便观察,还是认为限制随机数为1到1000)" << endl;
std::srand(time(NULL));
clock_t star, end;
int length, *c, **size, max, dida,*newc,rank;
stack<int> result,resultpos;
length = 20;
c = new int[length + 1];
newc = new int[length + 1];
size = new int*[length + 1];
for (int i = 1; i <= length; ++i)
size[i] = new int[length + 1];
cout << "原序列为:" << endl;
for (int i = 1; i < length + 1; ++i)
{
c[i] = rand()%1000+1 ;
cout << c[i] << " ";
}
cout << endl;
for (int i = 1; i < length + 1; ++i)
{
rank = 1;
for (int j = 1; j < length + 1; ++j)
{
if (c[j] < c[i])
++rank;
}
for (int j = i+1; j < i; ++j)
{
if (c[j] < c[i])
++rank;
}
newc[i] = rank;
}
cout << "排名后序列为:" << endl;
for (int i = 1; i < length + 1; ++i)
{
cout << newc[i] << " ";
}
for (int i = 1; i < newc[1]; ++i)
size[1][i] = 0;
for (int i = newc[1]; i <= length; ++i)
size[1][i] = 1;
for (int i = 2; i <= length; ++i)
{
for (int j = 1; j < newc[i]; ++j)
{
size[i][j] = size[i - 1][j];
}
for (int j = newc[i]; j <= length; ++j)
{
size[i][j] = size[i - 1][j] < (size[i - 1][newc[i] - 1] + 1) ? (size[i - 1][newc[i] - 1] + 1) : size[i - 1][j];
}
}
cout << endl;
cout << "size矩阵值为:" << endl;
for (int i = 1; i <= length; ++i)
{
for (int j = 1; j <= length; ++j)
cout << size[i][j] << ' ';
cout << endl;
}
max = length;
for (int i = length; i > 1; --i)
{
if (size[i][max] != size[i - 1][max])
{
max = newc[i] - 1;
result.push(c[i]);
resultpos.push(i);
}
//newcout << max << endl;
}
if (max >= newc[1])
{
result.push(c[1]);
resultpos.push(1);
}
cout << "最长升序子序列为:";
while (!result.empty())
{
cout << result.top() << ' ';
result.pop();
}
cout << "对应下标为:";
while (!resultpos.empty())
{
cout << resultpos.top() << ' ';
resultpos.pop();
}
cout << endl;
delete[]c;
for (int i = 1; i <= length; ++i)
delete[]size[i];
delete[]size;
}
int egg(int n, int k)
{
if (n == 1)
return k;
if (k == 0)
return 0;
return egg(n - 1, k - 1) + egg(n, k - 1) + 1;
}
int eggs(int n, int k)
{
if (n == 1&&k!=0)
{
if (find(sss.begin(), sss.end(), k) == sss.end())
{
sss.push_back(k);
a += k + 1;
if (a < 50)
cout << a << ' ';
}
return k;
}
if (k == 0)
return 0;
return eggs(n - 1, k - 1) + eggs(n, k - 1) + 1;
}
int main()
{
msn();
}