Obtenga de manera eficiente sumas clasificadas de una lista ordenada

votos
17

Tienes una lista ascendente de números, ¿cuál es el algoritmo más eficiente que puedes pensar para obtener la lista ascendente de las sumas de cada dos números en esa lista? Los duplicados en la lista resultante son irrelevantes, puede eliminarlos o evitarlos si lo desea.

Para ser claro, estoy interesado en el algoritmo. Siéntase libre de publicar el código en cualquier idioma y paradigma que desee.

Publicado el 03/08/2008 a las 22:08
fuente por usuario
En otros idiomas...                            


8 respuestas

votos
-4

Si está buscando una solución verdaderamente independiente del idioma, se sentirá muy decepcionado con mi opinión, ya que se encontrará con un bucle for y algunos condicionales. Sin embargo, si lo abres a idiomas funcionales o funciones de lenguaje funcional (te estoy viendo LINQ), mis colegas aquí pueden llenar esta página con ejemplos elegantes en Ruby, Lisp, Erlang y otros.

Respondida el 03/08/2008 a las 22:24
fuente por usuario

votos
1

Lo mejor que pude llegar es producir una matriz de sumas de cada par, y luego unir las filas, a-la merge sort. Siento que me falta algo de información simple que revelará una solución mucho más eficiente.

Mi algoritmo, en Haskell:

matrixOfSums list = [[a+b | b <- list, b >= a] | a <- list]

sortedSums = foldl merge [] matrixOfSums

--A normal merge, save that we remove duplicates
merge xs [] = xs
merge [] ys = ys
merge (x:xs) (y:ys) = case compare x y of
    LT -> x:(merge xs (y:ys))
    EQ -> x:(merge xs (dropWhile (==x) ys))
    GT -> y:(merge (x:xs) ys)

Encontré una mejora menor, una que es más susceptible a la codificación de flujo diferido. En lugar de fusionar las columnas en pares, combine todas a la vez. La ventaja es que comienzas a obtener elementos de la lista de inmediato.

-- wide-merge does a standard merge (ala merge-sort) across an arbitrary number of lists
-- wideNubMerge does this while eliminating duplicates
wideNubMerge :: Ord a => `a` -> [a]
wideNubMerge ls = wideNubMerge1 $ filter (/= []) ls
wideNubMerge1 [] = []
wideNubMerge1 ls = mini:(wideNubMerge rest)
    where mini = minimum $ map head ls
          rest = map (dropWhile (== mini)) ls

betterSortedSums = wideNubMerge matrixOfSums

Sin embargo, si sabe que va a usar todas las sumas, y no hay ninguna ventaja de obtener algunas antes, vaya con ' foldl merge []', ya que es más rápido.

Respondida el 03/08/2008 a las 22:36
fuente por usuario

votos
4

En lugar de codificar esto, creo que lo pseudocódigo en pasos y explicaré mi lógica, para que los mejores programadores puedan abrir agujeros en mi lógica si es necesario.

En el primer paso comenzamos con una lista de números de longitud n. Para cada número necesitamos crear una lista de longitud n-1 porque no estamos agregando un número a sí mismo. Al final tenemos una lista de n listas ordenadas que se generó en O (n ^ 2) tiempo.

step 1 (startinglist) 
for each number num1 in startinglist
   for each number num2 in startinglist
      add num1 plus num2 into templist
   add templist to sumlist
return sumlist 

En el paso 2 porque las listas fueron ordenadas por diseño (agregue un número a cada elemento en una lista ordenada y la lista todavía será ordenada) podemos simplemente hacer un mergesort fusionando cada lista en lugar de fusionar todo el lote. Al final, esto debería tomar O (n ^ 2) tiempo.

step 2 (sumlist) 
create an empty list mergedlist
for each list templist in sumlist
   set mergelist equal to: merge(mergedlist,templist)
return mergedlist

El método de fusión sería el paso de fusión normal con una verificación para asegurarse de que no hay sumas duplicadas. No escribiré esto porque cualquiera puede buscar mergesort.

Así que ahí está mi solución. El algoritmo completo es O (n ^ 2) tiempo. Siéntase libre de señalar cualquier error o mejora.

Respondida el 04/08/2008 a las 00:06
fuente por usuario

votos
1

En SQL:

create table numbers(n int not null)
insert into numbers(n) values(1),(1), (2), (2), (3), (4)


select distinct num1.n+num2.n sum2n
from numbers num1
inner join numbers num2 
    on num1.n<>num2.n
order by sum2n

C # LINQ:

List<int> num = new List<int>{ 1, 1, 2, 2, 3, 4};
var uNum = num.Distinct().ToList();
var sums=(from num1 in uNum
        from num2 in uNum 
        where num1!=num2
        select num1+num2).Distinct();
foreach (var s in sums)
{
    Console.WriteLine(s);
}
Respondida el 09/08/2008 a las 00:05
fuente por usuario

votos
2

Puedes hacer esto en dos líneas en python con

allSums = set(a+b for a in X for b in X)
allSums = sorted(allSums)

El costo de esto es n ^ 2 (¿tal vez un factor de registro adicional para el conjunto?) Para la iteración y s * log (s) para la clasificación donde s es el tamaño del conjunto.

El tamaño del conjunto podría ser tan grande como n * (n-1) / 2, por ejemplo, si X = [1,2,4, ..., 2 ^ n]. Por lo tanto, si desea generar esta lista, se necesitará al menos n ^ 2/2 en el peor de los casos, ya que este es el tamaño de la salida.

Sin embargo, si desea seleccionar los primeros k elementos del resultado, puede hacer esto en O (kn) usando un algoritmo de selección para matrices X + Y ordenadas por Frederickson y Johnson ( consulte aquí para obtener detalles sangrientos) . Aunque esto probablemente se pueda modificar para generarlos en línea al reutilizar el cálculo y obtener un generador eficiente para este conjunto.

@deuseldorf, Peter Existe cierta confusión sobre (n!) Tengo serias dudas de que deuseldorf significara "n factorial" sino simplemente "n, (muy emocionado)!"

Respondida el 11/08/2008 a las 15:47
fuente por usuario

votos
1

Esta pregunta ha estado atormentando mi cerebro por alrededor de un día. Increíble.

De todos modos, no puedes alejarte fácilmente de la naturaleza n ^ 2, pero puedes mejorar un poco con la combinación ya que puedes unir el rango para insertar cada elemento.

Si miras todas las listas que generas, tienen la siguiente forma:

(a[i], a[j]) | j>=i

Si lo volteas 90 grados, obtienes:

(a[i], a[j]) | i<=j

Ahora, el proceso de fusión debe tomar dos listas iy i+1(que corresponden a listas donde el primer miembro es siempre a[i]y a[i+1]), puede enlazar el rango para insertar elementos (a[i + 1], a[j])en la lista ipor la ubicación (a[i], a[j])y la ubicación de (a[i + 1], a[j + 1]).

Esto significa que debe fusionarse en reversa en términos de j. Todavía no sé (todavía) si puede aprovechar esto j, pero parece posible.

Respondida el 21/08/2008 a las 19:16
fuente por usuario

votos
12

Editar a partir de 2018: Probablemente deberías dejar de leer esto. (Pero no puedo eliminarlo ya que es aceptado).

Si escribe las sumas de esta manera:

1 4  5  6  8  9
---------------
2 5  6  7  9 10
  8  9 10 12 13
    10 11 13 14
       12 14 15
          16 17
             18

Notarás que desde M [i, j] <= M [i, j + 1] y M [i, j] <= M [i + 1, j], entonces solo necesitas examinar la parte superior izquierda " esquinas "y elige la más baja.

p.ej

  • solo 1 esquina superior izquierda, selección 2
  • solo 1, elige 5
  • 6 u 8, elige 6
  • 7 u 8, elige 7
  • 9 u 8, elige 8
  • 9 o 9, elige ambos :)
  • 10 o 10 o 10, elige todo
  • 12 u 11, elige 11
  • 12 o 12, elige ambos
  • 13 o 13, elige ambos
  • 14 o 14, elige ambos
  • 15 o 16, elige 15
  • solo 1, elige 16
  • solo 1, elige 17
  • solo 1, elige 18

Por supuesto, cuando tienes muchas esquinas superiores izquierdas, esta solución evoluciona.

Estoy bastante seguro de que este problema es Ω (n²), porque tienes que calcular las sumas para cada M [i, j], a menos que alguien tenga un algoritmo mejor para la suma :)

Respondida el 18/09/2008 a las 22:41
fuente por usuario

votos
1

No importa lo que hagas, sin restricciones adicionales en los valores de entrada, no puedes hacer mejor que O (n ^ 2), simplemente porque tienes que recorrer todos los pares de números. La iteración dominará la clasificación (que puede hacer en O (n log n) o más rápido).

Respondida el 18/09/2008 a las 23:15
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more