domingo, 7 de agosto de 2016

Reconocimiento de Patrones mediante redes neuronales

RECONOCIMIENTO DE PATRONES MEDIANTE REDES NEURONALES

Hola amigos en esta ocasión les comparto una aplicación que se realizo en un curso de Inteligencia Artificial que estuve llevando en la universidad espero les sea de utilidad como es de costumbre comenzare con algo de teoría para luego compartir con ustedes el código fuente de la aplicación.

REDES NEURONALES

Una red neuronal artificial es un procesador distribuido en paralelo de forma masiva que tiene una tendencia natural para almacenar conocimiento de forma experimental y lo hace disponible para su uso.

SIMILITUD CON EL CEREBRO HUMANO:

  • El conocimiento es adquirido por la red a través de un proceso de aprendizaje.
  • Los pesos sinápticos o fuerza con que están interconectadas las neuronas se utilizan para almacenar la información.

En esta publicación se describirá una aplicación típica de las redes neuronales multicapa, concretamente el reconocimiento de patrones.

PERCEPTRÓN MULTINIVEL
Dentro de las redes neuronales, las que más utilizadas son las redes con múltiples capas que funcionan hacia delante. Esta red esta compuesta por un conjunto de nodos de entrada que componen la capa de entrada, un conjunto de una o más capas ocultas de neuronas y una capa de neuronas de salida. La señal de entrada se propaga hacia adelante desde la capa de entrada por la oculta hasta la salida; este tipo de configuración se conoce como MLP o "MultiLayer Perceptrons".


El hecho de que este tipo de red se aplique para resolver con éxito multitud de problemas se debe a la utilización del algoritmo de aprendizaje que actualmente está más extendido, el algoritmo o regla back propagation, el cual es una generalización de la regla LMS "Least Mean Square", por lo tanto también se basa en la corrección del error.
Básicamente el proceso back propagation consiste en dos pasadas a través de las diferentes capas de la red, una pasada hacia adelante y una pasada hacia atrás. En la pasada hacia adelante, se aplica en la capa de entrada un patrón o vector de entrada, este propaga su efecto a través de las diferentes capas y como consecuencia produce un vector de salida. 
Durante este proceso, los pesos sinápticos de la red son fijos y no se modifican. Durante la pasada hacia atrás en cambio, los pesos si se modifican de acuerdo con la regla de corrección del error. La señal de salida real se compara con la señal deseada y como resultado se obtiene una señal de error, que se propaga en dirección contraria a través de la red modificando los pesos, de forma que, al volver a pasar el vector de entrada hacia adelante, la respuesta obtenida se asemeje más a la salida deseada. Concretando, se puede decir que un perceptrón multicapa tiene tres características:

  • El modelo de cada neurona (figura 2) incluye una función no lineal. En este caso, a diferencia del perceptrón donde es la función escalón, y debido a la necesidad de que sea una función continua y derivable, es la función sigmoide, donde uk es la suma total de la actividad interna en la neurona k (la señal de entrada) e yk la salida que se produce en la neurona.




  • La red contiene una o más capas ocultas de neuronas que no forman parte ni de la entrada ni de la salida. Estas neuronas ocultas capacitan a la red para aprender progresivamente cualquier correspondencia entre la entrada y la salida y almacenar internamente esta información.
  • La red posee un gran número de conexiones, estas vienen determinadas por los pesos de la red. Un cambio en la conexión entre las neuronas equivale a un cambio en los pesos.
La combinación de estas características, hace que la habilidad de esta red para aprender a partir del entrenamiento sea muy potente, por ejemplo es capaz de resolver el problema de la OR-exclusiva a diferencia del perceptrón.

De todas formas, este comportamiento hace que sea difícil conocer a priori la respuesta de la red. Esto se debe a dos motivos, el comportamiento no lineal de las neuronas, las cuales están muy interconectadas, (lo que hace difícil un análisis teórico de la red) y la existencia de neuronas ocultas, que impide poder "ver" como se produce el aprendizaje y determinar cuales son las características que mejorarían el aprendizaje.

El desarrollo del algoritmo back propagation proporciona un método eficiente para entrenar este tipo de redes. Aunque no es capaz de resolver todos los problemas, se ha demostrado como el mejor de todos. Su importancia está en su capacidad de autoadaptar los pesos de las neuronas intermedias para aprender la relación que existe entre el conjunto de vectores o patrones de entrada y su correspondiente salida, y poder aplicar esa relación después del entrenamiento a nuevos vectores de entrada imperfectos o con ruido. Esta capacidad se conoce como generalización. La red debe encontrar una representación interna que le permita generar las salidas deseadas durante la etapa de entrenamiento, y posteriormente durante el funcionamiento ser capaz de generar salidas para entradas que no le fueron mostradas durante el aprendizaje pero que se asemejan a alguna de las que si le fueron mostradas.

EJEMPLO DE APLICACIÓN:

Para simular el funcionamiento de un perceptrón multinivel entrenado mediante el algoritmo back propagation, se plantea un sencillo problema de reconocimiento de óptico de caracteres. Su descripción es la siguiente:

Dado un panel de entrada compuesto por una matriz de 7x5 puntos, se consideran 12 clases diferentes donde se pretenden clasificar las muestras que se introducen. Los patrones que definen correctamente a cada una de las clases son los números del 0 al 9, el punto y el guión (figura 3).

Cuando a la entrada se presente una muestra distinta de los patrones correctos, el sistema presentará a su salida la información decodificada de la clase a la que pertenece la muestra, o bien, de la clase a la cual se aproxima más. En base a este planteamiento, la red neuronal dispone de 35 entradas que se corresponden con los puntos de la matriz numerados en la figura 4. El valor de cada entrada puede ser 0 si el punto es blanco y 1 si el punto es negro. Por otro lado, dispone de 12 salidas, una por cada clase. Cuando se introduzca una muestra a la entrada únicamente se activará la salida de la clase a la que pertenezca, permaneciendo las 11 restantes desactivadas con valores próximos a cero. Se considera que una salida está activada cuando su valor es próximo a la unidad.




RESULTADOS DE LA SIMULACIÓN:
Se realizo la implementación de una red neuronal en Java.En él se ha programado un perceptrón multinivel con 35 entradas y 12 salidas. También dispone de dos capas ocultas a las cuales se les puede modificar el número de sus neuronas. La red neuronal se ha entrenado con el algoritmo back propagation fijando el valor del momento en 0.8 y el factor de aprendizaje en 0.2. En este proceso únicamente se han usado doce muestras diferentes, es decir, los doce patrones sin ningún punto erróneo.
En la tabla I se muestran los resultados obtenidos para una red neuronal de tamaño 35-30-20-12. Se aprecia que tras el proceso de entrenamiento, el sistema responde de forma casi ideal cuando se introduce un patrón sin error.


IMPLEMENTACIÓN:






package digitos;
import javax.swing.JOptionPane;
public class digitos {

        int filas_entrada = 7;
        int col_entrada = 5;
        int neuronas_entrada = 35;
        int neuronas_oculta = 4;
        int neuronas_salida = 6;
        int i, j, k;
        int respuesta;
        int[][] patron = new int[5][7];
        double[][] pesos_entrada_oculta = {{1.658270037469468, -0.49277400626557, 0.8458827274104553, -2.648551432075224},
            {1.1603936240938337, -2.518757565059801, -0.21162501186454472, 0.4483509644280242},
            {0.9770989216075385, -1.1100683771308992, 0.5996520501870459, -1.5726273589648947},
            {-0.013373815758508401, -1.8088573544737623, -0.1806466969385013, 1.5619996308946966},
            {1.5477043440927314, -0.7451285620786131, 0.9078974238442922, -2.35721899617673},
            {1.6375625959682456, 0.05352515438526517, -1.7744239541344213, 1.4633986290892553},
            {-0.4895683338278625, 1.8177553658981087, 1.2455793786819767, -2.190482442936407},
            {-1.6471092496923165, 3.229636202454947, 1.2981497902378096, -0.7057775268591435},
            {-1.5667407635246655, -1.3245937353788448, 2.136912211349784, 0.006483361683210399},
            {-0.30620712895539964, -2.082257488430547, -0.7831144540926332, 2.860170068975273},
            {1.90730880729867, 1.786324022652642, -2.7987064218352016, -0.01985271542781151},
            {0.6073378720265543, 2.964084801890168, -0.8036519556281736, -0.11124911355434831},
            {0.8920664710707924, 1.2889968423497409, 1.4524992877441234, -4.5753301334234395},
            {0.5438102680069633, 2.732096021393861, -2.46484031199823, 1.077136757466367},
            {-0.5920084064677441, -2.0176453417628553, -0.8929972447944864, 2.9295974905024473},
            {-1.2273348838814682, 0.9665674119959388, -1.5098278165865484, 2.3855936692870223},
            {0.00303354357445883, 0.001581271588014359, 0.5078086841379089, -0.15023910421206893},
            {-0.2793511488473059, 1.4245803844405784, -0.8720662059686077, -0.5734228021484368},
            {-2.0275946046978293, -3.0777889662395226, 2.599080818031238, 1.6382349764441964},
            {1.7261757824595334, 1.8271668605813292, -2.6230163017984798, -0.023102087227872905},
            {-1.0400284786362686, 0.7483028076885504, -1.4201964658386028, 2.5893497462079242},
            {-1.2173094553360448, 1.0413190887119863, -1.3232448940438146, 2.7280471684208245},
            {-2.0889500107780266, 1.2113367713729557, 1.834844976337663, 0.48588965399759093},
            {-1.3959767352554984, 1.340974141434604, 0.4669554523335953, 0.8396877795787216},
            {0.5504882733683742, -0.007280194521717799, -1.049601349327548, 0.10628579988651637},
            {1.443454707114963, -1.1329167860893807, -1.202733639186253, -1.1206540048585907},
            {-0.4331284905557263, -1.5624137840495447, 1.116280151906849, 1.4607759610209936},
            {-0.5819930384824622, 1.758192614385909, 0.9171022839298032, -2.0753538337866986},
            {-1.3256304624056356, 1.044596834400223, 0.26717962626473013, 1.1753997160814336},
            {1.5797847092614183, -1.103413366841749, -1.1869484693704706, -1.0426817633297494},
            {-0.42533166857338317, -1.5201849827392802, 0.8863374072683927, 1.5286999085413167},
            {0.7681134972990639, -0.9328664559674184, 0.3210235342096992, -1.6081085875846808},
            {0.8656526958020259, -1.075079526248985, 0.4316305641408157, 1.4876790164633573},
            {-0.3418033974126609, 0.26137924890846875, 0.44588921383473606, -0.44772778571106026},
            {-0.5446034869269726, -1.7972802169693622, 1.143196716800601, 1.244775128422288}};
        
        
        double[][] pesos_oculta_salida = {{0.43621096542085136, -4.4770098664620495, -3.4380683245083805, 1.8296159164887345, -8.118120689656921, 5.695458556119513},
            {-1.027258581059679, 5.023126898792579, -7.757507526435068, -7.156764734520939, 4.478145059142636, 2.0603488963286387},
            {-8.390374693334456, 2.4451631927893267, 1.1247117391293466, 3.3697153535415416, -1.6716192668623555, -5.293714775954221},
            {5.151637910542471, -7.364970554525968, 4.923974784168798, -6.575339204075441, 4.552711228559867, -6.736958999045322}};
        
        
        
        double[] bias_entrada = {-0.4332161363101981,
            -0.5121521114762152,
            -0.39471528129652694,
            -0.5445351790184725,
            -0.4226637563836109,
            -0.5350195879964442,
            -0.3350043955733998,
            -0.38791367789336956,
            -0.3785593217087376,
            -0.5461130438610313,
            -0.554558738450257,
            -0.44288964426354815,
            -0.4380540832801621,
            -0.5350394221895475,
            -0.5580057611292775,
            -0.5560034483432901,
            -0.12912939908250873,
            -0.4139805211691567,
            -0.48481548633114296,
            -0.5388170156483836,
            -0.5565657646610173,
            -0.5633684138684646,
            -0.26352247776223936,
            -0.17951484914217863,
            -0.3253386781429675,
            -0.5422856630504476,
            -0.3859925425287016,
            -0.2926752007933522,
            -0.33661294128974667,
            -0.5351966380727077,
            -0.43172146451991633,
            -0.3614435550912756,
            -0.3009252579538338,
            0.13116124654486072,
            -0.42061848358615506,};
        double[] bias_oculta = {-0.5396208362695601,
            0.2293253598179893,
            0.9023280877608804,
            -0.5367884581404073};
        
        double[] bias_salida = {-1.3662965402953284,
            -2.7832950402557444,
            -1.5508290258166995,
            -0.47188562328444716,
            -4.298755314842922,
            -2.4180200713742552,};
        
        int[] entrada = new int[neuronas_entrada];
        double[] sumatoria_entrada = new double[neuronas_entrada];
        double[] sigmoidal_entrada = new double[neuronas_entrada];
        
        double[] oculta = new double[neuronas_oculta];
        double[] sumatoria_oculta = new double[neuronas_oculta];
        double[] sigmoidal_oculta = new double[neuronas_oculta];
        
        double[] salida = new double[neuronas_salida];
        double[] sumatoria_salida = new double[neuronas_salida];
        double[] sigmoidal_salida = new double[neuronas_salida];
        public digitos(int patrones[][])
        {
            this.patron=patrones;
        }
        public void reconocimiento()
        {
            //procesamiento de la capa de entrada
            //linealizar el patron de entrada
            for (i = 0; i  <  filas_entrada; i++) {
                for (j = 0; j  <  col_entrada; j++) {

                    entrada[i * col_entrada + j] = patron[i][j];
                }
            }

            //1.2 sumar la entrada con el bias y calcular sigmidal
            for (i = 0; i  <  neuronas_entrada; i++) {
                sumatoria_entrada[i] = entrada[i] + bias_entrada[i];
                sigmoidal_entrada[i] = 1 / (1 + Math.exp(-1 * sumatoria_entrada[i]));
            }
            //2. procesamiento de la capa oculta
            // 2.1 sumatoria de productos de entrada por peso
            for (j = 0; j  <  neuronas_oculta; j++) {
                oculta[j] = 0;
                for (i = 0; i  <  neuronas_entrada; i++) {
                    oculta[j] += sigmoidal_entrada[i] * pesos_entrada_oculta[i][j];
                }
            }
            //2.2 sumatoria mas bias y calculo de sigmoidal
            for (i = 0; i  <  neuronas_oculta; i++) {
                sumatoria_oculta[i] = oculta[i] + bias_oculta[i];
                sigmoidal_oculta[i] = 1 / (1 + Math.exp(-1 * sumatoria_oculta[i]));
            }

            //3. procesamiento de la capa salida
            //3.1 suamtoria de productos de salida x peso
            for (j = 0; j  <  neuronas_salida; j++) {
                salida[j] = 0;
                for (i = 0; i  <  neuronas_oculta; i++) {
                    salida[j] += sigmoidal_oculta[i] * pesos_oculta_salida[i][j];
                }
            }
            //3.2 sumatoria mas bias y calculo de sigmoidal
            for (i = 0; i  <  neuronas_salida; i++) {
                sumatoria_salida[i] = salida[i] + bias_salida[i];
                sigmoidal_salida[i] = 1 / (1 + Math.exp(-1 * sumatoria_salida[i]));
                System.out.println("Sigmoidal [" + i + "]- " + sigmoidal_salida[i]);
            }
            // 4.construyendo la interface de salida
            double mayor = -0.999;
            int neurona_activada = -1;
            for (i = 0; i  <  neuronas_salida; i++) {
                if (sigmoidal_salida[i]  >  mayor) {

                    mayor = sigmoidal_salida[i];
                    neurona_activada = i;
                }
            }
            if (mayor  >  0.80 ) { // heuristica para saber si el patron es coherente
                switch (neurona_activada) {
                    case 0:
                        respuesta=0;
                        System.out.println("RESPUESTA:"+respuesta);
                        break;
                    case 1:
                        respuesta=1;
                        System.out.println("RESPUESTA:"+respuesta);
                        break;
                    case 2:
                        respuesta=2;
                        System.out.println("RESPUESTA:"+respuesta);
                        break;
                    case 3:
                        respuesta=3;
                        System.out.println("RESPUESTA:"+respuesta);
                        break;
                    case 4:
                        respuesta=4;
                        System.out.println("RESPUESTA:"+respuesta);
                        break;
                    case 5:
                        respuesta=5;
                        System.out.println("RESPUESTA:"+respuesta);
                        break;

                }
            } else
            { 
                respuesta=-1;
                System.out.println("RESPUESTA:"+respuesta);
            }
        }
        public int getReconocimiento()
        {
            return respuesta;
        }

}


RESULTADOS OBTENIDOS EN LA IMPLEMENTACIÓN



jueves, 13 de agosto de 2015

CIFRADO RSA

RSA es uno de los sistemas de cifrado (encriptación) asimétricos, más exitosos en la actualidad. Originalmente descubierto 1973 por la agencia de inteligencia británica GCHQ, Government Communications Headquarters (GCHQ), recibió la clasificación de alto secreto “Top Secret”.
 El algoritmo fue descrito en 1977 y es propiedad de los criptólogos Ron Rivest,  Adi Shamir y Leonard Adleman,  del Instituto Tecnológico de Massachusetts (MIT) - RSA sol las letras iniciales de sus apellidos.

El algoritmo fue patentado por MIT en 1983 y no fue revelado hasta 1997. A diferencia de los sistemas de codificación simétrica tradicionales, RSA trabaja con dos claves diferentes: una clave "pública", y otra "privada". Ambas son complementarias entre sí (trabajan de manera conjunta) así que un mensaje cifrado con una de ellas sólo puede ser descifrado por su contraparte. Dado que la clave privada no se puede calcular a partir de la clave pública, esta última queda generalmente queda a disposición del público. Estas propiedades permiten que los cripto-sistemas asimétricos sean utilizados en una amplia variedad de funciones, tales como las firmas digitales.


Algoritmo RSA:

El Algoritmo RSA Consta de 3 partes, la primera hace referencia a la generación de las claves o llaves que serán usadas para la encriptación  del mensaje, la segunda el proceso de encriptado y la tercera el proceso de desencriptar el mensaje.


Generación de las Llaves (Pública y Privada).



























Para generar un par de claves (KP ; Kp), en primer lugar se eligen aleatoriamente dos números primos grandes, p y q (de unas 200 cifras cada uno, por ejemplo). Después se calcula el producto n = p.q Escogeremos ahora un número e primo relativo con (p-1) y con (q-1). Este par de números (e,n) pueden ser conocidos por cualquiera, y constituyen la llamada clave pública e por tanto debe tener un inverso módulo (p-1)(q-1), al que llamamos d. Por supuesto se cumple que ed ≡ 1 mod((p-1)(q-1)), que es lo mismo que decir que ed = 1+k (p-1)(q-1) para algún entero k. La clave privada será el par (d,n). Este número d debe mantenerse secreto y sólo será conocido por el propietario del par de claves.


Proceso de Encriptación y Desencriptación del Mensaje






















Ejemplo Aplicativo:


























Implementación


Clase Conversor
public class Conversor {
    public int numero(String a){
        int C=-666;
        if(a.equals(" ")){
            C=-555;
        }else if(a.equals("a")||a.equals("A")){
            C=0;
        }else if(a.equals("b")||a.equals("B")){
            C=1;
        }else if(a.equals("c")||a.equals("C")){
            C=2;
        }else if(a.equals("d")||a.equals("D")){
            C=3;
        }else if(a.equals("e")||a.equals("E")){
            C=4;
        }else if(a.equals("f")||a.equals("F")){
            C=5;
        }else if(a.equals("g")||a.equals("G")){
            C=6;
        }else if(a.equals("h")||a.equals("H")){
            C=7;
        }else if(a.equals("i")||a.equals("I")){
            C=8;
        }else if(a.equals("j")||a.equals("J")){
            C=9;
        }else if(a.equals("k")||a.equals("K")){
            C=10;
        }else if(a.equals("l")||a.equals("L")){
            C=11;
        }else if(a.equals("m")||a.equals("M")){
            C=12;
        }else if(a.equals("n")||a.equals("N")){
            C=13;
        }else if(a.equals("o")||a.equals("O")){
            C=14;
        }else if(a.equals("p")||a.equals("P")){
            C=15;
        }else if(a.equals("q")||a.equals("Q")){
            C=16;
        }else if(a.equals("r")||a.equals("R")){
            C=17;
        }else if(a.equals("s")||a.equals("S")){
            C=18;
        }else if(a.equals("t")||a.equals("T")){
            C=19;
        }else if(a.equals("u")||a.equals("U")){
            C=20;
        }else if(a.equals("v")||a.equals("V")){
            C=21;
        }else if(a.equals("w")||a.equals("W")){
            C=22;
        }else if(a.equals("x")||a.equals("X")){
            C=23;
        }else if(a.equals("y")||a.equals("Y")){
            C=24;
        }else if(a.equals("z")||a.equals("Z")){
            C=25;
        }
        return C;
    }
    
    public String numeroletra(int a){
        String C=null;
        if(a==-555){
            C=" ";
        }else if(a==0){
            C="00";
        }else if(a==1){
            C="01";
        }else if(a==2){
            C="02";
        }else if(a==3){
            C="03";
        }else if(a==4){
            C="04";
        }else if(a==5){
            C="05";
        }else if(a==6){
            C="06";
        }else if(a==7){
            C="07";
        }else if(a==8){
            C="08";
        }else if(a==9){
            C="09";
        }else if(a==10){
            C="10";
        }else if(a==11){
            C="11";
        }else if(a==12){
            C="12";
        }else if(a==13){
            C="13";
        }else if(a==14){
            C="14";
        }else if(a==15){
            C="15";
        }else if(a==16){
            C="16";
        }else if(a==17){
            C="17";
        }else if(a==18){
            C="18";
        }else if(a==19){
            C="19";
        }else if(a==20){
            C="20";
        }else if(a==21){
            C="21";
        }else if(a==22){
            C="22";
        }else if(a==23){
            C="23";
        }else if(a==24){
            C="24";
        }else if(a==25){
            C="25";
        }
        return C;
    }
    
    public String letranumero(int a){
        String C=null;
        if(a==-555){
            C=" ";
        }else if(a==0){
            C="A";
        }else if(a==1){
            C="B";
        }else if(a==2){
            C="C";
        }else if(a==3){
            C="D";
        }else if(a==4){
            C="E";
        }else if(a==5){
            C="F";
        }else if(a==6){
            C="G";
        }else if(a==7){
            C="H";
        }else if(a==8){
            C="I";
        }else if(a==9){
            C="J";
        }else if(a==10){
            C="K";
        }else if(a==11){
            C="L";
        }else if(a==12){
            C="M";
        }else if(a==13){
            C="N";
        }else if(a==14){
            C="O";
        }else if(a==15){
            C="P";
        }else if(a==16){
            C="Q";
        }else if(a==17){
            C="R";
        }else if(a==18){
            C="S";
        }else if(a==19){
            C="T";
        }else if(a==20){
            C="U";
        }else if(a==21){
            C="V";
        }else if(a==22){
            C="W";
        }else if(a==23){
            C="X";
        }else if(a==24){
            C="Y";
        }else if(a==25){
            C="Z";
        }
        return C;
    }
    
}

Clase Euclides
public class Euclides {
public long[] euclidesExtendido(long a, long b) 
{
 long[] resp = new long[3];
 long x=0,y=0,d=0;
 if(b==0)
 {
  resp[0] = a; resp[1] = 1; resp[2] = 0;
 } 
 else
 {
  long x2 = 1, x1 = 0, y2 = 0, y1 = 1;
  long q = 0, r = 0;
  while(b > 0)
  {
   q = (a/b);
   r = a - q*b;
   x = x2-q*x1;
   y = y2 - q*y1;
   a = b;
   b = r;
   x2 = x1;
   x1 = x;
   y2 = y1;
   y1 = y;
  }
  resp[0] = a;
  resp[1] = x2;
  resp[2] = y2;
    }
 return resp;  
    } 
}

Clase Exponenciación
public int CalcularExp(int a,int k,int z)
    {
        int exp=1;
        int xp=a%z;
        while(k > 0)
        {
            if((k%2)!=0)
            {
                exp=(exp*xp)%z;
            }
            xp=(xp*xp)%z;
            k=k/2;
        }
        return exp;
    }

Clase Inverso
public class Inverso {
    public double CalcularInverso(long n,long z)
    {
        long mcd[] =new long[3];
        int x=0,y = 0;
        Euclides obj = new Euclides();
        if(n > z)
        {
            mcd=obj.euclidesExtendido(n,z);
        }
        if(n < z)
        {
            mcd=obj.euclidesExtendido(z,n);
        }
        if(mcd[0] > 1)
        {
            System.out.println("EL INVERSO NO EXISTE");
            y=0;
        }
        else
        {
            y=(int) mcd[2];
            if(y < 0)
            {
                y=(int) (y+z);
            }
        }
        return y;
    }
}


Clase RSA
import java.util.ArrayList;
public class RSA {
    private long n, q, p;
    private long fi,e,d;
    private String mensaje;
    private String cifrado;
    private String MensajeLimpio;
    private String num_letra;
    private String CadenaDescifradaNumeros;
    public RSA()
    {
        //this.p=43;
        //this.q=59;
        //this.e=13;
        GenerarKey();
    }

    public String getCadenaDescifradaNumeros() {
        return CadenaDescifradaNumeros;
    }

    public String getNum_letra() {
        return num_letra;
    }



    public String getMensajeLimpio() {
        return MensajeLimpio;
    }

    public long getN() {
        return n;
    }

    public long getQ() {
        return q;
    }

    public long getP() {
        return p;
    }

    public long getFi() {
        return fi;
    }

    public long getE() {
        return e;
    }

    public long getD() {
        return d;
    }

    public String getMensaje() {
        return mensaje;
    }

    public String getCifrado() {
        return cifrado;
    }
    public void RecibirMensaje(String m)
    {
        this.mensaje=m;
    }
    public void RecibirCifrado(String c)
    {
        this.cifrado=c;
    }
    public boolean primo(int n)
    {
 for(int i=2;i < n;i++)
        {
            if(n%i==0)
            {
                return false;
            }
        }
 return true;
    }
    public void GenerarPrimos()
    {
        Boolean resP,resQ;
        do
        {
            p = (int)(Math.random()*(100-10+1)+10); 
            q = (int)(Math.random()*(100-10+1)+10); 
            resP=primo((int) p);
            resQ=primo((int) q);
        }while((p==q)||(resP==false)||(resQ==false));
        System.out.println("P:"+p);
        System.out.println("Q:"+q);
    }
    public int GenerarE()
    {
        Boolean resE;
        long mcd[]= new long[3];
        Euclides euclides = new Euclides();
        do
        {
            e = (int)(Math.random()*(100-1+1)+1);
            resE=primo((int) e);
            mcd=euclides.euclidesExtendido(e, fi);
        }while((e > =fi)||(mcd[0]!=1)||(resE==false));
        return (int) e;
    }
    public void GenerarKey()
    {
        GenerarPrimos();
        Inverso inverso= new Inverso();
        n=p*q;
        fi=(p-1)*(q-1);
        e=GenerarE();
        d=(long) inverso.CalcularInverso(e,fi);
        if(d < 0)
        {
            d=d+fi;
        }  
        System.out.println("n: "+n);
        System.out.println("fi: "+fi);
        System.out.println("e: "+e);
        System.out.println("d: "+d);
    }
    public String EliminarEspaciosCaracteresEspeciales()
    {
        //Eliminando los espacios y caracteres especiales
        String AlfabetoValido="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        String aux_mensaje="";
        int contador=0;
        for(int i=0;i < mensaje.length();i++)
        {
            for(int j=0;j < AlfabetoValido.length();j++)
            {
                if(!String.valueOf(mensaje.charAt(i)).equals(String.valueOf(AlfabetoValido.charAt(j))))
                {
                    contador++;
                }
            }
            if(contador > =AlfabetoValido.length())
            {
                //AL EVALUAR EL CARACTER CON TODOS LOS ELEMENTOS DE LA CADENA UNA DEBE DE COINCIDIR POR LO QUE EL VALOR DEL CONTADOR DEBE SER UNO MENOS QUE
                //EL DEL TAMAÑO DE ALFABETOVALIDO, SI SON IGUALES O ES MAYOR SIGNIFICA QUE ESE CARACTER NO ES VALIDO Y DEBE SER IGNORADO
            }
            else
            {
                aux_mensaje=aux_mensaje+mensaje.charAt(i);//CREANDO UNA NUEVA CADENA SIN ESPACIOS
            }
            contador=0;
        }
        //En el caso de que falten caracteres para la agrupacion de 4, se completan con un x
        if(aux_mensaje.length()%2!=0)
        {
            aux_mensaje=aux_mensaje+"X";
        }
        return aux_mensaje;
    }
    public String ConvertirNumeros(String mensaje_sin_espacios)
    {
        int aux_num_letra;
        num_letra ="";
        Conversor letras= new Conversor();
        for(int i=0;i < mensaje_sin_espacios.length();i++)
        {
            //Realizando la conversion a numeros
            aux_num_letra=letras.numero(String.valueOf(mensaje_sin_espacios.charAt(i)));
            num_letra=num_letra+letras.numeroletra(aux_num_letra);
           
        }
        return num_letra;
    }
    public String ConvertirCadena(String cadenacifrada)
    {
        Conversor letras= new Conversor();
        int inicioC=0,finalC=2;
        String TextoCifrado="",aux_TextoCifrado;
        for(int i=0;i < cadenacifrada.length();i++)
        {
            aux_TextoCifrado=cadenacifrada.substring(inicioC,finalC);
            if(Integer.parseInt(aux_TextoCifrado) > 25)
            {
                aux_TextoCifrado=String.valueOf(Integer.parseInt(aux_TextoCifrado)%26);
            }
            TextoCifrado=TextoCifrado+letras.letranumero(Integer.parseInt(aux_TextoCifrado));//CREANDO UNA NUEVA CADENA SIN ESPACIOS
            inicioC=finalC;
            finalC=finalC+2;
            i=inicioC;
        }
        return TextoCifrado;
    }
    public String Cifrar(String num_letra)
    {
        Exponenciacion expo= new Exponenciacion();
        ArrayList < String >  Cifrado = new ArrayList <  > ();
        int inicioC=0,finalC=4,rexpo = 0;
        String auxiliar;
        for(int i =0 ;i < num_letra.length();i++)
        {
            auxiliar=num_letra.substring(inicioC,finalC);
            System.out.println("SUBCADENA ANTES DE CIFRAR: "+auxiliar);
            //Realizando operacion de encriptado
            rexpo=expo.CalcularExp(Integer.parseInt(auxiliar),(int)e,(int)n);
            System.out.println("YA ME CIFRARON: "+rexpo);
            //ALMACENANDO
            if(String.valueOf(rexpo).length()==1)
            {
                Cifrado.add("000"+String.valueOf(rexpo));
            }
            if(String.valueOf(rexpo).length()==2)
            {
                Cifrado.add("00"+String.valueOf(rexpo));
            }
            if(String.valueOf(rexpo).length()==3)
            {
                Cifrado.add("0"+String.valueOf(rexpo));
            }
            if(String.valueOf(rexpo).length()==4)
            {
                Cifrado.add(String.valueOf(rexpo));
            }
            inicioC=finalC;
            finalC=finalC+4;
            i=inicioC;
        }
        //Guardando todo en una sola cadena
        String cadena="";
        for(int i=0;i < Cifrado.size();i++)
        {
            cadena=cadena+Cifrado.get(i);
        }
        System.out.println("A MI TIENEN QUE DESCIFRARME: "+cadena);
        RecibirCifrado(cadena);//SALVANDO C , QUE SERA USADO PARA DESENCRIPTAR
        return cadena; 
    }
    public String Descifrar()
    {
        Exponenciacion expo= new Exponenciacion();
        ArrayList < String >  Descifrado = new ArrayList <  > ();
        int inicioC=0,finalC=4,rexpo = 0;
        String auxiliar;
        System.out.println("SOY EL MENSAJE CIFRADO: "+cifrado);
        for(int i =0 ;i < cifrado.length();i++)
        {
            auxiliar=cifrado.substring(inicioC,finalC); 
            System.out.println("SOY UNA SUBCADENA DEL MENSAJE CIFRADO ANTES DE SER DESCIFRADO: "+auxiliar);
            //Realizando operacion de encriptado
            rexpo=expo.CalcularExp(Integer.parseInt(auxiliar),(int)d,(int)n);
            System.out.println("ME DESCIFRARON: "+rexpo);
            //ALMACENANDO
            if(String.valueOf(rexpo).length()==1)
            {
                Descifrado.add("000"+String.valueOf(rexpo));
            }
            if(String.valueOf(rexpo).length()==2)
            {
                Descifrado.add("00"+String.valueOf(rexpo));
            }
            if(String.valueOf(rexpo).length()==3)
            {
                Descifrado.add("0"+String.valueOf(rexpo));
            }
            if(String.valueOf(rexpo).length()==4)
            {
                Descifrado.add(String.valueOf(rexpo));
            }
            inicioC=finalC;
            finalC=finalC+4;
            i=inicioC;
        }
        //Guardando todo en una sola cadena
        String cadena="";
        for(int i=0;i < Descifrado.size();i++)
        {
            cadena=cadena+Descifrado.get(i);
        }
        return cadena; 
    }
    public String OperacionRSAEncriptar()
    {
        //Generando llaves - ESTE PROCESO SE REALIZA EN EL METODO CONSTRUCTOR , AQUI SOLO LO INDICAMOS
        //Eliminando espacios y caracteres especiales
        MensajeLimpio=EliminarEspaciosCaracteresEspeciales();
        //Realizando conversion de texto a numeros
        System.out.println("SOY EL MENSAJE LIMPIO: "+MensajeLimpio);
        num_letra=ConvertirNumeros(MensajeLimpio);
        System.out.println("CADENA ANTERIOR PERO EN NUMEROS: "+num_letra);
        //Cifrando
        String cadena;
        cadena=Cifrar(num_letra);
        System.out.println("Soy el cifrado"+this.cifrado);
        //Realizando conversion de numeros a letras
        String TextoFinal;
        TextoFinal=ConvertirCadena(cadena);
        return TextoFinal;
    }
    public String OperacionRSADesencriptar()
    {
        CadenaDescifradaNumeros=Descifrar();
        String TextoFinal;
        TextoFinal=ConvertirCadena(CadenaDescifradaNumeros);
        return TextoFinal;
    }
}

RESULTADOS:



jueves, 30 de julio de 2015

CIFRADO DE RABIN

El criptosistema de Rabin es una técnica criptográfica asimétrica cuya seguridad, al igual que RSA, se basa en la complejidad de la factorización. Sin embargo, la ventaja del criptosistema de Rabin es que se ha demostrado que la complejidad del problema en el que se basa es tan duro como la factorización de enteros, cosa que se desconoce si es cierto en el caso del RSA simple. El inconveniente que tiene es que cada salida de la función de Rabin puede ser generado por 4 posibles entradas, y si cada salida es un texto cifrado se requiere un tiempo extra en el descifrado para identificar cual de las 4 posibles entradas era el correcto texto en claro. El algoritmo se publicó en enero de 1979 por Michael O. Rabin.
El sistema de llave asimétrica de Rabin se basa en el problema de calcular raíces cuadradas módulo un número compuesto. Este problema se ha demostrado que es equivalente al de la factorización de dicho número.

ALGORITMO DE RABIN

GENERACIÓN DE LLAVES
Escogemos dos números primos, p y q, ambos congruentes con 3 módulo 4.Estos primos son la clave privada.
La clave pública es su producto, n=p*q.

CIFRAR MENSAJE

  • Cabe aclarar  que para cifrar el mensaje este debe de estar en forma de cadena de bits con una longitud de 16 bits , en el caso de que falten se cogen las ultimas cifras de la cadena para completar los 16 bits. 
  • Se realiza una conversión de binario a decimal de la cadena ingresada (m) .
  • Para codificar un mensaje , simplemente se calcula:

DESCIFRAR MENSAJE
Para desencriptar el mensaje solo se deben obtener las 4 raíces de:

C (mod n)

Una de las raíces convertida a binario hará referencia al mensaje cifrado.

EJEMPLO APLICATIVO:
Generando llaves:


Sea:
  • p=277
  • q=331
Entonces n= p*q = 91687

  • Clave Publica=(p=277,q=331)
  • Clave Privada=(n=91687)

Cifrando:

Cadena Ingresada: 1001111001 , como no cumple con la cantidad maxima de 16 caracteres se cogen las ultimas  6 cifras para completar, obteniendo nuestra nueva cadena:1001111001111001.
Pasando a decimal nuestra cadena, obteniendo m= 40569.
Ahora procedemos a cifrar: aplicando :
C=40569 mod 91687  =  62111

Descifrando:
Para descifrar el mensaje lo único que debemos hacer es aplicar la raíz cuadrada de C(mod n) , en este ejemplo obtendríamos:

m1 = 69654   = > 10001000000010110
m2 = 22033   = > 101011000010001
m3 = 40569   = > 1001111001111001
m4 = 51118   = > 1100011110101110

Podemos observar que la raíz m3 corresponde al mensaje antes de cifrar.


Implementación
Se hace recordar que en la implementacion hace uso de algoritmos que ya hemos visto con anterioridad , por lo que recomendamos se pase por el siguiente post:




Clase Rabin:

public class Rabin {
    private String mensaje;
    private long n, q, p;
    private String cifrado;
    private String CadenaCompleta;
    private int m,c;

    public int getM() {
        return m;
    }

    public int getC() {
        return c;
    }

    public String getCadenaCompleta() {
        return CadenaCompleta;
    }
    
    public String getMensaje() {
        return mensaje;
    }

    public long getN() {
        return n;
    }

    public long getQ() {
        return q;
    }

    public long getP() {
        return p;
    }

    public String getCifrado() {
        return cifrado;
    }
    
    public void setMensaje(String mensaje) {
        this.mensaje = mensaje;
    }
    public boolean primo(int n)
    {
    for(int i=2;i< n;i++)
        {
            if(n%i==0)
            {
                return false;
            }
        }
    return true;
    }
    public void GenerarPrimos()
    {
        Boolean resP,resQ;
        do
        {
            p = (int)(Math.random()*(1000-100+1)+100); 
            q = (int)(Math.random()*(1000-100+1)+100); 
            resP=primo((int) p);
            resQ=primo((int) q);
        }while((p< q)||(p==q)||(resP==false)||(resQ==false)||((p-3)%4!=0)||((q-3)%4!=0));
        
        System.out.println("P:"+p);
        System.out.println("Q:"+q);
    }
    public void GenerarKey()
    {
        GenerarPrimos();
        n=p*q;
        System.out.println("n: "+n);
    }
    public String CompletarCadena()
    {
        String nuevo_mensaje,aux;
        int tam=mensaje.length();
        int diferencia=tam-(16-tam);
        System.out.println("diferencia: "+diferencia);
        if(tam< 16)
        {
            aux=mensaje.substring(diferencia,tam);
            nuevo_mensaje=mensaje+aux;
        }
        else
        {
            nuevo_mensaje=mensaje;
        }
        return nuevo_mensaje;
    }
    public String Encriptar()
    {
        //Completando Cadena en el caso de que no cumplan los 16 bits   
        CadenaCompleta=CompletarCadena();
        //Pasando la cadena a decimal para poder encriptar
        Binario objBinario= new Binario();
        m=objBinario.BinarioDecimal(CadenaCompleta);
        //Encriptando
        Exponenciacion expo= new Exponenciacion();
        c=expo.CalcularExp(m, 2, (int) n);
        //Pasando a Binario el mensaje cifrado
        cifrado=objBinario.decimalABinario(c);
        return cifrado;
    }
    public String[] Desencriptar(int m,int c)
    {
        RaizCompuesta obj = new RaizCompuesta();
        int raices[]=new int[4];
        Binario objBinario= new Binario();
        String Descifrado[]= new String[4];
        raices=obj.CalcularRaizCompuesta(m,c);
        if(raices==null)
        {
            JOptionPane.showMessageDialog(null, "NO EXISTEN RAICES");
        }
        else
        {
            
            Descifrado[0]=objBinario.decimalABinario(raices[0]);
            Descifrado[1]=objBinario.decimalABinario(raices[1]);
            Descifrado[2]=objBinario.decimalABinario(raices[2]);
            Descifrado[3]=objBinario.decimalABinario(raices[3]);
        }
        return Descifrado;
    }
}


Clase Binario:
public class Binario {
    int decimal;
    public int BinarioDecimal(String numero)
    {
        decimal= Integer.parseInt(numero,2);
        return decimal;
    }
    public String decimalABinario(int numeroDecimal){
    int temp = numeroDecimal;
    String resultado="";
    while (temp != 0){
     if(temp % 2 == 0)
     {
         resultado="0"+resultado;
     }
     else
     {
         resultado="1"+resultado;
     }
     temp = temp/2;
    }
    return resultado;
   }
}

Resultados:




miércoles, 29 de julio de 2015

ANÁLISIS DE LA COMPLEJIDAD DE MÉTODOS DE ORDENACIÓN Y SU IMPLEMENTACIÓN

La tarea de la programación esta ligada al objetivo de obtener algoritmos que resuelvan un problema con la mayor eficiencia posible; de hecho es sorprendente comprobar las múltiples formas como podemos resolver un mismo problema y las ventajas que conseguimos , en términos de eficiencia , al buscar soluciones alternativas a las ya conocidas o consideradas como evidentes.
Para comparar y analizar la eficiencia de los algoritmos , estos los consideramos escritos en un lenguaje de programación de alto nivel , pero aun empleando la misma representación , establecer una medida precisa de la eficiencia de un algoritmo no es fácil.En efecto , fijémonos en que una definición de eficiencia podría ser el numero de instrucciones que tiene el programa ; sin embargo esto no se correspondería , con el concepto intuitivo que tenemos de eficiencia(según el cual,el algoritmo mas eficiente seria aquel que tardase menos tiempo en resolver el problema sobre una misma maquina), dado que todas las instrucciones no utilizan el mismo tiempo de procesador aun realizando la misma función.

MÉTODOS DE ORDENACIÓN:
En computación y matemáticas un algoritmo de ordenamiento es un algoritmo que pone elementos de una lista o un vector en una secuencia dada por una relación de orden, es decir, el resultado de salida ha de ser una permutación  o reordenamiento de la entrada que satisfaga la relación de orden dada. Los ordenamientos eficientes son importantes para optimizar el uso de otros algoritmos (como los de búsqueda y fusión) que requieren listas ordenadas para una ejecución rápida. También es útil para poner datos en forma canónica y para generar resultados legibles por humanos.



MÉTODO DE ORDENAMIENTO BURBUJA:

Este es uno de los métodos de ordenamiento mas usados .aunque no de los mas eficaces.
Consiste en recorrer la lista de valores a ordenar y compararlos dos a dos.Si los elementos están bien ordenados, pasamos al siguiente , hasta llegar al final de la lista.El proceso completo se repite hasta que la lista este ordenada.




Algoritmo:
DESDE I=1 hasta N-1 hacer
    DESDE J=1 hasta N-1 hacer
          Si v[J]>V[J+1] entonces
                Aux= V[J]
                V[J]=V[J+1]
                V[J+1]=Aux
           FIN SI
     FIN DESDE
FIN DESDE

Análisis de Complejidad:











El ciclo interno se ejecuta n veces para una lista de n elementos. El ciclo externo también se ejecuta n veces. Es decir, la complejidad es n * n = O(n2). El comportamiento del caso promedio depende del orden de entrada de los datos, pero es sólo un poco mejor que el del peor caso, y sigue siendo O(n2).

Implementación
public int burbuja(int[] arreglo)
    {  
        int aux,num_intercambios=0;  
        for(int i=1;i <  arreglo.length;i++)
        {  
            for (int j=0 ; j <  arreglo.length- 1; j++)
            {  
                if (arreglo[j] > arreglo[j+1])
                {  
                    aux = arreglo[j];  
                    arreglo[j] = arreglo[j+1];  
                    arreglo[j+1] = aux;  
                    num_intercambios++;
                }  
            }  
        }  
        //Nos retorna el numero de operaciones que hace referencia a la complejidad
        //Si desea obtener la lista de valores ordenados , recuperar este valor a través de un método get
        return num_intercambios;
    }  

MÉTODO DE ORDENACIÓN INSERCIÓN:
La idea de este algoritmo de ordenación consiste en ir insertando un elemento de la lista o un arreglo en la parte ordenada de la misma, asumiendo que el primer elemento es la parte ordenada, el algoritmo ira comparando un elemento de la parte desordenada de la lista con los elementos de la parte ordenada, insertando el elemento en la posición correcta dentro de la parte ordenada, y así sucesivamente hasta obtener la lista ordenada. Para explicarlo mejor nos basaremos en el siguiente enunciado:
“Para cada elemento de la lista después del primero, comparar los elementos con los anteriores desplazando una posición a la derecha a todos los elementos anteriores que cumplan con la comparación y luego colocar el elemento en la posición del último elemento anterior desplazado.”

Algoritmo:
for (i=1; i<TAM; i++)
      aux = array[i];
      j = i - 1;
      while ( (array[j] > aux) && (j >= 0) )
          array[j+1] = array[j];
           j--;
      array[j+1] = aux;

Análisis de la Complejidad:


Para una lista de n elementos el ciclo externo se ejecuta n-1 veces. El ciclo interno se ejecuta como máximo una vez en la primera iteración, 2 veces en la segunda, 3 veces en la tercera, etc. Esto produce una complejidad O(n2).

Implementación:
//METODO DE ORDENACION : INSERCION
    public int Insercion(int[] array){
        int aux,num_intercambios=0;
        for (int i = 1; i <  array.length; i++) {
            aux = array[i];
            for (int j = i-1; j   > =0 && array[j]  > aux; j--) {
                array[j+1]=array[j];
                array[j]=aux;
                num_intercambios++;
            }
        }
        //Nos retorna el numero de operaciones que hace referencia a la complejidad
        //Si desea obtener la lista de valores ordenados , recuperar este valor a través de un método get
return num_intercambios; }

MÉTODO DE ORDENACIÓN QUICKSORT:


Sin duda, este algoritmo es uno de los más eficientes. Este método es el más rápido gracias a sus llamadas recursivas, basándose en la teoría de divide y vencerás.
Lo que hace este algoritmo es dividir recursivamente el vector en partes iguales, indicando un elemento de inicio, fin y un pivote (o comodín) que nos permitirá segmentar nuestra lista. Una vez dividida, lo que hace, es dejar todos los mayores que el pivote a su derecha y todos los menores a su izquierda Al finalizar el algoritmo, nuestros elementos están ordenados.
Por ejemplo, si tenemos 3 5 4 8 básicamente lo que hace el algoritmo es dividir la lista de 4 elementos en partes iguales, por un lado 3, por otro lado 4 8 y como comodín o pivote el 5. Luego pregunta, 3 es mayor o menor que el comodín? Es menor, entonces lo deja al lado izquierda Y como se acabaron los elementos de ese lado, vamos al otro lado. 4 Es mayor o menor que el pivote? Menor, entonces lo tira a su izquierda. Luego pregunta por el 8, al ser mayor lo deja donde está, quedando algo así: 3 4 5 8.
En esta figura se ilustra de mejor manera un vector con más elementos, usando como pivote el primer elemento:


Algoritmo

// Inicialización de variables
    elem_div = lista[sup];
    i = inf - 1;
    j = sup;
    cont = 1;
    
    // Verificamos que no se crucen los límites
    if (inf >= sup)
          retornar;
    
    //  Clasificamos la sublista
    while (cont)
          while (lista[++i] < elem_div);
          while (lista[--j] > elem_div);
         if (i < j)
              temp = lista[i];
              lista[i] = lista[j];
              lista[j] = temp;
         else
              cont = 0;
   
   // Copiamos el elemento de división
   // en su posición final
    temp = lista[i];
    lista[i] = lista[sup];
    lista[sup] = temp;
   
   // Aplicamos el procedimiento
   // recursivamente a cada sublista
    OrdRap (lista, inf, i - 1);
    OrdRap (lista, i + 1, sup);

Análisis de la Complejidad:

Caso promedio: La complejidad para dividir una lista de n es O(n). Cada sublista genera en promedio dos sublistas más de largo n/2. Por lo tanto la complejidad se define en forma recurrente como:
f(1) = 1
f(n) = n + 2 f(n/2)
La forma cerrada de esta expresión es:
f(n) = n log2n
Es decir, la complejidad es O(n log2n).
El peor caso ocurre cuando la lista ya está ordenada, porque cada llamada genera sólo una sublista (todos los elementos son menores que el elemento de división). En este caso el rendimiento se degrada a O(n2). 
Implementación

//METODO DE ORDENACION: QUICKSORT
    public  int ordenacionRapida(int[] v) {
        final int N = v.length;
        quicksort(v,0,N-1);
        return num_intercambios_qs;
    }
 
    public void quicksort(int A[], int izq, int der) {
    int pivote=A[izq]; // tomamos primer elemento como pivote
    int i=izq; // i realiza la búsqueda de izquierda a derecha
    int j=der; // j realiza la búsqueda de derecha a izquierda
    int aux;

    while(i< j){            // mientras no se crucen las búsquedas
       while(A[i]< =pivote && i< j) i++; // busca elemento mayor que pivote
       while(A[j] > pivote) j--;         // busca elemento menor que pivote
       if (i< j) {                      // si no se han cruzado                      
           aux= A[i];                  // los intercambia
           A[i]=A[j];
           A[j]=aux;
       }
       num_intercambios_qs++;
        
     }
     A[izq]=A[j]; // se coloca el pivote en su lugar de forma que tendremos
     A[j]=pivote; // los menores a su izquierda y los mayores a su derecha
     if(izq< j-1)
        quicksort(A,izq,j-1); // ordenamos subarray izquierdo
     if(j+1 < der)
        quicksort(A,j+1,der); // ordenamos subarray derecho
    }
    

MÉTODO DE ORDENACIÓN MERGESORT:
Este algoritmo consiste básicamente en dividir en partes iguales la lista de números y luego mezclarlos comparándolos, dejándolos ordenados.
Si se piensa en este algoritmo recursivamente, podemos imaginar que dividirá la lista hasta tener un elemento en cada lista, luego lo compara con el que está a su lado y según corresponda, lo sitúa donde corresponde.
En la siguiente figura podemos ver cómo funciona:



Análisis de la Complejidad:












La complejidad para dividir una lista de n es O(n). Cada sublista genera en promedio dos sublistas más de largo n/2. Por lo tanto la complejidad se define en forma recurrente como:
f(1) = 1
f(n) = n + 2 f(n/2)
La forma cerrada de esta expresión es:
f(n) = n log2n
Es decir, la complejidad es O(n log2n).




Implementación:

//METODO DE ORDENACION: MERGESORT     
    public int MergeSort(int a[], int iMin, int iMax) 
    {
        mergeSort (a,iMin,iMax) ;
        return num_intercambios_ms;
    }
    public void mergeSort (int a[], int iMin, int iMax) 
    {
        // Caso Base
    if(iMin  >= iMax) 
        {
            return;
    }
    // Cortamos para aplicar mergeSort recursivamente
    int k = (iMin+iMax) / 2;
    mergeSort(a, iMin, k);
    mergeSort(a, k+1, iMax);
    // Utilizamos un arreglo temporal
    int l = iMax-iMin+1;
    int temp[] = new int[l];
    for(int i = 0; i <  l; i++)
        {
        temp[i] = a[iMin+i];
        }
    // Mezclamos
    int i1 = 0;
    int i2 = k-iMin+1;
    for(int i = 0; i <  l; i++) 
        {
            if(i2 < = iMax-iMin) 
            {
                if(i1 < = k-iMin) 
                {
                    if(temp[i1]  > temp[i2]) 
                    {
                        a[i+iMin] = temp[i2++];
                    }
                    else
                    {
                        a[i+iMin] = temp[i1++];
                    }
                }
                else
                {
                    a[i+iMin] = temp[i2++];
                }
                
                num_intercambios_ms++;
            }
            else 
            {
                a[i+iMin] = temp[i1++];
                
                num_intercambios_ms++;
            }
        }
    }

MÉTODO DE ORDENACIÓN HEAPSORT


Este algoritmo consiste en almacenar todos los elementos del vector a ordenar en un montículo (heap), y luego extraer el nodo que queda como nodo raíz del montículo (cima) en sucesivas iteracciones obteniendo el conjunto ordenado. Basa su funcionamiento en una propiedad de los montículos, por la cual, la cima contiene siempre el menor elemento (o el mayor, según se haya definido el montículo) de todos los almacenados en él. El algoritmo, después de cada extracción, re coloca en el nodo raíz o cima, la última hoja por la derecha del último nivel. Lo cual destruye la propiedad heap del árbol. Pero, a continuación realiza un proceso de "descenso" del número insertado de forma que se elige a cada movimiento el mayor de sus dos hijos, con el que se intercambia. Este intercambio, realizado sucesivamente "hunde" el nodo en el árbol restaurando la propiedad montículo del árbol y dejando paso a la siguiente extracción del nodo raíz.


Análisis de Complejidad:





Implementación:


 // METODO DE ORDENACION: HEAP SORT
    public int ordenacionMonticulos(int[] v) 
    {
        final int N = v.length;
        for(int nodo = N/2; nodo >=0; nodo--) hacerMonticulo(v, nodo, N-1);
        for(int nodo = N-1; nodo >=0; nodo--) {
            int tmp = v[0];
            v[0]    = v[nodo];
            v[nodo] = tmp;
            hacerMonticulo(v, 0, nodo-1);
        }
        return num_intercambios_hs;
    }
    public void hacerMonticulo(int[] v, int nodo, int fin)
    {
        int izq = 2*nodo+1;
        int der = izq+1;
        int may;
        if(izq >fin) return;
        if(der >fin) 
        {
            may=izq;  
            num_intercambios_hs++;
        }
        else
        {
            if(v[izq] >v[der])
                may= izq;
            else
                may=der; 
            num_intercambios_hs++;
        }
        if(v[nodo] <  v[may]) 
        {
            int tmp = v[nodo];
            v[nodo] = v[may];
            v[may]  = tmp; 
            num_intercambios_hs++;
            hacerMonticulo(v, may, fin); 
            num_intercambios_hs++;
        }
    }
Fuente Teóricas: Wikipedia, algunos documentos de Prezzi, apuntes de Clase.