Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 119

Contenido

DSA - Inicio
DSA - Descripción general
DSA - Configuración del entorno
Algoritmo
DSA - Conceptos básicos de algoritmos
DSA - Análisis asintótico
DSA - Algoritmos codiciosos
DSA - Divide y vencerás
DSA - Programación dinámica
Estructuras de datos
DSA - Conceptos básicos de estructura de datos
DSA - Estructura de datos de matriz
Listas vinculadas
DSA - Conceptos básicos de la lista vinculada
DSA - Lista doblemente vinculada
DSA - Lista circular vinculada
Pila y cola
DSA - Pila
DSA - Análisis de expresiones
DSA - Cola
Técnicas de búsqueda
DSA - Búsqueda lineal
DSA - Búsqueda binaria
DSA - Búsqueda de interpolación
DSA - Tabla hash
Técnicas de clasificación
DSA - Algoritmos de clasificación
DSA - Clasificación de burbujas
DSA - Clasificación de inserción
DSA - Ordenar selección
DSA - Ordenar fusión
DSA - Clasificación de shell
DSA - Clasificación rápida
Estructura de datos del gráfico
DSA - Estructura de datos del gráfico
DSA - Profundidad del primer recorrido
DSA: primer recorrido transversal
Estructura de datos de árbol
DSA - Estructura de datos de árbol
DSA - Recorrido del árbol
DSA - Árbol de búsqueda binaria
DSA - Árbol AVL
DSA - Árbol de expansión
DSA - Montón
Recursividad
DSA - Conceptos básicos de recursividad
DSA - Torre de Hanoi
DSA - Serie Fibonacci
Estructuras de datos y algoritmos:
descripción general
La Estructura de datos es una forma sistemática de organizar los datos para
usarlos de manera eficiente. Los siguientes términos son los términos básicos
de una estructura de datos.
 Interfaz : cada estructura de datos tiene una interfaz. La interfaz representa el
conjunto de operaciones que admite una estructura de datos. Una interfaz solo
proporciona la lista de operaciones admitidas, el tipo de parámetros que pueden
aceptar y el tipo de retorno de estas operaciones.
 Implementación : la implementación proporciona la representación interna de una
estructura de datos. La implementación también proporciona la definición de los
algoritmos utilizados en las operaciones de la estructura de datos.

Características de una estructura de datos


 Corrección : la implementación de la estructura de datos debe implementar su
interfaz correctamente.
 Complejidad del tiempo: el tiempo de ejecución o el tiempo de ejecución de las
operaciones de la estructura de datos debe ser lo más pequeño posible.
 Complejidad espacial : el uso de la memoria de una operación de estructura de
datos debe ser lo menos posible.

Necesidad de estructura de datos


A medida que las aplicaciones se vuelven complejas y ricas en datos, hay tres
problemas comunes que enfrentan las aplicaciones hoy en día.
 Búsqueda de datos : considere un inventario de 1 millón (10 6 ) artículos de una
tienda. Si la aplicación busca un elemento, debe buscar un elemento en 1 millón
(10 6 ) elementos cada vez que ralentiza la búsqueda. A medida que crecen los
datos, la búsqueda será más lenta.
 Velocidad del procesador: la velocidad del procesador, aunque es muy alta,
cae limitada si los datos crecen a mil millones de registros.
 Múltiples solicitudes : dado que miles de usuarios pueden buscar datos
simultáneamente en un servidor web, incluso el servidor rápido falla al buscar los
datos.
Para resolver los problemas mencionados anteriormente, las estructuras de
datos vienen a rescatar. Los datos pueden organizarse en una estructura de
datos de tal manera que no sea necesario buscar todos los elementos, y los
datos requeridos pueden buscarse casi instantáneamente.

Casos de tiempo de ejecución


Hay tres casos que generalmente se utilizan para comparar el tiempo de
ejecución de varias estructuras de datos de manera relativa.
 Peor caso : este es el escenario en el que una operación de estructura de datos
particular lleva el tiempo máximo que puede tomar. Si el peor de los casos de una
operación es ƒ (n), entonces esta operación no tomará más de ƒ (n) tiempo donde
ƒ (n) representa la función de n.
 Caso promedio : este es el escenario que representa el tiempo promedio de
ejecución de una operación de una estructura de datos. Si una operación tarda ƒ
(n) tiempo en ejecución, entonces las operaciones m tomarán mƒ (n) tiempo.
 Mejor caso : este es el escenario que representa el menor tiempo de ejecución
posible de una operación de una estructura de datos. Si una operación tarda ƒ (n)
tiempo en ejecución, entonces la operación real puede tomar tiempo como el
número aleatorio que sería máximo como ƒ (n).

Terminología básica
 Datos : los datos son valores o un conjunto de valores.
 Elemento de datos: el elemento de datos se refiere a una sola unidad de valores.
 Elementos de grupo: los elementos de datos que se dividen en elementos
secundarios se denominan elementos de grupo.
 Elementos elementales: los elementos de datos que no se pueden dividir se
denominan elementos elementales.
 Atributo y entidad : una entidad es aquella que contiene ciertos atributos o
propiedades, a los que se les pueden asignar valores.
 Conjunto de entidades: las entidades de atributos similares forman un conjunto
de entidades.
 Campo : el campo es una unidad de información elemental única que representa
un atributo de una entidad.
 Registro : el registro es una colección de valores de campo de una entidad
determinada.
 Archivo : archivo es una colección de registros de las entidades en un conjunto de
entidades dado.

Estructuras de datos - Configuración del


entorno
Pruébelo Opción en línea
Realmente no necesita configurar su propio entorno para comenzar a
aprender el lenguaje de programación C. La razón es muy simple, ya hemos
configurado el entorno de programación C en línea, para que pueda compilar y
ejecutar todos los ejemplos disponibles en línea al mismo tiempo cuando está
haciendo su trabajo teórico. Esto le da confianza en lo que está leyendo y para
verificar el resultado con diferentes opciones. Siéntase libre de modificar
cualquier ejemplo y ejecutarlo en línea.
Pruebe el siguiente ejemplo utilizando la opción Probar disponible en la
esquina superior derecha del cuadro de código de muestra:
#include <stdio.h>
int main(){
/* My first program in C */
printf("Hello, World! \n");
return 0;
}
Para la mayoría de los ejemplos dados en este tutorial, encontrará la opción
Pruébelo, así que simplemente utilícelo y disfrute de su aprendizaje.

Configuración del entorno local


Si todavía está dispuesto a configurar su entorno para el lenguaje de
programación C, necesita las siguientes dos herramientas disponibles en su
computadora, (a) Editor de texto y (b) El compilador de C.
Editor de texto
Esto se usará para escribir su programa. Los ejemplos de algunos editores
incluyen el Bloc de notas de Windows, el comando Editar del sistema
operativo, Breve, Epsilon, EMACS y vim o vi.
El nombre y la versión del editor de texto pueden variar en diferentes sistemas
operativos. Por ejemplo, el Bloc de notas se usará en Windows y vim o vi se
pueden usar en Windows, así como en Linux o UNIX.
Los archivos que crea con su editor se denominan archivos fuente y contienen
el código fuente del programa. Los archivos de origen para programas en C
generalmente se nombran con la extensión " .c ".
Antes de comenzar su programación, asegúrese de tener un editor de texto y
tener suficiente experiencia para escribir un programa de computadora,
guardarlo en un archivo, compilarlo y finalmente ejecutarlo.
El compilador de C
El código fuente escrito en el archivo fuente es la fuente legible por humanos
para su programa. Debe "compilarse" para convertirse en lenguaje de
máquina para que su CPU pueda ejecutar el programa según las instrucciones
proporcionadas.
Este compilador de lenguaje de programación C se utilizará para compilar su
código fuente en un programa ejecutable final. Suponemos que tiene los
conocimientos básicos sobre un compilador de lenguaje de programación.
El compilador más utilizado y gratuito disponible es el compilador GNU C / C
++. De lo contrario, puede tener compiladores de HP o Solaris si tiene los
respectivos sistemas operativos (SO).
La siguiente sección lo guía sobre cómo instalar el compilador GNU C / C ++
en varios sistemas operativos. Estamos mencionando C / C ++ juntos porque
el compilador GNU GCC funciona para los lenguajes de programación C y C
++.

Instalación en UNIX / Linux


www.postparaprogramadores.com
/
Si está utilizando Linux o UNIX , verifique si GCC está instalado en su
sistema ingresando el siguiente comando desde la línea de comandos:
$ gcc -v
Si tiene un compilador GNU instalado en su máquina, debería imprimir un
mensaje como el siguiente:
Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix = /usr .......
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)
Si GCC no está instalado, tendrá que instalarlo usted mismo utilizando las
instrucciones detalladas disponibles en https://gcc.gnu.org/install/
Este tutorial se ha escrito basado en Linux y todos los ejemplos dados se han
compilado en el sistema Cent OS del sistema Linux.

Instalación en Mac OS
Si utiliza Mac OS X, la forma más fácil de obtener GCC es descargar el
entorno de desarrollo Xcode del sitio web de Apple y seguir las sencillas
instrucciones de instalación. Una vez que haya configurado Xcode, podrá
utilizar el compilador GNU para C / C ++.
Xcode está actualmente disponible en developer.apple.com/technologies/tools/

Instalación en Windows
Para instalar GCC en Windows, debe instalar MinGW. Para instalar MinGW,
vaya a la página de inicio de MinGW, www.mingw.org , y siga el enlace a la
página de descarga de MinGW. Descargue la última versión del programa de
instalación de MinGW, que debería llamarse MinGW- <versión> .exe.
Al instalar MinWG, como mínimo, debe instalar gcc-core, gcc-g ++, binutils y el
tiempo de ejecución de MinGW, pero es posible que desee instalar más.
Agregue el subdirectorio bin de su instalación MinGW a su variable
de entorno PATH , para que pueda especificar estas herramientas en la línea
de comandos por sus nombres simples.
Cuando se complete la instalación, podrá ejecutar gcc, g ++, ar, ranlib, dlltool y
varias otras herramientas GNU desde la línea de comandos de Windows.

Estructuras de datos - Conceptos básicos de


algoritmos
El algoritmo es un procedimiento paso a paso, que define un conjunto de
instrucciones que se ejecutarán en un cierto orden para obtener el resultado
deseado. Los algoritmos generalmente se crean independientemente de los
lenguajes subyacentes, es decir, se puede implementar un algoritmo en más
de un lenguaje de programación.
Desde el punto de vista de la estructura de datos, a continuación se presentan
algunas categorías importantes de algoritmos:
 Buscar : Algoritmo para buscar un elemento en una estructura de datos.
 Ordenar - Algoritmo para ordenar artículos en un orden determinado.
 Insertar : Algoritmo para insertar un elemento en una estructura de datos.
 Actualizar : Algoritmo para actualizar un elemento existente en una estructura de
datos.
 Eliminar : Algoritmo para eliminar un elemento existente de una estructura de
datos.

Síguenos en Instagram para que estés al tanto de los


nuevos libros de programación. Click aqui
Características de un algoritmo
No todos los procedimientos pueden llamarse algoritmo. Un algoritmo debe
tener las siguientes características:
 No ambiguo : el algoritmo debe ser claro e inequívoco. Cada uno de sus pasos (o
fases), y sus entradas / salidas deben ser claras y deben conducir a un solo
significado.
 Entrada : un algoritmo debe tener 0 o más entradas bien definidas.
 Salida : un algoritmo debe tener 1 o más salidas bien definidas y debe coincidir
con la salida deseada.
 Finitud : los algoritmos deben terminar después de un número finito de pasos.
 Viabilidad : debe ser factible con los recursos disponibles.
 Independiente : un algoritmo debe tener instrucciones paso a paso, que deben
ser independientes de cualquier código de programación.

¿Cómo escribir un algoritmo?


No hay estándares bien definidos para escribir algoritmos. Más bien, depende
del problema y de los recursos. Los algoritmos nunca se escriben para admitir
un código de programación particular.
Como sabemos, todos los lenguajes de programación comparten
construcciones de código básicas como bucles (do, for, while), control de flujo
(if-else), etc. Estas construcciones comunes se pueden usar para escribir un
algoritmo.
Escribimos algoritmos paso a paso, pero no siempre es así. La escritura de
algoritmos es un proceso y se ejecuta después de que el dominio del
problema esté bien definido. Es decir, debemos conocer el dominio del
problema, para el cual estamos diseñando una solución.
Ejemplo
Intentemos aprender a escribir algoritmos usando un ejemplo.
Problema : diseñe un algoritmo para agregar dos números y mostrar el
resultado.
Step 1 − START
Step 2 − declare three integers a, b & c
Step 3 − define values of a & b
Step 4 − add values of a & b
Step 5 − store output of step 4 to c
Step 6 − print c
Step 7 − STOP
Los algoritmos les dicen a los programadores cómo codificar el
programa. Alternativamente, el algoritmo se puede escribir como -
Step 1 − START ADD
Step 2 − get values of a & b
Step 3 − c ← a + b
Step 4 − display c
Step 5 − STOP
En el diseño y análisis de algoritmos, generalmente se usa el segundo método
para describir un algoritmo. Facilita al analista analizar el algoritmo ignorando
todas las definiciones no deseadas. Puede observar qué operaciones se están
utilizando y cómo fluye el proceso.
Escribir números de pasos es opcional.
Diseñamos un algoritmo para obtener una solución de un problema dado. Un
problema puede resolverse de más de una manera.

Por lo tanto, se pueden derivar muchos algoritmos de solución para un


problema dado. El siguiente paso es analizar los algoritmos de solución
propuestos e implementar la mejor solución adecuada.

Análisis de algoritmo
La eficiencia de un algoritmo se puede analizar en dos etapas diferentes,
antes de la implementación y después de la implementación. Ellos son los
siguientes:
 Un análisis a priori : este es un análisis teórico de un algoritmo. La eficiencia de
un algoritmo se mide asumiendo que todos los demás factores, por ejemplo, la
velocidad del procesador, son constantes y no tienen ningún efecto en la
implementación.
 Un análisis posterior : este es un análisis empírico de un algoritmo. El algoritmo
seleccionado se implementa utilizando lenguaje de programación. Esto se ejecuta
en la máquina de la computadora de destino. En este análisis, se recopilan
estadísticas reales como el tiempo de ejecución y el espacio requerido.
Aprenderemos sobre el análisis de algoritmos a priori . El análisis de
algoritmos se ocupa de la ejecución o el tiempo de ejecución de varias
operaciones involucradas. El tiempo de ejecución de una operación se puede
definir como el número de instrucciones de computadora ejecutadas por
operación.
Complejidad Algoritmo
Supongamos que X es un algoritmo yn es el tamaño de los datos de entrada,
el tiempo y el espacio utilizados por el algoritmo X son los dos factores
principales, que deciden la eficiencia de X.
 Factor de tiempo : el tiempo se mide contando el número de operaciones clave,
como las comparaciones en el algoritmo de clasificación.
 Factor de espacio : el espacio se mide contando el espacio de memoria máximo
requerido por el algoritmo.
La complejidad de un algoritmo f (n) proporciona el tiempo de ejecución y / o
el espacio de almacenamiento requerido por el algoritmo en términos
de n como el tamaño de los datos de entrada.

Complejidad espacial
La complejidad espacial de un algoritmo representa la cantidad de espacio de
memoria requerida por el algoritmo en su ciclo de vida. El espacio requerido
por un algoritmo es igual a la suma de los siguientes dos componentes:
 Una parte fija que es un espacio requerido para almacenar ciertos datos y
variables, que son independientes del tamaño del problema. Por ejemplo,
variables simples y constantes utilizadas, tamaño del programa, etc.
 Una parte variable es un espacio requerido por variables, cuyo tamaño depende
del tamaño del problema. Por ejemplo, asignación de memoria dinámica, espacio
de pila de recursión, etc.
La complejidad espacial S (P) de cualquier algoritmo P es S (P) = C + SP (I),
donde C es la parte fija y S (I) es la parte variable del algoritmo, que depende
de la característica de instancia I. A continuación es un ejemplo simple que
trata de explicar el concepto
Algorithm: SUM(A, B)
Step 1 - START
Step 2 - C ← A + B + 10
Step 3 - Stop
Aquí tenemos tres variables A, B y C y una constante. Por lo tanto, S (P) = 1 +
3. Ahora, el espacio depende de los tipos de datos de variables dadas y tipos
constantes y se multiplicará en consecuencia.

Complejidad de tiempo
La complejidad temporal de un algoritmo representa la cantidad de tiempo que
el algoritmo requiere para ejecutarse hasta su finalización. Los requisitos de
tiempo se pueden definir como una función numérica T (n), donde T (n) se
puede medir como el número de pasos, siempre que cada paso consuma
tiempo constante.
Por ejemplo, la suma de dos enteros de n bits toma n pasos. En
consecuencia, el tiempo de cálculo total es T (n) = c ∗ n, donde c es el tiempo
necesario para la adición de dos bits. Aquí, observamos que T (n) crece
linealmente a medida que aumenta el tamaño de entrada.

Estructuras de datos - Análisis asintótico


El análisis asintótico de un algoritmo se refiere a definir el límite matemático /
encuadre de su rendimiento en tiempo de ejecución. Utilizando el análisis
asintótico, podemos muy bien concluir el mejor caso, el caso promedio y el
peor escenario de un algoritmo.
El análisis asintótico está limitado a la entrada, es decir, si no hay entrada al
algoritmo, se concluye que funciona en un tiempo constante. Aparte de la
"entrada", todos los demás factores se consideran constantes.
El análisis asintótico se refiere a calcular el tiempo de ejecución de cualquier
operación en unidades matemáticas de cálculo. Por ejemplo, el tiempo de
ejecución de una operación se calcula como f (n) y puede ser para otra
operación, se calcula como g (n 2 ). Esto significa que el tiempo de ejecución
de la primera operación aumentará linealmente con el aumento de n y el
tiempo de ejecución de la segunda operación aumentará exponencialmente
cuando aumente n . Del mismo modo, el tiempo de ejecución de ambas
operaciones será casi el mismo si n es significativamente pequeño.
Por lo general, el tiempo requerido por un algoritmo se divide en tres tipos:
 Mejor caso : tiempo mínimo requerido para la ejecución del programa.
 Caso promedio: tiempo promedio requerido para la ejecución del programa.
 Peor caso : tiempo máximo requerido para la ejecución del programa.

Notaciones asintóticas
A continuación se presentan las notaciones asintóticas comúnmente utilizadas
para calcular la complejidad del tiempo de ejecución de un algoritmo.

 Ο Notación
 Notación Ω
 θ Notación

Big Oh Notation, Ο
La notación Ο (n) es la forma formal de expresar el límite superior del tiempo
de ejecución de un algoritmo. Mide la complejidad del tiempo en el peor de los
casos o la mayor cantidad de tiempo que un algoritmo puede tardar en
completarse.
Por ejemplo, para una función f (n)
Ο(f(n)) = { g(n) : there exists c > 0 and n0 such that f(n) ≤
c.g(n) for all n > n0. }

Notación Omega, Ω
La notación Ω (n) es la forma formal de expresar el límite inferior del tiempo de
ejecución de un algoritmo. Mide la complejidad del tiempo del mejor caso o la
mejor cantidad de tiempo que un algoritmo puede tardar en completarse.

Por ejemplo, para una función f (n)


Ω(f(n)) ≥ { g(n) : there exists c > 0 and n0 such that g(n) ≤
c.f(n) for all n > n0. }

Notación Theta, θ
La notación θ (n) es la forma formal de expresar tanto el límite inferior como el
límite superior del tiempo de ejecución de un algoritmo. Se representa de la
siguiente manera:
θ(f(n)) = { g(n) if and only if g(n) = Ο(f(n)) and g(n) =
Ω(f(n)) for all n > n0. }

Notaciones asintóticas comunes


A continuación se incluye una lista de algunas notaciones asintóticas
comunes:

constante - Ο (1)

logarítmico - Ο (log n)

lineal - Ο (n)

n log n - Ο (n log n)

cuadrático - Ο (n 2 )

cúbico - Ο (n 3 )

polinomio - n Ο (1)

exponencial - 2 Ο (n)

Estructuras de datos - Algoritmos codiciosos


Un algoritmo está diseñado para lograr una solución óptima para un problema
dado. En el enfoque de algoritmo codicioso, las decisiones se toman del
dominio de solución dado. Como codicioso, se elige la solución más cercana
que parece proporcionar una solución óptima.
Los algoritmos codiciosos intentan encontrar una solución óptima localizada,
que eventualmente puede conducir a soluciones globalmente optimizadas. Sin
embargo, los algoritmos generalmente codiciosos no proporcionan soluciones
optimizadas globalmente.

Contando monedas
Este problema es contar hasta un valor deseado eligiendo las monedas menos
posibles y el enfoque codicioso obliga al algoritmo a elegir la moneda más
grande posible. Si se nos proporcionan monedas de ₹ 1, 2, 5 y 10 y se nos
pide que contemos ₹ 18, el procedimiento codicioso será:
 1 - Seleccione una moneda ₹ 10, el recuento restante es 8
 2 - Luego seleccione una moneda ₹ 5, el recuento restante es 3
 3 - Luego seleccione una moneda ₹ 2, el recuento restante es 1
 4 - Y finalmente, la selección de una moneda ₹ 1 resuelve el problema
Sin embargo, parece estar funcionando bien, para este recuento necesitamos
elegir solo 4 monedas. Pero si cambiamos ligeramente el problema, entonces
el mismo enfoque puede no ser capaz de producir el mismo resultado óptimo.
Para el sistema monetario, donde tenemos monedas de valor 1, 7, 10, contar
monedas por valor 18 será absolutamente óptimo, pero para contar como 15,
puede usar más monedas de las necesarias. Por ejemplo, el enfoque
codicioso usará 10 + 1 + 1 + 1 + 1 + 1, un total de 6 monedas. Mientras que el
mismo problema podría resolverse utilizando solo 3 monedas (7 + 7 + 1)
Por lo tanto, podemos concluir que el enfoque codicioso elige una solución
optimizada inmediata y puede fallar donde la optimización global es una
preocupación importante.
Ejemplos
La mayoría de los algoritmos de redes utilizan el enfoque codicioso. Aquí hay
una lista de algunos de ellos:

 Problema de vendedor ambulante


 Algoritmo de árbol de expansión mínimo de Prim
 Algoritmo de árbol de expansión mínimo de Kruskal
 Algoritmo de árbol de expansión mínimo de Dijkstra
 Gráfico - Colorear mapa
 Gráfico - Cubierta de Vértice
 Problema de mochila
 Problema de programación de trabajo
Hay muchos problemas similares que utilizan el enfoque codicioso para
encontrar una solución óptima.
Estructuras de datos - Divide y vencerás
En el enfoque de dividir y conquistar, el problema en cuestión se divide en
subproblemas más pequeños y luego cada problema se resuelve de forma
independiente. Cuando seguimos dividiendo los subproblemas en
subproblemas aún más pequeños, eventualmente podemos llegar a una etapa
en la que no es posible más división. Se resuelven los subproblemas
(fracciones) más pequeños "atómicos" posibles. La solución de todos los
subproblemas finalmente se fusiona para obtener la solución de un problema
original.

En términos generales, podemos entender el enfoque de divide y


vencerás en un proceso de tres pasos.

Divide / Break
Este paso implica dividir el problema en subproblemas más pequeños. Los
subproblemas deben representar una parte del problema original. Este paso
generalmente toma un enfoque recursivo para dividir el problema hasta que
ningún subproblema sea más divisible. En esta etapa, los subproblemas se
vuelven de naturaleza atómica pero aún representan una parte del problema
real.

Conquistar / Resolver
Este paso recibe muchos subproblemas más pequeños que deben
resolverse. En general, a este nivel, los problemas se consideran "resueltos"
por sí mismos.

Fusionar / Combinar
Cuando se resuelven los subproblemas más pequeños, esta etapa los
combina de manera recursiva hasta que formulan una solución del problema
original. Este enfoque algorítmico funciona de forma recursiva y los pasos de
conquistar y fusionar funcionan tan cerca que aparecen como uno solo.
Ejemplos
Los siguientes algoritmos informáticos se basan en el enfoque de
programación de divide y vencerás :

 Ordenar fusión
 Ordenación rápida
 Búsqueda binaria
 Multiplicación matricial de Strassen
 Par más cercano (puntos)
Hay varias formas disponibles para resolver cualquier problema informático,
pero las mencionadas son un buen ejemplo de enfoque de divide y vencerás.

Estructuras de datos - Programación


dinámica
El enfoque de programación dinámica es similar a dividir y vencer al dividir el
problema en subproblemas más pequeños y posibles. Pero a diferencia de
dividir y conquistar, estos subproblemas no se resuelven de forma
independiente. Más bien, los resultados de estos subproblemas más
pequeños se recuerdan y se usan para subproblemas similares o
superpuestos.
La programación dinámica se usa cuando tenemos problemas, que se pueden
dividir en subproblemas similares, de modo que sus resultados se puedan
reutilizar. En su mayoría, estos algoritmos se utilizan para la
optimización. Antes de resolver el subproblema disponible, el algoritmo
dinámico intentará examinar los resultados de los subproblemas resueltos
previamente. Las soluciones de subproblemas se combinan para lograr la
mejor solución.
Entonces podemos decir que ...
 El problema debería poder dividirse en un subproblema superpuesto más
pequeño.
 Se puede lograr una solución óptima utilizando una solución óptima de
subproblemas más pequeños.
 Los algoritmos dinámicos usan Memoization.

Comparación
A diferencia de los algoritmos codiciosos, donde se aborda la optimización
local, los algoritmos dinámicos están motivados para una optimización general
del problema.
A diferencia de los algoritmos de dividir y conquistar, donde las soluciones se
combinan para lograr una solución general, los algoritmos dinámicos utilizan la
salida de un subproblema más pequeño y luego intentan optimizar un
subproblema más grande. Los algoritmos dinámicos usan Memoization para
recordar la salida de subproblemas ya resueltos.
Ejemplo
Los siguientes problemas de la computadora se pueden resolver utilizando el
enfoque de programación dinámica:

 Serie de números de Fibonacci


 Problema de mochila
 Torre de Hanoi
 Todo el camino más corto de Floyd-Warshall
 El camino más corto por Dijkstra
 Programación de proyectos
La programación dinámica se puede utilizar tanto de arriba hacia abajo como
de abajo hacia arriba. Y, por supuesto, la mayoría de las veces, referirse a la
salida de la solución anterior es más barato que volver a calcular en términos
de ciclos de CPU.

Estructuras de datos y algoritmos Conceptos


básicos
Este capítulo explica los términos básicos relacionados con la estructura de
datos.

Definición de datos
La definición de datos define datos particulares con las siguientes
características.
 Atómico : la definición debe definir un concepto único.
 Rastreable : la definición debe poder asignarse a algún elemento de datos.
 Preciso : la definición no debe ser ambigua.
 Claro y conciso : la definición debe ser comprensible.

Objeto de datos
Objeto de datos representa un objeto que tiene datos.

Tipo de datos
El tipo de datos es una forma de clasificar varios tipos de datos, como enteros,
cadenas, etc., que determina los valores que se pueden usar con el tipo de
datos correspondiente, el tipo de operaciones que se pueden realizar en el tipo
de datos correspondiente. Hay dos tipos de datos:

 Tipo de datos incorporado


 Tipo de datos derivados

Tipo de datos incorporado


Los tipos de datos para los que un idioma tiene soporte incorporado se
conocen como tipos de datos incorporados. Por ejemplo, la mayoría de los
idiomas proporcionan los siguientes tipos de datos integrados.

 Enteros
 Booleano (verdadero, falso)
 Flotante (números decimales)
 Carácter y cadenas

Tipo de datos derivados


Los tipos de datos que son independientes de la implementación, ya que se
pueden implementar de una u otra forma, se conocen como tipos de datos
derivados. Estos tipos de datos normalmente se crean mediante la
combinación de tipos de datos primarios o integrados y las operaciones
asociadas en ellos. Por ejemplo

 Lista
 Formación
 Apilar
 Cola

Operaciones básicas
Los datos en las estructuras de datos son procesados por ciertas
operaciones. La estructura de datos particular elegida depende en gran
medida de la frecuencia de la operación que debe realizarse en la estructura
de datos.

 Atravesar
 buscando
 Inserción
 Supresión
 Clasificación
 Fusionando

Estructuras de datos y algoritmos: matrices


La matriz es un contenedor que puede contener un número fijo de elementos y
estos elementos deben ser del mismo tipo. La mayoría de las estructuras de
datos utilizan matrices para implementar sus algoritmos. Los siguientes son
los términos importantes para comprender el concepto de matriz.
 Elemento : cada elemento almacenado en una matriz se denomina elemento.
 Índice : cada ubicación de un elemento en una matriz tiene un índice numérico,
que se utiliza para identificar el elemento.

Representación de matriz
Las matrices se pueden declarar de varias maneras en diferentes idiomas. Por
ejemplo, tomemos la declaración de la matriz C.

Las matrices se pueden declarar de varias maneras en diferentes idiomas. Por


ejemplo, tomemos la declaración de la matriz C.

Según la ilustración anterior, los siguientes son los puntos importantes a


considerar.
 El índice comienza con 0.
 La longitud de la matriz es 10, lo que significa que puede almacenar 10 elementos.
 Se puede acceder a cada elemento a través de su índice. Por ejemplo, podemos
buscar un elemento en el índice 6 como 9.

Operaciones básicas
Las siguientes son las operaciones básicas compatibles con una matriz.
 Traverse : imprime todos los elementos de la matriz uno por uno.
 Inserción : agrega un elemento en el índice dado.
 Eliminación : elimina un elemento en el índice dado.
 Buscar : busca un elemento usando el índice dado o por el valor.
 Actualizar : actualiza un elemento en el índice dado.
En C, cuando una matriz se inicializa con tamaño, asigna valores
predeterminados a sus elementos en el siguiente orden.

Tipo de datos Valor por defecto


bool falso

carbonizarse 00

En t 00

flotador 0.0

doble 0.0f

vacío

wchar_t 00

Operación transversal
Esta operación es atravesar los elementos de una matriz.
Ejemplo
El siguiente programa atraviesa e imprime los elementos de una matriz:
#include <stdio.h>
main() {
int LA[] = {1,3,5,7,8};
int item = 10, k = 3, n = 5;
int i = 0, j = n;
printf("The original array elements are :\n");
for(i = 0; i<n; i++) {
printf("LA[%d] = %d \n", i, LA[i]);
}
}
Cuando compilamos y ejecutamos el programa anterior, produce el siguiente
resultado:
Salida
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
Operación de inserción
La operación de inserción consiste en insertar uno o más elementos de datos
en una matriz. Según el requisito, se puede agregar un nuevo elemento al
principio, al final o en cualquier índice dado de la matriz.
Aquí, vemos una implementación práctica de la operación de inserción, donde
agregamos datos al final de la matriz:
Ejemplo
A continuación se muestra la implementación del algoritmo anterior:
Demo en vivo

#include <stdio.h>

main() {
int LA[] = {1,3,5,7,8};
int item = 10, k = 3, n = 5;
int i = 0, j = n;

printf("The original array elements are :\n");

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


printf("LA[%d] = %d \n", i, LA[i]);
}

n = n + 1;

while( j >= k) {
LA[j+1] = LA[j];
j = j - 1;
}

LA[k] = item;

printf("The array elements after insertion :\n");

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


printf("LA[%d] = %d \n", i, LA[i]);
}
}
Cuando compilamos y ejecutamos el programa anterior, produce el siguiente
resultado:
Salida
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
The array elements after insertion :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 10
LA[4] = 7
LA[5] = 8
Para otras variaciones de la operación de inserción de matriz, haga clic aquí

Operación de borrado
La eliminación se refiere a eliminar un elemento existente de la matriz y
reorganizar todos los elementos de una matriz.
Algoritmo
Considere LA es un array lineal con N elementos, y K es un número entero
positivo tal que K <= N . A continuación se presenta el algoritmo para eliminar
un elemento disponible en el K ª posición de LA.
1. Start
2. Set J = K
3. Repeat steps 4 and 5 while J < N
4. Set LA[J] = LA[J + 1]
5. Set J = J+1
6. Set N = N-1
7. Stop

Ejemplo
A continuación se muestra la implementación del algoritmo anterior:
Demo en vivo

#include <stdio.h>

void main() {
int LA[] = {1,3,5,7,8};
int k = 3, n = 5;
int i, j;

printf("The original array elements are :\n");

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


printf("LA[%d] = %d \n", i, LA[i]);
}

j = k;

while( j < n) {
LA[j-1] = LA[j];
j = j + 1;
}
n = n -1;

printf("The array elements after deletion :\n");

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


printf("LA[%d] = %d \n", i, LA[i]);
}
}

Cuando compilamos y ejecutamos el programa anterior, produce el siguiente


resultado:
Salida
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
The array elements after deletion :
LA[0] = 1
LA[1] = 3
LA[2] = 7
LA[3] = 8

Operación de búsqueda
Puede realizar una búsqueda de un elemento de matriz en función de su valor
o su índice.
Algoritmo
Considere LA es un array lineal con N elementos, y K es un número entero
positivo tal que K <= N . El siguiente es el algoritmo para encontrar un
elemento con un valor de ITEM utilizando la búsqueda secuencial.
1. Start
2. Set J = 0
3. Repeat steps 4 and 5 while J < N
4. IF LA[J] is equal ITEM THEN GOTO STEP 6
5. Set J = J +1
6. PRINT J, ITEM
7. Stop

Ejemplo
A continuación se muestra la implementación del algoritmo anterior:
Demo en vivo

#include <stdio.h>

void main() {
int LA[] = {1,3,5,7,8};
int item = 5, n = 5;
int i = 0, j = 0;

printf("The original array elements are :\n");

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


printf("LA[%d] = %d \n", i, LA[i]);
}

while( j < n){


if( LA[j] == item ) {
break;
}

j = j + 1;
}

printf("Found element %d at position %d\n", item, j+1);


}

Cuando compilamos y ejecutamos el programa anterior, produce el siguiente


resultado:
Salida
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
Found element 5 at position 3

Operación de actualización
La operación de actualización se refiere a actualizar un elemento existente de
la matriz en un índice dado.
Algoritmo
Considere LA es un array lineal con N elementos, y K es un número entero
positivo tal que K <= N . A continuación se presenta el algoritmo para
actualizar un elemento disponible en el K ª posición de LA.
1. Start
2. Set LA[K-1] = ITEM
3. Stop

Ejemplo
A continuación se muestra la implementación del algoritmo anterior:
Demo en vivo
#include <stdio.h>

void main() {
int LA[] = {1,3,5,7,8};
int k = 3, n = 5, item = 10;
int i, j;

printf("The original array elements are :\n");

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


printf("LA[%d] = %d \n", i, LA[i]);
}

LA[k-1] = item;

printf("The array elements after updation :\n");

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


printf("LA[%d] = %d \n", i, LA[i]);
}
}
Cuando compilamos y ejecutamos el programa anterior, produce el siguiente
resultado:
Salida
The original array elements are :
LA[0] = 1
LA[1] = 3
LA[2] = 5
LA[3] = 7
LA[4] = 8
The array elements after updation :
LA[0] = 1
LA[1] = 3
LA[2] = 10
LA[3] = 7
LA[4] = 8
Estructura de datos y algoritmos: lista
vinculada
Una lista vinculada es una secuencia de estructuras de datos, que están
conectadas entre sí mediante enlaces.
Lista enlazada es una secuencia de enlaces que contiene elementos. Cada
enlace contiene una conexión a otro enlace. La lista vinculada es la segunda
estructura de datos más utilizada después de la matriz. Los siguientes son los
términos importantes para comprender el concepto de Lista enlazada.
 Enlace : cada enlace de una lista vinculada puede almacenar datos llamados
elementos.
 Siguiente : cada enlace de una lista enlazada contiene un enlace al siguiente
enlace llamado Siguiente.
 LinkedList : una lista vinculada contiene el enlace de conexión al primer enlace
llamado First.

Representación de la lista vinculada


La lista vinculada se puede visualizar como una cadena de nodos, donde cada
nodo apunta al siguiente nodo.

Según la ilustración anterior, los siguientes son los puntos importantes a


considerar.
 Lista enlazada contiene un elemento de enlace llamado primero.
 Cada enlace lleva un campo de datos y un campo de enlace llamado a
continuación.
 Cada enlace está vinculado con su siguiente enlace utilizando su siguiente enlace.
 El último enlace lleva un enlace como nulo para marcar el final de la lista.

Tipos de lista vinculada


Los siguientes son los diversos tipos de lista vinculada.
 Lista simple vinculada : la navegación de elementos es solo hacia adelante.
 Lista doblemente vinculada : los elementos se pueden navegar hacia adelante y
hacia atrás.
 Lista circular vinculada : el último elemento contiene el enlace del primer
elemento como el siguiente y el primer elemento tiene un enlace al último
elemento como el anterior.

Operaciones básicas
Las siguientes son las operaciones básicas compatibles con una lista.
 Inserción : agrega un elemento al principio de la lista.
 Eliminación : elimina un elemento al comienzo de la lista.
 Pantalla : muestra la lista completa.
 Buscar : busca un elemento utilizando la clave dada.
 Eliminar : elimina un elemento usando la clave dada.

Operación de inserción
Agregar un nuevo nodo en la lista vinculada es una actividad de más de un
paso. Aprenderemos esto con diagramas aquí. Primero, cree un nodo con la
misma estructura y encuentre la ubicación donde debe insertarse.

Imagine que estamos insertando un nodo B (NewNode), entre A (LeftNode)


y C (RightNode). Luego apunte B.siguiente a C -
NewNode.next −> RightNode;
Debería verse así:

Ahora, el siguiente nodo a la izquierda debería apuntar al nuevo nodo.


LeftNode.next −> NewNode;

Esto colocará el nuevo nodo en el medio de los dos. La nueva lista debería
verse así:
Se deben seguir pasos similares si el nodo se inserta al principio de la lista. Al
insertarlo al final, el segundo último nodo de la lista debe apuntar al nuevo
nodo y el nuevo nodo apuntará a NULL.

Operación de borrado
La eliminación también es un proceso de más de un paso. Aprenderemos con
representación pictórica. Primero, ubique el nodo objetivo que se eliminará,
utilizando algoritmos de búsqueda.

El nodo izquierdo (anterior) del nodo objetivo ahora debe apuntar al siguiente
nodo del nodo objetivo:
LeftNode.next −> TargetNode.next;

Esto eliminará el enlace que apuntaba al nodo de destino. Ahora, usando el


siguiente código, eliminaremos lo que apunta el nodo de destino.
TargetNode.next −> NULL;

Necesitamos usar el nodo eliminado. Podemos mantener eso en la memoria;


de lo contrario, simplemente podemos desasignar la memoria y borrar
completamente el nodo de destino.

Operación inversa
Esta operación es exhaustiva. Necesitamos hacer que el último nodo sea
señalado por el nodo principal e invertir toda la lista vinculada.

Primero, atravesamos hasta el final de la lista. Debería estar apuntando a


NULL. Ahora, haremos que apunte a su nodo anterior:

Tenemos que asegurarnos de que el último nodo no sea el nodo


perdido. Entonces tendremos un nodo temporal, que se parece al nodo
principal que apunta al último nodo. Ahora, haremos que todos los nodos del
lado izquierdo apunten a sus nodos anteriores uno por uno.

Excepto el nodo (primer nodo) señalado por el nodo principal, todos los nodos
deben apuntar a su predecesor, convirtiéndolos en su nuevo sucesor. El
primer nodo apuntará a NULL.

Haremos que el nodo principal apunte al nuevo primer nodo utilizando el nodo
temporal.

La lista vinculada ahora se invierte. Para ver la implementación de la lista


vinculada en lenguaje de programación C, haga clic aquí .
Estructura de datos - Lista doblemente
vinculada
La lista doblemente vinculada es una variación de la lista vinculada en la que
la navegación es posible en ambos sentidos, tanto hacia adelante como hacia
atrás fácilmente en comparación con la lista única vinculada. Los siguientes
son los términos importantes para comprender el concepto de lista doblemente
vinculada.
 Enlace : cada enlace de una lista vinculada puede almacenar datos llamados
elementos.
 Siguiente : cada enlace de una lista enlazada contiene un enlace al siguiente
enlace llamado Siguiente.
 Anterior : cada enlace de una lista enlazada contiene un enlace al enlace anterior
llamado Anterior.
 LinkedList : una lista vinculada contiene el enlace de conexión al primer enlace
llamado First y al último enlace llamado Last.

Representación de lista doblemente vinculada

Según la ilustración anterior, los siguientes son los puntos importantes a


considerar.
 La Lista doblemente vinculada contiene un elemento de enlace llamado primero y
último.
 Cada enlace lleva un campo (s) de datos y dos campos de enlace llamados next y
prev.
 Cada enlace está vinculado con su siguiente enlace utilizando su siguiente enlace.
 Cada enlace está vinculado con su enlace anterior utilizando su enlace anterior.
 El último enlace lleva un enlace como nulo para marcar el final de la lista.

Operaciones básicas
Las siguientes son las operaciones básicas compatibles con una lista.
 Inserción : agrega un elemento al principio de la lista.
 Eliminación : elimina un elemento al comienzo de la lista.
 Insertar último : agrega un elemento al final de la lista.
 Eliminar último : elimina un elemento del final de la lista.
 Insertar después : agrega un elemento después de un elemento de la lista.
 Eliminar : elimina un elemento de la lista con la tecla.
 Mostrar hacia adelante : muestra la lista completa de manera hacia adelante.
 Mostrar al revés : muestra la lista completa al revés.

Operación de inserción
El siguiente código muestra la operación de inserción al comienzo de una lista
doblemente vinculada.
Ejemplo
//insert link at the first location
void insertFirst(int key, int data) {

//create a link
struct node *link = (struct node*) malloc(sizeof(struct
node));
link->key = key;
link->data = data;

if(isEmpty()) {
//make it the last link
last = link;
} else {
//update first prev link
head->prev = link;
}

//point it to old first link


link->next = head;

//point first to new first link


head = link;
}

Operación de borrado
El siguiente código muestra la operación de eliminación al comienzo de una
lista doblemente vinculada.
Ejemplo
//delete first item
struct node* deleteFirst() {

//save reference to first link


struct node *tempLink = head;

//if only one link


if(head->next == NULL) {
last = NULL;
} else {
head->next->prev = NULL;
}

head = head->next;

//return the deleted link


return tempLink;
}

Inserción al final de una operación


El siguiente código muestra la operación de inserción en la última posición de
una lista doblemente vinculada.
Ejemplo
//insert link at the last location
void insertLast(int key, int data) {

//create a link
struct node *link = (struct node*) malloc(sizeof(struct
node));
link->key = key;
link->data = data;

if(isEmpty()) {
//make it the last link
last = link;
} else {
//make link a new last link
last->next = link;

//mark old last node as prev of new link


link->prev = last;
}

//point last to new last node


last = link;
}
Para ver la implementación en lenguaje de programación C, haga clic aquí .

Estructura de datos - Lista circular vinculada


La lista enlazada circular es una variación de la lista enlazada en la que el
primer elemento apunta al último elemento y el último elemento apunta al
primer elemento. Tanto la Lista enlazada individualmente como la Lista
enlazada doblemente se pueden convertir en una lista enlazada circular.

Lista enlazada individualmente como circular


En una lista vinculada individualmente, el siguiente puntero del último nodo
apunta al primer nodo.

Lista doblemente enlazada como circular


En una lista doblemente vinculada, el siguiente puntero del último nodo apunta
al primer nodo y el puntero anterior del primer nodo apunta al último nodo
haciendo la circular en ambas direcciones.

Según la ilustración anterior, los siguientes son los puntos importantes a


considerar.
 Los siguientes puntos del último enlace al primer enlace de la lista en ambos
casos de lista individual y doble.
 Los puntos anteriores del primer enlace al último de la lista en caso de lista
doblemente enlazada.

Operaciones básicas
Las siguientes son las operaciones importantes respaldadas por una lista
circular.
 insert : inserta un elemento al comienzo de la lista.
 eliminar : elimina un elemento desde el comienzo de la lista.
 display : muestra la lista.

Operación de inserción
El siguiente código demuestra la operación de inserción en una lista enlazada
circular basada en una lista enlazada única.
Ejemplo
//insert link at the first location
void insertFirst(int key, int data) {
//create a link
struct node *link = (struct node*) malloc(sizeof(struct
node));
link->key = key;
link->data= data;

if (isEmpty()) {
head = link;
head->next = head;
} else {
//point it to old first node
link->next = head;

//point first to new first node


head = link;
}
}

Operación de borrado
El siguiente código muestra la operación de eliminación en una lista enlazada
circular basada en una lista enlazada única.
//delete first item
struct node * deleteFirst() {
//save reference to first link
struct node *tempLink = head;

if(head->next == head) {
head = NULL;
return tempLink;
}

//mark next to first link as first


head = head->next;

//return the deleted link


return tempLink;
}

Operación de lista de visualización


El siguiente código muestra el funcionamiento de la lista de visualización en
una lista enlazada circular.
//display the list
void printList() {
struct node *ptr = head;
printf("\n[ ");

//start from the beginning


if(head != NULL) {
while(ptr->next != ptr) {
printf("(%d,%d) ",ptr->key,ptr->data);
ptr = ptr->next;
}
}

printf(" ]");
}

Para conocer su implementación en lenguaje de programación C, haga clic


aquí .

Estructura de datos y algoritmos: pila


Una pila es un tipo de datos abstractos (ADT), comúnmente utilizado en la
mayoría de los lenguajes de programación. Se llama pila, ya que se comporta
como una pila del mundo real, por ejemplo: una baraja de cartas o una pila de
platos, etc.

Una pila del mundo real permite operaciones solo en un extremo. Por ejemplo,
podemos colocar o quitar una tarjeta o plato de la parte superior de la pila
solamente. Del mismo modo, Stack ADT permite todas las operaciones de
datos en un solo extremo. En cualquier momento, solo podemos acceder al
elemento superior de una pila.
Esta característica hace que sea una estructura de datos LIFO. LIFO significa
último en entrar, primero en salir. Aquí, el elemento que se coloca (insertado o
agregado) en último lugar, se accede primero. En la terminología de la pila, la
operación de inserción se llama operación PUSH y la operación de extracción
se llama operación POP .

Representación de pila
El siguiente diagrama muestra una pila y sus operaciones:
Se puede implementar una pila mediante Array, Estructura, Puntero y Lista
enlazada. La pila puede ser de tamaño fijo o puede tener una sensación de
cambio de tamaño dinámico. Aquí, vamos a implementar la pila usando
matrices, lo que la convierte en una implementación de pila de tamaño fijo.

Operaciones básicas
Las operaciones de apilamiento pueden implicar inicializar el apilamiento,
usarlo y luego desinicializarlo. Además de estas cosas básicas, se utiliza una
pila para las siguientes dos operaciones principales:
 push () - Empujar (almacenar) un elemento en la pila.
 pop () - Eliminar (acceder) un elemento de la pila.
Cuando los datos se EMPUJAN en la pila.
Para usar una pila de manera eficiente, también debemos verificar el estado
de la pila. Con el mismo propósito, se agrega la siguiente funcionalidad a las
pilas:
 peek () : obtiene el elemento de datos superior de la pila, sin eliminarlo.
 isFull () : comprueba si la pila está llena.
 isEmpty () : comprueba si la pila está vacía.
En todo momento, mantenemos un puntero a los últimos datos PUSHed en la
pila. Como este puntero siempre representa la parte superior de la pila, se
llama top . El puntero superior proporciona el valor superior de la pila sin
eliminarlo realmente.
Primero, deberíamos conocer los procedimientos para admitir las funciones de
la pila:
ojeada()
Algoritmo de la función peek () -
begin procedure peek
return stack[top]
end procedure
Implementación de la función peek () en lenguaje de programación C -
Ejemplo
int peek() {
return stack[top];
}

está lleno()
Algoritmo de la función isfull () -
begin procedure isfull

if top equals to MAXSIZE


return true
else
return false
endif

end procedure
Implementación de la función isfull () en lenguaje de programación C -
Ejemplo
bool isfull() {
if(top == MAXSIZE)
return true;
else
return false;
}

esta vacio()
Algoritmo de la función isempty () -
begin procedure isempty

if top less than 1


return true
else
return false
endif

end procedure
La implementación de la función isempty () en el lenguaje de programación C
es ligeramente diferente. Inicializamos top en -1, ya que el índice en la matriz
comienza desde 0. Por lo tanto, verificamos si el top está por debajo de cero o
-1 para determinar si la pila está vacía. Aquí está el código.
Ejemplo
bool isempty() {
if(top == -1)
return true;
else
return false;
}

Operación de empuje
El proceso de poner un nuevo elemento de datos en la pila se conoce como
Operación Push. La operación de empuje implica una serie de pasos:
 Paso 1 : comprueba si la pila está llena.
 Paso 2 : si la pila está llena, produce un error y sale.
 Paso 3 : si la pila no está llena, aumenta de arriba hacia abajo para apuntar al
siguiente espacio vacío.
 Paso 4 : agrega un elemento de datos a la ubicación de la pila, donde apunta la
parte superior.
 Paso 5 - Devuelve el éxito.

Si la lista vinculada se usa para implementar la pila, en el paso 3, debemos


asignar espacio dinámicamente.
Algoritmo para la Operación PUSH
Se puede derivar un algoritmo simple para la operación Push de la siguiente
manera:
begin procedure push: stack, data

if stack is full
return null
endif

top ← top + 1
stack[top] ← data

end procedure
La implementación de este algoritmo en C es muy fácil. Ver el siguiente código
-
Ejemplo
void push(int data) {
if(!isFull()) {
top = top + 1;
stack[top] = data;
} else {
printf("Could not insert data, Stack is full.\n");
}
}

Operación Pop
Acceder al contenido mientras se elimina de la pila, se conoce como
Operación Pop. En una implementación de matriz de la operación pop (), el
elemento de datos no se elimina realmente, sino que la parte superior se
reduce a una posición inferior en la pila para apuntar al siguiente valor. Pero
en la implementación de la lista vinculada, pop () en realidad elimina el
elemento de datos y desasigna el espacio de memoria.
Una operación Pop puede incluir los siguientes pasos:
 Paso 1 : comprueba si la pila está vacía.
 Paso 2 : si la pila está vacía, produce un error y sale.
 Paso 3 : si la pila no está vacía, accede al elemento de datos al que apunta
la parte superior .
 Paso 4 - disminuye el valor de la parte superior por 1.
 Paso 5 - Devuelve el éxito.
Algoritmo para la Operación Pop
Un algoritmo simple para la operación Pop puede derivarse de la siguiente
manera:
begin procedure pop: stack

if stack is empty
return null
endif

data ← stack[top]
top ← top - 1
return data

end procedure

La implementación de este algoritmo en C es la siguiente:


Ejemplo
int pop(int data) {

if(!isempty()) {
data = stack[top];
top = top - 1;
return data;
} else {
printf("Could not retrieve data, Stack is empty.\n");
}
}

Para obtener un programa de pila completo en lenguaje de programación


C, haga clic aquí .

Estructura de datos: análisis de expresiones


La forma de escribir expresiones aritméticas se conoce como notación . Una
expresión aritmética se puede escribir en tres notaciones diferentes pero
equivalentes, es decir, sin cambiar la esencia o el resultado de una
expresión. Estas anotaciones son:

 Notación de infijo
 Notación de prefijo (polaco)
 Notación Postfix (polaco inverso)
Estas anotaciones se denominan como usan el operador en la
expresión. Aprenderemos lo mismo aquí en este capítulo.

Notación de infijo
Escribimos expresión en infijo notación, por ejemplo, a - b + c, donde los
operadores se utilizan en -entre operandos. Es fácil para nosotros los
humanos leer, escribir y hablar en notación infija, pero lo mismo no funciona
bien con los dispositivos informáticos. Un algoritmo para procesar la notación
infija podría ser difícil y costoso en términos de consumo de tiempo y espacio.

Notación de prefijo
En esta notación, el operador está prefijado a los operandos, es decir, el
operador se escribe antes de los operandos. Por ejemplo, + ab . Esto es
equivalente a su notación infija a + b . La notación de prefijo también se
conoce como notación polaca .

Notación de Postfix
Este estilo de notación se conoce como notación polaca invertida . En este
estilo de notación, el operador se pospone en los operandos, es decir, el
operador se escribe después de los operandos. Por ejemplo, ab + . Esto es
equivalente a su notación infija a + b .
La siguiente tabla trata brevemente de mostrar la diferencia en las tres
notaciones:

No Señor. Notación de infijo Notación de prefijo Notación de Postfix

1 a+b + ab ab +

2 (a + b) ∗ c ∗ + abc ab + c ∗

3 a ∗ (b + c) ∗ a + bc abc + ∗

44 a/b+c/d + / ab / cd ab / cd / +
55 (a + b) ∗ (c + d) ∗ + ab + cd ab + cd + ∗

66 ((a + b) ∗ c) - d - ∗ + abcd ab + c ∗ d -

Analizando Expresiones
Como hemos discutido, no es una forma muy eficiente de diseñar un algoritmo
o programa para analizar las anotaciones infijadas. En cambio, estas
anotaciones de infijo se convierten primero en anotaciones de postfijo o prefijo
y luego se calculan.
Para analizar cualquier expresión aritmética, también debemos ocuparnos de
la precedencia del operador y la asociatividad.
Precedencia
Cuando un operando se encuentra entre dos operadores diferentes, qué
operador tomará el operando primero, se decide por la precedencia de un
operador sobre otros. Por ejemplo

Como la operación de multiplicación tiene prioridad sobre la suma, b * c se


evaluará primero. Más adelante se proporciona una tabla de precedencia de
operadores.
Asociatividad
La asociatividad describe la regla donde los operadores con la misma
precedencia aparecen en una expresión. Por ejemplo, en la expresión a + b -
c, ambos + y - tienen la misma precedencia, entonces, qué parte de la
expresión se evaluará primero, está determinada por la asociatividad de esos
operadores. Aquí, ambos + y - se dejan asociativos, por lo que la expresión se
evaluará como (a + b) - c .
La precedencia y la asociatividad determinan el orden de evaluación de una
expresión. A continuación se muestra una tabla de prioridad y asociatividad
del operador (de mayor a menor):

No Señor. Operador Precedencia Asociatividad

1 Exponenciación ^ Más alto Derecho asociativo

2 Multiplicación (∗) y División (/) Segundo más alto Asociativo de izquierda


3 Suma (+) y Resta (-) Más bajo Asociativo de izquierda

La tabla anterior muestra el comportamiento predeterminado de los


operadores. En cualquier momento en la evaluación de la expresión, el orden
puede modificarse usando paréntesis. Por ejemplo
En a + b * c , la parte de expresión b * c se evaluará primero, con la
multiplicación como precedencia sobre la suma. Aquí usamos paréntesis para
que a + b se evalúe primero, como (a + b) * c .

Algoritmo de Evaluación Postfix


Ahora veremos el algoritmo sobre cómo evaluar la notación postfix -
Step 1 − scan the expression from left to right
Step 2 − if it is an operand push it to stack
Step 3 − if it is an operator pull operand from stack and
perform operation
Step 4 − store the output of step 3, back to stack
Step 5 − scan the expression until all operands are consumed
Step 6 − pop the stack and perform operation
Para ver la implementación en lenguaje de programación C, haga clic aquí .

Estructura de datos y algoritmos: cola


La cola es una estructura de datos abstracta, algo similar a las pilas. A
diferencia de las pilas, una cola está abierta en ambos extremos. Un extremo
siempre se usa para insertar datos (poner en cola) y el otro se usa para
eliminar datos (poner en cola). La cola sigue la metodología Primero en entrar,
primero en salir, es decir, primero se accederá al elemento de datos
almacenado primero.

Un ejemplo real de cola puede ser una carretera de un solo sentido, donde el
vehículo entra primero, sale primero. Se pueden ver más ejemplos del mundo
real como colas en las ventanillas y las paradas de autobús.

Representación de cola
Como ahora entendemos que en la cola, accedemos a ambos extremos por
diferentes razones. El siguiente diagrama a continuación intenta explicar la
representación de la cola como estructura de datos:
Al igual que en las pilas, una cola también se puede implementar utilizando
matrices, listas vinculadas, punteros y estructuras. En aras de la simplicidad,
implementaremos colas utilizando una matriz unidimensional.

Operaciones básicas
Las operaciones de la cola pueden implicar inicializar o definir la cola, utilizarla
y luego borrarla completamente de la memoria. Aquí intentaremos comprender
las operaciones básicas asociadas con las colas:
 enqueue () : agrega (almacena) un elemento a la cola.
 dequeue () : elimina (accede) un elemento de la cola.
Se requieren pocas funciones más para que la operación de la cola
mencionada anteriormente sea eficiente. Estos son -
 peek () - Obtiene el elemento al frente de la cola sin eliminarlo.
 isfull () - Comprueba si la cola está llena.
 isempty () - Comprueba si la cola está vacía.
En la cola, siempre ponemos en cola (o accedemos) los datos, apuntando con
el puntero delantero y mientras enquistamos (o almacenamos) datos en la
cola, tomamos la ayuda del puntero trasero .
Primero aprendamos sobre las funciones de apoyo de una cola:
ojeada()
Esta función ayuda a ver los datos al frente de la cola. El algoritmo de la
función peek () es el siguiente:
Algoritmo
begin procedure peek
return queue[front]
end procedure
Implementación de la función peek () en lenguaje de programación C -
Ejemplo
int peek() {
return queue[front];
}
está lleno()
Como estamos utilizando una matriz de una sola dimensión para implementar
la cola, solo verificamos que el puntero trasero llegue a MAXSIZE para
determinar que la cola está llena. En caso de que mantengamos la cola en
una lista circular vinculada, el algoritmo será diferente. Algoritmo de la función
isfull () -
Algoritmo
begin procedure isfull

if rear equals to MAXSIZE


return true
else
return false
endif

end procedure

Implementación de la función isfull () en lenguaje de programación C -


Ejemplo
bool isfull() {
if(rear == MAXSIZE - 1)
return true;
else
return false;
}

esta vacio()
Algoritmo de la función isempty () -
Algoritmo
begin procedure isempty

if front is less than MIN OR front is greater than rear


return true
else
return false
endif

end procedure
Si el valor de front es menor que MIN o 0, indica que la cola aún no se ha
inicializado, por lo tanto, está vacía.
Aquí está el código de programación C -
Ejemplo
bool isempty() {
if(front < 0 || front > rear)
return true;
else
return false;
}

Operación en cola
Las colas mantienen dos punteros de datos, frontal y posterior . Por lo tanto,
sus operaciones son relativamente difíciles de implementar que las de las
pilas.
Se deben seguir los siguientes pasos para poner en cola (insertar) datos en
una cola:
 Paso 1 : compruebe si la cola está llena.
 Paso 2 : si la cola está llena, produzca un error de desbordamiento y salga.
 Paso 3 : si la cola no está llena, incremente el puntero trasero para apuntar al
siguiente espacio vacío.
 Paso 4 : agregue un elemento de datos a la ubicación de la cola, donde apunta la
parte posterior.
 Paso 5 : devuelve el éxito.

A veces, también verificamos si una cola se inicializa o no, para manejar


situaciones imprevistas.
Algoritmo para operación en cola
procedure enqueue(data)

if queue is full
return overflow
endif

rear ← rear + 1
queue[rear] ← data
return true

end procedure
Implementación de enqueue () en lenguaje de programación C -
Ejemplo
int enqueue(int data)
if(isfull())
return 0;

rear = rear + 1;
queue[rear] = data;

return 1;
end procedure

Operación en espera
El acceso a los datos desde la cola es un proceso de dos tareas: acceder a los
datos hacia donde apunta el frente y eliminar los datos después del
acceso. Se realizan los siguientes pasos para realizar la operación de retirada
de cola :
 Paso 1 : compruebe si la cola está vacía.
 Paso 2 : si la cola está vacía, produzca un error de flujo insuficiente y salga.
 Paso 3 : si la cola no está vacía, acceda a los datos donde apunta el frente .
 Paso 4 : incremente el puntero frontal para señalar el siguiente elemento de datos
disponible.
 Paso 5 - Devuelve el éxito.
Algoritmo para operar en cola
procedure dequeue

if queue is empty
return underflow
end if

data = queue[front]
front ← front + 1
return true

end procedure
Implementación de dequeue () en lenguaje de programación C -
Ejemplo
int dequeue() {
if(isempty())
return 0;

int data = queue[front];


front = front + 1;

return data;
}
Para un programa completo de Queue en lenguaje de programación C, haga
clic aquí .

Estructura de datos y algoritmos de


búsqueda lineal
La búsqueda lineal es un algoritmo de búsqueda muy simple. En este tipo de
búsqueda, se realiza una búsqueda secuencial de todos los elementos uno
por uno. Se verifica cada elemento y, si se encuentra una coincidencia, se
devuelve ese elemento en particular; de lo contrario, la búsqueda continúa
hasta el final de la recopilación de datos.

Algoritmo
Linear Search ( Array A, Value x)

Step 1: Set i to 1
Step 2: if i > n then go to step 7
Step 3: if A[i] = x then go to step 6
Step 4: Set i to i + 1
Step 5: Go to Step 2
Step 6: Print Element x Found at index i and go to step 8
Step 7: Print element not found
Step 8: Exit

Pseudocódigo
procedure linear_search (list, value)

for each item in the list


if match item == value
return the item's location
end if
end for

end procedure
Para conocer la implementación de búsqueda lineal en lenguaje de
programación C, haga clic aquí .

Estructura de datos y algoritmos de


búsqueda binaria
La búsqueda binaria es un algoritmo de búsqueda rápida con una complejidad
de tiempo de ejecución de Ο (log n). Este algoritmo de búsqueda funciona
según el principio de divide y vencerás. Para que este algoritmo funcione
correctamente, la recopilación de datos debe estar en forma ordenada.
La búsqueda binaria busca un elemento en particular comparando el elemento
más intermedio de la colección. Si se produce una coincidencia, se devuelve
el índice del elemento. Si el ítem del medio es mayor que el ítem, entonces el
ítem se busca en la sub-matriz a la izquierda del ítem del medio. De lo
contrario, el elemento se busca en la submatriz a la derecha del elemento
central. Este proceso también continúa en la submatriz hasta que el tamaño
de la submatriz se reduce a cero.

¿Cómo funciona la búsqueda binaria?


Para que funcione una búsqueda binaria, es obligatorio ordenar la matriz de
destino. Aprenderemos el proceso de búsqueda binaria con un ejemplo
pictórico. La siguiente es nuestra matriz ordenada y supongamos que
necesitamos buscar la ubicación del valor 31 usando la búsqueda binaria.

Primero, determinaremos la mitad de la matriz usando esta fórmula:


mid = low + (high - low) / 2
Aquí está, 0 + (9-0) / 2 = 4 (valor entero de 4.5). Entonces, 4 es el medio de la
matriz.

Ahora comparamos el valor almacenado en la ubicación 4, con el valor que se


busca, es decir, 31. Encontramos que el valor en la ubicación 4 es 27, lo que
no coincide. Como el valor es mayor que 27 y tenemos una matriz ordenada,
también sabemos que el valor objetivo debe estar en la parte superior de la
matriz.

Cambiamos nuestro bajo a medio + 1 y encontramos el nuevo valor medio


nuevamente.
low = mid + 1
mid = low + (high - low) / 2
Nuestro nuevo mid es 7 ahora. Comparamos el valor almacenado en la
ubicación 7 con nuestro valor objetivo 31.

El valor almacenado en la ubicación 7 no es una coincidencia, más bien es


más de lo que estamos buscando. Por lo tanto, el valor debe estar en la parte
inferior de esta ubicación.

Por lo tanto, calculamos el medio nuevamente. Esta vez son las 5.

Comparamos el valor almacenado en la ubicación 5 con nuestro valor


objetivo. Encontramos que es un partido.

Concluimos que el valor objetivo 31 se almacena en la ubicación 5.


La búsqueda binaria reduce a la mitad los elementos que se pueden buscar y,
por lo tanto, reduce el recuento de las comparaciones que se realizarán a muy
pocos números.

Pseudocódigo
El pseudocódigo de los algoritmos de búsqueda binaria debería verse así:
Procedure binary_search
A ← sorted array
n ← size of array
x ← value to be searched

Set lowerBound = 1
Set upperBound = n
while x not found
if upperBound < lowerBound
EXIT: x does not exists.

set midPoint = lowerBound + ( upperBound - lowerBound )


/ 2

if A[midPoint] < x
set lowerBound = midPoint + 1

if A[midPoint] > x
set upperBound = midPoint - 1

if A[midPoint] = x
EXIT: x found at location midPoint
end while

end procedure

Para saber acerca de la aplicación binaria búsqueda usando matriz en


lenguaje de programación C, por favor haga clic aquí .

Estructura de datos - Búsqueda de


interpolación
La búsqueda de interpolación es una variante mejorada de la búsqueda
binaria. Este algoritmo de búsqueda funciona en la posición de sondeo del
valor requerido. Para que este algoritmo funcione correctamente, la
recopilación de datos debe estar ordenada y distribuida equitativamente.
La búsqueda binaria tiene una gran ventaja de la complejidad del tiempo sobre
la búsqueda lineal. La búsqueda lineal tiene la complejidad de Ο (n) en el peor
de los casos, mientras que la búsqueda binaria tiene Ο (log n).
Hay casos en los que la ubicación de los datos objetivo puede conocerse de
antemano. Por ejemplo, en el caso de una guía telefónica, si queremos buscar
el número de teléfono de Morphius. Aquí, la búsqueda lineal e incluso la
búsqueda binaria parecerán lentas, ya que podemos saltar directamente al
espacio de memoria donde se almacenan los nombres que comienzan desde
'M'.

Posicionamiento en Búsqueda Binaria


En la búsqueda binaria, si no se encuentran los datos deseados, el resto de la
lista se divide en dos partes, inferior y superior. La búsqueda se lleva a cabo
en cualquiera de ellos.
Incluso cuando los datos están ordenados, la búsqueda binaria no aprovecha
para sondear la posición de los datos deseados.

Sondeo de posición en la búsqueda de interpolación


La búsqueda de interpolación encuentra un elemento particular calculando la
posición de la sonda. Inicialmente, la posición de la sonda es la posición del
elemento más intermedio de la colección.

Si se produce una coincidencia, se devuelve el índice del elemento. Para


dividir la lista en dos partes, utilizamos el siguiente método:
mid = Lo + ((Hi - Lo) / (A[Hi] - A[Lo])) * (X - A[Lo])

where −
A = list
Lo = Lowest index of the list
Hi = Highest index of the list
A[n] = Value stored at index n in the list
Si el ítem central es mayor que el ítem, entonces la posición de la sonda se
calcula nuevamente en la matriz secundaria a la derecha del ítem central. De
lo contrario, el elemento se busca en la submatriz a la izquierda del elemento
central. Este proceso también continúa en la submatriz hasta que el tamaño
de la submatriz se reduce a cero.
La complejidad del tiempo de ejecución del algoritmo de búsqueda de
interpolación es Ο (log (log n)) en comparación con Ο (log n) de BST en
situaciones favorables.
Algoritmo
Como es una improvisación del algoritmo BST existente, estamos
mencionando los pasos para buscar el índice de valor de datos 'objetivo',
utilizando el sondeo de posición:
Step 1 − Start searching data from middle of the list.
Step 2 − If it is a match, return the index of the item, and
exit.
Step 3 − If it is not a match, probe position.
Step 4 − Divide the list using probing formula and find the
new midle.
Step 5 − If data is greater than middle, search in higher
sub-list.
Step 6 − If data is smaller than middle, search in lower sub-
list.
Step 7 − Repeat until match.

Pseudocódigo
A → Array list
N → Size of A
X → Target Value

Procedure Interpolation_Search()

Set Lo → 0
Set Mid → -1
Set Hi → N-1

While X does not match

if Lo equals to Hi OR A[Lo] equals to A[Hi]


EXIT: Failure, Target not found
end if

Set Mid = Lo + ((Hi - Lo) / (A[Hi] - A[Lo])) * (X -


A[Lo])

if A[Mid] = X
EXIT: Success, Target found at Mid
else
if A[Mid] < X
Set Lo to Mid+1
else if A[Mid] > X
Set Hi to Mid-1
end if
end if
End While

End Procedure
Para conocer la implementación de la búsqueda de interpolación en lenguaje
de programación C, haga clic aquí .

Estructura de datos y algoritmos: tabla hash


Hash Table es una estructura de datos que almacena datos de forma
asociativa. En una tabla hash, los datos se almacenan en un formato de
matriz, donde cada valor de datos tiene su propio valor de índice único. El
acceso a los datos se vuelve muy rápido si conocemos el índice de los datos
deseados.
Por lo tanto, se convierte en una estructura de datos en la que las operaciones
de inserción y búsqueda son muy rápidas, independientemente del tamaño de
los datos. Hash Table utiliza una matriz como medio de almacenamiento y
utiliza la técnica de hash para generar un índice donde se insertará o se
ubicará un elemento.

Hashing
El hash es una técnica para convertir un rango de valores clave en un rango
de índices de una matriz. Vamos a utilizar el operador de módulo para obtener
un rango de valores clave. Considere un ejemplo de tabla hash de tamaño 20,
y los siguientes elementos deben almacenarse. Los artículos están en el
formato (clave, valor).

 (1,20)
 (2,70)
 (42,80)
 (4,25)
 (12,44)
 (14,32)
 (17,11)
 (13,78)
 (37,98)

No Señor. Llave Picadillo Índice de matriz

1 1 1% 20 = 1 1

2 2 2% 20 = 2 2

3 42 42% 20 = 2 2
44 44 4% 20 = 4 44

55 12 12% 20 = 12 12

66 14 14% 20 = 14 14

77 17 17% 20 = 17 17

8 13 13% 20 = 13 13

99 37 37% 20 = 17 17

Sondeo lineal
Como podemos ver, puede suceder que la técnica de hashing se use para
crear un índice ya usado de la matriz. En tal caso, podemos buscar la
siguiente ubicación vacía en la matriz mirando la siguiente celda hasta que
encontremos una celda vacía. Esta técnica se llama sondeo lineal.

No Índice de Después del sondeo lineal, índice


Llave Picadillo
Señor. matriz de matriz

1 1 1% 20 = 1 1 1

2 2 2% 20 = 2 2 2

3 42 42% 20 = 2 2 3

44 44 4% 20 = 4 44 44

55 12 12% 20 = 12 12 12

66 14 14% 20 = 14 14 14
77 17 17% 20 = 17 17 17

8 13 13% 20 = 13 13 13

99 37 37% 20 = 17 17 18 años

Operaciones básicas
Las siguientes son las operaciones primarias básicas de una tabla hash.
 Buscar : busca un elemento en una tabla hash.
 Insertar : inserta un elemento en una tabla hash.
 eliminar : elimina un elemento de una tabla hash.

DataItem
Defina un elemento de datos que tenga algunos datos y clave, en función de
los cuales se realizará la búsqueda en una tabla hash.
struct DataItem {
int data;
int key;
};

Método hash
Defina un método de hash para calcular el código de hash de la clave del
elemento de datos.
int hashCode(int key){
return key % SIZE;
}

Operación de búsqueda
Siempre que se busque un elemento, calcule el código hash de la clave
pasada y ubique el elemento usando ese código hash como índice en la
matriz. Utilice el sondeo lineal para adelantar el elemento si el elemento no se
encuentra en el código hash calculado.
Ejemplo
struct DataItem *search(int key) {
//get the hash
int hashIndex = hashCode(key);
//move in array until an empty
while(hashArray[hashIndex] != NULL) {

if(hashArray[hashIndex]->key == key)
return hashArray[hashIndex];

//go to next cell


++hashIndex;

//wrap around the table


hashIndex %= SIZE;
}

return NULL;
}

Insertar operación
Siempre que se inserte un elemento, calcule el código hash de la clave
pasada y ubique el índice utilizando ese código hash como índice en la
matriz. Utilice el sondeo lineal para la ubicación vacía, si se encuentra un
elemento en el código hash calculado.
Ejemplo
void insert(int key,int data) {
struct DataItem *item = (struct DataItem*)
malloc(sizeof(struct DataItem));
item->data = data;
item->key = key;

//get the hash


int hashIndex = hashCode(key);

//move in array until an empty or deleted cell


while(hashArray[hashIndex] != NULL &&
hashArray[hashIndex]->key != -1) {
//go to next cell
++hashIndex;

//wrap around the table


hashIndex %= SIZE;
}

hashArray[hashIndex] = item;
}

Eliminar operación
Siempre que deba eliminarse un elemento, calcule el código hash de la clave
pasada y ubique el índice utilizando ese código hash como índice en la
matriz. Utilice el sondeo lineal para adelantar el elemento si no se encuentra
un elemento en el código hash calculado. Cuando lo encuentre, guarde un
elemento ficticio allí para mantener intacto el rendimiento de la tabla hash.
Ejemplo
struct DataItem* delete(struct DataItem* item) {
int key = item->key;

//get the hash


int hashIndex = hashCode(key);

//move in array until an empty


while(hashArray[hashIndex] !=NULL) {

if(hashArray[hashIndex]->key == key) {
struct DataItem* temp = hashArray[hashIndex];

//assign a dummy item at deleted position


hashArray[hashIndex] = dummyItem;
return temp;
}

//go to next cell


++hashIndex;

//wrap around the table


hashIndex %= SIZE;
}

return NULL;
}

Para saber acerca de la implementación de hash en lenguaje de programación


C, haga clic aquí .

Estructura de datos - Técnicas de


clasificación
La clasificación se refiere a la organización de datos en un formato
particular. El algoritmo de clasificación especifica la forma de organizar los
datos en un orden particular. Los pedidos más comunes están en orden
numérico o lexicográfico.
La importancia de la clasificación radica en el hecho de que la búsqueda de
datos se puede optimizar a un nivel muy alto, si los datos se almacenan de
manera ordenada. La clasificación también se utiliza para representar datos en
formatos más legibles. Los siguientes son algunos ejemplos de clasificación
en escenarios de la vida real:
 Directorio telefónico: el directorio telefónico almacena los números de teléfono de
las personas clasificadas por sus nombres, de modo que los nombres se pueden
buscar fácilmente.
 Diccionario : el diccionario almacena las palabras en orden alfabético para que la
búsqueda de cualquier palabra sea fácil.
Ordenación in situ y ordenación no in situ
Los algoritmos de clasificación pueden requerir un espacio adicional para la
comparación y el almacenamiento temporal de pocos elementos de
datos. Estos algoritmos no requieren espacio adicional y se dice que la
ordenación ocurre en el lugar o, por ejemplo, dentro de la matriz misma. Esto
se llama clasificación en el lugar . La clasificación de burbujas es un ejemplo
de clasificación in situ.
Sin embargo, en algunos algoritmos de clasificación, el programa requiere un
espacio que es mayor o igual que los elementos que se ordenan. La
ordenación que utiliza igual o más espacio se denomina ordenación no en el
lugar . Merge-sort es un ejemplo de ordenación no in situ.

Clasificación estable y no estable


Si un algoritmo de ordenación, después de ordenar los contenidos, no cambia
la secuencia de contenido similar en el que aparecen, se llama ordenación
estable .

Si un algoritmo de ordenación, después de ordenar los contenidos, cambia la


secuencia de contenido similar en el que aparecen, se llama ordenación
inestable .

La estabilidad de un algoritmo es importante cuando deseamos mantener la


secuencia de elementos originales, como en una tupla, por ejemplo.
Algoritmo de clasificación adaptativo y no adaptativo
Se dice que un algoritmo de ordenación es adaptativo, si aprovecha los
elementos ya "ordenados" en la lista que se va a ordenar. Es decir, al ordenar
si la lista fuente tiene algún elemento ya ordenado, los algoritmos adaptativos
lo tendrán en cuenta e intentarán no reordenarlos.
Un algoritmo no adaptativo es aquel que no tiene en cuenta los elementos que
ya están ordenados. Intentan forzar que cada elemento sea reordenado para
confirmar su clasificación.

Términos importantes
Algunos términos generalmente se acuñan al discutir técnicas de clasificación,
aquí hay una breve introducción a ellos:
Orden creciente
Se dice que una secuencia de valores está en orden creciente , si el
elemento sucesivo es mayor que el anterior. Por ejemplo, 1, 3, 4, 6, 8, 9 están
en orden creciente, ya que cada elemento siguiente es mayor que el elemento
anterior.
Orden decreciente
Se dice que una secuencia de valores está en orden decreciente , si el
elemento sucesivo es menor que el actual. Por ejemplo, 9, 8, 6, 4, 3, 1 están
en orden decreciente, ya que cada elemento siguiente es menor que el
elemento anterior.
Orden no creciente
Se dice que una secuencia de valores está en orden no creciente , si el
elemento sucesivo es menor o igual que su elemento anterior en la
secuencia. Este orden ocurre cuando la secuencia contiene valores
duplicados. Por ejemplo, 9, 8, 6, 3, 3, 1 están en orden no creciente, ya que
cada elemento siguiente es menor o igual que (en el caso de 3) pero no mayor
que cualquier elemento anterior.
Orden no decreciente
Se dice que una secuencia de valores está en orden no decreciente , si el
elemento sucesivo es mayor o igual que su elemento anterior en la
secuencia. Este orden ocurre cuando la secuencia contiene valores
duplicados. Por ejemplo, 1, 3, 3, 6, 8, 9 están en orden no decreciente, ya que
cada elemento siguiente es mayor o igual que (en el caso de 3) pero no menor
que el anterior.

Estructura de datos - Algoritmo de


clasificación de burbujas
La clasificación de burbujas es un algoritmo de clasificación simple. Este
algoritmo de clasificación es un algoritmo basado en la comparación en el que
se compara cada par de elementos adyacentes y los elementos se
intercambian si no están en orden. Este algoritmo no es adecuado para
grandes conjuntos de datos, ya que su complejidad promedio y en el peor de
los casos es de Ο (n 2 ) donde n es el número de elementos.

¿Cómo funciona Bubble Sort?


Tomamos una matriz sin clasificar para nuestro ejemplo. El ordenamiento de
burbujas lleva Ο (n 2 ) tiempo, por lo que lo mantenemos breve y preciso.

La clasificación de burbujas comienza con los primeros dos elementos,


comparándolos para verificar cuál es mayor.

En este caso, el valor 33 es mayor que 14, por lo que ya está en ubicaciones
ordenadas. A continuación, comparamos 33 con 27.

Encontramos que 27 es menor que 33 y estos dos valores deben


intercambiarse.

La nueva matriz debería verse así:

Luego comparamos 33 y 35. Encontramos que ambos están en posiciones ya


ordenadas.

Luego pasamos a los siguientes dos valores, 35 y 10.


Sabemos que 10 es menor 35. Por lo tanto, no están ordenados.

Intercambiamos estos valores. Encontramos que hemos llegado al final de la


matriz. Después de una iteración, la matriz debería verse así:

Para ser precisos, ahora mostramos cómo debería verse una matriz después
de cada iteración. Después de la segunda iteración, debería verse así:

Observe que después de cada iteración, al menos un valor se mueve al final.

Y cuando no se requiere un intercambio, el tipo burbuja descubre que una


matriz está completamente ordenada.

Ahora deberíamos analizar algunos aspectos prácticos del ordenamiento de


burbujas.

Algoritmo
Suponemos que list es una matriz de n elementos. Además, suponemos que
la función de intercambio intercambia los valores de los elementos de la
matriz dados.
begin BubbleSort(list)

for all elements of list


if list[i] > list[i+1]
swap(list[i], list[i+1])
end if
end for
return list

end BubbleSort

Pseudocódigo
Observamos en el algoritmo que Bubble Sort compara cada par de elementos
de la matriz a menos que toda la matriz esté completamente ordenada en
orden ascendente. Esto puede causar algunos problemas de complejidad,
como si la matriz no necesita más intercambio ya que todos los elementos ya
están ascendiendo.
Para solucionar el problema, utilizamos una variable de
bandera intercambiada que nos ayudará a ver si ha ocurrido o no un
intercambio. Si no se ha producido un intercambio, es decir, la matriz no
requiere más procesamiento para clasificarse, saldrá del bucle.
El pseudocódigo del algoritmo BubbleSort se puede escribir de la siguiente
manera:
procedure bubbleSort( list : array of items )

loop = list.count;

for i = 0 to loop-1 do:


swapped = false

for j = 0 to loop-1 do:

/* compare the adjacent elements */


if list[j] > list[j+1] then
/* swap them */
swap( list[j], list[j+1] )
swapped = true
end if

end for

/*if no number was swapped that means


array is sorted now, break the loop.*/

if(not swapped) then


break
end if

end for

end procedure return list

Implementación
Un problema más que no abordamos en nuestro algoritmo original y su
pseudocódigo improvisado es que, después de cada iteración, los valores más
altos se establecen al final de la matriz. Por lo tanto, la siguiente iteración no
necesita incluir elementos ya ordenados. Para este propósito, en nuestra
implementación, restringimos el ciclo interno para evitar valores ya ordenados.
Para saber acerca de la implementación de clasificación de burbujas en
lenguaje de programación C, haga clic aquí .

Estructura de datos y clasificación de


inserción de algoritmos
Este es un algoritmo de clasificación basado en comparación in situ. Aquí, se
mantiene una sublista que siempre se ordena. Por ejemplo, la parte inferior de
una matriz se mantiene ordenada. Un elemento que debe 'insertarse' en esta
sublista ordenada tiene que encontrar su lugar apropiado y luego debe
insertarse allí. De ahí el nombre, tipo de inserción .
La matriz se busca secuencialmente y los elementos sin clasificar se mueven
e insertan en la sublista ordenada (en la misma matriz). Este algoritmo no es
adecuado para grandes conjuntos de datos, ya que su complejidad promedio y
en el peor de los casos es de Ο (n 2 ), donde n es el número de elementos.

¿Cómo funciona la ordenación por inserción?


Tomamos una matriz sin clasificar para nuestro ejemplo.

La ordenación por inserción compara los dos primeros elementos.

Se encuentra que tanto 14 como 33 ya están en orden ascendente. Por ahora,


14 está en una sublista ordenada.

La ordenación por inserción avanza y compara 33 con 27.

Y descubre que 33 no está en la posición correcta.


Intercambia 33 con 27. También verifica con todos los elementos de la sublista
ordenada. Aquí vemos que la sublista ordenada tiene solo un elemento 14, y
27 es mayor que 14. Por lo tanto, la sublista ordenada permanece ordenada
después del intercambio.

Por ahora tenemos 14 y 27 en la sublista ordenada. A continuación, compara


33 con 10.

Estos valores no están en un orden ordenado.

Entonces los intercambiamos.

Sin embargo, el intercambio hace que 27 y 10 estén sin clasificar.

Por lo tanto, los intercambiamos también.

Nuevamente encontramos 14 y 10 en un orden sin clasificar.

Los cambiamos de nuevo. Al final de la tercera iteración, tenemos una sublista


ordenada de 4 elementos.
Este proceso continúa hasta que todos los valores no clasificados estén
cubiertos en una sublista ordenada. Ahora veremos algunos aspectos de
programación del tipo de inserción.
Algoritmo
Ahora tenemos una visión más amplia de cómo funciona esta técnica de
clasificación, por lo que podemos derivar pasos simples mediante los cuales
podemos lograr la clasificación por inserción.
Step 1 − If it is the first element, it is already sorted.
return 1;
Step 2 − Pick next element
Step 3 − Compare with all elements in the sorted sub-list
Step 4 − Shift all the elements in the sorted sub-list that
is greater than the
value to be sorted
Step 5 − Insert the value
Step 6 − Repeat until list is sorted

Pseudocódigo
procedure insertionSort( A : array of items )
int holePosition
int valueToInsert

for i = 1 to length(A) inclusive do:

/* select value to be inserted */


valueToInsert = A[i]
holePosition = i

/*locate hole position for the element to be inserted


*/

while holePosition > 0 and A[holePosition-1] >


valueToInsert do:
A[holePosition] = A[holePosition-1]
holePosition = holePosition -1
end while

/* insert the number at hole position */


A[holePosition] = valueToInsert

end for

end procedure
Para saber acerca de la implementación de clasificación de inserción en
lenguaje de programación C, haga clic aquí .

Estructura de datos y selección de algoritmos


La selección de clasificación es un algoritmo de clasificación simple. Este
algoritmo de clasificación es un algoritmo basado en comparación in situ en el
que la lista se divide en dos partes, la parte ordenada en el extremo izquierdo
y la parte no ordenada en el extremo derecho. Inicialmente, la parte ordenada
está vacía y la parte no ordenada es la lista completa.
El elemento más pequeño se selecciona de la matriz no ordenada y se
intercambia con el elemento más a la izquierda, y ese elemento se convierte
en parte de la matriz ordenada. Este proceso continúa moviendo el límite de la
matriz sin clasificar por un elemento a la derecha.
Este algoritmo no es adecuado para grandes conjuntos de datos ya que sus
complejidades promedio y en el peor de los casos son de Ο (n 2 ), donde n es
el número de elementos.

¿Cómo funciona la selección de selección?


Considere la siguiente matriz representada como un ejemplo.

Para la primera posición en la lista ordenada, toda la lista se escanea


secuencialmente. En la primera posición donde se almacena 14 actualmente,
buscamos en toda la lista y encontramos que 10 es el valor más bajo.

Entonces reemplazamos 14 por 10. Después de una iteración 10, que resulta
ser el valor mínimo en la lista, aparece en la primera posición de la lista
ordenada.

Para la segunda posición, donde reside 33, comenzamos a escanear el resto


de la lista de manera lineal.

Encontramos que 14 es el segundo valor más bajo en la lista y debería


aparecer en el segundo lugar. Intercambiamos estos valores.
Después de dos iteraciones, se colocan dos valores mínimos al principio de
forma ordenada.

El mismo proceso se aplica al resto de los elementos de la matriz.


A continuación se muestra una representación pictórica de todo el proceso de
clasificación:
Ahora, aprendamos algunos aspectos de programación del tipo de selección.
Algoritmo
Step 1 − Set MIN to location 0
Step 2 − Search the minimum element in the list
Step 3 − Swap with value at location MIN
Step 4 − Increment MIN to point to next element
Step 5 − Repeat until list is sorted

Pseudocódigo
procedure selection sort
list : array of items
n : size of list

for i = 1 to n - 1
/* set current element as minimum*/
min = i

/* check the element to be minimum */

for j = i+1 to n
if list[j] < list[min] then
min = j;
end if
end for

/* swap the minimum element with the current element*/


if indexMin != i then
swap list[min] and list[i]
end if
end for

end procedure
Para saber acerca de la implementación de selección de selección en lenguaje
de programación C, haga clic aquí .

Estructuras de datos - Algoritmo de


clasificación de fusión
La combinación de clasificación es una técnica de clasificación basada en la
técnica de dividir y conquistar. Dado que la complejidad de tiempo en el peor
de los casos es Ο (n log n), es uno de los algoritmos más respetados.
La ordenación por fusión primero divide la matriz en mitades iguales y luego
las combina de manera ordenada.

¿Cómo funciona la ordenación por fusión?


Para comprender el tipo de fusión, tomamos una matriz sin clasificar como la
siguiente:
Sabemos que la ordenación por fusión primero divide toda la matriz
iterativamente en mitades iguales a menos que se logren los valores
atómicos. Vemos aquí que una matriz de 8 elementos se divide en dos
matrices de tamaño 4.

Esto no cambia la secuencia de aparición de elementos en el original. Ahora


dividimos estas dos matrices en mitades.

Dividimos aún más estas matrices y logramos un valor atómico que ya no se


puede dividir.

Ahora, los combinamos exactamente de la misma manera que se


desglosaron. Tenga en cuenta los códigos de colores dados a estas listas.
Primero comparamos el elemento para cada lista y luego los combinamos en
otra lista de manera ordenada. Vemos que 14 y 33 están en posiciones
ordenadas. Comparamos 27 y 10 y en la lista objetivo de 2 valores ponemos
10 primero, seguido de 27. Cambiamos el orden de 19 y 35 mientras que 42 y
44 se colocan secuencialmente.

En la próxima iteración de la fase de combinación, comparamos listas de dos


valores de datos, y los fusionamos en una lista de valores de datos
encontrados, colocando todos en un orden ordenado.

Después de la fusión final, la lista debería verse así:


Ahora deberíamos aprender algunos aspectos de programación de la
ordenación por fusión.
Algoritmo
La ordenación por fusión continúa dividiendo la lista en mitades iguales hasta
que ya no se puede dividir. Por definición, si es solo un elemento en la lista,
está ordenado. Luego, la opción de combinación combina las listas ordenadas
más pequeñas manteniendo la nueva lista ordenada también.
Step 1 − if it is only one element in the list it is already
sorted, return.
Step 2 − divide the list recursively into two halves until it
can no more be divided.
Step 3 − merge the smaller lists into new list in sorted
order.

Pseudocódigo
Ahora veremos los pseudocódigos para las funciones de clasificación de
fusión. Como nuestros algoritmos señalan dos funciones principales: dividir y
fusionar.
La ordenación por fusión funciona con recursividad y veremos nuestra
implementación de la misma manera.
procedure mergesort( var a as array )
if ( n == 1 ) return a

var l1 as array = a[0] ... a[n/2]


var l2 as array = a[n/2+1] ... a[n]

l1 = mergesort( l1 )
l2 = mergesort( l2 )

return merge( l1, l2 )


end procedure

procedure merge( var a as array, var b as array )

var c as array
while ( a and b have elements )
if ( a[0] > b[0] )
add b[0] to the end of c
remove b[0] from b
else
add a[0] to the end of c
remove a[0] from a
end if
end while

while ( a has elements )


add a[0] to the end of c
remove a[0] from a
end while
while ( b has elements )
add b[0] to the end of c
remove b[0] from b
end while

return c

end procedure

Para saber acerca de la implementación de clasificación de fusión en lenguaje


de programación C, haga clic aquí .
Shell sort es un algoritmo de clasificación altamente eficiente y se basa en un
algoritmo de clasificación por inserción. Este algoritmo evita grandes cambios
como en el caso de la ordenación por inserción, si el valor más pequeño está
en el extremo derecho y tiene que moverse hacia el extremo izquierdo.
Este algoritmo utiliza la ordenación por inserción en elementos muy
extendidos, primero para ordenarlos y luego ordena los elementos menos
espaciados. Este espacio se denomina intervalo . Este intervalo se calcula en
función de la fórmula de Knuth como:
Fórmula de Knuth
h = h * 3 + 1
where −
h is interval with initial value 1
Este algoritmo es bastante eficiente para conjuntos de datos de tamaño
mediano ya que su complejidad promedio y en el peor de los casos es de Ο
(n), donde n es el número de elementos.

¿Cómo funciona Shell Sort?


Consideremos el siguiente ejemplo para tener una idea de cómo funciona la
ordenación de shell. Tomamos la misma matriz que hemos usado en nuestros
ejemplos anteriores. Para nuestro ejemplo y facilidad de comprensión,
tomamos el intervalo de 4. Haga una sublista virtual de todos los valores
ubicados en el intervalo de 4 posiciones. Aquí estos valores son {35, 14}, {33,
19}, {42, 27} y {10, 44}
Comparamos valores en cada sublista y los intercambiamos (si es necesario)
en la matriz original. Después de este paso, la nueva matriz debería verse así:

Luego, tomamos el intervalo de 2 y esta brecha genera dos sublistas: {14, 27,
35, 42}, {19, 10, 33, 44}

Comparamos e intercambiamos los valores, si es necesario, en la matriz


original. Después de este paso, la matriz debería verse así:

Finalmente, clasificamos el resto de la matriz usando el intervalo de valor 1. La


ordenación de shell usa la ordenación por inserción para ordenar la matriz.
A continuación se muestra la descripción paso a paso:
Vemos que solo se requieren cuatro intercambios para ordenar el resto de la
matriz.
Algoritmo
El siguiente es el algoritmo para la ordenación de shell.
Step 1 − Initialize the value of h
Step 2 − Divide the list into smaller sub-list of equal
interval h
Step 3 − Sort these sub-lists using insertion sort
Step 3 − Repeat until complete list is sorted
Pseudocódigo
El siguiente es el pseudocódigo para la ordenación de shell.
procedure shellSort()
A : array of items

/* calculate interval*/
while interval < A.length /3 do:
interval = interval * 3 + 1
end while

while interval > 0 do:

for outer = interval; outer < A.length; outer ++ do:

/* select value to be inserted */


valueToInsert = A[outer]
inner = outer;

/*shift element towards right*/


while inner > interval -1 && A[inner - interval] >=
valueToInsert do:
A[inner] = A[inner - interval]
inner = inner - interval
end while

/* insert the number at hole position */


A[inner] = valueToInsert

end for

/* calculate interval*/
interval = (interval -1) /3;

end while

end procedure
Para saber sobre la implementación de ordenación de shell en lenguaje de
programación C, haga clic aquí .

Estructura de datos y algoritmos:


clasificación de shell
Shell sort es un algoritmo de clasificación altamente eficiente y se basa en un
algoritmo de clasificación por inserción. Este algoritmo evita grandes cambios
como en el caso de la ordenación por inserción, si el valor más pequeño está
en el extremo derecho y tiene que moverse hacia el extremo izquierdo.
Este algoritmo utiliza la ordenación por inserción en elementos muy
extendidos, primero para ordenarlos y luego ordena los elementos menos
espaciados. Este espacio se denomina intervalo . Este intervalo se calcula en
función de la fórmula de Knuth como:
Fórmula de Knuth
h = h * 3 + 1
where −
h is interval with initial value 1
Este algoritmo es bastante eficiente para conjuntos de datos de tamaño
mediano, ya que su complejidad promedio y el peor de los casos de este
algoritmo depende de la secuencia de brecha, la más conocida es Ο (n),
donde n es el número de elementos. Y la complejidad del espacio en el peor
de los casos es O (n).

¿Cómo funciona Shell Sort?


Consideremos el siguiente ejemplo para tener una idea de cómo funciona la
ordenación de shell. Tomamos la misma matriz que hemos usado en nuestros
ejemplos anteriores. Para nuestro ejemplo y facilidad de comprensión,
tomamos el intervalo de 4. Haga una sublista virtual de todos los valores
ubicados en el intervalo de 4 posiciones. Aquí estos valores son {35, 14}, {33,
19}, {42, 27} y {10, 44}

Comparamos valores en cada sublista y los intercambiamos (si es necesario)


en la matriz original. Después de este paso, la nueva matriz debería verse así:

Luego, tomamos el intervalo de 1 y esta brecha genera dos sublistas: {14, 27,
35, 42}, {19, 10, 33, 44}
Comparamos e intercambiamos los valores, si es necesario, en la matriz
original. Después de este paso, la matriz debería verse así:

Finalmente, clasificamos el resto de la matriz usando el intervalo de valor 1. La


ordenación de shell usa la ordenación por inserción para ordenar la matriz.
A continuación se muestra la descripción paso a paso:
Vemos que solo se requieren cuatro intercambios para ordenar el resto de la
matriz.
Algoritmo
El siguiente es el algoritmo para la ordenación de shell.
Step 1 − Initialize the value of h
Step 2 − Divide the list into smaller sub-list of equal
interval h
Step 3 − Sort these sub-lists using insertion sort
Step 3 − Repeat until complete list is sorted
Pseudocódigo
El siguiente es el pseudocódigo para la ordenación de shell.
procedure shellSort()
A : array of items

/* calculate interval*/
while interval < A.length /3 do:
interval = interval * 3 + 1
end while

while interval > 0 do:

for outer = interval; outer < A.length; outer ++ do:

/* select value to be inserted */


valueToInsert = A[outer]
inner = outer;

/*shift element towards right*/


while inner > interval -1 && A[inner - interval] >=
valueToInsert do:
A[inner] = A[inner - interval]
inner = inner - interval
end while

/* insert the number at hole position */


A[inner] = valueToInsert

end for

/* calculate interval*/
interval = (interval -1) /3;

end while

end procedure
Para saber sobre la implementación de ordenación de shell en lenguaje de
programación C, haga clic aquí .

Estructura de datos y algoritmos: ordenación


rápida
La clasificación rápida es un algoritmo de clasificación altamente eficiente y se
basa en la partición de una matriz de datos en matrices más pequeñas. Una
matriz grande se divide en dos matrices, una de las cuales contiene valores
más pequeños que el valor especificado, por ejemplo, pivote, en función de la
cual se realiza la partición y otra matriz contiene valores mayores que el valor
pivote.
Quicksort divide una matriz y luego se llama recursivamente dos veces para
ordenar las dos submatrices resultantes. Este algoritmo es bastante eficiente
para conjuntos de datos de gran tamaño ya que su complejidad promedio y en
el peor de los casos es O (nLogn) e image.png (n 2 ), respectivamente.

Partición en ordenación rápida


La siguiente representación animada explica cómo encontrar el valor de pivote
en una matriz.

El valor de pivote divide la lista en dos partes. Y de manera recursiva,


encontramos el pivote para cada sublista hasta que todas las listas contengan
solo un elemento.

Algoritmo de pivote de clasificación rápida


Según nuestra comprensión de la partición en forma rápida, ahora
intentaremos escribir un algoritmo para ello, que es el siguiente.
Step 1 − Choose the highest index value has pivot
Step 2 − Take two variables to point left and right of the
list excluding pivot
Step 3 − left points to the low index
Step 4 − right points to the high
Step 5 − while value at left is less than pivot move right
Step 6 − while value at right is greater than pivot move left
Step 7 − if both step 5 and step 6 does not match swap left
and right
Step 8 − if left ≥ right, the point where they met is new
pivot

Pseudocódigo de clasificación rápida


El pseudocódigo para el algoritmo anterior se puede derivar como -
function partitionFunc(left, right, pivot)
leftPointer = left
rightPointer = right - 1
while True do
while A[++leftPointer] < pivot do
//do-nothing
end while

while rightPointer > 0 && A[--rightPointer] > pivot do


//do-nothing
end while

if leftPointer >= rightPointer


break
else
swap leftPointer,rightPointer
end if

end while

swap leftPointer,right
return leftPointer

end function

Algoritmo de clasificación rápida


Usando el algoritmo pivote recursivamente, terminamos con particiones
posibles más pequeñas. Cada partición se procesa para una ordenación
rápida. Definimos algoritmo recursivo para quicksort de la siguiente manera:
Step 1 − Make the right-most index value pivot
Step 2 − partition the array using pivot value
Step 3 − quicksort left partition recursively
Step 4 − quicksort right partition recursively

Seudocódigo de clasificación rápida


Para obtener más información, veamos el pseudocódigo para el algoritmo de
ordenación rápida:
procedure quickSort(left, right)

if right-left <= 0
return
else
pivot = A[right]
partition = partitionFunc(left, right, pivot)
quickSort(left,partition-1)
quickSort(partition+1,right)
end if

end procedure
Para saber acerca de la implementación de ordenación rápida en lenguaje de
programación C, haga clic aquí .

Estructura de datos - Estructura de datos del


gráfico
Un gráfico es una representación gráfica de un conjunto de objetos donde
algunos pares de objetos están conectados por enlaces. Los objetos
interconectados están representados por puntos denominados vértices , y los
enlaces que conectan los vértices se llaman bordes .
Formalmente, un gráfico es un par de conjuntos (V, E) , donde V es el
conjunto de vértices y E es el conjunto de aristas, que conecta los pares de
vértices. Eche un vistazo al siguiente gráfico:

En el gráfico anterior,
V = {a, b, c, d, e}
E = {ab, ac, bd, cd, de}

Estructura de datos del gráfico


Los gráficos matemáticos se pueden representar en la estructura de
datos. Podemos representar un gráfico usando una matriz de vértices y una
matriz bidimensional de bordes. Antes de continuar, familiarizémonos con
algunos términos importantes:
 Vértice : cada nodo del gráfico se representa como un vértice. En el siguiente
ejemplo, el círculo etiquetado representa vértices. Por lo tanto, A a G son
vértices. Podemos representarlos usando una matriz como se muestra en la
siguiente imagen. Aquí A puede identificarse por el índice 0. B puede identificarse
usando el índice 1 y así sucesivamente.
 Borde : el borde representa una ruta entre dos vértices o una línea entre dos
vértices. En el siguiente ejemplo, las líneas de A a B, de B a C, etc., representan
aristas. Podemos usar una matriz bidimensional para representar una matriz
como se muestra en la siguiente imagen. Aquí AB puede representarse como 1
en la fila 0, columna 1, BC como 1 en la fila 1, columna 2, etc., manteniendo otras
combinaciones como 0.
 Adyacencia : dos nodos o vértices son adyacentes si están conectados entre sí a
través de un borde. En el siguiente ejemplo, B es adyacente a A, C es adyacente
a B, y así sucesivamente.
 Trayectoria : la ruta representa una secuencia de aristas entre los dos
vértices. En el siguiente ejemplo, ABCD representa una ruta de A a D.

Operaciones básicas
Las siguientes son operaciones primarias básicas de un gráfico:
 Agregar vértice : agrega un vértice al gráfico.
 Agregar borde : agrega un borde entre los dos vértices del gráfico.
 Mostrar vértice : muestra un vértice del gráfico.

Para saber más sobre Graph, lea Tutorial de teoría de


gráficos . Aprenderemos sobre atravesar un gráfico en los próximos capítulos.

Estructura de datos: primer recorrido


transversal de profundidad
El algoritmo de primera búsqueda de profundidad (DFS) atraviesa un gráfico
en un movimiento de profundidad y utiliza una pila para recordar obtener el
siguiente vértice para iniciar una búsqueda, cuando se produce un punto
muerto en cualquier iteración.
Como en el ejemplo dado anteriormente, el algoritmo DFS atraviesa primero
de S a A a D a G a E a B, luego a F y finalmente a C. Emplea las siguientes
reglas.
 Regla 1 - Visita el vértice adyacente no visitado. Marcarlo como
visitado. Muéstralo. Empújalo en una pila.
 Regla 2 : si no se encuentra ningún vértice adyacente, abre un vértice de la
pila. (Aparecerán todos los vértices de la pila, que no tienen vértices adyacentes).
 Regla 3 - Repita la Regla 1 y la Regla 2 hasta que la pila esté vacía.

Paso El recorrido Descripción

Inicializa la pila.
2
Marque S como visitado y póngalo
en la pila. Explora cualquier nodo
adyacente unvisited
de S . Tenemos tres nodos y
podemos elegir cualquiera de
ellos. Para este ejemplo,
tomaremos el nodo en orden
alfabético.

Marque A como visitado y póngalo


en la pila. Explore cualquier nodo
adyacente no visitado desde A.
Tanto S como D son adyacentes
a A, pero solo nos interesan los
nodos no visitados.

44

Visite D y márquelo como visitado


y póngalo en la pila. Aquí,
tenemos nodos B y C , que son
adyacentes a D y ambos no están
visitados. Sin embargo, elegiremos
nuevamente en orden alfabético.

55

Elegimos B , lo marcamos como


visitado y lo colocamos en la
pila. Aquí B no tiene ningún nodo
adyacente no visitado. Entonces,
sacamos B de la pila.
66

Verificamos la parte superior de la


pila para regresar al nodo anterior
y verificamos si tiene nodos no
visitados. Aquí, encontramos
que D está en la parte superior de
la pila.

77

Solo el nodo adyacente no visitado


es de D es C ahora. Entonces
visitamos C , lo marcamos como
visitado y lo colocamos en la pila.

Como C no tiene ningún nodo adyacente no visitado, seguimos haciendo


estallar la pila hasta encontrar un nodo que tenga un nodo adyacente no
visitado. En este caso, no hay ninguno y seguimos apareciendo hasta que la
pila esté vacía.
Para conocer la implementación de este algoritmo en lenguaje de
programación C, haga clic aquí .

Estructura de datos: primer recorrido


transversal
El algoritmo Breadth First Search (BFS) atraviesa un gráfico en un movimiento
de amplitud y utiliza una cola para recordar obtener el siguiente vértice para
iniciar una búsqueda, cuando se produce un callejón sin salida en cualquier
iteración.
Como en el ejemplo dado anteriormente, el algoritmo BFS atraviesa de A a B
a E a F primero y luego a C y G por último a D. Emplea las siguientes reglas.
 Regla 1 - Visita el vértice adyacente no visitado. Marcarlo como
visitado. Muéstralo. Insertarlo en una cola.
 Regla 2 : si no se encuentra ningún vértice adyacente, elimine el primer vértice de
la cola.
 Regla 3 - Repita la Regla 1 y la Regla 2 hasta que la cola esté vacía.

Paso El recorrido Descripción

Inicializa la cola.
2

Comenzamos por visitar S (nodo


de inicio) y lo marcamos como
visitado.

Vemos entonces un nodo


adyacente unvisited de S . En este
ejemplo, tenemos tres nodos pero
alfabéticamente elegimos A , lo
marcamos como visitado y lo
ponemos en cola.

44

A continuación, el nodo adyacente


unvisited de S es B . Lo marcamos
como visitado y lo ponemos en
cola.

55

A continuación, el nodo adyacente


unvisited de S es C . Lo marcamos
como visitado y lo ponemos en
cola.
66

Ahora, S queda sin nodos


adyacentes no visitados. Así, nos
encontramos quitar de la cola y A .

77

De A tenemos D como nodo


adyacente no visitado. Lo
marcamos como visitado y lo
ponemos en cola.

En esta etapa, nos quedamos sin nodos no marcados (no visitados). Pero
según el algoritmo, seguimos disminuyendo para obtener todos los nodos no
visitados. Cuando la cola se vacía, el programa termina.
La implementación de este algoritmo en lenguaje de programación C se
puede ver aquí .

Estructura de datos y algoritmos: árbol


El árbol representa los nodos conectados por bordes. Discutiremos el árbol
binario o el árbol de búsqueda binario específicamente.
Binary Tree es una estructura de datos especial que se utiliza para el
almacenamiento de datos. Un árbol binario tiene una condición especial de
que cada nodo puede tener un máximo de dos hijos. Un árbol binario tiene los
beneficios de una matriz ordenada y una lista vinculada, ya que la búsqueda
es tan rápida como en una matriz ordenada y la operación de inserción o
eliminación es tan rápida como en una lista vinculada.
Términos importantes
Los siguientes son los términos importantes con respecto al árbol.
 Ruta : la ruta se refiere a la secuencia de nodos a lo largo de los bordes de un
árbol.
 Raíz : el nodo en la parte superior del árbol se llama raíz. Solo hay una raíz por
árbol y una ruta desde el nodo raíz a cualquier nodo.
 Padre : cualquier nodo, excepto el nodo raíz, tiene un borde hacia arriba a un
nodo llamado padre.
 Hijo : el nodo debajo de un nodo dado conectado por su borde hacia abajo se
llama nodo hijo.
 Hoja : el nodo que no tiene ningún nodo hijo se llama nodo hoja.
 Subárbol : el subárbol representa los descendientes de un nodo.
 Visitas : las visitas se refieren a verificar el valor de un nodo cuando el control
está en el nodo.
 Recorrido : atravesar significa pasar a través de los nodos en un orden
específico.
 Niveles : el nivel de un nodo representa la generación de un nodo. Si el nodo raíz
está en el nivel 0, su siguiente nodo hijo está en el nivel 1, su nieto está en el nivel
2, y así sucesivamente.
 claves : la clave representa un valor de un nodo en función del cual se realizará
una operación de búsqueda para un nodo.

Representación de árbol de búsqueda binaria


El árbol de búsqueda binaria exhibe un comportamiento especial. El elemento
secundario izquierdo de un nodo debe tener un valor menor que el valor
primario y el elemento secundario derecho del nodo debe tener un valor mayor
que su valor primario.

Vamos a implementar el árbol usando un objeto de nodo y conectándolos a


través de referencias.

Nodo del árbol


El código para escribir un nodo de árbol sería similar al que se proporciona a
continuación. Tiene una parte de datos y referencias a sus nodos secundarios
izquierdo y derecho.
struct node {
int data;
struct node *leftChild;
struct node *rightChild;
};
En un árbol, todos los nodos comparten una construcción común.

Operaciones BST Básicas


Las operaciones básicas que se pueden realizar en una estructura de datos de
árbol de búsqueda binaria son las siguientes:
 Insertar : inserta un elemento en un árbol / crea un árbol.
 Buscar : busca un elemento en un árbol.
 Preorden Transversal - Traverses un árbol de una manera pre-orden.
 Recorrido en orden: atraviesa un árbol de forma ordenada.
 Recorrido posterior al pedido: atraviesa un árbol de forma posterior.
Aprenderemos a crear (insertar) una estructura de árbol y buscar un elemento
de datos en un árbol en este capítulo. Aprenderemos sobre los métodos de
recorrido de árboles en el próximo capítulo.

Insertar operación
La primera inserción crea el árbol. Luego, cada vez que se inserte un
elemento, primero ubique su ubicación correcta. Comience a buscar desde el
nodo raíz, luego, si los datos son menores que el valor clave, busque la
ubicación vacía en el subárbol izquierdo e inserte los datos. De lo contrario,
busque la ubicación vacía en el subárbol derecho e inserte los datos.
Algoritmo
If root is NULL
then create root node
return

If root exists then


compare the data with node.data

while until insertion position is located

If data is greater than node.data


goto right subtree
else
goto left subtree

endwhile

insert data

end If

Implementación
La implementación de la función de inserción debería verse así:
void insert(int data) {
struct node *tempNode = (struct node*)
malloc(sizeof(struct node));
struct node *current;
struct node *parent;

tempNode->data = data;
tempNode->leftChild = NULL;
tempNode->rightChild = NULL;

//if tree is empty, create root node


if(root == NULL) {
root = tempNode;
} else {
current = root;
parent = NULL;

while(1) {
parent = current;

//go to left of the tree


if(data < parent->data) {
current = current->leftChild;

//insert to the left


if(current == NULL) {
parent->leftChild = tempNode;
return;
}
}

//go to right of the tree


else {
current = current->rightChild;

//insert to the right


if(current == NULL) {
parent->rightChild = tempNode;
return;
}
}
}
}
}

Operación de búsqueda
Siempre que se deba buscar un elemento, comience a buscar desde el nodo
raíz, luego, si los datos son menores que el valor clave, busque el elemento en
el subárbol izquierdo. De lo contrario, busque el elemento en el subárbol
derecho. Siga el mismo algoritmo para cada nodo.
Algoritmo
If root.data is equal to search.data
return root
else
while data not found

If data is greater than node.data


goto right subtree
else
goto left subtree

If data found
return node
endwhile

return data not found

end if
La implementación de este algoritmo debería verse así.
struct node* search(int data) {
struct node *current = root;
printf("Visiting elements: ");

while(current->data != data) {
if(current != NULL)
printf("%d ",current->data);

//go to left tree

if(current->data > data) {


current = current->leftChild;
}
//else go to right tree
else {
current = current->rightChild;
}

//not found
if(current == NULL) {
return NULL;
}

return current;
}
}

Para conocer la implementación de la estructura de datos del árbol de


búsqueda binaria, haga clic aquí .

Estructura de datos y algoritmos: transversal


del árbol
El recorrido es un proceso para visitar todos los nodos de un árbol y también
puede imprimir sus valores. Debido a que todos los nodos están conectados a
través de bordes (enlaces), siempre comenzamos desde el nodo raíz
(cabeza). Es decir, no podemos acceder aleatoriamente a un nodo en un
árbol. Hay tres formas que usamos para atravesar un árbol:

 Recorrido en orden
 Pre-pedido transversal
 Recorrido posterior al pedido
En general, atravesamos un árbol para buscar o localizar un elemento o clave
en el árbol o para imprimir todos los valores que contiene.

Recorrido en orden
En este método transversal, primero se visita el subárbol izquierdo, luego la
raíz y luego el subárbol derecho. Siempre debemos recordar que cada nodo
puede representar un subárbol en sí.
Si se atraviesa un árbol binario en orden , la salida producirá valores de clave
ordenados en orden ascendente.

Partimos de una , y siguiendo el recorrido en orden, nos movemos a su


subárbol izquierdo B . B también se atraviesa en orden. El proceso continúa
hasta que se visiten todos los nodos. La salida del recorrido en orden de este
árbol será:
D→B→E→A→F→C→G
Algoritmo
Until all nodes are traversed −
Step 1 − Recursively traverse left subtree.
Step 2 − Visit root node.
Step 3 − Recursively traverse right subtree.

Pre-pedido transversal
En este método transversal, primero se visita el nodo raíz, luego el subárbol
izquierdo y finalmente el subárbol derecho.
Partimos de una , y siguiendo el recorrido de pre-orden, primero
visitamos Un sí y luego pasar a su subárbol izquierdo B . B también se
atraviesa el pedido anticipado. El proceso continúa hasta que se visiten todos
los nodos. La salida del recorrido de preorden de este árbol será:
A→B→D→E→C→F→G
Algoritmo
Until all nodes are traversed −
Step 1 − Visit root node.
Step 2 − Recursively traverse left subtree.
Step 3 − Recursively traverse right subtree.

Recorrido posterior al pedido


En este método transversal, el nodo raíz se visita en último lugar, de ahí el
nombre. Primero atravesamos el subárbol izquierdo, luego el subárbol derecho
y finalmente el nodo raíz.
Partimos de una , y siguiendo el recorrido posterior a la orden, primero
visitamos el subárbol izquierdo B . B también se atraviesa en orden
posterior. El proceso continúa hasta que se visiten todos los nodos. La salida
del recorrido posterior al pedido de este árbol será:
D→E→B→F→G→C→A
Algoritmo
Until all nodes are traversed −
Step 1 − Recursively traverse left subtree.
Step 2 − Recursively traverse right subtree.
Step 3 − Visit root node.
Para verificar la implementación en C del recorrido del árbol, haga clic aquí .

Estructura de datos - Árbol de búsqueda


binaria
Un árbol de búsqueda binaria (BST) es un árbol en el que todos los nodos
siguen las propiedades mencionadas a continuación:
 El subárbol izquierdo de un nodo tiene una clave menor o igual que la clave de su
nodo padre.
 El subárbol derecho de un nodo tiene una clave mayor que la clave de su nodo
principal.
Por lo tanto, BST divide todos sus subárboles en dos segmentos; el subárbol
izquierdo y el subárbol derecho y se pueden definir como -
left_subtree (keys) ≤ node (key) ≤ right_subtree (keys)

Representación
BST es una colección de nodos dispuestos de manera que mantienen las
propiedades BST. Cada nodo tiene una clave y un valor asociado. Durante la
búsqueda, la clave deseada se compara con las claves en BST y, si se
encuentra, se recupera el valor asociado.
A continuación se muestra una representación gráfica de BST:

Observamos que la clave del nodo raíz (27) tiene todas las claves de menor
valor en el subárbol izquierdo y las claves de mayor valor en el subárbol
derecho.

Operaciones básicas
Las siguientes son las operaciones básicas de un árbol:
 Buscar : busca un elemento en un árbol.
 Insertar : inserta un elemento en un árbol.
 Recorrido de pedido anticipado : atraviesa un árbol de manera ordenada.
 Recorrido en orden : atraviesa un árbol en orden.
 Recorrido posterior al pedido : atraviesa un árbol de manera posterior.

Nodo
Defina un nodo que tenga algunos datos, referencias a sus nodos secundarios
izquierdo y derecho.
struct node {
int data;
struct node *leftChild;
struct node *rightChild;
};

Operación de búsqueda
Siempre que se busque un elemento, comience a buscar desde el nodo
raíz. Luego, si los datos son menores que el valor clave, busque el elemento
en el subárbol izquierdo. De lo contrario, busque el elemento en el subárbol
derecho. Siga el mismo algoritmo para cada nodo.
Algoritmo
struct node* search(int data){
struct node *current = root;
printf("Visiting elements: ");

while(current->data != data){

if(current != NULL) {
printf("%d ",current->data);

//go to left tree


if(current->data > data){
current = current->leftChild;
} //else go to right tree
else {
current = current->rightChild;
}

//not found
if(current == NULL){
return NULL;
}
}
}

return current;
}

Insertar operación
Siempre que se inserte un elemento, primero ubique su ubicación
correcta. Comience a buscar desde el nodo raíz, luego, si los datos son
menores que el valor clave, busque la ubicación vacía en el subárbol izquierdo
e inserte los datos. De lo contrario, busque la ubicación vacía en el subárbol
derecho e inserte los datos.
Algoritmo
void insert(int data) {
struct node *tempNode = (struct node*)
malloc(sizeof(struct node));
struct node *current;
struct node *parent;

tempNode->data = data;
tempNode->leftChild = NULL;
tempNode->rightChild = NULL;

//if tree is empty


if(root == NULL) {
root = tempNode;
} else {
current = root;
parent = NULL;
while(1) {
parent = current;

//go to left of the tree


if(data < parent->data) {
current = current->leftChild;
//insert to the left

if(current == NULL) {
parent->leftChild = tempNode;
return;
}
} //go to right of the tree
else {
current = current->rightChild;

//insert to the right


if(current == NULL) {
parent->rightChild = tempNode;
return;
}
}
}
}
}

Estructura de datos y algoritmos: árboles AVL


¿Qué pasa si la entrada al árbol de búsqueda binaria viene de manera
ordenada (ascendente o descendente)? Entonces se verá así:

Se observa que el rendimiento en el peor de los casos de BST está más cerca
de los algoritmos de búsqueda lineal, es decir, Ο (n). En datos en tiempo real,
no podemos predecir el patrón de datos y sus frecuencias. Por lo tanto, surge
la necesidad de equilibrar el BST existente.
El nombre de su inventor Adelson , Velski & Landis , los árboles
AVL son árboles de búsqueda binaria de equilibrio de altura. El árbol AVL
verifica la altura de los subárboles izquierdo y derecho y asegura que la
diferencia no sea mayor que 1. Esta diferencia se llama Factor de equilibrio .
Aquí vemos que el primer árbol está equilibrado y los dos siguientes no lo
están:

En el segundo árbol, el subárbol izquierdo de C tiene altura 2 y el subárbol


derecho tiene altura 0, por lo que la diferencia es 2. En el tercer árbol, el
subárbol derecho de A tiene altura 2 y falta el izquierdo, por lo que es 0 , y la
diferencia es 2 nuevamente. El árbol AVL permite que la diferencia (factor de
equilibrio) sea solo 1.
BalanceFactor = height(left-sutree) − height(right-sutree)
Si la diferencia en la altura de los subárboles izquierdo y derecho es mayor
que 1, el árbol se equilibra utilizando algunas técnicas de rotación.

Rotaciones AVL
Para equilibrarse, un árbol AVL puede realizar los siguientes cuatro tipos de
rotaciones:

 Rotación izquierda
 Rotación derecha
 Rotación izquierda-derecha
 Rotación derecha-izquierda
Las dos primeras rotaciones son rotaciones simples y las siguientes dos
rotaciones son rotaciones dobles. Para tener un árbol desequilibrado, al
menos necesitamos un árbol de altura 2. Con este árbol simple,
comprendamos uno por uno.
Rotación izquierda
Si un árbol se desequilibra, cuando se inserta un nodo en el subárbol derecho
del subárbol derecho, entonces realizamos una sola rotación izquierda:
En nuestro ejemplo, el nodo A se ha desequilibrado a medida que se inserta
un nodo en el subárbol derecho del subárbol derecho de A. Realizamos la
rotación a la izquierda haciendo que A sea el subárbol izquierdo de B.

Rotación derecha
El árbol AVL puede desequilibrarse si se inserta un nodo en el subárbol
izquierdo del subárbol izquierdo. El árbol entonces necesita una rotación
correcta.

Como se muestra, el nodo desequilibrado se convierte en el hijo derecho de


su hijo izquierdo al realizar una rotación a la derecha.
Rotación izquierda-derecha
Las rotaciones dobles son versiones ligeramente complejas de versiones de
rotaciones ya explicadas. Para comprenderlos mejor, debemos tomar nota de
cada acción realizada durante la rotación. Primero veamos cómo realizar la
rotación izquierda-derecha. Una rotación izquierda-derecha es una
combinación de rotación izquierda seguida de rotación derecha.

Estado Acción
Se ha insertado un nodo en el subárbol derecho del subárbol
izquierdo. Esto hace que C sea un nodo desequilibrado. Estos
escenarios hacen que el árbol AVL realice una rotación izquierda-
derecha.

En primer lugar, realizamos la rotación izquierda en el subárbol


izquierdo de C . Esto hace que una , el subárbol izquierdo de B .

El nodo C todavía no está equilibrado, sin embargo, ahora se debe


al subárbol izquierdo del subárbol izquierdo.

Ahora giraremos a la derecha el árbol, haciendo de B el nuevo


nodo raíz de este subárbol. C ahora se convierte en el subárbol
derecho de su propio subárbol izquierdo.

El árbol ahora está equilibrado.


Rotación derecha-izquierda
El segundo tipo de doble rotación es la rotación derecha-izquierda. Es una
combinación de rotación derecha seguida de rotación izquierda.

Estado Acción

Se ha insertado un nodo en el subárbol izquierdo del subárbol


derecho. Esto hace que A , un nodo desequilibrado con factor de
equilibrio 2.

En primer lugar, realizamos la rotación a la derecha por C nodo, por


lo que C el subárbol derecho de su propio subárbol
izquierdo B . Ahora, B se convierte en el subárbol derecho de A .

El nodo A todavía está desequilibrado debido al subárbol derecho


de su subárbol derecho y requiere una rotación a la izquierda.

Una rotación a la izquierda se realiza haciendo que B sea el nuevo


nodo raíz del subárbol. Una se convierte en el subárbol izquierdo
de su subárbol derecho B .
El árbol ahora está equilibrado.

Estructura de datos y algoritmos: árbol de


expansión
Un árbol de expansión es un subconjunto del Gráfico G, que tiene todos los
vértices cubiertos con el mínimo número posible de aristas. Por lo tanto, un
árbol de expansión no tiene ciclos y no se puede desconectar.
Con esta definición, podemos llegar a la conclusión de que cada Gráfico G
conectado y no dirigido tiene al menos un árbol de expansión. Un gráfico
desconectado no tiene ningún árbol de expansión, ya que no se puede
abarcar a todos sus vértices.

Encontramos tres árboles de expansión en un gráfico completo. Un gráfico


completo no dirigido puede tener un número máximo n n-2 de árboles de
expansión, donde n es el número de nodos. En el ejemplo mencionado
anteriormente, n es 3, por lo tanto , 3 3−2 = 3 árboles de expansión son
posibles.

Propiedades generales de Spanning Tree


Ahora entendemos que un gráfico puede tener más de un árbol de
expansión. Las siguientes son algunas propiedades del árbol de expansión
conectado al gráfico G:
 Un gráfico G conectado puede tener más de un árbol de expansión.
 Todos los árboles de expansión posibles del gráfico G tienen el mismo número de
aristas y vértices.
 El árbol de expansión no tiene ningún ciclo (bucles).
 Eliminar un borde del árbol de expansión hará que el gráfico se desconecte, es
decir, el árbol de expansión está mínimamente conectado .
 Agregar un borde al árbol de expansión creará un circuito o bucle, es decir, el
árbol de expansión es máximamente acíclico .

Propiedades Matemáticas de Spanning Tree


 El árbol de expansión tiene n-1 aristas, donde n es el número de nodos (vértices).
 A partir de un gráfico completo, eliminando los bordes máximos e - n + 1 ,
podemos construir un árbol de expansión.
 Un gráfico completo puede tener un número máximo n n-2 de árboles de expansión.
Por lo tanto, podemos concluir que los árboles de expansión son un
subconjunto del Gráfico G conectado y los gráficos desconectados no tienen
un árbol de expansión.

Aplicación de Spanning Tree


El árbol de expansión se usa básicamente para encontrar una ruta mínima
para conectar todos los nodos en un gráfico. La aplicación común de los
árboles de expansión son:
 Planificación de redes civiles
 Protocolo de enrutamiento de red informática
 Análisis de conglomerados
Comprendamos esto a través de un pequeño ejemplo. Considere la red de la
ciudad como un gran gráfico y ahora planea implementar líneas telefónicas de
tal manera que en líneas mínimas podamos conectarnos a todos los nodos de
la ciudad. Aquí es donde entra en juego el árbol de expansión.

Árbol de expansión mínima (MST)


En un gráfico ponderado, un árbol de expansión mínimo es un árbol de
expansión que tiene un peso mínimo que todos los demás árboles de
expansión del mismo gráfico. En situaciones del mundo real, este peso se
puede medir como distancia, congestión, carga de tráfico o cualquier valor
arbitrario indicado en los bordes.

Algoritmo de árbol de expansión mínimo


Aquí aprenderemos sobre los dos algoritmos de árbol de expansión más
importantes:
 Algoritmo de Kruskal
 Algoritmo de Prim
Ambos son algoritmos codiciosos.

Estructuras de datos de montón


El montón es un caso especial de estructura de datos de árbol binario
equilibrado donde la clave del nodo raíz se compara con sus elementos
secundarios y se organiza en consecuencia. Si α tiene un nodo
hijo β entonces -
clave (α) ≥ clave (β)
Como el valor de parent es mayor que el de child, esta propiedad genera Max
Heap . Según este criterio, un montón puede ser de dos tipos:
For Input → 35 33 42 10 14 19 27 44 26 31
Min-Heap : donde el valor del nodo raíz es menor o igual que cualquiera de
sus elementos secundarios.

Max-Heap : donde el valor del nodo raíz es mayor o igual que cualquiera de
sus elementos secundarios.
Ambos árboles se construyen utilizando la misma entrada y el mismo orden de
llegada.

Algoritmo de construcción de montón máximo


Utilizaremos el mismo ejemplo para demostrar cómo se crea un Max Heap. El
procedimiento para crear Min Heap es similar, pero buscamos valores
mínimos en lugar de valores máximos.
Vamos a derivar un algoritmo para el montón máximo insertando un elemento
a la vez. En cualquier momento, el montón debe mantener su
propiedad. Durante la inserción, también asumimos que estamos insertando
un nodo en un árbol ya heapified.
Step 1 − Create a new node at the end of heap.
Step 2 − Assign new value to the node.
Step 3 − Compare the value of this child node with its
parent.
Step 4 − If value of parent is less than child, then swap
them.
Step 5 − Repeat step 3 & 4 until Heap property holds.
Nota : en el algoritmo de construcción Min Heap, esperamos que el valor del
nodo primario sea menor que el del nodo secundario.
Comprendamos la construcción de Max Heap por una ilustración
animada. Consideramos la misma muestra de entrada que utilizamos
anteriormente.
Algoritmo de eliminación de montón máximo
Derivemos un algoritmo para eliminar del montón máximo. La eliminación en el
montón máximo (o mínimo) siempre ocurre en la raíz para eliminar el valor
máximo (o mínimo).
Step 1 − Remove root node.
Step 2 − Move the last element of last level to root.
Step 3 − Compare the value of this child node with its
parent.
Step 4 − If value of parent is less than child, then swap
them.
Step 5 − Repeat step 3 & 4 until Heap property holds.

Estructura de datos: conceptos básicos de


recursión
Algunos lenguajes de programación de computadoras permiten que un módulo
o función se llame a sí mismo. Esta técnica se conoce como recursión. En la
recursividad, una función α se llama a sí misma directamente o llama a una
función β que a su vez llama a la función original α . La función α se llama
función recursiva.
Ejemplo : una función que se llama a sí misma.
int function(int value) {
if(value < 1)
return;
function(value - 1);

printf("%d ",value);
}
Ejemplo : una función que llama a otra función que a su vez la vuelve a
llamar.
int function1(int value1) {
if(value1 < 1)
return;
function2(value1 - 1);
printf("%d ",value1);
}
int function2(int value2) {
function1(value2);
}

Propiedades
Una función recursiva puede ser infinita como un bucle. Para evitar el
funcionamiento infinito de la función recursiva, hay dos propiedades que debe
tener una función recursiva:
 Criterios de Base - debe haber al menos uno de los criterios de base o condición,
de tal manera que, cuando esta condición se cumple la función se detiene hace
llamar de forma recursiva.
 Enfoque progresivo : las llamadas recursivas deben progresar de tal manera que
cada vez que se realiza una llamada recursiva se acerque al criterio base.

Implementación
Muchos lenguajes de programación implementan la recursión por medio
de pilas . Generalmente, cada vez que una función ( llamante ) llama a otra
función ( llamante ) o se llama a sí misma como llamada, la función llamante
transfiere el control de ejecución al llamante. Este proceso de transferencia
también puede implicar que algunos datos pasen de la persona que llama a la
persona que llama.
Esto implica que la función de llamada tiene que suspender su ejecución
temporalmente y reanudarla más tarde cuando el control de ejecución regrese
de la función de llamada. Aquí, la función de llamada debe comenzar
exactamente desde el punto de ejecución donde se pone en espera. También
necesita exactamente los mismos valores de datos en los que estaba
trabajando. Para este propósito, se crea un registro de activación (o marco de
pila) para la función del llamante.

Este registro de activación mantiene la información sobre variables locales,


parámetros formales, dirección de retorno y toda la información que se pasa a
la función de la persona que llama.

Análisis de recursión
Uno puede discutir por qué usar la recursión, ya que la misma tarea se puede
hacer con la iteración. La primera razón es que la recursividad hace que un
programa sea más legible y, debido a los últimos sistemas de CPU mejorados,
la recursividad es más eficiente que las iteraciones.

Complejidad de tiempo
En caso de iteraciones, tomamos varias iteraciones para contar la complejidad
del tiempo. Del mismo modo, en caso de recursión, suponiendo que todo sea
constante, tratamos de calcular la cantidad de veces que se realiza una
llamada recursiva. Una llamada realizada a una función es Ο (1), por lo tanto,
el (n) número de veces que se realiza una llamada recursiva convierte la
función recursiva Ο (n).

Complejidad espacial
La complejidad del espacio se cuenta como la cantidad de espacio adicional
que se requiere para que un módulo se ejecute. En caso de iteraciones, el
compilador apenas requiere espacio adicional. El compilador sigue
actualizando los valores de las variables utilizadas en las iteraciones. Pero en
caso de recursividad, el sistema necesita almacenar el registro de activación
cada vez que se realiza una llamada recursiva. Por lo tanto, se considera que
la complejidad espacial de la función recursiva puede ser mayor que la de una
función con iteración.

Estructura de datos y algoritmos - Torre de


Hanoi
Torre de Hanoi, es un rompecabezas matemático que consta de tres torres
(clavijas) y más de un anillo es como se muestra:

Estos anillos son de diferentes tamaños y se apilan en orden ascendente, es


decir, el más pequeño se asienta sobre el más grande. Hay otras variaciones
del rompecabezas donde aumenta el número de discos, pero el recuento de la
torre sigue siendo el mismo.

Reglas
La misión es mover todos los discos a otra torre sin violar la secuencia de
disposición. Algunas reglas a seguir para la Torre de Hanoi son:

 Solo se puede mover un disco entre las torres en un momento dado.


 Solo se puede quitar el disco "superior".
 Ningún disco grande puede sentarse sobre un disco pequeño.
A continuación se muestra una representación animada de cómo resolver un
rompecabezas de la Torre de Hanoi con tres discos.
El rompecabezas de la Torre de Hanoi con n discos se puede resolver en un
mínimo de 2 n −1 pasos. Esta presentación muestra que un rompecabezas con
3 discos ha tomado 2 3 - 1 = 7 pasos.

Algoritmo
Para escribir un algoritmo para la Torre de Hanoi, primero debemos aprender
a resolver este problema con una menor cantidad de discos, digamos → 1 o 2.
Marcamos tres torres con nombre, origen , destino y auxiliar (solo para
ayudar a mover los discos ) Si solo tenemos un disco, se puede mover
fácilmente de la clavija de origen a la de destino.
Si tenemos 2 discos:

 Primero, movemos el disco más pequeño (superior) a la clavija auxiliar.


 Luego, movemos el disco más grande (inferior) a la clavija de destino.
 Y finalmente, movemos el disco más pequeño de la clavija auxiliar a la de destino.

Ahora, estamos en condiciones de diseñar un algoritmo para la Torre de Hanoi


con más de dos discos. Dividimos la pila de discos en dos partes. El disco más
grande ( enésimo disco) está en una parte y todos los otros discos (n-1) están en la
segunda parte.
Nuestro objetivo final es mover el disco n desde el origen al destino y luego
colocar todos los demás discos (n1) en él. Podemos imaginar aplicar lo mismo
de forma recursiva para todos los conjuntos de discos.
Los pasos a seguir son:
Step 1 − Move n-1 disks from source to aux
Step 2 − Move nth disk from source to dest
Step 3 − Move n-1 disks from aux to dest
Un algoritmo recursivo para la Torre de Hanoi se puede manejar de la
siguiente manera:
START
Procedure Hanoi(disk, source, dest, aux)

IF disk == 1, THEN
move disk from source to dest
ELSE
Hanoi(disk - 1, source, aux, dest) // Step 1
move disk from source to dest // Step 2
Hanoi(disk - 1, aux, dest, source) // Step 3
END IF

END Procedure
STOP
Para verificar la implementación en la programación en C, haga clic aquí .

Estructura de datos y algoritmos Serie


Fibonacci
La serie de Fibonacci genera el número subsiguiente al sumar dos números
anteriores. La serie de Fibonacci comienza con dos números: F 0 y F 1 . Los
valores iniciales de F 0 y F 1 se pueden tomar 0, 1 o 1, 1 respectivamente.
La serie de Fibonacci satisface las siguientes condiciones:
Fn = Fn-1 + Fn-2
Por lo tanto, una serie de Fibonacci puede verse así:
F 8 = 0 1 1 2 3 5 8 13
o esto -
F 8 = 1 1 2 3 5 8 13 21
Con fines ilustrativos, Fibonacci de F 8 se muestra como -
Algoritmo iterativo de Fibonacci
Primero tratamos de redactar el algoritmo iterativo para la serie Fibonacci.
Procedure Fibonacci(n)
declare f0, f1, fib, loop

set f0 to 0
set f1 to 1

display f0, f1

for loop ← 1 to n

fib ← f0 + f1
f0 ← f1
f1 ← fib

display fib
end for

end procedure

Para conocer la implementación del algoritmo anterior en lenguaje de


programación C, haga clic aquí .

Algoritmo Recursivo de Fibonacci


Aprendamos cómo crear un algoritmo recursivo de la serie Fibonacci. Los
criterios básicos de recursividad.
START
Procedure Fibonacci(n)
declare f0, f1, fib, loop

set f0 to 0
set f1 to 1

display f0, f1

for loop ← 1 to n

fib ← f0 + f1
f0 ← f1
f1 ← fib

display fib
end for

END

También podría gustarte