Buscar este blog

miércoles, 13 de julio de 2011

Complejidad Computacional

Clases de Complejidad

Los problemas de decisión se clasifican en conjuntos de complejidad comparable llamados clases de complejidad.

La clase de complejidad P es el conjunto de los problemas de decisión que pueden ser resueltos en una máquina determinista en tiempo polinómico, lo que corresponde intuitivamente a problemas que pueden ser resueltos aún en el peor de sus casos.

La clase de complejidad NP es el conjunto de los problemas de decisión que pueden ser resueltos por una máquina no determinista en tiempo polinómico. Esta clase contiene muchos problemas que se desean resolver en la práctica, incluyendo el problema de satisfactibilidad booleana y el problema del viajante, un camino Hamiltoniano para recorrer todos los vértices una sola vez. Todos los problemas de esta clase tienen la propiedad de que su solución puede ser verificada efectivamente.


La pregunta P=NP

Preguntas como esta motivan la introducción de los conceptos de hard (difícil) y completo. Un conjunto X de problemas es hard con respecto a un conjunto de problemas Y ( 'Y' pertenecientes a NP) si X>Y o X=Y, es decir Y se puede escribir como un conjunto de soluciones de los problemas X. En palabras simples, Y es "más sencillo" que X. El término sencillo se define precisamente en cada caso. El conjunto hard más importante es NP-hard. El conjunto X es completo para Y si es hard para Y y es también un subconjunto de Y (caso X=Y). El conjunto completo más importante es NP-completo. En otras palabras, los problemas del conjunto NP-completo tienen la característica de que, si se llega a encontrar una solución en tiempo P para algún miembro del conjunto (cualquiera de los problemas de NP-completo), entonces de hecho existe una solución en tiempo P para todos los problemas de NP-completo.


Intratabilidad
Los problemas que pueden ser resueltos en teoría, pero no en práctica, se llaman intratables. Qué se puede y qué no en la práctica es un tema debatible, pero en general sólo los problemas que tienen soluciones de tiempos polinomiales son solubles para más que unos cuantos valores. Entre los problemas intratables se incluyen los de EXPTIME-completo. Si NP no es igual a P, entonces todos los problemas de NP-completo son también intratables.

Para ver por qué las soluciones de tiempo exponencial no son útiles en la práctica, se puede considerar un problema que requiera 2n operaciones para su resolución (n es el tamaño de la fuente de información). Para una fuente de información relativamente pequeña, n=100, y asumiendo que una computadora puede llevar a cabo 1010 (10 giga) operaciones por segundo, una solución llevaría cerca de 4*1012 años para completarse, mucho más tiempo que la actual edad del universo.





decsai.ugr.es/~smc/docencia/mittr2.pdf
http://es.wikipedia.org/wiki/Clases_de_complejidad_P_y_NP
www.dm.uba.ar/materias/optimizacion/2006/1/fbonomo_clase_npc.pdf
http://es.wikipedia.org/wiki/Complejidad_computacional


lunes, 11 de julio de 2011

Algoritmo de Floyd-Warshall

En informática, el algoritmo de Floyd-Warshall, descrito en 1959 por Bernard Roy, es un algoritmo de análisis sobre grafos para encontrar el camino mínimo en grafos dirigidos ponderados. El algoritmo encuentra el camino entre todos los pares de vértices en una única ejecución. El algoritmo de Floyd-Warshall es un ejemplo de programación dinámica.

El algoritmo de Floyd-Warshall compara todos los posibles caminos a través del grafo entre cada par de vértices. El algoritmo es capaz de hacer esto con sólo V3 comparaciones (esto es notable considerando que puede haber hasta V2 aristas en el grafo, y que cada combinación de aristas se prueba). Lo hace mejorando paulatinamente una estimación del camino más corto entre dos vértices, hasta que se sabe que la estimación es óptima.

La siguiente es una implementación en código C del algoritmo de Floyd-Warshall para la solución de todos los pares de la menor problema del camino.
Código en C++

//resuelve todos los pares de la menor problema del camino utilizando el algoritmo de Floyd-Warshall

void fwarsh(int nn, double *cmat, double **dist_mat, int **pred_mat)
{
  double *dist;
  int *pred;
  int i,j,k; / / bucle de contadores

   / / inicializar las estructuras de datos
  dist = (double *)malloc(sizeof(double) * nn * nn);
  pred = (int *)malloc(sizeof(int) * nn * nn);
  memset(dist, 0, sizeof(double)*nn*nn);
  memset(pred, 0, sizeof(int)*nn*nn);

 / / algoritmo de inicialización
  for (i=0; i < nn; i++) {
    for (j=0; j < nn; j++) {
      if (cmat[i*nn+j] != 0.0)
 dist[i*nn+j] = cmat[i*nn + j];
      else
 dist[i*nn+j] = HUGE_VAL; / / desconectado
      if (i==j)  / / caso diagonal
 dist[i*nn+j] = 0;

      if ((dist[i*nn + j] > 0.0) && (dist[i*nn+j] < HUGE_VAL))
 pred[i*nn+j] = i;
    }
  }
 
  / / Bucle principal del algoritmo
  for (k=0; k < nn; k++) {
    for (i=0; i < nn; i++) {
      for (j=0; j < nn; j++) {
 if (dist[i*nn+j] > (dist[i*nn+k] + dist[k*nn+j])) {
   dist[i*nn+j] = dist[i*nn+k] + dist[k*nn+j];
   pred[i*nn+j] = k; 
   //printf("actualiza la entrada %d,%d with %d\n", i,j, dist[i*nn+j]);
 }
      }
    }
  }

   / * / / Imprime la tabla de resultados de las distancias más cortas
  for (i=0; i < nn; i++) {
    for (j=0; j < nn; j++)
      printf("%g ", dist[i*nn+j]);
    printf("\n");
    } */
  
   / / ahora el conjunto de matrices y dist pred para la función de llamada
   / / pero algunos controles porque permitimos que se pasa NULL si el usuario
   / / no se preocupa por uno de los resultados.
  if (dist_mat)
    *dist_mat = dist;
  else
    free(dist);

  if (pred_mat)
    *pred_mat = pred;
  else
    free(pred);

  return;
}  //Fin de fwarsh()

// Declaraciones en el archivo .h
int cn; //cantidad de nodos
vector< vector<int> > ady; //matriz de adyacencia
 
// Devuelve una matriz con las distancias minimas de cada nodo al resto de los vertices.
vector< vector<int> > Grafo :: floyd(){
    vector< vector<int> > path = this->ady;
 
    for(int i = 0; i < cn; i++)
        path[i][i] = 0;
 
    for(int k = 0; k < cn; k++)
        for(int i = 0; i < cn; i++)
            for(int j = 0; j < cn; j++){
                int dt = path[i][k] + path[k][j];
                if(path[i][j] > dt)
                    path[i][j] = dt;
            }
 
    return path;
}


  • Obtiene la mejor ruta entre todo par de nodos.
  • Trabaja con la matriz D inicializada con las distancias directas entre todo par de nodos.
  • La iteración se produce sobre nodos intermedios, es decir, para todo elemento de la matriz se prueba si lo mejor para ir de i a j es a través de un nodo intermedio elegido o como estaba anteriormente, y esto se prueba con todos los nodos de la red.
  • Una vez probados todos los nodos de la red como nodos intermedios, la matriz resultante da la mejor distancia entre todo par de nodos.

Es decir el algoritmo es el siguiente:
Iniciación.
- matriz de distancias.
- distancia del enlace entre el nodo i y el nodo j.
Iteración. Para n=0,1 ... ,N-1

Empezando con el nodo 1 como intermedio (n=0), se prueba con todos los nodos como nodos intermedios, el último es con el nodo N como nodo intermedio (n=N-1), y así se van hallando las distancias mínimas.


http://pier.guillen.com.mx/algorithms/10-graficas/10.5-floyd.htm
http://es.wikipedia.org/wiki/Algoritmo_de_Floyd-Warshall
www.pms.ifi.lmu.de/lehre/compgeometry/Gosper/.../shortest_path.html

domingo, 10 de julio de 2011

Tipos de Algoritmos



Fuerza Bruta

Este tipo de algoritmos resuelven el problema con la estrategia mas obvia de solución que no siempre es la mejor según el número de operaciones que se requiere.

Un algoritmo de fuerza bruta para encontrar el divisor de un número natural n consistiría en enumerar todos los enteros desde 1 hasta n, chequeando si cada uno de ellos divide n sin generar resto. Otro ejemplo de búsqueda por fuerza bruta, en este caso para solucionar el problema de las ocho reinas (posicionar ocho reinas en el tablero de ajedrez de forma que ninguna de ellas ataque al resto), consistiría en examinar todas las combinaciones de posición para las 8 reinas (en total 64! /56! = 178.462.987.637.760 posiciones diferentes), comprobando en cada una de ellas si las reinas se atacan mutuamente.






Algoritmos Voraces

Estos tipos de algoritmos ofrecen una solución rápida, sin llegar a la solución completa; solucionan la opción de la solución(solución local) que tenga un costo menor en la etapa de solución en la que se encuentran, sin considerar se esa opción es parte de una solución óptima para el problema completo (solución global).

Funcionamiento:
El algoritmo escoge en cada paso al mejor elemento x conjunto de C, posible, conocido como el elemento más prometedor. Se elimina ese elemento del conjunto de candidatos, comprueba si la inclusión de este elemento en el conjunto de elementos seleccionados S unión de x, produce una solución factible.

En caso de que así sea, se incluye ese elemento en S. Si la inclusión no fuera factible, se descarta el elemento. Iteramos el bucle, comprobando si el conjunto de seleccionados es una solución y, si no es así, pasando al siguiente elemento del conjunto de candidatos.


Programación Dinámica

Cuando un problema presenta una subestructura óptima osea cuando la solución óptima de un problema, se obtiene a partir de las soluciones óptimas de sus subproblemas más sencillos y luego utilizando esas subsoluciones para resolver problemas incrementalmente difíciles. (problema grande, lo dividimos en partes)

Programación Lineal
Para resolver un problema utilizando este método se plantea una serie de inecuaciones y luego se busca maximizar o minimizar las variables, respetando las inecuaciones.
Muchos problemas pueden plantearse en la forma de un conjunto de inecuaciones, para luego resolverlas utilizando un algoritmo genérico,

Ejemplo: el método simplex.
Es un método numérico para optimización de problemas libres multidimensionales, perteneciente a la clase más general de algoritmos de búsqueda. El que permite encontrar una solución óptima en un problema de maximización o minimización, buscando en los vértices del polígono. En ambos casos, el método usa el concepto de un símplex, que es un politopo de N + 1 vértices en N dimensiones: un segmento de línea sobre una línea, un triángulo sobre un plano, un tetraedro en un espacio de tres dimensiones y así sucesivamente.




Divide y Reinarás
Ésta metodología divide las instancias del problema a resolver en instancias cada vez más pequeñas usualmente en forma recursiva, hasta llegar a la instancia en la que el problema es soluble o trivial con unas pocas instrucciones (los algoritmos de búsqueda binaria son claros ejemplos)

EJEMPLO de Búsqueda Binaria:
La búsqueda binaria sólo se puede implementar si el arreglo está ordenado. La idea consiste en ir dividiendo el arreglo en mitades. Por ejemplo supongamos que tenemos este vector:
int vector[10] =
   
{2,4,6,8,10,12,14,16,18,20};


La clave que queremos buscar es 6. El algoritmo funciona de la siguiente manera
  1. Se determinan un índice arriba y un índice abajo, Iarriba=0 e Iabajo=9 respectivamente.
  2. Se determina un indice central, Icentro = (Iarriba + Iabajo)/2, en este caso quedaría Icentro = 4.
  3. Evaluamos si vector[Icentro] es igual a la clave de búsqueda, si es igual ya encontramos la clave y devolvemos Icentro.
  4. Si son distintos, evaluamos si vector[Icentro] es mayor o menos que la clave, como el arreglo está ordenado al hacer esto ya podemos descartar una mitad del arreglo asegurándonos que en esa mitad no está la clave que buscamos. En nuestro caso vector[Icentro] = 4 < 6, entonces la parte del arreglo vector[0...4] ya puede descartarse.
  5. Reasignamos Iarriba o Iabajo para obtener la nueva parte del arreglo en donde queremos buscar. Iarriba, queda igual ya que sigue siendo el tope. Iabajo lo tenemos subir hasta 5, entonces quedaría Iarriba = 9, Iabajo = 5. Y volvemos al paso 2.

    Si la clave no fuese encontrada en algún momento Iabajo > Iarriba, con un while vamos a controlar esta condición para salir del ciclo en tal caso y devolver -1 (clave no encontrada).
    Hagamos modificaciones al código de búsqueda lineal para implementar una función de búsqueda binaria.


    //Busqueda binaria
    //en un arreglo.
    #include <iostream>
    using std::cout;
    using std::cin;
    using std::endl;
    void mostrarArreglo(const int[], int); //prototipo de funcion que
    recibe un arreglo constanteint busquedaBinaria(const int[], int, int); //arreglo, tamano, clave
    void ordenarArreglo(int[], int); //prototipo que modifica y ordena el
    arreglovoid intercambiar(int&, int&); //prototipo, intercambia
    los valores de dos elementosint main()
    {
    int clave =0;
      const int tamano = 15;
      int arreglo[tamano] = {25,17,13,16,41,32,12,115,95,84,54,63,78,21,10};
      //ordenamos el arreglo para que funcione la busquedaBinaria
      ordenarArreglo(arreglo,tamano);
      cout << "Elementos del arreglo: " << endl;
      mostrarArreglo(arreglo,tamano);
      cout << "Indique un valor a buscar y se le devolvera el indice: " << endl;
      cin >> clave;
      cout<< "Su valor se encuentra en arreglo["<<busquedaBinaria(arreglo,tamano,clave)<<"]" << endl;
      cout << "Fin del programa :)" << endl;
      return 0;
    }//fin de main
    void mostrarArreglo(const int arreglo[], int tamano)
    {
      for (int i = 0 ; i < tamano ; i++)
        cout << "arreglo[" << i << "]=" << arreglo[i] << endl;
    }
    int busquedaBinaria(const int arreglo[], int tamano, int clave)
    {
      int Iarriba = tamano-1;
      int Iabajo = 0;
      int Icentro;
      while (Iabajo <= Iarriba)
        {
          Icentro = (Iarriba + Iabajo)/2;
          if (arreglo[Icentro] == clave)
     return Icentro;
          else
     if (clave < arreglo[Icentro])
       Iarriba=Icentro-1;
     else
       Iabajo=Icentro+1;
        }
      return -1;
    }
    void ordenarArreglo(int arreglo[], int tamano)
    {
      for (int i = 0; i< tamano -1 ; i++)
        for (int j = 0; j< tamano -1 ; j++)
          if (arreglo[j] > arreglo[j+1])
     intercambiar(arreglo[j],arreglo[j+1]);
    }
    void intercambiar(int &a, int &b)
    {
      int tmp = b;
      b = a;
      a = tmp;
    }






Búsqueda y Enumeración
Muchos problemas(por ejemplo un juego de ajedrez) pueden modelarse con grafos y resolverse a partir de un algoritmo de exploración del grafo. Tal algoritmo especificará las reglas para moverse en el grafo en búsqueda de la solución al problema.

EJEMPLO:
Disponemos de un tablero de ajedrez de tamaño NxN, y se trata de colocar en él N reinas de manera que no se amenacen según las normas del ajedrez.

proc NReinas (↕[1 . . . i ]: TSolución, ↓N: N, ↑ok: B)
       variables j : N
       inicio
           si i=N entonces ok=CIERTO
           en otro caso
               ok=FALSO
               j=1
               mientras ¬ok ^ (j≤N) hacer
                   si EsFactible (R, j) entonces
                       R[i + 1]= j
                   NReinas (R, N, ok)
                   finsi
                   j=j+1
               finmientras
           finsi
       fin

*********
 func EsFactible (↓R[1 . . . i ]: TSolución, ↓j : N): B
       variables factible: B
       inicio
           factible=CIERTO
           k=1
               mientras factible ^ (k≤i) hacer
                   si (j=R[k])\/(i+1−k= |j−R[k]|) entonces
                   factible=FALSO
                   finsi
                   k=k+1
               finmientras
           devolver factible
        fin




Algoritmos Heurísticos
El propósito de estos algoritmos no es necesariamente encontrar una solución final al problema, sino encontrar una solución aproximada cuando el tiempo o los recursos necesarios para encontrar la solución perfecta son exclusivos.



En cualquier problema de búsqueda donde hay b opciones en cada nodo y una profundidad d al nodo objetivo, un algoritmo de búsqueda ingenuo deberá buscar potencialmente entre bd nodos antes de encontrar la solución. Las heurísticas mejoran la eficiencia de los algoritmos de búsqueda reduciendo el factor de ramificación de b a (idealmente) una constante b * .
Aunque cualquier heurística admisible devolverá una respuesta óptima, una heurística que devuelve un factor de ramificación más bajo es computacionalmente más eficiente para el problema en particular. Puede demostrarse que una heurística h2(n) es mejor que otra h1(n), si h2(n) domina h1(n), esto quiere decir que h1(n) < h2(n) para todo n.










http://es.wikipedia.org/wiki/Vuelta_Atr%C3%A1s
http://es.wikipedia.org/wiki/Heur%C3%ADstica_%28inform%C3%A1tica%29
http://codigomaldito.blogspot.com/2005/11/busqueda-binaria.html

miércoles, 6 de julio de 2011

ÁRBOL BINARIO


Un árbol binario es una estructura de datos en la cual cada nodo siempre tiene un hijo izquierdo y un hijo derecho. No pueden tener más de dos hijos (de ahí el nombre "binario"). Si algún hijo tiene como referencia a null, es decir que no almacena ningún dato, entonces este es llamado un nodo externo. En el caso contrario el hijo es llamado un nodo interno. Usos comunes de los árboles binarios son los árboles binarios de búsqueda, los montículos binarios y Codificación de Huffman.

Tipos de árboles binarios
  •     Un árbol binario es un árbol con raíz en el que cada nodo tiene como máximo dos hijos.
  •     Un árbol binario lleno es un árbol en el que cada nodo tiene cero o dos hijos.
  •     Un árbol binario perfecto es un árbol binario lleno en el que todas las hojas (vértices con cero hijos) están a la misma profundidad (distancia desde la raíz, también llamada altura).
    A veces un árbol binario perfecto es denominado árbol binario completo. Otros definen un árbol binario completo como un árbol binario lleno en el que todas las hojas están a profundidad n o n-1, para alguna n.

Un árbol binario es un árbol en el que ningún nodo puede tener más de dos subárboles. En un árbol binario cada nodo puede tener cero, uno o dos hijos (subárboles). Se conoce el nodo de la izquierda como hijo izquierdo y el nodo de la derecha como hijo derecho.

Métodos para almacenar árboles binarios

Los árboles binarios pueden ser construidos a partir de lenguajes de programación de varias formas. En un lenguaje con registros y referencias, los árboles binarios son construidos típicamente con una estructura de nodos y punteros en la cual se almacenan datos, cada uno de estos nodos tiene una referencia o puntero a un nodo izquierdo y a un nodo derecho denominados hijos. En ocasiones, también contiene un puntero a un único nodo. Si un nodo tiene menos de dos hijos, algunos de los punteros de los hijos pueden ser definidos como nulos para indicar que no dispone de dicho nodo. En la figura adjunta se puede observar la estructura de dicha implementación.
Los árboles binarios también pueden ser almacenados como una estructura de datos implícita en vectores, y si el árbol es un árbol binario completo, este método no desaprovecha el espacio en memoria.

Tomaremos como notación la siguiente: si un nodo tiene un índice i, sus hijos se encuentran en índices 2i + 1 y 2i + 2, mientras que sus padres (si los tiene) se encuentra en el índice (partiendo de que la raíz tenga índice cero). Este método tiene como ventajas el tener almacenados los datos de forma más compacta y por tener una forma más rápida y eficiente de localizar los datos en particular durante un preoden transversal. Sin embargo, desperdicia mucho espacio en memoria.







 webs.uvigo.es/joselu/material/Algoritmo%20de%20Huffman.pdf
 http://www.xatakaciencia.com/matematicas/algoritmo-de-huffman
http://es.wikipedia.org/wiki/Algoritmo_de_Huffman
http://es.wikipedia.org/wiki/%C3%81rbol_binario

TABLA DE DISPERSIÓN

Introducción

Las tablas de datos permiten el acceso directo a un elemento de una secuencia, indicando la posición que ocupan. Un diccionario es también una secuencia de elementos, pero éstos son realmente pares formados, por ejemplo, por un identificador y un número entero. Las tablas de dispersión se suelen utilizar para implementar cualquier tipo de diccionario. El potencial de las tablas hash o dispersas radica en la búsqueda de elementos; conociendo el campo clave se puede obtener directamente la posición que ocupa, y por consiguiente, la información asociada a dicha clave. Sin embargo, no permiten algoritmos eficientes para acceder a todos los elementos de la tabla, en su recorrido. El estudio de las tablas hash acarrea el estudio de funciones hash o dispersión, que mediante expresiones matemáticas permiten obtener direcciones según una clave que es el argumento de la función.

El potencial de las tablas hash o dispersas radica en la búsqueda de elementos; conociendo el campo clave se puede obtener directamente la posición que ocupa, y por consiguiente, la información asociada a dicha clave.

Tablas de Dispersión

Las tablas de dispersión, o simplemente tablas hash, son estructuras de datos que se usan en aplicaciones que manejan una secuencia de elementos, de tal forma que cada elemento tiene asociado un valor clave, que es un número entero positivo perteneciente a un rango de valores, relativamente pequeño. En estas organizaciones, cada uno de los elementos ha de tener una clave que identifica de manera única al elemento. Por ejemplo, el campo número de cuenta del conjunto de alumnos de la Universidad Iberoamericana puede considerarse un campo clave para organizar la información relativa al alumnado de la universidad ya que el número de cuenta es único. Hay una relación única (uno a uno) entre el campo y el registro alumno. Podemos suponer que no existen, simultáneamente, dos registros con el mismo número de cuenta.


Funciones de Dispersión

Una función de dispersión convierte el dato del campo clave, un entero o una cadena, en un valor entero en el rango de definición del arreglo o vector que va a almacenar los elementos, de tal forma que sea adecuado para indexar el arreglo.

La idea básica es utilizar la calve de un registro para determinar su dirección, pero sin desperdiciar mucho espacio, para lo que hay que realizar una transformación mediante una función hash, del conjunto de K claves sobre el conjunto L de direcciones de memoria:

h(x) : K -> L

Ésta es la función de direccionamiento hash o función de dispersión. Si x es una clave, entonces h(x) se denomina direccionamiento hash de la clave x, además es el índice de la tabla donde se debe guardar el registro con esa clave.

Hay que contemplar el hecho de que la función hash, h(x), no dé valores distintos: es posible (según la función elegida) que dos claves diferentes c1 y c2 den la misma dirección h(c1) = h(c2). Entonces se produce el fenómeno de la colisión, y se debe usar algún método para resolverla. Por lo tanto, en el estudio del direccionamiento hash hay que dividirlo en dos partes: búsqueda de funciones hash y resolución de colisiones.


www.lcc.uma.es/~lopez/modular/tads/dispersion.pdf


http://www.angelfire.com/wy2/est_info/secciona.html

http://www.ie.uia.mx/acad/atortole/progra2/hash.html


www.ublug.org.ar/index.php?...top%2FSPHINX3%2FTablas...Dispersion...

ALGORITMOS DE ORDENAMIENTO

Los algoritmos de ordenamiento se pueden clasificar de las siguientes maneras:



La más común es clasificar según el lugar donde se realice la ordenación
  • Algoritmos de ordenamiento interno: en la memoria del ordenador.
  • Algoritmos de ordenamiento externo: en un lugar externo como un disco duro.
Por el tiempo que tardan en realizar la ordenación, dadas entradas ya ordenadas o inversamente ordenadas:
  • Algoritmos de ordenación natural: Tarda lo mínimo posible cuando la entrada está ordenada.
  • Algoritmos de ordenación no natural: Tarda lo mínimo posible cuando la entrada está inversamente ordenada.
Por estabilidad: un ordenamiento estable mantiene el orden relativo que tenían originalmente los elementos con claves iguales. Por ejemplo, si una lista ordenada por fecha se reordena en orden alfabético con un algoritmo estable, todos los elementos cuya clave alfabética sea la misma quedarán en orden de fecha.




Los algoritmos de ordenamiento estable mantienen un relativo pre-orden total. Esto significa que un algoritmo es estable solo cuando hay dos registros R y S con la misma clave y con R apareciendo antes que S en la lista original.

(4, 1)  (3, 7)  (3, 1)  (5, 6)
 
En este caso, dos resultados diferentes son posibles, uno de los cuales mantiene un orden relativo de registros con claves iguales, y una en la que no:

(3, 7) (3, 1) (4, 1) (5, 6) (orden mantenido)
(3, 1) (3, 7) (4, 1) (5, 6) (orden cambiado)
 
 
Los algoritmos de ordenamiento inestable pueden cambiar el orden relativo de registros con claves iguales, pero los algoritmos estables nunca lo hacen.

Los algoritmos inestables pueden ser implementados especialmente para ser estables. Una forma de hacerlo es extender artificialmente el cotejamiento de claves, para que las comparaciones entre dos objetos con claves iguales sean decididas usando el orden de las entradas original. 
 
Ordenar según una clave primaria, secundaria, terciara, etc., puede ser realizado utilizando cualquier método de ordenamiento, tomando todas las claves en consideración (en otras palabras, usando una sola clave compuesta).
 
Si un método de ordenamiento es estable, es posible ordenar múltiples ítems, cada vez con una clave distinta. En este caso, las claves necesitan estar aplicadas en orden de aumentar la prioridad.
 
Ejemplo: ordenar pares de números, usando ambos valores

(4, 1)  (3, 7)  (3, 1)  (4, 6) (original)
 
 
(4, 1)  (3, 1)  (4, 6)  (3, 7) (después de ser ordenado por el segundo valor)
(3, 1)  (3, 7)  (4, 1)  (4, 6) (después de ser ordenado por el primer valor) 

Por otro lado:

(3, 7)  (3, 1)  (4, 1)  (4, 6) (después de ser ordenado por el primer valor)
  (3, 1)  (4, 1)  (4, 6)  (3, 7) (después de ser ordenando por el segundo valor,
                                 el orden por el primer valor es perturbado)







  






martes, 5 de julio de 2011

TIPOS DE ALGORITMOS

Insertion Sort

Propiedades

  •      estable
  •      O (1) espacio extra
  •      O (n2) comparaciones y swaps
  •      Adaptación: O (n) el tiempo en que casi clasificado
  •      Gastos generales muy bajos


    A pesar de que es uno de los algoritmos elementales de clasificación con O (n2) peor de los casos el tiempo, ordenación por inserción es el algoritmo de elección, ya sea cuando los datos se ordenan casi (porque es de adaptación) o cuando el tamaño del problema es pequeña (debido a que ha bajos costos).
 
 
 SELECTION SORT


 Propiedades

  •      no es estable
  •      (1) espacio extra
  •       (n2) comparaciones
  •       (n) los swaps
  •      no de adaptación
 DESCRIPCIÓN
 Se puede concluir que la ordenación por selección nunca debe ser usado. No se adapta a los datos de cualquier manera, por lo que su tiempo de ejecución es siempre de segundo grado.

Sin embargo, una especie de selección tiene la propiedad de reducir al mínimo el número de intercambios. En aplicaciones donde el costo de los artículos de intercambio es alto, una especie de selección muy bien puede ser el algoritmo de elección.


 BUBBLE SORT

Propiedades
  •      estable
  •      O (1) espacio extra
  •      O (n2) comparaciones y swaps
  •      Adaptación: O (n) cuando casi clasificado

 
DESCRIPCIÓN

Especie de burbuja tiene muchas de las mismas propiedades que la ordenación por inserción, pero tiene sobrecarga ligeramente superior. En el caso de los datos de cerca de clasificar, ordenar burbuja toma O (n), pero requiere al menos 2 pasa a través de los datos (mientras que la ordenación por inserción requiere algo más parecido a un pase).





SHELL SHORT


propiedades

  •      no es estable
  •      O (1) espacio extra
  •      O (n3 / 2) hora como se muestra (ver más abajo)
  •      Adaptación: O (n · lg (n)) momento en que casi clasificados

DESCRIPCIÓN

La complejidad del tiempo peor de los casos de tipo concha depende de la secuencia de incremento. Para el 1 4 13 40 121 incrementos de ..., que es lo que se usa aquí, la complejidad en tiempo es O (n3 / 2). Para otros incrementos, la complejidad de tiempo se sabe que es O (n4 / 3) y hasta O (n · LG2 (n)). Ni estrechos límites superiores de la complejidad del tiempo ni la mejor secuencia de incremento se conocen.

Debido a que tipo de shell se basa en la ordenación por inserción, una especie de shell hereda las propiedades de adaptación tipo de inserción. El adapation no es tan dramático, porque la ordenación Shell requiere un paso a través de los datos por cada incremento, pero es significativo. Para la secuencia de incremento se muestra más arriba, hay log3 (n) se incrementa, por lo que la complejidad del tiempo para los datos de casi ordenada es O (n · log3 (n)).

Debido a su bajo costo operativo, de aplicación relativamente sencilla, las propiedades de adaptación, y sub-cuadrática complejidad de tiempo, tipo concha puede ser una alternativa viable a la O (n · lg (n)) algoritmos de ordenación para algunas aplicaciones cuando los datos a ordenar es no es muy grande.



MERGE SORT

 Propiedades

  •      estable
  •       (n) un espacio extra para las matrices (como se muestra)
  •       (lg (n)) un espacio extra para listas enlazadas
  •       (n · lg (n)) el tiempo
  •      no de adaptación
  •      No requiere acceso aleatorio a los datos

DESCRIPCIÓN

Tipo de mezcla es muy predecible. Hace entre 0,5 * lg (n) y LG (n) comparaciones por elemento, y entre los lg (n) y 1.5 * lg (n) los swaps de cada elemento. Los mínimos se obtienen de los datos ya están ordenados, los máximos se alcanzan, en promedio, para datos aleatorios. Si se utiliza Θ (n) un espacio extra no es de interés, y luego fusionar especie es una excelente opción: es fácil de implementar, y es la única estable O (n · lg (n)) algoritmo de ordenación. Tenga en cuenta que al ordenar las listas enlazadas, fusionar tipo sólo requiere Θ (lg (n)) el espacio extra (por repetición).

Tipo de combinación es el algoritmo de elección para una variedad de situaciones: cuando la estabilidad es necesaria, al ordenar las listas enlazadas, y cuando el acceso al azar es mucho más caro que el acceso secuencial (por ejemplo, la clasificación externa en la cinta).

No existen el tiempo lineal en lugar de combinar los algoritmos para el último paso del algoritmo, pero son costosos y complejos. La complejidad se justifica para aplicaciones como la clasificación externa cuando Θ (n) un espacio extra no está disponible.



HEAP SORT

Propiedades

  •      no es estable
  •      O (1) de espacio extra (véase el debate)
  •      O (n · lg (n)) el tiempo
  •      En realidad, no adaptativa

DESCRIPCIÓN

Ordenación por montículo es fácil de implementar, realiza una operación O (n · lg (n)) en lugar de ordenar, pero no es estable.

El primer bucle, el Θ (n) "heapify" fase, pone a la matriz en orden montón. El segundo bucle, el O (n · lg (n)) "sortdown" fase, en repetidas ocasiones extrae el máximo y restaura el orden montón.

La función de sumidero que está escrito de forma recursiva para mayor claridad. Así, como muestra, el código requiere Θ (lg (n)) un espacio para la pila de llamadas recursivas. Sin embargo, la recursión de cola en el lavabo () se convierte fácilmente en iteración, lo que da la O (1) espacio obligado.

Ambas fases son un poco de adaptación, aunque no de manera especialmente útil. En el caso de casi ordenados, la fase de heapify destruye el orden original. En el caso de invertir, la fase de heapify es lo más rápido posible ya que la matriz se inicia con el fin de montón, pero luego la fase de sortdown es típico. En el caso de algunas claves únicas, hay una cierta aceleración, pero no tanto como en una especie de shell o quicksort 3-way.



QUICK SORT


Propiedades

  •      no es estable
  •      O (lg (n)) espacio adicional (véase el debate)
  •      O (n2), pero por lo general O (n · lg (n)) el tiempo
  •      no de adaptación

DESCRIPCIÓN

Cuando cuidadosamente implementado, más o menos rápido es robusto y tiene bajos costos. Cuando una especie estable, no se necesita, más o menos rápido es un excelente tipo de propósito general - aunque la versión de partición de 3 vías siempre se debe utilizar en su lugar.

A más eficiente y robusto de 2 vías método de división se da en el Quicksort es óptima por Robert Sedgewick y Jon Bentley. La partición robusta produce recursividad equilibrada cuando hay muchos valores iguales al pivote, dando garantías probabilística de O (n · lg (n)) y el tiempo de O (lg (n)) espacio para todas las entradas.

Con ambos tipos sub-realiza de manera recursiva, más o menos rápida requiere O (n) más espacio para la pila de recursión en el peor de los casos cuando la recursividad no es equilibrada. Esto es muy poco probable que ocurra, pero puede ser evitado por la clasificación de pequeñas sub-conjunto recursivamente primero y el segundo sub-array tipo es una llamada recursiva de cola, que se puede hacer con la iteración en su lugar. Con esta optimización, el algoritmo utiliza O (lg (n)) espacio extra en el peor de los casos.




QUICK SORT 3

Propiedades

  •      no es estable
  •      O (lg (n)) espacio extra
  •      O (n2), pero por lo general O (n · lg (n)) el tiempo
  •      Adaptación: O (n) tiempo en O (1) claves únicas


DESCRIPCIÓN

La variación de la partición de 3 vías de tipo rápido tiene arriba un poco mayor en comparación con la versión estándar de partición de 2 vías. Ambos tienen el mismo mejor, típico, y en el peor caso de los límites de tiempo, pero esta versión es altamente adaptable en el caso muy común de la clasificación con algunas claves únicas.

Cuando la estabilidad no es necesario, de 3 vías tipo de partición rápido es el algoritmo de propósito general de clasificación de la elección.







En resumen el algoritmo mas rápido es el quick sort 3 aunque no es muy estable, despues le sigue el heap y quick, y por ultimo el de inserción

REFERENCIAS:

http://www.sorting-algorithms.com/
http://en.wikipedia.org/wiki/Algorithm