Editing Longest increasing subsequence

Jump to: navigation, search

Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.

The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then save the changes below to finish undoing the edit.
Latest revision Your text
Line 1: Line 1:
The '''longest increasing subsequence''' (LIS) problem is to find an [[subsequence#Definitions|increasing subsequence]] (either strictly or non-strictly) of maximum length, given a (finite) input sequence whose elements are taken from a partially ordered set. For example, consider the sequence [9,2,6,3,1,5,0,7]. An increasing subsequence is [2,3,5,7], and, in fact, there is no longer increasing subsequence. Therefore [2,3,5,7] is a longest increasing subsequence of [9,2,6,3,1,5,0,7]. The ''longest decreasing subsequence'' can be defined analogously; it is clear that a solution to one gives a solution to the other.
+
The '''longest increasing subsequence''' (LIS) problem is to find an [[subsequence#Definitions|increasing subsequence]] (either strictly or non-strictly) of maximum length, given an input sequence whose elements belong are taken from a partially ordered set. For example, consider the sequence [9,2,6,3,1,5,0,7]. An increasing subsequence is [2,3,5,7], and, in fact, there is no longer increasing subsequence. Therefore [2,3,5,7] is a longest increasing subsequence of [9,2,6,3,1,5,0,7]. The ''longest decreasing subsequence'' can be defined analogously; it is clear that a solution to one gives a solution to the other.
 
+
We will focus on the non-strict case (with some parenthetical comments about the strict case).
+
  
 
==Discussion==
 
==Discussion==
Line 17: Line 15:
 
For example, consider the sequence [9,2,6,3,1,5,0]. Its nonempty prefixes are [9], [9,2], [9,2,6], [9,2,6,3], [9,2,6,3,1], [9,2,6,3,1,5], and [9,2,6,3,1,5,0]. For each of these, we may find an increasing subsequence that uses the last element of maximal length, for example, [9], [2], [2,6], [2,3], [1], [2,3,5], and [0], respectively. The longest of these, [2,3,5], is then also an (unrestricted) LIS of the original sequence, [9,2,6,3,1,5,0,7].
 
For example, consider the sequence [9,2,6,3,1,5,0]. Its nonempty prefixes are [9], [9,2], [9,2,6], [9,2,6,3], [9,2,6,3,1], [9,2,6,3,1,5], and [9,2,6,3,1,5,0]. For each of these, we may find an increasing subsequence that uses the last element of maximal length, for example, [9], [2], [2,6], [2,3], [1], [2,3,5], and [0], respectively. The longest of these, [2,3,5], is then also an (unrestricted) LIS of the original sequence, [9,2,6,3,1,5,0,7].
  
Denote the LIS of <math>x</math> by <math>\operatorname{LIS}(x)</math>, and denote the LIS subject to the restriction that the last element must be used as <math>\operatorname{LIS}'(i)</math>. Then we see that <math>|\operatorname{LIS}(x)| = \max_{1 \leq i \leq n} |\operatorname{LIS}'(i)|</math>. We will now focus on calculating the <math>\operatorname{LIS}'</math> values, of which there are <math>n</math> (one for each nonempty prefix of <math>x</math>).
+
Denote the LIS of <math>x</math> by <math>\operatorname{LIS}(x)</math>, and denote the LIS subject to the restriction that the last element must be used as <math>\operatorname{LIS}'(x[i])</math>. Then we see that <math>|\operatorname{LIS}(x)| = \max_{1 \leq i \leq n} |\operatorname{LIS}'(x[i])|</math>. We will now focus on calculating the <math>\operatorname{LIS}'</math> values, of which there are <math>n</math> (one for each nonempty prefix of <math>x</math>).
 
+
===Optimal substructure===
+
The value for <math>\operatorname{LIS}'(i)</math> consists of either:
+
* the element <math>x_i</math> alone, which always forms an increasing subsequence by itself, or
+
* the element <math>x_i</math> tacked on to the end of an increasing subsequence ending with <math>x_j</math>, where <math>j < i</math> and <math>x_j \leq x_i</math> (for the non-strict case) or <math>x_j < x_i</math> (for the strict case).
+
 
+
For example, the longest increasing subsequence that ends on the last element of <math>[9,2,6,3,1]</math> is just the element <math>1</math> itself, the element at the very end. But if we consider <math>[9,2,6,3,1,5]</math>, which has as a longest increasing subsequence ending at its last element <math>[2,3,5]</math>, we see that <math>[2,3]</math> is an increasing subsequence of <math>[9,2,6,3]</math> ending at ''its'' last element and that <math>3 < 5</math> as required.
+
 
+
Furthermore, it is not hard to see that the increasing subsequence we are left with after removing the last element, in the second case, must itself be optimal for the element it ends on; that is, must be a possible value of <math>\operatorname{LIS}'(j)</math>. If this were not the case, then we would have a longer increasing subsequence ending on <math>x_j</math>, and then we could tack the element <math>x_i</math> on the end to obtain an increasing subsequence ending at <math>x_i</math> that is longer than the one we originally supposed was longest --- a contradiction.
+
 
+
Also, if we already know <math>\operatorname{LIS}'(j)</math> for all <math>j < i</math>, then one of them, when the element <math>x_i</math> is appended to the end, must give a LIS that ends at <math>x_i</math>, unless <math>\operatorname{LIS}'(x[i])</math> consists of <math>x_i</math> only. This is because if this were not the case, then whenever we were allowed to append <math>x_i</math> we would obtain a suboptimal solution --- but then removing the last element from <math>\operatorname{LIS}'(i)</math> would give a suboptimal solution to a subinstance, which we know to be impossible.
+
 
+
===Overlapping subproblems===
+
When computing <math>\operatorname{LIS}'(x[i])</math>, we might need to know the values of <math>\operatorname{LIS}'(x[j])</math> for all <math>j < i</math>. These are the shared subinstances; there are only <math>n</math> possible values in the <math>\operatorname{LIS}'</math> table in total.
+
 
+
===Implementation===
+
The optimal substructure discussed gives a very simple formula:
+
:<math>\operatorname{LIS}'(i) = (\text{the longest out of } (\{\operatorname{LIS}'(j) \mid 1 \leq j < i \wedge x_j \leq x_i\} \cup \{[\,]\}))x_i</math>
+
(In the strict case, we have <math>x_j < x_i</math> strictly.)
+
 
+
We can easily compute this bottom-up, since we only need to know values of smaller subinstances.
+
 
+
The LIS of the entire sequence <math>x</math> is then just the largest of all <math>\operatorname{LIS}'</math> values.
+
 
+
If only the length is desired, the above formula becomes
+
:<math>\operatorname{LIS\_len}'(i) = 1 + \max (\{\operatorname{LIS\_len}'(j) \mid 1 \leq j < i \wedge x_j \leq x_i\} \cup \{0\})</math>
+
 
+
====Pseudocode====
+
(This is for the non-strict case.)
+
<pre>
+
input x
+
n &larr; length of x
+
for each i &isin; [1..n]
+
    lis[i] &larr; 1
+
    for each j &isin; [1..(i-1)]
+
          if x[i] &ge; x[j]
+
          lis[i] &larr; max(lis[i],1+lis[j])
+
return max element of lis
+
</pre>
+
 
+
===Analysis===
+
====Time====
+
With the length-only formula above, computing the <math>i^{\text{th}}</math> entry takes <math>O(i)</math> time to compute, giving <math>O(1) + O(2) + ... + O(n) = O(n^2)</math> time overall.
+
 
+
This bound still holds when computing the LIS itself, but since we probably wish to avoid needless copying and concatenation of sequences, a better way is to, instead of storing the <math>\operatorname{LIS}'</math> values themselves in the table, simply storing their lengths as well as making a note of the next-to-last element of the sequence (or storing a zero if there is none) so that we can backtrack to reconstruct the original sequence.
+
 
+
====Memory====
+
<math>\Theta(n)</math> memory is used.
+
 
+
==A faster algorithm==
+
If the set from which the elements of <math>x</math> is taken is totally ordered, then we can do even better than this. As a matter of fact, this is usually the case, as the elements will often be integers or real numbers. This algorithm runs in <math>O(n \log n)</math> time, which is asymptotically optimal.<ref>Fredman, Michael L. (1975), "On computing the length of longest increasing subsequences", ''Discrete Mathematics'' '''11''' (1): 29–35, [http://dx.doi.org/10.1016%2F0012-365X%2875%2990103-X doi:10.1016/0012-365X(75)90103-X]</ref>
+
 
+
The idea behind the algorithm is that if an increasing subsequence is long, then it is useful because it might give optimal subsequences that end on later elements, and if an increasing subsequence ends on a small value, then it is useful because it is versatile concerning what may be appended to the end, but if it is short and it ends on a large value, then it is not very useful. In the <math>O(n^2)</math> algorithm already described, a lot of time is spent looking at possibly useless increasing subsequences; for example, when computing <math>\operatorname{LIS}'(i)</math>, we examine all values of <math>\operatorname{LIS}'(j)</math>, where <math>j < i</math>, even though some <math>x_j</math>'s may be larger than <math>x_i</math>.
+
 
+
Thus we will maintain an auxiliary array <math>a</math> indexed by LIS length. The entry <math>a[i]</math> will, at any given time, hold the least possible value for the last element of an increasing subsequence of <math>x</math> of length <math>i</math> composed of elements we have so far examined. Initially all entries of <math>a</math> will be set to <math>+\infty</math>. At the conclusion of the algorithm, the highest index at which a finite value is found is the length of the LIS.
+
 
+
The first thing to note is that the entries of <math>a</math> are (non-strictly) increasing. For example, consider the sequence <math>[9,2,6,3,1,5,0,7]</math>. At the conclusion of the algorithm, the array <math>a</math> will contain the values <math>[0,3,5,7,+\infty,+\infty,+\infty,+\infty]</math>. This tells us that the last element of any increasing subsequence of length 1 will be at least 0, the last element of any increasing subsequence of length 2 will be at least 3, and so on; the infinite values indicate that subsequences of those lengths do not exist. We know that <math>a</math> is increasing because it would be absurd if it were not. For example, suppose that <math>a_3 < a_2</math>, so that the least last element attainable for an increasing sequence of length 3 were less than the least last element attainable for an increasing sequence of length 2. Then we could remove the last element from the sequence of length 3 to obtain an even smaller element (possibly) at the end of an increasing sequence of length 2, which contradicts the optimality of the value we already have on file.
+
 
+
Now let's see how the array <math>a</math> allows us to find the length of the LIS. We consider the elements of <math>x</math> one at a time starting from <math>x_1</math>. For each element <math>x_i</math> we consider, we notice that we want to find the ''longest increasing subsequence so far discovered that ends on a value less than or equal to <math>x_i</math>''. To do this, we perform a [[binary search]] on the array <math>a</math> for the largest index <math>j</math> for which <math>a_j \leq x_i</math>, or return <math>j=0</math> if no such index exists (empty sequence). Then, we know we can obtain an increasing subsequence of length <math>j+1</math> by appending <math>x_i</math> to the end of the increasing subsequence of length <math>j</math> ending at on value <math>a_j</math>; if there is no such value then we get an increasing subsequence of length 1 by taking <math>x_i</math> by itself. What effect does this have on <math>a</math>? We know that <math>x_i < a_{j+1}</math>, and we also know that we have obtained an increasing subsequence of length <math>j+1</math> whose last element is <math>x_i</math>, which is therefore ''better'' than what we have on file for <math>a_{j+1}</math>. Therefore, we update <math>a_{j+1}</math> so that it equals <math>x_i</math>. Note that after this operation, ''<math>a</math> will still be sorted'', because <math>x_i</math> was originally less than <math>a_{j+2}</math>, so <math>a_{j+1}</math> will be less after the update, and <math>a_{j+1}</math> will still be greater than or equal to <math>a_j</math>, because <math>x_i \geq a_j</math>. After iterating through all elements of <math>x</math> and updating <math>a</math> at each step, the algorithm terminates.
+
 
+
===Pseudocode===
+
(Non-strict case.)
+
<pre>
+
input x
+
n &larr; length of x
+
result &larr; 0
+
a[0] &larr; -&#8734;
+
for each i &isin; [1..n]
+
    a[i] &larr; +&#8734;
+
for each i &isin; [1..n]
+
    l &larr; 0
+
    u &larr; n
+
    while u &gt; l
+
          if a[&lfloor;(l+u)/2&rfloor;] &le; x[i]
+
              l &larr; 1 + &lfloor;(l+u)/2&rfloor;
+
          else
+
              u &larr; &lfloor;(l+u)/2&rfloor;
+
    a[l] &larr; x[i]
+
    result &larr; max(result,l)
+
return result
+
</pre>
+
 
+
===Analysis===
+
The reason why we expect this algorithm to be more efficient is that, by examining only <math>a</math> instead of the preceding <math>x</math>-values, all the irrelevant increasing subsequences (the ones that are both short and high at the end) are ignored, as they cannot "win" on either front and hence secure a position in <math>a</math>.
+
 
+
====Time====
+
The time taken for a binary search in the auxiliary array, of size <math>n</math>, is <math>O(\log n)</math>, and one is executed as each element of <math>x</math> is examined. Therefore this algorithm achieves the stated time bound of <math>O(n \log n)</math>.
+
  
====Memory====
+
==Optimal substructure==
Still <math>O(n)</math>, as our auxiliary array has size <math>n</math>.
+
  
==References==
 
<references/>
 
  
 
[[Category:Dynamic programming]]
 
[[Category:Dynamic programming]]

Please note that all contributions to PEGWiki are considered to be released under the Attribution 3.0 Unported (see PEGWiki:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!

Cancel | Editing help (opens in new window)