Thursday, July 14, 2011

Contenido extra: Tablas de dispersión



Una tabla (hash) o de dispersiones es una estructura de datos que asocia llaves o claves con valores.
Las tablas de dispersión o hashing tables (en inglés) es una técnica que se utiliza para implementar inserciones, eliminaciones y búsquedas en un tiempo medio constante.
La operación principal que soporta de manera eficiente es la búsqueda: permite el acceso a los elementos (teléfono y dirección, por ejemplo) almacenados a partir de una clave generada (usando el nombre o número de cuenta, por ejemplo). Funciona transformando la clave con una función hashen un hash, un número que la tabla hash utiliza para localizar el valor deseado.
Las tablas hash se suelen implementar sobre vectores de una dimensión, aunque se pueden hacer implementaciones multi-dimensionales basadas en varias claves. Como en el caso de los arrays, las tablas hash proveen tiempo constante de búsqueda promedio O(1),1 sin importar el número de elementos en la tabla. Sin embargo, en casos particularmente malos el tiempo de búsqueda puede llegar a O(n), es decir, en función del número de elementos.
Comparada con otras estructuras de arrays asociadas, las tablas hash son más útiles cuando se almacenan grandes cantidades de información.
Las tablas hash almacenan la información en posiciones pseudo-aleatorias, así que el acceso ordenado a su contenido es bastante lento. Otras estructuras como árboles binarios auto-balanceables son más rápidos en promedio (tiempo de búsqueda O(log n)) pero la información está ordenada en todo momento.

Una estructura hash se construye con tres elementos básicos :
•            Un vector direccionable mediante número de posición capaz de almacenar N elementos.
•            Una función de dispersión que nos permita a partir de la clave obtener el índice donde estará el dato asociado a esa clave.
•            Una función de resolución de colisiones.

BIBLIOGRAFIA

Contenido extra. Listas enlazadas



Una lista enlazada es una serie de objetos que “saben" dónde se encuentra el siguiente miembro de la lista en la memoria del ordenador. El último miembro de la lista suele indicar que el miembro siguiente es un "null". Para lograrlo, cada objeto de la lista debe disponer de un miembro que pueda almacenar la ubicación en la memoria del siguiente objeto de la lista. Este tipo de listas son listas enlazadas simples, ya que el código puede desplazarse en la lista en una sola dirección. También existen listas enlazadas dobles. Una referencia al objeto de la lista suele indicar el comienzo de la misma.

Las listas enlazadas permiten almacenar información en posiciones de memoria que no sean contiguas. Estas listas para almacenar la información, contienen elementos llamados nodos. Estos nodos poseen dos campos uno para almacenar la información o valor del elemento y otro para el enlace que determina la posición del siguiente elemento o nodo de la lista.

Para inserta o borrar información no es necesario realizar un desplazamiento, para esto las listas cuentan con punteros o enlaces que contienen la posición o dirección del otro nodo de la lista. Por esta razón no es necesario que los elementos de la lista se almacenen en posiciones contiguas.

Cuando en una lista enlazada no hay ningún elemento quiere decir que la lista esta vacía, además existe un puntero de cabecera para acceder al primer nodo de la lista y un puntero nulo para determinar el ultimo elemento (nodo) de la lista.

Cuando utilizamos listas enlazadas podemos realizar la siguientes operaciones:
● Podemos añadir información a la lista insertando un nuevo nodo en un determinado lugar dentro de la lista.
● Podemos eliminar un nodo especifico dentro de la lista que contenga información.
● Podemos recuperar la información almacenada en un nodo especifico o encontrar la posición de un determinado nodo que contenga alguna información especifica.

Tipos de listas enlazadas
·         Listas simples enlazadas: Tiene un enlace por nodo. Este enlace apunta al siguiente nodo en la lista, o al valor Nulo o lista Vacia, si es el último nodo.


·         Listas doblemente enlazadas: Cada nodo tiene dos enlaces: uno apunta al nodo anterior, o apunta al valor Nulo o la lista vacia si es el primer nodo; y otro que apunta al siguiente nodo, o apunta al valor Nulo o la lista vacia si es el último nodo.



·         Listas enlazadas circulares: En una lista enlazada circular, el primer y el último nodo están unidos. Para recorrer una lista enlazada circular podemos empezar por cualquier nodo y seguir la lista en cualquier dirección hasta que se regrese al nodo original.



·         Listas de listas: El campo de datos de un nodo puede ser otra lista enlazada.



BIBLIOGRAFIA

Tarea 6. Arboles

Hola. Lo que modifique realemente fue muy sencillo y les mencionare que hice: Aqui deje el codigo que se subio de la ultima clase, el de arboles.c, recibi una asesoria mas, que me ayudó a comprender un poco mas este programa. Cuando nosotros ejecutamos el programa en clase nos manejaba que se ordenaban los datos que se estanban ingresando y los acomodaba de izquierda a derecha, deje como comentario lo que estaba y copie lo mismo abajo, para solo cambiarle la forma en que los acomoda y a la hora de ejecutar el programa lo que hice fue acomodar de derecha a izquierda, segun lo que me mencionaron en la asesoria es que a la hora de ejecutar el programa lo unico que se muestra son los valores de la hojas y que la raiz no, queda de algun modo oculto, que nos los muestra. Quisiera modificar algo mas pero aun necesito entender mas el programa.Aun no encuentro el modo para eliminar.


#include 
#include 
#include 

//#define DEBUG 1

// NOTA: faltan implementar las rotaciones
// dobles; por ahora no balancea bien

// un nodo de ruteo no cuenta duplicados
#define NO_VALIDO -1

#ifdef DEBUG
  int next = 0;
#endif

struct nodo_de_arbol {
  char dato;
  int altura;
  int contador;
#ifdef DEBUG
  int id;
#endif
  struct nodo_de_arbol* padre;
  struct nodo_de_arbol* izq; // hijo
  struct nodo_de_arbol* der; // otro hijo
};
typedef struct nodo_de_arbol nodo;

void borra(nodo* n) {
  if (n == NULL) {
    return;
  } else {
    borra(n->izq);
    borra(n->der);
    free(n);
  }
  return;
}

/*
void muestra(nodo* n) {
  if (n == NULL) {
    return; // caso base de recursion
  } else {
    muestra(n->izq);
#ifdef DEBUG
    if (n->izq != NULL && n->der != NULL) {
      printf("[%c, %d <%d / %d, %d>] ", 
      n->dato, n->altura, n->id,
      n->izq->id, n->der->id);
    } else {
      printf("(%c, %d <%d>) ", 
      n->dato, n->altura, n->id);
    }
#else
    if (n->izq == NULL && n->der == NULL) {
      printf("%c", n->dato);
    }
#endif
    muestra(n->der);
  }
  return;
}
*/


void muestra(nodo* n) {
  if (n == NULL) {
    return; // caso base de recursion
  } else {
    muestra(n->der);
#ifdef DEBUG
    if (n->izq != NULL && n->der != NULL) {
      printf("[%c, %d <%d / %d, %d>] ", 
      n->dato, n->altura, n->id,
      n->izq->id, n->der->id);
    } else {
      printf("(%c, %d <%d>) ", 
      n->dato, n->altura, n->id);
    }
#else
    if (n->izq == NULL && n->der == NULL) {
      printf("%c", n->dato);
    }
#endif
    muestra(n->izq);
  }
  return;
}

void balancea(nodo* n, nodo** raiz) {
  int ai, ad, max, min, aalt, balt;
  nodo* t = NULL;
  nodo* u = NULL;
  nodo* v = NULL;
  nodo* a = NULL;
  nodo* b = NULL;
  nodo* p = NULL;

  if (n == NULL) {
#ifdef DEBUG
    printf("Ya topamos con la raiz.\n");
#endif
    return;
  }
  if (n->izq == NULL ||
      n->der == NULL) {
#ifdef DEBUG
    printf("No se puede sin hijos.\n");
#endif
    return;
  }

  t = n; // nodo actual
  assert(t != NULL);

#ifdef DEBUG
  printf("Checando balance en %c (alt. %d).\n",
  t->dato, t->altura);
#endif


  u = n->izq; // hijo izquierdo
  assert(u != NULL);
  v = n->der; // hijo derecho
  assert(v != NULL);

  ai = u->altura;
  ad = v->altura;

#ifdef DEBUG
  printf("Hijos %c (alt. %d) y %c (alt. %d).\n",
  u->dato, ai, v->dato, ad);
#endif

  max = (ai > ad) ? ai : ad;
  min = (ai > ad) ? ad : ai;

  if (max + 1 != t->altura) {
    t->altura = max + 1; // actualizar altura
  }

  if (max - min > 1) { // balancear
    p = n->padre; // padre del actual

#ifdef DEBUG
    printf("%c a altura %d requiere balanceo.\n", 
    n->dato, n->altura);
    printf("Entrando tenemos:\n");
    if (p != NULL) {
      printf("p = %d, ", p->id);
    } else {
      printf("p = NULL, ");
    }
    printf("t = %d, \n", t->id);
    printf("u = %d, v = %d, \n", 
    u->id, v->id);
#endif
    
    if (ai <= ad - 2) { 
      // si hay demasiada altura a la der
      // => voltear "peso" hacia la izq.
      a = v->izq;
      b = v->der;
      assert(a != NULL);
      assert(b != NULL);
      aalt = a->altura;
      balt = b->altura;
      
#ifdef DEBUG
      printf("a = %d, b = %d, \n", 
      a->id, b->id);
#endif

      if (aalt <= balt) { // rotacion simple izq
 // printf("Rot. simp. izq.\n");
 if (p != NULL) {
   // v debe reemplazar a t como hijo
   if (p->izq == t) {
     p->izq = v;
   } else { // era derecho 
     p->der = v;
   }
 } else {
   // v es ahora raiz
   *raiz = v;
 }
 // el padre de t sera padre de v
 v->padre = p;
 // printf("Arreglado hacia arriba.\n");
 
 // v ahora es hijo izq de t
 // t es padre de v
 v->izq = t;
 t->padre = v;
 // el hijo der de t no cambia
 // printf("Arreglado entre t y v.\n");
 
 // a va a ser hijo derecho de t
 // t va a ser padre de a
 t->der = a;
 a->padre = t;
 // printf("Arreglado entre a y t.\n");

#ifdef DEBUG
 if (p != NULL) {
   printf("v = %d es hijo de p = %d,\n",
   v->id, p->id);
 }
 printf("t = %d = %d es hijo de v = %d,\n",
        t->id, v->izq->id, v->id);
 printf("a = %d = %d es hijo de t = %d\n",
        a->id, t->der->id, t->id);
 printf("b = %d = %d es hijo de v = %d,\n",
        b->id, v->der->id, v->id);
 printf("u = %d = %d es hijo de t = %d.\n",
        u->id, t->izq->id, t->id);
#endif
 // para ni u ni b nada cambia
      } else {
 printf("Rot. doble req. sin impl.\n");
      }
    } else if (ai >= ad + 2) {
      // voltear hacia la derecha
      a = u->izq;
      b = u->der;
      assert(a != NULL);
      assert(b != NULL);
      aalt = a->altura;
      balt = b->altura;
      if (aalt >= balt) { 
 // printf("Rot. simp. der.\n");
 if (p != NULL) {
   if (p->izq == t) {
     p->izq = u;
   } else { // era derecho 
     p->der = u;
   }
 } else {
   // cambiando la raiz para ser u
   *raiz = u;
 }
 u->padre = p;
 // printf("Arreglado hacia arriba.\n");
 u->der = t;
 t->padre = u;
 // printf("Arreglado entre t y u.\n");
 t->izq = b;
 b->padre = t;
 // printf("Arreglado entre b y t.\n");
      } else {
 printf("Rot. req. no implementada.\n");
      }
    }
    if (p != NULL) {
      balancea(p, raiz);
    }
  } else {
#ifdef DEBUG
    printf("%c esta bien (%d vs. %d).\n", 
    n->dato, ai, ad);
#endif
    // siempre checa hasta la raiz
    balancea(n->padre, raiz);
  }
  return;
}

void agrega(char dato, nodo* arbol, nodo** n) {
  nodo* nuevo;
  nodo* reemplazo;
  char ruteo;
  if (arbol == NULL) {
    // caso especial de no tener nada aun
    nuevo = (nodo*)malloc(sizeof(nodo));
    nuevo->contador = 0;
#ifdef DEBUG
    nuevo->id = next++;
#endif
    nuevo->altura = 1; // como hoja
    nuevo->dato = dato;
    nuevo->padre = NULL;
    nuevo->izq = NULL;
    nuevo->der = NULL;
    *n = nuevo; // nueva raiz
    return;
  } else {
    if (arbol->izq == NULL &&
 arbol->der == NULL) { // si es hoja

#ifdef DEBUG
      printf("Agregando %c en %c.\n",
      dato, arbol->dato);
#endif
      
      ruteo = (arbol->dato < dato ? 
        arbol->dato : dato);

      nuevo = (nodo*)malloc(sizeof(nodo));
      nuevo->contador = 0;
#ifdef DEBUG
      nuevo->id = next++;
#endif
      nuevo->altura = 1; // como hoja
      nuevo->dato = dato;
      nuevo->padre = arbol;
      nuevo->izq = NULL;
      nuevo->der = NULL;

      reemplazo = (nodo*)malloc(sizeof(nodo));
      reemplazo->contador = arbol->contador;
      arbol->contador = NO_VALIDO;
#ifdef DEBUG
      reemplazo->id = next++;
#endif
      reemplazo->altura = 1; // como hoja
      reemplazo->dato = arbol->dato;
      reemplazo->padre = arbol;
      reemplazo->izq = NULL;
      reemplazo->der = NULL;

      if (dato < arbol->dato) {
 arbol->izq = nuevo;
 arbol->der = reemplazo;
      } else if (dato > arbol->dato) {
 arbol->der = nuevo;
 arbol->izq = reemplazo;
      } else {
 // iguales; agregar duplicidad
 arbol->contador++;
 return;
      }

      arbol->dato = ruteo;
      arbol->altura = 2;
      balancea(arbol->padre, n);
    } else {
      if (dato <= arbol->dato) {
 agrega(dato, arbol->izq, n);
      } else { // mayor
 agrega(dato, arbol->der, n);
      }
    }
  }
  return;
}

int main(int argc, char** args) {
  nodo* raiz = NULL;
  char dato;

  do {
    dato = getchar(); // lee un caracter
    if (!isspace(dato)) {
      agrega(dato, raiz, &raiz);
      printf("\nArbol actual: ");
      muestra(raiz);
      printf("\n");
    }
  } while (dato != '!'); // parar despues de !
  borra(raiz);

  return 1;
}


Video de Arboles y Grafos

Aqui les dejo un link acerca de un video de arboles y grafos, en donde se pude observar graficamente cada uno, asi mismo muestra algunos conceptos basicos, su aplicación y algunos tipos.


http://www.youtube.com/watch?v=wUCDZiTM59g

El unico inconveniente es que la resolucion no es buena. Hojala les sea de apoyo para alguna duda.

Contenido extra: Grafos


Los grafos son artefactos matemáticos que permiten expresar de una forma visualmente muy sencilla y efectiva las relaciones que se dan entre elementos de muy diversa índole. Un grafo simple está formado por dos conjuntos:
Un conjunto V de puntos llamados vértices o nodos.
Un conjunto de pares de vértices que se llaman aristas o arcos y que indican qué nodos están relacionados.

Un grafo G es un par (V,E) donde:
V ={v1,…,vn} es un conjunto de vértices
E = {e1,…,em} es un conjunto de aristas,
con cada ek  {vi, vj}, con vi, vj  V, vi ≠ vj
Los vértices se representan como puntos y las aristas como líneas entre vértices
Ejemplo:
G = (V,E)
V = {a,b,c,d }
E = {{a,b}, {b,c}, {a,c}, {a,d}, {d,b} }




Algunos conceptos basicos:

*Dos vértices se dicen adyacentes si existe una arista que los une.
*Los vértices que forman una arista son los extremos de la arista.
*Si v es un extremo de una arista a , se dice que a es incidente con v
*El grado de un vértice v , gr(v) es el número de aristas incidentes en v. Si hace falta indicar el grafo en el que está v escribiremos gr(G,v).



Representación de grafos
Una característica especial en los grafos es que podemos representarlos utilizando dos estructuras de datos distintas. En particular, los tiempos de ejecución variarán en función del número de vértices y el de aristas, por lo que la utilización de una representación u otra dependerá en gran medida de si el grafo es denso o disperso.

Representación por matriz de adyacencia
Es la forma más común de representación y la más directa. Consiste en una tabla de tamaño V x V, en que la que a[i][j] tendrá como valor 1 si existe una arista del nodo i al nodo j. En caso contrario, el valor será 0. Cuando se trata de grafos ponderados en lugar de 1 el valor que tomará será el peso de la arista. Si el grafo es no dirigido hay que asegurarse de que se marca con un 1 (o con el peso) tanto la entrada a[i][j] como la entrada a[j][i], puesto que se puede recorrer en ambos sentidos.
int V,A;
int a[maxV][maxV];

void inicializar()
{
  int i,x,y,p;
  char v1,v2;
  // Leer V y A
  memset(a,0,sizeof(a));
  for (i=1; i<=A; i++)
  { 
    scanf("%c %c %d\n",&v1,&v2,&p);
    x=v1-'A'; y=v2-'A';
    a[x][y]=p; a[y][x]=p;
  } 
}

En esta implementación se ha supuesto que los vértices se nombran con una letra mayúscula y no hay errores en la entrada. Evidentemente, cada problema tendrá una forma de entrada distinta y la inicialización será conveniente adaptarla a cada situación. En todo caso, esta operación es sencilla si el número de nodos es pequeño. Si, por el contrario, la entrada fuese muy grande se pueden almacenar los nombres de nodos en un árbol binario de búsqueda o utilizar una tabla de dispersión, asignando un entero a cada nodo, que será el utilizado en la matriz de adyacencia.

La matriz de adyacencia siempre ocupa un espacio de V*V, es decir, depende solamente del número de nodos y no del de aristas, por lo que será útil para representar grafos densos.

Representación por lista de adyacencia
Otra forma de representar un grafo es por medio de listas que definen las aristas que conectan los nodos. Lo que se hace es definir una lista enlazada para cada nodo, que contendrá los nodos a los cuales es posible acceder. Es decir, un nodo A tendrá una lista enlazada asociada en la que aparecerá un elemento con una referencia al nodo B si A y B tienen una arista que los une. Obviamente, si el grafo es no dirigido, en la lista enlazada de B aparecerá la correspondiente referencia al nodo A.

Las listas de adyacencia serán estructuras que contendrán un valor entero (el número que identifica al nodo destino), así como otro entero que indica el coste en el caso de que el grafo sea ponderado. En el ejemplo se ha utilizado un nodo z ficticio en la cola.
struct nodo
{
  int v;
  int p;
  nodo *sig;
};

int V,A;  // vértices y aristas del grafo
struct nodo *a[maxV], *z;

void inicializar()
{
  int i,x,y,peso;
  char v1,v2;
  struct nodo *t;
  z=(struct nodo *)malloc(sizeof(struct nodo));
  z->sig=z;
  for (i=0; i<V; i++)
    a[i]=z;
  for (i=0; i<A; i++)
  {
    scanf("%c %c %d\n",&v1,&v2,&peso);
    x=v1-'A'; y=v2-'A';

    t=(struct nodo *)malloc(sizeof(struct nodo));
    t->v=y; t->p=peso; t->sig=a[x]; a[x]=t;

    t=(struct nodo *)malloc(sizeof(struct nodo));
    t->v=x; t->p=peso; t->sig=a[y]; a[y]=t;
  }
}

En este caso el espacio ocupado es O(V + A), muy distinto del necesario en la matriz de adyacencia, que era de O(V2). La representación por listas de adyacencia, por tanto, será más adecuada para grafos dispersos.
Hay que tener en cuenta un aspecto importante y es que la implementación con listas enlazadas determina fuertemente el tratamiento del grafo posterior.



BIBLIOGRAFIA
http://www.youtube.com/watch?v=wUCDZiTM59g


Contenido extra: Arboles


Un árbol es una estructura no lineal formada por un conjunto de nodos y un conjunto de ramas.

Tenemos un nodo especial llamado raíz. Un nodo del que sale una rama se llama nodo de bifurcación o nodo rama. Un nodo que no tiene ramas se llama nodo terminal o nodo hoja.

De cada nodo cuelgan nuevos nodos que constituyen un nuevo árbol denominado
subárbol de la raíz. El número de ramas de un nodo recibe el nombre de grado del nodo. Si la raíz tiene nivel 0, el nivel de un nodo es igual a la distancia de ese nodo al nodo raíz. El nivel máximo se llama profundidad o altura del árbol.


Árboles binarios

Si limitamos los árboles en el sentido de que cada nodo sólo pueda ser de grado 2, tendremos árboles binarios, en los que podemos distinguir árbol derecho y árbol izquierdo.

A su vez, cada subárbol es un árbol binario.

Ejemplo. Las fórmulas algebráicas, debido a que los operadores que intervienen son operadores binarios, nos dan un ejemplo de estructura de árbol binario. Por ejemplo:
(a+b*c)/(d-e/f)

Dada la definición de árbol binario, resulta sencilla su representación en un ordenador.

Dispondremos de una variable raíz para referenciar al árbol y cada nodo contendrá dos enlaces: izquierdo y derecho:

typedef struct datos nodo;
struct datos
{
nodo * izquierdo;
nodo * derecho;
};


Recorrido de árboles binarios
Esencialmente se puede recorrer un árbol binario de tres formas:

*Preorden: raíz, subárbol izquierdo, subárbol derecho.
*Inorden: subárbol izquierdo, raíz, subárbol derecho.
*Postorden: subárbol izquierdo, subárbol derecho, raíz.


En una estructura lineal resulta trivial establecer un criterio de movimiento por la misma para acceder a los elementos, pero en un árbol esa tarea no resulta tan simple.
Si consideramos el esquema general de un árbol tal como muestra la figura siguiente,los recorridos se definen como sigue:



  1. El listado en preorden es:
    • Si el árbol tiene un único elemento, dicho elemento es el listado en preorden.
    • Si el árbol tiene más de un elemento,es decir,una estructura como muestra la figura 2,el listado en preorden es listar el nodo raíz seguido del listado en preorden de cada uno de los subárboles hijos de izquierda a derecha.
  2. El listado en inorden es:
    • Si el árbol tiene un único elemento,dicho elemento es el listado en inorden.
    • Si el árbol tiene una estructura como muestra la figura 2,el listado en inorden es listar el subárbol A1 en inorden,y listar el nodo raíz seguido del listado en inorden de cada uno de los subárboles hijos de izquierda a derecha restantes.
  3. El listado en postorden es:
    • Si el árbol tiene un único elemento,dicho elemento es el listado en postorden.
    • Si el árbol tiene una estructura como muestra la figura 2,el listado en postorden es listar en postorden cada uno de los subárboles hijos de izquierda a derecha seguidos por el nodo raíz.

  4. El listado por niveles es: desde i=0 hasta la altura h del árbol,listar de izquierda a derecha los elementos de profundidad i.Como podemos observar,un nodo n1 aparece antes que n2 en el listado por niveles si la profundidad de n1 es menor que la profundidad de n2 usando el orden de los nodos definido anteriormente para el caso en que tengan la misma profundidad.

Como ejemplo de listados veamos el resultado que se obtendría sobre el árbol A de la figura 3.

Los resultados de los listados de preorden,postorden e inorden son los siguientes:




El listado por niveles de este árbol es el siguiente:r,v,s,u,w,p,q,x,y,z.
Finalmente es interesante conocer que un árbol no puede,en general,recuperarse con uno solo de sus recorridos.Por ejemplo:Dada la lista en inorden:vwyxzrtupsq,los árboles de la figura 4 tienen ese mismo recorrido en inorde.




BIBLIOGRAFIA
http://decsai.ugr.es/~jfv/ed1/tedi/cdrom/index.htm-419&source=hp&q=arboles+en+programacion+c.pdf&aq=f&aqi=&aql=&oq=&pbx=1&bav=on.2,or.r_gc.r_pw.&fp=5f12827f8e650db9&biw=1366&bih=680
 





 










Wednesday, July 13, 2011

Tarea 5: Pilas

Aqui esta el archivo modificado, pero tengo un error, no pude corregir. Ya no alcance a hacer el de colas, pero lo que me llegaron a explicar fue que tenemos que hacer una condicion que nos permita agregar al final y eliminar al principio para que funcione. En las pilas que puse aqui se maneja que tiene que insertarse una pila al principio y tiene que eliminarse del mismo modo al principio. Lo que hice fue hacer una copia de cada uno de lo que es nuestro programa principal (spilas.c), la libreria (spilas.h) y de noveno.c que ahora es bien.c para que pueda ejecutarse adecuadamente nuestro programa. cambie del archivo listas.h a spilas.h

#ifndef SPILAS_H

#define SPILAS_H

#include "bool.h"

// estructura de un elemento de la lista
struct elemento_de_lista {
  int dato; // donde la info va
  // doblemente enlazada
  struct elemento_de_lista* siguiente; // puntero
  struct elemento_de_lista* anterior; // puntero
}; // <= ojo con el punto y coma

// redefinicion por brevedad
typedef struct elemento_de_lista elem;

// eliminar todos los elementos de la lista
elem* borrar(elem* esto);

// checar si la lista contiene un valor dado
// devuelve verdad o falso
// recibe un puntero a un elemento de la lista
// implementacion recursiva
bool buscar(int valor, elem* aqui);

// devuelve si o no se pudo eliminar
// (no se puede eliminar si no esta)
// valor cuyo elemento hay que eliminar
// (unicamente elimina el primer elemento
// cuyo valor coincide)
// elemento en el cual estamos buscando = aqui
// direccion del inicio de la lista
bool eliminar_elemento(int valor, elem* aqui, 
         elem** inicio);

// interface para llamadas mas bonitas
bool eliminar(int valor, elem** inicio);

void imprime_elemento(elem* esto);

// interfase que agrega [ ... ] y el \n
void imprimir(elem* lista);

// agregar un elemento en la posicion que
// le corresponde (valores de menor a mayor)
elem* insertar(int valor, elem* aqui);

#define MAX 40
#define MIN 1

// numeros pseudoaleatorios [MIN, MAX]
int pseudoaleatorio();

#endif

Tambien cambie lo que eran listas.c a spilas.c

#include  // imprimir (printf)
#include  // reservar memoria
#include "spilas.h"

// eliminar todos los elementos de la lista
// opcion vaciar
elem* borrar(elem* esto) {
  elem* temp; // auxiliar 
  // iterativa
  while (esto != NULL) {
    temp = esto->siguiente;
    free(esto); // liberar
    esto = temp; // avanza al siguinte
  }
  return NULL; // que ya no hay lista
}

// checar si la lista contiene un valor dado
// devuelve verdad o falso
// recibe un puntero a un elemento de la lista
// implementacion recursiva
bool buscar(int valor, elem* aqui) {
  if (aqui != NULL) {

    //#define DEBUG
#ifdef DEBUG 
    printf("Buscando por %d en %d.\n", 
    valor, aqui->dato);
#endif // hasta aqui si no esta definido DEBUG

    // si el valor buscado esta en este elemento
    if (aqui->dato == valor) {

#ifdef DEBUG
      printf("Son iguales.\n");
#endif

      return TRUE; // busqueda exitosa

    } else if (aqui->dato > valor) {
      // ojo: lista esta ordenada de menor
      // a mayor (por construccion a traves
      // de la implementacion del metodo
      // insertar(...)

#ifdef DEBUG
      printf("Ya es mayor. No va a estar.\n");
#endif

      return FALSE; // busqueda fallida
    }
    // pasar la pelota al siguiente elemento
    return buscar(valor, aqui->siguiente);
  } else { // aqui es null
    // este elemento actual ya es null, o sea,
    // no en realidad es un elemento

#ifdef DEBUG
    printf("Ya se acabo. No estuvo.\n");
#endif
    return FALSE; // busqueda fallida
  }
}

// devuelve si o no se pudo eliminar
// (no se puede eliminar si no esta)
// valor cuyo elemento hay que eliminar
// (unicamente elimina el primer elemento
// cuyo valor coincide)
// elemento en el cual estamos buscando = aqui
// direccion del inicio de la lista
bool eliminar_elemento(int valor, elem* aqui, 
         elem** inicio) {
  if (aqui != NULL) { // si hay algo
    if (aqui->dato == valor) {
      // hay que borrar el elemento
      if (aqui->siguiente != NULL) {
 aqui->siguiente->anterior = 
   aqui->anterior; 
      }
      if (aqui->anterior == NULL) {
 *inicio = aqui->siguiente;
      } else {
 aqui->anterior->siguiente = 
   aqui->siguiente;
      }
      free(aqui); // borrame
      return TRUE; // eliminacion exitosa
    } else if (aqui->dato > valor) {
      return FALSE;
    }
    return eliminar_elemento(valor, 
        aqui->siguiente, 
        inicio);
  }
  return FALSE;
}

// interface para llamadas mas bonitas
bool eliminar(int valor, elem** inicio) {
  return 
    eliminar_elemento(valor, *inicio, inicio);
}

void imprime_elemento(elem* esto) {
  // iterativa
  while (esto != NULL) {
    printf("%d ", esto->dato);
    esto = esto->siguiente;
  }
  return;
}

// interfase que agrega [ ... ] y el \n
void imprimir(elem* lista) {
  printf("[ ");
  imprime_elemento(lista);
  printf("]\n");
  return;
}

// agregar un elemento en la posicion que
// le corresponde (valores de menor a mayor)
elem* insertar(int valor, elem* aqui) {
  elem* nuevo = NULL; // auxiliar
  // para crear el nuevo elemento
 
#ifdef DEBUG
  if (nuevo->siguiente != NULL) {
    printf("Estoy en %d, insertando un %d.\n",
    nuevo->dato, valor);
  } else {
    printf("No hay nada.\n");
  }
#endif
 
  if (aqui == NULL) { // 
    nuevo = (elem*)malloc(sizeof(elem));
    nuevo->dato = valor; 
    nuevo->anterior = nuevo; 
    nuevo->siguiente = NULL; 
    return nuevo;
  } else {
    if (valor < aqui->dato) {
      nuevo = (elem*)malloc(sizeof(elem));
      nuevo->dato = valor; // pon el valor
      if (aqui->siguiente == NULL) {
 // aqui es el primer elemento
 nuevo->siguiente = aqui;
 aqui->anterior = nuevo;
 nuevo->anterior = NULL; 
 return nuevo;
      } else { // un chorro de flechas
 nuevo->anterior = aqui->anterior;
 nuevo->siguiente = aqui;
 nuevo->anterior->siguiente = nuevo;
 aqui->anterior = nuevo;
      }
    }
  }
}
// numeros pseudoaleatorios [MIN, MAX]
int pseudoaleatorio() {
  return ((rand() % (MAX - MIN + 1)) + MIN);
}

Por ultimo cambie lo que era noveno.c a bien.c para que funcione

#include "spilas.h"
#include "entrada.h"
#include  // printf
#include  // srand

// rutina principal
int main(int argc, char** args) {
  elem* lista = NULL; // vacia al inicio
  int valor; // auxiliares
  char op;
  bool basta = FALSE;

  while (!basta) {
    printf("Que quieres hacer?\n");
    printf("Agregar = a\nBuscar = b\n");
    printf("Eliminar = e\nVaciar = v\n");
    printf("Imprimir = i\nSalir = s\n> ");
    op = pide_opcion("abevis");
    switch (op) {
    case 'a':
      valor = pide_pila(MIN, MAX);
      lista = 
 insertar(valor, lista);      
      break;
    case 'b':
      valor = pide_pila(MIN, MAX);
      printf("%d %s esta en la spila.\n",
      valor, (buscar(valor, lista) ? 
       "SI" : "NO"));
      break;
    case 'e':
      valor = pide_pila(MIN, MAX);
      if (eliminar(valor, &lista)) {
 printf("%d eliminado.\n", valor);
      } else {
 printf("%d no estuvo presente.\n", 
        valor);
      }
      break;
    case 'v':
      lista = borrar(lista);
      break;
    case 'i':
      imprimir(lista);
      break;
    case 's':
      basta = TRUE;
      break;
    default:
      printf("Esto no deberia pasar nunca.\n");
      break;
    }
  }
  return 1; 
}

Monday, July 11, 2011

Contenido extra: Punteros


Un puntero es una variable un tanto especial. Con un puntero podemos almacenar direcciones de memoria. En un puntero podemos tener guardada la dirección de una variable.

Un puntero se define igual que una variable normal, salvo que delante del identificador se colocara un asterisco. Por ejemplo:

char *pc; /*puntero a carácter */
char *pi; /* puntero a entero */

Normalmente al definir un puntero lo suelen inicializar para que apunte a algún dato.

Se dispone de tres formas de inicializar un puntero:

Inicializarlo con la dirección de una variable que ya existe en memoria.

Para obtener la dirección en la que está ubicada una variable colocamos delante del identificador de la variable el operador dirección &. No se suele dejar espacios entre el signo & y el identificador. Por ejemplo:


char *p = &p1;

Asignarle el contenido de otro puntero que ya está‚ inicializado:

char *p = &p1;
char *p2 = p; /* p ya est inicializado */


Inicializarlo con cualquier expresión constante que devuelva un lvalue.

Las más frecuentes son una cadena de caracteres, el identificador de un arreglo, el identificador de una función, y otros muchos. Este es un detalle importante: los identificadores de funciones y de arreglos son en si mismos valores por la izquierda (lvalues), por lo que se pueden usar directamente para inicializar punteros.

Una forma adicional de inicializarlo es darle directamente una posición de memoria. Este método no es portable, ya que depende del sistema, pero suele ser muy útil en programación de sistemas, que es uno de los usos fundamentales del C.

Un error muy frecuente consiste en no inicializar el puntero antes de usarlo. Este error frecuentemente lo localiza el compilador y avisa de ello.

Declaración de un puntero

Un puntero, en C, se declara como sigue:

TIPO * nombre_puntero ;

Donde TIPO es cualquier tipo definido. Asi, un puntero a caracter se declararia de la siguiente forma:

char *pchar;



Diferencia entre "*" y "&"


En C, al contrario que en otros lenguajes de programacion, se puede obtener directamente la direccion de memoria de cualquier variable. Esto es posible hacerlo con el operador unario "&"; asi:

char a; /* Variable 'a' de tipo char */

printf("la direccion de memoria de 'a' es: %p \n", &a);

y para obtener lo apuntado por un puntero se utiliza el operador unario "*" de esta forma:

char a; /* Variable 'a' de tipo char */
char *pchar; /* Puntero a char 'pchar' */
pchar = &a; /* 'pchar' <- @ de 'a' */
printf("la direccion de memoria de 'a' es: %p \n", &a);
printf("y su contenido es : %c \n", *pchar);


Uno de los casos mas comunes donde se ve la relacion entre estos dos operadores es la declaracion y utilizacion de funciones:

void Funcion ( int *int_pointer )
/* Paso de una variable de tipo entero por REFERENCIA */
/* equivalente en Modula 2: PROCEDURE Funcion ( VAR a:INTEGER ) */
.
.
.
int a;
a=6;
Funcion ( &a ); /* ya que la declaracion de la funcion pide la
direccion de una variable de tipo entero */


Los punteros tienen muchas utilidades, por ejemplo nos permiten pasar argumentos (o parámetros) a una función y modificarlos. También permiten el manejo de cadenas y de arrays. Otro uso importante es que nos permiten acceder directamente a la pantalla, al teclado y a todos los componentes del ordenador.


BIBLIOGRAFIA

http://www.elrincondelc.com/cursoc/cursoc9.html
http://linuxupc.upc.es/~pep/OLD/Punteros.html
 http://translate.google.com.mx/translate?hl=es&langpair=en|es&u=http://www.cs.cf.ac.uk/Dave/C/CE.html
http://www.wikiciencia.org/informatica/programacion/c/index.php







CONTENIDO EXTRA……SESIÓN NUMERO 8: SUBRUTINAS



Una subrutina es una porción de código que forma parte de un programa más grande. Esa porción de código realiza una tarea específica, relativamente independiente del resto del código.

Existen varias ventajas de "romper" un programa en subrutinas:
*Reducción de código duplicado.
*Permite el reusó de código en múltiples programas.
*Descomposición de problemas complejos en simples piezas (lo que aumenta la mantenibilidad y extensibilidad).


Las subrutinas sirven para estructurar o dividir el programa en bloques más pequeños y, por tanto, más fáciles de gestionar. Esta ventaja se puede aprovechar a la hora de realizar tareas de comprobación y mantenimiento del programa.

Cuando el programa principal llama a una subrutina para que ésta se ejecute, la subrutina procesa un programa hasta el final. El sistema retorna luego el control al segmento del programa principal desde donde se llamo a la subrutina.

Para utilizar una subrutina en el programa es preciso realizar tres tareas:

*Crear subrutina

*Definir los parámetros en la tabla de variables locales de la subrutina.

*Llamar a la subrutina desde la unidad de organización del programa en cuestión, es decir, desde el programa principal o desde una subrutina diferente.


En las subrutinas se han de tener en cuenta las siguientes consideraciones:

*Realizan funciones concretas y no son operativas por si mismas.

*Siempre están ligadas a un programa principal o a otras subrutinas.

*Permiten la división del programa en bloques por lo que realizan la función de estructuración. Proporcionando mayor visibilidad y comprensión del mismo.



COMUNICACIÓN ENTRE SUBPROGRAMAS O SUBRUTINAS

Los subprogramas no operan aisladamente, en la mayoría de los casos dependen de lo que produzcan otros subprogramas. Para que un subprograma reciba de o envíe datos a otros subprogramas es necesario que el subprograma tenga parámetros o instrucción de retorno. Los parámetros permiten a un subprograma comunicarse con el mundo externo, para recibir datos del exterior o enviar información al exterior. No todos los subprogramas tienen parámetros para recibir datos del exterior o parámetros para enviar información al exterior.



A manera de un pequeño ejemplo.

Cuando ya se tiene un programa que resuelva un problema concreto, como ya se había mencionado, se puede usar tantas veces como queramos sin tener que reescribirlo.

# include ……

// programa principal

Int main (int argc, char** argv)

{

….

}

// Subprogramas

… // funciones y procedimientos


Importante: los subprogramas solo se ejecutan cuando son invocados desde el principal programa o desde otros subprogramas.


Para crear un subprograma o subrutina, lo primero que se tiene que hacer es escribir su cabecera, que incluye el nombre del subprograma y su lista de parámetros:


Float sqrt (float x)

….

Además de la cabecera, tendremos que escribir el cuerpo de la función:

….

// Calculamos la raíz cuadrada

…..

// valor devuelto por la función return…..



Una vez que se ha completado la implementación del subprograma, podemos usarlo cuando lo necesitemos:


a = sqrt (4.0)

b = 2*sqrt

c =1 / sqrt (b)

x = ( -b + sqrt (b*b - 4 *a*c)) / ( 2 *a)

c = sqrt ( 2.33 t sqrt ( a*b) / 34.12)


Todos los cambios que se hagan dentro de la subrutina para modificar un parámetro pasado por referencia se reflejaran en el parámetro actual correspondiente.


BIBLIOGRAFIA:

http://www.alegsa.com.ar/Dic/subrutina.php

http://galia.fc.uaslp.mx/~cantocar/automatas/PRESENTACIONES_PLC_PDF_S/19_SUBRUTINAS.PDF

http://www.alciro.org/alciro/microcontroladores-8051_24/subrutina-subprograma_357.htm




Esté link, los considero importante

http://elvex.ugr.es/decsai/c/apuntes/subprogramas.pdf

Espero que les haya aportado un poco de información útil para aclarar algunas dudas.



Thursday, July 7, 2011

Tarea 4. ciclos

Mi codigo consiste en que cuando un usuario ingresa el precio de un articulo, arroja tres diferentes opciones que le permitirán aplicar un descuento, esto, dependiendo de la cantidad que arroje, lo que me faltó fue incluir el método pide_opción (se utilizó ayer), para que si en la condición while cumple al ingresar una "s", se regresa y hace alguna de las opciones que hay, pero si aparece una "n", terminaría el programa. También ocupé una variable tipo booleana que lo va aguardar como un tipo short.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define TRUE 1
#define FALSE 0
typedef short bool;

int main (int argc, char** args) {
  //p es el precio del articulo
  double p;
  // pt es el precio total con descuento
  double pt;
  int desc1 = 20;
  int desc2 = 50;
  int desc3 = 100;
  bool opcion;


  printf("ingresa el precio del producto:$ ");
  scanf("%f", &p);

  do{
  if(p < 150){
    printf("No aplica descuento.\n");
  }
  else if (p >= 150 && p < 300); {
    pt = (p - desc1);
    printf("obtuvo el descuento num 1 por su adquisicion,pagando $");
    printf("%.2f.\n", pt);
  }
  else if (p >= 300 && p < 500) {
    pt = (p - desc2);
    printf("Obtuvo el descuento num 2 por su adquisicion, pagando $");
    printf("%.2f.\n", pt);
  }
  else if(p >= 500){
    pt = (p - desc3);
    printf("Obtuvo el descuento num 3 por su adquisicion, pagando $");
    printf("%.2f", pt);
  }

  printf("Deseas ingresar otro precio? (s/n): \n");
  opcion = (pide_opcion("sn") == 's');
  }while(opcion);
  return 0;
}

Creación propia.

Wednesday, July 6, 2011

Tarea 3. Un clasificador simple con condiciones

Aqui esta mi codigo, pero me hizo falta algo, debido a que despues de que me pide la calificacion, le doy enter y no aparece nada, pero si escribo cualquier cosa y vuelvo a dar enter, ya me da lo que se condiciona, segun la calificacion que mete el usuario. agradeceria si me dicen mi error. gracias.



#include <stdio.h>
#include <stdlib.h>

#define maximo 10
 

int main(int argc, char** args) {
  short a;
  int  valor;
  double decimal;


  printf ("dame tu calificacion de este semestre: ");
  scanf("%d %lf.\n", &valor, &decimal);
  
  
  if (valor == 10 ) { 
    printf("Felicidades sigue asi.\n");
  } else if (valor == 8) {
    printf("continua esforzandote.\n");
  } else if (valor == 6) {
    printf("apenas y pasaste.\n");
  } else if (valor < 6) { 
    printf("tendras que hacer examen extraordinario.\n");      
  }

   return 1;

  }