subsecuencia mas longest larga increasing comun algorithm computer-science dynamic-programming memoization lis

algorithm - mas - longest increasing subsequence



¿Cómo determinar la subsecuencia creciente más larga usando programación dinámica? (14)

Tengo un conjunto de enteros. Quiero encontrar la subsecuencia cada vez mayor más larga de ese conjunto utilizando la programación dinámica.


Aquí está la implementación de Java O (nlogn).

import java.util.Scanner; public class LongestIncreasingSeq { private static int binarySearch(int table[],int a,int len){ int end = len-1; int beg = 0; int mid = 0; int result = -1; while(beg <= end){ mid = (end + beg) / 2; if(table[mid] < a){ beg=mid+1; result = mid; }else if(table[mid] == a){ return len-1; }else{ end = mid-1; } } return result; } public static void main(String[] args) { // int[] t = {1, 2, 5,9,16}; // System.out.println(binarySearch(t , 9, 5)); Scanner in = new Scanner(System.in); int size = in.nextInt();//4; int A[] = new int[size]; int table[] = new int[A.length]; int k = 0; while(k<size){ A[k++] = in.nextInt(); if(k<size-1) in.nextLine(); } table[0] = A[0]; int len = 1; for (int i = 1; i < A.length; i++) { if(table[0] > A[i]){ table[0] = A[i]; }else if(table[len-1]<A[i]){ table[len++]=A[i]; }else{ table[binarySearch(table, A[i],len)+1] = A[i]; } } System.out.println(len); } }


Aquí hay otra O (n ^ 2) implementación de JAVA. Sin recursión / memorización para generar la subsecuencia real. Solo una matriz de cadenas que almacena el LIS real en cada etapa y una matriz para almacenar la longitud del LIS para cada elemento. Bastante fácil Echar un vistazo:

import java.io.BufferedReader; import java.io.InputStreamReader; /** * Created by Shreyans on 4/16/2015 */ class LNG_INC_SUB//Longest Increasing Subsequence { public static void main(String[] args) throws Exception { BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); System.out.println("Enter Numbers Separated by Spaces to find their LIS/n"); String[] s1=br.readLine().split(" "); int n=s1.length; int[] a=new int[n];//Array actual of Numbers String []ls=new String[n];// Array of Strings to maintain LIS for every element for(int i=0;i<n;i++) { a[i]=Integer.parseInt(s1[i]); } int[]dp=new int[n];//Storing length of max subseq. int max=dp[0]=1;//Defaults String seq=ls[0]=s1[0];//Defaults for(int i=1;i<n;i++) { dp[i]=1; String x=""; for(int j=i-1;j>=0;j--) { //First check if number at index j is less than num at i. // Second the length of that DP should be greater than dp[i] // -1 since dp of previous could also be one. So we compare the dp[i] as empty initially if(a[j]<a[i]&&dp[j]>dp[i]-1) { dp[i]=dp[j]+1;//Assigning temp length of LIS. There may come along a bigger LIS of a future a[j] x=ls[j];//Assigning temp LIS of a[j]. Will append a[i] later on } } x+=(" "+a[i]); ls[i]=x; if(dp[i]>max) { max=dp[i]; seq=ls[i]; } } System.out.println("Length of LIS is: " + max + "/nThe Sequence is: " + seq); } }

Código en acción: http://ideone.com/sBiOQx


Aquí hay tres pasos para evaluar el problema desde el punto de vista de la programación dinámica:

  1. Definición de recurrencia: maxLength (i) == 1 + maxLength (j) donde 0 <j <i y matriz [i]> array [j]
  2. Límite del parámetro de recurrencia: puede haber 0 a i - 1 subsecuencias pasadas como un parámetro
  3. Orden de evaluación: a medida que aumenta la subsecuencia, debe evaluarse de 0 a n

Si tomamos como ejemplo la secuencia {0, 8, 2, 3, 7, 9}, en el índice:

  • [0] obtendremos la subsecuencia {0} como caso base
  • [1] tenemos 1 nueva subsecuencia {0, 8}
  • [2] tratando de evaluar dos nuevas secuencias {0, 8, 2} y {0, 2} agregando el elemento en el índice 2 a las subsecuencias existentes - solo una es válida, por lo que agrega la tercera secuencia posible {0, 2} solamente a la lista de parámetros ...

Aquí está el código de trabajo de C ++ 11:

#include <iostream> #include <vector> int getLongestIncSub(const std::vector<int> &sequence, size_t index, std::vector<std::vector<int>> &sub) { if(index == 0) { sub.push_back(std::vector<int>{sequence[0]}); return 1; } size_t longestSubSeq = getLongestIncSub(sequence, index - 1, sub); std::vector<std::vector<int>> tmpSubSeq; for(std::vector<int> &subSeq : sub) { if(subSeq[subSeq.size() - 1] < sequence[index]) { std::vector<int> newSeq(subSeq); newSeq.push_back(sequence[index]); longestSubSeq = std::max(longestSubSeq, newSeq.size()); tmpSubSeq.push_back(newSeq); } } std::copy(tmpSubSeq.begin(), tmpSubSeq.end(), std::back_insert_iterator<std::vector<std::vector<int>>>(sub)); return longestSubSeq; } int getLongestIncSub(const std::vector<int> &sequence) { std::vector<std::vector<int>> sub; return getLongestIncSub(sequence, sequence.size() - 1, sub); } int main() { std::vector<int> seq{0, 8, 2, 3, 7, 9}; std::cout << getLongestIncSub(seq); return 0; }


Aquí hay una implementación de Scala del algoritmo O (n ^ 2):

object Solve { def longestIncrSubseq[T](xs: List[T])(implicit ord: Ordering[T]) = { xs.foldLeft(List[(Int, List[T])]()) { (sofar, x) => if (sofar.isEmpty) List((1, List(x))) else { val resIfEndsAtCurr = (sofar, xs).zipped map { (tp, y) => val len = tp._1 val seq = tp._2 if (ord.lteq(y, x)) { (len + 1, x :: seq) // reversely recorded to avoid O(n) } else { (1, List(x)) } } sofar :+ resIfEndsAtCurr.maxBy(_._1) } }.maxBy(_._1)._2.reverse } def main(args: Array[String]) = { println(longestIncrSubseq(List( 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15))) } }


Esta es una implementación de Java en O (n ^ 2). Simplemente no usé Binary Search para encontrar el elemento más pequeño en S, que es> = que X. Simplemente usé un bucle for. Usar Binary Search haría la complejidad en O (n logn)

public static void olis(int[] seq){ int[] memo = new int[seq.length]; memo[0] = seq[0]; int pos = 0; for (int i=1; i<seq.length; i++){ int x = seq[i]; if (memo[pos] < x){ pos++; memo[pos] = x; } else { for(int j=0; j<=pos; j++){ if (memo[j] >= x){ memo[j] = x; break; } } } //just to print every step System.out.println(Arrays.toString(memo)); } //the final array with the LIS System.out.println(Arrays.toString(memo)); System.out.println("The length of lis is " + (pos + 1)); }


Esto se puede resolver en O (n ^ 2) usando la Programación Dinámica. El código de Python para el mismo sería como:

def LIS(numlist): LS = [1] for i in range(1, len(numlist)): LS.append(1) for j in range(0, i): if numlist[i] > numlist[j] and LS[i]<=LS[j]: LS[i] = 1 + LS[j] print LS return max(LS) numlist = map(int, raw_input().split('' '')) print LIS(numlist)

Para entrada: 5 19 5 81 50 28 29 1 83 23

la salida sería: [1, 2, 1, 3, 3, 3, 4, 1, 5, 3] 5

El list_index de la lista de salida es el list_index de la lista de entrada. El valor en un list_index dado en la lista de salida denota la longitud de la subsecuencia creciente más larga para ese list_index.


Esto se puede resolver en O (n ^ 2) usando programación dinámica.

Procese los elementos de entrada en orden y mantenga una lista de tuplas para cada elemento. Cada tupla (A, B), para el elemento i denota, A = longitud de la subsecuencia con el mayor incremento que termina en i y B = índice del predecesor de la lista [i] en la subsecuencia con el mayor aumento que termina en la lista [i ].

Comenzando desde el elemento 1, la lista de la tupla para el elemento 1 será [(1,0)] para el elemento i, escaneará la lista 0..i y encontrará la lista de elementos [k] tal que la lista [k] <lista [i] , el valor de A para el elemento i, Ai será Ak + 1 y Bi será k. Si hay varios de estos elementos, agréguelos a la lista de tuplas para el elemento i.

Al final, encuentre todos los elementos con el valor máximo de A (longitud de LIS que termina en el elemento) y retroceda usando las tuplas para obtener la lista.

He compartido el código del mismo en http://www.edufyme.com/code/?id=66f041e16a60928b05a7e228a89c3799


Hablando sobre la solución de DP, me sorprendió que nadie mencionara el hecho de que LIS puede reducirse a LCS . Todo lo que necesita hacer es ordenar la copia de la secuencia original, eliminar todos los duplicados y hacer LCS de ellos. En pseudocódigo es:

def LIS(S): T = sort(S) T = removeDuplicates(T) return LCS(S, T)

Y la implementación completa escrita en Go. No necesita mantener toda la matriz n ^ 2 DP si no necesita reconstruir la solución.

func lcs(arr1 []int) int { arr2 := make([]int, len(arr1)) for i, v := range arr1 { arr2[i] = v } sort.Ints(arr1) arr3 := []int{} prev := arr1[0] - 1 for _, v := range arr1 { if v != prev { prev = v arr3 = append(arr3, v) } } n1, n2 := len(arr1), len(arr3) M := make([][]int, n2 + 1) e := make([]int, (n1 + 1) * (n2 + 1)) for i := range M { M[i] = e[i * (n1 + 1):(i + 1) * (n1 + 1)] } for i := 1; i <= n2; i++ { for j := 1; j <= n1; j++ { if arr2[j - 1] == arr3[i - 1] { M[i][j] = M[i - 1][j - 1] + 1 } else if M[i - 1][j] > M[i][j - 1] { M[i][j] = M[i - 1][j] } else { M[i][j] = M[i][j - 1] } } } return M[n2][n1] }


La explicación de Petar Minchev me ayudó a aclararme las cosas, pero para mí fue difícil analizar todo, así que hice una implementación de Python con nombres de variables excesivamente descriptivos y muchos comentarios. Hice una solución recursiva ingenua, la solución O (n ^ 2) y la solución O (n log n).

Espero que ayude a aclarar los algoritmos!

La solución recursiva

def recursive_solution(remaining_sequence, bigger_than=None): """Finds the longest increasing subsequence of remaining_sequence that is bigger than bigger_than and returns it. This solution is O(2^n).""" # Base case: nothing is remaining. if len(remaining_sequence) == 0: return remaining_sequence # Recursive case 1: exclude the current element and process the remaining. best_sequence = recursive_solution(remaining_sequence[1:], bigger_than) # Recursive case 2: include the current element if it''s big enough. first = remaining_sequence[0] if (first > bigger_than) or (bigger_than is None): sequence_with = [first] + recursive_solution(remaining_sequence[1:], first) # Choose whichever of case 1 and case 2 were longer. if len(sequence_with) >= len(best_sequence): best_sequence = sequence_with return best_sequence

La solución de programación dinámica O (n ^ 2)

def dynamic_programming_solution(sequence): """Finds the longest increasing subsequence in sequence using dynamic programming. This solution is O(n^2).""" longest_subsequence_ending_with = [] backreference_for_subsequence_ending_with = [] current_best_end = 0 for curr_elem in range(len(sequence)): # It''s always possible to have a subsequence of length 1. longest_subsequence_ending_with.append(1) # If a subsequence is length 1, it doesn''t have a backreference. backreference_for_subsequence_ending_with.append(None) for prev_elem in range(curr_elem): subsequence_length_through_prev = (longest_subsequence_ending_with[prev_elem] + 1) # If the prev_elem is smaller than the current elem (so it''s increasing) # And if the longest subsequence from prev_elem would yield a better # subsequence for curr_elem. if ((sequence[prev_elem] < sequence[curr_elem]) and (subsequence_length_through_prev > longest_subsequence_ending_with[curr_elem])): # Set the candidate best subsequence at curr_elem to go through prev. longest_subsequence_ending_with[curr_elem] = (subsequence_length_through_prev) backreference_for_subsequence_ending_with[curr_elem] = prev_elem # If the new end is the best, update the best. if (longest_subsequence_ending_with[curr_elem] > longest_subsequence_ending_with[current_best_end]): current_best_end = curr_elem # Output the overall best by following the backreferences. best_subsequence = [] current_backreference = current_best_end while current_backreference is not None: best_subsequence.append(sequence[current_backreference]) current_backreference = (backreference_for_subsequence_ending_with[current_backreference]) best_subsequence.reverse() return best_subsequence

La solución de programación dinámica O (n log n)

def find_smallest_elem_as_big_as(sequence, subsequence, elem): """Returns the index of the smallest element in subsequence as big as sequence[elem]. sequence[elem] must not be larger than every element in subsequence. The elements in subsequence are indices in sequence. Uses binary search.""" low = 0 high = len(subsequence) - 1 while high > low: mid = (high + low) / 2 # If the current element is not as big as elem, throw out the low half of # sequence. if sequence[subsequence[mid]] < sequence[elem]: low = mid + 1 # If the current element is as big as elem, throw out everything bigger, but # keep the current element. else: high = mid return high def optimized_dynamic_programming_solution(sequence): """Finds the longest increasing subsequence in sequence using dynamic programming and binary search (per http://en.wikipedia.org/wiki/Longest_increasing_subsequence). This solution is O(n log n).""" # Both of these lists hold the indices of elements in sequence and not the # elements themselves. # This list will always be sorted. smallest_end_to_subsequence_of_length = [] # This array goes along with sequence (not # smallest_end_to_subsequence_of_length). Following the corresponding element # in this array repeatedly will generate the desired subsequence. parent = [None for _ in sequence] for elem in range(len(sequence)): # We''re iterating through sequence in order, so if elem is bigger than the # end of longest current subsequence, we have a new longest increasing # subsequence. if (len(smallest_end_to_subsequence_of_length) == 0 or sequence[elem] > sequence[smallest_end_to_subsequence_of_length[-1]]): # If we are adding the first element, it has no parent. Otherwise, we # need to update the parent to be the previous biggest element. if len(smallest_end_to_subsequence_of_length) > 0: parent[elem] = smallest_end_to_subsequence_of_length[-1] smallest_end_to_subsequence_of_length.append(elem) else: # If we can''t make a longer subsequence, we might be able to make a # subsequence of equal size to one of our earlier subsequences with a # smaller ending number (which makes it easier to find a later number that # is increasing). # Thus, we look for the smallest element in # smallest_end_to_subsequence_of_length that is at least as big as elem # and replace it with elem. # This preserves correctness because if there is a subsequence of length n # that ends with a number smaller than elem, we could add elem on to the # end of that subsequence to get a subsequence of length n+1. location_to_replace = find_smallest_elem_as_big_as(sequence, smallest_end_to_subsequence_of_length, elem) smallest_end_to_subsequence_of_length[location_to_replace] = elem # If we''re replacing the first element, we don''t need to update its parent # because a subsequence of length 1 has no parent. Otherwise, its parent # is the subsequence one shorter, which we just added onto. if location_to_replace != 0: parent[elem] = (smallest_end_to_subsequence_of_length[location_to_replace - 1]) # Generate the longest increasing subsequence by backtracking through parent. curr_parent = smallest_end_to_subsequence_of_length[-1] longest_increasing_subsequence = [] while curr_parent is not None: longest_increasing_subsequence.append(sequence[curr_parent]) curr_parent = parent[curr_parent] longest_increasing_subsequence.reverse() return longest_increasing_subsequence


La siguiente implementación de C ++ también incluye algunos códigos que construyen la subsecuencia real más larga usando una matriz llamada prev .

std::vector<int> longest_increasing_subsequence (const std::vector<int>& s) { int best_end = 0; int sz = s.size(); if (!sz) return std::vector<int>(); std::vector<int> prev(sz,-1); std::vector<int> memo(sz, 0); int max_length = std::numeric_limits<int>::min(); memo[0] = 1; for ( auto i = 1; i < sz; ++i) { for ( auto j = 0; j < i; ++j) { if ( s[j] < s[i] && memo[i] < memo[j] + 1 ) { memo[i] = memo[j] + 1; prev[i] = j; } } if ( memo[i] > max_length ) { best_end = i; max_length = memo[i]; } } // Code that builds the longest increasing subsequence using "prev" std::vector<int> results; results.reserve(sz); std::stack<int> stk; int current = best_end; while (current != -1) { stk.push(s[current]); current = prev[current]; } while (!stk.empty()) { results.push_back(stk.top()); stk.pop(); } return results; }

Implementación sin pila solo revertir el vector

#include <iostream> #include <vector> #include <limits> std::vector<int> LIS( const std::vector<int> &v ) { auto sz = v.size(); if(!sz) return v; std::vector<int> memo(sz, 0); std::vector<int> prev(sz, -1); memo[0] = 1; int best_end = 0; int max_length = std::numeric_limits<int>::min(); for (auto i = 1; i < sz; ++i) { for ( auto j = 0; j < i ; ++j) { if (s[j] < s[i] && memo[i] < memo[j] + 1) { memo[i] = memo[j] + 1; prev[i] = j; } } if(memo[i] > max_length) { best_end = i; max_length = memo[i]; } } // create results std::vector<int> results; results.reserve(v.size()); auto current = best_end; while (current != -1) { results.push_back(s[current]); current = prev[current]; } std::reverse(results.begin(), results.end()); return results; }


O (n ^ 2) implementación de java:

void LIS(int arr[]){ int maxCount[]=new int[arr.length]; int link[]=new int[arr.length]; int maxI=0; link[0]=0; maxCount[0]=0; for (int i = 1; i < arr.length; i++) { for (int j = 0; j < i; j++) { if(arr[j]<arr[i] && ((maxCount[j]+1)>maxCount[i])){ maxCount[i]=maxCount[j]+1; link[i]=j; if(maxCount[i]>maxCount[maxI]){ maxI=i; } } } } for (int i = 0; i < link.length; i++) { System.out.println(arr[i]+" "+link[i]); } print(arr,maxI,link); } void print(int arr[],int index,int link[]){ if(link[index]==index){ System.out.println(arr[index]+" "); return; }else{ print(arr, link[index], link); System.out.println(arr[index]+" "); } }


verifique el código en java para la subsecuencia más larga y creciente con los elementos de la matriz

http://ideone.com/Nd2eba

/** ** Java Program to implement Longest Increasing Subsequence Algorithm **/ import java.util.Scanner; /** Class LongestIncreasingSubsequence **/ class LongestIncreasingSubsequence { /** function lis **/ public int[] lis(int[] X) { int n = X.length - 1; int[] M = new int[n + 1]; int[] P = new int[n + 1]; int L = 0; for (int i = 1; i < n + 1; i++) { int j = 0; /** Linear search applied here. Binary Search can be applied too. binary search for the largest positive j <= L such that X[M[j]] < X[i] (or set j = 0 if no such value exists) **/ for (int pos = L ; pos >= 1; pos--) { if (X[M[pos]] < X[i]) { j = pos; break; } } P[i] = M[j]; if (j == L || X[i] < X[M[j + 1]]) { M[j + 1] = i; L = Math.max(L,j + 1); } } /** backtrack **/ int[] result = new int[L]; int pos = M[L]; for (int i = L - 1; i >= 0; i--) { result[i] = X[pos]; pos = P[pos]; } return result; } /** Main Function **/ public static void main(String[] args) { Scanner scan = new Scanner(System.in); System.out.println("Longest Increasing Subsequence Algorithm Test/n"); System.out.println("Enter number of elements"); int n = scan.nextInt(); int[] arr = new int[n + 1]; System.out.println("/nEnter "+ n +" elements"); for (int i = 1; i <= n; i++) arr[i] = scan.nextInt(); LongestIncreasingSubsequence obj = new LongestIncreasingSubsequence(); int[] result = obj.lis(arr); /** print result **/ System.out.print("/nLongest Increasing Subsequence : "); for (int i = 0; i < result.length; i++) System.out.print(result[i] +" "); System.out.println(); } }


Bien, primero describiré la solución más simple que es O (N ^ 2), donde N es el tamaño de la colección. También existe una solución O (N log N), que describiré también. Búscalo here en la sección Algoritmos eficientes.

Asumiré que los índices de la matriz son de 0 a N - 1. Así que definamos DP[i] como la longitud de la LIS (la subsecuencia de aumento más larga) que termina en el elemento con el índice i . Para calcular DP[i] analizamos todos los índices j < i y verificamos si DP[j] + 1 > DP[i] y array[j] < array[i] (queremos que aumente). Si esto es cierto, podemos actualizar el óptimo actual para DP[i] . Para encontrar el óptimo global para la matriz, puede tomar el valor máximo de DP[0...N - 1] .

int maxLength = 1, bestEnd = 0; DP[0] = 1; prev[0] = -1; for (int i = 1; i < N; i++) { DP[i] = 1; prev[i] = -1; for (int j = i - 1; j >= 0; j--) if (DP[j] + 1 > DP[i] && array[j] < array[i]) { DP[i] = DP[j] + 1; prev[i] = j; } if (DP[i] > maxLength) { bestEnd = i; maxLength = DP[i]; } }

Utilizo la matriz prev para poder encontrar más tarde la secuencia real, no solo su longitud. Simplemente retroceda recursivamente desde bestEnd en un bucle usando prev[bestEnd] . El valor -1 es un signo para detener.

OK, ahora a la solución O(N log N) más eficiente:

Dejemos que S[pos] se defina como el entero más pequeño que termina una secuencia creciente de longitud pos . Ahora repita cada entero X del conjunto de entrada y haga lo siguiente:

  1. Si X > último elemento en S , entonces agrega X al final de S Esto significa esencialmente que hemos encontrado un nuevo LIS más grande.

  2. De lo contrario, encuentre el elemento más pequeño en S , que es >= que X , y cámbielo a X Debido a que S se ordena en cualquier momento, el elemento se puede encontrar utilizando la búsqueda binaria en el log(N) .

Tiempo de ejecución total: N enteros y una búsqueda binaria para cada uno de ellos: N * log (N) = O (N log N)

Ahora vamos a hacer un ejemplo real:

Colección de números enteros: 2 6 3 4 1 2 9 5 8

Pasos:

0. S = {} - Initialize S to the empty set 1. S = {2} - New largest LIS 2. S = {2, 6} - New largest LIS 3. S = {2, 3} - Changed 6 to 3 4. S = {2, 3, 4} - New largest LIS 5. S = {1, 3, 4} - Changed 2 to 1 6. S = {1, 2, 4} - Changed 3 to 2 7. S = {1, 2, 4, 9} - New largest LIS 8. S = {1, 2, 4, 5} - Changed 9 to 5 9. S = {1, 2, 4, 5, 8} - New largest LIS

Así que la longitud del LIS es 5 (el tamaño de S).

Para reconstruir el LIS real, volveremos a utilizar una matriz principal. Deje que parent[i] sea ​​el predecesor del elemento con el índice i en el LIS termina en el elemento con el índice i .

Para simplificar las cosas, podemos mantener en la matriz S , no los enteros reales, sino sus índices (posiciones) en el conjunto. No mantenemos {1, 2, 4, 5, 8} , pero mantenemos {4, 5, 3, 7, 8} .

Esa es la entrada [4] = 1 , entrada [5] = 2 , entrada [3] = 4 , entrada [7] = 5 , entrada [8] = 8 .

Si actualizamos correctamente la matriz principal, el LIS real es:

input[S[lastElementOfS]], input[parent[S[lastElementOfS]]], input[parent[parent[S[lastElementOfS]]]], ........................................

Ahora a lo importante: ¿cómo actualizamos la matriz principal? Hay dos opciones:

  1. Si X > último elemento en S , entonces parent[indexX] = indexLastElement . Esto significa que el padre del elemento más nuevo es el último elemento. Acabamos de añadir X al final de S

  2. De lo contrario, encuentre el índice del elemento más pequeño en S , que es >= que X , y cámbielo a X Aquí parent[indexX] = S[index - 1] .


def longestincrsub(arr1): n=len(arr1) l=[1]*n for i in range(0,n): for j in range(0,i) : if arr1[j]<arr1[i] and l[i]<l[j] + 1: l[i] =l[j] + 1 l.sort() return l[-1] arr1=[10,22,9,33,21,50,41,60] a=longestincrsub(arr1) print(a)

aunque hay una manera por la cual puede resolver esto en el tiempo O (nlogn) (esto se resuelve en el tiempo O (n ^ 2)), pero de esta manera se obtiene el enfoque de programación dinámica que también es bueno.