Vous êtes sur la page 1sur 122

SUBSECRETARA DE EDUCACIN SUPERIOR DIRECCIN GENERAL DE EDUCACIN SUPERIOR TECNOLGICA UNIVERSIDAD TECNOLOGICA SANTA CATARINA PROGRAMA EDUCATIVO DE:

TECNOLOGIAS DE LA INFORMACION Y COMUNICACION

MANUAL DE ASIGNATURA DE:

ESTRUCTURA DE DATOS

Universidad Tecnolgica Santa Catarina Carretera Saltillo-Monterrey Km. 61.5 Santa Catarina C.p. 66359 Tel. 81248400 www.utsc.edu.mx

Unidades Temticas I. Conceptos bsicos II. Arreglos III. Listas IV. Pilas V. Colas VI. rboles

I. Conceptos bsicos
Objetivo General: El alumno elaborar programas que integren el uso de recursividad y definir estructuras de datos para generar alternativas de programacin. Tipo de dato abstracto Un tipo de dato abstracto (TDA) o Tipo abstracto de datos (TAD) es un modelo matemtico compuesto por una coleccin de operaciones definidas sobre un conjunto de datos para el modelo. 1 Introduccin 2 Historia 3 Definicin 4 Separacin de la interfaz e implementacin 5 Caracterizacin 6 La abstraccin 7 Ejemplos de utilizacin de TDAs Introduccin En el mundo de la programacin existen diversos lenguajes que se han ido creando con el paso del tiempo y que se han perfeccionado debido a las necesidades de los programadores de la poca a la que pertenecen. Los primeros lenguajes de programacin eran de tipo lineal, ya que un programa se recorra desde un punto marcado como Inicio hasta llegar a un punto Fin. Con el tiempo se fueron creando nuevos lenguajes y en nuestros das los ms utilizados son los llamados Orientados a Objetos. Los Lenguajes Orientados a Objetos (LOO) tienen la caracterstica de que no son lenguajes lineales, sino que se forman de diversas funciones, las cuales son llamadas en el orden en que el programa mismo las pide o el usuario determina. Para entender mejor cmo funcionan los Lenguajes Orientados a Objetos, vamos a introducir un concepto fundamental en las Estructuras de Datos denominado Abstraccin de Datos y que es parte importante de estos Lenguajes y de la manera en que funciona la mayora del software comercial de nuestros das. Historia El concepto de tipo de dato abstracto (TDA, Abstract Data Type), fue propuesto por primera vez hacia 1974 por John Guttag y otros, pero no fue hasta 1975 que por primera vez Liskov lo propuso para el lenguaje CLU. El lenguaje Turbo Pascal fue determinante para la comn aceptacin de los TDAs con la introduccin de las Units, si bien estas no cumplen con las caractersticas bsicas de un Tipo de dato Abstracto como por ejemplo la encapsulacin de los datos. El lenguaje Ada pudo implementar exitosamente los TDAs con sus Packages. Vale recordar que estos dos ltimos lenguajes soportan formalmente la Programacin modular. [editar] Definicin Con mucha frecuencia se utilizan los trminos TDA y Abstraccin de Datos de manera equivalente, y esto es debido a la similitud e interdependencia de ambos. Sin embargo, es importante definir por separado los dos conceptos. Como ya se mencion, los Lenguajes de Programacin Orientados a Objetos son lenguajes formados por diferentes mtodos o funciones y que son llamados en el orden en que el programa lo requiere, o el usuario lo desea. La abstraccin de datos consiste en ocultar las caractersticas de un objeto y obviarlas, de manera que solamente utilizamos el nombre del objeto en nuestro programa. Esto es similar a una situacin de la vida cotidiana. Cuando yo digo la palabra perro, usted no necesita que yo le diga lo que hace el perro. Usted ya sabe la forma que tiene un perro y tambin sabe que los perros ladran. De manera que yo abstraigo todas las caractersticas de todos los perros en un solo trmino, al cual llamo perro. A esto se le llama Abstraccin y es un concepto muy til en la programacin, ya que un usuario no necesita mencionar todas las caractersticas y funciones de un objeto cada vez que ste se utiliza, sino que son declaradas por separado en el programa y simplemente se utiliza el trmino abstracto (perro) para mencionarlo.

En el ejemplo anterior, perro es un Tipo de Dato Abstracto y todo el proceso de definirlo, implementarlo y mencionarlo es a lo que llamamos Abstraccin de Datos. Vamos a poner un ejemplo real de la programacin. Supongamos que en algn Lenguaje de Programacin Orientado a Objetos un pequeo programa saca el rea de un rectngulo de las dimensiones que un usuario decida. Pensemos tambin que el usuario probablemente quiera saber el rea de varios rectngulos. Sera muy tedioso para el programador definir la multiplicacin de base por altura varias veces en el programa, adems que limitara al usuario a sacar un nmero determinado de reas. Por ello, el programador puede crear una funcin denominada rea, la cual va a ser llamada el nmero de veces que sean necesitadas por el usuario y as el programador se evita mucho trabajo, el programa resulta ms rpido, ms eficiente y de menor longitud. Para lograr esto, se crea el mtodo rea de una manera separada de la interfaz grfica presentada al usuario y se estipula ah la operacin a realizar, devolviendo el valor de la multiplicacin. En el mtodo principal solamente se llama a la funcin rea y el programa hace el resto. Al hecho de guardar todas las caractersticas y habilidades de un objeto por separado se le llama Encapsulamiento y es tambin un concepto importante para entender la estructuracin de datos. Es frecuente que el Encapsulamiento sea usado como un sinnimo del Ocultacin de informacin, aunque algunos creen que no es as [1]. Separacin de la interfaz e implementacin Cuando se usa en un programa de computacin, un TDA es representado por su interfaz, la cual sirve como cubierta a la correspondiente implementacin. Los usuarios de un TDA tienen que preocuparse por la interfaz, pero no con la implementacin, ya que esta puede cambiar en el tiempo y afectar a los programas que usan el TDA. Esto se basa en el concepto de Ocultacin de informacin, una proteccin para el programa de decisiones de diseo que son objeto de cambio. La solidez de un TDA reposa en la idea de que la implementacin est escondida al usuario. Solo la interfaz es pblica. Esto significa que el TDA puede ser implementado de diferentes formas, pero mientras se mantenga consistente con la interfaz, los programas que lo usan no se ven afectados. Hay una diferencia, aunque a veces sutil, entre el Tipo de Dato Abstracto y la Estructura de Dato usada en su implementacin. Por ejemplo, un TDA de una lista puede ser implementado mediante un Arreglo o una Lista Enlazada o hasta un rbol binario de bsqueda. Una lista es un Tipo de Dato Abstracto con operaciones bien definidas (agregar elemento, agregar al final, agregar al principio, recuperar, eliminar, etc) mientras una lista enlazada es una estructura de datos basada en punteros o referencias (dependiendo del lenguaje) que puede ser usada para crear una representacin de una Lista. La Lista Enlazada es comnmente usada para representar una TDA Lista, y a veces, hasta confundida. Un ejemplo es la clase Linked List de Java, la cual ofrece una gran cantidad de mtodos que no corresponden a una Lista Enlazada "pura", sino a un fuerte TDA. De forma similar, un TDA rbol binario de bsqueda puede ser representado de muchas maneras: rbol binario, rbol AVL, rbol rojo-negro, Arreglo, etc. A pesar de la implementacin un rbol binario siempre tiene las mismas operaciones (insertar, eliminar, encontrar, etc.) Separar la interfaz de la implementacin no siempre significa que el usuario ignora totalmente la implementacin de la rutina, pero lo suficiente para no depender de ningn aspecto de la implementacin. Por ejemplo, un TDA puede ser creado usando un script o cualquiera que pueda ser decompilado (como C). En la terminologa de Lenguaje Orientado a Objeto, un TDA es una clase; un instancia de un TDA o clase, es un objeto. Caracterizacin Un TDA est caracterizado por un conjunto de operaciones (funciones) al cual le denominaron usualmente como su interfaz pblica y representan el comportamiento del TDA; mientras que la implementacin como la parte privada del TDA est oculta al programa cliente que lo usa. Todos los lenguajes de alto nivel tienen predefinidos TDA; que son los tipos denominados simples y las estructuras predefinidas, y estos tienen sus interfaces pblicas que incluyen las operaciones como la +, -, *, etc. no se necesita conocer como actan tales

operadores sobre la representacin interna de los tipos definidos, que adems, suele ser una implementacin bastante dependiente de la mquina sobre la que trabaje el compilador. Lo interesante es que los lenguajes actuales nos van a permitir ampliar los TDA predefinidos con otros que sern definidos por el propio programador para adecuar as los tipos de datos a las necesidades de los programas. Los TDA que nos van a interesar de ahora en adelante son aquellos que reflejen cierto comportamiento organizando cierta variedad de datos estructuradamente. A esta forma estructurada de almacenar los datos ser a la que nos refiramos para caracterizar cada TDA. Los TDA que tienen informaciones simples pero dependientes de un comportamiento estructural sern llamados polilticos y aquellos TDA simples, como son los tipos predefinidos donde la informacin no es relacionada mediante ninguna estructura y no admiten ms que un valor en cada momento sern denominados TDA monolticos. Ntese que cuando hablemos de un TDA no haremos ninguna alusin al tipo de los elementos sino tan slo a la forma en que estn dispuestos estos elementos. Slo nos interesa la estructura que soporta la informacin y sus operaciones. Para determinar el comportamiento estructural basta con observar la conducta que seguirn los datos. Caractericemos entonces los TDA. Un TDA tendr una parte que ser invisible al usuario la cual hay que proteger y que se puede decir que es irrelevante para el uso del usuario y est constituida tanto por la maquinaria algortmica que implemente la semntica de las operaciones como por los datos que sirvan de enlace entre los elementos del TDA, es decir, informacin interna necesaria para la implementacin que se est haciendo para ese comportamiento del TDA. Resumiendo podemos decir, que tanto la implementacin de las operaciones como los elementos internos del TDA sern privados al acceso externo y ocultos a cualquier otro nivel. Un TDA representa una abstraccin: Se destacan los detalles (normalmente pocos) de la especificacin (el qu). Se ocultan los detalles (casi siempre numerosos) de la implementacin (el cmo). [editar] La abstraccin La abstraccin, una de las herramientas que ms nos ayuda a la hora de solucionar un problema, es un mecanismo fundamental para la comprensin de problemas y fenmenos que poseen una gran cantidad de detalles, su idea principal consiste en manejar un problema, fenmeno, objeto, tema o idea como un concepto general, sin considerar la gran cantidad de detalles que estos puedan tener. El proceso de abstraccin presenta dos aspectos complementarios. 1.Destacar los aspectos relevantes del objeto. 2.Ignorar los aspectos irrelevantes del mismo (la irrelevancia depende del nivel de abstraccin, ya que si se pasa a niveles ms concretos, es posible que ciertos aspectos pasen a ser relevantes). De modo general podemos decir que la abstraccin permite establecer un nivel jerrquico en el estudio de los fenmenos, el cual se establece por niveles sucesivos de detalles. Generalmente, se sigue un sentido descendente de detalles, desde los niveles ms generales a los niveles ms concretos. Por ejemplo: los lenguajes de programacin de alto nivel permiten al programador abstraerse del sin fin de detalles de los lenguajes ensambladores. Otro ejemplo, la memoria de la computadora es una estructura unidimensional formada por celdas y sin embargo trabajamos como si fuera nica. La abstraccin nos brinda la posibilidad de ir definiendo una serie de refinamientos sucesivos a nuestro TDA y entindase bien que cuando decimos refinamientos sucesivos nos estamos refiriendo a la estrategia que se utiliza para descomponer un problema en subproblemas. Conforme evoluciona el diseo de software a cada nivel de mdulos se representa un refinamiento en el nivel de abstraccin. Esto es, incluir detalles que fueron obviados en un nivel superior, en un nivel ms bajo de la jerarqua. Veamos los diferentes tipos de abstraccin que podemos encontrar en un programa:

1. Abstraccin funcional: crear procedimientos y funciones e invocarlos mediante un nombre donde se destaca qu hace la funcin y se ignora cmo lo hace. El usuario slo necesita conocer la especificacin de la abstraccin (el qu) y puede ignorar el resto de los detalles (el cmo). 2. Abstraccin de datos: Tipo de datos: proporcionado por los leguajes de alto nivel. La representacin usada es invisible al programador, al cual solo se le permite ver las operaciones predefinidas para cada tipo. Tipos definidos: por el programador que posibilitan la definicin de valores de datos ms cercanos al problema que se pretende resolver. TDA: para la definicin y representacin de tipos de datos (valores + operaciones), junto con sus propiedades. Objetos: Son TDA a los que se aade propiedades de reutilizacin y de comparticin de cdigo. Si profundizamos ms al mundo de la programacin y sus conceptos, existen dos de estos conceptos que no se deben confundir, ellos son: tipo de datos y estructura de datos. Un tipo de dato, en un lenguaje de programacin, define un conjunto de valores que una determinada variable puede tomar, as como las operaciones bsicas sobre dicho conjunto. Ahora veamos como se van relacionando estos conceptos. Los tipos de datos constituyen un primer nivel de abstraccin, ya que no se tiene en cuenta cmo se implementan o se representan realmente la informacin sobre la memoria de la mquina. Para el usuario, el proceso de implementacin o representacin es invisible. Veamos entonces que son las estructuras de datos. Las estructuras de datos son colecciones de variables, no necesariamente del mismo tipo, relacionadas entre s de alguna forma. Las estructuras de datos estn caracterizadas por el tipo de dato de los elementos guardados en la estructura y por la relacin definida sobre estos elementos. Al nivel de las estructuras de datos son totalmente irrelevantes las operaciones sobre un elemento en particular, solamente tienen carcter relevante las operaciones que envuelvan la estructura de forma global. Ejemplos de utilizacin de TDAs Algunos ejemplos de utilizacin de TDAs en programacin son: Conjuntos: Implementacin de conjuntos con sus operaciones bsicas (unin, interseccin y diferencia), operaciones de insercin, borrado, bsqueda... rboles Binarios de Bsqueda: Implementacin de rboles de elementos, utilizados para la representacin interna de datos complejos. Aunque siempre se los toma como un TDA separado son parte de la familia de los grafos. Pilas y Colas: Implementacin de los algoritmos FIFO y LIFO. Grafos: Implementacin de grafos; una serie de vrtices unidos mediante una serie de arcos o aristas.

Recursin

Recurrencia, Recursin (incorrecto en castellano) o recursividad es la forma en la cual se especifica un proceso basado en su propia definicin. Siendo un poco ms precisos, y para evitar el aparente crculo sin fin en esta definicin: Un problema que pueda ser definido en funcin de su tamao, sea este N, pueda ser dividido en instancias ms pequeas (< N) del mismo problema y se conozca la solucin explcita a las instancias ms simples, lo que se conoce como casos base, se puede aplicar induccin sobre las llamadas ms pequeas y suponer que estas quedan resueltas.

Para que se entienda mejor a continuacin se exponen algunos ejemplos:

Factorial(x: Entero): Sea N := x el tamao del problema, podemos definir el problema de forma recurrente como x*Factorial(x - 1); como el tamao de Factorial(x - 1) es menor que N podemos aplicar induccin por lo que disponemos del resultado. El caso base es el Factorial(0) que es 1. Ordenacin por fusin(v: vector): Sea N := tamao(v), podemos separar el vector en dos mitades. Estas dos mitades tienen tamao N/2 por lo que por induccin podemos aplicar la ordenacin en estos dos subproblemas. Una vez tenemos ambas mitades ordenadas simplemente debemos fusionarlas. El caso base es ordenar un vector de 0 elementos, que est trivialmente ordenado y no hay que hacer nada.

En estos ejemplos podemos observar como un problema se divide en varias (>= 1) instancias del mismo problema, pero de tamao menor gracias a lo cual se puede aplicar induccin, llegando a un punto donde se conoce el resultado (el caso base).. Nota: aunque los trminos "recursin" y "recursividad" son ampliamente empleados en el campo de la informtica, el trmino correcto en castellano es recurrencia. Sin embargo este ltimo trmino es algo ms especfico. Vase relacin de recurrencia. Contenido

1 Los nmeros naturales 2 Funciones definidas de forma recurrente 3 Algoritmo recursivo 4 Ejemplos de recurrencias 5 Vase tambin 6 Enlaces externos

Los nmeros naturales Un ejemplo de conjunto definido de forma recurrente es el de los nmeros naturales: a) 0 pertenece a N b) Si n pertenece a N, entonces n+1 pertenece a N c) Si X verifica a) y b) , entonces N est incluido en X Los nmeros naturales es el conjunto de nmeros enteros no negativos. Funciones definidas de forma recurrente Aquellas funciones cuyo dominio puede ser recursivamente definido pueden ser definidas de forma recurrente. El ejemplo ms conocido es la definicin recurrente de la funcin factorial n!:

Con esta definicin veamos como funciona esta funcin para el valor del factorial de 3: 3! = 3 (3-1)! = 3 2! = 3 2 (2-1)! = 3 2 1!

= 3 2 1 (1-1)! = 3 2 1 0! =3211 =6 Algoritmo recursivo Artculo principal: Algoritmo recursivo Un mtodo usual de simplificacin de un problema complejo es la divisin de este en subproblemas del mismo tipo. Esta tcnica de programacin se conoce como divide y vencers y es el ncleo en el diseo de numerosos algoritmos de gran importancia, as como tambin es parte fundamental de la programacin dinmica.

El ejemplo del clculo recursivo del factorial de un nmero llevado al campo de la programacin, en este ejemplo C++: int factorial (int x) { if (x < 2) return 1; // Caso base: Cuando X < 2 devolvemos 1 puesto que 1! = 1 return x*factorial(x - 1); // Si X >= 2 devolvemos el producto de 'X' por el factorial de 'X'-1 } El seguimiento de la recursividad programada es casi exactamente igual al ejemplo antes dado, para intentar ayudar a que se entienda mejor se ha acompaado con muchas explicaciones y con colores que diferencia los distintos sub-procesos de la recursividad. X = 3 //Queremos 3!, por lo tanto X inicial es 3 X >= 2 -> return 3*factorial(2); X = 2 //Ahora estamos solicitando el factorial de 2 X >= 2 -> return 2*factorial(1); X = 1 // Ahora estamos solicitando el factorial de 1 X < 2 -> return 1; [En este punto tenemos el factorial de 1 por lo que volvemos marcha atrs resolviendo todos los resultados] return 2 [es decir: return 2*1 = return 2*factorial(1)] return 6 [es decir: return 3*2 = return 3*factorial(2)*factorial(1)] // El resultado devuelto es 6 Ejemplos de recurrencias Resolucin de ecuaciones homogneas de primer grado, segundo orden: a) Se pasan al primer miembro los trminos an, an 1, an 2, los cuales tambin podran figurar como an + 2, an + 1, an b) Se reemplaza an por r2, an 1 por r y an 2 por 1, quedando una ecuacin de segundo grado con races reales y distintas r1 y r2. c) Se plantea d) Debemos tener como dato los valores de los dos primeros trminos de la sucesin: Utilizando estos datos ordenamos el sistema de 2x2: y .

La resolucin de este sistema nos da como resultado los valores u0 y v0, que son nmeros reales conocidos. e) La solucin general es:

Algunos ejemplos de recurrencia:


Factorial -- n! = n (n-1)! Sucesin de Fibonacci -- f(n) = f(n-1) + f(n-2) Nmeros de Catalan -- C(2n, n)/(n+1) Las Torres de Hani Funcin de Ackermann

II. Arreglos
Objetivo General: El alumno elaborar programas que integren el uso de recursividad y definir estructuras de datos para generar alternativas de programacin. Contenido

1 Arrays y cadenas de texto 2 Indices de un arreglo 3 Dimensiones de un arreglo o 3.1 Arreglo unidimensional o 3.2 Arreglo bidimensional 4 Declaracin de arreglos en C, C++ o 4.1 Iteraciones dentro de un arreglo (vector) o 4.2 Iteraciones dentro de un arreglo (matriz) 5 Cadenas de caracteres o 5.1 La biblioteca string 6 Cadenas en C++ 7 Arreglos en C++

Arrays y cadenas de texto Los arreglos son usados extensamente por los programadores para contener listas de datos en la memoria, por ejemplo, los datos almacenados en un disco suelen leerse y ponerse dentro de un arreglo con el objetivo de facilitar la manipulacin de dichos datos, ya que los datos en memoria pueden ser modificados, clasificados, marcados para su eliminacion, etc. para luego ser reescritos al disco. Otro ejemplo podra ser el de un men de opciones que se desplegarn dentro de una ventana para que el usuario pueda elegir una de stas, en tales casos y cuando las opciones son numerosas, solamente se ponen unas cuantas de ellas dentro de la ventana pero se le da al usuario la oportunidad de poder subir y bajar a su antojo para ver el resto de opciones que, aunque no se vean en la ventana, forman parte del men o arreglo de opciones. Arreglo:

Un arreglo es un conjunto de datos del mismo tipo ordenados en forman lneal uno despues de otro. Los componentes de un arreglo se han de referenciar por medio del nombre del arreglo y un ndice de desplazamiento para indicar el componente deseado. Indices de un arreglo Los ndices son nmeros que se utilizan para identificar a cada uno de los componentes de un arreglo. A manera de ejemplo, podemos pensar que los ndices son como los nmeros de cuartos de un hotel, es decir, para poder dirijirnos a un hotel especfico es necesario saber el nombre del mismo, luego, si queremos llegar a un cuarto especfico de dicho hotel necesitaremos, ademas del nombre del hotel, el nmero de cuarto deseado. Tambin, podemos pensar en los casilleros del estante de paquetera de un supermercado, asi que si deseamos guardar o retirar un paquete nos direjimos a paquetera el cual sera el nombre del arreglo; luego, el empleado guarda nuestro paquete dentro de un casillero con un nmero especfico el cual sera el ndice para identificar el lugar del casillero en paquetera donde qued guardado el paquete. Supongamos que el empleado nos entrega una ficha con el nmero 12 impreso sobre la misma; de tal manera que podemos decir que nuestro paquete est guardado en paqueteria(12) paqueteria[12]. Dimensiones de un arreglo De acuerdo a la forma en que se construye o declara un arreglo, ste puede ser clasificado como: unidimensional, bidimensional y multidimensional. Los arreglos que se emplean con mucha ms frecuencia son los estructurados a manera de vector ( arreglo unidimensional ) y los estructurados a manera de matriz ( arreglo bidimensional ), as, aunque en C++ se pueden crear estructuras multidimensionales, en este captulo solo trataremos con vectores y matrices. Arreglo unidimensional

Una arreglo uni-dimensional es aquel en donde los componentes son accesados por medio de uno y solamente un ndice que apunte al componente requerido. Los arreglos de este tipo son conocidos tambin con el nombre de vectores. Conceptualmente, podemos pensar en un arreglo unidimensional como en una lista compuesta de lneas o filas en donde para referinos a una de ellas emplearemos un nmero para indicar la posicin de la misma dentro de la lista. Por ejemplo, consideremos el caso de la tabla o arreglo VentaSemanal, la cual est pensada para registrar las ventas de cada uno de los das de la semana. Luego, de manera conceptual podemos ver al arreglo como se muestra en seguida. Nota: en C++ los arreglos estan basados en 0 ( cero ), es decir, el primer elemento de un arreglo se indexa mediante el 0, y el ndice para el ltimo de los elementos es igual al nmero de componentes menos uno. arreglo: VentaSemanal +------+ | dato | <-- componente 0, ( fila 0 ) |------| | dato | <-- componente 1, ( fila 1 ) |------| | dato | ... |------| | dato | ... |------| | dato | ... |------| | dato | ... |------| | dato | <-- componente 6, ( fila 6 ) |------|

Si en el arreglo VentaSemanal queremos que el elemento 4 ( por ejemplo ) contenga el valor de 8987 lo podemos lograr con la instruccin: VentaSemanal[4] = 8987; y el estado del arreglo sera: arreglo: VentaSemanal +------+ | dato | |------| | dato | |------| | dato | |------| | dato | |------| | 8987 | <--- componente 4 |------| | dato | |------| | dato | |------| Arreglo bidimensional

Una arreglo bi-dimensional es aquel en donde los componentes son accesados por medio de una pareja de ndices que apunten a la fila y columna del componente requerido. Los arreglos de este tipo son conocidos tambin con el nombre de matrices. Conceptualmente, podemos pensar en un arreglo bidimensional como en una lista compuesta de filas y columnas, en donde para referinos a una de ellas emplearemos un nmero para indicar la posicin de fila y otro nmero para indicar la posicin de la columna del componente deseado. Por ejemplo, consideremos el caso de la tabla o arreglo VentaSemanaQ, la cual est pensada para registrar las ventas de cada uno de los das de la semana por cuatro semanas, o sea , una tabla de 7 x 4 elementos. Luego, de manera conceptual podemos ver al arreglo como se muestra en seguida. arreglo: VentaSemanaQ COLUMNAS +--- componente ( 0, 0 ) | +------+------+------+------+ | dato | dato | dato | dato | |------|------|------|------| F | dato | dato | dato | dato | |------|------|------|------| I | dato | dato | dato | dato | |------|------|------|------| L | dato | dato | dato | dato | |------|------|------|------| A | dato | dato | dato | dato | |------|------|------|------| S | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | +------+------+------+------+ | +---- componente ( 6, 3 )

Si en el arreglo VentaSemanaQ queremos que el elemento de la fila 4, columna 3 ( por ejemplo ) contenga el valor de 5000 lo podemos lograr con la instruccin: VentaSemanaQ[4][3] = 5000; y el estado del arreglo sera: arreglo: VentaSemanaQ +--- componente ( 0, 0 ) | +------+------+------+------+ | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | 5000 | <-- componente ( 4, 3 ) |------|------|------|------| | dato | dato | dato | dato | |------|------|------|------| | dato | dato | dato | dato | +------+------+------+------+ | +---- componente ( 6, 3 ) Declaracin de arreglos en C, C++ En C, C++ para declarar un arreglo se emplea la sintaxis: tipo identificador [tamao] = { lista de inicializacin } ; donde,

tipo se refiere al tipo de datos que contendr el arreglo. El tipo puede ser cualquiera de los tipos estndar (char, int, float, etc.) o un tipo definido por el usuario. Es ms, el tipo del arreglo puede ser de una estructura creada con: struct, union y class. identificador se refiere al nombre que le daremos al arreglo. tamao es opcional e indica el nmero de elementos que contendr el arreglo. Si un arreglo se declara sin tamao, el mismo no podr contener elemento alguno a menos que en la declaracin se emplee una lista de inicializacin. lista de inicializacin es opcional y se usa para establecer valores para cada uno de los componentes del arreglo. Si el arreglo es declarado con un tamao especifco el nmero de valores inicializados no podr ser mayor a dicho tamao.

Ejemplos: int intA[5]; long longA[5] = { 1, 2, 3, 4, 5 }; char charA[3] = { 'a', 'b', 'c' ); Iteraciones dentro de un arreglo (vector) El termino Iterar se refiere al hecho de acceder ( con el fin de leer o escribir) sobre cada uno de los componentes de un arreglo. As, para poner un ejemplo reconsideremos el caso de la tabla VentaSemanal (vista

en una seccin anterior), y que dicho sea de paso es un arreglo de 7 elementos de tipo double. Luego, vamos a mostrar como ejemplo un programa completo en el cual se declara el arreglo mencionado con valores inicializados, mismos que sern mostrados en pantalla y al final la suma de estos. Observe que la variable i usada para iterar dentro del arreglo va desde 0 hasta FILAS - 1 ( FILAS es el tamao del arreglo ). Nota: por motivos de simplificacin el programa est escrito al estilo de C estndar. Sin embargo puede ser compilado y ejecutado en un compilador de C++. #include <stdio.h> #include <stdlib.h> #define FILAS 7 int main() { float ventas[FILAS] = { 123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 0.0 }; float total = 0; int i; puts("Ventas de la semana"); puts("-------------------"); for (i=0; i<FILAS; i++) { total += ventas[i]; printf( "%8.2f\n", ventas[i] ); } puts("--------"); printf("%8.2f\n", total ); system("pause"); return 0; } Esta es la salida del programa: Ventas de la semana ------------------123.50 234.60 345.45 321.40 345.00 456.65 0.00 -------1826.60 Iteraciones dentro de un arreglo (matriz) Con el fin de leer o escribir sobre cada uno de los componentes de una matriz se deben crear dos ciclos de iteracin. As, para poner un ejemplo reconsideremos el caso de la tabla VentaSemanaQ (vista en una seccin anterior), y que dicho sea de paso es un arreglo de 4 x 4 elementos de tipo double. Luego, vamos a mostrar como ejemplo un programa completo en el cual se declara el arreglo mencionado con valores inicializados, mismos que sern mostrados en pantalla y al final la suma de estos. Observe que en este caso se utilizan dos variables, una para iterar sobre las filas y otra para iterar sobre las columnas de la matriz.

#include <stdio.h> #include <stdlib.h> #define FILAS 7 #define COLS 4 int main() { float VentaSemanaQ[FILAS][COLS] = { 123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 123.50, 234.60, 345.45, 321.40, 345.00, 456.65, 0.0, 0.0, 0.0, 0.0 }; float totales[COLS] = { 0.0, 0.0, 0.0, 0.0 }; float grantotal = 0; int f, c, t = 0 ; /* indices para filas, columnas y totales */ puts("Ventas de cuatro semanas"); puts("------------------------"); for (f=0; f<FILAS; f++) { for (c=0; c<COLS; c++) { totales[c] += ventas[f][c]; printf("%8.2f ", ventas[f][c] ); } puts(""); } puts("--------------------------------------"); for (t=0; t<COLS; t++) { printf("%8.2f ", totales[t] ); grantotal += totales[t]; } printf("\n\nGran total: %10.2f\n", grantotal); system("pause"); return 0; }

Salida del programa: Ventas de cuatro semanas -----------------------123.50 234.60 345.45 321.40 345.00 456.65 123.50 234.60 345.45 321.40 345.00 456.65 123.50 234.60 345.45 321.40 345.00 456.65 123.50 234.60 345.45 321.40 345.00 456.65 0.00 0.00 0.00 0.00

-------------------------------------1627.90 2025.30 1627.90 2025.30 Gran total: 7306.40

Cadenas de caracteres En C,C++ las cadenas de caracteres no son ms que arreglos de caracteres, salvo que a este tipo de arreglos el compilador les da un tratamiento especial. Usted puede manipular las cadenas de caracteres de la misma manera en que manipula cualquier otro tipo de arreglo, sin embargo, es preferible hacer uso de una librera estndar especialmente escrita para manipulacion de cadenas de caracteres, me refiero a la librera <string.h> y que viene incluida con todo compilador de C,C++. Para comenzar y antes de ver algunas de las funciones de la mencionada librera, tenemos los siguientes ejemplos: 1. char nombre[] = "Oscar"; 2. char nombre2[] = { 'O', 's', 'c', 'a', 'r', '\0' };

En el ejemplo 1 se est declarando la variable nombre como una cadena de caracteres y cuyo contenido inicial es "Oscar". En el ejemplo 2 se est declarando la variable nombre2 como una cadena de caracteres y cuyo contenido inicial es { 'O', 's', 'c', 'a', 'r', '\0' };.

En ambos casos el resultado es el mismo, es decir, al final se obtiene la misma cadena, pero usted debe poner atencin al hecho de que toda cadena de caracteres en C,C++ debe terminar con el caracter NULL, mismo que normalmente es igual a cero y se puede escribir como '\0'. Ahora bien, cuando usted usa la sintaxis mostrada en el ejemplo 1 no tiene que preocuparse por agregar el caracter NULL, ya que esto lo hace el compilador automticamente. La biblioteca string Los compiladores de C, C++ dan soporte a la biblioteca de funciones <string.h>, misma que accesible por medio de la directiva #include <string.h>. No veremos en detalle todas las funciones contenidas en dicha biblioteca, y nos limitaremos a mostrar algunos ejemplos de ciertas funciones importantes.

strlen(): Obtener longitud de cadenas Sintaxis: size_t strlen(const char *s); Comentarios: La funcin strlen() devuelve la longitud de la cadena s. Ejemplo: char *nombre = "Oscar E. Palacios"; cout << strlen(nombre) << endl; strcpy(): Copiar cadenas Sintaxis: char *stpcpy(char *dest, const char *src); Comentarios: stpcpy copia la cadena src hacia dest, la funcin termina hasta haber encontrado en src el caracter de terminacin null. Ejemplo: char *nombre = "Oscar E. Palacios";

char copia[80]; strcpy(copia, nombre); cout << copia << endl; strcat(): Concatenar cadenas Sintaxis: char *strcat(char *dest, const char *src); Comentarios: strcat agrega la cadena src a dest, la funcin termina hasta haber encontrado en src el caracter de terminacin null. Ejemplo: char nombre[] = "Oscar E."; char copia[80] = " Palacios"; strcat(copia, nombre); cout << copia << endl; strlwr(): Convertir a minsculas. Sintaxis: char *strlwr(char *dest); Comentarios: strlwr convierte todos los caracteres alfabticos ( 'A' .. 'Z' ) en dest a sus correspondientes caracteres alfabticos ( 'a' .. 'z' ). Ejemplo: char nombre[] = "Oscar E. Palacios"; strlwr(nombre); cout << nombre << endl; strupr(): Convertir a maysculas. Sintaxis: char *strupr(char *dest); Comentarios: strupr convierte todos los caracteres alfabticos ( 'a' .. 'z' ) en dest a sus correspondientes caracteres alfabticos ( 'A' .. 'Z' ).

strchr(): Buscar caracter ( hacia adelante ) Sintaxis: char *strchr(char *s, int c); Comentarios: strchr busca en s el caracter c. La busqueda se lleva a cabo desde el inicio hasta el final de s. Regreso: si la operacin es exitosa strchr regresa un puntero hacia la primera ocurrencia de c en s, en caso contrario strchr regresa null. Ejemplo: char nombre[] = "Oscar E. Palacios"; char *p; p = strchr(nombre, 'E'); if (p) { cout << "nombre contiene a E" << endl; cout << "indice = " << (p - nombre) << endl; } else cout << "E no est en nombre" << endl;

strrchr(): Buscar caracter ( hacia atras ) Sintaxis: char *strrchr(char *s, int c); Comentarios: strchr busca en s el caracter c. La busqueda se lleva a cabo desde el final hasta el inicio de s. Regreso: si la operacin es exitosa strchr regresa un puntero hacia la ltima ocurrencia de c en s, en caso contrario strchr regresa null. Ejemplo: char nombre[] = "Oscar E. Palacios"; char *p; p = strrchr(nombre, 'E'); if (p) { cout << "nombre contiene a E" << endl; cout << "indice = " << (p - nombre) << endl; } else cout << "E no est en nombre" << endl;

strstr(): Buscar subcadena Sintaxis: char *strstr(const char *s1, char *s2); Comentarios: strstr busca en s1 la subcadena s2. La busqueda se lleva a cabo desde el el inicio hasta el final de s1. Regreso: si la operacin es exitosa strstr regresa un puntero hacia la primera ocurrencia de s2 en s1, en caso contrario strstr regresa null. Ejemplo: char s[] = "Un barco de tristeza"; char *p; p = strstr(s, "barco"); if (p) { cout << "barco est en s" << endl; cout << "indice = " << (p - s) << endl; } else cout << "barco no est en s" << endl; Cadenas en C++ En la seccin anterior descubrimos algunas funciones para trabajar con cadenas de caracteres al estilo de C estndar, si bien no est de ms tener tal conocimiento tambien es cierto que C++ es un lenguaje de programacn orientado al objeto, de tal manera que ciertos compiladores ( como el gcc, utilzado por Bloodshed Dev-C++ y otros tantos entornos de desarrolo ) dan soporte a la clase cstring, que no debe confundires con la <string.h>. Nota: Bloodshed Dev-C++ es un IDE (Editor con Depurador Integrado) para programar en C++ en un ambiente grfico para Windows, distibuido gratuitamente bajo licencia GPL GNU y usted puede encontrarlo aqu: www.bloodshed.net. Actualmente (febrero de 2008) se recomienda bajar la versin Dev-C++ 4.9.9.2.

Una de las ventajas que ofrece la clase cstring es que, a diferencia de las cadenas estndar, sta posee la capacidad de crecer o disminuir su tamao en tiempo de ejecucin. Adems, entre otras caracteristicas destacables, la clase string soporta operaciones de asignacin tales como: =, +, +=, etc.; y de comparacin tales como: ==, <=, etc. Para tener una idea bsica sobre las cadenas en C++ veamos el siguiente programa: Nota: en el programa se debe de observar el uso del operador de asignacin +=, algo que no es posible hacer con las cadenas estndar. // Ejemplo: demostracin de la clase string // Compilado y ejecutado con exito en Bloodshed Dev-C++ #include <cstdlib> #include <iostream> using namespace std; int main(int argc, char *argv[]) { string s("Hola, "); s += "cmo estan ustedes..."; cout << s << endl; system("PAUSE"); return EXIT_SUCCESS; } Un estudio exaustivo sobre la clase string requiere de un captulo completo, ya que la misma, segn el manual de referencia de C++, posee aproximadamente 33 mtodos y unos 7 constructores; adems de los atributos. Arreglos en C++ As como C++ da aternativas elegantes para la manipulacin de cadenas de caracteres, tambin da el soporte para la manipulacon de arreglos dinmicos. Este tema ser ampliado en el captulo Libreria de Plantillas Estndar STL, sin embargo para tener una idea de lo que vendr mostraremos aqu un ejemplo sencillo en donde se usar la clase plantilla vector. // Ejemplo: demostracin de la clase vector // Compilado y ejecutado con exito en Bloodshed Dev-C++ #include <cstdlib> #include <iostream> #include <vector> using namespace std; int main(int argc, char *argv[]) { // creacin de un vector de enteros vector<int> v; // metiendo datos en el vector for (int x = 500; x<1000; x+=50) v.push_back(x); // desplegando los datos del vector for (int x = 0; x < v.size(); x++) cout << "v[" << x << "] = " << v[x] << endl;

system("PAUSE"); return EXIT_SUCCESS; } Mtodos de ordenamiento y bsqueda

Ordenamiento de burbuja
El Ordenamiento de burbuja (Bubble Sort en ingls) es un sencillo algoritmo de ordenamiento. Funciona revisando cada elemento de la lista que va a ser ordenada con el siguiente, intercambindolos de posicin si estn en el orden equivocado. Es necesario revisar varias veces toda la lista hasta que no se necesiten ms intercambios, lo cual significa que la lista est ordenada. Este algoritmo obtiene su nombre de la forma con la que suben por la lista los elementos durante los intercambios, como si fueran pequeas "burbujas". Tambin es conocido como el mtodo del intercambio directo. Dado que solo usa comparaciones para operar elementos, se lo considera un algoritmo de comparacin, siendo el ms sencillo de implementar.

Contenido

1 Descripcin 2 Anlisis o 2.1 Rendimiento en casos ptimos o 2.2 Conejos y Tortugas (Yo-yos) (?) 3 En la prctica 4 Implementacin 5 Vase tambin 6 Enlaces externos

Descripcin
Una manera simple de expresar el ordenamiento de burbuja en pseudocdigo es la siguiente: En este algoritmo se trata de ordenar una lista de valores: a, de n trminos numerados del termino 0 al n-1, consta de dos bucles anidados uno con el ndice i, que acota el recorrido de la burbuja en sentido inverso de 2 a n, y un segundo bucle con el ndice j, con un recorrido desde 0 hasta n-i, para cada iteracin del primer bucle, que indica el lugar de la burbuja. La burbuja son dos trminos de la lista seguidos, j y j+1, que se comparan, si el primero es menor que el segundo sus valores se intercambian. Esta comparacin se repite en el centro de los dos bucles, dando lugar a la postre a una lista ordenada, puede verse que el nmero de repeticiones sola depende de n, y no del orden de los trminos, esto es si pasamos al

algoritmo una lista ya ordenada, realizara todas las comparaciones exactamente igual que para una lista no ordenada, esta es una caracterstica de este algoritmo, luego veremos una variante que evita este problema. Para comprender el funcionamiento, veamos un ejemplo sencillo: Partimos de una lista de nmeros que hay que ordenar:

Podemos ver que la lista tiene cinco trminos, luego:

El ndice i har un recorrido de 2 hasta n:

Que en este caso ser de 2 a 5. Para cada uno de los valores de i, j tomara sucesivamente los valores de 0 hasta n-i:

Para cada valor de j, obtenido en ese orden, se compara el valor del ndice j con el siguiente:

Si el termino j es menor, en su caso podra se mayor, que el termino j+1, los valores se permutan, en caso contrario se contina con la iteracin. Para el caso del ejemplo, tenemos que:

Para la primera iteracin del primer bucle:

y j tomara los valores de 0 hasta 3:

Cuando j vale 0, se comparan Ahora j vale 1 y se comparan

, el 55 y el 86, dado que 55 < 86 no se permutan el orden. el 86 y el 48 Como 86 > 48, se permutan, dando lugar a una nueva lista.

Se repite el proceso hasta que j valga 3, dando lugar a una lista parcialmente ordenada, podemos ver que el termino de mayor valor esta en el lugar ms alto. Ahora i vale 3, y j har un recorrido de 0 a 2. Primero j vale 0, se comparan , el 55 y el 48, como 55 > 48 se permutan dando lugar a la nueva lista. Para j = 1 se compara el 55 con el 16 y se cambian de orden. Para j = 2 se compara el 55 y el 82 y se dejan como estn, finalizando el bucle con una lista mejor ordenada, puede verse

que los dos valores ms altos ya ocupan su lugar. No se ha realizado ninguna comparacin con el termino cuarto, dado que ya se sabe que despus del primer ciclo es el mayor de la lista. El algoritmo consiste en comparaciones sucesivas de dos trminos consecutivos, ascendiendo de abajo a arriba en cada iteracin, como la ascensin de las burbujas de aire en el agua, de ah el nombre del procedimiento, en la primera iteracin el recorrido ha sido completo, en el segundo se ha dejado l ultimo termino, al tener ya el mayor de los valores, en los sucesivos s ira dejando re realizar las ultimas comparaciones, como se puede ver. Ahora ya i vale 4 y j recorrer los valores de 0 a 1. Cuando j vale 0, se comparan esto es el 48 y el 16 dado que 48 es mayor que 16 se permutan los valores, dando lugar a una lista algo ms ordenada que la anterior, desde esta nueva ordenacin, j pasa a valer 1, con lo que se comparan los trminos el 48 y el 55 que quedan en el mismo orden. En este caso la burbuja ha ascendido menos que en los casos anteriores, y la lista esta ya ordenada, pero el algoritmo tendr que completarse, realizando una ultima iteracin. Hay que tener en cuenta que el bucle para realiza un nmero fijo de repeticiones y para finalizar tendrn que completarse, aun en el caso extremo, de que la lista estara previamente ordenada. Por ultimo i vale 5 y j solo puede vale 0, con lo que solo se realizara una comparacin de que ya estn ordenados y se dejan igual. Los bucles finalizan y tambin el procedimiento, dejando la lista ordenada. Una variante que finaliza en caso de que la lista este ordenada, puede ser la siguiente, empleando un centinela ordenado, que detecta que no se ha modificado la lista en un recorrido de la burbuja, y que por tanto la lista ya esta ordenada, finalizando. el 16 y el 48,

Anlisis
Ejemplo del ordenamiento de burbuja ordenando una lista de nmeros aleatorios. Rendimiento en casos ptimos El ordenamiento de burbuja tiene una complejidad(n). Cuando una lista ya est ordenada, a diferencia del ordenamiento por insercin que pasar por la lista una vez, y encontrar que no hay necesidad de intercambiar las posiciones de los elementos, el mtodo de ordenacin por burbuja est forzado a pasar por dichas comparaciones, lo que hace que su complejidad sea cuadrtica en el mejor de los casos, esto lo cataloga como el algoritmo mas ineficiente que existe aunque para muchos programadores sea el ms sencillo de implementar. Conejos y Tortugas (Yo-yos) (?) La posicin de los elementos en el ordenamiento de burbuja juegan un papel muy importante en la determinacin del rendimiento. Los elementos mayores al principio de la lista son rpidamente movidos hacia abajo. En cambio, elementos menores en el fondo de la lista, se mueven a la parte superior muy lentamente. Esto llev a nombrar estos elementos conejos y tortugas, respectivamente. Varios esfuerzos se han realizado para eliminar las tortugas vase Exterminacin y mejorar la velocidad del ordenamiento de burbuja, la cual ser ms redonda que nunca. El Ordenamiento por sacudida es un buen ejemplo, aunque an mantiene, en el peor de los casos, una complejidad O (n2). El ordenamiento por combinacin compara los elementos primero en pedazos grandes de la lista, moviendo tortugas extremadamente rpido, antes de proceder a pedazos cada vez ms pequeos para alisar la lista. Su velocidad promedio es comparable a algoritmos rpidos (y complejos) como el ordenamiento rpido.

En la prctica
A pesar de que el ordenamiento de burbuja es uno de los algoritmos ms sencillos de implementar, su orden O (n2) lo hace muy ineficiente para usar en listas que tengan ms que un nmero reducido de elementos. Incluso entre los algoritmos de ordenamiento de orden O (n2), otros procedimientos como el Ordenamiento por insercin son considerados ms eficientes.

Dada su simplicidad, el ordenamiento de burbuja es utilizado para introducir el concepto de algoritmo, o de algoritmo de ordenamiento para estudiantes de ciencias de la computacin. Aunque algunos investigadores como Owen Astrachan han criticado al ordenamiento de burbuja y su popularidad en la educacin de la computacin, recomendando que ya no debe ser enseado. El ordenamiento de burbuja es asintticamente equivalente, en tiempos de ejecucin con el Ordenamiento por insercin en el peor de los casos, pero ambos algoritmos difieren principalmente en la cantidad de intercambios

que son necesarios. Resultados experimentales, como los descubiertos por Astrachan han demostrado que el ordenamiento por insercin funcionan considerablemente mejor incluso con listas aleatorias. Por esta razn, muchos libros de algoritmos modernos evitan usar el ordenamiento de burbuja, utilizando en cambio el ordenamiento por insercin. El ordenamiento de burbuja interacta vagamente con el hardware de las CPU modernas. Requiere al menos el doble de escrituras que el ordenamiento por insercin, el doble de prdidas de cache, y asintticamente ms prediccin de saltos. Varios experimentos, hechos por Astrachan, de ordenamiento de cadenas en Java, muestran que el ordenamiento de burbuja es 5 veces ms lento que el ordenamiento por insercin y 40% ms lento que el ordenamiento por seleccin.

Implementacin
A continuacin se muestra el Ordenamiento de burbuja en distintos lenguajes de programacin:

void ordenamientoBurbuja(int v[], int util_v) { int temp, i, j; for (i = 0; i < util_v - 1; i++) { for (j = i + 1; j < util_v; j++) { if (v[i] > v[j]) { temp = v[i]; v[i] = v[j]; v[j] = temp; } } } }

C++

template<typename _Ty> void bubble_sort(vector<_Ty> & v){ for (size_t i = 0; i < v.size() - 1; ++i){ for (size_t j = i + 1; j < v.size(); ++j){ if (v[i] > v[j]) swap(v[i], v[j]); } } }

C#
Public int[] OrdenarBurbuja(int[]x) { int t= x.Length, temp; for(int i=1 ; i< t ; i++) for(int j = t-1 ; j >= i; j--) { if(x[j] < x[j-1]) { temp= x[j]; x[j]= x[j-1]; x[j-1]= temp; } } }

Java

//Ordenamiento por Burbuja // by ramses2999 public static int[] OrdenarBurbuja(int[] n){ int temp; int t = n.length; for (int i = 1; i < t; i++) { for (int k = t- 1; k >= i; k--) { if(n[k] < n[k-1]){ temp = n[k]; n[k] = n[k-1]; n[k-1]= temp; }//fin if }// fin 2 for }//fin 1 for return n; }//fin

Quicksort
Quicksort en accin sobre una lista de nmeros aleatorios. Las lneas horizontales son valores pivote. El ordenamiento rpido (quicksort en ingls) es un algoritmo basado en la tcnica de divide y vencers, que permite, en promedio, ordenar n elementos en un tiempo proporcional a n log n.

Contenido

1 Descripcin del algoritmo o 1.1 Demostracin 2 Optimizacin del algoritmo o 2.1 Tcnicas de eleccin del pivote o 2.2 Tcnicas de reposicionamiento o 2.3 Transicin a otro algoritmo 3 Ejemplo 4 Implementaciones 5 Vase tambin 6 Referencias

Descripcin del algoritmo


El algoritmo fundamental es el siguiente:

Elegir un elemento de la lista de elementos a ordenar, al que llamaremos pivote. Resituar los dems elementos de la lista a cada lado del pivote, de manera que a un lado queden todos los menores que l, y al otro los mayores. Los elementos iguales al pivote pueden ser colocados tanto a su derecha como a su izquierda, dependiendo de la implementacin deseada. En este momento, el pivote ocupa exactamente el lugar que le corresponder en la lista ordenada. La lista queda separada en dos sublistas, una formada por los elementos a la izquierda del pivote, y otra por los elementos a su derecha. Repetir este proceso de forma recursiva para cada sublista mientras stas contengan ms de un elemento. Una vez terminado este proceso todos los elementos estarn ordenados.

Como se puede suponer, la eficiencia del algoritmo depende de la posicin en la que termine el pivote elegido.

En el mejor caso, el pivote termina en el centro de la lista, dividindola en dos sublistas de igual tamao. En este caso, el orden de complejidad del algoritmo es O(nlog n). En el peor caso, el pivote termina en un extremo de la lista. El orden de complejidad del algoritmo es entonces de O(n). El peor caso depender de la implementacin del algoritmo, aunque habitualmente ocurre en listas que se encuentran ordenadas, o casi ordenadas. Pero principalmente depende del pivote, si por ejemplo el algoritmo implementado toma como pivote siempre el primer elemento del array, y el array que le pasamos est ordenado, siempre va a generar a su izquierda un array vaco, lo que es ineficiente.

En el caso promedio, el orden es O(nlog n).

No es extrao, pues, que la mayora de optimizaciones que se aplican al algoritmo se centren en la eleccin del pivote. Demostracin Podramos probar el orden de ejecucin en el mejor caso de la siguiente manera: Vamos a suponer que el nmero total de elementos a ordenar es potencia de dos, es decir, n = 2k. de aqu podemos ver que k = log2(n), donde k es el nmero de divisiones que realizar el algoritmo. En la primera fase del algoritmo habrn n comparaciones, en la segunda fase el algoritmo crear dos sublistas aproximadamente de tamao n/2. El nmero total de comparaciones de estas dos sublistas es: 2(n/2) = n. En la tercera fase el algoritmo procesar 4 sublistas ms, por tanto el nmero total de comparaciones en esta fase es 4(n/4) = n. En conclusin, el nmero total de comparaciones que hace el algoritmo es: n + n + n + ..... + n = kn, donde k = log2(n), por tanto el tiempo de ejecucin del algoritmo en el mejor caso es O(n.log2n)

Optimizacin del algoritmo


Cabe destacar que de usarse en su versin recursiva las siguientes optimizaciones y sus desventajas no se ven vistas en el tiempo de ejecucin del mismo mantenindose, as el tiempo de ejecucin planteado en un principio. Tcnicas de eleccin del pivote El algoritmo bsico Quicksort permite tomar cualquier elemento de la lista como pivote, dependiendo de la particin n que se elija, el algoritmo ser ms o menos eficiente.

Tomar un elemento cualquiera como pivote tiene la ventaja de no requerir ningn clculo adicional, lo cual lo hace bastante rpido. Sin embargo, esta eleccin a ciegas siempre provoca que el algoritmo tenga un orden de O(n) para ciertas permutaciones de los elementos en la lista. Otra opcin puede ser recorrer la lista para saber de antemano qu elemento ocupar la posicin central de la lista, para elegirlo como pivote. Esto puede hacerse en O(n) y asegura que hasta en el peor de los casos, el algoritmo sea O(nlog n). No obstante, el clculo adicional rebaja bastante la eficiencia del algoritmo en el caso promedio. La opcin a medio camino es tomar tres elementos de la lista - por ejemplo, el primero, el segundo, y el ltimo - y compararlos, eligiendo el valor del medio como pivote.

Tcnicas de reposicionamiento Una idea preliminar para ubicar el pivote en su posicin final sera contar la cantidad de elementos menores que l, y colocarlo un lugar ms arriba, moviendo luego todos esos elementos menores que l a su izquierda, para que pueda aplicarse la recursividad. Existe, no obstante, un procedimiento mucho ms efectivo. Se utilizan dos ndices: i, al que llamaremos ndice izquierdo, y j, al que llamaremos ndice derecho. El algoritmo es el siguiente:

Recorrer la lista simultneamente con i y j: por la izquierda con i (desde el primer elemento), y por la derecha con j (desde el ltimo elemento). Cuando lista[i] sea mayor que el pivote y lista[j] sea menor, se intercambian los elementos en esas posiciones. Repetir esto hasta que se crucen los ndices. El punto en que se cruzan los ndices es la posicin adecuada para colocar el pivote, porque sabemos que a un lado los elementos son todos menores y al otro son todos mayores (o habran sido intercambiados).

Transicin a otro algoritmo Como se coment antes, el algoritmo quicksort ofrece un orden de ejecucin O(n) para ciertas permutaciones "crticas" de los elementos de la lista, que siempre surgen cuando se elige el pivote a ciegas. La permutacin concreta depende del pivote elegido, pero suele corresponder a secuencias ordenadas. Se tiene que la probabilidad de encontrarse con una de estas secuencias es inversamente proporcional a su tamao.

Los ltimos pases de quicksort son numerosos y ordenan cantidades pequea de elementos. Un porcentaje medianamente alto de ellos estarn dispuestos de una manera similar al peor caso del algoritmo, volviendo a ste ineficiente. Una solucin a este problema consiste en ordenar las secuencias pequeas usando otro algoritmo. Habitualmente se aplica el algoritmo de insercin para secuencias de tamao menores de 8-15 elementos. Pese a que en secuencias largas de elementos la probabilidad de hallarse con una configuracin de elementos "crtica" es muy baja, esto no evita que sigan apareciendo (a veces, de manera intencionada). El algoritmo introsort es una extensin del algoritmo quicksort que resuelve este problema utilizando heapsort en vez de quicksort cuando el nmero de recursiones excede al esperado.

Parmetros: o Se debe llamar a la funcin Quicksort desde donde quiera ejecutarse o sta llamar a colocar pivote para encontrar el valor del mismo o Se ejecutar el algoritmo Quicksort de forma recursiva a ambos lados del pivote

int colocar(int *v, int b, int t) { int i; int pivote, valor_pivote; int temp; pivote = b; valor_pivote = v[pivote]; for (i=b+1; i<=t; i++){ if (v[i] < valor_pivote){ pivote++; temp=v[i]; v[i]=v[pivote]; v[pivote]=temp; } }

temp=v[b]; v[b]=v[pivote]; v[pivote]=temp; return pivote; }

void Quicksort(int* v, int b, int t) { int pivote; if(b < t){ pivote=colocar(v, b, t); Quicksort(v, b, pivote-1); Quicksort(v, pivote+1, t); } }

Nota: Los tres parmetros de la llamada inicial a Quicksort sern array[0], 0, numero_elementos -1, es decir, si es un array de 6 elementos array[0], 0, 5

Ejemplo
En el siguiente ejemplo se marcan el pivote y los ndices i y j con las letras p, i y j respectivamente. Comenzamos con la lista completa. El elemento pivote ser el 4:
5 - 3 - 7 - 6 - 2 - 1 - 4 p

Comparamos con el 5 por la izquierda y el 1 por la derecha.


5 - 3 - 7 - 6 - 2 - 1 - 4 i j p

5 es mayor que 4 y 1 es menor. Intercambiamos:


1 - 3 - 7 - 6 - 2 - 5 - 4 i j p

Avanzamos por la izquierda y la derecha:


1 - 3 - 7 - 6 - 2 - 5 - 4 i j p

3 es menor que 4: avanzamos por la izquierda. 2 es menor que 4: nos mantenemos ah.
1 - 3 - 7 - 6 - 2 - 5 - 4 i j p

7 es mayor que 4 y 2 es menor: intercambiamos.


1 - 3 - 2 - 6 - 7 - 5 - 4 i j p

Avanzamos por ambos lados:


1 - 3 - 2 - 6 - 7 - 5 - 4 iyj p

En este momento termina el ciclo principal, porque los ndices se cruzaron. Ahora intercambiamos lista[i] con lista[sup] (pasos 16-18):
1 - 3 - 2 - 4 - 7 - 5 - 6 p

Aplicamos recursivamente a la sublista de la izquierda (ndices 0 - 2). Tenemos lo siguiente:


1 - 3 - 2

1 es menor que 2: avanzamos por la izquierda. 3 es mayor: avanzamos por la derecha. Como se intercambiaron los ndices termina el ciclo. Se intercambia lista[i] con lista[sup]:
1 - 2 - 3

El mismo procedimiento se aplicar a la otra sublista. Al finalizar y unir todas las sublistas queda la lista inicial ordenada en forma ascendente.
1 - 2 - 3 - 4 - 5 - 6 - 7

Implementaciones
El algoritmo de ordenamiento rpido (Quicksort) en: Pseudocdigo
function quicksort(array) var list, less, greater if length(array) 1 return array seleccionar y eliminar un valor pivote pivot en el array for each x in array if x < pivot then aadir x a less else aadir x a greater return concadenar(quicksort(less), pivot, quicksort(greater))

C
int pivotar (int v[], int izq, int der) { int posicionPivote = (izq + der) / 2; int valorPivote = v[posicionPivote]; int indiceAlmacenamiento; swap (v, posicionPivote, der);//Se situal pivote al final del vector indiceAlmacenamiento = izq; for (int indiceLectura = izq; indiceLectura < der; indiceLectura++){ if (v[indiceLectura] <= valorPivote){ swap (v, indiceLectura, indiceAlmacenamiento); indiceAlmacenamiento++; } }

swap (v, indiceAlmacenamiento, der); //Se coloca el pivote en su lugar. return indiceAlmacenamiento; } void quicksort (int v[], int izq, int der) { int pivote; if(izq < der){ pivote = pivotar (v, izq, der); //Esta operacin coloca el pivote en su lugar. quicksort(v, izq, pivote-1); quicksort(v, pivote+1, der); } }

Otra en C. Trabaja slo con punteros (no ndices). Ordena enteros de menor a mayor. Pivot: El primero del vector.
void quicksort(int* izq, int* der) /*Se llama con: quicksort(&vector[0],&vector[n-1]);*/ { if(der<izq) return; int pivot=*izq; int* ult=der; int* pri=izq; while(izq<der) { while(*izq<=pivot && izq<der+1) izq++; while(*der>pivot && der>izq-1) der--; if(izq<der) swap(izq,der); } swap(pri,der); quicksort(pri,der-1); quicksort(der+1,ult); } void swap(int* a, int* b) { int temp=*a; *a=*b; *b=temp; }

Java
//Recibe un vector de enteros y el ndice del primer y ltimo elemento vlido del mismo void ordenarQuicksort(int[] vector, int primero, int ultimo){ int i=primero, j=ultimo; int pivote=vector[(primero + ultimo) / 2]; int auxiliar; do{ while(vector[i]<pivote) i++; while(vector[j]>pivote) j--; if (i<=j){ auxiliar=vector[j]; vector[j]=vector[i]; vector[i]=auxiliar; i++; j--; } } while (i<=j);

if(primero<j) ordenarQuicksort(vector,primero, j); if(ultimo>i) ordenarQuicksort(vector,i, ultimo); }

C#
void Quicksort(int[] v, int prim, int ult) { if (prim < ult) { /* Selecciona un elemento del vector y coloca los menores que l a su izquierda y los mayores a su derecha */ int p = Pivote(v, prim, ult, ult); /* Repite el particiones Quicksort(v, Quicksort(v, } } /* Implementacin no clsica de la funcin Pivote. En lugar de recorrer el vector simultneamente desde ambos extremos hasta el cruce de ndices, se recorre desde el comienzo hasta el final */ int Pivote(int[] v, int prim, int ult, int piv) { int p = v[piv]; int j = prim; // Mueve el pivote a la ltima posicin del vector Intercambia(v, piv, ult); /* Recorre o iguales for (int i { if { } } // Mueve el pivote a la posicin que le corresponde Intercambia(v, j, ult); return j; } void Intercambia(int[] v, int a, int b) { if (a != b) { int tmp = v[a]; v[a] = v[b]; v[b] = tmp; } } el vector moviendo los elementos menores que el pivote al comienzo del mismo */ = prim; i < ult; i++) (v[i] <= p) Intercambia(v, i, j); j++; proceso para cada una de las generadas en el paso anterior */ prim, p - 1); p + 1, ult);

Python
def quicksort(datos, primero, ultimo): i = primero j = ultimo pivote = (datos[primero] + datos[ultimo]) / 2 while i < j:

while datos[i] < pivote: i+=1 while datos[j] > pivote: j-=1 if i <= j: aux = datos[i] datos[i] = datos[j] datos[j] = aux i+=1 j-=1 if primero < j: datos = quicksort(datos, primero, j) if ultimo > i: datos = quicksort(datos, i, ultimo) return datos

Otra en Python
def qsort(list): try: x=list.pop() except: return [] return qsort(filter((lambda y: y<x), list)) + [x] + qsort(filter((lambda y: y>=x), list))

Haskell
qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)

Otra en Haskell
qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort elmts_lt_x ++ [x] ++ qsort elmts_greq_x where elmts_lt_x = [y | y <- xs, y < x] elmts_greq_x = [y | y <- xs, y >= x]

Asm
quicksort: push ebp mov ebp,esp push esi push ebx push ecx push edx mov ebx,dword[ebp + 12] mov ecx,dword[ebp + 16] cdq mov eax, ebx add eax, ecx push ecx mov ecx,2 div ecx pop ecx xchg edx,eax mov esi, [ebp + 8] mov edx,dword[esi + edx * 4] qs@L1: qs@L1@L1:

cmp dword[esi + ebx * 4],edx jge qs@L1@L1@out inc ebx jmp qs@L1@L1 qs@L1@L1@out: qs@L1@L2: cmp dword[esi + ecx * 4],edx jle qs@L1@L2@out dec ecx jmp qs@L1@L2 qs@L1@L2@out: qs@L1@IF1: cmp ebx, ecx jg qs@L1@IF1@out mov eax, dword[esi + ebx * 4] xchg eax, dword[esi + ecx * 4] mov dword[esi + ebx * 4], eax inc ebx dec ecx qs@L1@IF1@out: cmp ebx,ecx jle qs@L1 qs@L1@out: qs@IF1: cmp dword[ebp + 12],ecx jge qs@IF1@out push ecx push dword[ebp + 12] push esi call quicksort qs@IF1@out: qs@IF2: cmp ebx, dword[ebp + 16] jge qs@IF2@out push dword[ebp + 16] push ebx push esi call quicksort qs@IF2@out: pop edx pop ecx pop ebx pop esi pop ebp retn 12

Prolog
quicksort([], []). quicksort([CABEZA | COLA], ORDENADO) :- partir(CABEZA, COLA, IZDA, DCHA), quicksort(IZDA, ORDENADO_IZDA), quicksort(DCHA, ORDENADO_DCHA), concatenar(ORDENADO_IZDA, [CABEZA | ORDENADO_DCHA], ORDENADO). partir(PIVOTE, [], [], []). partir(PIVOTE, [CABEZA | COLA], [CABEZA | IZDA], DCHA) :- CABEZA @=< PIVOTE, partir(PIVOTE, COLA, IZDA, DCHA). partir(PIVOTE, [CABEZA | COLA], IZDA, [CABEZA | DCHA]) :- CABEZA @> PIVOTE, partir(PIVOTE, COLA, IZDA, DCHA). concatenar([], LISTA, LISTA). concatenar([CABEZA | LISTA_1], LISTA_2, [CABEZA | LISTA_3]) :- concatenar(LISTA_1, LISTA_2, LISTA_3).

Ordenamiento Shell
El ordenamiento Shell (Shell sort en ingls) es un algoritmo de ordenamiento. El mtodo se denomina Shell en honor de su inventor Donald Shell. Su implementacin original, requiere O(n2) comparaciones e intercambios en el peor caso. Un cambio menor presentado en el libro de V. Pratt produce una implementacin con un rendimiento de O(nlog2 n) en el peor caso. Esto es mejor que las O(n2) comparaciones requeridas por algoritmos simples pero peor que el ptimo O(n log n). Aunque es fcil desarrollar un sentido intuitivo de cmo funciona este algoritmo, es muy difcil analizar su tiempo de ejecucin. El Shell sort es una generalizacin del ordenamiento por insercin, teniendo en cuenta dos observaciones: 1. El ordenamiento por insercin es eficiente si la entrada est "casi ordenada". 2. El ordenamiento por insercin es ineficiente, en general, porque mueve los valores slo una posicin cada vez. El algoritmo Shell sort mejora el ordenamiento por insercin comparando elementos separados por un espacio de varias posiciones. Esto permite que un elemento haga "pasos ms grandes" hacia su posicin esperada. Los pasos mltiples sobre los datos se hacen con tamaos de espacio cada vez ms pequeos. El ltimo paso del Shell sort es un simple ordenamiento por insercin, pero para entonces, ya est garantizado que los datos del vector estn casi ordenados.

Contenido

1 Ejemplo 2 Secuencia de espacios 3 Implementaciones o 3.1 ActionScript o 3.2 C o 3.3 C# o 3.4 Java o 3.5 Perl o 3.6 Python o 3.7 Visual Basic 4 Referencias 5 Enlaces externos

Ejemplo
Considere un pequeo valor que est inicialmente almacenado en el final del vector. Usando un ordenamiento O(n2) como el ordenamiento de burbuja o el ordenamiento por insercin, tomar aproximadamente n comparaciones e intercambios para mover este valor hacia el otro extremo del vector. El Shell sort primero mueve los valores usando tamaos de espacio gigantes, de manera que un valor pequeo se mover bastantes posiciones hacia su posicin final, con slo unas pocas comparaciones e intercambios. Uno puede visualizar el algoritmo Shell sort de la siguiente manera: coloque la lista en una tabla y ordene las columnas (usando un ordenamiento por insercin). Repita este proceso, cada vez con un nmero menor de columnas ms largas. Al final, la tabla tiene slo una columna. Mientras que transformar la lista en una tabla hace ms fcil visualizarlo, el algoritmo propiamente hace su ordenamiento en contexto (incrementando el ndice por el tamao de paso, esto es usando i += tamao_de_paso en vez de i++). Por ejemplo, considere una lista de nmeros como [ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ]. Si comenzamos con un tamao de paso de 5, podramos visualizar esto dividiendo la lista de nmeros en una tabla con 5 columnas. Esto quedara as:

13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10

Entonces ordenamos cada columna, lo que nos da


10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45

Cuando lo leemos de nuevo como una nica lista de nmeros, obtenemos [ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]. Aqu, el 10 que estaba en el extremo final, se ha movido hasta el extremo inicial. Esta lista es entonces de nuevo ordenada usando un ordenamiento con un espacio de 3 posiciones, y despus un ordenamiento con un espacio de 1 posicin (ordenamiento por insercin simple). El Shell sort lleva este nombre en honor a su inventor, Donald Shell, que lo public en 1959. Algunos libros de texto y referencias antiguas le llaman ordenacin "Shell-Metzner" por Marlene Metzner Norton, pero segn Metzner, "No tengo nada que ver con el algoritmo de ordenamiento, y mi nombre nunca debe adjuntarse a ste." [1]

Secuencia de espacios
La secuencia de espacios es una parte integral del algoritmo Shell sort. Cualquier secuencia incremental funcionara siempre que el ltimo elemento sea 1. El algoritmo comienza realizando un ordenamiento por insercin con espacio, siendo el espacio el primer nmero en la secuencia de espacios. Continua para realizar un ordenamiento por insercin con espacio para cada nmero en la secuencia, hasta que termina con un espacio de 1. Cuando el espacio es 1, el ordenamiento por insercin con espacio es simplemente un ordenamiento por insercin ordinario, garantizando que la lista final estar ordenada. La secuencia de espacios que fue originalmente sugerida por Donald Shell deba comenzar con N / 2 y dividir por la mitad el nmero hasta alcanzar 1. Aunque esta secuencia proporciona mejoras de rendimiento significativas sobre los algoritmos cuadrticos como el ordenamiento por insercin, se puede cambiar ligeramente para disminuir ms el tiempo necesario medio y el del peor caso. El libro de texto de Weiss demuestra que esta secuencia permite un ordenamiento O(n2) del peor caso, si los datos estn inicialmente en el vector como (pequeo_1, grande_1, pequeo_2, grande_2, ...) - es decir, la mitad alta de los nmeros estn situados, de forma ordenada, en las posiciones con ndice par y la mitad baja de los nmeros estn situados de la misma manera en las posiciones con ndice impar. Quizs la propiedad ms crucial del Shell sort es que los elementos permanecen k-ordenados incluso mientras el espacio disminuye. Se dice que un vector dividido en k subvectores esta k-ordenado si cada uno de esos subvectores esta ordenado en caso de considerarlo aislado. Por ejemplo, si una lista fue 5-ordenada y despus 3ordenada, la lista est ahora no slo 3-ordenada, sino tanto 5-ordenada como 3-ordenada. Si esto no fuera cierto, el algoritmo deshara el trabajo que haba hecho en iteraciones previas, y no conseguira un tiempo de ejecucin tan bajo. Dependiendo de la eleccin de la secuencia de espacios, Shell sort tiene un tiempo de ejecucin en el peor caso de O(n2) (usando los incrementos de Shell que comienzan con 1/2 del tamao del vector y se dividen por 2 cada vez), O(n3 / 2) (usando los incrementos de Hibbard de 2k 1), O(n4 / 3) (usando los incrementos de Sedgewick de 9(4i) 9(2i) + 1, o 4i + 1 + 3(2i) + 1), o O(nlog2n), y posiblemente mejores tiempos de ejecucin no comprobados. La existencia de una implementacin O(nlogn) en el peor caso del Shell sort permanece como una pregunta por resolver.

Implementaciones

El Shell sort se usa comnmente en lenguajes de programacin; esto es una implementacin del algoritmo en C/C++ para ordenar un vector de enteros. La secuencia de incrementos usada en este ejemplo de cdigo da un tiempo de ejecucin O(n2) en el peor caso. ActionScript
var arrayOriginal:Array = new Array(2,5,6,8,10,2,3,64,23,76,43,27,75,33,23,45,67,89); trace("Array desordenado: "+arrayOriginal); ordenamiento_shell(arrayOriginal,arrayOriginal.length); function ordenamiento_shell(arrayDesordenado:Array, tamano:uint) { var i:uint; var j:uint; var incremento:uint; var temp:uint; incremento = tamano / 2; while (incremento>0) { for (i=incremento; i<tamano; i++) { j = i; temp = arrayDesordenado[i]; while ((j >= incremento) && (arrayDesordenado[j-incremento] > temp)) { arrayDesordenado[j] = arrayDesordenado[j - incremento]; j = j - incremento; } arrayDesordenado[j] = temp; } incremento = incremento/2; } trace("Array ordenado: "+arrayDesordenado); }

C
void shell_sort(int A[], int size) { int i, j, incrmnt, temp; incrmnt = size/2; while (incrmnt > 0) { for (i=incrmnt; i < size; i++) { j = i; temp = A[i]; while ((j >= incrmnt) && (A[j-incrmnt] > temp)) { A[j] = A[j - incrmnt]; j = j - incrmnt; } A[j] = temp; } incrmnt /= 2; } }

C#
using System; public class ShellSorter { public void Sort(int [] list) { int j,inc; inc=list.length/2;

while(inc>0) { for(int i=inc+1;i<list.length;i++) { j=i-inc; while(j>0) { if(list[j] > list[j+inc]) { Swap(list[j],list[j+inc]); j=j-inc; } else { j=0; } } } inc=inc/2; } } } public class MainClass { public static void Main() { int[] iArrary=new int[]{1,5,3,6,10,55,9,2,87,12,34,75,33,47}; ShellSorter sh=new ShellSorter(); sh.Sort(iArrary); for(int m=0;m<=13;m++) Console.WriteLine("{0}",iArrary[m]); } }

Java
public static void shellSort(int[] a) { for ( int increment = a.length / 2; increment > 0; increment = (increment == 2 ? 1 : (int) Math.round(increment / 2.2))) { for (int i = increment; i < a.length; i++) { for (int j = i; j >= increment && a[j - increment] > a[j]; j -= increment) { int temp = a[j]; a[j] = a[j - increment]; a[j - increment] = temp; } } } }

Perl
sub shellsort { my $array = shift; my $i; my $j; my $shell; # Recibimos una referencia a un array # ndice del elemento a comparar # ndice del elemento actual a comparar # Tamao del incremento

# Calculamos el valor del incremento for ( $shell = 1; $shell < @$array; $shell = 2 * $shell + 1 ) { ; } do { $shell = int( ( $shell - 1 ) / 2 ); # Para todos los elementos, elegidos con un incremento for ( $i = $shell; $i < @$array; $i++ ) { for ( $j = $i - $shell;

$j >= 0 && $array->[ $j ] > $array->[ $j + $shell ]; $j -= $shell ) { # Intercambiamos valores @$array[ $j, $j + $shell ] = @$array[ $j + $shell, $j ]; } } } while $shell > 1; }

Python
def shellsort(a): def new_increment(a): i = int(len(a) / 2) yield i while i<>1: if i==2: i = 1 else: i = int(round(i/2.2)) yield i for increment in new_increment(a): for i in range(increment, len(a)): for j in range(i, increment-1, -increment): if a[j-increment] < a[j]: break a[j],a[j-increment] = a[j - increment],a[j] return a

Visual Basic
Sub Shell_Sort(ByRef V) 'debemos pasarle el arreglo(de enteros) desde el programa principal() Dim I, Medio, J, Temp As Integer Dim Ordenado As Boolean Medio = UBound(V) While (Medio > 0) Medio = Medio \ 2 Do Ordenado = True For J = 0 To UBound(V) - Medio ' se asume que el arreglo va de 0 a UBound(V) elementos I = J + Medio If V(J) > V(I) Then ' Se intercambian los elementos Temp = V(J) V(J) = V(I) V(I) = Temp Ordenado = False End If Next Loop Until Ordenado Wend End Sub Sub principal() Dim Arr(99) As Integer Dim I As Integer For I = 0 To 99 Arr(I) = Int(Rnd() % 1000) Next I Call Shell_Sort(Arr) End Sub

' Se llena el arreglo con nmeros menores que 1000

Merge Sort

El mtodo Quicksort divide la estructura en dos y ordena cada mitad recursivamente. El caso del MergeSort es el opuesto, es decir, en ste mtodo de unen dos estructuras ordenadas para formar una sola ordenada correctamente. Tiene la ventaja de que utiliza un tiempo proporcional a: n log (n), su desventaja radica en que se requiere de un espacio extra para el procedimiento. Este tipo de ordenamiento es til cuando se tiene una estructura ordenada y los nuevos datos a aadir se almacenan en una estructura temporal para despus agregarlos a la estructura original de manera que vuelva a quedar ordenada. Procedimiento MergeSort /*recibe el arreglo a ordenar un ndice l que indica el lmite inferior del arreglo a ordenar y un ndice r que indica el lmite superior*/
void mergesort(int a[], int l, int r) { int i,j,k,m,b[MAX]; if (r > l) { m = (r+l) /2; mergesort(a, l, m); mergesort(a, m+1, r); for (i= m+1; i > l;i--) b[i-1] = a[i-1]; for (j= m; j < r;j++) b[r+m-j] = a[j+1]; for (k=l ; k <=r; k++) if(b[i] < b[j]) a[k] = b[i++]; else a[k] = b[j--]; } } a = {a,s,o,r,t,i,n,g,e,x,a,m,p,l,e} {a,s, o,r, a,o,r,s, i,t, g,n, g,i,n,t, a,g,i,n,o,r,s,t, e,x, a,m, a,e,m,x, l,p, e,l,p} a,e,e,l,m,p,x} a = {a,a,e,e,g,i,l,m,n,o,p,r,s,t,x}

Algoritmo de ordenamiento
En computacin y matemticas un algoritmo de ordenamiento recursivo es un algoritmo que pone elementos de una lista o un vector en una secuencia dada por una relacin de orden, es decir, el resultado de salida ha de ser una permutacin o reordenamiento de la entrada que satisfaga la relacin de orden dada. Las relaciones

de orden ms usadas son el orden numrico y el orden lexicogrfico. Ordenamientos eficientes son importantes para optimizar el uso de otros algoritmos (como los de bsqueda y fusin) que requieren listas ordenadas para una ejecucin rpida. Tambin es til para poner datos en forma cannica y para generar resultados legibles por humanos. Desde los comienzos de la computacin, el problema del ordenamiento ha atrado gran cantidad de investigacin, tal vez debido a la complejidad de resolverlo eficientemente a pesar de su planteamiento simple y familiar. Por ejemplo, BubbleSort fue analizado desde 1956.1 Aunque muchos puedan considerarlo un problema resuelto, nuevos y tiles algoritmos de ordenamiento se siguen inventado hasta el da de hoy (por ejemplo, el ordenamiento de biblioteca se public por primera vez en el 2004). Los algoritmos de ordenamiento son comunes en las clases introductorias a la computacin, donde la abundancia de algoritmos para el problema proporciona una gentil introduccin a la variedad de conceptos ncleo de los algoritmos, como notacin de O mayscula, algoritmos divide y vencers, estructuras de datos, anlisis de los casos peor, mejor, y promedio, y lmites inferiores.

1 Clasificacin 2 Estabilidad 3 Lista de algoritmos de ordenamiento 4 Referencias 5 Enlaces externos

Clasificacin
Los algoritmos de ordenamiento se pueden clasificar de las siguientes maneras:

La ms comn es clasificar segn el lugar donde se realice la ordenacin o Algoritmos de ordenamiento interno: en la memoria del ordenador. o Algoritmos de ordenamiento externo: en un lugar externo como un disco duro. Por el tiempo que tardan en realizar la ordenacin, dadas entradas ya ordenadas o inversamente ordenadas: o Algoritmos de ordenacin natural: Tarda lo mnimo posible cuando la entrada est ordenada. o Algoritmos de ordenacin no natural: Tarda lo mnimo posible cuando la entrada est inversamente ordenada. Por estabilidad: un ordenamiento estable mantiene el orden relativo que tenan originalmente los elementos con claves iguales. Por ejemplo, si una lista ordenada por fecha se reordena en orden alfabtico con un algoritmo estable, todos los elementos cuya clave alfabtica sea la misma quedarn en orden de fecha. Otro caso sera cuando no interesan las maysculas y minsculas, pero se quiere que si una clave aBC estaba antes que AbC, en el resultado ambas claves aparezcan juntas y en el orden original: aBC, AbC. Cuando los elementos son indistinguibles (porque cada elemento se ordena por la clave completa) la estabilidad no interesa. Los algoritmos de ordenamiento que no son estables se pueden implementar para que s lo sean. Una manera de hacer esto es modificar artificialmente la clave de ordenamiento de modo que la posicin original en la lista participe del ordenamiento en caso de coincidencia.

Los algoritmos se distinguen por las siguientes caractersticas:

Complejidad computacional (peor caso, caso promedio y mejor caso) en trminos de n, el tamao de la lista o arreglo. Para esto se usa el concepto de orden de una funcin y se usa la notacin O(n). El mejor comportamiento para ordenar (si no se aprovecha la estructura de las claves) es O(n log n). Los algoritmos ms simples son cuadrticos, es decir O(n). Los algoritmos que aprovechan la estructura de las claves de ordenamiento (p. ej. bucket sort) pueden ordenar en O(kn) donde k es el tamao del

espacio de claves. Como dicho tamao es conocido a priori, se puede decir que estos algoritmos tienen un desempeo lineal, es decir O(n). Uso de memoria y otros recursos computacionales. Tambin se usa la notacin O(n).

Estabilidad
Los algoritmos de ordenamiento estable mantienen un relativo preorden total. Esto significa que un algoritmo es estable solo cuando hay dos registros R y S con la misma clave y con R apareciendo antes que S en la lista original. Cuando elementos iguales (indistinguibles entre s), como nmeros enteros, o ms generalmente, cualquier tipo de dato en donde el elemento entero es la clave, la estabilidad no es un problema. De todas formas, se asume que los siguientes pares de nmeros estn por ser ordenados por su primer componente:
(4, 1) (3, 7) (3, 1) (5, 6)

En este caso, dos resultados diferentes son posibles, uno de los cuales mantiene un orden relativo de registros con claves iguales, y una en la que no:
(3, 7) (3, 1) (3, 1) (3, 7) (4, 1) (4, 1) (5, 6) (5, 6) (orden mantenido) (orden cambiado)

Los algoritmos de ordenamiento inestable pueden cambiar el orden relativo de registros con claves iguales, pero los algoritmos estables nunca lo hacen. Los algoritmos inestables pueden ser implementados especialmente para ser estables. Una forma de hacerlo es extender artificialmente el cotejamiento de claves, para que las comparaciones entre dos objetos con claves iguales sean decididas usando el orden de las entradas original. Recordar este orden entre dos objetos con claves iguales es una solucin poco prctica, ya que generalmente acarrea tener almacenamiento adicional. Ordenar segn una clave primaria, secundaria, terciara, etc., puede ser realizado utilizando cualquier mtodo de ordenamiento, tomando todas las claves en consideracin (en otras palabras, usando una sola clave compuesta). Si un mtodo de ordenamiento es estable, es posible ordenar mltiples tems, cada vez con una clave distinta. En este caso, las claves necesitan estar aplicadas en orden de aumentar la prioridad. Ejemplo: ordenar pares de nmeros, usando ambos valores
(4, 1) (4, 1) (3, 1) (3, 7) (3, 1) (3, 7) (3, 1) (4, 6) (4, 1) (4, 6) (original) (3, 7) (despus de ser ordenado por el segundo valor) (4, 6) (despus de ser ordenado por el primer valor)

Por otro lado:


(3, 7) (3, 1) (3, 1) (4, 1) (4, 1) (4, 6) (4, 6) (despus de ser ordenado por el primer valor) (3, 7) (despus de ser ordenando por el segundo valor, el orden por el primer valor es perturbado)

Lista de algoritmos de ordenamiento


Algunos algoritmos de ordenamiento agrupados segn estabilidad tomando en cuenta la complejidad computacional.

Estables

Nombre traducido

Nombre original

Complejidad

Memoria Mtodo

Ordenamiento de burbuja

Bubblesort

O(n)

O(1)

Intercambio

Ordenamiento de burbuja bidireccional

Cocktail sort

O(n)

O(1)

Intercambio

Ordenamiento por insercin

Insertion sort

O(n)

O(1)

Insercin

Ordenamiento por casilleros

Bucket sort

O(n)

O(n)

No comparativo

Ordenamiento por cuentas

Counting sort

O(n+k)

O(n+k)

No comparativo

Ordenamiento por mezcla

Merge sort

O(n log n)

O(n)

Mezcla

Ordenamiento con rbol binario

Binary tree sort

O(n log n)

O(n)

Insercin

Pigeonhole sort

O(n+k)

O(k)

Ordenamiento Radix

Radix sort

O(nk)

O(n)

No comparativo

Distribution sort

O(n) versin recursiva

O(n)

Gnome sort

O(n)

Inestables

Nombre traducido

Nombre original

Complejidad

Memoria Mtodo

Ordenamiento Shell

Shell sort

O(n1.25)

O(1)

Insercin

Comb sort

O(n log n)

O(1)

Intercambio

Ordenamiento por seleccin

Selection sort

O(n)

O(1)

Seleccin

Ordenamiento por montculos

Heapsort

O(n log n)

O(1)

Seleccin

Smoothsort

O(n log n)

O(1)

Seleccin

Ordenamiento rpido

Quicksort

Promedio: O(n log n), peor caso: O(n)

O(log n) Particin

Several Unique Sort

Promedio: O(n u), peor caso: O(n); u=n; u = nmero nico de registros

Cuestionables, imprcticos

Nombre traducido

Nombre original

Complejidad

Memoria Mtodo

Bogosort

O(n n!), peor: no termina

Pancake sorting

O(n), excepto en mquinas de Von Neumann

Randomsort

Algoritmo de bsqueda
Un algoritmo de bsqueda es aquel que est diseado para localizar un elemento con ciertas propiedades dentro de una estructura de datos; por ejemplo, ubicar el registro correspondiente a cierta persona en una base de datos, o la mejor movida en una partida de ajedrez. La variante ms simple del problema es la bsqueda de un nmero en un vector.

Bsqueda secuencial
Se utiliza cuando el vector no est ordenado o no puede ser ordenado previamente. Consiste en buscar el elemento comparndolo secuencialmente (de ah su nombre) con cada elemento del array hasta encontrarlo, o hasta que se llegue al final. La existencia se puede asegurar cuando el elemento es localizado, pero no podemos asegurar la no existencia hasta no haber analizado todos los elementos del array. A continuacin se muestra el pseudocdigo del algoritmo:
Datos de entrada: vec: vector en el que se desea buscar el dato tam: tamao del vector. Los subndices vlidos van desde 0 hasta tam-1 inclusive. dato: elemento que se quiere buscar. Variables

pos: posicin actual en el array pos = 0 Mientras pos < tam: Si vec[pos] == dato devolver verdadero y/o pos, de lo contrario: pos = pos + 1 Fin (Mientras) Devolver falso

Bsqueda binaria (dicotmica)


Se utiliza cuando el vector en el que queremos determinar la existencia de un elemento est previamente ordenado. Este algoritmo reduce el tiempo de bsqueda considerablemente, ya que disminuye exponencialmente el nmero de iteraciones necesarias. Est altamente recomendado para buscar en arrays de gran tamao. Por ejemplo, en uno conteniendo 50.000.000 elementos, realiza como mximo 26 comparaciones (en el peor de los casos). Para implementar este algoritmo se compara el elemento a buscar con un elemento cualquiera del array (normalmente el elemento central): si el valor de ste es mayor que el del elemento buscado se repite el procedimiento en la parte del array que va desde el inicio de ste hasta el elemento tomado, en caso contrario se toma la parte del array que va desde el elemento tomado hasta el final. De esta manera obtenemos intervalos cada vez ms pequeos, hasta que se obtenga un intervalo indivisible. Si el elemento no se encuentra dentro de este ltimo entonces se deduce que el elemento buscado no se encuentra en todo el array. A continuacin se presenta el pseudocdigo del algoritmo, tomando como elemento inicial el elemento central del array.
Datos de entrada: vec: vector en el que se desea buscar el dato tam: tamao del vector. Los subndices vlidos van desde 0 hasta tam-1 inclusive. dato: elemento que se quiere buscar. Variables centro: subndice central del intervalo inf: lmite inferior del intervalo sup: lmite superior del intervalo inf = 0 sup = tam-1 Mientras inf <= sup: centro = ((sup - inf) / 2) + inf // Divisin entera: se trunca la fraccin Si vec[centro] == dato devolver verdadero y/o pos, de lo contrario: Si dato < vec[centro] entonces: sup = centro - 1 En caso contrario: inf = centro + 1 Fin (Mientras) Devolver Falso

III. Listas
Objetivo General: El alumno elaborar programas que integren el uso de recursividad y definir estructuras de datos para generar alternativas de programacin.

Listas
En Ciencias de la Computacin, una lista enlazada es una de las estructuras de datos fundamentales, y puede ser usada para implementar otras estructuras de datos. Consiste en una secuencia de nodos, en los que se guardan campos de datos arbitrarios y una o dos referencias (punteros) al nodo anterior o posterior. El principal beneficio de las listas enlazadas respecto a los array convencionales es que el orden de los elementos enlazados puede ser diferente al orden de almacenamiento en la memoria o el disco, permitiendo que el orden de recorrido de la lista sea diferente al de almacenamiento. Una lista enlazada es un tipo de dato auto-referenciado porque contienen un puntero o link a otro dato del mismo tipo. Las listas enlazadas permiten inserciones y eliminacin de nodos en cualquier punto de la lista en tiempo constante (suponiendo que dicho punto est previamente identificado o localizado), pero no permiten un acceso aleatorio. Existen diferentes tipos de listas enlazadas: Lista Enlazadas Simples, Listas Doblemente Enlazadas, Listas Enlazadas Circulares y Listas Enlazadas Doblemente Circulares. Las listas enlazadas pueden ser implementadas en muchos lenguajes. Lenguajes tales como Lisp y Scheme tiene estructuras de datos ya construidas, junto con operaciones para acceder a las listas enlazadas. Lenguajes imperativos u orientados a objetos tales como C o C++ y Java, respectivamente, disponen de referencias para crear listas enlazadas.

1 Historia 2 Tipos de Listas Enlazadas o 2.1 Listas enlazadas lineales 2.1.1 Listas simples enlazadas 2.1.2 Lista Doblemente Enlazada o 2.2 Listas enlazadas circulares 2.2.1 Listas enlazadas circulares simples 2.2.2 Lista Enlazada Doblemente Circular o 2.3 Nodos Centinelas 3 Aplicaciones de las listas enlazadas 4 Ventajas o 4.1 Listas Enlazadas vs. Vectores o Matrices o 4.2 Doblemente Enlazadas vs. Simples Enlazadas o 4.3 Circulares Enlazadas vs. Lineales Enlazadas o 4.4 Nodos Centinelas (header nodes) 5 Listas enlazadas usando Arrays de Nodos 6 Lenguajes soportados 7 Almacenamiento interno y externo 8 Agilizacin de la bsqueda 9 Estructuras de datos relacionadas 10 Implementaciones o 10.1 Operaciones sobre listas enlazadas 10.1.1 Listas Enlazadas Lineales 10.1.1.1 Listas Simples Enlazadas 10.1.1.2 Listas Doblemente Enlazadas 10.1.2 Listas Enlazadas Circulares 10.1.2.1 Listas Enlazadas Doblemente Circulares o 10.2 Listas enlazadas usando arrays de nodos o 10.3 Implementacin de una lista enlazada en C o 10.4 Implementacin de una lista enlazada en Maude o 10.5 Ejemplos de almacenamiento interno y externo 11 Referencias 12 Enlaces externos

Historia
Las listas enlazadas fueron desarrolladas en 1955-56 por Cliff Shaw y Herbert Simon en RAND Corporation como la principal estructura de datos para su Lenguaje de Procesamiento de la Informacin (IPL). IPL fue usado por los autores para desarrollar varios programas relacionados con la inteligencia artificial, incluida la Mquina de la Teora General, el Solucionador de Problemas Generales, y un programa informtico de ajedrez. Se public en IRE Transactions on Information Theory en 1956, y en distintas conferencias entre 1957-1959, incluida Proceedings of the Western Joint Computer en 1957 y 1958, y en Information Processing (Procendents de la primera conferencia internacional del procesamiento de la informacin de la Unesco) en 1959. El diagrama clsico actual, que consiste en bloques que representan nodos de la lista con flechas apuntando a los sucesivos nodos de la lista, apareci en Programming the Logic Theory Machine, de Newell y Shaw. Newell y Simon fueron reconocidos por el ACM Turing Award en 1975 por hacer contribuciones bsicas a la inteligencia artificial, a la psicologa del conocimiento humano y al procesamiento de las listas. El problema de los traductores del procesamiento natural del lenguaje condujo a Victor Yngve del Instituto Tecnolgico de Massachusetts (MIT) a usar listas enlazadas como estructura de datos en su COMIT, lenguaje de programacin para computadoras, que investig en el campo de la Lingstica computacional. Un informe de este lenguaje, titulado A programming language for mechanical translation apareci en Mechanical Translation en 1958. LISP, el principal procesador de listas, fue creado en 1958. Una de las mayores estructuras de datos de LISP es la lista enlazada. En torno a los 60, la utilidad de las listas enlazadas y los lenguajes que utilizaban estas estructuras como su principal representacin de datos estaba bien establecida. Bert Green del MIT Lincoln Laboratory, public un estudio titulado Computer languages for symbol manipulation en IRE Transaction on Human Factors in Electronics en marzo de 1961 que resuma las ventajas de las listas enlazadas. Un posterior artculo, A Comparison of list-processing computer languages by Bobrow and Raphael, apareca en Communications of the ACM en abril de 1964. Muchos sistemas operativos desarrollados por Technical Systems Consultants (originalmente de West Lafayette Indiana y despus de Raleigh, Carolina del Norte) usaron listas enlazadas simples como estructuras de ficheros. Un directorio de entrada apuntaba al primer sector de un fichero y daba como resultado porciones de la localizacin del fichero mediante punteros. Los sistemas que utilizaban esta tcnica incluan Flex (para el Motorola 6800 CPU), mini-Flex (la misma CPU) y Flex9 (para el Motorola 6809 CPU). Una variante desarrollada por TSC se comercializ a Smoke Signal Broadcasting en California, usando listas doblemente enlazadas del mismo modo. El sistema operativo TSS, desarrollado por IBM para las mquinas System 360/370, usaba una lista doblemente enlazada para su catlogo de ficheros de sistema. La estructura del directorio era similar a Unix, donde un directorio poda contener ficheros u otros directorios que se podan extender a cualquier profundidad. Una utilidad fue creada para arreglar problemas del sistema despus de un fallo desde las porciones modificadas del catlogo de ficheros que estaban a veces en memoria cuando ocurra el fallo. Los problemas eran detectados por comparacin de los links posterior y anterior por consistencia. Si el siguiente link era corrupto y el anterior enlace del nodo infectado era encontrado, el posterior link era asignado al nodo con el link del anterior.

Tipos de Listas Enlazadas


Listas enlazadas lineales Listas simples enlazadas La lista enlazada bsica es la lista enlazada simple la cual tiene un enlace por nodo. Este enlace apunta al siguiente nodo en la lista, o al valor NULL o a la lista vaca, si es el ltimo nodo.

Una lista enlazada simple contiene dos valores: el valor actual del nodo y un enlace al siguiente nodo

[editar] Lista Doblemente Enlazada Un tipo de lista enlazada ms sofisticado es la lista doblemente enlazada o lista enlazadas de dos vas. Cada nodo tiene dos enlaces: uno apunta al nodo anterior, o apunta al valor NULL si es el primer nodo; y otro que apunta al nodo siguiente, o apunta al valor NULL si es el ltimo nodo.

Una lista doblemente enlazada contiene tres valores: el valor, el link al nodo siguiente, y el link al anterior

En algn lenguaje de muy bajo nivel, XOR-Linking ofrece una va para implementar listas doblemente enlazadas, usando una sola palabra para ambos enlaces, aunque el uso de esta tcnica no se suele utilizar. Listas enlazadas circulares En una lista enlazada circular, el primer y el ltimo nodo estn unidos juntos. Esto se puede hacer tanto para listas enlazadas simples como para las doblemente enlazadas. Para recorrer una lista enlazada circular podemos empezar por cualquier nodo y seguir la lista en cualquier direccin hasta que se regrese hasta el nodo original. Desde otro punto de vista, las listas enlazadas circulares pueden ser vistas como listas sin comienzo ni fin. Este tipo de listas es el ms usado para dirigir buffers para ingerir datos, y para visitar todos los nodos de una lista a partir de uno dado.

Una lista enlazada circular que contiene tres valores enteros

Listas enlazadas circulares simples Cada nodo tiene un enlace, similar al de las listas enlazadas simples, excepto que el siguiente nodo del ltimo apunta al primero. Como en una lista enlazada simple, los nuevos nodos pueden ser solo eficientemente insertados despus de uno que ya tengamos referenciado. Por esta razn, es usual quedarse con una referencia solamente al ltimo elemento en una lista enlazada circular simple, esto nos permite rpidas inserciones al principio, y tambin permite accesos al primer nodo desde el puntero del ltimo nodo. 1 Lista Enlazada Doblemente Circular En una lista enlazada doblemente circular, cada nodo tiene dos enlaces, similares a los de la lista doblemente enlazada, excepto que el enlace anterior del primer nodo apunta al ltimo y el enlace siguiente del ltimo nodo, apunta al primero. Como en una lista doblemente enlazada, las inserciones y eliminaciones pueden ser hechas desde cualquier punto con acceso a algn nodo cercano. Aunque estructuralmente una lista circular doblemente enlazada no tiene ni principio ni fin, un puntero de acceso externo puede establecer el nodo apuntado que est en la cabeza o al nodo cola, y as mantener el orden tan bien como en una lista doblemente enlazada. Nodos Centinelas A veces las listas enlazadas tienen un nodo centinela (tambin llamado falso nodo o nodo ficticio) al principio o al final de la lista, el cual no es usado para guardar datos. Su propsito es simplificar o agilizar algunas operaciones, asegurando que cualquier nodo tiene otro anterior o posterior, y que toda la lista (incluso alguna que no contenga datos) siempre tenga un primer y ltimo nodo.

Aplicaciones de las listas enlazadas

Las listas enlazadas son usadas como mdulos para otras muchas estructuras de datos, tales como pilas, colas y sus variaciones. El campo de datos de un nodo puede ser otra lista enlazada. Mediante este mecanismo, podemos construir muchas estructuras de datos enlazadas con listas; esta practica tiene su origen en el lenguaje de programacin Lisp, donde las listas enlazadas son una estructura de datos primaria (aunque no la nica), y ahora es una caracterstica comn en el estilo de programacin funcional. A veces, las listas enlazadas son usadas para implementar arrays asociativos, y estas en el contexto de las llamadas listas asociativas. Hay pocas ventajas en este uso de las listas enlazadas; hay mejores formas de implementar stas estructuras, por ejemplo con rboles binarios de bsqueda equilibrados. Sin embargo, a veces una lista enlazada es dinmicamente creada fuera de un subconjunto propio de nodos semejante a un rbol, y son usadas ms eficientemente para recorrer sta serie de datos

Ventajas
Como muchas opciones en programacin y desarrollo, no existe un nico mtodo correcto para resolver un problema. Una estructura de lista enlazada puede trabajar bien en un caso pero causar problemas en otros. He aqu una lista con un algunas de las ventajas ms comunes que implican las estructuras de tipo lista. En general, teniendo una coleccin dinmica donde los elementos estn siendo aadidos y eliminados frecuentemente e importa la localizacin de los nuevos elementos introducidos se incrementa el beneficio de las listas enlazadas. [editar] Listas Enlazadas vs. Vectores o Matrices
Array Lista Enlazada

Indexado

O(1)

O(n)

Insercin / Eliminacin al final

O(1)

O(1) or O(n)2

Insercin / Eliminacin en la mitad O(n)

O(1)

Persistencia

No

Simples s

Localizacin

Buena Mala

Las listas enlazadas poseen muchas ventajas sobre los arrays. Los elementos se pueden insertar en una lista indefinidamente mientras que un array tarde o temprano se llenar necesitar ser redimensionado, una costosa operacin que incluso puede no ser posible si la memoria se encuentra fragmentada. En algunos casos se pueden lograr ahorros de memoria almacenando la misma cola de elementos entre dos o ms listas es decir, la lista acaba en la misma secuencia de elementos. De este modo, uno puede aadir nuevos elementos al frente de la lista manteniendo una referencia tanto al nuevo como a los viejos elementos - un ejemplo simple de una estructura de datos persistente. Por otra parte, los arrays permiten acceso aleatorio mientras que las listas enlazadas slo permiten acceso secuencial a los elementos. Las listas enlazadas simples, de hecho, solo pueden ser recorridas en una direccin. Esto hace que las listas sean inadecuadas para aquellos casos en los que es til buscar un elementos por su

ndice rpidamente, como el heapsort. El acceso secuencial en los arrays tambin es ms rpido que en las listas enlazadas. Otra desventaja de las listas enlazadas es el almacenamiento extra necesario para las referencias, que a menudos las hacen poco prcticas para listas de pequeos datos como caracteres o valores booleanos. Tambin puede resultar lento y abusivo el asignar memoria para cada nuevo elemento. Existe una variedad de listas enlazadas que contemplan los problemas anteriores para resolver los mismos. Un buen ejemplo que muestra los pros y contras del uso de arrays sobre listas enlazadas es la implementacin de un programa que resuelva el problema de Josephus. Este problema consiste en un grupo de personas dispuestas en forma de crculo. Se empieza a partir de una persona predeterminadas y se cuenta n veces, la persona n-sima se saca del crculo y se vuelve a cerrar el grupo. Este proceso se repite hasta que queda una sola persona, que es la que gana. Este ejemplo muestra las fuerzas y debilidades de las listas enlazadas frente a los arrays, ya que viendo a la gente como nodos conectados entre s en una lista circular se observa como es ms fcil suprimir estos nodos. Sin embargo, se ve como la lista perder utilidad cuando haya que encontrar a la siguiente persona a borrar. Por otro lado, en un array el suprimir los nodos ser costoso ya que no se puede quitar un elemento sin reorganizar el resto. Pero en la bsqueda de la n-sima persona tan slo basta con indicar el ndice n para acceder a l resultando mucho ms eficiente. Doblemente Enlazadas vs. Simples Enlazadas Las listas doblemente enlazadas requieren ms espacio por nodo y sus operaciones bsicas resultan ms costosas pero ofrecen una mayor facilidad para manipular ya que permiten el acceso secuencial a lista en ambas direcciones. En particular, uno puede insertar o borrar un nodo en un nmero fijo de operaciones dando nicamente la direccin de dicho nodo (Las listas simples requieren la direccin del nodo anterior para insertar o suprimir correctamente). Algunos algoritmos requieren el acceso en ambas direcciones. Circulares Enlazadas vs. Lineales Enlazadas Las listas circulares son ms tiles para describir estructuras circulares y tienen la ventaja de poder recorrer la lista desde cualquier punto. Tambin permiten el acceso rpido al primer y ltimo elemento por medio de un puntero simple. Nodos Centinelas (header nodes) La bsqueda comn y los algoritmos de ordenacin son menos complicados si se usan los llamados Nodos Centinelas o Nodos Ficticios, donde cada elemento apunta a otro elemento y nunca a nulo. El Nodo Centinela o Puntero Cabeza contiene, como otro, un puntero siguiente que apunta al que se considera como primer elemento de la lista . Tambin contiene un puntero previo que hace lo mismo con el ltimo elemento . El Nodo Centinela es definido como otro nodo en una lista doblemente enlazada , la asignacin del puntero frente no es necesaria y los puntero anterior y siguiente estarn apuntando a s mismo en ese momento. Si los punteros anterior y siguiente apuntan al Nodo Centinela la lista se considera vaca. En otro caso, si a la lista se le aaden elementos ambos puntero apuntarn a otros nodos. Estos Nodos Centinelas simplifican muchos las operaciones pero hay que asegurarse de que los punteros anterior y siguiente existen en cada momento. Como ventaja eliminan la necesidad de guardar la referencia al puntero del prinicipio de la lista y la posibilidad de asignaciones accidentales. Por el contrario, usan demasiado almacenamiento extra y resultan complicados en algunas operaciones.

Listas enlazadas usando Arrays de Nodos


Los lenguajes que no aceptan cualquier tipo de referencia pueden crear uniones reemplazando los punteros por ndices de un array. La ventaja es de mantener un array de entradas , donde cada entrada tiene campos enteros indicando el ndice del siguiente elemento del array. Puede haber nodos sin usarse. Si no hay suficiente espacio, pueden usarse arrays paralelos.

Entonces una lista enlazada puede ser construida, creado un array con esta estructura, y una variable entera para almacenar el ndice del primer elemento. (ver en la seccin de implementaciones). Las utilidades de esta propuesta son:

La lista enlazada puede ser movida sobre la memoria y tambin ser rpidamente serializada para almacenarla en un disco o transferirla sobre una red. Especialmente para una lista pequea, los arrays indexados pueden ocupar mucho menos espacio que un conjunto de punteros. La localidad de referencia puede ser mejorada guardando los nodos juntos en memoria y siendo reordenados peridicamente.

Algunas desventajas son:


Incrementa la complejidad de la implementacin. Usar un fondo general de memoria deja ms memoria para otros datos si la lista es ms pequea de lo esperado si muchos nodos son liberados. El crecimiento de un array cuando est lleno no puede darse lugar (o habra que redimensionarlo) mientras que encontrar espacio para un nuevo nodo en una lista resulta posible y ms fcil.

Por estas razones, la propuesta se usa principalmente para lenguajes que no soportan asignacin de memoria dinmica. Estas desventajas se atenan tambin si el tamao mximo de la lista se conoce en el momento en el que el array se crea.

Lenguajes soportados
Muchos lenguajes de programacin tales como Lisp y Scheme tienen listas enlazadas simples ya construidas. En muchos lenguajes de programacin, estas listas estn construidas por nodos, cada uno llamado cons o celda cons. Las celdas cons tienen dos campos: el car, una referencia del dato al nodo, y el cdr, una referencia al siguiente nodo. Aunque las celdas cons pueden ser usadas para construir otras estructuras de datos, este es su principal objetivo. En lenguajes que soportan tipos abstractos de datos o plantillas, las listas enlazadas ADTs o plantillas estn disponibles para construir listas enlazadas. En otros lenguajes, las listas enlazadas son tpicamente construidas usando referencias junto con el tipo de dato record. En la seccin de implementaciones hay un ejemplo completo en C y en Maude

Almacenamiento interno y externo


Cuando se construye una lista enlazada, nos enfrentamos a la eleccin de si almacenar los datos de la lista directamente en los nodos enlazados de la lista, llamado almacenamiento interno, o simplemente almacenar una referencia al dato, llamado almacenamiento externo. El almacenamiento interno tiene la ventaja de hacer accesos a los datos ms eficientes, requiriendo menos almacenamiento global, teniendo mejor referencia de localidad, y simplifica la gestin de memoria para la lista (los datos son alojados y desalojados al mismo tiempo que los nodos de la lista). El almacenamiento externo, por otro lado, tiene la ventaja de ser ms genrico, en la misma estructura de datos y cdigo mquina puede ser usado para una lista enlazada, no importa cual sea su tamao o los datos. Esto hace que sea ms fcil colocar el mismo dato en mltiples listas enlazadas. Aunque con el almacenamiento interno los mismos datos pueden ser colocados en mltiples listas incluyendo mltiples referencias siguientes en la estructura de datos del nodo, esto podra ser entonces necesario para crear rutinas separadas para aadir o borrar celdas basadas en cada campo. Esto es posible creando listas enlazadas de elementos adicionales que usen almacenamiento interno usando almacenamiento externo, y teniendo las celdas de las listas enlazadas adicionales almacenadas las referencias a los nodos de las listas enlazadas que contienen los datos.

En general, si una serie de estructuras de datos necesita ser incluida en mltiples listas enlazadas, el almacenamiento externo es el mejor enfoque. Si una serie de estructuras de datos necesitan ser incluidas en una sola lista enlazada, entonces el almacenamiento interno es ligeramente mejor, a no ser que un paquete genrico de listas genricas que use almacenamiento externo est disponible. Asimismo, si diferentes series de datos que pueden ser almacenados en la misma estructura de datos son incluidos en una lista enlazada simple, entonces el almacenamiento interno puede ser mejor. Otro enfoque que puede ser usado con algunos lenguajes implica tener diferentes estructuras de datos, pero todas tienen los campos iniciales, incluyendo la siguiente (y anterior si es una lista doblemente enlazada) referencia en la misma localizacin. Despus de definir estructuras distintas para cada tipo de dato, una estructura genrica puede ser definida para que contenga la mnima cantidad de datos compartidos por todas las estructuras y contenidos al principio de las estructuras. Entonces las rutinas genricas pueden ser creadas usando las mnimas estructuras para llevar a cabo las operaciones de los tipos de las listas enlazadas, pero separando las rutinas que pueden manejar los datos especficos. Este enfoque es usado a menudo en rutinas de anlisis de mensajes, donde varios tipos de mensajes son recibidos, pero todos empiezan con la misma serie de campos, generalmente incluyendo un campo para el tipo de mensaje. Las rutinas genricas son usadas para aadir nuevos mensajes a una cola cuando son recibidos, y eliminarlos de la cola en orden para procesarlos. El campo de tipo de mensaje es usado para llamar a la rutina correcta para procesar el tipo especfico de mensaje. En la seccin implementaciones (en este mismo artculo) se expone cdigo referente a este tema. Hay que notar que cuando usamos almacenamiento externo, se necesita dar un paso extra para extraer la informacin del nodo y hacer un casting dentro del propio tipo del dato. Esto es porque ambas listas, de familias y miembros, son almacenadas en dos listas enlazadas usando la misma estructura de datos (nodo), y este lenguaje no tiene tipos paramtricos. Si conocemos el nmero de familias a las que un miembro puede pertenecer en tiempo de compilacin, el almacenamiento interno trabaja mejor. Si, sin embargo, un miembro necesita ser incluido en un nmero arbitrario de familias, sabiendo el nmero especfico de familias solo en tiempo de ejecucin, el almacenamiento externo ser necesario.

Agilizacin de la bsqueda
Buscando un elemento especfico en una lista enlazada, incluso si esta es ordenada, normalmente requieren tiempo O (n) (bsqueda lineal). Esta es una de las principales desventajas de listas enlazadas respecto a otras estructuras. Adems algunas de las variantes expuestas en la seccin anterior, hay numerosas vas simples para mejorar el tiempo de bsqueda. En una lista desordenada, una forma simple para decrementar el tiempo de bsqueda medio es el mover al frente de forma heurstica, que simplemente mueve un elemento al principio de la lista una vez que es encontrado. Esta idea, til para crear cachs simples, asegura que el tem usado ms recientemente es tambin el ms rpido en ser encontrado otra vez. Otro enfoque comn es indizar una lista enlazada usando una estructura de datos externa ms eficiente. Por ejemplo, podemos construir un rbol rojo-negro o una tabla hash cuyos elementos estn referenciados por los nodos de las listas enlazadas. Pueden ser construidos mltiples ndices en una lista simple. La desventaja es que estos ndices puede necesitar ser actualizados cada vez que uno nodo es aadido o eliminado (o al menos, antes que el ndice sea utilizado otra vez).

Estructuras de datos relacionadas


Tanto las pilas como las colas son a menudo implementadas usando listas enlazadas, y simplemente restringiendo el tipo de operaciones que son soportadas.

La skip list, o lista por saltos, es una lista enlazada aumentada con capas de punteros para saltos rpidos sobre grandes nmeros de elementos, y descendiendo haca la siguiente capa. Este proceso contina hasta llegar a la capa inferior, la cual es la lista actual. Un rbol binario puede ser visto como un tipo de lista enlazada donde los elementos estn enlazados entre ellos mismos de la misma forma. El resultado es que cada nodo puede incluir una referencia al primer nodo de una o dos listas enlazadas, cada cual con su contenido, formando as los subrboles bajo el nodo. Una lista enlazada desenrollada es una lista enlazada cuyos nodos contiene un array de datos. Esto mejora la ejecucin de la cach, siempre que las listas de elementos estn contiguas en memoria, y reducen la sobrecarga de la memoria, porque necesitas menos metadatos para guardar cada elemento de la lista. Una tabla hash puede usar listas enlazadas para guardar cadenas de tems en la misma posicin de la tabla hash.

Implementaciones
Aqu se expone el cdigo necesario para complementar el artculo a fin de poder realizar una lectura gil sobre el artculo y a su vez quien necesite el cdigo pueda fcilmente encontrar el mismo si est contenido. Operaciones sobre listas enlazadas Cuandos se manipulan listas enlazadas, hay que tener cuidado con no usar valores que hayamos invalidado en asignaciones anteriores. Esto hace que los algoritmos de insertar y borrar nodos en las listas sean algo especiales. A continuacin se expone el pseudocdigo para aadir y borrar nodos en listas enlazadas simples, dobles y circulares. Listas Enlazadas Lineales Listas Simples Enlazadas Nuestra estructura de datos tendr dos campos. Vamos a mantener la variables PrimerNodos que siempre apunta al primer nodo de tal lista, nulo para la lista vaca.
record Node { data // El dato almacenado en el nodo next // Una referencia al nodo siguiente, nulo para el ltimo nodo } record List { Node PrimerNodo // Apunta al primer nodo de la lista; nulo para la lista vaca }

El recorrido en una lista enlazada es simple, empezamos por el primer nodo y pasamos al siguiente hasta que la lista llegue al final.
node := list.PrimerNodo while node not null { node := node.next }

El siguiente cdigo inserta un elemento a continuacin de otro en una lista simple. El diagrama muestra como funciona.

function insertAfter(Node node, Node newNode) { newNode.next := node.next node.next := newNode }

Insertar al principio de una lista requiere una funcin por separado. Se necesita actualizar PrimerNodo.
function insertBeginning(List list, Node newNode) { newNode.next := list.firstNode list.firstNode := newNode }

De forma similar, tambin tenemos funciones para borrar un nodo dado para borrar un nodo del principio de la lista. Ver diagrama.

function removeAfter(Node node) { obsoleteNode := node.next node.next := node.next.next destroy obsoleteNode } function removeBeginning(List list) { obsoleteNode := list.firstNode list.firstNode := list.firstNode.next destroy obsoleteNode }

Advertimos que BorrarPrincipio pone PrimerNodo a nulo cuando se borra el ltimo elemento de la lista. Adjuntar una lista enlazada a otra puede resultar ineficiente a menos que se guarde una referencia a la cola de la lista, porque si no tendramos que recorrer la lista en orden hasta llegar a la cola y luego aadir la segunda lista. Listas Doblemente Enlazadas Con estas listas es necesario actualizar muchos ms punteros pero tambin se necesita menos informacin porque podemos usar un puntero para recorrer hacia atrs y consultar elementos. Se crean nuevas operaciones y elimina algunos casos especiales. Aadimos el campo anterior a nuestros nodos, apuntando al elemento anterior, y UltimoNodo a nuestra estructura, el cual siempre apunta al ltimo elemento de la lista. PrimerNodo y UltimoNodo siempre estn a nulo en la lista vaca.
record Node { data // El dato almacenado en el nodo next // Una referencia al nodo siguiente, nulo para el ltimo nodo prev // Una referencia al nodo anterior, nulo para el primer nodo } record List { Node firstNode // apunta al primer nodo de la lista; nulo para la lista vaca Node lastNode // apunta al ltimo nodo de la lista; nulo para la lista vaca }

Formas de recorrer la lista: Hacia Delante


node := list.firstNode while node null <do something with node.data>

node := node.next

Hacia Atrs
node := list.lastNode while node null <do something with node.data> node := node.prev

Estas funciones simtricas aaden un nodo despus o antes de uno dado.


function insertAfter(List list, Node node, Node newNode) newNode.prev := node newNode.next := node.next if node.next = null node.next := newNode list.lastNode := newNode else node.next.prev := newNode node.next := newNode function insertBefore(List list, Node node, Node newNode) newNode.prev := node.prev newNode.next := node if node.prev is null node.prev := newNode list.firstNode := newNode else node.prev.next := newNode node.prev := newNode

Tambin necesitamos una funcin para insertar un nodo al comienzo de una lista posiblemente vaca.
function insertBeginning(List list, Node newNode) if list.firstNode = null list.firstNode := newNode list.lastNode := newNode newNode.prev := null newNode.next := null else insertBefore (list, list.firstNode, newNode)

Una funcin simtrica que inserta al final:


function insertEnd(List list, Node newNode) if list.lastNode = null insertBeginning (list, newNode) else insertAfter (list, list.lastNode, newNode)

Borrar un nodo es fcil, solo requiere usar con cuidado firstNode y lastNode.
function remove(List list, Node node) if node.prev = null list.firstNode := node.next else node.prev.next := node.next if node.next = null list.lastNode := node.prev else node.next.prev := node.prev destroy node

Una consecuencia especial de este procedimiento es que borrando el ltimo elemento de una lista se ponen PrimerNodo y UltimoNodo a nulo, habiendo entonces un problema en una lista que tenga un nico elemento.

Listas Enlazadas Circulares Estas pueden ser simples o doblemente enlazadas. En una lista circular todos los nodos estn enlazados como un crculo, sin usar nulo. Para listas con frente y final (como una cola), se guarda una referencia al ltimo nodo de la lista. El siguiente nodo despus del ltimo sera el primero de la lista. Los elementos se pueden aadir por el final y borrarse por el principio en todo momento. Ambos tipos de listas circulares tienen la ventaja de poderse recorrer completamente empezando desde cualquier nodo. Esto nos permite normalmente evitar el uso de PrimerNodo y UltimoNodo, aunque si la lista estuviera vaca necesitaramos un caso especial, como una variables UltimoNodo que apunte a algn nodo en la lista o nulo si est vaca. Las operaciones para estas listas simplifican el insertar y borrar nodos en una lista vaca pero introducen casos especiales en la lista vaca.

Listas Enlazadas Doblemente Circulares Asumiendo que someNodo es algn nodo en una lista no vaca, esta lista presenta el comienzo de una lista con someNode. Hacia Delante
node := someNode do do something with node.value node := node.next while node != someNode

Hacia Atrs
node := someNode do do something with node.value node := node.prev while node := someNode

Esta funcin inserta un nodo en una lista enlazada doblemente circular despus de un elemento dado:
function insertAfter(Node node, Node newNode) newNode.next := node.next newNode.prev := node node.next.prev := newNode node.next := newNode

Para hacer "insertBefore", podemos simplificar "insertAfter (node.prev, newNode)". Insertar un elemento en una lista que puede estar vaca requiere una funcin especial.
function insertEnd(List list, Node node) if list.lastNode = null node.prev := node node.next := node else insertAfter (list.lastNode, node) list.lastNode := node

Para insertar al principio simplificamos "insertAfter (list.lastNode, node)".


function remove(List list, Node node) if node.next = node list.lastNode := null else node.next.prev := node.prev

node.prev.next := node.next if node = list.lastNode list.lastNode := node.prev; destroy node

Como una lista doblemente enlazada, "removeAfter" y "removeBefore" puede ser implementada con "remove (list, node.prev)" y "remove (list, node.next)". Listas enlazadas usando arrays de nodos Previamente se crea una estructura que contiene los apuntadores:
record Entry { integer next; // ndice de la nueva entrada en el array integer prev; // entrada previa string name; real balance; }

Y finalmente se declara el array: integer listHead;


Entry Records[1000];

Implementacin de una lista enlazada en C Las listas enlazadas son tpicamente construidas usando referencias junto con el tipo de dato record
#include <stdio.h> #include <stdlib.h> /* for printf */ /* for malloc */

typedef struct ns { int data; struct ns *next; } node; node *list_add(node **p, int i) { /* algunos compiladores no requieren un casting del valor del retorno para malloc node *n = (node *)malloc(sizeof(node)); if (n == NULL) return NULL; n->next = *p; *p = n; n->data = i; return n; } void list_remove(node **p) { /* borrar cabeza*/ if (*p != NULL) { node *n = *p; *p = (*p)->next; free(n); } } node **list_search(node **n, int i) { while (*n != NULL) { if ((*n)->data == i) { return n; } n = &(*n)->next; } return NULL; } void list_print(node *n) { */

if (n == NULL) { printf("lista esta vaca\n"); } while (n != NULL) { printf("print %p %p %d\n", n, n->next, n->data); n = n->next; } } int main(void) { node *n = NULL; list_add(&n, 0); /* lista: 0 */ list_add(&n, 1); /* lista: 1 0 */ list_add(&n, 2); /* lista: 2 1 0 */ list_add(&n, 3); /* lista: 3 2 1 0 */ list_add(&n, 4); /* lista: 4 3 2 1 0 */ list_print(n); list_remove(&n); /* borrar primero(4) */ list_remove(&n->next); /* borrar nuevo segundo (2) */ list_remove(list_search(&n, 1)); /* eliminar la celda que contiene el list_remove(&n->next); /* eliminar segundo nodo del final(0)*/ list_remove(&n); /* eliminar ultimo nodo (3) */ list_print(n); return 0; }

1 (primera) */

Implementacin de una lista enlazada en Maude


fmod LISTA-GENERICA {X :: TRIV} is protecting NAT . *** tipos sorts ListaGenNV{X} ListaGen{X} . subsort ListaGenNV{X} < ListaGen{X} . *** generadores op crear : -> ListaGen{X} [ctor] . op cons : X$Elt ListaGen{X} -> ListaGenNV{X} [ctor] . *** constructores op _::_ : ListaGen{X} ListaGen{X} -> ListaGen{X} [assoc id: crear ] . *** concatenacion op invertir : ListaGen{X} -> ListaGen{X} . op resto : ListaGenNV{X} -> ListaGen{X} .

*** selectores op primero : ListaGenNV{X} -> X$Elt . op esVacia? : ListaGen{X} -> Bool . op longitud : ListaGen{X} -> Nat . *** variables vars L L1 L2 : ListaGen{X} . vars E E1 E2 : X$Elt . *** ecuaciones

eq esVacia?(crear) = true . eq esVacia?(cons(E, L)) = false . eq primero(cons(E, L)) = E . eq resto(cons(E, L)) = L . eq longitud(crear) = 0 . eq longitud(cons(E, L)) = 1 + longitud(L) . eq cons(E1, L1) :: cons(E2, L2) = cons(E1, L1 :: cons(E2, L2)) . eq invertir(crear) = crear . eq invertir(cons(E, L)) = invertir(L) :: cons(E, crear) . endfm

Ejemplos de almacenamiento interno y externo Suponiendo que queremos crear una lista enlazada de familias y sus miembros. Usando almacenamiento interno, la estructura podra ser como la siguiente:
record member { // miembro de una familia member next string firstName integer age } record family { // // la propia familia family next string lastName string address member members // de la lista de miembros de la familia }

Para mostrar una lista completa de familias y sus miembros usando almacenamiento interno podramos escribir algo como esto:
aFamily := Families // comienzo de la lista de familias while aFamily null { // bucle a travs de la lista de familias print information about family aMember := aFamily.members // coger cabeza de esta lista de miembros de esta familia while aMember null { //bucle para recorrer la lista de miembros print information about member aMember := aMember.next } aFamily := aFamily.next }

Usando almacenamiento externo, nosotros podramos crear las siguientes estructuras:

record node { // estructura genrica de enlace node next pointer data // puntero genrico del dato al nodo } record member { // estructura de una familia string firstName integer age }

record family { // estructura de una familia string lastName string address node members // cabeza de la lista de miembros de esta familia }

Para mostrar una lista completa de familias y sus miembros usando almacenamiento externo, podramos escribir:
famNode := Families // comienzo de la cabeza de una lista de familias while famNode null { // bucle de lista de familias aFamily = (family) famNode.data // extraer familia del nodo print information about family memNode := aFamily.members // coger lista de miembros de familia while memNode null { bucle de lista de miembros aMember := (member) memNode.data // extraer miembro del nodo print information about member memNode := memNode.next } famNode := famNode.next }

IV. Pilas
Objetivo General: El alumno elaborar programas que integren el uso de recursividad y definir estructuras de datos para generar alternativas de programacin.

Pila (informtica)
Una pila (stack en ingls) es una lista ordinal o estructura de datos en la que el modo de acceso a sus elementos es de tipo LIFO (del ingls Last In First Out, ltimo en entrar, primero en salir) que permite almacenar y recuperar datos. Se aplica en multitud de ocasiones en informtica debido a su simplicidad y ordenacin implcita en la propia estructura.

Para el manejo de los datos se cuenta con dos operaciones bsicas: apilar (push), que coloca un objeto en la pila, y su operacin inversa, retirar (o desapilar, pop), que retira el ltimo elemento apilado. En cada momento slo se tiene acceso a la parte superior de la pila, es decir, al ltimo objeto apilado (denominado TOS, Top of Stack en ingls). La operacin retirar permite la obtencin de este elemento, que es retirado de la pila permitiendo el acceso al siguiente (apilado con anterioridad), que pasa a ser el nuevo TOS.

Por analoga con objetos cotidianos, una operacin apilar equivaldra a colocar un plato sobre una pila de platos, y una operacin retirar a retirarlo. Las pilas suelen emplearse en los siguientes contextos:

Evaluacin de expresiones en notacin postfija (notacin polaca inversa). Reconocedores sintcticos de lenguajes independientes del contexto Implementacin de recursividad.

Contenido

1 Historia 2 Pila de llamadas 3 Pila como tipo abstracto de datos o 3.1 Operaciones o 3.2 Implementacin 3.2.1 EN PYTHON 3.2.2 EN MAUDE 3.2.3 PILA CON PUNTEROS EN C++ o 3.3 Estructuras de datos relacionadas 4 Pilas Hardware 5 Arquitectura bsica de una pila 6 Soporte de Hardware 7 Soporte de Software 8 Expresin de evaluacin y anlisis sintctico o 8.1 Tiempo de ejecucin de la gestin de memoria o 8.2 Solucionar problemas de bsqueda 9 Seguridad 10 Vase tambin

Historia
El mtodo de pila para la evaluacin de expresiones fue propuesto en 1955 y dos aos despus patentado por Fiedrich L.Bauer, quin recibi en 1988 el premio "IEEE Computer Society Pioneer Award" por su trabajo en el desarrollo de dicha estructura de datos.

Pila de llamadas
La pila de llamadas es un segmento de memoria que utiliza esta estructura de datos para almacenar informacin sobre las llamadas a subrutinas actualmente en ejecucin en un programa en proceso.

Cada vez que una nueva subrutina es llamada, se apila una nueva entrada con informacin sobre sta tal como sus variables locales. En especial, se almacena aqu el punto de retorno al que regresar cuando esta subrutina termine (para volver a la subrutina anterior y continuar su ejecucin despus de esta llamada)..

Pila como tipo abstracto de datos


A modo de resumen tipo de datos, la pila es un contenedor de nodos y tiene dos operaciones bsicas: push (o apilar) y pop (o desapilar). 'Push' aade un nodo a la parte superior de la pila, dejando por debajo el resto de los nodos. 'Pop' elimina y devuelve el actual nodo superior de la pila. Una metfora que se utiliza con frecuencia es la idea de una pila de platos en una cafetera con muelle de pila. En esa serie, slo la primera placa es visible y accesible para el usuario, todas las dems placas permanecen ocultas. Como se aaden las nuevas placas, cada nueva placa se convierte en la parte superior de la pila, escondidos debajo de cada plato, empujando a la pila de placas. A medida que la placa superior se elimina de la pila, la segunda placa se convierte en la parte superior de la pila. Dos principios importantes son ilustrados por esta metfora: En primer lugar la ltima salida es un principio, la segunda es que el contenido de la pila est oculto. Slo la placa de la parte superior es visible, por lo que para ver lo que hay en la tercera placa, el primer y segundo platos tendrn que ser retirados. Operaciones Una pila cuenta con 2 operaciones imprescindibles: apilar y desapilar, a las que en las implementaciones modernas de las pilas se suelen aadir ms de uso habitual.

Crear: se crea la pila vaca. Apilar: se aade un elemento a la pila.(push) Desapilar: se elimina el elemento frontal de la pila.(pop) Cima: devuelve el elemento que esta en la cima de la pila. (top o peek) Vaca: devuelve cierto si la pila est vaca o falso en caso contrario.

Implementacin Un requisito tpico de almacenamiento de una pila de n elementos es O(n). El requisito tpico de tiempo de O(1) las operaciones tambin son fciles de satisfacer con un array o con listas enlazadas simples. La biblioteca de plantillas de C++ estndar proporciona una "pila" clase templated que se limita a slo apilar/desapilar operaciones. Java contiene una biblioteca de la clase Pila que es una especializacin de Vector. Esto podra ser considerado como un defecto, porque el diseo heredado get () de Vector mtodo LIFO ignora la limitacin de la Pila. Estos son ejemplos sencillos de una pila con las operaciones descritas anteriormente (pero no hay comprobacin de errores).

EN PYTHON
class Stack(object): def __init__(self): self.stack_pointer = None def push(self, element): self.stack_pointer = Node(element, self.stack_pointer) def pop(self): e = self.stack_pointer.element self.stack_pointer = self.stack_pointer.next return e def peek(self): return self.stack_pointer.element

def __len__(self): i = 0 sp = self.stack_pointer while sp: i += 1 sp = sp.next return i class Node(object): def __init__(self, element=None, next=None): self.element = element self.next = next if __name__ == '__main__': # small use example s = Stack() [s.push(i) for i in xrange(10)] print [s.pop() for i in xrange(len(s))]

EN MAUDE La PilaNV es la pila no vaca, que diferenciamos de la pila normal a la hora de tomar en cuenta errores. El elemento X representa el tipo de valor que puede contener la pila: entero, carcter, registro....
fmod PILA-GENERICA {X :: TRIV} is sorts Pila{X} PilaNV{X}. subsorts PilaNV{X} < Pila{X}. ***generadores: op crear: -> Pila {X} [ctor]. op apilar : X$Elt Pila{X} -> PilaNV{X} [ctor]. ***constructores op desapilar : Pila{X} -> Pila{X}. ***selectores op cima : PilaNV{X} -> X$Elt. ***variables var P : Pila{X}. var E : X$Elt. ***ecuaciones eq desapilar (crear) = crear. eq desapilar(apilar(E, P)) = P. eq cima(apilar(E, P)) = E. endfm

PILA CON PUNTEROS EN C++


#include<stdio.h> #include<conio.h> #define TAM 6 #define MAX TAM-1 typedef struct { int tope; int item[TAM]; }pila; int full(pila *); int empty(pila *);

void push(pila *, int); void pop(pila *,int *); void main() { pila p,t; int dato,opc,elemento,flag=0; p.tope=0; do { clrscr(); printf("\nMENU-PILA"); printf("\n1-> Insertar elemento"); printf("\n2-> Eliminar elemento"); printf("\n3-> Eliminar elemento X"); printf("\n4-> Visualizar"); printf("\n5-> Salir"); printf("\n\nDe su opcin : "); scanf("%d",&opc); switch(opc) { case 1: if(!full(&p)) // si pila no esta llena { printf("\nDe el elemento a insertar: "); scanf("%d",&dato); push(&p,dato); printf("\nElemento insertado..."); } else { printf("\nERROR: Pila llena"); } break; case 2: if(!empty(&p)) { pop(&p,&dato); printf("\nEl elemento eliminado es %d",dato); } else { printf("\nERROR: Pila vaca"); } break; case 3: if(!empty(&p)) { printf("eliminar elemento seleccionado: "); scanf("%d",&elemento); if(p.tope != 1){ t.tope=0; do { pop(&p,&dato); if (dato != elemento) { push(&t,dato); } }while(!empty(&p)); do { pop(&t,&dato); push(&p,dato);

}while(!empty(&t)); } else if(dato == elemento){pop(&p,&dato);} else {printf("el elemento no se encuentra en la pila");} } else { printf("\nERROR: Pila vaca"); } break; case 4: if(!empty(&p)) { t.tope=0; do { pop(&p,&dato); printf("\n%d",dato); push(&t,dato); }while(!empty(&p)); do { pop(&t,&dato); push(&p,dato); }while(!empty(&t)); } else { printf("\nERROR: Pila vaca"); } break; case 5: flag=1; break; case 6: flag=0; break; default: printf("\nOpcin no v lida..."); } if(!flag) { printf("\n\nPresione una tecla para continuar..."); getch(); } }while(!flag); } int full(pila *p) { return(p->tope==MAX); } int empty(pila *p) { return(p->tope==0); } void push(pila *p,int dato) { if(!full(p)) { (p->tope)++; p->item[p->tope]=dato; }

//elemento[1]=dato

else printf("\nOVERFLOW"); } void pop(pila *p,int *dato) { if(!empty(p)) { *dato=p->item[p->tope]; (p->tope)--; } else printf("\nUNDERFLOW"); }

Estructuras de datos relacionadas El tipo base de la estructura FIFO (el primero en entrar es el primero en salir)es la cola, y la combinacin de las operaciones de la pila y la cola es proporcionado por el deque. Por ejemplo, el cambio de una pila en una cola en un algoritmo de bsqueda puede cambiar el algoritmo de bsqueda en primera profundidad (en ingls, DFS) por una bsqueda en amplitud (en ingls, BFS). Una pila acotada es una pila limitada a un tamao mximo impuesto en su especificacin.

Pilas Hardware
Un uso muy comn de las pilas a nivel de arquitectura hardware es la asignacin de memoria.

Arquitectura bsica de una pila


Una pila tpica es un rea de la memoria de los computadores con un origen fijo y un tamao variable. Al principio, el tamao de la pila es cero. Un puntero de pila, por lo general en forma de un registro de hardware, apunta a la ms reciente localizacin en la pila; cuando la pila tiene un tamao de cero, el puntero de pila de puntos en el origen de la pila. Las dos operaciones aplicables a todas las pilas son:

Una operacin apilar, en el que un elemento de datos se coloca en el lugar apuntado por el puntero de pila, y la direccin en el puntero de pila se ajusta por el tamao de los datos de partida. Una operacin desapilar: un elemento de datos en la ubicacin actual apuntado por el puntero de pila es eliminado, y el puntero de pila se ajusta por el tamao de los datos de partida.

Hay muchas variaciones en el principio bsico de las operaciones de pila. Cada pila tiene un lugar fijo en la memoria en la que comienza. Como los datos se aadirn a la pila, el puntero de pila es desplazado para indicar el estado actual de la pila, que se expande lejos del origen (ya sea hacia arriba o hacia abajo, dependiendo de la aplicacin concreta). Por ejemplo, una pila puede comenzar en una posicin de la memoria de mil, y ampliar por debajo de las direcciones, en cuyo caso, los nuevos datos se almacenan en lugares que van por debajo de 1000, y el puntero de pila se decrementa cada vez que un nuevo elemento se agrega. Cuando un tema es eliminado de la pila, el puntero de pila se incrementa. Los punteros de pila pueden apuntar al origen de una pila o de un nmero limitado de direcciones, ya sea por encima o por debajo del origen (dependiendo de la direccin en que crece la pila), sin embargo el puntero de pila no puede cruzar el origen de la pila. En otras palabras, si el origen de la pila est en la direccin 1000 y la pila crece hacia abajo (hacia las direcciones 999, 998, y as sucesivamente), el puntero de pila nunca debe ser incrementado ms all de 1000 (para 1001, 1002, etc.) Si un desapilar operacin en la pila hace que el puntero de pila se deje atrs el origen de la pila, una pila se produce desbordamiento. Si una operacin de apilar hace

que el puntero de pila incremente o decremente ms all del mximo de la pila, en una pila se produce desbordamiento. La pila es visualizada ya sea creciente de abajo hacia arriba (como pilas del mundo real), o, con el mximo elemento de la pila en una posicin fija, o creciente, de izquierda a derecha, por lo que el mximo elemento se convierte en el mximo a "la derecha". Esta visualizacin puede ser independiente de la estructura real de la pila en la memoria. Esto significa que rotar a la derecha es mover el primer elemento a la tercera posicin, la segunda a la primera y la tercera a la segunda. Aqu hay dos equivalentes visualizaciones de este proceso:

Manzana Pltano Fresa ==rotar a la derecha==>

Pltano Fresa Manzana

Fresa Pltano Manzana ==rotar a la izquierda==>

Manzana Fresa Pltano

Una pila es normalmente representada en los ordenadores por un bloque de celdas de memoria, con los "de abajo" en una ubicacin fija, y el puntero de pila de la direccin actual de la "cima" de clulas de la pila. En la parte superior e inferior se utiliza la terminologa con independencia de que la pila crece realmente a la baja de direcciones de memoria o direcciones de memoria hacia mayores. Apilando un elemento en la pila,se ajusta el puntero de pila por el tamao de elementos (ya sea decrementar o incrementar, en funcin de la direccin en que crece la pila en la memoria), que apunta a la prxima celda, y copia el nuevo elemento de la cima en rea de la pila. Dependiendo de nuevo sobre la aplicacin exacta, al final de una operacin de apilar, el puntero de pila puede sealar a la siguiente ubicacin no utilizado en la pila, o tal vez apunte al mximo elemento de la pila. Si la pila apunta al mximo elemento de la pila, el puntero de pila se actualizar antes de que un nuevo elemento se apile, si el puntero que apunta a la prxima ubicacin disponible en la pila, que se actualizar despus de que el mximo elemento se apile en la pila. Desapilando es simplemente la inversa de apilar. El primer elemento de la pila es eliminado y el puntero de pila se actualiza, en el orden opuesto de la utilizada en la operacin de apilar.

Soporte de Hardware
Muchas CPUs tienen registros que se pueden utilizar como punteros de pila. Algunos, como el Intel x86, tienen instrucciones especiales que implcitamente el uso de un registro dedicado a la tarea de ser un puntero de pila. Otros, como el DEC PDP-11 y de la familia 68000 de Motorola tienen que hacer frente a los modos de hacer posible la utilizacin de toda una serie de registros como un puntero de pila. La serie Intel 80x87 numrico de coprocessors tiene un conjunto de registros que se puede acceder ya sea como una pila o como una serie de registros numerados. Algunos microcontroladores, por ejemplo algunos PICs, tienen un fondo fijo de pila que no es directamente accesible. Tambin hay una serie de microprocesadores que aplicar una pila directamente en el hardware:

Computer vaqueros MuP21 Harris RTX lnea Novix NC4016

Muchas pilas basadas en los microprocesadores se utilizan para aplicar el lenguaje de programacin Forth en el nivel de microcdigo. Pila tambin se utilizaron como base de una serie de mainframes y miniordenadores. Esas mquinas fueron llamados pila de mquinas, el ms famoso es el Burroughs B5000

Soporte de Software
En programas de aplicacin escrito en un lenguaje de alto nivel, una pila puede ser implementada de manera eficiente, ya sea usando vectores o listas enlazadas. En LISP no hay necesidad de aplicar la pila, ya que las funciones apilar y desapilar estn disponibles para cualquier lista. Adobe PostScript tambin est diseada en torno a una pila que se encuentra directamente visible y manipuladas por el programador. El uso de las pilas est muy presente en el desarrollo de software por ello la importancia de las pilas como tipo abstracto de datos.

Expresin de evaluacin y anlisis sintctico


Se calcula empleando la notacin polaca inversa utilizando una estructura de pila para los posibles valores. Las expresiones pueden ser representadas en prefijo, infijo, postfijo. La conversin de una forma de la expresin a otra forma necesita de una pila. Muchos compiladores utilizan una pila para analizar la sintaxis de las expresiones, bloques de programa, etc. Antes de traducir el cdigo de bajo nivel. La mayora de los lenguajes de programacin son de contexto libre de los idiomas que les permite ser analizados con mquinas basadas en la pila. Por ejemplo, el clculo: ((1 + 2) * 4) + 3, puede ser anotado como en notacin postfija con la ventaja de no prevalecer las normas y los parntesis necesario: 12+4*3+ La expresin es evaluada de izquierda a derecha utilizando una pila:

Apilar cuando se enfrentan a un operando y Desafilar dos operandos y evaluar el valor cuando se enfrentan a una operacin. Apilar el resultado.

De la siguiente manera (la Pila se muestra despus de que la operacin se haya llevado a cabo):
ENTRADA 1 2 + 4 * 3 + OPERACION Apilar operando Apilar operando Aadir Apilar operando Multiplicar Apilar operando Aadir PILA 1 1, 2 3 3, 4 12 12, 3 15

El resultado final, 15, se encuentra en la parte superior de la pila al final del clculo. Tiempo de ejecucin de la gestin de memoria Artculo principal: Pila basada en la asignacin de memoria y Pila mquina. Una serie de lenguajes de programacin estn orientadas a la pila, lo que significa que la mayora definen operaciones bsicas (aadir dos nmeros, la impresin de un carcter) cogiendo sus argumentos de la pila, y realizando de nuevo los valores de retorno en la pila. Por ejemplo, PostScript tiene una pila de retorno y un operando de pila, y tambin tiene un montn de grficos estado y un diccionario de pila.

Forth utiliza dos pilas, una para pasar argumentos y una subrutina de direcciones de retorno. El uso de una pila de retorno es muy comn, pero el uso poco habitual de un argumento para una pila legible para humanos es el lenguaje de programacin Forth razn que se denomina una pila basada en el idioma. Muchas mquinas virtuales tambin estn orientadas hacia la pila, incluida la p-cdigo mquina y la mquina virtual Java. Casi todos los entornos de computacin de tiempo de ejecucin de memoria utilizan una pila especial PILA para tener informacin sobre la llamada de un procedimiento o funcin y de la anidacin con el fin de cambiar al contexto de la llamada a restaurar cuando la llamada termina. Ellos siguen un protocolo de tiempo de ejecucin entre el que llama y el llamado para guardar los argumentos y el valor de retorno en la pila. Pila es una forma importante de apoyar llamadas anidadas o a funciones recursivas. Este tipo de pila se utiliza implcitamente por el compilador para apoyar CALL y RETURN estados (o sus equivalentes), y no es manipulada directamente por el programador. Algunos lenguajes de programacin utilizar la pila para almacenar datos que son locales a un procedimiento. El espacio para los datos locales se asigna a los temas de la pila cuando el procedimiento se introduce, y son borradas cuando el procedimiento termina. El lenguaje de programacin C es generalmente aplicado de esta manera. Utilizando la misma pila de los datos y llamadas de procedimiento tiene importantes consecuencias para la seguridad (ver ms abajo), de los que un programador debe ser consciente, a fin de evitar la introduccin de graves errores de seguridad en un programa. Solucionar problemas de bsqueda La bsqueda de la solucin de un problema, es independientemente de si el enfoque es exhaustivo u ptimo, necesita espacio en la pila. Ejemplos de bsqueda exhaustiva mtodos son fuerza bruta y backtraking. Ejemplos de bsqueda ptima a explorar mtodos,son branch and bound y soluciones heursticas. Todos estos algoritmos utilizan pilas para recordar la bsqueda de nodos que se han observado, pero no explorados an. La nica alternativa al uso de una pila es utilizar la recursividad y dejar que el compilador sea recursivo (pero en este caso el compilador todava est utilizando una pila interna). El uso de pilas es frecuente en muchos problemas, que van desde almacenar la profundidad de los rboles hasta resolver crucigramas o jugar al ajedrez por ordenador. Algunos de estos problemas pueden ser resueltos por otras estructuras de datos como una cola.

Seguridad
La seguridad a la hora de desarrollar software usando estructuras de datos de tipo pila es un factor a tener en cuenta debido a ciertas vulnerabilidad que un uso incorrecto de stas puede originar en la seguridad de nuestro software o en la seguridad del propio sistema que lo ejecuta. Por ejemplo, algunos lenguajes de programacin usan una misma pila para almacenar los datos para un procedimientos y el link que permite retornar a su invocador. sto significa que el programa introduce y extrae los datos de la misma pila en la que se encuentra informacin crtica con las direcciones de retorno de las llamadas a procedimiento, supongamos que al introducir datos en la pila lo hacemos en una posicin errnea de manera que introducimos una datos de mayor tamao al soportado por la pila corrompiendo as las llamadas a procedimientos provocariamos un fallo en nuestro programa. sta tcnica usada de forma maliciosa (es similar pero en otro mbito al buffer overflow) permitira a un atacante modificar el funcionamiento normal de nuestro programa y nuestro sistema, y es al menos una tcnica til si no lo evitamos en lenguajes muy populares como el ejemplo C.

V. Colas
Objetivo General: El alumno elaborar programas que integren el uso de recursividad y definir estructuras de datos para generar alternativas de programacin.

Cola (informtica)
Una cola es una estructura de datos, caracterizada por ser una secuencia de elementos en la que la operacin de insercin push se realiza por un extremo y la operacin de extraccin pop por el otro. Tambin se le llama estructura FIFO (del ingls First In First Out), debido a que el primer elemento en entrar ser tambin el primero en salir. Las colas se utilizan en sistemas informticos, transportes y operaciones de investigacin (entre otros), dnde los objetos, personas o eventos son tomados como datos que se almacenan y se guardan mediante colas para su posterior procesamiento. Este tipo de estructura de datos abstracta se implementa en lenguajes orientados a objetos mediante clases, en forma de listas enlazadas.

Contenido

1 Usos concretos de la cola 2 Informacin adicional 3 Operaciones Bsicas 4 Implementaciones o 4.1 Colas en Maude o 4.2 Colas en C++ o 4.3 Colas en JAVA o 4.4 Colas en C# 5 Tipos de colas 6 Vase tambin 7 Enlaces externos

Usos concretos de la cola


La particularidad de una estructura de datos de cola es el hecho de que slo podemos acceder al primer y al ltimo elemento de la estructura. As mismo, los elementos slo se pueden eliminar por el principio y slo se pueden aadir por el final de la cola.

Ejemplos de colas en la vida real seran: personas comprando en un supermercado, esperando para entrar a ver un partido de bisbol, esperando en el cine para ver una pelcula, una pequea peluquera, etc. La idea esencial es que son todos lneas de espera.

En estos casos, el primer elemento de la lista realiza su funcin (pagar comida, pagar entrada para el partido o para el cine) y deja la cola. Este movimiento est representado en la cola por la funcin pop o desencolar. Cada vez que otro elemento se aade a la lista de espera se aaden al final de la cola representando la funcin push o encolar. Hay otras funciones auxiliares para ver el tamao de la cola (size), para ver si est vaca en el caso de que no haya nadie esperando (empty) o para ver el primer elemento de la cola (front).

Informacin adicional
En caso de estar vaca borrar un elemento sera imposible hasta que no se aade un nuevo elemento. A la hora de aadir un elemento podramos darle una mayor importancia a unos elementos que a otros (un cargo VIP) y para ello se crea un tipo de cola especial que es la cola de prioridad.

Operaciones Bsicas

Crear: se crea la cola vaca. Encolar (aadir, entrar, push): se aade un elemento a la cola. Se aade al final de esta. Desencolar (sacar, salir, pop): se elimina el elemento frontal de la cola, es decir, el primer elemento que entr. Frente (consultar, front): se devuelve el elemento frontal de la cola, es decir, el primero elemento que entr.

Implementaciones
Colas en Maude La ColaNV es la cola no vaca, que diferenciamos de la cola normal a la hora de tomar en cuenta errores. A su vez, el elemento X representa el tipo de valor que puede contener la cola: entero, carcter, registro....
fmod COLA {X :: TRIV} is sorts ColaNV{X} Cola{X} . subsort ColaNV{X} < Cola{X} . *** generadores op crear : -> Cola{X} [ctor] . op encolar : X$Elt Cola{X} -> ColaNV {X} [ctor] . *** constructores op desencolar : Cola{X} -> Cola{X} . *** selectores op frente : ColaNV{X} -> X$Elt . *** variables var C : ColaNV{X} . vars E E2 : X$Elt . *** ecuaciones eq desencolar(crear) = crear . eq desencolar(encolar(E, crear)) = crear . eq desencolar(encolar(E, C)) = encolar(E, desencolar(C)) . eq frente(encolar(E, crear)) = E . eq frente(encolar(E, C)) = frente(C) . endfm Especificacin de una cola de colas de enteros en Maude: view VInt from TRIV to INT is sort Elt to Int . endv

view VColaInt from TRIV to COLA{VInt} is sort Elt to Cola{VInt} . endv fmod COLA-COLAS-INT is protecting INT . protecting COLA{VColaInt} . *** operaciones propias de la cola de colas de enteros op encolarInt : Int ColaNV{VColaInt} -> ColaNV{VColaInt} . op desencolarInt : Cola{VColaInt} -> Cola{VColaInt} . op frenteInt : ColaNV{VColaInt} -> [Int] . *** var var var var variables CCNV : ColaNV{VColaInt} . CC : Cola{VColaInt} . CE : Cola{VInt} . E : Int .

*** ecuaciones eq encolarInt(E, encolar(CE, CC)) = encolar(encolar(E, CE), CC) . eq desencolarInt (encolar(CE, crear)) = encolar(desencolar(CE), crear) . eq desencolarInt (encolar(CE, CCNV)) = encolar(CE, desencolarInt(CCNV)) . eq frenteInt(CCNV) = frente(frente(CCNV)) . endfm

Colas en C++
#ifndef COLA #define COLA // Define la cola template <class T> class Cola{ private: struct Nodo{ T elemento; struct Nodo* siguiente; // coloca el nodo en la segunda posicin }* primero; struct Nodo* ultimo; unsigned int elementos; public: Cola(){ elementos = 0; } ~Cola(){ while (elementos != 0) pop(); } void push(const T& elem){ Nodo* aux = new Nodo; aux->elemento = elem; if (elementos == 0) primero = aux; else ultimo->siguiente = aux; ultimo = aux; ++elementos; } void pop(){ Nodo* aux = primero; primero = primero->siguiente; delete aux; --elementos; } T consultar() const{ return primero->elemento; } bool vacia() const{ return elementos == 0; } unsigned int size() const{ return elementos;

} }; #endif

Colas en JAVA
public void inserta(Elemento x) { Nodo Nuevo; Nuevo = new Nodo(x, null); if (NodoCabeza == null) { NodoCabeza = Nuevo; } else { NodoFinal.Siguiente = Nuevo; } NodoFinal = Nuevo; } public Elemento cabeza() throws IllegalArgumentException { if (NodoCabeza == null) { throw new IllegalArgumentException(); } else { return NodoCabeza.Info; } } public Cola() { // Devuelve una Cola vaca NodoCabeza = null; NodoFinal = null; }

//Realizado Por Lic: Helton Petters

Colas en C#
public partial class frmPrincipal { // Variables globales public static string[] Cola; public static int Frente; public static int Final; public static int N; [STAThread] public static void Main(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new frmPrincipal()); } public frmPrincipal() { // Constructor

InitializeComponent(); Cola = new string[5]; N = 4; Frente = -1; Final = -1; } void CmdInsercionClick(object sender, System.EventArgs e) { frmInsercion Insercion = new frmInsercion(); Insercion.Show(); } void CmdRecorridoClick(object sender, System.EventArgs e) { frmRecorrido Recorrido = new frmRecorrido(); Recorrido.Show(); } // Arreglo lineal de 5

void CmdBusquedaClick(object sender, EventArgs e) { frmBusqueda Busqueda = new frmBusqueda(); Busqueda.Show(); } void CmdEliminacionClick(object sender, EventArgs e) { frmEliminacion Eliminar = new frmEliminacion(); Eliminar.Show(); } }

Algoritmo Insertar(Cola, N, Frente, Final, Elemento)


void CmdInsertarClick(object sender, System.EventArgs e) { elemento = txtInsercion.Text; // Se verifica que haya espacio en la Cola if (frmPrincipal.Frente == 0 && frmPrincipal.Final == frmPrincipal.N) { MessageBox.Show("La Cola esta llena"); return; } if (frmPrincipal.Frente == frmPrincipal.Final + 1) { MessageBox.Show("La Cola esta llena"); return; } // Si la cola esta vacia se inicializan punteros if (frmPrincipal.Frente == -1) { frmPrincipal.Frente = 0; frmPrincipal.Final = 0; } else if (frmPrincipal.Final == frmPrincipal.N) { frmPrincipal.Final = 0; } else { frmPrincipal.Final = frmPrincipal.Final + 1; } // Se agrega elemento a la Cola frmPrincipal.Cola[frmPrincipal.Final] = elemento; txtInsercion.Text = ""; }

Algoritmo Eliminacin (Cola, Frente, Final, N)


void CmdEliminarClick(object sender, EventArgs e) { if (frmPrincipal.Frente == -1) { MessageBox.Show("Cola Vacia"); return; } string elemento = frmPrincipal.Cola[frmPrincipal.Frente]; // si la cola tiene un solo elemento if (frmPrincipal.Frente == frmPrincipal.Final) { frmPrincipal.Frente = -1; frmPrincipal.Final = -1;

} else if (frmPrincipal.Frente == frmPrincipal.N) { frmPrincipal.Frente = 0; } else { frmPrincipal.Frente = frmPrincipal.Frente + 1; } lsEliminado.Items.Add(elemento); }

Tipos de colas

Colas circulares (anillos): en las que el ltimo elemento y el primero estn unidos. Colas de prioridad: En ellas, los elementos se atienden en el orden indicado por una prioridad asociada a cada uno. Si varios elementos tienen la misma prioridad, se atendern de modo convencional segn la posicin que ocupen. Hay 2 formas de implementacin:

1. Aadir un campo a cada nodo con su prioridad. Resulta conveniente mantener la cola ordenada por orden de prioridad. 2. Crear tantas colas como prioridades haya, y almacenar cada elemento en su cola.

Bicolas: son colas en donde los nodos se pueden aadir y quitar por ambos extremos; se les llama DEQUE (Double Ended QUEue). Para representar las bicolas lo podemos hacer con un array circular con Inicio y Fin que apunten a cada uno de los extremos. Hay variantes: Bicolas de entrada restringida: Son aquellas donde la insercin slo se hace por el final, aunque podemos eliminar al inicio al final. Bicolas de salida restringida: Son aquellas donde slo se elimina por el final, aunque se puede insertar al inicio y al final.

VI. rboles
Objetivo General: El alumno elaborar programas que integren el uso de recursividad y definir estructuras de datos para generar alternativas de programacin.

rbol (informtica)
En ciencias de la informtica, un rbol es una estructura de datos ampliamente usada que imita la forma de un rbol (un conjunto de nodos conectados). Un nodo es la unidad sobre la que se construye el rbol y puede tener cero o ms nodos hijos conectados a l. Se dice que un nodo a es padre de un nodo b si existe un enlace desde a hasta b (en ese caso, tambin decimos que b es hijo de a). Slo puede haber un nico nodo sin padres, que llamaremos raz. Un nodo que no tiene hijos se conoce como hoja. Los dems nodos (tienen padre y uno o varios hijos) se les conoce como rama.

Contenido

1 Definicin 2 Tipos de rboles 3 Operaciones de rboles. Representacin 4 Uso de los rboles 5 Vase tambin o 5.1 Algoritmos de bsqueda en rboles

Definicin
Formalmente, podemos definir un rbol de la siguiente forma:

Caso base: un rbol con slo un nodo (es a la vez raz del rbol y hoja). Un nuevo rbol a partir de un nodo nr y k rboles de races con

elementos cada uno, puede construirse estableciendo una relacin padre-hijo entre nr y cada una de las races de los k rboles. El rbol resultante de nodos tiene como raz el nodo nr, los nodos son los hijos de nr y el conjunto de nodos hoja est formado por la unin de los k conjuntos hojas iniciales. A cada uno de los rboles Ai se les denota ahora subrboles de la raz. Una sucesin de nodos del rbol, de forma que entre cada dos nodos consecutivos de la sucesin haya una relacin de parentesco, decimos que es un recorrido rbol. Existen dos recorridos tpicos para listar los nodos de un rbol: primero en profundidad y primero en anchura. En el primer caso, se listan los nodos expandiendo el hijo actual de cada nodo hasta llegar a una hoja, donde se vuelve al nodo anterior probando por el siguiente hijo y as sucesivamente. En el segundo, por su parte, antes de listar los nodos de nivel n + 1 (a distancia n + 1 aristas de la raz), se deben haber listado todos los de nivel n. Otros recorridos tpicos del rbol son preorden, postorden e inorden:

El recorrido en preorden, tambin llamado orden previo consiste en recorrer en primer lugar la raz y luego cada uno de los hijos en orden previo. El recorrido en inorden, tambin llamado orden simtrico (aunque este nombre slo cobra significado en los rboles binarios) consiste en recorrer en primer lugar A1, luego la raz y luego cada uno de los hijos en orden simtrico. El recorrido en postorden, tambin llamado orden posterior consiste en recorrer en primer lugar cada uno de los hijos en orden posterior y por ltimo la raz.

Finalmente, puede decirse que esta estructura es una representacin del concepto de rbol en teora de grafos. Un rbol es un grafo conexo y acclico (ver tambin teora de grafos y Glosario en teora de grafos).

Tipos de rboles

Ejemplo de rbol (binario).

rboles Binarios o rbol de bsqueda binario auto-balanceable rboles AVL rboles Rojo-Negro rbol AA rboles Multicamino o rboles B (Arboles de bsqueda multicamino autobalanceados) rbol-B+ rbol-B*

Operaciones de rboles. Representacin


Las operaciones comunes en rboles son:

Enumerar todos los elementos. Buscar un elemento. Dado un nodo, listar los hijos (si los hay). Borrar un elemento. Eliminar un subrbol (algunas veces llamada podar). Aadir un subrbol (algunas veces llamada injertar). Encontrar la raz de cualquier nodo.

Por su parte, la representacin puede realizarse de diferentes formas. Las ms utilizadas son:

Representar cada nodo como una variable en el heap, con punteros a sus hijos y a su padre. Representar el rbol con un array donde cada elemento es un nodo y las relaciones padre-hijo vienen dadas por la posicin del nodo en el array.

Uso de los rboles


Usos comunes de los rboles son:

Representacin de datos jerrquicos. Como ayuda para realizar bsquedas en conjuntos de datos (ver tambin: algoritmos de bsqueda en rboles).

rbol binario
En ciencias de la computacin, un rbol binario es una estructura de datos en la cual cada nodo siempre tiene un hijo izquierdo y un hijo derecho. No pueden tener ms de dos hijos (de ah el nombre "binario"). Si algn hijo tiene como referencia a null, es decir que no almacena ningn dato, entonces este es llamado un nodo externo. En el caso contrario el hijo es llamado un nodo interno. Usos comunes de los rboles binarios son los rboles binarios de bsqueda, los montculos binarios y Codificacin de Huffman.

Contenido

1 Definicin de teora de grafos 2 Tipos de rboles binarios 3 Implementacin en C 4 Recorridos sobre rboles binarios o 4.1 Recorridos en profundidad 4.1.1 Recorrido en preorden 4.1.2 Recorrido en postorden 4.1.3 Recorrido en inorden o 4.2 Recorridos en amplitud (o por niveles) 5 Mtodos para almacenar rboles binarios 6 Codificacin de rboles n-arios como rboles binarios

Definicin de teora de grafos

Un rbol binario sencillo de tamao 9 y altura 3, con un nodo raz cuyo valor es 2. En teora de grafos, se usa la siguiente definicin: Un rbol binario es un grafo conexo, acclico y no dirigido tal que el grado de cada vrtice no es mayor a 3. De esta forma slo existe un camino entre un par de nodos. Un rbol binario con enraizado es como un grafo que tiene uno de sus vrtices, llamado raz, de grado no mayor a 2. Con la raz escogida, cada vrtice tendr un nico padre, y nunca ms de dos hijos. Si rehusamos el requerimiento de la conectividad, permitiendo mltiples componentes conectados en el grafo, llamaremos a esta ltima estructura un bosque.

Tipos de rboles binarios


Un rbol binario es un rbol con raz en el que cada nodo tiene como mximo dos hijos. Un rbol binario lleno es un rbol en el que cada nodo tiene cero o dos hijos. Un rbol binario perfecto es un rbol binario lleno en el que todas las hojas (vrtices con cero hijos) estn a la misma profundidad (distancia desde la raz, tambin llamada altura).

A veces un rbol binario perfecto es denominado rbol binario completo. Otros definen un rbol binario completo como un rbol binario lleno en el que todas las hojas estn a profundidad n o n-1, para alguna n.

Un rbol binario es un rbol en el que ningn nodo puede tener ms de dos subrboles. En un rbol binario cada nodo puede tener cero, uno o dos hijos (subrboles). Se conoce el nodo de la izquierda como hijo izquierdo y el nodo de la derecha como hijo derecho.

Implementacin en C
Un rbol binario puede declararse de varias maneras. Algunas de ellas son: Estructura con manejo de memoria dinmica, siendo puntA el puntero que apunta al rbol de tipo tArbol:
struct celda { telemento dato; struct celda *izdo, *dcho; }; typedef struct celda *arbolbin;

Estructura con arreglo indexado:


typedef struct tArbol { int clave; int hIzquierdo, hDerecho; } tArbol; tArbol rbol[NUMERO_DE_NODOS];

En el caso de un rbol binario casi-completo (o un rbol completo), puede utilizarse un sencillo arreglo de enteros con tantas posiciones como nodos deba tener el rbol. La informacin de la ubicacin del nodo en el rbol es implcita a cada posicin del arreglo. As, si un nodo est en la posicin i, sus hijos se encuentran en las posiciones 2i+1 y 2i+2, mientras que su padre (si tiene), se encuentra en la posicin truncamiento((i-1)/2) (suponiendo que la raz est en la posicin cero). Este mtodo se beneficia de un almacenamiento ms compacto y una mejor localidad de referencia, particularmente durante un recorrido en preorden. La estructura para este caso sera por tanto:
int rbol[NUMERO_DE_NODOS];

Recorridos sobre rboles binarios


Recorridos en profundidad El mtodo de este recorrido es tratar de encontrar de la cabecera a la raz en nodo de unidad binaria. Ahora pasamos a ver la implementacin de los distintos recorridos: Recorrido en preorden En este tipo de recorrido se realiza cierta accin (quizs simplemente imprimir por pantalla el valor de la clave de ese nodo) sobre el nodo actual y posteriormente se trata el subrbol izquierdo y cuando se haya concluido, el subrbol derecho. Otra forma para entender el recorrido con este metodo seria seguir el orden: nodo raiz, nodo izquierda, nodo derecha. En el rbol de la figura el recorrido en preorden sera: 2, 7, 2, 6, 5, 11, 5, 9 y 4.
void preorden(tArbol *a) { if (a != NULL) {

tratar(a); preorden(a->hIzquierdo); preorden(a->hDerecho); } }

//Realiza una operacin en nodo

Implementacin en pseudocdigo de forma iterativa:


push(s,NULL); //insertamos en una pila (stack) el valor NULL, para asegurarnos de que est vaca push(s,raz); //insertamos el nodo raz MIENTRAS (s <> NULL) HACER p = pop(s); //sacamos un elemento de la pila tratar(p); //realizamos operaciones sobre el nodo p SI (D(p) <> NULL) //preguntamos si p tiene rbol derecho ENTONCES push(s,D(p)); FIN-SI SI (I(p) <> NULL) //preguntamos si p tiene rbol izquierdo ENTONCES push(s,I(p)); FIN-SI FIN-MIENTRAS

Recorrido en postorden En este caso se trata primero el subrbol izquierdo, despus el derecho y por ltimo el nodo actual. Otra forma para entender el recorrido con este metodo seria seguir el orden: nodo izquierda, nodo derecha, nodo raiz. En el rbol de la figura el recorrido en postorden sera: 2, 5, 11, 6, 7, 4, 9, 5 y 2.
void postorden(tArbol *a) { if (a != NULL) { postorden(a->hIzquiedo); postorden(a->hDerecho); tratar(a); } }

//Realiza una operacin en nodo

Recorrido en inorden En este caso se trata primero el subrbol izquierdo, despus el nodo actual y por ltimo el subrbol derecho. En un ABB este recorrido dara los valores de clave ordenados de menor a mayor. Otra forma para entender el recorrido con este metodo seria seguir el orden: nodo izquierda,nodo raiz,nodo derecha. En el rbol de la figura el recorrido en inorden sera: 2, 7, 5, 6, 11, 2, 5, 4 y 9. Esquema de implementacin:
void inorden(tArbol *a) { if (a != NULL) { inorden(a->hIzquierdo); tratar(a); inorden(a->hDerecho); } }

//Realiza una operacin en nodo

Recorridos en amplitud (o por niveles) En este caso el recorrido se realiza en orden por los distintos niveles del rbol. As, se comenzara tratando el nivel 1, que slo contiene el nodo raz, seguidamente el nivel 2, el 3 y as sucesivamente. En el rbol de la figura el recorrido en amplitud sera: 2, 7, 5, 2, 6, 9, 5, 11 y 4. Al contrario que en los mtodos de recorrido en profundidad, el recorrido por niveles no es de naturaleza recursiva. Por ello, se debe utilizar una cola para recordar los subrboles izquierdos y derecho de cada nodo.

El esquema algoritmo para implementar un recorrido por niveles es exactamente el mismo que el utilizado en la versin iterativa del recorrido en preorden pero cambiando la estructura de datos que almacena los nodos por una cola. Implementacin en C:
void arbol_recorrido_anch (tipo_Arbol* A) { tipo_Cola cola_nodos; // esta cola esta implementada previamente, almacena punteros (posiciones de nodos de arbol) tipo_Pos nodo_actual; // este es un puntero llevara el recorrido if (vacio(A)) // sie el arbol esta vacio, salimos return; cola_inicializa(&cola_nodos); // obvio, y necesario cola_enqueue(A, &cola_nodos); // se encola la raiz while (!vacia(&cola_nodos)) { // mientras la cola no se vacie se realizara el recorrido nodo_actual = cola_dequeue(&cola_nodos) // de la cola saldran los nodos ordenados por nivel printf("%c,", nodo_actual->info); // se "procesa" el nodo donde va el recorrido, en este caso se imprime if (nodo_actual->izq != null) // si existe, ponemos el hijo izquierdo en la cola cola_enqueue(nodo_actual->izq, &cola_nodos); if (nodo_actual->der != null) // si existe, ponemos el hijo derecho en la cola cola_enqueue(nodo_actual->der, &cola_nodos); } // al vaciarse la cola se han visitado todos los nodos del arbol }

Mtodos para almacenar rboles binarios


Los rboles binarios pueden ser construidos a partir de lenguajes de programacin de varias formas. En un lenguaje con registros y referencias, los rboles binarios son construidos tpicamente con una estructura de nodos y punteros en la cual se almacenan datos, cada uno de estos nodos tiene una referencia o puntero a un nodo izquierdo y a un nodo derecho denominados hijos. En ocasiones, tambin contiene un puntero a un nico nodo. Si un nodo tiene menos de dos hijos, algunos de los punteros de los hijos pueden ser definidos como nulos para indicar que no dispone de dicho nodo. En la figura adjunta se puede observar la estructura de dicha implementacin.

Los rboles binarios tambin pueden ser almacenados como una estructura de datos implcita en vectores, y si el rbol es un rbol binario completo, este mtodo no desaprovecha el espacio en memoria. Tomaremos como notacin la siguiente: si un nodo tiene un ndice i, sus hijos se encuentran en ndices 2i + 1 y 2i + 2, mientras que sus padres (si los tiene) se encuentra en el ndice (partiendo de que la raz tenga ndice cero). Este mtodo tiene como ventajas el tener almacenados los datos de forma ms compacta y por tener una forma ms rpida y eficiente de localizar los datos en particular durante un preoden transversal. Sin embargo, desperdicia mucho espacio en memoria.

Codificacin de rboles n-arios como rboles binarios


Hay un mapeo uno a uno entre los rboles generales y rboles binarios, el cual en particular es usado en Lisp para representar rboles generales como rboles binarios. Cada nodo N ordenado en el rbol corresponde a un nodo N 'en el rbol binario; el hijo de la izquierda de N es el nodo correspondiente al primer hijo de N, y el hijo derecho de N' es el nodo correspondiente al siguiente hermano de N, es decir, el prximo nodo en orden entre los hijos de los padres de N. Esta representacin como rbol binario de un rbol general, se conoce a veces como un rbol binario primer hijo hermano, o un rbol doblemente encadenado. Una manera de pensar acerca de esto es que los hijos de cada nodo estn en una lista enlazada, encadenados junto con el campo derecho, y el nodo slo tiene un puntero al comienzo o la cabeza de esta lista, a travs de su campo izquierdo. Por ejemplo, en el rbol de la izquierda, la A tiene 6 hijos (B, C, D, E, F, G). Puede ser convertido en el rbol binario de la derecha. Un ejemplo de transformar el rbol n-ario a un rbol binario Cmo pasar de rboles n-arios a rboles FLOFO.

El rbol binario puede ser pensado como el rbol original inclinado hacia los lados, con los bordes negros izquierdos representando el primer hijo y los azules representado los siguientes hermanos. Las hojas del rbol de la izquierda seran escritas en Lisp como:
(((M N) H I) C D ((O) (P)) F (L))

Que se ejecutar en la memoria como el rbol binario de la derecha, sin ningn tipo de letras en aquellos nodos que tienen un hijo izquierdo.

rbol binario de bsqueda auto-balanceable


En ciencias de la computacin, un rbol binario de bsqueda auto-balanceable o equilibrado es un rbol binario de bsqueda que intenta mantener su altura, o el nmero de niveles de nodos bajo la raz, tan pequeos como sea posible en todo momento, automticamente. Esto es importante, ya que muchas operaciones en un rbol de bsqueda binaria tardan un tiempo proporcional a la altura del rbol, y los rboles binarios de bsqueda ordinarios pueden tomar alturas muy grandes en situaciones normales, como cuando las claves son

insertadas en orden. Mantener baja la altura se consigue habitualmente realizando transformaciones en el rbol, como la rotacin de rboles, en momentos clave. Tiempos para varias operaciones en trminos del nmero de nodos en el rbol n:

Operacin

Tiempo en cota superior asinttica

Bsqueda

O(log n)

Insercin

O(log n)

Eliminacin

O(log n)

Iteracin en orden O(n)

Para algunas implementaciones estos tiempos son el peor caso, mientras que para otras estn amortizados. Estructuras de datos populares que implementan este tipo de rbol:

rbol AVL rbol rojo-negro

rbol AVL
rbol AVL es un tipo especial de rbol binario ideado por los matemticos rusos Adelson-Velskii y Landis. Fue el primer rbol de bsqueda binario auto-balanceable que se ide.

Contenido

1 Descripcin 2 Definicin formal o 2.1 Definicin de la altura de un rbol o 2.2 Definicin de rbol AVL 3 Factor de equilibrio 4 Operaciones o 4.1 Rotaciones o 4.2 Insercin o 4.3 Extraccin o 4.4 Bsqueda 5 Vase tambin 6 Enlaces externos

Descripcin

Un ejemplo de rbol binario no equilibrado (no es AVL)

Un ejemplo de rbol binario equilibrado (si es AVL) El rbol AVL toma su nombre de las iniciales de los apellidos de sus inventores, Adelson-Velskii y Landis. Lo dieron a conocer en la publicacin de un artculo en 1962: "An algorithm for the organization of information" ("Un algoritmo para la organizacin de la informacin"). Los rboles AVL estn siempre equilibrados de tal modo que para todos los nodos, la altura de la rama izquierda no difiere en ms de una unidad de la altura de la rama derecha. Gracias a esta forma de equilibrio (o balanceo), la complejidad de una bsqueda en uno de estos rboles se mantiene siempre en orden de complejidad O(log n). El factor de equilibrio puede ser almacenado directamente en cada nodo o ser computado a partir de las alturas de los subrboles. Para conseguir esta propiedad de equilibrio, la insercin y el borrado de los nodos se ha de realizar de una forma especial. Si al realizar una operacin de insercin o borrado se rompe la condicin de equilibrio, hay que realizar una serie de rotaciones de los nodos. Los rboles AVL ms profundos son los rboles de Fibonacci.

Definicin formal
Definicin de la altura de un rbol Sea T un rbol binario de bsqueda y sean Ti y Td sus subrboles, su altura H(T), es:

0 si el rbol T contiene solo la raz 1 + max(H(Ti),H(Td)) si contiene ms nodos

Definicin de rbol AVL


Un rbol vaco es un rbol AVL Si T es un rbol no vaco y Ti y Td sus subrboles, entonces T es AVL si y solo si:

Ti es AVL

Td es AVL | H(Ti) H(Td) | < = 1

Factor de equilibrio
Cada nodo, adems de la informacin que se pretende almacenar, debe tener los dos punteros a los rboles derecho e izquierdo, igual que los rboles binarios de bsqueda (ABB), y adems el dato que controla el factor de equilibrio. El factor de equilibrio es la diferencia entre las alturas del rbol derecho y el izquierdo: FE = altura subrbol derecho - altura subrbol izquierdo; Por definicin, para un rbol AVL, este valor debe ser -1, 0 1. Si el factor de equilibrio de un nodo es: 0 -> el nodo est equilibrado y sus subrboles tienen exactamente la misma altura. 1 -> el nodo est equilibrado y su subrbol derecho es un nivel ms alto. -1 -> el nodo est equilibrado y su subrbol izquierdo es un nivel ms alto. Si el factor de equilibrio Fe2 o Fe-2 es necesario reequilibrar.

Operaciones
Las operaciones bsicas de un rbol AVL implican generalmente el realizar los mismos algoritmos que seran realizados en un rbol binario de bsqueda desequilibrado, pero precedido o seguido por una o ms de las llamadas "rotaciones AVL".

Rotaciones El reequilibrado se produce de abajo hacia arriba sobre los nodos en los que se produce el desequilibrio. Pueden darse dos casos: rotacin simple o rotacin doble; a su vez ambos casos pueden ser hacia la derecha o hacia la izquierda.

ROTACIN SIMPLE A LA DERECHA. De un rbol de raz (r) y de hijos izquierdo (i) y derecho (d), lo que haremos ser formar un nuevo rbol cuya raz sea la raz del hijo izquierdo, como hijo izquierdo colocamos el hijo izquierdo de i (nuestro i) y como hijo derecho construimos un nuevo rbol que tendr como raz, la raz del rbol (r), el hijo derecho de i (d) ser el hijo izquierdo y el hijo derecho ser el hijo derecho del rbol (d).

op rotDer: AVL{X} -> [AVL{X}] . eq rotDer(arbolBin(R1, arbolBin(R2, I2, D2), D1)) == arbolBin(R2, I2, arbolBin(R1, D2, D)) .

ROTACIN SIMPLE A LA IZQUIERDA. De un rbol de raz (r) y de hijos izquierdo (i) y derecho (d), consiste en formar un nuevo rbol cuya raz sea la raz del hijo derecho, como hijo derecho colocamos el hijo derecho de d (nuestro d) y como hijo izquierdo construimos un nuevo rbol que tendr como raz la raz del rbol (r), el hijo izquierdo de d ser el hijo derecho (i) y el hijo izquierdo ser el hijo izquierdo del rbol (i). Precondicin : Tiene que tener hijo derecho no vaco.

op rotIzq: AVL{X} -> [AVL{X}] . eq rotIzq(arbolBin(R1, I, arbolBin(R2, I2, D2))) == arbolBin(R2, arbolBin(R1, I, I2), D2) .

Si la insercin se produce en el hijo derecho del hijo izquierdo del nodo desequilibrado (o viceversa) hay que realizar una doble rotacin. ROTACIN DOBLE A LA DERECHA.

ROTACIN DOBLE A LA IZQUIERDA.

Insercin La insercin en un rbol de AVL puede ser realizada insertando el valor dado en el rbol como si fuera un rbol de bsqueda binario desequilibrado y despus retrocediendo hacia la raz, rotando sobre cualquier nodo que pueda haberse desequilibrado durante la insercin. Proceso de insercin: 1.buscar hasta encontrar la posicin de insercin o modificacin (proceso idntico a insercin en rbol binario de bsqueda) 2.insertar el nuevo nodo con factor de equilibrio equilibrado 3.desandar el camino de bsqueda, verificando el equilibrio de los nodos, y re-equilibrando si es necesario

Debido a que las rotaciones son una operacin que tienen complejidad constante y a que la altura esta limitada a O (log(n)), el tiempo de ejecucin para la insercin es del orden O (log(n)).
op insertar: X$Elt AVL{X} -> AVLNV{X} . eq insertar(R, crear) == arbolBin(R, crear, crear) . ceq insertar(R1, arbolBin(R2, I, D)) == if (R1==R2) then arbolBin(R2, I, D) elseif (R1<R2) then if ( (altura(insertar(R1,I)) altura(D)) < 2) then arbolBin(R2, insertar(R1, I), D) else ***hay que reequilibrar if (R1 < raiz(I)) then rotarDer(arbolBin(R2, insertar(R1, I), D)) else rotarDer(arbolBin(R2, rotarIzq(insertar(R1, I)), D)) fi .

fi . else if ( (altura(insertar(R1,I)) altura(D)) < 2) then arbolBin(R2, insertar(R1, I), D) else *** hay que reequilibrar if (R1 > raiz(D)) then rotarIzq(arbolBin(R, I, insertar(R1, D))) else rotatIzq(arbolBin(R, I, rotarDer(insertar(R1, D)))) fi . fi . fi .

Extraccin El procedimiento de borrado es el mismo que en el caso de rbol binario de bsqueda.La diferencia se encuentra en el proceso de reequilibrado posterior. El problema de la extraccin puede resolverse en O (log n) pasos. Una extraccin trae consigo una disminucin de la altura de la rama donde se extrajo y tendr como efecto un cambio en el factor de equilibrio del nodo padre de la rama en cuestin, pudiendo necesitarse una rotacin. Esta disminucin de la altura y la correccin de los factores de equilibrio con sus posibles rotaciones asociadas pueden propagarse hasta la raz.

Borrar A, y la nueva raz ser M.

Borrado A, la nueva raz es M. Aplicamos la rotacin a la derecha.

El rbol resultante ha perdido altura. En borrado pueden ser necesarias varias operaciones de restauracin del equilibrio, y hay que seguir comprobando hasta llegar a la raz.

op eliminar: X$Elt AVL{X} -> AVL{X} . eq eliminar(R, crear) == crear . ceq eliminar(R1, arbolBin(R2, I, D)) == if (R1 == R2) then if esVacio(I) then D elseif esVacio(D) then I else if (altura(I) - altura(eliminar(min(D),D)) < 2) then arbolBin(min(D), I, eliminar(min(D), D))

***tenemos que reequilibrar elseif (altura(hijoIzq(I) >= altura(hijoDer(I)))) then rotDer(arbolBin(min(D), I, eliminar(min(D),D))) else rotDer(arbolBin(min(D), rotIzq(I), eliminar(min(D),D)))

Bsqueda public class Nodo {


int numMat; Nodo izqda, drcha; public Nodo(int mat){ numMat = mat; izqda = drcha = null; } public void re_enorden(){ if(izqda != null) izqda.re_enorden(); System.out.println("Matricula: if(drcha != null) drcha.re_enorden(); }

" +numMat);

} public class ABB {


private Nodo raiz; public ABB(){ raiz = null; } public void insertar(int nuevoM){ if(raiz==null){ raiz = new Nodo(nuevoM); } else{ insertar(raiz,nuevoM); } } private void insertar(Nodo rz, int nm){ if (rz == null) rz = new Nodo(nm); else if(nm < rz.numMat) insertar(rz.izqda,nm); else if(nm > rz.numMat) insertar(rz.drcha,nm); else System.out.println("Numero Duplicados"); } public void visualizar(){ if(raiz!=null) raiz.re_enorden(); }

} public class Ejecutar {


public static void main(String []args){ ABB arbol = new ABB(); arbol.insertar(6); arbol.insertar(3); arbol.insertar(7); arbol.visualizar(); }

} Ejemplo de TAD AVL

#include <stdio.h> typedef struct AVL{ int dato, FB; // FB es la altura del subarbol izquierdo menos la altura del subarbol derecho AVL *izq, *der; bool borrado; } AVL; void rotarLL(AVL* &A){ //precond: el arbol necesita una rotacion LL AVL* aux = A->izq->der; A->izq->der = A; A->izq->FB = 0; AVL* aux2 = A->izq; A->izq = aux; A->FB = 0; A = aux2; } void rotarRR(AVL* &A){ //precond: el arbol necesita una rotacion RR AVL* aux = A->der->izq; A->der->izq = A; A->der->FB = 0; AVL* aux2 = A->der; A->der = aux; A->FB = 0; A = aux2; } void rotarLRalter(AVL* &A){ //precond: el arbol necesita una rotacion LR rotarRR(A->izq); rotarLL(A); } void rotarRLalter(AVL* &A){ //precond: el arbol necesita una rotacion RL rotarLL(A->der); rotarRR(A); } AVL* Crear(){ return NULL; } void Insert(int n, bool &aumento, AVL* &A){ if (A == NULL){ A = new AVL; A->dato = n; A->FB = 0; A->izq = NULL; A->der = NULL; aumento = true; A->borrado = false; }else{ if (n < A->dato){ Insert(n, aumento, A->izq); if (aumento){ switch (A->FB){ case -1:{ A->FB = aumento break; } case 0:{ A->FB = aumento break; } case 1:{

0; = false;

1; = true;

if (A->izq->FB == 1){ // Si es 1 necesita una rotacion LL si es -1 necesita una rotacion LR rotarLL(A); }else{ rotarLRalter(A); } aumento = false; break; } } } }else{ Insert(n, aumento, A->der); if (aumento){ switch (A->FB){ case -1:{ if (A->der->FB == 1){ // Si es 1 necesita una rotacion RL si es -1 necesita una rotacion RR rotarRLalter(A); }else{ rotarRR(A); } aumento = false; break; } case 0:{ A->FB = -1; aumento = true; break; } case 1:{ A->FB = 0; aumento = false; break; } } } } } } void Insertar(AVL* &A, int n){ bool aumento; Insert(n, aumento, A); } bool EsVacio(AVL* A){ return A == NULL; } bool Pertenece(AVL* A, int n){ if (A == NULL){ return false; }else{ if (A->dato == n){ if (A->borrado){ return false; }else{ return true; } }else if (n < A->dato){ return Pertenece(A->izq, n); }else{ return Pertenece(A->der, n); } } } void Borrar(AVL* &A, int n){

if (A->dato == n){ A->borrado = true; }else if (n < A->dato){ Borrar(A->izq, n); }else{ Borrar(A->der, n); } }

rbol rojo-negro
Un rbol rojo negro es un tipo abstracto de datos, concretamente es un rbol binario de bsqueda equilibrado, una estructura de datos utilizada en informtica y ciencias de la computacin. La estructura original fue creada por Rudolf Bayer en 1972, que le dio el nombre de rboles-B binarios simtricos, pero tom su nombre moderno en un trabajo de Leo J. Guibas y Robert Sedgewick realizado en 1978. Es complejo, pero tiene un buen peor caso de tiempo de ejecucin para sus operaciones y es eficiente en la prctica. Puede buscar, insertar y borrar en un tiempo O(log n), donde n es el nmero de elementos del rbol. Sera ideal exponer la especificacin algebraica completa de este tipo abstracto de datos (TAD) escrita en algn lenguaje de especificacin de TADs como podra ser Maude; sin embargo, la complejidad de la estructura hace que la especificacin quede bastante ilegible, y no aportara nada. Por tanto, explicaremos su funcionamiento con palabras, esquemas e implementaciones de funciones en el lenguaje de programacin C.

Contenido

1 Terminologa 2 Propiedades 3 Usos y ventajas 4 Operaciones o 4.1 Rotacin o 4.2 Bsqueda o 4.3 Insercin o 4.4 Eliminacin 5 Demostracin de cotas 6 Complejidad 7 Referencias 8 Vase tambin 9 Enlaces externos o 9.1 Demos y simuladores o 9.2 Implementaciones

Terminologa
Un rbol rojo-negro es un tipo especial de rbol binario usado en informtica para organizar informacin compuesta por datos comparables (como por ejemplo nmeros). En los rboles rojo-negro las hojas no son relevantes y no contienen datos. A la hora de implementarlo en un lenguaje de programacin, para ahorrar memoria, un nico nodo (nodo-centinela) hace de nodo hoja para todas las ramas. As,todas las referencias de los nodos internos a las hojas van a parar al nodo centinela.

En los rboles rojo-negro, como en todos los rboles binarios de bsqueda, es posible moverse ordenadamente a travs de los elementos de forma eficiente si hay forma de localizar el padre de cualquier nodo. El tiempo de desplazarse desde la raz hasta una hoja a travs de un rbol equilibrado que tiene la mnima altura posible es de O(log n).

Propiedades

Un ejemplo de rbol rojo-negro Un rbol rojo-negro es un rbol binario de bsqueda en el que cada nodo tiene un atributo de color cuyo valor es o bien rojo o bien negro. Adems de los requisitos impuestos a los rboles binarios de bsqueda convencionales, se deben satisfacer los siguientes para tener un rbol rojo-negro vlido: 1. 2. 3. 4. 5. Todo nodo es o bien rojo o bien negro. La raz es negra. Todas las hojas son negras (las hojas son los hijos nulos). Los hijos de todo nodo rojo son negros (tambin llamada "Propiedad del rojo"). Cada camino simple desde un nodo a una hoja descendiente contiene el mismo nmero de nodos negros, ya sea contando siempre los nodos negros nulos, o bien no contndolos nunca (el resultado es equivalente). Tambin es llamada "Propiedad del camino", y al nmero de nodos negros de cada camino, que es constante para todos los caminos, se le denomina "Altura negra del rbol", y por tanto el cmino no puede tener dos rojos seguidos. 6. El camino ms largo desde la raz hasta una hoja no es ms largo que 2 veces el camino ms corto desde la raz del rbol a una hoja en dicho rbol. El resultado es que dicho rbol est aproximadamente equilibrado. Dado que las operaciones bsicas como insertar, borrar y encontrar valores tienen un peor tiempo de bsqueda proporcional a la altura del rbol, esta cota superior de la altura permite a los rboles rojo-negro ser eficientes en el peor caso, de forma contraria a lo que sucede en los rboles binarios de bsqueda. Para ver que estas propiedades garantizan lo dicho, basta ver que ningn camino puede tener 2 nodos rojos seguidos debido a la propiedad 4. El camino ms corto posible tiene todos sus nodos negros, y el ms largo alterna entre nodos rojos y negros. Como todos los caminos mximos tienen el mismo nmero de nodos negros, por la propiedad 5, esto muestra que no hay ningn camino que pueda tener el doble de longitud que otro camino. En muchas presentaciones de estructuras arbreas de datos, es posible para un nodo tener solo un hijo y las hojas contienen informacin. Es posible presentar los rboles rojo-negro en este paradigma, pero cambian algunas de las propiedades y se complican los algoritmos. Por esta razn, este artculo utilizan hojas nulas, que no contienen informacin y simplemente sirven para indicar dnde el rbol acaba, como se mostr antes. Habitualmente estos nodos son omitidos en las representaciones, lo cual da como resultado un rbol que parece contradecir los principios expuestos antes, pero que realmente no los contradice. Como consecuencia de esto todos los nodos internos tienen dos hijos, aunque uno o ambos nodos podran ser una hoja nula.

Otra explicacin que se da del rbol rojo-negro es la de tratarlo como un rbol binario de bsqueda cuyas aristas, en lugar de nodos, son coloreadas de color rojo o negro, pero esto no produce ninguna diferencia. El color de cada nodo en la terminologa de este artculo corresponde al color de la arista que une el nodo a su padre, excepto la raz, que es siempre negra (por la propiedad 2) donde la correspondiente arista no existe.

Usos y ventajas
Los rboles rojo-negro ofrecen un peor caso con tiempo garantizado para la insercin, el borrado y la bsqueda. No es esto nicamente lo que los hace valiosos en aplicaciones sensibles al tiempo como las aplicaciones en tiempo real, sino que adems son apreciados para la construccin de bloques en otras estructuras de datos que garantizan un peor caso. Por ejemplo, muchas estructuras de datos usadas en geometra computacional pueden basarse en rboles rojo-negro. El rbol AVL es otro tipo de estructura con O(log n) tiempo de bsqueda, insercin y borrado. Est equilibrado de forma ms rgida que los rboles rojo-negro, lo que provoca que la insercin y el borrado sean ms lentos pero la bsqueda y la devolucin del resultado de la misma ms veloz. Los rboles rojo-negro son particularmente valiosos en programacin funcional, donde son una de las estructuras de datos persistentes ms comnmente utilizadas en la construccin de arrays asociativos y conjuntos que pueden retener versiones previas tras mutaciones. La versin persistente del rbol rojo-negro requiere un espacio O(log n) para cada insercin o borrado, adems del tiempo. Los rboles rojo-negro son isomtricos a los rboles 2-3-4. En otras palabras, para cada rbol 2-3-4, existe un rbol correspondiente rojo-negro con los datos en el mismo orden. La insercin y el borrado en rboles 2-3-4 son tambin equivalentes a los cambios de colores y las rotaciones en los rboles rojo-negro. Esto los hace ser una herramienta til para la comprensin del funcionamiento de los rboles rojo-negro y por esto muchos textos introductorios sobre algoritmos presentan los rboles 2-3-4 justo antes que los rboles rojo-negro, aunque frecuentemente no sean utilizados en la prctica.

Operaciones
Las operaciones de slo lectura en un rbol rojo-negro no requieren modificacin alguna con respecto a las utilizadas en los rboles binarios de bsqueda, ya que cada rbol rojo-negro es un caso especial de rbol binario de bsqueda. Sin embargo, el resultado inmediato de una insercin o la eliminacin de un nodo utilizando los algoritmos de un rbol binario de bsqueda normal podra violar las propiedades de un rbol rojo-negro. Restaurar las propiedades rojo-negro requiere un pequeo nmero (O(log n))de cambios de color (que son muy rpidos en la prctica) y no ms de 3 rotaciones (2 por insercin). A pesar de que las operaciones de insercin y borrado son complicadas, sus tiempos de ejecucin siguen siendo O(log n).

Rotacin Para conservar las propiedades que debe cumplir todo rbol rojo-negro, en ciertos casos de la insercin y la eliminacin ser necesario reestructurar el rbol, si bien no debe perderse la ordenacin relativa de los nodos. Para ello, se llevan a cabo una o varias rotaciones, que no son ms que reestructuraciones en las relaciones padre-hijo-to-nieto. Las rotaciones que se consideran a continuacin son simples; sin embargo, tambin se dan las rotaciones dobles.

En las imgenes pueden verse de forma simplificada cmo se llevan a cabo las rotaciones simples hacia la izquierda y hacia la derecha en cualquier rbol binario de bsqueda, en particular en cualquier rbol rojo-negro. Podemos ver tambin la implementacin en C de dichas operaciones.

void rotar_izda(struct node *p) { struct node *aux; aux = p; p = p->dcho; aux-> dcho = p->izdo; p->izdo = aux; }

void rotar_dcha(struct node *p) { struct node *aux; aux = p; p = p->izdo; aux->izdo = p->dcho; p->dcho = aux, }

Bsqueda La bsqueda consiste acceder a la raz del rbol, si el elemento a localizar coincide con ste la bsqueda ha concluido con xito, si el elemento es menor se busca en el subrbol izquierdo y si es mayor en el derecho. Si se alcanza un nodo hoja y el elemento no ha sido encontrado se supone que no existe en el rbol. Cabe destacar que la bsqueda en este tipo de rboles es muy eficiente, representa una funcin logartmica. La bsqueda de un

elemento en un ABB (rbol Binario de Bsqueda) en general, y en un rbol rojo negro en particular, se puede realizar de dos formas, iterativa o recursiva. Ejemplo de versin iterativa en el lenguaje de programacin C, suponiendo que estamos buscando una clave alojada en un nodo donde est el correspondiente "dato" que precisamos encontrar:
data Buscar_ABB(abb t,clave k) { abb p; dato e; e=NULL; p=t; if (!estaVacio(p)) { while (!estaVacio(p) && (p->k!=k) ) { if (k < p->k) { p=p->l; } if (p->k < k) { p=p->r; } } if (!estaVacio(p) &&(p->d!=NULL) ) { e=copiaDato(p->d); } } return e; }

Vase ahora la versin recursiva en ese mismo lenguaje:


int buscar(tArbol *a, int elem) { if (a == NULL) return 0; else if (a->clave < elem) return buscar(a->hDerecho, elem); else if (a->clave > elem) return buscar(a->hIzquierdo, elem); else return 1; }

Insercin La insercin comienza aadiendo el nodo como lo haramos en un rbol binario de bsqueda convencional y pintndolo de rojo. Lo que sucede despus depende del color de otros nodos cercanos. El trmino to nodo ser usado para referenciar al hermano del padre de un nodo, como en los rboles familiares humanos. Conviene notar que:

La propiedad 3 (Todas las hojas, incluyendo las nulas, son negras) siempre se cumple. La propiedad 4 (Ambos hijos de cada nodo rojo son negros) est amenazada solo por aadir un nodo rojo, por repintar un nodo negro de color rojo o por una rotacin. La propiedad 5 (Todos los caminos desde un nodo dado hasta sus nodos hojas contiene el mismo nmero de nodos negros) est amenazada solo por aadir un nodo rojo, por repintar un nodo negro de color rojo o por una rotacin.

Al contrario de lo que sucede en otros rboles como puede ser el rbol AVL, en cada insercin se realiza un mximo de una rotacin, ya sea simple o doble. Por otra parte, se asegura un tiempo de recoloracin mximo de O(log2n) por cada insercin. Nota: En los esquemas que acompaan a los algoritmos, la etiqueta N ser utilizada por el nodo que est siendo insertado, P para los padres del nodo N, G para los abuelos del nodo N, y U para los tos del nodo N. Notamos que los roles y etiquetas de los nodos estn intercambiados entre algunos casos, pero en cada caso, toda etiqueta contina representando el mismo nodo que representaba al comienzo del caso. Cualquier color mostrado en el diagrama est o bien supuesto en el caso o implicado por dichas suposiciones.

Los nodos to y abuelo pueden ser encontrados por las siguientes funciones:

struct node * abuelo(struct node *n) { if ((n != NULL) && (n->padre != NULL)) return n->padre->padre; else return NULL; } struct node * tio(struct node *n) { struct node *a = abuelo(n); if (n->padre == a->izdo) return a->dcho; else return a->izdo; }

Estudiemos ahora cada caso de entre los posibles que nos podemos encontrar al insertar un nuevo nodo. Caso 1: El nuevo nodo N es la raz de del rbol. En este caso, es repintado a color negro para satisfacer la propiedad 2 (la raz es negra). Como esto aade un nodo negro a cada camino, la propiedad 5 (todos los caminos desde un nodo dado a sus hojas contiene el mismo nmero de nodos negros) se mantiene. En C quedara as:
void insercion_caso1(struct node *n) { if (n->padre == NULL) n->color = NEGRO; else insercion_caso2(n); }

Caso 2: El padre del nuevo nodo (esto es, el nodo P) es negro, as que la propiedad 4 (ambos hijos de cada nodo rojo son negros) se mantiene. En este caso, el rbol es aun vlido. La propiedad 5 (todos los caminos desde cualquier nodo dado a sus hojas contiene igual nmero de nodos negros) se mantiene, porque el nuevo nodo N tiene dos hojas negras como hijos, pero como N es rojo, los caminos a travs de cada uno de sus hijos tienen el mismo nmero de nodos negros que el camino hasta la hoja que reemplaz, que era negra, y as esta propiedad se mantiene satisfecha. Su implementacin:
void

insercion_caso2(struct node *n) { if (n->padre->color == NEGRO) return; /* rbol vlido. */ else insercion_caso3(n); }

Nota: En los siguientes casos se puede asumir que N tiene un abuelo, el nodo G, porque su padre P es rojo, y si fuese la raz, sera negro. Consecuentemente, N tiene tambin un nodo to U a pesar de que podra ser una hoja en los casos 4 y 5.

Caso 3: Si el padre P y el to U son rojos, entonces ambos nodos pueden ser repintados de negro y el abuelo G se convierte en rojo para mantener la propiedad 5 (todos los caminos desde cualquier nodo dado hasta sus hojas contiene el mismo nmero de nodos negros). Ahora, el nuevo nodo rojo N tiene un padre negro. Como cualquier camino a travs del padre o el to debe pasar a travs del abuelo, el nmero de nodos negros en esos caminos no ha cambiado. Sin embargo, el abuelo G podra ahora violar la propiedad 2 (la raz es negra) o la 4 (ambos hijos de cada nodo rojo son negros), en el caso de la 4 porque G podra tener un padre rojo. Para solucionar este problema, el procedimiento completo se realizar de forma recursiva hacia arriba hasta alcanzar el caso 1. El cdigo en C quedara de la siguiente forma:
void insercion_caso3(struct node *n) { struct node *t = tio(n), *a; if ((t != NULL) && (t->color == ROJO)) { n->padre->color = NEGRO; t->color = NEGRO; a = abuelo(n); a->color = ROJO; insercion_caso1(a); } else { insercion_caso4(n); } }

Nota: En los casos restantes, se asume que el nodo padre P es el hijo izquierdo de su padre. Si es el hijo derecho, izquierda y derecha deberan ser invertidas a partir de los casos 4 y 5. El cdigo del ejemplo toma esto en consideracin.

Caso 4: El nodo padre P es rojo pero el to U es negro; tambin, el nuevo nodo N es el hijo derecho de P, y P es el hijo izquierdo de su padre G. En este caso, una rotacin a la izquierda que cambia los roles del nuevo nodo N y su padre P puede ser realizada; entonces, el primer nodo padre P se ve implicado al usar el caso 5 de insercin (reetiquetando N y P ) debido a que la propiedad 4 (ambos hijos de cada nodo rojo son negros) se mantiene an incumplida. La rotacin causa que algunos caminos (en el sub-rbol etiquetado como 1) pasen a travs del nuevo nodo donde no lo hacan antes, pero ambos nodos son rojos, as que la propiedad 5 (todos los caminos desde cualquier nodo dado a sus hojas contiene el mismo nmero de nodos negros) no es violada por la rotacin. Aqu tenemos una posible implementacin:
void insercion_caso4(struct node *n) { struct node *a = abuelo(n); if ((n == n->padre->dcho) && (n->padre == a->izdo)) { rotar_izda(n->padre); n = n->izdo; } else if ((n == n->padre->izdo) && (n->padre == a->dcho)) { rotar_dcha(n->padre); n = n->dcho; } insercion_caso5(n); }

Caso 5: El padre P es rojo pero el to U es negro, el nuevo nodo N es el hijo izquierdo de P, y P es el hijo izquierdo de su padre G. En este caso, se realiza una rotacin a la derecha sobre el padre P; el resultado es un rbol donde el padre P es ahora el padre del nuevo nodo N y del inicial abuelo G. Este nodo G ha de ser negro, as como su hijo P rojo. Se intercambian los colores de ambos y el resultado satisface la propiedad 4 (ambos hijos de un nodo rojo son negros). La propiedad 5 (todos los caminos desde un nodo dado hasta sus hojas contienen el mismo nmero de nodos negros) tambin se mantiene satisfecha, ya que todos los caminos que iban a travs de esos tres nodos entraban por G antes, y ahora entran por P. En cada caso, este es el nico nodo negro de los tres. Una posible implementacin en C es la siguiente:
void insercion_caso5(struct node *n) { struct node *a = abuelo(n); n->padre->color = NEGRO; a->color = ROJO; if ((n == n->padre->izdo) && (n->padre == a->izdo)) { rotar_dcha(a); } else {

/* * En este caso, (n == n->padre->dcho) && (n->padre == a->dcho). */ rotar_izda(a); } }

Ntese que la insercin se realiza sobre el propio rbol y que los cdigos del ejemplo utilizan recursin de cola. Eliminacin En un rbol binario de bsqueda normal, cuando se borra un nodo con dos nodos internos como hijos, tomamos el mximo elemento del subrbol izquierdo o el mnimo del subrbol derecho, y movemos su valor al nodo que es borrado (como se muestra aqu). Borramos entonces el nodo del que copibamos el valor que debe tener menos de dos nodos no hojas por hijos. Copiar un valor no viola ninguna de las propiedades rojo-negro y reduce el problema de borrar en general al de borrar un nodo con como mucho un hijo no hoja. No importa si este nodo es el nodo que queramos originalmente borrar o el nodo del que copiamos el valor. Resumiendo, podemos asumir que borramos un nodo con como mucho un hijo no hoja (si solo tiene nodos hojas por hijos, tomaremos uno de ellos como su hijo). Si borramos un nodo rojo, podemos simplemente reemplazarlo con su hijo, que debe ser negro. Todos los caminos hasta el nodo borrado simplemente pasarn a travs de un nodo rojo menos, y ambos nodos, el padre del borrado y el hijo, han de ser negros, as que las propiedades 3 (todas las hojas, incluyendo las nulas, son negras) y 4 (los dos hijos de cada nodo rojo son negros) se mantienen. Otro caso simple es cuando el nodo borrado es negro y su hijo es rojo. Simplemente eliminar un nodo negro podra romper las propiedades 4 (los dos hijos de cada nodo rojo son negros) y 5 (todos los caminos desde un nodo dado hasta sus hojas contienen el mismo nmero de nodos negros), pero si repintamos su hijo de negro, ambas propiedades quedan preservadas. El caso complejo es cuando el nodo que va a ser borrado y su hijo son negros. Empezamos por reemplazar el nodo que va a ser borrado con su hijo. Llamaremos a este hijo (en su nueva posicin) N, y su hermano (el nuevo hijo de su padre) S. En los diagramas de debajo, usaremos P para el nuevo padre de N, SL para el hijo izquierdo de S, y SR para el nuevo hijo derecho de S (se puede mostrar que S no puede ser una hoja). Nota: Entre algunos casos cambiamos roles y etiquetas de los nodos, pero en cada caso, toda etiqueta sigue representando al mismo nodo que representaba al comienzo del caso. Cualquier color mostrado en el diagrama es o bien supuesto en su caso o bien implicado por dichas suposiciones. El blanco representa un color desconocido (o bien rojo o bien negro). El cumplimiento de estas reglas en un rbol con n nodos, asegura un mximo de tres rotaciones y hasta O(log2n) recoloraciones.

Encontraremos el hermano usando esta funcin:


struct node * hermano(struct node *n) { if (n == n->padre->izdo) return n->padre->dcho; else return n->padre->izdo; }

Nota: Con el fin de preservar la buena definicin del rbol, necesitamos que toda hoja nula siga siendo una hoja nula tras todas las transformaciones (que toda hoja nula no tendr ningn hijo). Si el nodo que estamos borrando tiene un hijo no hoja N, es fcil ver que la propiedad se satisface. Si, por otra parte N fuese una hoja nula, se verifica por los diagramas o el cdigo que para todos los casos la propiedad se satisface tambin.

Podemos realizar los pasos resaltados arriba con el siguiente cdigo, donde la funcin reemplazar_nodo sustituye hijo en el lugar de n en el rbol. Por facilitar la comprensin del ejemplo, en el cdigo de esta seccin supondremos que las hojas nulas estn representadas por nodos reales en lugar de NULL (el cdigo de la seccin insercin trabaja con ambas representaciones).
void elimina_un_hijo(struct node *n) { /* * Precondicin: n tiene al menos un hijo no nulo. */ struct node *hijo = es_hoja(n->dcho) ? n->izdo : n->dcho; reemplazar_nodo(n, hijo); if (n->color == NEGRO) { if (hijo->color == ROJO) hijo->color = NEGRO; else eliminar_caso1(hijo); } free(n); }

Nota: Si N es una hoja nula y no queremos representar hojas nulas como nodos reales, podemos modificar el algoritmo llamando primero a eliminar_caso1() en su padre (el nodo que borramos, n en el cdigo anterior) y borrndolo despus. Podemos hacer esto porque el padre es negro, as que se comporta de la misma forma que una hoja nula (y a veces es llamada hoja fantasma). Y podemos borrarla con seguridad, de tal forma que n seguir siendo una hoja tras todas las operaciones, como se muestra arriba.

Si N y su padre original son negros, entonces borrar este padre original causa caminos que pasan por N y tienen un nodo negro menos que los caminos que no. Como esto viola la propiedad 5 (todos los caminos desde un nodo dado hasta su nodos hojas deben contener el mismo nmero de nodos negros), el rbol debe ser reequilibrado. Hay varios casos a considerar.

Caso 1: N es la nueva raz. En este caso, hemos acabado. Borramos un nodo negro de cada camino y la nueva raz es negra, as las propiedades se cumplen. Una posible implementacin en el lenguaje de programacin C sera la siguiente:
void eliminar_caso1(struct node *n) { if (n->padre!= NULL) eliminar_caso2(n); }

Nota: En los casos 2, 5 y 6, asumimos que N es el hijo izquierdo de su padre P. Si ste fuese el hijo derecho, la izquierda y la derecha deberan ser invertidas en todos estos casos. De nuevo, el cdigo del ejemplo toma ambos casos en cuenta.

Caso 2: S es rojo. En este caso invertimos los colores de P y S, por lo que rotamos a la izquierda P, pasando S a ser el abuelo de N. Ntese que P tiene que ser negro al tener un hijo rojo. Aunque todos los caminos tienen todava el mismo nmero de nodos negros, ahora N tiene un hermano negro y un padre rojo, as que podemos proceder a al paso 4, 5 o 6 (este nuevo hermano es negro porque ste era uno de los hijos de S, que es rojo). En casos posteriores, reetiquetaremos el nuevo hermano de N como S. Aqu podemos ver una implementacin:
void eliminar_caso2(struct node *n) { struct node *hm = hermano_menor(n); if (hm->color == ROJO) { n->padre->color = ROJO; hm->color = NEGRO; if (n == n->padre->izdo) rotar_izda(n->padre); else rotar_dcha(n->padre); } eliminar_caso3(n); }

Caso 3: P, S y los hijos de S son negros. En este caso, simplemente cambiamos S a rojo. El resultado es que todos los caminos a travs de S, precisamente aquellos que no pasan por N, tienen un nodo negro menos. El hecho de borrar el padre original de N haciendo que todos los caminos que pasan por N tengan un nodo negro menos nivela el rbol. Sin embargo, todos los caminos a travs de P tienen ahora un nodo negro menos que los caminos que no pasan por P, as que la propiedad 5 an no se cumple (todos los caminos desde cualquier nodo a su nodo hijo contienen el mismo nmero de nodos negros). Para corregir esto, hacemos el proceso de reequilibrio en P, empezando en el caso 1. Su implementacin en C:
void eliminar_caso3(struct node *n) { struct node *hm = hermano_menor(n); if ((n->padre->color == NEGRO) && (hm->color == NEGRO) && (hm->izdo->color == NEGRO) && (hm->dcho->color == NEGRO)) { hm->color = ROJO; eliminar_caso1(n->padre); } else eliminar_caso4(n); }

Caso 4: S y los hijos de ste son negros, pero P es rojo. En este caso, simplemente intercambiamos los colores de S y P. Esto no afecta al nmero de nodos negros en los caminos que no van a travs de S, pero aade uno al nmero de nodos negros a los caminos que van a travs de N, compensando as el borrado del nodo negro en dichos caminos. Si lo implementamos en C, quedara:
void eliminar_caso4(struct node *n) { struct node *hm = hermano_menor(n); if ((n->padre->color == ROJO) && (hm->color == NEGRO) && (hm->izdo->color == NEGRO) && (hm->dcho->color == NEGRO)) { hm->color = ROJO; n->padre->color = NEGRO; } else eliminar_caso5(n); }

Caso 5: S es negro, su hijo izquierdo es rojo, el derecho es negro, y N es el hijo izquierdo de su padre. En este caso rotamos a la derecha S, as su hijo izquierdo se convierte en su padre y en el hermano de N. Entonces intercambiamos los colores de S y su nuevo padre. Todos los caminos tienen an el mismo nmero de nodos negros, pero ahora N tiene un hermano negro cuyo hijo derecho es rojo, as que caemos en el caso 6. Ni N ni su padre son afectados por esta transformacin (de nuevo, por el caso 6, reetiquetamos el nuevo hermano de N como S). He aqu la implementacin en C:
void eliminar_caso5(struct node *n) { struct node *hm = hermano_menor(n); if ((n == n->padre->izdo) && (hm->color == NEGRO) && (hm->izdo->color == ROJO) && (hm->dcho->color == NEGRO)) { hm->color = ROJO; hm->izdo->color = NEGRO; rotar_dcha(hm); } else if ((n == n->padre->dcho) && (hm->color == NEGRO) && (hm->dcho->color == ROJO) && (hm->izdo->color == NEGRO)) { hm->color = ROJO; hm->dcho->color = NEGRO eliminar_caso6(n); }

Caso 6: S es negro, su hijo derecho es rojo, y N es el hijo izquierdo de P, su padre. En este caso rotamos a la izquierda P, as que S se convierte en el padre de P y ste en el hijo derecho de S. Entonces intercambiamos los colores de P y S, y ponemos el hijo derecho de S en negro. El subrbol an tiene el mismo color que su raz, as que las propiedades 4 (los hijos de todo nodo rojo son negros) y 5 (todos los caminos desde cualquier nodo a sus nodos hoja contienen el mismo nmero de nodos negros) se verifican. Sin embargo, N tiene ahora un antecesor negro mas: o bien P se ha convertido en negro, o bien era negro y S se ha aadido como un abuelo negro. De este modo, los caminos que pasan por N pasan por un nodo negro mas. Mientras tanto, si un camino no pasa por N, entonces hay dos posibilidades:

ste pasa a travs del nuevo hermano de N. Entonces, ste debe pasar por S y P, al igual que antes, y tienen slo que intercambiar los colores. As los caminos contienen el mismo nmero de nodos negros. ste pasa por el nuevo to de N, el hijo derecho de S. ste anteriormente pasaba por S, su padre y su hijo derecho, pero ahora slo pasa por S, el cual ha tomado el color de su anterior padre, y por su hijo derecho, el cual ha cambiado de rojo a negro. El efecto final es que este camino va por el mismo nmero de nodos negros.

De cualquier forma, el nmero de nodos negros en dichos caminos no cambia. De este modo, hemos restablecido las propiedades 4 (los hijos de todo nodo rojo son negros) y 5 (todos los caminos desde cualquier nodo a sus nodos hoja contienen el mismo nmero de nodos negros). El nodo blanco en diagrama puede ser rojo o negro, pero debe tener el mismo color tanto antes como despus de la transformacin. Adjuntamos el ltimo algoritmo:
void eliminar_caso6(struct node *n) { struct node *hm = hermano_menor(n); hm->color = n->padre->color; n->padre->color = NEGRO; if (n == n->padre->izdo) { /* * Aqu, hm->dcho->color == ROJO. */ hm->dcho->color = NEGRO; rotar_izda(n->padre); } else { /* * Aqu, hm->izdo->color == ROJO. */ hm->izdo->color = NEGRO; rotar_dcha(n->padre); } }

De nuevo, todas las llamadas de la funcin usan recursin de cola as que el algoritmo realiza sus operaciones sobre el propio rbol. Adems, las llamadas no recursivas se harn despus de una rotacin, luego se harn un nmero de rotaciones (ms de 3) que ser constante.

Demostracin de cotas

Un rbol rojo-negro que contiene n nodos internos tiene una altura de O(log(n)). Hagamos los siguientes apuntes sobre notacin:

H(v) = altura del rbol cuya raz es el nodo v. bh(v) = nmero de nodos negros (sin contar v si es negro) desde v hasta cualquier hoja del subrbol (llamado altura-negra).

Lema: Un subrbol enraizado al nodo v tiene al menos 2bh(v) 1 nodos internos. Demostracin del lema (por induccin sobre la altura): Caso base: h(v)=0 Si v tiene altura cero entonces debe ser rbol vaco, por tanto bh(v)=0. Luego: 2bh(''v'') 1 = 20 1 = 1 1 = 0 Hiptesis de Induccin: si v es tal que h(v) = k y contiene 2bh(v) 1 nodos internos, veamos que esto implica que v' tal que h(v') = k+1 contiene 2bh(v') 1 nodos internos. Si v' tiene h(v') > 0 entonces es un nodo interno. Como ste tiene dos hijos que tienen altura-negra, o bh(v') o bh(v')-1 (dependiendo si es rojo o negro). Por la hiptesis de induccin cada hijo tiene al menos 2bh(v') 1 1 nodos internos, as que v' tiene :2bh(v') 1 1 + 2bh(v') 1 1 + 1 = 2bh(v') 1 nodos internos.

Usando este lema podemos mostrar que la altura del rbol es algortmica. Puesto que al menos la mitad de los nodos en cualquier camino desde la raz hasta una hoja negra (propiedad 4 de un rbol rojo-negro), la alturanegra de la raz es al menos h(raz)/2. Por el lema tenemos que:

Por tanto, la altura de la raz es O(log(n)).

Complejidad
En el cdigo del rbol hay un bucle donde la raz de la propiedad rojo-negro que hemos querido devolver a su lugar, x, puede ascender por el rbol un nivel en cada iteracin Como la altura original del rbol es O(log n), hay O(log n) iteraciones. As que en general la insercin tiene una complejidad de O(log n).

rbol AA
En informtica un rbol AA es un tipo de rbol binario de bsqueda auto-balanceable utilizado para almacenar y recuperar informacin ordenada de manera eficiente. Los rboles AA reciben el nombre de su inventor, Arne Andersson. Los rboles AA son una variacin del rbol rojo-negro, que a su vez es una mejora del rbol binario de bsqueda. A diferencia de los rboles rojo-negro, los nodos rojos en un rbol AA slo pueden aadirse como un hijo derecho. En otras palabras, ningn nodo rojo puede ser un hijo izquierdo. De esta manera se simula un rbol 2-3 en lugar de un rbol 2-3-4, lo que simplifica las operaciones de mantenimiento. Los algoritmos de mantenimiento para un rbol rojo-negro necesitan considerar siete diferentes formas para balancear adecuadamente el rbol:

En un rbol AA, al cumplirse el estricto requisito de que slo los enlaces derechos pueden ser rojos, slo es necesario considerar dos formas:

Contenido

1 Rotaciones de balanceo 2 Insercin 3 Borrado 4 Rendimiento 5 Vase tambin 6 Referencias 7 Enlaces externos

Rotaciones de balanceo
En general, los rboles AA se implementan con la idea de un nivel en lugar de la de un color, a diferencia de los rboles rojo-negro. Cada nodo tiene un campo nivel y se deben cumplir las siguientes condiciones para que el rbol sea vlido: 1. 2. 3. 4. 5. El nivel de un nodo hoja es uno. El nivel de un hijo izquierdo es estrictamente menor que el de su padre. El nivel de un hijo derecho es menor o igual que el de su padre. El nivel de un nieto derecho es estrictamente menor que el de su abuelo. Cada nodo de nivel mayor que uno debe tener dos hijos.

Slo se necesitan dos operaciones para mantener el equilibrio en un rbol AA. Estas operaciones se llaman torsin (skew) y divisin (split). La torsin es una rotacin derecha que se realiza cuando una insercin o un borrado genera un enlace horizontal izquierdo, puede pensarse como un enlace rojo izquierdo en el contexto del rbol rojo-negro. La divisin es una rotacin izquierda condicional que tiene lugar cuando una insercin o un borrado crea dos enlaces horizontales derechos, lo que de nuevo se corresponde con dos enlaces rojos consecutivos en el contexto de los rboles rojo-negro.
function skew is input: T, a node representing an AA tree that needs to be rebalanced. output: Another node representing the rebalanced AA tree. if nil(T) then return Nil else if level(left(T)) == level(T) then Swap the pointers of horizontal left links. L = left(T) left(T) := right(L) right(L) := T return L else return T end if

end function

Torsin:
function split is input: T, a node representing an AA tree that needs to be rebalanced. output: Another node representing the rebalanced AA tree. if nil(T) then return Nil else if level(T) == level(right(right(T))) then We have two horizontal right links. Take the middle node, elevate it, and return it. R = right(T) right(T) := left(R) left(R) := T level(R) := level(R) + 1 return R else return T end if end function

Divisin:

Insercin
La insercin comienza con la bsqueda normal en un rbol binario y su procedimiento de insercin. Despus, a medida que se desenrolla la pila de llamadas, es fcil comprobar la validez del rbol y realizar las rotaciones que se precisen. Si aparece un enlace horizontal izquierdo, se realiza una torsin, y si aparecen dos enlaces horizontales derechos, se realiza una divisin, posiblemente incrementando el nivel del nuevo nodo raz del subrbol correspondiente. Observe que el cdigo de muestra realiza un incremento de nivel(T). Lo que hace necesario continuar comprobando la validez del rbol a medida que las modificaciones suben desde las hojas.
function insert is input: X, the value to be inserted, and T, the root of the tree to insert it into. output: A balanced version T including X. Do the normal binary tree insertion procedure. Set the result of the recursive call to the correct child in case a new node was created or the root of the subtree changes. if nil(T) then Create a new leaf node with X. return node(X, 1, Nil, Nil) else if X < value(T) then left(T) := insert(X, left(T)) else if X > value(T) then right(T) := insert(X, right(T)) end if

Note that the case of X == value(T) is unspecified. As given, an insert will have no effect. The implementor may desire different behavior. Perform skew and then split. The conditionals that determine whether or not a rotation will occur or not are inside of the procedures, as given above. T := skew(T) T := split(T) return T end function

Borrado
Como en la mayora de rboles binarios balanceados, el borrado de un nodo interno puede convertirse en el borrado de un nodo hoja al intercambiar el nodo interno bien con su predecesor o sucesor ms prximo, dependiendo del que est en el rbol o de los deseos del implementador. Para recuperar un predecesor smplemente se debe seguir un enlace izquierdo y despus todos los enlaces derechos restantes. De forma similar, el sucesor se puede encontrar al ir una vez a la derecha una vez y a la izquierda hasta que se encuentre un puntero nulo. Dada la propiedad de los rboles AA de que todos los nodos de un nivel superior a uno tienen dos hijos, el nodo sucesor o predecesor tendr nivel 1, haciendo que su eliminado sea trivial. Para re-equilibrar un rbol existen diferentes aproximaciones. La que describi Andersson en su publicacin original es la ms simple, y se describir aqu, aunque implementaciones reales pueden optar por un enfoque ms optimizado. Tras un borrado, el primer paso para mantener la validez es reducir el nivel de todos los nodos cuyos hijos estn dos niveles por debajo de ellos, o a los que les faltan hijos. Despus, todo el nivel debe ser torsionado y dividido. Esta aproximacin se ha visto favorecida por el hecho de que se basa en tres pasos independientes y fciles de entender: 1. Decrementar el nivel, si es necesario. 2. Torsionar el nivel. 3. Dividir el nivel. Sin embargo, debemos torsionar y dividir todo el nivel en lugar de un solo nodo lo que complica nuestro cdigo.
function delete is input: X, the value to delete, and T, the root of the tree from which it should be deleted. output: T, balanced, without the value X. if X > value(T) then right(T) := delete(X, right(T)) else if X < value(T) then left(T) := delete(X, left(T)) else If we're a leaf, easy, otherwise reduce to leaf case. if leaf(T) then return Nil else if nil(left(T)) then L := successor(T) right(T) := delete(L, right(T)) value(T) := L else L := predecessor(T) left(T) := delete(L, left(T)) value(T) := L end if end if Rebalance the tree. Decrease the level of all nodes in this level if necessary, and then skew and split all nodes in the new level. T := decrease_level(T)

T := skew(T) right(T) := skew(right(T)) right(right(T)) := skew(right(right(T))) T := split(T) right(T) := split(right(T)) return T end function function decrease_level is input: T, a tree for which we want to remove links that skip levels. output: T with its level decreased. should_be = min(level(left(T)), level(right(T))) + 1 if should_be < level(T) then level(T) := should_be if should_be < level(right(T)) then level(right(T)) := should_be end if end if return T end function

Un buen ejemplo de borrado por este algoritmo est presente en la publicacin de Andersson.

Rendimiento
El rendimiento de un rbol AA es equivalente al de un rbol rojo-negro. Un rbol AA realiza ms rotaciones que un rbol red-black, pero la mayor sencillez de sus algoritmos tiende a hacerlos ms rpidos, y estos factores se compensan resultando en un rendimiento similar. Un rbol rojo-negro es ms constante en su rendimiento que un rbol AA, pero un rbol AA tiende a ser ms llano lo que produce unos tiempos de bsqueda ligeramente ms pequeos.[cita requerida]

rbol multicamino
Los rboles multicamino o rboles multirrama son estructuras de datos de tipo rbol usadas en computacin.

Contenido

1 Definicin 2 Ventajas e inconvenientes o 2.1 Nota 3 Vase tambin

Definicin
Un rbol multicamino posee un grado g mayor a dos, donde cada nodo de informacin del rbol tiene un mximo de g hijos.

Sea un rbol de m-caminos A, es un rbol m-caminos si y solo si:


A est vaco Cada nodo de A muestra la siguiente estructura:


[nClaves,Enlace0,Clave1,...,ClavenClaves,EnlacenClaves]

nClaves es el nmero de valores de clave de un nodo, pudiendo ser: 0 <= nClaves <= g-1 Enlacei, son los enlaces a los subrboles de A, pudiendo ser: 0 <= i <= nClaves

Clavei, son los valores de clave, pudiendo ser: 1 <= i <= nClaves
Clavei < Clavei+1

Cada valor de clave en el subrbol Enlacei es menor que el valor de Clavei+1 Los subrboles Enlacei, donde 0 <= i <= nClaves, son tambin rboles m-caminos.

Existen muchas aplicaciones en las que el volumen de la informacin es tal, que los datos no caben en la memoria principal y es necesario almacenarlos, organizados en archivos, en dispositivos de almacenaminento secundario. Esta organizacin de archivos debe ser suficientemente adecuada como para recuperar los datos del mismo en forma eficiente.

Ventajas e inconvenientes
La principal ventaja de este tipo de rboles consiste en que existen ms nodos en un mismo nivel que en los rboles binarios con lo que se consigue que, si el rbol es de bsqueda, los accesos a los nodos sean ms rpidos. El inconveniente ms importante que tienen es la mayor ocupacin de memoria, pudiendo ocurrir que en ocasiones la mayora de los nodos no tengan descendientes o al menos no todos los que podran tener desaprovechndose por tanto gran cantidad de memoria. Cuando esto ocurre lo ms frecuente es transformar el rbol multicamino en su binario de bsqueda equivalente. Nota Un tipo especial de rboles multicamino utilizado para solucionar el problema de la ocupacin de memoria son los rboles B o rboles Bayer.

5.1.1 Procedimiento 5.2 Insercin 5.3 Eliminacin 5.3.1 Eliminacin en un nodo hoja 5.3.2 Eliminacin en un nodo interno o 5.4 Rebalanceo despus de la eliminacin o 5.5 Construccin Inicial 6 Notas o 6.1 Multi-modo:combinar y dividir o 6.2 Relacin entre U y L o 6.3 Acceso concurrente 7 Vase tambin 8 Enlaces externos
o o

Definicin
La idea tras los rboles-B es que los nodos internos deben tener un nmero variable de nodos hijo dentro de un rango predefinido. Cuando se inserta o se elimina un dato de la estructura, la cantidad de nodos hijo vara dentro de un nodo. Para que siga mantenindose el nmero de nodos dentro del rango predefinido, los nodos internos se juntan o se parten. Dado que se permite un rango variable de nodos hijo, los rboles-B no necesitan rebalancearse tan frecuentemente como los rboles binarios de bsqueda auto-balanceables, pero por otro lado pueden desperdiciar memoria, porque los nodos no permanecen totalmente ocupados. Los lmites superior e

inferior en el nmero de nodos hijo son definidos para cada implementacin en particular. Por ejemplo, en un rbol-B 2-3 (A menudo simplemente llamado rbol 2-3 ), cada nodo slo puede tener 2 3 nodos hijo. Un rbol-B se mantiene balanceado porque requiere que todos los nodos hoja se encuentren a la misma altura. Los rboles B tienen ventajas sustanciales sobre otras implementaciones cuando el tiempo de acceso a los nodos excede al tiempo de acceso entre nodos. Este caso se da usualmente cuando los nodos se encuentran en dispositivos de almacenamiento secundario como los discos rgidos. Al maximizar el nmero de nodos hijo de cada nodo interno, la altura del rbol decrece, las operaciones para balancearlo se reducen, y aumenta la eficiencia. Usualmente este valor se coloca de forma tal que cada nodo ocupe un bloque de disco, o un tamao anlogo en el dispositivo. Mientras que los rboles B 2-3 pueden ser tiles en la memoria principal, y adems ms fciles de explicar, si el tamao de los nodos se ajustan para caber en un bloque de disco, el resultado puede ser un rbol B 129-513. Los creadores del rbol B, Rudolf Bayer y Ed McCreight, no han explicado el significado de la letra B de su nombre. Se cree que la B es de balanceado, dado que todos los nodos hoja se mantienen al mismo nivel en el rbol. La B tambin puede referirse a Bayer, o a Boeing, porque sus creadores trabajaban en el Boeing Scientific Research Labs en ese entonces.

Definicin tcnica
B-rbol es un rbol de bsqueda que puede estar vaco o aquel cuyos nodos pueden tener varios hijos, existiendo una relacin de orden entre ellos, tal como muestra el dibujo. Un rbol-B de orden M (el mximo nmero de hijos que puede tener cada nodo) es un rbol que satisface las siguientes propiedades: 1. 2. 3. 4. 5. 6. Cada nodo tiene como mximo M hijos. Cada nodo (excepto raz y hojas) tiene como mnimo M/2 hijos. La raz tiene al menos 2 hijos si no es un nodo hoja. Todos los nodos hoja aparecen al mismo nivel. Un nodo no hoja con k hijos contiene k-1 elementos almacenados. Los hijos que cuelgan de la raz (r1, , rm) tienen que cumplir ciertas condiciones: 1. El primero tiene valor menor que r1. 2. El segundo tiene valor mayor que r1 y menor que r2, etc. 3. El ltimo hijo tiene valor mayor que rm.

Altura: El mejor y el peor caso


En el mejor de los casos,la altura de un rbol-B es: logMn En el peor de los casos,la altura de un rbol-B es:

Donde M es el nmero mximo de hijos que puede tener un nodo.

Estructura de los nodos


Cada elemento de un nodo interno acta como un valor separador, que lo divide en subrboles. Por ejemplo, si un nodo interno tiene tres nodos hijo, debe tener dos valores separadores o elementos a1 y a2. Todos los valores

del subrbol izquierdo deben ser menores a a1, todos los valores del subrbol del centro deben estar entre a1 y a2, y todos los valores del subrbol derecho deben ser mayores a a2. Los nodos internos de un rbol B, es decir los nodos que no son hoja, usualmente se representan como un conjunto ordenado de elementos y punteros a los hijos. Cada nodo interno contiene un mximo de U hijos y, con excepcin del nodo raz, un mnimo de L hijos. Para todos los nodos internos exceptuando la raz, el nmero de elementos es uno menos que el nmero de punteros a nodos. El nmero de elementos se encuentra entre L-1 y U-1. El nmero U debe ser 2L o 2L-1, es decir, cada nodo interno est por lo menos a medio llenar. Esta relacin entre U y L implica que dos nodos que estn a medio llenar pueden juntarse para formar un nodo legal, y un nodo lleno puede dividirse en dos nodos legales (si es que hay lugar para subir un elemento al nodo padre). Estas propiedades hacen posible que el rbol B se ajuste para preservar sus propiedades ante la insercin y eliminacin de elementos. Los nodos hoja tienen la misma restriccin sobre el nmero de elementos, pero no tienen hijos, y por tanto carecen de punteros. El nodo raz tiene lmite superior de nmero de hijos, pero no tiene lmite inferior. Por ejemplo, si hubiera menos de L-1 elementos en todo el rbol, la raz sera el nico nodo del rbol, y no tendra hijos. Un rbol B de altura n+1 puede contener U veces por elementos ms que un rbol B de profundidad n, pero el costo en la bsqueda, insercin y eliminacin crece con la altura del rbol. Como todo rbol balanceado, el crecimiento del costo es ms lento que el del nmero de elementos. Algunos rboles balanceados guardan valores slo en los nodos hoja, y por lo tanto sus nodos internos y nodos hoja son de diferente tipo. Los rboles B guardan valores en cada nodo, y pueden utilizar la misma estructura para todos los nodos. Sin embargo, como los nodos hoja no tienen hijos, una estructura especial para stos mejora el funcionamiento. class nodo rbol B en c++
#include <iostream.h> #include <stdlib.h> #define TAMANO 1000 struct stclave { int valor; long registro; }; class bnodo { public: bnodo (int nClaves); // Constructor ~bnodo (); // Destructor private: int clavesUsadas; stclave *clave; bnodo **puntero; bnodo *padre; friend class btree; };

Algoritmos
Bsqueda La bsqueda es similar a la de los rboles binarios. Se empieza en la raz, y se recorre el rbol hacia abajo, escogiendo el sub-nodo de acuerdo a la posicin relativa del valor buscado respecto a los valores de cada nodo. Tpicamente se utiliza la bsqueda binaria para determinar esta posicin relativa.

Procedimiento

ejemplo2 insercin en rbol B. 1. Situarse en el nodo raz. 2. (*) Comprobar si contiene la clave a buscar. 1. Encontrada fin de procedimiento. 2. No encontrada: 1. Si es hoja no existe la clave. 2. En otro caso el nodo actual es el hijo que corresponde: 1. La clave a buscar k < k1: hijo izquierdo. 2. La clave a buscar k > ki y k < ki+1 hijo isimo. 3. Volver a paso 2(*). Insercin Todas las inserciones se hacen en los nodos hoja. 1. Realizando una bsqueda en el rbol, se halla el nodo hoja en el cual debera ubicarse el nuevo elemento. 2. Si el nodo hoja tiene menos elementos que el mximo nmero de elementos legales, entonces hay lugar para uno ms. Inserte el nuevo elemento en el nodo, respetando el orden de los elementos. 3. De otra forma, el nodo debe ser dividido en dos nodos. La divisin se realiza de la siguiente manera: 1. Se escoge el valor medio entre los elementos del nodo y el nuevo elemento.

2. Los valores menores que el valor medio se colocan en el nuevo nodo izquierdo, y los valores mayores que el valor medio se colocan en el nuevo nodo derecho; el valor medio acta como valor separador. 3. El valor separador se debe colocar en el nodo padre, lo que puede provocar que el padre sea dividido en dos, y as sucesivamente. Si las divisiones de nodos suben hasta la raz, se crea una nueva raz con un nico elemento como valor separador, y dos hijos. Es por esto por lo que la cota inferior del tamao de los nodos no se aplica a la raz. El mximo nmero de elementos por nodo es U-1. As que debe ser posible dividir el nmero mximo de elementos U-1 en dos nodos legales. Si este nmero fuera impar, entonces U=2L, y cada uno de los nuevos nodos tendran (U-2)/2 = L-1 elementos, y por lo tanto seran nodos legales. Si U-1 fuera par, U=2L-1, as que habra 2L-2 elementos en el nodo. La mitad de este nmero es L-1, que es el nmero mnimo de elementos permitidos por nodo. Un algoritmo mejorado admite una sola pasada por el rbol desde la raz,hasta el nodo donde la insercin tenga lugar,dividiendo todos los nodos que estn llenos encontrados a su paso.Esto evita la necesidad de volver a cargar en memoria los nodos padres,que pueden ser caros si los nodos se encuentran en una memoria secundaria.Sin embargo,para usar este algoritmo mejorado, debemos ser capaces de enviar un elemento al nodo padre y dividir el resto U-2 elementos en 2 nodos legales,sin aadir un nuevo elemento.Esto requiere que U=2L en lugar de U=L-1,lo que explica por qu algunos libros de texto imponen este requisito en la definicin de rboles-B. Eliminacin La eliminacin de un elemento es directa si no se requiere correccin para garantizar sus propiedades. Hay dos estrategias populares para eliminar un nodo de un rbol B.

localizar y eliminar el elemento, y luego corregir, o hacer una nica pasada de arriba a abajo por el rbol, pero cada vez que se visita un nodo, reestructurar el rbol para que cuando se encuentre el elemento a ser borrado, pueda eliminarse sin necesidad de continuar reestructurando

Se pueden dar dos problemas al eliminar elementos: primero, el elemento puede ser un separador de un nodo interno. Segundo, puede suceder que al borrar el elemento, el nmero de elementos del nodo quede debajo de la cota mnima. Estos problemas se tratan a continuacin en orden. Eliminacin en un nodo hoja

Busque el valor a eliminar. Si el valor se encuentra en un nodo hoja, se elimina directamente la clave, posiblemente dejndolo con muy pocos elementos; por lo que se requerirn cambios adicionales en el rbol.

eliminar clave 20 de un nodo interno. Eliminacin en un nodo interno Cada elemento de un nodo interno acta como valor separador para dos subrboles, y cuando ese elemento es eliminado, pueden suceder dos casos. En el primero, tanto el hijo izquierdo como el derecho tienen el nmero mnimo de elementos, L-1. Pueden entonces fundirse en un nico nodo con 2L-2 elementos, que es un nmero que no excede U-1 y por lo tanto es un nodo legal. A menos que se sepa que este rbol B en particular no tiene datos duplicados, tambin se debe eliminar el elemento en cuestin (recursivamente) del nuevo nodillo. En el segundo caso, uno de los dos nodos hijos tienen un nmero de elementos mayor que el mnimo. Entonces se debe hallar un nuevo separador para estos dos subrboles. Note que el mayor elemento del rbol izquierdo es el mayor elemento que es menor que el separador. De la misma forma, el menor elemento del subrbol derecho es el menor elemento que es mayor que el separador. Ambos elementos se encuentran en nodos hoja, y cualquiera de los dos puede ser el nuevo separador.

Si el valor se encuentra en un nodo interno, escoja un nuevo separador (puede ser el mayor elemento del subrbol izquierdo o el menor elemento del subrbol derecho), elimnelo del nodo hoja en que se encuentra, y reemplace el elemento a eliminar por el nuevo separador. Como se ha eliminado un elemento de un nodo hoja, se debe tratar este caso de manera equivalente.

Rebalanceo despus de la eliminacin Si al eliminar un elemento de un nodo hoja el nodo se ha quedado con menos elementos que el mnimo permitido, algunos elementos se deben redistribuir. En algunos casos el cambio lleva la deficiencia al nodo padre, y la redistribucin se debe aplicar iterativamente hacia arriba del rbol, quiz incluso hasta a la raz. Dado que la cota mnima en el nmero de elementos no se aplica a la raz, el problema desaparece cuando llega a sta. La estrategia consiste en hallar un hermano para el nodo deficiente que tenga ms del mnimo nmero de elementos y redistribuir los elementos entre los hermanos para que todos tengan ms del mnimo. Esto tambin cambia los separadores del nodo padre.

Si el nodo hermano inmediato de la derecha del nodo deficiente tiene ms del mnimo nmero de elementos, escoja el valor medio entre el separador y ambos hermanos como nuevo separador y colquelo en el padre. Redistribuya los elementos restantes en los nodos hijo derecho e izquierdo. Redistribuya los subrboles de los dos nodos . Los subrboles son trasplantados por completo, y no se alteran si se mueven a un otro nodo padre, y esto puede hacerse mientras los elementos se redistribuyen. Si el nodo hermano inmediato de la derecha del nodo deficiente tiene el mnimo nmero de elementos, examine el nodo hermano inmediato de la izquierda. Si los dos nodos hermanos inmediatos tienen el mnimo nmero de elementos, cree un nuevo nodo con todos los elementos del nodo deficiente, todos los elementos de uno de sus hermanos, colocando el separador del padre entre los elementos de los dos nodos hermanos fundidos. Elimine el separador del padre, y reemplace los dos hijos que separaba por el nuevo nodo fundido. Si esa accin deja al nmero de elementos del padre por debajo del mnimo, repita estos pasos en el nuevo nodo deficiente, a menos que sea la raz, ya que no tiene cota mnima en el nmero de elementos.

Construccin Inicial En aplicaciones, es frecuentemente til construir un rbol-B para representar un gran nmero de datos existentes y despus actualizarlo de forma creciente usando operaciones Standard de los rboles-B. En este caso, el modo ms eficiente para construir el rbol-B inicial no sera insertar todos los elementos en el conjunto inicial sucesivamente, si no construir el conjunto inicial de nodos hoja directamente desde la entrada, y despus construir los nodos internos a partir de este conjunto. Inicialmente, todas las hojas excepto la ltima tienen un elemento ms, el cual ser utilizado para construir los nodos internos. Por ejemplo, si los nodos hoja tienen un tamao mximo de 4 y el conjunto inicial es de enteros desde el 1 al 24, tenemos que construir inicialmente 5 nodos hoja conteniendo 5 valores cada uno (excepto el ltimo que contiene 4):

1 2 3 4 5

6 7 8 9 10

11 12 13 14 15

16 17 18 19 20

21 22 23 24

Construiremos el siguiente nivel hacia arriba desde las hojas tomando el ltimo elemento de cada hoja excepto el ltimo. De nuevo, cada nodo excepto el ltimo contendr un valor ms. En el ejemplo, es supuesto que los nodos internos contienen como mucho 2 valores (por lo que pueden tener 3 hijos). Luego el siguiente nivel de nodos internos nos quedara de la siguiente manera:

5 10 15

20

1 2 3 4

6 7 8 9

11 12 13 14

16 17 18 19

21 22 23 24

Este proceso se continuar hasta que alcancemos un nivel con un solo nodo y no esta sobrecargado. En nuestro ejemplo solo nos quedara el nivel de la raz:

15

5 10

20

1 2 3 4

6 7 8 9

11 12 13 14

16 17 18 19

21 22 23 24

Notas
Cada nodo tendr siempre entre L y U hijos incluidos con una excepcin: el nodo raz debe tener entre 2 y U hijos. En otras palabras, la raz est exenta de la restriccin del lmite inferior. Esto permite al rbol sostener un pequeo nmero de elementos. Un nodo raz con un solo hijo no tendra sentido, ya que podramos aadrselo a la raz. Un nodo raz sin hijos es tambin innecesario, ya que un rbol sin hijos se suele representar sin raz. Multi-modo:combinar y dividir Es posible modificar el algoritmo anterior, cuando tratamos de encontrar ms elementos para un nodo al que le faltan, examinamos a los hermanos, y si alguno tiene ms del valor mnimo de nmeros, reordenamos los valores de los hermanos de un extremo a otro para rellenar al mnimo el nodo al que le faltan. De la misma manera, cuando un nodo se divide, los elementos extra pueden ser movidos cerca, por ejemplo a hermanos menos poblados; o la divisin puede dar lugar a un nmero de hermanos, redistribuyendo los elementos entre ellos en lugar de dividir un nodo. En la prctica, el uso ms comn de los rboles-B implica mantener los nodos una memoria secundaria, donde ser lento acceder a un nodo que no haya sido usado con anterioridad. Utilizando solo divisiones y combinaciones, disminuimos el nmero de nodos que se necesitan para la mayora de situaciones comunes, pero podran ser tiles en otras. Relacin entre U y L Es casi universal el dividir nodos eligiendo un elemento medio y creando dos nuevos nodos. Esto limita la relacin entre L y U. Si intentamos insertar un elemento dentro de un nodo con U elementos, esto conlleva una redistribucin de U elementos. Uno de estos, el intermedio, ser trasladado al nodo padre, y los restantes sern divididos equitativamente, en la medida de lo posible, entre los dos nuevos nodos. Por ejemplo, en un rbol-B 2-3, aadiendo un elemento a un nodo que ya contiene 3 hijos, y por consiguiente 2 valores separadores (padres), da lugar a 3 valores (los dos separadores y el nuevo valor). El valor medio se convierte en el nuevo separador (padre), y los otros valores se hacen independientes y con 2 hijos. Por lo general, si U es impar, cada uno de los nuevos nodos tienen (U+2)/2 hijos. Si U es par, unos tiene U/2 hijos y el otro U/2+1. Si un nodo est completo y se divide exactamente en 2 nodos, L debe tener un tamao permitido, lo suficiente pequeo, una vez q el nodo ha sido divido. Tambin es posible dividir nodos completos en ms de dos nodos nuevos. Eligiendo dividir un nodo en ms de 2 nodos nuevos requerir un valor ms pequeo para L para el mismo valor de U. Como L se hace ms pequeo, esto permite que haya ms espacio sin usar en los nodos. Esto disminuir la frecuencia de divisin de nodos, pero de la misma manera aumentar la cantidad de memoria que se necesita para almacenar el mismo nmero de valores, y el nmero de nodos que tienen que ser examinados para una operacin particular. Acceso concurrente Lehman y Yao nos mostraron que uniendo los bloques de rboles en cada nivel, con un puntero al siguiente nivel, en una estructura de rbol, donde los permisos de lectura de los bloques del rbol se pueden evitar, por que el rbol desciende desde la raz hasta las hojas por bsqueda e insercin. Los permisos de escritura solo se requieren cuando un bloque del rbol es modificado. Minimizando los permisos a un nodo colgante simple, solo durante su modificacin ayuda a maximizar el acceso concurrente por mltiples usuarios. Un dato a ser considerado en las bases de datos, por ejemplo y/o otro rbol basado en ISAM (Mtodos Indexados de Acceso Secuencial) mtodos de almacenamiento.

rbol-B+

Un rbol B+ simple (una variacin del rbol B) que enlaza los elementos 1 al 7 a valores de datos d1-d7. Note la lista enlazada (en rojo) que permite el recorrido de los elementos en orden. En informtica, un rbol-B es un tipo de estructura de datos de rboles. Representa una coleccin de datos ordenados de manera que se permite una insercin y borrado eficientes de elementos. Es un ndice, multinivel, dinmico, con un lmite mximo y mnimo en el nmero de claves por nodo. Un rbol-B+ es una variacin de un rbol-B. En un rbol-B+, en contraste respecto un rbol-B, toda la informacin se guarda en las hojas. Los nodos internos slo contienen claves y punteros. Todas las hojas se encuentran en el mismo, ms bajo nivel. Los nodos hoja se encuentran unidos entre s como una lista enlazada para permitir bsqueda secuencial. El nmero mximo de claves en un registro es llamado el orden del rbol-B+. El mnimo nmero de claves por registro es la mitad del mximo nmero de claves. Por ejemplo, si el orden de un rbol-B+ es n, cada nodo (exceptuando la raz) debe tener entre n/2 y n claves. El nmero de claves que pueden ser indexadas usando un rbol-B+ est en funcin del orden del rbol y su altura. Para un rbol-B+ de orden n, con una altura h:

Nmero mximo de claves es: nh Nmero mnimo de claves es: 2(n / 2)h 1

El rbol-B+ fue descrito por primera vez en el documento "Rudolf Bayer, Edward M. McCreight: Organization and Maintenance of Large Ordered Indexes. Acta Informtica 1: 173-189 (1972)".

rbol-B*
Un rbol-B* es una estructura de datos de rbol, una variante de rbol-B utilizado en los sistemas de ficheros HFS y Reiser4, que requiere que los nodos no raz estn por lo menos a 2/3 de ocupacin en lugar de 1/2. Para mantener esto los nodos, en lugar de generar inmediatamente un nodo cuando se llenan, comparten sus claves con el nodo adyacente. Cuando ambos estn llenos, entonces los dos nodos se transforman en tres. Tambin requiere que la clave ms a la izquierda no sea usada nunca. No se debe confundir un rbol-B* con un rbol-B+, en el que los nodos hoja del rbol estn conectados entre s a travs de una lista enlazada, aumentando el coste de insercin para mejorar la eficiencia en la bsqueda.

Transformacin de un Arbol Gral. En un Arbol Binario.


En esta seccin estableceremos los mecanismos necesarios para convertir un rbol general en un rbol binario. Para esto, debemos seguir los pasos que se describen a continuacin: 1. Enlazar los hijos de cada nodo en forma horizontal (los hermanos). 2. Enlazar en forma vertical el nodo padre con el nodo hijo que se encuentra ms a la izquierda. Adems, debe eliminarse el vnculo de ese padre con el resto de sus hijos. 3. Rotar el diagrama resultante aproximadamente 45 grados hacia la izquierda, y as se obtendr el rbol binario correspondiente.

Vous aimerez peut-être aussi