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; 
}