Informática


Algoritmos: técnicas y análisis


INTRODUCCIÓN

Hoy en día, para cualquier persona que aspire al titulo de ingeniero de sistemas o ya lo sea, busca la facilidad y rapidez para la solución de algoritmos. Es por eso que estudiaremos una de las tantas estrategias que más se suele usar, los algoritmos voraces.

La capacidad de optimización que brinda y la facilidad de su realización es vastísima. Cada instrucción se lleva a cabo de la forma más sencilla y eficaz, pero no eficiente.

Estudiar las estrategias de programación, en especial los algoritmos voraces conlleva cierto tiempo de análisis, a pesar de su simplicidad.

Para preparar un trabajo escrito, con su debida exposición, se debe partir del concepto básico de la estrategia en estudio, dándole a conocer a los interesados en el tema lo interesante que se vuelve el análisis de algoritmos si lo enfatizamos en las diferentes estrategias que se presentan en la realización de cada uno de ellos.

Este informe servirá de guía a todo aquel que desea cumplir con una tarea intelectual tanto en el campo estudiantil como en el profesional, dentro de las distintas ramas del saber.

ALGORITMOS VORACES

Concepto

Voraz: destruye o consume rápidamente. Llevándolo al concepto que nos interesa, se puede decir que destruyen el problema que se nos presente al momento de construir un algoritmo.

Tal como su nombre lo indica, el enfoque que aplican es muy corto, y toman decisiones basándose en la información que tienen disponible de modo inmediato, sin tener en cuenta los efectos que estas decisiones puedan tener en el futuro. Por tal razón resultan fáciles de inventar, fáciles de implementar y, cuando funcionan, suelen ser eficaces, pero no eficientes.

Funcionamiento

Un algoritmo voraz funciona seleccionando el arco, o la tarea, que parezca más prometedora en un determinado instante; nunca reconsidera su decisión, sea cual fuere la situación que pudiera surgir más adelante. No hay necesidad de evaluar alternativas, ni de emplear sofisticados procedimientos de seguimiento que permitan deshacer las decisiones anteriores.

A continuación se analizará un ejemplo muy cotidiano en donde esta táctica o estrategia funciona bien:

Función devolver_cambio(n): conjunto de monedas

{Da el cambio de n unidades utilizando el menor número posible de monedas. La constante C especifica las monedas disponibles}

const C= {100, 25, 10, 5, 1}

S! {S es un conjunto que contendrá la solución}

s!0 {s es la suma de los elementos de S}

mientras (s<>n) hacer

x!el mayor elemento de c tal que (s+x)<=n

si no existe ese elemento entonces

devolver “no encuentro la solución”

S!S U {una moneda de valor x}

s!s+x

devolver S

Características generales de los algoritmos voraces

  • se tienen que resolver los problemas en forma óptima.

  • Existen diferentes funciones como son:

    • función solución: comprueba si un cierto número de candidatos constituye una solución de nuestro problema, ignorando si es óptima por el momento.

    • función de factibilidad: comprueba si un cierto conjunto de candidatos es factible, esto es, si es posible o no completar el conjunto añadiendo otros candidatos para obtener al menos una solución de nuestro problema.

    • función de selección: indica en cualquier momento cuál es el más prometedor de los candidatos restantes, que no han sido seleccionados ni rechazados.

    • función objetivo: da el valor de la solución que hemos hallado.

  • Los algoritmos voraces avanzan paso a paso

  • Inicialmente el conjunto de elementos seleccionados está vacío.

Guiándonos de las características generales analizaremos las razones por las cuales el algoritmo anterior (algoritmo de las monedas), es voraz:

Función solución: comprueba si el valor de las monedas seleccionadas hasta el momento es exactamente el valor que hay que pagar.

Función de factibilidad: si su valor total no sobrepasa la cantidad que haya que pagar.

Función de selección: toma la moneda de valor más alto que quede en el conjunto de candidatos

Función objetivo: cuenta el número de monedas utilizadas en la solución.

El que en cada paso selecciona la mayor de las monedas que pueda escoger, sin preocuparse por lo correcto de esta decisión a la larga, y una vez que una moneda se ha incluido en la solución, la tal moneda queda allí para siempre, son las razones por las que convierten este algoritmo, en una estrategia voraz.

OTROS ALGORITMOS VORACES

import java.awt.*;

//-------------------------------------------------------------------------/

// AlgoritmoD.java

//

// Agradecimientos a:

// David Alejandro Jaime Ospina

// Ricardo Andres Jaime Prada

// Roberto Dircio Palacios Macedo

// Programacion Lineal y Redes

//

// Applet para ejecutar el algoritmo de Dijkstra en un grafo dirigido

// y encontrar el camino mas corto a todos los nodos.

//-------------------------------------------------------------------------/

public class AlgoritmoD extends java.applet.Applet {

GraphCanvas grafocanvas = new GraphCanvas(this);

Options options = new Options(this);

Documentacion documentacion = new Documentacion();

public void init() {

setLayout(new BorderLayout(10, 10));

add("Center", grafocanvas);

add("North", documentacion);

add("East", options);

}

public Insets insets() {

return new Insets(10, 10, 10, 10);

}

public void lock() {

grafocanvas.lock();

options.lock();

}

public void unlock() {

grafocanvas.unlock();

options.unlock();

}

}

class Options extends Panel {

// opciones a un lado de la pantalla

Button b1 = new Button("limpiar");

Button b2 = new Button("ejecutar");

Button b3 = new Button("paso");

Button b4 = new Button("inicializar");

Button b5 = new Button("ejemplo");

Button b6 = new Button("salir");

AlgoritmoD parent;

boolean Locked=false;

Options(AlgoritmoD myparent) {

parent = myparent;

setLayout(new GridLayout(6, 1, 0, 10));

add(b1);

add(b2);

add(b3);

add(b4);

add(b5);

add(b6);

}

public boolean action(Event evt, Object arg) {

if (evt.target instanceof Button) {

if (((String)arg).equals("paso")) {

if (!Locked) {

b3.setLabel("siguiente paso");

parent.grafocanvas.stepalg();

}

else parent.documentacion.doctext.showline("cerrado");

}

if (((String)arg).equals("siguiente paso"))

parent.grafocanvas.nextstep();

if (((String)arg).equals("inicializar")) {

parent.grafocanvas.inicializar();

b3.setLabel("paso");

parent.documentacion.doctext.showline("referencia");

}

if (((String)arg).equals("limpiar")) {

parent.grafocanvas.limpiar();

b3.setLabel("paso");

parent.documentacion.doctext.showline("referencia");

}

if (((String)arg).equals("ejecutar")) {

if (!Locked)

parent.grafocanvas.runalg();

else parent.documentacion.doctext.showline("cerrado");

}

if (((String)arg).equals("ejemplo")) {

if (!Locked)

parent.grafocanvas.showejemplo();

else parent.documentacion.doctext.showline("cerrado");

}

if (((String)arg).equals("salir")) {

System.exit(0);

}

}

return true;

}

public void lock() {

Locked=true;

}

public void unlock() {

Locked=false;

b3.setLabel("paso");

}

}

class Documentacion extends Panel {

// Documentacion arriba de la pantalla

DocOptions docopt = new DocOptions(this);

DocText doctext = new DocText();

Documentacion() {

setLayout(new BorderLayout(10, 10));

add("West", docopt);

add("Center", doctext);

}

}

class DocOptions extends Panel {

Choice doc = new Choice();

Documentacion parent;

DocOptions(Documentacion myparent) {

setLayout(new GridLayout(2, 1, 5, 0));

parent = myparent;

add(new Label("DOCUMENTACION:"));

doc.addItem("dibujar nodos");

doc.addItem("remover nodos");

doc.addItem("mover nodos");

doc.addItem("el nodo_inicial");

doc.addItem("dibujar aristas");

doc.addItem("cambiar pesos");

doc.addItem("remover aristas");

doc.addItem("limpiar / inicializar");

doc.addItem("ejecutar algoritmo");

doc.addItem("pasar");

doc.addItem("ejemplo");

doc.addItem("salir");

doc.addItem("referencia");

add(doc);

}

public boolean action(Event evt, Object arg) {

if (evt.target instanceof Choice) {

String str=new String(doc.getSelectedItem());

parent.doctext.showline(str);

}

return true;

}

}

class DocText extends TextArea {

final String drawnodos = new String("DIBUJAR NODOS:\n"+

"Dibuje un nodo haciendo click en el mouse.\n\n");

final String rmvnodos = new String("REMOVER NODOS:\n"+

"Para remover un nodo presione <ctrl> y haga click en el nodo.\n"+

"No se puede remover el nodo_inicial.\n"+

"Seleccione otro nodo_inicial, asi podra remover el nodo.\n\n");

final String mvnodos = new String("MOVER NODOS\n"+

"Para mover un nodo presione <Shift>, haga click en el nodo,\ny arrastrelo a"+

" su nueva posicion.\n\n");

final String nodo_inicial = new String("NODO INICIAL:\n"+

"El nodo_inicial es azul, los otros nodos son grises.\n"+

"El primer nodo que usted dibuja en la pantalla sera el nodo_inicial.\n"+

"Para seleccionar otro nodo_inicial presione <ctrl>, haga click en el nodo_inicial,\n"+

"y arrastre el mouse a otro nodo.\n"+

"Para borrar el nodo_inicial, primero seleccione otro nodo_inicial, y despues"+

"\nremueva el nodo normalmente.\n\n");

final String drawaristas = new String("DIBUJAR ARISTAS:\n"+

"Para dibujar una arista haga click al mouse en un nodo,"+

"y arrastrelo a otro nodo.\n\n");

final String peso = new String("CAMBIAR PESOS:\n"+

"Para cambiar el peso de una arista, haga click en la flecha y \n"+

"arrastrela sobre la arista.\n\n");

final String rmvaristas = new String("REMOVER ARISTAS:\n"+

"Para remover una arista, cambiar su peso a 0.\n\n");

final String clrreset = new String("BOTON DE<LIMPIAR>: "+

"Remover el grafo de la pantalla.\n"+

"BOTON DE<INICIALIZAR>: "+

"Remover los resultados del algoritmo en el grafo,\n"+

" y abrir la pantalla.\n\n");

final String runalg = new String("BOTON DE <EJECUTAR>: "+

"Ejecuta el algoritmo en el grafo, habra un tiempo\n" +

"de retraso de +/- 1 segundos entre cada paso.\n"+

"Para ejecutar el algoritmo mas lento, use <PASO>.\n");

final String paso = new String("BOTON DE <PASO>: " +

"Recorrer el algoritmo paso a paso.\n");

final String ejemplo = new String("BOTON DE <EJEMPLO>: "+

"Despliega un grafo en la pantalla.\n"+

"Usted puede usar <PASO> or <EJECUTAR>\n");

final String exitbutton = new String("BOTON DE <SALIR>: " +

"Solo funciona si el applet es ejecutado en appletviewer.\n");

final String toclose = new String("ERROR: "+

"Esta posicion es para cerrar a otro nodo/arista.\n");

final String hecho = new String("El lgoritmo ha terminado, " +

"siga las aristas naranjas del nodo_inicial a cualquier nodo "+

"para obtener\nel mas corto camino al " +

"nodo. La longitud del camino se escribe en el nodo.\n" +

"presione <INICIALIZAR> para inicializar el grafo, y liberar la pantalla.");

final String alguno = new String("El algoritmo ha terminado, " +

"siga las aristas naranjas del nodo_inicial a cualquier nodo "+

"para obtener\nel mas corto camino al " +

"nodo. La longitud del camino se escribe en el nodo.\n" +

"No hay caminos del nodo_inicial a ningun otro nodo.\n" +

"presione <INICIALIZAR> para inicializar el grafo, y liberar la pantalla.");

final String ninguno = new String("El algoritmo ha terminado, " +

"no hay nodos alcanzables desde el nodo inicial.\n"+

"presione <INICIALIZAR> para inicializar el grafo, y liberar la pantalla.");

final String maxnodos = new String("ERROR: "+

"Maximo numero de nodos alcanzado!\n\n");

final String info = new String("DOCUMENTACION:\n"+

"Usted puede ver toda la documentacion u obtener documentacion\n"+

"de algo especifico "+

"seleccionando el item a la izquierda.\nSeleccionar <Referencia> "+

"lo regresa "+

" al texto.\n\n");

final String cerrado = new String("ERROR: "+

"Teclado/mouse cerrado para esta accion.\n"+

"Presione <SIGUIENTE PASO> o <INICIALIZAR>.\n");

final String doc = info + drawnodos + rmvnodos + mvnodos +

nodo_inicial + drawaristas + peso + rmvaristas +

clrreset + runalg + paso + ejemplo + exitbutton;

DocText() {

super(5, 2);

setText(doc);

}

public void showline(String str) {

if (str.equals("dibujar nodos")) setText(drawnodos);

else if (str.equals("remover nodos")) setText(rmvnodos);

else if (str.equals("mover nodos")) setText(mvnodos);

else if (str.equals("el nodo_inicial")) setText(nodo_inicial);

else if (str.equals("dibujar aristas")) setText(drawaristas);

else if (str.equals("cambiar pesos")) setText(peso);

else if (str.equals("remover aristas")) setText(rmvaristas);

else if (str.equals("limpiar / inicializar")) setText(clrreset);

else if (str.equals("ejecutar algoritmo")) setText(runalg);

else if (str.equals("pasar")) setText(paso);

else if (str.equals("ejemplo")) setText(ejemplo);

else if (str.equals("salir")) setText(exitbutton);

else if (str.equals("referencia")) setText(doc);

else if (str.equals("toclose")) setText(toclose);

else if (str.equals("hecho")) setText(hecho);

else if (str.equals("cerrado")) setText(cerrado);

else if (str.equals("maxnodos")) setText(maxnodos);

else if (str.equals("ninguno")) setText(ninguno);

else if (str.equals("alguno")) setText(alguno);

else setText(str);

}

}

class GraphCanvas extends Canvas implements Runnable {

// area de dibujo del grafo

final int MAXNODOS = 20;

final int MAX = MAXNODOS+1;

final int NODOSIZE = 26;

final int NODORADIX = 13;

final int DIJKSTRA = 1;

// informacion basica del grafo

Point nodo[] = new Point[MAX]; // nodo

int peso[][] = new int[MAX][MAX]; // peso de arista

Point arista[][] = new Point[MAX][MAX]; // posicion actual de la flecha

Point startp[][] = new Point[MAX][MAX]; // punto inicial

Point endp[][] = new Point[MAX][MAX]; // y final de arista

float dir_x[][] = new float[MAX][MAX]; // direccion de arista

float dir_y[][] = new float[MAX][MAX]; // direccion de arista

// informacion del grafo al ejecutar el algoritmo

boolean algedge[][] = new boolean[MAX][MAX];

int dist[] = new int[MAX];

int finaldist[] = new int[MAX];

Color colornodo[] = new Color[MAX];

boolean changed[] = new boolean[MAX]; // indica cambio de distancia durante el algoritmo

int numchanged =0;

int neighbours=0;

int paso=0;

// informacion usada por el algoritmo para encontrar el siguiente

// nodo con minima distancia

int mindist, minnodo, minstart, minend;

int numnodos=0; // numero ode nodos

int emptyspots=0; // lugares vacios en el arreglo nodo[] (por borrado de nodos)

int startgrafo=0; // comienzo de grafo

int hitnodo; // click del mouse en o cerca del nodo

int nodo1, nodo2; // numero de nodos envueltos in la accion

Point thispoint=new Point(0,0); // posicion actual del mouse

Point oldpoint=new Point(0, 0); // posicion previa del nodo

// accion actual

boolean newarista = false;

boolean movearista = false;

boolean movestart = false;

boolean deletenodo = false;

boolean movenodo = false;

boolean performalg = false;

boolean clicked = false;

// fonts

Font roman= new Font("TimesRoman", Font.BOLD, 12);

Font helvetica= new Font("Helvetica", Font.BOLD, 15);

FontMetrics fmetrics = getFontMetrics(roman);

int h = (int)fmetrics.getHeight()/3;

// buffer doble

private Image offScreenImage;

private Graphics offScreenGraphics;

private Dimension offScreenSize;

// opcion de ejecutar

Thread algrthm;

// algoritmo actual, (se pueden aniadir mas)

int algoritmo;

// informacion del algoritmo para ser desplegado en la documentacion

String showstring = new String("");

boolean stepthrough=false;

// cerrar la pantalla mientras se ejecuta el algoritmo

boolean Locked = false;

AlgoritmoD parent;

GraphCanvas(AlgoritmoD myparent) {

parent = myparent;

init();

algoritmo=DIJKSTRA;

setBackground(Color.white);

}

public void lock() {

// cerrar la pantalla mientras se ejecuta el algoritmo

Locked=true;

}

public void unlock() {

Locked=false;

}

public void start() {

if (algrthm != null) algrthm.resume();

}

public void init() {

for (int i=0;i<MAXNODOS;i++) {

colornodo[i]=Color.gray;

for (int j=0; j<MAXNODOS;j++)

algedge[i][j]=false;

}

colornodo[startgrafo]=Color.blue;

performalg = false;

}

public void limpiar() {

// remueve grafo de la pantalla

startgrafo=0;

numnodos=0;

emptyspots=0;

init();

for(int i=0; i<MAXNODOS; i++) {

nodo[i]=new Point(0, 0);

for (int j=0; j<MAXNODOS;j++)

peso[i][j]=0;

}

if (algrthm != null) algrthm.stop();

parent.unlock();

repaint();

}

public void inicializar() {

// inicializa aun grafo despues de ejecutar un algoritmo

init();

if (algrthm != null) algrthm.stop();

parent.unlock();

repaint();

}

public void runalg() {

// anima el algoritmo

parent.lock();

initalg();

performalg = true;

algrthm = new Thread(this);

algrthm.start();

}

public void stepalg() {

// le permite pasar por el algoritmo

parent.lock();

initalg();

performalg = true;

nextstep();

}

public void initalg() {

init();

for(int i=0; i<MAXNODOS; i++) {

dist[i]=-1;

finaldist[i]=-1;

for (int j=0; j<MAXNODOS;j++)

algedge[i][j]=false;

}

dist[startgrafo]=0;

finaldist[startgrafo]=0;

paso=0;

}

public void nextstep() {

// calcula un paso en el algoritmo (encuentra un mas corto

// camino al siguiente nodo).

finaldist[minend]=mindist;

algedge[minstart][minend]=true;

colornodo[minend]=Color.orange;

// mas informacion para la documentacion

paso++;

repaint();

}

public void stop() {

if (algrthm != null) algrthm.suspend();

}

public void run() {

for(int i=0; i<(numnodos-emptyspots); i++){

nextstep();

try { algrthm.sleep(2000); }

catch (InterruptedException e) {}

}

algrthm = null;

}

public void showejemplo() {

// dibuja un grafo en la pantalla

int w, h;

limpiar();

init();

numnodos=10;

emptyspots=0;

for(int i=0; i<MAXNODOS; i++) {

nodo[i]=new Point(0, 0);

for (int j=0; j<MAXNODOS;j++)

peso[i][j]=0;

}

w=this.size().width/8;

h=this.size().height/8;

nodo[0]=new Point(w, h); nodo[1]=new Point(3*w, h);

nodo[2]=new Point(5*w, h); nodo[3]=new Point(w, 4*h);

nodo[4]=new Point(3*w, 4*h); nodo[5]=new Point(5*w, 4*h);

nodo[6]=new Point(w, 7*h); nodo[7]=new Point(3*w, 7*h);

nodo[8]=new Point(5*w, 7*h); nodo[9]=new Point(7*w, 4*h);

peso[0][1]=4; peso[0][3]=85;

peso[1][0]=74; peso[1][2]=18; peso[1][4]=12;

peso[2][5]=74; peso[2][1]=12; peso[2][9]=12;

peso[3][4]=32; peso[3][6]=38;

peso[4][3]=66; peso[4][5]=76; peso[4][7]=33;

peso[5][8]=11; peso[5][9]=21;

peso[6][7]=10; peso[6][3]=12;

peso[7][6]=2; peso[7][8]=72;

peso[8][5]=31; peso[8][9]=78; peso[8][7]=18;

peso[9][5]=8;

for (int i=0;i<numnodos;i++)

for (int j=0;j<numnodos;j++)

if (peso[i][j]>0)

aristaupdate(i, j, peso[i][j]);

repaint();

}

public boolean mouseDown(Event evt, int x, int y) {

if (Locked)

parent.documentacion.doctext.showline("cerrado");

else {

clicked = true;

if (evt.shiftDown()) {

// mover un nodo

if (nodohit(x, y, NODOSIZE)) {

oldpoint = nodo[hitnodo];

nodo1 = hitnodo;

movenodo=true;

}

}

else if (evt.controlDown()) {

// borrar un nodo

if (nodohit(x, y, NODOSIZE)) {

nodo1 = hitnodo;

if (startgrafo==nodo1) {

movestart=true;

thispoint = new Point(x,y);

colornodo[startgrafo]=Color.gray;

}

else

deletenodo= true;

}

}

else if (aristahit(x, y, 5)) {

// cambiar peso de una arista

movearista = true;

repaint();

}

else if (nodohit(x, y, NODOSIZE)) {

// dibuja una nueva arista

if (!newarista) {

newarista = true;

thispoint = new Point(x, y);

nodo1 = hitnodo;

}

}

else if ( !nodohit(x, y, 50) && !aristahit(x, y, 50) ) {

// dibuja nuevo nodo

if (emptyspots==0) {

// toma el siguiente punto disponible en el arreglo

if (numnodos < MAXNODOS)

nodo[numnodos++]=new Point(x, y);

else parent.documentacion.doctext.showline("maxnodos");

}

else {

// tomar un punto vacio en el array (de algun nodo borrado previamente)

int i;

for (i=0;i<numnodos;i++)

if (nodo[i].x==-100) break;

nodo[i]=new Point(x, y);

emptyspots--;

}

}

else

// mouseclick para cerrar a un point r flecha, probablemente un error

parent.documentacion.doctext.showline("toclose");

repaint();

}

return true;

}

public boolean mouseDrag(Event evt, int x, int y) {

if ( (!Locked) && clicked ) {

if (movenodo) {

// mover nodo y ajustar aristas entrando/saliendo del nodo

nodo[nodo1]=new Point(x, y);

for (int i=0;i<numnodos;i++) {

if (peso[i][nodo1]>0) {

aristaupdate(i, nodo1, peso[i][nodo1]);

}

if (peso[nodo1][i]>0) {

aristaupdate(nodo1, i, peso[nodo1][i]);

}

}

repaint();

}

else if (movestart || newarista) {

thispoint = new Point(x, y);

repaint();

}

else if (movearista) {

changepeso(x, y);

repaint();

}

}

return true;

}

public boolean mouseUp(Event evt, int x, int y) {

if ( (!Locked) && clicked ) {

if (movenodo) {

// mover el nodo si la nueva posicion no esta muy cerca a

// otro nodo o fuera del panel

nodo[nodo1]=new Point(0, 0);

if ( nodohit(x, y, 50) || (x<0) || (x>this.size().width) ||

(y<0) || (y>this.size().height) ) {

nodo[nodo1]=oldpoint;

parent.documentacion.doctext.showline("toclose");

}

else nodo[nodo1]=new Point(x, y);

for (int i=0;i<numnodos;i++) {

if (peso[i][nodo1]>0)

aristaupdate(i, nodo1, peso[i][nodo1]);

if (peso[nodo1][i]>0)

aristaupdate(nodo1, i, peso[nodo1][i]);

}

movenodo=false;

}

else if (deletenodo) {

nododelete();

deletenodo=false;

}

else if (newarista) {

newarista = false;

if (nodohit(x, y, NODOSIZE)) {

nodo2=hitnodo;

if (nodo1!=nodo2) {

aristaupdate(nodo1, nodo2, 50);

if (peso[nodo2][nodo1]>0) {

aristaupdate(nodo2, nodo1, peso[nodo2][nodo1]);

}

parent.documentacion.doctext.showline("cambiar pesos");

}

else System.out.println("zelfde");

}

}

else if (movearista) {

movearista = false;

if (peso[nodo1][nodo2]>0)

changepeso(x, y);

}

else if (movestart) {

// si la nueva posicion es un nodo, este nodo es el nodo_inicial

if (nodohit(x, y, NODOSIZE))

startgrafo=hitnodo;

colornodo[startgrafo]=Color.blue;

movestart=false;

}

repaint();

}

return true;

}

public boolean nodohit(int x, int y, int dist) {

// checa si hay click sobre un nodo

for (int i=0; i<numnodos; i++)

if ( (x-nodo[i].x)*(x-nodo[i].x) +

(y-nodo[i].y)*(y-nodo[i].y) < dist*dist ) {

hitnodo = i;

return true;

}

return false;

}

public boolean aristahit(int x, int y, int dist) {

// checa si hay click sobre una arista

for (int i=0; i<numnodos; i++)

for (int j=0; j<numnodos; j++) {

if ( ( peso[i][j]>0 ) &&

(Math.pow(x-arista[i][j].x, 2) +

Math.pow(y-arista[i][j].y, 2) < Math.pow(dist, 2) ) ) {

nodo1 = i;

nodo2 = j;

return true;

}

}

return false;

}

public void nododelete() {

// borra un nodo y las aristas que entran/salen del nodo

nodo[nodo1]=new Point(-100, -100);

for (int j=0;j<numnodos;j++) {

peso[nodo1][j]=0;

peso[j][nodo1]=0;

}

emptyspots++;

}

public void changepeso(int x, int y) {

// cambia el peso de una arista, cuando el usuario arrastra

// la flecha sobre la arista que conecta el nodo1 al nodo2.

// direccion de la arista

int diff_x = (int)(20*dir_x[nodo1][nodo2]);

int diff_y = (int)(20*dir_y[nodo1][nodo2]);

// dependiendo de la direccion de la arista , sigue el x, o el y

// del mouse mientras la flecha se arrastra

boolean siga_x=false;

if (Math.abs(endp[nodo1][nodo2].x-startp[nodo1][nodo2].x) >

Math.abs(endp[nodo1][nodo2].y-startp[nodo1][nodo2].y) ) {

siga_x = true;

}

// encuentra la nueva posicion de la flecha, y calcula

// el peso correspondiente

if (siga_x) {

int hbound = Math.max(startp[nodo1][nodo2].x,

endp[nodo1][nodo2].x)-Math.abs(diff_x);

int lbound = Math.min(startp[nodo1][nodo2].x,

endp[nodo1][nodo2].x)+Math.abs(diff_x);

// la arista debe quedarse entre los nodos

if (x<lbound) { arista[nodo1][nodo2].x=lbound; }

else if (x>hbound) { arista[nodo1][nodo2].x=hbound; }

else arista[nodo1][nodo2].x=x;

arista[nodo1][nodo2].y=

(arista[nodo1][nodo2].x-startp[nodo1][nodo2].x) *

(endp[nodo1][nodo2].y-startp[nodo1][nodo2].y)/

(endp[nodo1][nodo2].x-startp[nodo1][nodo2].x) +

startp[nodo1][nodo2].y;

// nuevo peso

peso[nodo1][nodo2]=

Math.abs(arista[nodo1][nodo2].x-startp[nodo1][nodo2].x-diff_x)*

100/(hbound-lbound);

}

// hacer lo mismo si sigue y

else {

int hbound = Math.max(startp[nodo1][nodo2].y,

endp[nodo1][nodo2].y)-Math.abs(diff_y);

int lbound = Math.min(startp[nodo1][nodo2].y,

endp[nodo1][nodo2].y)+Math.abs(diff_y);

if (y<lbound) { arista[nodo1][nodo2].y=lbound; }

else if (y>hbound) { arista[nodo1][nodo2].y=hbound; }

else arista[nodo1][nodo2].y=y;

arista[nodo1][nodo2].x=

(arista[nodo1][nodo2].y-startp[nodo1][nodo2].y) *

(endp[nodo1][nodo2].x-startp[nodo1][nodo2].x)/

(endp[nodo1][nodo2].y-startp[nodo1][nodo2].y) +

startp[nodo1][nodo2].x;

peso[nodo1][nodo2]=

Math.abs(arista[nodo1][nodo2].y-startp[nodo1][nodo2].y-diff_y)*

100/(hbound-lbound);

}

}

public void aristaupdate(int p1, int p2, int w) {

// hacer una arista nueva del nodo p1 al p2 con peso w, o cambiar

// el peso de la arista a w, calcular la

// posicion resultante de la flecha

int dx, dy;

float l;

peso[p1][p2]=w;

// linea de direccion entre p1 y p2

dx = nodo[p2].x-nodo[p1].x;

dy = nodo[p2].y-nodo[p1].y;

// distancia entre p1 y p2

l = (float)( Math.sqrt((float)(dx*dx + dy*dy)));

dir_x[p1][p2]=dx/l;

dir_y[p1][p2]=dy/l;

// calcular el start y endpoints de la arista,

// ajustar startpoints si tambien hay una arista de p2 a p1

if (peso[p2][p1]>0) {

startp[p1][p2] = new Point((int)(nodo[p1].x-5*dir_y[p1][p2]),

(int)(nodo[p1].y+5*dir_x[p1][p2]));

endp[p1][p2] = new Point((int)(nodo[p2].x-5*dir_y[p1][p2]),

(int)(nodo[p2].y+5*dir_x[p1][p2]));

}

else {

startp[p1][p2] = new Point(nodo[p1].x, nodo[p1].y);

endp[p1][p2] = new Point(nodo[p2].x, nodo[p2].y);

}

// la distancia de la flecha no es todo el camino a los puntos de inicio/final

int diff_x = (int)(Math.abs(20*dir_x[p1][p2]));

int diff_y = (int)(Math.abs(20*dir_y[p1][p2]));

// calcular nueva posicion en x de la flecha

if (startp[p1][p2].x>endp[p1][p2].x) {

arista[p1][p2] = new Point(endp[p1][p2].x + diff_x +

(Math.abs(endp[p1][p2].x-startp[p1][p2].x) - 2*diff_x )*

(100-w)/100 , 0);

}

else {

arista[p1][p2] = new Point(startp[p1][p2].x + diff_x +

(Math.abs(endp[p1][p2].x-startp[p1][p2].x) - 2*diff_x )*

w/100, 0);

}

// calcular nueva posicion en y de la flecha

if (startp[p1][p2].y>endp[p1][p2].y) {

arista[p1][p2].y=endp[p1][p2].y + diff_y +

(Math.abs(endp[p1][p2].y-startp[p1][p2].y) - 2*diff_y )*

(100-w)/100;

}

else {

arista[p1][p2].y=startp[p1][p2].y + diff_y +

(Math.abs(endp[p1][p2].y-startp[p1][p2].y) - 2*diff_y )*

w/100;

}

}

public String intToString(int i) {

char c=(char)((int)'a'+i);

return ""+c;

}

public final synchronized void update(Graphics g) {

// preparar nueva imagen fuera de la pantalla

Dimension d=size();

if ((offScreenImage == null) || (d.width != offScreenSize.width) ||

(d.height != offScreenSize.height)) {

offScreenImage = createImage(d.width, d.height);

offScreenSize = d;

offScreenGraphics = offScreenImage.getGraphics();

}

offScreenGraphics.setColor(Color.white);

offScreenGraphics.fillRect(0, 0, d.width, d.height);

paint(offScreenGraphics);

g.drawImage(offScreenImage, 0, 0, null);

}

public void drawarista(Graphics g, int i, int j) {

// dibuja arista entre nodo i y nodo j

int x1, x2, x3, y1, y2, y3;

// calcular flecha

x1= (int)(arista[i][j].x - 3*dir_x[i][j] + 6*dir_y[i][j]);

x2= (int)(arista[i][j].x - 3*dir_x[i][j] - 6*dir_y[i][j]);

x3= (int)(arista[i][j].x + 6*dir_x[i][j]);

y1= (int)(arista[i][j].y - 3*dir_y[i][j] - 6*dir_x[i][j]);

y2= (int)(arista[i][j].y - 3*dir_y[i][j] + 6*dir_x[i][j]);

y3= (int)(arista[i][j].y + 6*dir_y[i][j]);

int flecha_x[] = { x1, x2, x3, x1 };

int flecha_y[] = { y1, y2, y3, y1 };

// si la arista ya se escogio por el algoritmo cambiar color

if (algedge[i][j]) g.setColor(Color.orange);

// dibuja arista

g.drawLine(startp[i][j].x, startp[i][j].y, endp[i][j].x, endp[i][j].y);

g.fillPolygon(flecha_x, flecha_y, 4);

// escribe el peso de la arista en una posicion apropiada

int dx = (int)(Math.abs(7*dir_y[i][j]));

int dy = (int)(Math.abs(7*dir_x[i][j]));

String str = new String("" + peso[i][j]);

g.setColor(Color.black);

if ((startp[i][j].x>endp[i][j].x) && (startp[i][j].y>=endp[i][j].y))

g.drawString( str, arista[i][j].x + dx, arista[i][j].y - dy);

if ((startp[i][j].x>=endp[i][j].x) && (startp[i][j].y<endp[i][j].y))

g.drawString( str, arista[i][j].x - fmetrics.stringWidth(str) - dx ,

arista[i][j].y - dy);

if ((startp[i][j].x<endp[i][j].x) && (startp[i][j].y<=endp[i][j].y))

g.drawString( str, arista[i][j].x - fmetrics.stringWidth(str) ,

arista[i][j].y + fmetrics.getHeight());

if ((startp[i][j].x<=endp[i][j].x) && (startp[i][j].y>endp[i][j].y))

g.drawString( str, arista[i][j].x + dx,

arista[i][j].y + fmetrics.getHeight() );

}

public void detailsDijkstra(Graphics g, int i, int j) {

// checar que arista entre nodo i y nodo j esta cerca de la arista s para

//escoger durante este paso del algoritmo

// checar si el nodo j tiene la siguiente minima distancia al nodo_inicial

if ( (finaldist[i]!=-1) && (finaldist[j]==-1) ) {

g.setColor(Color.red);

if ( (dist[j]==-1) || (dist[j]>=(dist[i]+peso[i][j])) ) {

if ( (dist[i]+peso[i][j])<dist[j] ) {

changed[j]=true;

numchanged++;

}

dist[j] = dist[i]+peso[i][j];

colornodo[j]=Color.red;

if ( (mindist==0) || (dist[j]<mindist) ) {

mindist=dist[j];

minstart=i;

minend=j;

}

}

}

else g.setColor(Color.gray);

}

public void endstepDijkstra(Graphics g) {

// despliega distancias parcial y total de los nodos, ajusta la distancia final

// para el nodo que tuvo la minima distancia en este paso

// explica el algoritmo en el panel de documentacion

for (int i=0; i<numnodos; i++)

if ( (nodo[i].x>0) && (dist[i]!=-1) ) {

String str = new String(""+dist[i]);

g.drawString(str, nodo[i].x - (int)fmetrics.stringWidth(str)/2 -1,

nodo[i].y + h);

if (finaldist[i]==-1) {

neighbours++;

if (neighbours!=1)

showstring = showstring + ", ";

showstring = showstring + intToString(i) +"=" + dist[i];

}

}

showstring = showstring + ". ";

if ( (paso>1) && (numchanged>0) ) {

if (numchanged>1)

showstring = showstring + "Note que las distancias a ";

else showstring = showstring + "Note que la distancia a ";

for (int i=0; i<numnodos; i++)

if ( changed[i] )

showstring = showstring + intToString(i) +", ";

if (numchanged>1)

showstring = showstring + "han cambiado!\n";

else showstring = showstring + "ha cambiado!\n";

}

else showstring = showstring + " ";

if (neighbours>1) {

// si hay otros candidatos explicar porque se tomo este

showstring = showstring + "El nodo " + intToString(minend) +

" tiene la distancia minima.\n";

//checar sy hay otros caminos a minend.

int newcaminos=0;

for (int i=0; i<numnodos; i++)

if ( (nodo[i].x>0) && (peso[i][minend]>0) && ( finaldist[i] == -1 ) )

newcaminos++;

if (newcaminos>0)

showstring = showstring + "Cualquier otro camino a " + intToString(minend) +

" visita otro nodo de la red, y sera mas largo que " + mindist + ".\n";

else showstring = showstring +

"No hay otras aristas entrando a "+

intToString(minend) + ".\n";

}

else {

boolean morenodos=false;

for (int i=0; i<numnodos; i++)

if ( ( nodo[i].x>0 ) && ( finaldist[i] == -1 ) && ( peso[i][minend]>0 ) )

morenodos=true;

boolean bridge=false;

for (int i=0; i<numnodos; i++)

if ( ( nodo[i].x>0 ) && ( finaldist[i] == -1 ) && ( peso[minend][i]>0 ) )

bridge=true;

if ( morenodos && bridge )

showstring = showstring + "Dado que este nodo forma un 'puente' a "+

"los nodos restantes,\ncualquier otro camino a este nodo sera mas largo.\n";

else if ( morenodos && (!bridge) )

showstring = showstring + "Los nodos grises restantes no son alcanzables.\n";

else showstring = showstring + "No hay otras aristas entrando a "+

intToString(minend) + ".\n";

}

showstring = showstring + "Node " + intToString(minend) +

" sera coloreado naranja para indicar que " + mindist +

" es la longitud del camino mas corto a " + intToString(minend) +".";

parent.documentacion.doctext.showline(showstring);

}

public void detailsalg(Graphics g, int i, int j) {

// mas algoritmos pueden ser aniadidos

if (algoritmo==DIJKSTRA)

detailsDijkstra(g, i, j);

}

public void endstepalg(Graphics g) {

// mas algoritmos pueden ser aniadidos

if (algoritmo==DIJKSTRA)

endstepDijkstra(g);

if ( ( performalg ) && (mindist==0) ) {

if (algrthm != null) algrthm.stop();

int nalcanzable = 0;

for (int i=0; i<numnodos; i++)

if (finaldist[i] > 0)

nalcanzable++;

if (nalcanzable == 0)

parent.documentacion.doctext.showline("ninguno");

else if (nalcanzable< (numnodos-emptyspots-1))

parent.documentacion.doctext.showline("alguno");

else

parent.documentacion.doctext.showline("hecho");

}

}

public void paint(Graphics g) {

mindist=0;

minnodo=MAXNODOS;

minstart=MAXNODOS;

minend=MAXNODOS;

for(int i=0; i<MAXNODOS; i++)

changed[i]=false;

numchanged=0;

neighbours=0;

g.setFont(roman);

g.setColor(Color.black);

if (paso==1)

showstring="Algoritmo ejecutando: las aristas rojas apuntan a nodos alcanzables desde " +

" el nodo_inicial.\nLa distancia a: ";

else

showstring="Paso " + paso + ": Las aristas rojsa apuntan a nodos alcanzables desde " +

"nodos que ya tienen una distancia final ." +

"\nLa distancia a: ";

// dibuja una nueva arista en la posicion del mouse

if (newarista)

g.drawLine(nodo[nodo1].x, nodo[nodo1].y, thispoint.x, thispoint.y);

// dibuja todas las aristas

for (int i=0; i<numnodos; i++)

for (int j=0; j<numnodos; j++)

if (peso [i][j]>0) {

// si el algoritmo se esta ejecutando entonces hacer el siguiente paso para esta arista

if (performalg)

detailsalg(g, i, j);

drawarista(g, i, j);

}

// si la flecha ha sido arrastyrado a 0, dibujala, para que el usuario

//tenga la opcion de hacerla positiva de nuevo

if (movearista && peso[nodo1][nodo2]==0) {

drawarista(g, nodo1, nodo2);

g.drawLine(startp[nodo1][nodo2].x, startp[nodo1][nodo2].y,

endp[nodo1][nodo2].x, endp[nodo1][nodo2].y);

}

// dibuja los nodos

for (int i=0; i<numnodos; i++)

if (nodo[i].x>0) {

g.setColor(colornodo[i]);

g.fillOval(nodo[i].x-NODORADIX, nodo[i].y-NODORADIX,

NODOSIZE, NODOSIZE);

}

// refleja el nodo_inicial que se mueve

g.setColor(Color.blue);

if (movestart)

g.fillOval(thispoint.x-NODORADIX, thispoint.y-NODORADIX,

NODOSIZE, NODOSIZE);

g.setColor(Color.black);

// termina este paso del algoritmo

if (performalg) endstepalg(g);

// dibuja circulos negros alrededor de los nodos, escribe sus nombres a la pantalla

g.setFont(helvetica);

for (int i=0; i<numnodos; i++)

if (nodo[i].x>0) {

g.setColor(Color.black);

g.drawOval(nodo[i].x-NODORADIX, nodo[i].y-NODORADIX,

NODOSIZE, NODOSIZE);

g.setColor(Color.blue);

g.drawString(intToString(i), nodo[i].x-14, nodo[i].y-14);

}

}

}

ALGORITMO DE KRUSKAL

//********************************************/

/* Kruskal.java */

/* Copyright (C) 1997, 1998, 2000 K. Ikeda */

/********************************************/

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

import java.util.*;

import java.io.*;

import java.net.URL;

/*class Node {

int x;

int y;

int set;

int first;

int next;

int w;

int h;

String name;

}

//class Edge {

// int rndd_plus; /* initial vertex of this edge */

// int rndd_minus; /* terminal vertex of this edge */

// int len; /* length */

// int select;

// String name;

//}

public class Kruskal extends Applet implements MouseListener {

int n,m;

int num,den;

int u, usel, step;

Node v[] = new Node[100];

Edge e[] = new Edge[200];

int idx[] = new int[200];

int findNode(String name) {

for (int i=0; i<n; i++)

if (v[i].name.equals(name))

return i;

return -1;

}

void input_graph(InputStream is) throws IOException {

int x,y,l;

String s;

Reader r = new BufferedReader(new InputStreamReader(is));

StreamTokenizer st = new StreamTokenizer(r);

st.commentChar('#');

st.nextToken(); n = (int)st.nval;

st.nextToken(); m = (int)st.nval;

st.nextToken(); s = st.sval;

for (int i = 0; i<n; i++) {

Node node = new Node();

st.nextToken(); node.name = st.sval;

st.nextToken(); node.x = (int)st.nval;

st.nextToken(); node.y = (int)st.nval;

v[i] = node;

}

for (int i = 0; i<m; i++) {

Edge edge = new Edge();

st.nextToken(); edge.name = st.sval;

switch (st.nextToken()) {

case StreamTokenizer.TT_NUMBER:

edge.rndd_plus = (int)st.nval;

break;

case StreamTokenizer.TT_WORD:

edge.rndd_plus = findNode(st.sval);

break;

default:

break;

}

switch (st.nextToken()) {

case StreamTokenizer.TT_NUMBER:

edge.rndd_minus = (int)st.nval;

break;

case StreamTokenizer.TT_WORD:

edge.rndd_minus = findNode(st.sval);

break;

default:

break;

}

st.nextToken(); edge.len = (int)st.nval;

e[i] = edge;

}

for (int i=0; i<m; i++)

e[i].select = -1;

den = 0;

num = 128;

for (int i=0; i<m; i++)

if (e[i].len>den)

den = e[i].len;

step1();

Krsub p = (Krsub)getAppletContext().getApplet("krsub");

if (p!=null)

p.set(1,n,m,num,den,v,e,idx);

step = 2;

}

void swap(int i, int j) {

int k = idx[i];

idx[i] = idx[j];

idx[j] = k;

}

int partition(int left, int right) {

int pivot = e[idx[(int)((left+right)/2)]].len;

while (left<=right) {

while (e[idx[left]].len < pivot)

left++;

while (e[idx[right]].len > pivot)

right--;

if (left <= right)

swap(left++,right--);

}

return left;

}

void qsort(int left, int right) {

int i;

if (left >= right)

return;

i = partition(left,right);

qsort(left,i-1);

qsort(i,right);

}

void step1() { /* initialize */

for (int i=0; i<m; i++)

idx[i] = i;

for (int i=0; i<m; i++)

e[i].select = -1;

qsort(0,m-1);

for (int i=0; i<m; i++)

e[i].select = -1;

for (int i=0; i<n; i++) {

v[i].set = i;

v[i].first = i;

v[i].next = -1;

}

usel = u = 0;

}

void step2() { /* select the shortest edge */

e[idx[u]].select = 1; /* pick up the edge */

}

void step3() { /* check the loop */

int vl = e[idx[u]].rndd_plus;

int vr = e[idx[u]].rndd_minus;

int i,j,k;

if (v[vl].set == v[vr].set) {

e[idx[u++]].select = -2; /* de-select the edge */

return;

}

usel ++;

e[idx[u++]].select = 2; /* select the edge */

for (i = vl; v[i].next>=0; i = v[i].next)

;

v[i].next = v[vr].first;

j = v[vl].first;

k = v[vl].set;

for (i = v[vr].first; i>=0; i = v[i].next) {

v[i].first = j;

v[i].set = k;

}

}

void step4() {

for (; u<m; u++)

e[idx[u]].select = -2;

}

public void init() {

String mdname = getParameter("inputfile");

try {

InputStream is;

is = new URL(getDocumentBase(),mdname).openStream();

input_graph(is);

try {

if (is != null)

is.close();

} catch(Exception e) {

}

} catch (FileNotFoundException e) {

System.err.println("File not found.");

} catch (IOException e) {

System.err.println("Cannot access file.");

}

setBackground(Color.white);

addMouseListener(this);

}

public void paintNode(Graphics g, Node n, FontMetrics fm) {

String s;

int x = n.x;

int y = n.y;

int w = fm.stringWidth(n.name) + 10;

int h = fm.getHeight() + 4;

n.w = w;

n.h = h;

g.setColor(Color.black);

g.drawRect(x-w/2,y-h/2,w,h);

g.setColor(getBackground());

g.fillRect(x-w/2+1,y-h/2+1,w-1,h-1);

g.setColor(Color.black);

g.drawString(n.name,x-(w-10)/2,(y-(h-4)/2)+fm.getAscent());

}

int [] xy(int a, int b, int w, int h) {

int x[] = new int[2];

if (Math.abs(w*b)>=Math.abs(h*a)) {

x[0] = ((b>=0)?1:-1)*a*h/b/2;

x[1] = ((b>=0)?1:-1)*h/2;

} else {

x[0] = ((a>=0)?1:-1)*w/2;

x[1] = ((a>=0)?1:-1)*b*w/a/2;

}

return x;

}

void drawEdge(Graphics g,int x1,int y1,int x2,int y2) {

g.drawLine(x1,y1,x2,y2);

}

public void paintEdge(Graphics g, Edge e, FontMetrics fm) {

Node v1 = v[e.rndd_plus];

Node v2 = v[e.rndd_minus];

int a = v1.x-v2.x;

int b = v1.y-v2.y;

int x1[] = xy(-a,-b,v1.w,v1.h);

int x2[] = xy(a,b,v2.w,v2.h);

if (e.select == -1 )

g.setColor(Color.black);

else if (e.select == -2)

g.setColor(Color.lightGray);

else if (e.select == 1)

g.setColor(Color.orange);

else

g.setColor(Color.red);

drawEdge(g,v1.x+x1[0],v1.y+x1[1],v2.x+x2[0],v2.y+x2[1]);

int w = fm.stringWidth("" + e.len);

int h = fm.getHeight();

g.setColor(getBackground());

g.fillRect((v1.x+v2.x-w)/2,(v1.y+v2.y-h)/2,w,h);

if (e.select == -1 )

g.setColor(Color.black);

else if (e.select == -2)

g.setColor(Color.lightGray);

else if (e.select == 1)

g.setColor(Color.orange);

else

g.setColor(Color.red);

g.drawString("" + e.len,(v1.x+v2.x-w)/2,(v1.y+v2.y-h)/2+fm.getAscent());

}

public void paint(Graphics g) {

FontMetrics fm = g.getFontMetrics();

for (int i=0; i<n; i++)

paintNode(g,v[i],fm);

for (int i=0; i<m; i++)

paintEdge(g,e[i],fm);

Krsub p = (Krsub)getAppletContext().getApplet("krsub");

if (p!=null)

p.set(1,n,m,num,den,v,e,idx);

}

public void update(Graphics g) {

paint(g);

}

public void mousePressed(MouseEvent e) {

if (step == 1) {

step1();

step = 2;

} else if (usel >= n-1) {

step4();

step = 1;

} else {

if (step == 3) {

step3();

step = 2;

} else {

step2();

step = 3;

}

}

repaint();

}

public void mouseClicked(MouseEvent event) {}

public void mouseReleased(MouseEvent event) {}

public void mouseEntered(MouseEvent event) {}

public void mouseExited(MouseEvent event) {}

}

El conjunto de aristas T está vacío inicialmente. A medida que progresa el algoritmo, se van añadiendo aristas a T. Mientras no haya encontrado una solución, el grafo parcial formado por los nodos de G y las aristas de T consta de varias componentes conexas.(inicialmente, cuando T está vacío, cada nodo de G forma una componente conexa distinta trivial). Los elementos de T que se incluyen en una componente conexa dada forman un árbol de recubrimiento mínimo para los nodos de este componente. Al final del algoritmo, solo queda una componente conexa, así que T es un árbol de recubrimiento mínimo para todos los nodos de G. Para construir componentes conexas más y más grandes, examinamos las aristas de G por orden creciente de longitudes. Si una arista une dos nodos de componentes conexas distintas, se lo añadimos a T. Consiguientemente, las dos componentes conexas forman ahora una única componente. En caso contrario, se rechaza la arista: une a dos nodos de la misma componente conexa, y por tanto no se puede añadir a T sin formar un ciclo (porque las aristas de T forman un árbol para cada componente). El algoritmo se detiene cuando sólo queda una componente conexa.




Descargar
Enviado por:Yoli
Idioma: castellano
País: Colombia

Te va a interesar