lunes, 22 de noviembre de 2010

.: Listas Vinculadas :.

:: Listas Enlazadas y sus Procesos ::



Materia. Lenguajes de Programación
Hora. M1 - M3 (Martes)

Aquí me dedicaré a comentar algo sobre el trabajo o las acciones realizadas con el proyecto de listas que use el semestre pasado ya modificado.
Primero comentaremos las variables presentes es la estructura de la lista:
  • ID. Funciona como la matricula del alumno.
  • Name. Es el nombre del alumno.
  • Facu. Almacena el nombre de la facultad del alumno.
  • Carr. Contiene el nombre de la carrera que cursa el alumno.
  • Sem. Variable que contiene el semestre que cursa el alumno en ese momento.
Definición de la Estructura.
Obviamente, primero se define lo que es la estructura de tipo NODO para hacer referencia a cada nodo presente en la lista.
#define TAM 80
typedef struct nodo {
  int ID,Sem;
  char Name[TAM];
  char Facu[TAM];
  char Carr[TAM];

  struct nodo *next; ---->> Punto para enlace al nodo anterior
  struct nodo *prev; ---->> Punto para enlace al nodo siguiente
} NODO;
Ingreso de Registros Nuevos: Nodos Nuevos
Para este proceso, que es el principal, tenemos la función CrearNodo donde usamos un puntero Q que se posiciona en la parte inicial de la lista.

NODO *CrearNodo(NODO *Q, NODO *list){
  NODO *P, *R;
  int x;
Ya para este paso, habíamos asignado al primer ID de la lista el valor de cero (0) con el propósito de usarlo como base para la organización de la lista.
Nosotros tenemos el conocimiento de que los elementos en una lista enlazada se van agregando como se van creando. En el caso de este proyecto no es asi, pues es ahí donde entra el valor cero del primer nodo de la lista.
Al querer crear un nodo nuevo e ingresar el ID de esa nodo, nos topamos después con la siguiente instrucción:
while (x > Q->ID) {
         if (Q->next != NULL) {
            Q = Q->next;
         } else if (Q->next == NULL) {
             break;
         }
}
La cual va a estar operando mientras el ID nuevo que se ingresa sea mayor al último ID presente en la lista enlazada.
La manera en que ira iterando este WHILE es con la ayuda del IF-ELSE, pues éste me dice que si el siguiente elemento es diferente de un valor NULO, se pasará al valor del ID del siguiente nodo y cuando llegue el momento en que el siguiente nodo sea un valor NULO o que el ID nuevo que ingresamos ya no sea mayor al ID que se está tomando de base, el WHILE se va a quebrar o invalidar.
Todo este rollo es sólo para posicionarnos en la parte donde vamos a crear el nodo en la lista enlazada.
Ahora bien, para ya formalizar la creación de nuestro nodo y capturar todas las variables, tenemos el siguiente IF-ELSE:
if (x > Q->ID) {
    P = (NODO*)malloc(sizeof(NODO));  ---->> Reserva de memoria
    P->ID = x;
        
    captura(P); ---->> Función para la captura de la información
        
    P->next = NULL;
    P->prev = Q;
    Q->next = P;
    Q = P;
    } else {
      P = (NODO*)malloc(sizeof(NODO));              
      R = Q;
      Q = Q->prev;
      P->ID = x;
          
      captura(P);
           
      P->prev = Q;
      P->next = R;
      R->prev = P;
      Q->next = P;
      Q = P;
    }
Aquí ya definimos oficialmente como va a quedar lo que es la posición completa del nodo que se va a crear: lectura de información, la reserva de memoria para ese nodo, la lectura de todas las variables y las secciones destinadas para apuntar al nodo anterior y el siguiente.
Propósito del orden de la lista.
El propósito para que hayamos ordenado nuestra lista enlazada era para el uso apropiado de lo que era la función Búsqueda, Modificación de Registros, Eliminación de Nodos.
Todo parte desde la búsqueda, para la cual tenemos las siguientes instrucciones:
while (dato != 0) {
     if ((Q->ID > dato) || (Q->next == NULL) && (Q->ID < dato)) {
          return NULL;
     } else if (Q->ID == dato) {
           
          imprime(Q); --->> Función para imprimir el registro
                 
          switch (proceso) {
               case 1: {       //FUNCION DE BUSQUEDA
                        return;
                }
               case 2: {       //FUNCION DE MODIFICAR
                        modify(Q, dato);
                        return;
                }
                case 3: {       //FUNCION PARA ELIMINAR
                         Borrar(Q, dato);     
                         return;
                }
          }
     } else if (dato != Q->ID) {
          Q = Q->next;
     }
}
Primeramente comento que ya desde la función main se le envio a esta función lo que es el valor de ID que estamos tratando de localizar en la lista enlazada.
Tenemos la primer condición donde si el ID que se está analizando es mayor al dato buscado o si es dicha condición junto con el hecho de que el siguiente nodo es un valor NULO, regresará un valor falso, el cual sirve de base para decir que el elemento que se busca no existe.
De lo contrario, si no pasa lo antes mencionado, solo nos pasamos al siguiente nodo para seguir comparando.
Una vez que el ID  que buscamos sea el mismo que el ID del nodo donde nos ubicamos en ese instante, se imprimen los datos correspondientes y luego ya el programa sabrá si es una buscado, eliminación o modificación.
¿Y el programa como sabrá que hacer en este paso?
En lo que es la función main estamos usando un switch para cada acción que podemos realizar con la lista. Dependiendo del proceso que hayamos elegido desde main, también se envía a nuestra función que realiza las búsquedas  en la lista, una especia de dato predefinido que te indica que tipo proceso elegir.
Función Búsqueda
No tiene gracia. Su dato predefinido para realizar esta acción es el 1, por lo que al imprimir el registro localizado, sólo nos regresamos a main y no pasa nada más.
Función Modificar
Tenemos la siguiente función para esta acción:
NODO *modify(NODO *Q, int modify) {
int op;
  
     printf("\n\t1. Nombre");
     printf("\n\t2. Facultad");
     printf("\n\t3. Carrera");
     printf("\n\t4. Semestre");
     printf("\n\n\tModificar---> ");
     scanf("%d", &op);
      
     switch(op){
        case 1:{
             lectura("Nombre Nuevo: ", Q->Name); --->> Función de
                                                       captura
              break;
       }
        case 2:{
             lectura("\nFacultad Nueva: ", Q->Facu);
              break;
        }
        case 3:{
             lectura("\nCarrera Nueva: ", Q->Carr);
              break;
        }
        case 4:{
              printf("\nSemestre Nuevo: ");
              scanf(" %d", &(Q->Sem));
              break;
        }
        default:{
              printf("\nEdicion NO Autorizada.\n");
              break;
        }
     }

return;
}
Aquí lo principal es mandar desde nuestra función selectiva de procesos la dirección de memoria y el ID del nodo que hemos localizado para poder modificarlo; es nada más de sobre escribir el dato o la variable que deseemos utilizando la dirección de memoria ya ubicada.
Función Eliminar
Al igual que en la función de modificar, desde la función selectiva de procesos mandamos la dirección de memoria y el ID del registro o nodo localizado a la función para depurar los nodos:
void Borrar(NODO *Q, int borrar) {
   char answer;
   NODO *ant, *aux;
  
   printf("\n\nEliminar Registro [Y/N]: ");
   scanf("%s", &answer);
               
   if (answer == 'Y' || answer == 'y') {
              aux = Q;
              ant = Q->prev;
                          
              if (aux->next == NULL) {
                     ant->next = NULL;
                                         
                     free(aux); --->> Liberar espacio en memoria
                                      de aux
                                        
                     printf("\n\n----->> Registro eliminado\n");
                    
                     return;
              } else {
                     ant->next = aux->next;
                     Q = Q->next;
                     Q->prev = ant;
                                 
                     free(aux);
                                 
                     printf("\n\n----->> Registro eliminado\n");
                    
                     return;
              }
   } else {
              printf("\n\n----->> Registro NO eliminado\n");
             
              return;
   }
}
Aquí es donde se me hizo más interesante.
Nosotros tenemos dos casos: cuando el nodo a eliminar está al final de la lista o en una parte media.
Para ambos casos necesitamos dos punteros de tipo NODO: ant y aux.
En el caso de que el nodo a eliminar se encuentre al final de la lista tenemos las operaciones:
aux = Q; ------------------->>> Paso 1
ant = Q->prev; ------------->>> Paso 2
                          
if (aux->next == NULL) { --->>> Elemento a eliminar es el último
ant->next = NULL; ---->>> Paso 3
                                    
              free(aux); -->>> Paso 4
}
De manera gráfica sería lo siguiente:
Paso 1:
Paso 2:
Paso 3:
Paso 4:
En el caso de que el nodo a eliminar se encuentra en la parte media sería con los siguientes pasos:
aux = Q; ---------------------------->>> Paso 1
ant = Q->prev; ---------------------->>> Paso 2
Agregamos la parte del else del if
ant->next = aux->next; -------------->>> Paso 3
Q = Q->next; ------------------------>>> Paso 4
Q->prev = ant; ---------------------->>> Paso 5
                                 
free(aux); -------------------------->>> Paso 6
En dibujito sería:
Paso 1:
Paso 2:
Paso 3:
Paso 4:
Paso 5:
Paso 6:
A grandes rasgos esos son los procesos que realiza este programa.
Saludos a todos.

.: Punteros: Parte I :.

:: Punteros: Procesos Básicos ::



Materia. Lab de Leng de Progr
Hora. V1 (Jueves)

Hola de nuevos compañeros; en las próximas entradas estaremos recordando cosas del semestre pasado.
Primeramente comenzare por hablar de los punteros.
Definición.
Antes de explicar en forma práctica cómo funcionan los punteros vamos a ver la definición.
Definimos un puntero o apuntador como una variable que únicamente hace referencia a una región de memoria. Podemos verlo también como una variable cuyo valor es una dirección de memoria a la cual está señalando.
Generalmente los punteros se usan al momento de que nosotros creamos nuestras propias estructuras de datos, se usan en pilas, colas, listas, grafos, árboles, etc.
En los “Arrays” también son utilizados, pero de una manera más implícita. De hecho yo diría que más que apuntadores que nosotros declaramos, son sólo índices van junto con loa “arrays” al declararlos para poder estar accesando a cada elemento del arreglo, pero eso es otra historia.
Caso Práctico
Sé que el siguiente programa no es ninguna estructura de datos, pero el objetivo de esta entrada es demostrar cómo funcionan los apuntadores o punteros.
Declaro que me estaré basando en un programa creado en cierta clase de Estructura de Datos a la cual me “cole” el semestre pasado.
Primeramente, nosotros declaramos las siguientes variables enteras:
int x = 3, y = 7, z[5] = {2,4,6,8,10};
Pero además, nosotros declarmos nuestro puntero P de tipo entero. Menciono que los punteros se declaran como cualquier variable, con la diferencia de que se le antepone un asterisco (*).
int *P;
Si mandamos a imprimir nuestras variables enteras, tendremos el siguiente resultado (línea 1):
Como pueden observar no es gran ciencia, sólo se imprimen las variables antes declaradas.
Ahora, veremos las siguientes instrucciones:
P = &x; ----->> Aquí definimos que el puntero tendrá el valor de
                la dirección de memoria que tiene “x”
y = *P;  ----->> Ahora decimos que “y” tendrá el valor que posee
                 nuestro apuntador.
*P = 1;  ----->> En esta parte decimos que “P” tendrá el valor de
                 “1”.
Al imprimir resultados tendremos (ver línea dos):
Como pueden ver, sólo se le asignó el valor de la dirección de memoria a la que señalaba el apuntador a la variable y, no es que el puntero se haya movido a la variable y.
Como P sigue posicionado en la dirección de x, al momento de alterar el valor del apuntador, lo que se altero fue el valor de la dirección de memoria, que es en sí el valor de x.
Nuevamente tenemos los siguientes cambios:
P = &z[2]; ----->> Aquí ya cambiamos la dirección de memoria del
                   puntero, pues ya los colocamos sobre la
                   dirección del elemento “2” del arreglo “z”.
y = *P; ----->> Le asignamos a “y”  el valor de la dirección
                actual del puntero.
*P = 15 --->> Alteramos el valor de la dirección de memoria del
              puntero.
Veamos cómo se verá esto (línea 3):
Recuerden que P se encuentra sobre z[2]. Al asignar a y el valor de P, se le asigna el valor del segundo elemento del arreglo, y al alterar el valor del puntero, alteramos el valor del elemento 2 de nuestro arreglo.
Veamos ahora que pasa con lo siguiente:
x = *P + 5; ----->> A “x” se le asigna el valor de la dirección
                    del memoria añadiendo “5”
*P = *P - 5; ----->> La dirección de memoria al que se señala
                     actualmente se le resta el valor de “5”.
Estas operaciones serían así (línea 4):

Recordemos que P aún está sobre z[2]; por lo tanto, al asignar a x el valor actual de P + 5 es lo mismo que asignarle 15 + 5 = 20, como lo pueden apreciar. Pero observen que el valor del puntero no se esta alterando.
Cuando llega a P – 5, aquí si estamos alterando el valor de P y por consiguiente el valor de la dirección de memoria que viene siendo la variable z[2] = 15 -5 = 10.
Pasemos a las siguientes operaciones:
++*P; ----->> Incrementamos en una unidad el valor de la
              dirección actual de nuestro apuntador
 *P += 1; ----->> Nuevamente incrementamos el valor de nuestro
                  puntero en una unidad.
De modo que tendremos el siguiente resultado (línea 5):
Hagamos recordatorio que P aún está en la dirección de memoria de z[2] = 10; por lo que al aumentar dos veces en una unidad el valor del puntero, es lo mismo que tener 10 + 1 + 1 = 12, como pueden verlo.
Nuevamente, haciendo los siguientes cambios:
x = *(P + 1) ----->> Movemos el apuntador una posición hacía
                     adelante.
y = *P; ----->> Asignamos el valor actual del puntero a la
                variable “y”.
Al imprimir tendremos (línea 6):
No se confundan, escribir “x = *P + 1” es sólo sumar “1” al valor del puntero, pero en sí, colocar “P + 1”, estamos incrementando en una unidad la dirección de memoria, NO SU VALOR; es decir, la estamos moviendo una posición hacía adelante para asignarle ese valor a x.
Pero OJO, no hemos indicado que P se posicione en la dirección de memoria de z[3]. Para eso tendríamos que poner P = &z[3], lo cual no hemos hecho. Aún el puntero está sobre z[2] = 12.
Si se dan cuenta, el & hace la función como de asignación de dirección de memoria para que los punteros se sitúen en ella.
En fin, pasemos a lo siguiente (línea 7):
P = P + 1; ----->> Volvemos a mover una posición hacía adelante
                   la dirección de memoria de “P”
y = *P; ----->> Asignamos el valor de “P” a la variable “y”.
Y los resultados son…
Haciendo énfasis nuevamente, P sigue situado en la posición z[2] = 12. Nuevamente movimos una posición la dirección del puntero en una unidad; es decir, movimos el puntero a z[3] = 8 con el único propósito de asignarle ese valor a la variable y. INSISTO, P NO está guardando la dirección de memoria de z[3].
Continuamos:
P = P + 4; ----->> Movemos “P” cuatro posiciones en el arreglo
                   hacia
adelante
 y = *P; ----->> Asignamos el valor de la posición ahora señalada
                 a la variable
“y
”.
Mostramos en pantalla (línea 8):
Va de nuevo: EL PUNTERO SIGUE SOBRE Z[2] = 12. Otra vez movimos cuatro posiciones hacía adelante en el arreglo para asignar el valor señalado a y.
Como podemos ver y = 152 (?).
¿Pero qué paso? ¿De dónde “$%&!!#/%$” salió ese 152?
Fácil. P estaba situado en la posición 2 de nuestro arreglo de tamaño 5. Al movernos cuatro posiciones, debimos llegar a la posición 6 del arreglo, la cual no existe.
¿Pero entonces de dónde obtuvo P ese 152? Podemos decir que es sólo un valor “basura” que estaba volando por ahí, o un valor extraviado sin ubicación fija. O podemos verlo como un valor imaginario creado por generación espontánea por parte de nuestro sistema operativo. O quizás quiere surgir SKYNET de nuestra computadora y genero este valor extraño de dudosa procedencia. (De acuerdo, ya me aloque. Veo muchas películas de repente de acuerdo. Tomen en cuenta que estoy un poco cansado).
Por si ya se dieron cuenta, estoy ejecutando este código en Windows 7, así que tal vez quieran echarle la culpa a Windows, ¿quién sabe? Si no estoy en Linux es porque perdí el grub y no puedo accesar a UBUNTU.
Pero en fin, hasta para mí es una incógnita los orígenes  reales de este valor. Si alguien tiene una fuente más cuerda de éste origen, por favor compártala.
Prosiguiendo con el código, veamos por último lo siguiente (línea 9):
P = &x; ----->> Ahora cambiamos la posición de “P”. Nuevamente
                colocamos el puntero en la dirección de memoria
                de “x”
P = P + 1; ----->> Movemos una posición en la dirección de
                   memoria a “P”.
x = *P; ------>> Asignamos el valor ahora señalado a “x”.
En pantalla tendremos (línea 9):
¿Ahora qué sucedió? Bueno, tengo dos hipótesis para esto.
Primero notemos que ahora que P se sitúo sobre la dirección de memoria de x, tenemos que P se movió de posición en una unidad para asignar el valor señalado a x.
Yo veo dos posibilidades:
1.       De nuevo se salió a un sitio que no existe como nuestro caso pasado en el arreglo colocando un valor “basura” a x que curiosamente es el mismo que tenía en un principio.
2.       Como x no es un arreglo, ni ninguna estructura de datos, en realidad el puntero no se movió a ninguna parte y por eso asigno el mismo valor que tenía x a la dirección de memoria donde se encontraba.
La verdad es que en este punto, el tema se me convirtió en un misterio para mí mismo.
Espero que les haya servido esta información. A continuación les dejo el código de este programa más abajo.
Cualquier comentario es bienvenido (y vaya que siento que deje abierto para varios comentarios o eso creo).
Saludos a todos.