You are on page 1of 153

Programación y Algoritmia

Un enfoque práctico y didáctico


para el diseño de algoritmos

Material Didáctico Algoritmos y Estructura de datos Prof.


Mgs. Oscar R. Bruno

Contenido
Conceptos de programación Teoría y práctica
Ejercicios y problemas propuestos y resueltos
Evaluación

Recomendaciones
Si Usted esta cursando por primera vez la materia y quiere abordar las definiciones
específicas de términos y frases de la programación debería acceder a Conceptos de
programación, en particular el capitulo primero es allí donde se abordan estos temas.
Si ya conoce la terminología de la disciplina y necesita profundizar conocimientos sobre
técnicas de desarrollo de algoritmos debe acceder Conceptos de programación en los
capítulos segundo a cuarto. Analice con mucha atención todo lo desarrollado en el
capitulo referido a acciones y funciones y al capitulo referido a estructuras enlazadas si
su propósito es ver los temas finales y mas complejos de la materia. Para profundizar
sus conocimientos de patrones de algoritmia debe acceder al capitulo de algoritmos
puntuales donde encontrara un desarrollo detallado y con explicación paso a paso de la
secuenciación de las acciones para la solución de situaciones problemáticas particulares
Si lo que necesita es ejercitar para acompañar los conceptos teóricos de sus clases puede
acceder a Ejercicios , allí encontrara ejercitación y problemas, separados por nivel de
complejidad desde los mas simples hasta los exámenes finales mas recientes, alguno de
los cuales se presentan con estrategias de resolución, para orientarlo sobre los mismos.
Todo el Material esta debidamente enlazado para que Usted pueda interactuar con ellos.
Por ultimo puede acceder a su propia evaluación, diagnostica al principio y de
comprensión de cada uno de los módulos y de resolución de problemas. Para mayor
diversidad de problemas puede acceder al grupo o blog que se indica mas adelante, allí
se actualizan semanalmente las situaciones problemáticas distintas agrupadas por nivel
de complejidad para que Usted acceda según su propia necesidad y evolución.
El modulo de Evaluación le permite acceder a su propia evaluación, ya sea diagnostica
o según su evolución, sugiriéndole según una matriz de recomendaciones como
planificar su estudio en función del conocimiento demostrado.
Por último, usted puede contactarse conmigo vía correo electrónico a
oscarrbruno@yahoo.com, o suscribirse al grupo de apoyo a través un correo electrónico
a oscarrbruno-subscribe@gruposyahoo.com.ar Suerte en el esfuerzo, espero su consulta o
su crítica al material para la mejora del mismo Cordialmente, Oscar Ricardo Bruno

Agradezco a Karina Lion, Alejandra Tolcachier, Karina Cuzzani, Marcelo Giura y UTN
por orientarnos en la elaboración de material Didáctico
Prologo
De la experiencia en las aulas al libro de programación
He procurado volcar en esta publicación toda mi experiencia docente a lo largo de
muchos años en la UTN FRBA, E.T. Otto Krause, USAL y otros institutos terciarios y
universitarios.
El propósito es que no sea este otra publicación de programación, se propone un estilo
riguroso, científico y disciplinado pero al mismo tiempo con un enfoque practico y
realista para quien desea aprender las técnicas y el arte de la programación desde el
inicio y solidamente.
El desafió, uno de los más atractivos desde el punto de vista de la actividad académica,
es introducir al lector en los conceptos básicos de la disciplina para su formación
integral. Se busca profundizar sobre los aspectos conceptuales superando los límites de
la simple transmisión de los conocimientos para que pueda transformarse en una
enseñanza basada en la comprensión.
Se abordan los temas de especificación y representación de algoritmos combinándolo
con la utilización de las estructuras de datos mas adecuadas para cada situación
problemática planteada para extender los conceptos a tipos de datos derivados, definidos
por el usuario y tipos abstractos de datos.
En definitiva, el objetivo principal de esta publicación esta centrado en especificar y
expresar correctamente los algoritmos, analizando su eficiencia y corrección; el
tratamiento adecuado de las estructuras de datos hasta alcanzar el concepto de tipo
abstracto de dato y la contextualización de los algoritmos considerando los aspectos de
reusabilidad y documentación.
Las técnicas que se desarrollan son validas con independencia del lenguaje de
programación que pueda escoger. De ninguna manera se ve esta publicación como un
libro definitivo sino como un planteo ordenado de la experiencia docente en la
formación básica en técnicas de programación.
Por todo esto espero disfrute la lectura y que la misma sea de utilidad para el
aprendizaje comprensivo de algoritmia.
Oscar Ricardo Bruno
Contenido
INTRODUCCION _____________________________________________________ 8
CAPITULO 1 Conceptos básicos ________________________________________ 11
Introducción:______________________________________________________ 11
Informática _______________________________________________________ 11
Programación _____________________________________________________ 11
Partes de un programa______________________________________________ 12
Dato _____________________________________________________________ 13
Abstracción _______________________________________________________ 13
Modelizacion ______________________________________________________ 13
Precondición ______________________________________________________ 13
Poscondición ______________________________________________________ 13
Especificación _____________________________________________________ 13
Lenguaje de programación __________________________________________ 13
Del problema real a su solución por computadoras ______________________ 13
Características de un algoritmo ______________________________________ 16
Propiedades de los algoritmos ________________________________________ 17
Eficiencia de un algoritmo ___________________________________________ 18
Complejidades más comunes_________________________________________ 18
Léxico y algoritmo _________________________________________________ 18
Estructura de un algoritmo __________________________________________ 19
Proceso Computacional _____________________________________________ 19
CAPITULO 2 Asignación - Control – Iteración ____________________________ 23
Introducción:______________________________________________________ 23
Asignación, secuenciación y análisis de casos ___________________________ 23
Asignación. _______________________________________________________ 23
Análisis de casos ___________________________________________________ 24
La necesidad de iterar ______________________________________________ 28
Composiciones iterativas ____________________________________________ 29
Composiciones iterativas No Exactas __________________________________ 30
Composiciones iterativas Exactas _____________________________________ 30
Recursividad ______________________________________________________ 32
Iteración vs. Recursion______________________________________________ 33
Algoritmos que utilizan secuencias ____________________________________ 34
CAPITULO 3 Acciones y Funciones _____________________________________ 40
Introducción ______________________________________________________ 40
Modularizacion ____________________________________________________ 40
Módulos __________________________________________________________ 40
Alcance de los datos ________________________________________________ 41
Datos locales y globales _____________________________________________ 41
Ocultamiento y protección de datos ___________________________________ 41
Parámetros _______________________________________________________ 41
Integridad de los datos ______________________________________________ 41
Protección de datos_________________________________________________ 42
Uso de parámetros para retornar valores ______________________________ 42
Utilidad del uso de parámetros _______________________________________ 42
Reusabilidad ______________________________________________________ 42
Acciones __________________________________________________________ 42
Utilización de acciones ______________________________________________ 42
Acciones con parámetros ____________________________________________ 43
Abstracción y acciones ______________________________________________ 43
Tipos de Parámetros________________________________________________ 44
Beneficios del uso de acciones ________________________________________ 45
Funciones_________________________________________________________ 45
CAPITULO 4 Tipos de datos y Operadores ________________________________ 47
Introducción:______________________________________________________ 47
Variables _________________________________________________________ 47
Constante_________________________________________________________ 47
Tipos de Datos_____________________________________________________ 47
análisis Comparativo de estructuras __________________________________ 50
Criterio de selección de estructuras ___________________________________ 51
Comparación entre estructuras enlazadas lineales _______________________ 52
Operadores _______________________________________________________ 53
CAPITULO 5 Estructuras Enlazadas_____________________________________ 57
Introducción ______________________________________________________ 57
Estructuras enlazadas vs. estructuras indexadas ________________________ 57
Estructuras enlazadas con asignación dinámica en memoria ______________ 57
El tipo de dato Puntero______________________________________________ 59
Acceso a datos mediante apuntadores _________________________________ 61
Tipos de datos autorreferenciados o recursivos _________________________ 62
Estructuras de datos dinámicas lineales________________________________ 63
El tipo pila ________________________________________________________ 63
Insertar elemento en una pila:________________________________________ 65
Desapilar: leer y eliminar un elemento ________________________________ 65
El tipo cola________________________________________________________ 66
Añadir un elemento Encolar: ________________________________________ 66
Leer un elemento de una cola Eliminar primero: ________________________ 67
El tipo lista________________________________________________________ 67
Listas simplemente enlazadas ________________________________________ 67
Eliminar elementos en una lista ______________________________________ 68
Algoritmo de inserción ______________________________________________ 70
Listas circulares ___________________________________________________ 72
Operaciones básicas con listas circulares _______________________________ 73
Añadir un elemento ________________________________________________ 73
Eliminar un elemento de una lista circular _____________________________ 74
Listas doblemente enlazadas _________________________________________ 78
Operaciones básicas con listas doblemente enlazadas ____________________ 78
Añadir un elemento ________________________________________________ 78
Eliminar un elemento de una lista doblemente enlazada __________________ 81
A modo de síntesis con estructuras enlazadas ___________________________ 83
CAPITULO 6 Árboles _________________________________________________ 85
Introducción ______________________________________________________ 85
Árboles___________________________________________________________ 85
Definiciones comunes _______________________________________________ 86
árbol AVL ________________________________________________________ 93
Inserciones en árboles AVL __________________________________________ 94
Los árboles-B _____________________________________________________ 94
TABLAS HASHING (DISPERSION) _________________________________ 95
CAPITULO 7 ALGORITMOS _________________________________________ 108
Acciones y funciones para vectores___________________________________ 108
Acciones y funciones para archivos __________________________________ 117
Acciones y funciones para pilas______________________________________ 121
Acciones y funciones para Colas _____________________________________ 122
Acciones y funciones para Listas Ordenadas enlazadas __________________ 123
Acciones y funciones para arboles ___________________________________ 132
Inserciones en arboles AVL _________________________________________ 137
Funciones recursivas ______________________________________________ 138
Archivos en pascal ________________________________________________ 140
Ejemplo en C con aplicaciones de estructuras enlzadas __________________ 145
ANEXO 1 Representaciones gráficas de algoritmos________________________ 147
Diagrama de Nassi-Sneiderman _____________________________________ 147
Diagramas de Jackson _____________________________________________ 147
Diagramas de Lindsay._____________________________________________ 148
Llaves de Warniel _________________________________________________ 149
Notación Algoritmica ______________________________________________ 149
Equivalencias entre notación algorítmica y lenguajes de programación ____ 150
Estilos de Indentación _____________________________________________ 150
BIBLIOGRAFIA ____________________________________________________ 152

Guía de ejercicios y problemas


INTRODUCCION

Muchas publicaciones se centran en acercar conocimientos en algún lenguaje en


particular y para quienes ya han tenido algún acercamiento a las técnicas de
programación. En este caso, se busca introducir en esas técnicas a los no iniciados en la
disciplina y ser una guía para sistematizar los conocimientos de quienes ya los posean.
El esfuerzo se centra en desarrollar una publicación didáctica teniendo en cuenta que “lo
que aprenden los alumnos tiene mucho que ver con lo que se enseña y como se lo
enseña.”
Los destinatarios son todas aquellas personas que quieran iniciarse en la práctica de la
programación, quienes pueden ser estudiantes de los ciclos superiores de la especialidad
de escuelas medias, tecnicaturas, primeros años de la universidad, o todas aquellas
personas con interés en iniciarse por si mismos en el mundo de la programación.
Es importante tener en cuenta hoy el avance de la disciplina, lo creciente de la industria
y las oportunidades que el sector ofrece para quien tenga este conocimiento.
Desde el punto de vista pedagógico, este trabajo tiene por objetivo volcar el
conocimiento que se dispone enfocando la enseñanza de las técnicas de programación
como metodología de resolución de problemas.
Sabido es que “Aprender a programar es mucho más que simplemente aprender un
lenguaje de programación”, por lo que el presente trabajo no esta centrado en aprender
un lenguaje de programación.
Se induce a dividir cada una de las dificultades en tantas partes como lo exija su mejor
solución, conducir en orden los pensamientos y hacer en todos los casos revisiones tan
generales que den la seguridad de no omitir nada, simplemente esto es seguir los
preceptos de la lógica.
Entender, además que, “un principio fundamental para la resolución de problemas es
que al encararlos se debe reflejar todo lo que se sabe sobre él. Es decir, si hay
evidencias convincentes que puede resolverse modificando o adaptando metodologías
ya conocidas se deben incluir estas prácticas. El conocimiento debe utilizarse para
mejorar las posibilidades de resolución.

Aprender es organizar el conocimiento.


Se quiere enseñar que las preguntas fundamentales al encarar un problema deben ser.
1. Cual es el problema?,
2. Cuales son los aspectos negativos?,
3. Cuales son las soluciones posibles?,
4. Cuales son las consecuencias de las posibles soluciones?
5. Cual es la solución mas adecuada?.
Se busca que el estudiante tenga acceso al conocimiento y lo pueda vincular con
aplicaciones relacionadas y ofrecerle estrategias para recuperarlos.

Para la práctica de la programación se necesita:


1. El conocimiento estratégico, de conceptos y de hechos, el QUE.
2. El conocimiento táctico, procedimental, el CÓMO hacerlo.
3. El conocimiento condicional el cuándo y él PORQUE.
4. El conocimiento fáctico CON QUE HACERLO.

Que se busca con esta publicación.


Principalmente escribir una publicación didáctica que describa claramente las
estrategias de resolución de problemas buscando en forma sistemática soluciones
eficientes y creativas, para ello se agrupan todos los algoritmos puntuales que permiten
la solución de problemas dentro del paradigma procedimental y estructurado.
Para ello, el presente trabajo se estructura de la siguiente forma_
Capitulo 1
Se presenta el alcance del presente trabajo y se introducen conceptos fundamentales de
algoritmia y programación, los que servirán de base para el desarrollo de los temas a
tratar.
Capitulo 2
Se presenta en este capitulo un primer criterio de descomposición del problema para
poder alcanzar la solución del mismo. Se establece la secuenciación y el análisis de
casos como primer mecanismo correcto para la solución de problemas.
Capitulo 3
En este capitulo se analiza la descomposición como forma de alcanzar la solución de
problemas. Una regla básica de la programación indica que si existe un programa de
longitud L tal que L = L1 + L2, se dice que el esfuerzo de resolver L es mayor que la
suma de los esfuerzos de resolución de L1 y L2, aun cuando haya que derivar esfuerzo
para la integración de los módulos. Se introducen los conceptos fundamentales de
acciones y funciones.
Capitulo 4
Se introducen los conceptos Tipos de datos y operadores. Se abordan temas de tipos de
datos primitivos, derivados y abstractos. Se definen los operadores aritméticos, lógicos,
de relación y los de acceso a los miembros particulares de las estructuras de datos con
análisis comparativo de las distintas estructuras de datos y una sugerencia para el modo
correcto de selección de las mismas en función de los resultados a obtener.
Capitulo 5
Se incorpora el concepto de asignación dinámica en memoria. Esto permite romper con
la limitación de tamaño fijo que proporcionan las tablas cuando es necesario trabajar
con colección de datos el mismo tipo en memoria. Se abordan los temas de estructuras
enlazadas, punteros y asignación dinámica en memoria.
Capitulo 6
En este capitulo se introduce el concepto de estructuras enlazadas arborescentes.
Estructuras de datos dinámicas arborescentes y el concepto de tablas hash.
Capitulo 7
Para la aplicación de todos los conocimientos adquiridos en los capítulos anteriores se
desarrollan, con explicación precisa paso a paso, los algoritmos puntuales para
resolución de problemas agrupados por estructuras de datos.
Los anexos muestran equivalencias entre la notación algorítmica desarrollada y otras
representaciones graficas de los algoritmos como así también las equivalencias entre la
notación y lenguajes de programación como pascal y C.

Las metas que se persiguen son que el estudiante:


1. Retenga el conocimiento, lo comprenda y haga uso activo del mismo.
2. Adquiera un conocimiento organizado.
3. Fomentar en el estudiante pasiones intelectuales como la curiosidad.
4. Que aprenda a aprender, a autogestionar su conocimiento, a sintetizar, a abstraer
y que sea capaz de transferir.
5. Que haga uso de lenguajes del pensamiento, que de valor a la formalización
como forma de adquisición sistematizada del conocimiento.
6. Que sepa como volcar ideas en el papel: Un problema que pasa de estar en la
cabeza a estar en el papel consume menos atención y produce menos ansiedad.
Esta liberación posibilita, en general, ver las cosas desde otra perspectiva.
7. Que sepa como obtener beneficios de problemas analizados. Un problema ya
analizado puede representar gran parte de la solución para otro problema
análogo.

Objetivos específicos
1. Abordar los temas propios de la programación sin centrarse en un lenguaje
concreto.
2. Ir de la observación de la realidad a la especificación de los problemas y de allí a
los programas con técnicas creativas.
3. Presentar con claridad el tipo de razonamiento que lleva a la estrategia de
solución.
4. Conocer las estructuras de datos y tener capacidad para seleccionar la más
adecuada según el problema que se intenta resolver.
5. Agrupar y sistematizar los algoritmos puntuales de resolución de problemas.

Sugerencias
No es suficiente con los conocimientos teóricos o con el análisis de lo que otro escribió,
como toda actividad constructiva REQUIERE DE PRACTICA.
Pensar primero en soluciones algorítmicas no ejecutables para el análisis y comprensión
como paso previo a escribir código. Se debe procurar pensar sobre la solución antes de
comenzar a escribir programas evitando transformarse en un programador compulsivo.

Computadora como maquina que procesa algoritmos


La computadora es una maquina maravillosa que ejecuta algoritmos, no esta diseñada
para una única tarea concreta, puede realizar tareas muy diferentes como gestionar la
contabilidad, desarrollar juegos, estar presente en las comunicaciones.
Una computadora es un mecanismo de propósito general para un uso específico cuando
procesa un determinado algoritmo. Esta ejecución supone la transformación de entrada
o datos en una información de salida o resultados. Buscamos formar programadores
entrenados en el lenguaje formal que permita entenderse con una computadora, para
que hagan un uso intenso, creativo e intelectual de la computadora.
CAPITULO 1 Conceptos básicos

Objetivos de aprendizaje
Dominando los temas del presente capitulo Usted podrá.
1. Conocer la terminología propia de la disciplina.
2. Definir y comprender claramente conceptos específicos muchas veces
mal definidos
3. Comprender el valor de la abstracción.
4. Dar valor a la eficiencia en las soluciones
5. Introducirse en la notación algorítmica y a la forma e encarar los
problemas de programación

Introducción:
Se presenta el alcance del presente trabajo y se introducen conceptos fundamentales de
algoritmia y programación, los que servirán de base para el desarrollo de los temas a
tratar.

Informática
Disciplina del estudio sistematizado de los procesos algorítmicos que describen y
transforman información, su teoría, análisis, diseño, eficiencia, implementación y
aplicación.
La informática es una disciplina científica, matemática y una ingeniería; tiene tres
formas de pensar propias: Teoría, abstracción y diseño.
Las tres se complementan para la resolución de la mayoría de los problemas.
 Teoría: Con el pensamiento teórico se describen y prueban relaciones.
 Abstracción: Recolección de datos y formulación de un modelo, se eliminan los
detalles irrelevantes.
 Diseño: se tienen en cuenta requisitos, especificaciones y se diseñan o analizan
mecanismos para resolver problemas. Supone llevar a la práctica los resultados
teóricos.

Programación
La programación es una actividad transversal asociada a cualquier área de la
informática, aunque es la ingeniería del software el área específica que se ocupa de la
creación del software.
En principio la programación se veía como un arte, solo era cuestión de dominar un
lenguaje de programación y aplicar habilidades personales a la resolución de problemas,
casi en forma artesanal. El software era visto como algo desarrollado a través de la
intuición sin la utilización de métodos de diseño con técnicas para proceder en forma
sistemática y sin ningún control de su desarrollo. Con el reconocimiento de la
complejidad del desarrollo del software nació la ingeniería del software.
Se considero que al igual que cualquier otra disciplina la creación de software debía ser
reconocida como una actividad de ingeniería que requería la aplicación de sólidos
principios científicos.
La ingeniería del software es la disciplina que se ocupa de la aplicación del
conocimiento científico al diseño y construcción de programas de computación y a
todas las actividades asociadas de documentación, operación y mantenimiento, lo que
proporciona un enfoque sistemático.
La programación es una actividad en la que la creatividad juega un rol primordial

Programa:
Conjunto de instrucciones, ejecutables sobre una computadora, que permite cumplir una
función especifica. Se asocia al programa con una determinada función o requerimiento
a satisfacer por la ejecución del conjunto de instrucciones que lo forman. En general
alcanzan su objetivo en tiempo finito, aunque hay excepciones, por ejemplo los
programas de control de un sistema de alarma poseen requerimiento de tiempo infinito.
Un programa sin errores que se ejecuta puede no ser correcto si no cumple con los
requerimientos.

Definición
Programa: conjunto de instrucciones no activas almacenadas en un computador, se
vuelve tarea a partir de que se selecciona para su ejecución y permite cumplir una
función específica. Un proceso es un programa en ejecución.

En principio las tareas más importantes a la que se enfrenta quien debe escribir
programas en computadoras son:
1. Definir el conjunto de instrucciones cuya ejecución ordenada conduce a la
solución.
2. Elegir la representación adecuada de los datos del problema.
La función esencial del especialista informático es explotar el potencial de las
computadoras para resolver situaciones del mundo real. Para esto debe analizar los
problemas del mundo real, ser capaz de sintetizar sus aspectos principales y poder
especificar la función objetivo que se desee. Posteriormente debe expresar la solución
en forma de programa, manejando los datos del mundo real mediante una
representación valida para una computadora.

Partes de un programa
Los componentes básicos son las instrucciones y los datos. Las instrucciones o
sentencias representan las operaciones que se ejecutaran al interpretar el programa.
Todos los lenguajes de programación tienen un conjunto mínimo de operaciones que
son las de asignación, selección e iteración. Un lenguaje con solo estas tres
instrucciones permite escribir cualquier algoritmo.
Los datos son valores de información de los que se necesita disponer, en ocasiones
transformar para ejecutar la función del programa.
Los datos están representados simbólicamente por un nombre que se asocia con una
dirección única de memoria.
El contenido de la dirección de memoria correspondiente a un dato constante se asigna
solo una vez y solo puede ser modificado en una nueva compilación. En cambio el
contenido o valor de la dirección de memoria correspondiente a un dato variable puede
ser asignado o modificado en tiempo de ejecución.
Un programa se corresponde con una transformación de datos. A partir de un contexto
determinado por las precondiciones.
El programa transforma la información debiendo llegar al resultado esperado
produciendo el nuevo contexto caracterizado por las poscondiciones.
Dato
Representación de un objeto el mundo real mediante el cual se pueden modelizar
aspectos de un problema que se desea resolver con un programa en una computadora.

Definición
Dato representación de un objeto el mundo real mediante el cual se pueden modelizar
aspectos de un problema que se desea resolver con un programa en una computadora.
<dato> -> <objeto><atributo><valor>

Abstracción
Proceso de análisis del mundo real con el propósito de interpretar los aspectos
esenciales de un problema y expresarlo en términos precisos.

Modelización
Abstraer un problema del mundo real y simplificar su expresión, tratando de encontrar
los aspectos principales que se pueden resolver, requerimientos, los datos que se han de
procesar y el contexto del problema.

Precondición
Información conocida como verdadera antes de iniciar el programa.

Poscondición
Información que debiera ser verdadera al cumplir un programa, si se cumple
adecuadamente el requerimiento pedido.

Especificación
Proceso de analizar problemas del mundo real y determinar en forma clara y concreta el
objetivo que se desea. Especificar un problema significa establecer en forma univoca el
contexto, las precondiciones el resultado esperado, del cual se derivan las
poscondiciones.

Lenguaje de programación
Conjunto de instrucciones permitidas y definidas por sus reglas sintácticas y su valor
semántico para la expresión de soluciones de problemas.

Del problema real a su solución por computadoras


Analizando un problema del mundo real se llega a la modelización del problema por
medio de la abstracción.
A partir del modelo se debe elaborar el análisis de la solución como sistema, esto
significa la descomposición en módulos. Estos módulos deben tener una función bien
definida.
La modularización es muy importante y no solo se refiere a los procesos a cumplir, sino
también a la distribución de los datos de entrada, salida y los datos intermedios
necesarios para alcanzar la solución.

Estudio de los datos del problema.


Cada módulo debe tener un proceso de refinamiento para expresar su solución en forma
ordenada, lo que llevara a la construcción del algoritmo correspondiente.
A partir de los algoritmos se pueden escribir y probar programas en un lenguaje
determinado y con un conjunto de datos significativos.

Etapas de resolución de problemas con computadoras.


1. Análisis del problema: en su contexto del mundo real.
2. Diseño de la solución: Lo primero es la modularización del problema, es decir la
descomposición en partes con funciones bien definidas y datos propios
estableciendo la comunicación entre los módulos.
3. Especificación del algoritmo: La elección adecuada del algoritmo para la
función de cada modulo es vital para la eficiencia posterior.
4. Escritura del programa: Un algoritmo es una especificación simbólica que debe
convertirse en un programa real sobre un lenguaje de programación concreto.
5. Verificación: una vez escrito el programa en un lenguaje real y depurado los
errores sintácticos se debe verificar que su ejecución conduzca al resultado
deseado con datos representativos del problema real.

Programación modular – programación estructurada


Se dice modular porque permite la descomposición del problema en módulos y
estructurada solo permite la utilización de tres estructuras: Asignación, selección,
repetición.

Algoritmo
El termino algoritmo es en honor del matemático árabe del siglo IX, Abu Jafar
Mohamed ibn Musa Al Khowârizmî. Refiere conjunto de reglas, ordenadas de forma
lógica, finito y preciso para la solución de un problema, con utilización o no de un
computador.
En la actualidad al término se lo vincula fuertemente con la programación, como paso
previo a la realización de un programa de computación aunque en realidad es una
metodología de resolución presente en muchas de las actividades que se desarrolla a lo
largo de la vida.
Desde los primeros años de escuela se trabaja con algoritmos, en especial en el campo
de las matemáticas. Los métodos utilizados para sumar, restar, multiplicar y dividir son
algoritmos que cumplen perfectamente las características de precisión, finitud,
definición y eficiencia.
Para que el algoritmo pueda ser fácilmente traducido a un lenguaje de programación y
luego ser ejecutado la especificación debe ser clara, precisa, que pueda ser interpretada
con precisión y corresponda a pocas acciones, si esto no ocurre será necesario acudir a
desarrollar un mayor nivel de refinamiento.
La utilización de refinamientos sucesivos es lo que permite alcanzar la solución modular
que se propone.
Diseño modular, entonces, es la aplicación del criterio de refinamientos sucesivos,
partiendo de un plan de acción, determinando que hacer, por aplicación de los
conocimientos estratégicos de resolución pasando luego al como hacerlo con los
conocimientos tácticos para la realización del algoritmo.
La programación de algoritmos representa un caso de resolución de problemas que
requiere representación mental del mundo real, adaptación para tener una solución
computable y criterio para elegir una alternativa eficiente de implementación.
Cuando se analiza un problema, particularmente de programación, y éste es difícil de
describir, el plan de acción recomendable para alcanzar la solución es comenzar
trazando un esbozo de las formas más gruesas, para que sirvan de andamio a las demás;
aunque algunas de ellas se deban cambiar posteriormente. Después, se agregan los
detalles, (obteniéndose el algoritmo refinado), para dotar a estos esqueletos de una
estructura más realista.
Durante la tarea de integración final, se descartan aquellas primeras ideas provisionales
que ya no encajan en la solución. Por lo que, hasta que no se haya visto el conjunto
global es imposible encontrarle sentido a ninguna de las partes por sí solas.
Siempre es mejor explicar un misterio en términos de lo que se conoce, pero cuando
esto resulta difícil de hacer, se debe elegir entre seguir tratando de aplicar las antiguas
teorías, o de descartarlas y probar con otras nuevas. Siguiendo este análisis, se define
como reduccionistas a aquellas personas que prefieren trabajar sobre la base de ideas
existentes, y como renovadores a los que les gusta impulsar nuevas hipótesis. En
programación debe encontrarse un equilibrio entre ambas posturas.
La programación como toda actividad que requiere creatividad necesita que se produzca
un salto mental que se puede sintetizar como señala David Perkins en:
1. Larga búsqueda, se requiere esfuerzo en analizar y buscar.
2. Escaso avance aparente: el salto mental sobreviene tras un avance que parece
escaso o no muy evidente, pero sobreviene.
3. Acontecimiento desencadenante: El típico proceso de hacer clic comienza con
un acontecimiento que lo desencadena.
4. Chasquido cognitivo: De pronto aparece la solución la que sobreviene con
rapidez que hace que las piezas encajen con precisión, aun cuando sea necesario
todavía ajustar algunos detalles. Pero la idea generadora apareció.
5. Transformación. Este avance nos va modificando nuestro mundo mental.
En síntesis, la practica del salto de pensamiento requiere en primer lugar de buscar
analogías, en segundo lugar juegan un papel importante las conexiones lógicas,
formulación de una pregunta crucial ocupa un papel decisivo. El repertorio de acciones
tras el salto del pensamiento se expande para incluir no solo la analogía sino una
extrapolación lógica y la formulación de la pregunta adecuada
Para encontrar una solución muchas veces se necesita desplazarse bastante por el
entorno adecuado. Conforme a esto Thomas Edison declaro que la invención significa
99% de transpiración y 1% de inspiración, en contraposición con Platón que sostenía
que las soluciones aparecen por inspiración divina.
Muchos problemas son razonables, cabe razonarlos paso a paso para alcanzar la
solución. Otros son irrazonables no se prestan a un a reflexión por etapas.

Definición
Algoritmo
Especificación rigurosa (debe expresarse en forma univoca) de la secuencia de pasos,
instrucciones, a realizar sobre un autómata para alcanzar un resultado deseado en un
tiempo finito. Esto último supone que el algoritmo empieza y termina, en el caso de los
que no son de tiempo finito (ej. Sistemas en tiempo real) deben ser de número finito de
instrucciones.

En definitiva un algoritmo es una especificación ordenada de la solución a un problema


de la vida real. Son el fundamento de la programación de computadores en el paradigma
de programación imperativo.
Bajo este paradigma desarrollar un programa significa indicarle al computador, con
precisión, sin ambigüedad y en un lenguaje que este pueda entender, todos y cada uno
de los pasos que debe ejecutar para lograr el objetivo propuesto.
Previo a la traducción en un lenguaje de programación es necesario poder entender el
problema, conocer las pre condiciones, establecer cual debe ser la pos condición, o
aquello que debe ser cierto al finalizar la ejecución del algoritmo, en definitiva entender
claramente Que es lo que se debe hacer para luego avanzar en Como hacerlo. Aquí
debe utilizarse todas las herramientas al alcance de la mano para el desarrollo del
algoritmo como paso previo a la solución del problema por el computador.
Existen varias técnicas para representar formalmente un algoritmo, una descriptiva
llamada pseudo código, y otras graficas como los diagrama de flujo, diagrama Nassi
Sneiderman, Diagramas de Lindsay, diagramas de Jackson, entre otros, en este caso se
presentara una notación algorítmica similar a la presentada por Piere Scholl en el texto
Esquemas algorítmicos fundamentales: Secuencia e iteración.

Definición
Algoritmo:
Secuencia finita de instrucciones, reglas o pasos que describen en forma precisa las
operaciones que una computadora debe realizar para llevar a cabo una tarea en tiempo
finito [Knuth, 1968].
Descripción de un esquema de comportamiento expresado mediante un repertorio finito
de acciones y de informaciones elementales, identificadas, bien comprendidas y
realizables a priori. Este repertorio se denomina léxico[Scholl, 1988].
Esta formado por reglas, pasos e instrucciones.
Las reglas especifican operaciones.
La computadora es el agente ejecutor.
La secuencia de reglas y la duración de la ejecución son finitas.

Características de un algoritmo
Un algoritmo debe tener al menos las siguientes características:
1. Ser preciso: esto significa que las operaciones o pasos del algoritmo deben
desarrollarse en un orden estricto, ya que el desarrollo de cada paso debe
obedecer a un orden lógico.
2. Ser definido. Ya que en el área de programación, el algoritmo es el paso
previo fundamental para desarrollar un programa, es necesario tener en cuenta
que el computador solo desarrollará las tareas programadas y con los datos
suministrados; es decir, no puede improvisar y tampoco inventará o adivinará el
dato que necesite para realizar un proceso. Por eso, el algoritmo debe estar
plenamente definido; esto es, que cuantas veces se ejecute, el resultado depende
estrictamente de los datos suministrados. Si se ejecuta con un mismo conjunto
de datos de entrada, el resultado deberá ser siempre el mismo.
3. Ser finito: esta característica implica que el número de pasos de un algoritmo,
por grande y complicado que sea el problema que soluciona, debe ser limitado.
Todo algoritmo, sin importar el número de pasos que incluya, debe llegar a un
final. Para hacer evidente esta característica, en la representación de un
algoritmo siempre se incluyen los pasos inicio y fin.
4. Presentación formal: para que el algoritmo sea entendido por cualquier persona
interesada es necesario que se exprese en alguna de las formas comúnmente
aceptadas; pues, si se describe de cualquier forma puede no ser muy útil ya que
solo lo entenderá quien lo diseñó. Las formas de presentación de algoritmos
son: el pseudo código, diagrama de flujo y diagramas de Nassi/Schneiderman,
entre otras. En esta publicación se propondrá una notación algorítmica y se
darán las equivalencias entre la propuesta y las existentes y también con las
sentencias de los lenguajes de programación, en particular Pascal y C.
5. Corrección: el algoritmo debe ser correcto, es decir debe satisfacer la necesidad
o solucionar el problema para el cual fue diseñado. Para garantizar que el
algoritmo logre el objetivo, es necesario ponerlo a prueba; a esto se le llama
verificación o prueba de escritorio.
6. Eficiencia: hablar de eficiencia o complejidad de un algoritmo es evaluar los
recursos de cómputo que requiere para almacenar datos y para ejecutar
operaciones frente al beneficio que ofrece. En cuanto menos recursos requiere
será más eficiente el algoritmo.
La vida cotidiana está llena de soluciones algorítmicas, algunas de ellas son tan
comunes que no se requiere pensar en los pasos que incluye la solución. La mayoría de
las actividades que se realizan diariamente están compuestas por tareas más simples que
se ejecutan en un orden determinado, lo cual genera un algoritmo. Muchos de los
procedimientos utilizados para desarrollar tareas cotidianas son algorítmicos, sin
embargo, esto no significa que todo lo que se hace está determinado por un algoritmo.
El primer paso en el diseño de un algoritmo es conocer la temática a tratar, el segundo
será pensar en las actividades a realizar y el orden en que deben ejecutarse para lograr el
objetivo, el tercero y no menos importante es la presentación formal.

Propiedades de los algoritmos


1. Especificación precisa de la entrada: El algoritmo debe dejar claro el número y
tipo de datos de entrada y las condiciones iniciales que deben cumplir esos
valores de entrada para conseguir que las operaciones tengan éxito.
2. Especificación precisa de cada instrucción: cada etapa del algoritmo debe estar
definida con precisión, no debe haber ambigüedades sobre las acciones que se
deben ejecutar en cada momento.
3. Un algoritmo debe ser exacto y correcto: Un algoritmo se espera que resuelva un
problema y se debe poder demostrar que eso ocurre. Si las condiciones de
entrada se cumplen y se ejecutan todos los pasos el algoritmo entonces debe
producir la salida deseada.
4. Un algoritmo debe tener etapas bien definidas y concretas, un número finito de
pasos, debe terminar y debe estar claro la tarea que el algoritmo debe ejecutar.
5. Debe ser fácil de entender, codificar y depurar.
6. Debe hacer uso eficiente de los recursos de la computadora

Finitud: en longitud y duración.


Precisión: Determinar sin ambigüedad las operaciones que se deben ejecutar.
Efectividad: las reglas pueden ejecutarse sin el ordenador obteniéndose el mismo
resultado.
Generalidad: Resolver una clase de problema y no un problema particular.
Entradas y salidas: puede tener varias entradas pero una sola salida, el resultado que se
debe obtener.
Eficiencia de un algoritmo
Se pueden tener varias soluciones algorítmicas para un mismo problema, sin embargo el
uso de recursos y la complejidad para cada una de las soluciones puede ser muy
diferente.
La eficiencia puede definirse como una métrica de calidad de los algoritmos asociada
con la utilización optima de los recursos del sistema de cómputo donde se ejecutara el
algoritmo, su claridad y el menor grado de complejidad que se pueda alcanzar. Hacer
todo tan simple como se pueda, no más (Albert Einstein).
La eficiencia como factor espacio temporal debe estar estrechamente relacionada con la
buena calidad, el funcionamiento y la facilidad del mantenimiento.
Medidas de eficiencia para N = 10.0000
Eficiencia Iteraciones Tiempo estimado
Logarítmica Log 2 N 14 Microsegundos
Lineal N 10.000 0.1 segundo
Logarítmica lineal N * Log 2 N 140.000 2 segundos
Cuadrática N2 10.000 2 15-20 minutos
k K
Poli nómica N 10.000 Horas
Exponencial 2N 2 10.000 Inmedible

Complejidades más comunes


1. Complejidad constante: se expresa como O(1). Se encuentra en algoritmos sin
ciclos, por ejemplo en un intercambio de variables.
2. Complejidad logarítmica: Es una complejidad eficiente, la búsqueda binaria
tiene esta complejidad.
3. Complejidad lineal: se encuentra en los ciclos simples.
4. Complejidad logarítmica lineal: Los mejores algoritmos de ordenamiento tienen
esta complejidad.
5. Complejidad cuadrática: Aparece en el manejo de matrices de dos dimensiones,
generalmente con dos ciclos anidados.
6. Complejidad cúbica: Aparece en el manejo de matrices de tres dimensiones,
generalmente con tres ciclos anidados.
7. Complejidad exponencial: es la complejidad de algoritmos recursivos.

Léxico y algoritmo
Para escribir un algoritmo deben seguirse un conjunto de pasos básicos
1. Comprender el problema
2. Identificar los elementos a incluir en el léxico: constantes, tipos, variables y
acciones.
3. Encontrar la forma de secuenciar las acciones para obtener el resultado, esto es,
alcanzar las poscondiciones a partir de un estado inicial que cumple con la
precondición. Para establecer el orden de las acciones los lenguajes de
programación proporcionan mecanismos de composición: Secuenciación,
análisis de casos, iteración y reexcursión.
4. Al organizar las acciones en el tercer paso puede ocurrir que se detecte que
faltan elementos en el léxico o que algún aspecto del problema no ha sido bien
comprendido lo cual requeriría volver a los pasos anteriores.
5. Nunca la solución aparece en el primer intento, en general aparece en un proceso
cíclico, entonces se debe:
6. Escribir el léxico,
a. escribir la primera versión,
b. incluir en el léxico nuevos elementos que faltaban,
c. escribir la nueva versión del algoritmo y así sucesivamente

Estructura de un algoritmo
LEXICO {Léxico Global del algoritmo}
{Declaración de tipos, constantes, variables y acciones}
Acción 1
PRE {Precondición de la acción 1}
POS {Poscondición de la acción 1}
LEXICO {Léxico local, propio de la acción 1}
Declaraciones locales
ALGORITMO {Implementación de la acción 1}
{Secuencia de instrucciones de la acción 1}
FIN {Fin implementación algoritmo de la acción 1}

ALGORITMO
PRE {Precondición del algoritmo principal}
POS {Poscondición del algoritmo principal}
{Secuencia de instrucciones del algoritmo principal}
FIN {Fin del algoritmo principal}

Proceso Computacional
Se refiere a un algoritmo en ejecución. La ejecución de las instrucciones origina una
serie de acciones sobre elementos de memoria que representan información manejada
por el algoritmo. A nivel algorítmico se asigna un nombre a cada información de modo
de manejar un par nombre-valor para cada información.
Una variable representa alguna entidad del mundo real, relevante para el problema que
se quiere resolver. El efecto que producen las acciones del proceso sobre las variables
produce cambio de estados o de sus valores.

Definiciones

Programa: Algoritmo escrito en un lenguaje cuyas instrucciones son ejecutables por


una computadora y que están almacenados en un disco.
Tarea: Un programa se vuelve tarea a partir del momento que se lo selecciona para su
ejecución y hasta que esta termina.
Proceso: programa en ejecución, se ha iniciado pero aún no ha finalizado.
Lenguajes de programación: notación que permite escribir programas a mayor nivel
de abstracción que los lenguajes de máquina. Sus instrucciones deben ser traducidas a
lenguaje de máquina.
Lenguaje de máquina: Instrucciones que son ejecutables por el hardware de una
computadora.
Paradigmas de programación
Paradigma: Colección de conceptos que guían el proceso de construcción de un
programa. Estos conceptos controlan la forma en que se piensan y formulan los
programas.
Imperativo – Procedural – Objetos.
Declarativo – Funcional – Lógico.
Dato Información Conocimiento
Dato: <objeto><atributo><valor> sin interpretar.
Información: añade significado al dato.
Conocimiento: Añade propósito y capacidad a la información. Potencial para generar
acciones.
Problema
Enunciado con una incógnita, la solución es encontrar el valor de esa incógnita.
Problema computacional o algorítmico: tarea ejecutada por una computadora con una
especificación precisa de los datos de entrada y de los resultados requeridos en función
de estos.
Clase de problemas
No computables: No existe un algoritmo.
Computables
Tratables: Existe un algoritmo eficiente.
Intratable: No existe algoritmo eficiente.
Expresiones Sentencias Léxico
Expresiones: secuencia de operadores y operandos que se reduce a un solo valor.
Sentencias: acción produce un efecto, puede ser primitiva o no primitiva.
Léxico: Descripción del conjunto de acciones e informaciones a partir de la cual se
expresa el esquema de comportamiento del algoritmo.

Pasos para resolver un algoritmo


Comprender el problema.
Identificar información y acciones a incluir en el léxico (constantes, tipos, variables y
acciones).
Encontrar un camino de secuenciar las acciones para obtener el resultado, es decir para
alcanzar la poscondición a partir del estado inicial que cumple con la precondición.
Acciones primitivas y derivadas
Acciones primitivas: Incorporadas por el lenguaje.
Acciones derivadas: realizadas mediante la combinación de acciones primitivas con el
objeto de desarrollar una tarea en particular. Son complementarias y pueden ser
desarrolladas por el programador.

Estructura de un algoritmo
LEXICO {Léxico Global del algoritmo}
{Declaración de tipos, constantes, variables y acciones}
Acción 1
PRE {Precondición de la acción 1}
POS {Poscondición de la acción 1}
LEXICO {Léxico local, propio de la acción 1}
Declaraciones locales
ALGORITMO {Implementación de la acción 1}
{Secuencia de instrucciones de la acción 1}
FIN {Fin implementación algoritmo de la acción 1}

ALGORITMO
PRE {Precondición del algoritmo principal}
POS {Poscondición del algoritmo principal}
{Secuencia de instrucciones del algoritmo principal}
FIN {Fin del algoritmo principal}
Resumen:
En el presente capitulo se introdujeron términos y frases de la disciplina en estudio.
Se abordo el problema desde un punto de vista conceptual definiendo con precisión
términos y frases para evitar ambigüedades. Se puso especial énfasis en la necesidad de
pensar las soluciones antes de escribir código. Se establecieron cuales son los pasos que
deben seguirse para una solución correcta de problemas y cuales son los problemas que
pueden tratarse en forma algorítmica. Se definió cuales son las características deseables
de los algoritmos y se introdujo el concepto de eficiencia de modo de hacer, tal como
recomendaba Albert Einstein, las cosas tan simple como se pueda.
Se introdujo, además una representación sem. formal para la descripción de los
algoritmos
CAPITULO 2 Asignación - Control – Iteración

Objetivos de aprendizaje
Dominando los temas del presente capitulo Usted podrá.
1. Conocer las acciones simples que permiten capturar o mostrar los datos.
2. Encontrar en la secuenciación el camino simple para la resolución de
problemas
3. Dividir el dominio de los datos en subdominios excluyentes y
abarcativos.
4. Descubrir la necesidad de la repetición.
5. Verificar que es posible repetir acciones en formas diferentes y para
distintos conjunto de secuencias.

Introducción:
Se presenta en este capitulo un primer criterio de descomposición del problema para
poder alcanzar la solución del mismo. Se establece la secuenciación y el análisis de
casos como primer mecanismo correcto para la solución de problemas.
Habitualmente se observa que aquellos jóvenes que se acercan por primera vez a la
resolución de problemas de programación encuentran muchas dificultades para
organizar su pensamiento y encontrar una solución algorítmica.
No es simple comprender como se razona cuando se diseña un algoritmo o cual es el
mecanismo que permite extraer de nuestro conocimiento la técnica que nos guíe a la
solución de los mismos.
Una de las técnicas que se dispone para dominar la complejidad de los problemas es la
descomposición en subproblemas que pueden abordarse por separado, utilizando una de
las premisas básicas en la programación que es “dividir para vencer”. Cada problema
puede entonces verse como un conjunto de módulos, vinculados y relacionados
adecuadamente. Estos módulos utilizan mecanismos como la secuenciación, análisis de
casos, composiciones iterativas exactas, condicionales y recursivas para alcanzar la
solución.

Asignación, secuenciación y análisis de casos


Las instrucciones o sentencias representan las operaciones que ejecuta la computadora
al implementar un algoritmo. Estas instrucciones utilizan cierta información en forma de
expresiones que se resumen en un valor y que pueden ser constantes o variables.
Todos los lenguajes de programación tienen un conjunto mínimo de instrucciones que
son asignación, análisis de casos y repeticiones.

Asignación.
Asignar significa almacenar valores, constantes o variables, en una determinada
dirección de memoria, este valor puede ser almacenado desde una instrucción del
programa u obtenido o derivado hacia algún dispositivo externo.

Asignación Interna. Almacenar un valor desde una instrucción del programa.


NombreDelIdentificador  Valor;
Almacena en NombreDelIdentificador el valor. Valor puede corresponder a un dato
simple o a una expresión (operadores y operandos) la precondición es que el tipo de
dato de valor debe corresponder al del identificador.

Asignación Externa de entrada.


Almacena en una dirección de memoria un valor obtenido desde un dispositivo o flujo
de datos externo, el dispositivo estándar es el teclado pero es posible obtener datos
almacenados en archivos, en general esta acción recibe el nombre de leer.

Agnación externa de salida.


Obtiene el valor contenido en una dirección de memoria y lo envía o deriva a algún
dispositivo o flujo externo. El dispositivo estándar es la pantalla pero la salida puede ser
direccionada a la impresora o un archivo (esta acción se llama grabar en un archivo).

Análisis de casos
Al no ser posible resolver problemas con instrucciones secuenciales puras, muchas
veces es necesario tener que tomar decisiones en función de los datos del problema,
para ello se cuentan con instrucciones que permiten analizar alternativas y decidir que
camino seguir en cada caso.

Estructura de decisión Simple. Es la estructura básica de decisión entre dos


alternativas. Evalúa una expresión condicional o condición. Entendiéndose por
expresión condicional aquella expresión en la que tiene sentido preguntar por su valor
de verdad. Es una afirmación que puede ser verdadera o falsa y se establecen con
identificadores vinculados con valores o expresiones a través de operadores de relación
con igual, mayor, menor, menor o igual, mayor o igual o distinto.

Estructura de decisión simple Incompleta. Solo procesa acciones en el caso de ser


verdadero el resultado de la expresión, no procesa instrucciones en el caso de ser falsa.

Formato Efecto
SI Condición Si la expresión toma el valor verdadero entonces se ejecuta
ENTONCES la secuencia S si es falso ejecuta la acción siguiente a la
S instrucción en caso de existir.
FIN_SI

Estructura de decisión simple completa. Procesa acciones por la condición de


verdadera y acciones distintas en caso de ser falso.

Formato Efecto
SI Condición Si la expresión toma el valor verdadero entonces se ejecuta
ENTONCES la secuencia S si es falso se ejecuta la secuencia R.
S
SI_NO
R
FIN_SI

Estructura de decisión Múltiple. Es una estructura de selección entre varios valores,


estos valores deben corresponder a variables de tipo ordinal y se pueden determinar
acciones distintas según los valores del ordinal. Pueden reemplazar en forma eficiente a
estructuras de decisión simple que evalúan expresiones booleanas anidadas que con
tipos de datos ordinales (caracteres o enteros).

Estructuras de decisión múltiple Incompleta. Solo realiza acciones para la lista de


valores determinada. No realiza acción en caso de que el ordinal no corresponda a un
valor definido.

Formato Efecto
SEGÚN expr Según el valor Vi de la expresión ordinal expr, se ejecuta la
V1 : S1 secuencia Si(i = 1 ..n). Cualquier valor no especificado no
V2 : S2 tiene acción asociada
FIN_SEGÚN

Estructura de decisión múltiple Completa. Agrega una cláusula para realizar acciones
particulares en caso de que el ordinal no se corresponda con ninguno de los valores
determinados.

Formato Efecto
SEGÚN expr Según el valor Vi de la expresión ordinal expr, se ejecuta la
V1 : S1 secuencia Si(i = 1 ..n). Cualquier valor no especificado
V2 : S2 realiza la secuencia Sn.
EN_OTRO_CASO : Sn
FIN_SEGÚN

Ejemplos con uso de acciones primitivas de asignación, entrada- salida y


declaraciones de acciones y léxico

Calculo de la nota final de una materia


LEXICO
NotaPrimerParcial : Real
NotaSegundoParcial: Real
NotaTrabajoPractico: Real
NotaFinal: Real
ALGORITMO
Leer(NotaPrimerParcial, NotaSegundoParcial, NotaTrabajoPractico)
NotaFinal  (NotaPrimerParcial + NotaSegundo Parcial) / 2
Mostrar(NotaFinal, NotaTrabajoPractico)
FIN

Conversión de grados Fahrenheit en grados Celcius


LEXICO
TempFahrenheit : Real {dato la temperatura en grados Fahrenheit}
TempCelcius: Real {Resultado, temperatura en grados celsius}
CERO_ABSOLUTO = -459.58 {Constante el cero absoluto de temperatura}
ConvertirFahrenheitCelcius : UnaAccion
PRE {TempFaherenheit >= CERO_ABSOLUTO}
POS {Convierte a grados Celsius la Temperatura TempFahrenheit}
ALGORITMO
TempCelcius  (5.0 /9.0) * TempFahrenheit – 32.0)
FIN.
ALGORITMO
Ler(TempFahrenheit)
ConvertirFahrenheitCelcius
Escribir(“La temperatura es :”, TempCelcius
FIN

Ejemplos con análisis de casos


PROBLEMA: Desarrollar un programa que simule una calculadora simple. Que
ingrese dos operandos y un operador. Según el operador que muestre la suma, resta,
multiplicación o división
El analisis de caso no evalua la posibilidad de
LEXICO en otro caso, por lo tanto se deber cumplir
Operando1, Operando2 : Entero; con la precondición que el operador debe
Operador : Carácter; estar entre uno de los caracteres validos. El
operador de division que se utiliza s el
ALGORITMO operador div que retorna la parte entera de la
Leer (Operando1, Operando2, Operador); division de dos enteros
SEGÚN Operador
Operador = ´+´ : Escribir (Operando1 + Operando2)
Operador = ´-´ : Escribir (Operando1 - Operando2)
Operador = ´*´ : Escribir (Operando1 * Operando2)
Operador = ´/´ : Escribir (Operando1 Div Operando2)
FIN_SEGUN
FIN

Solución alternativa utilizando acción en otro caso


LEXICO En este caso se realizan acciones en caso
Operando1, Operando2 : Entero; que el operador sea distinto a los
Operador : Carácter; permitidos, por lo que si no se cumple con
la precondición es el programa quien
ALGORITMO informa de una situación irregular o no
Leer (Operando1, Obrando, Operador); deseada
SEGÚN Operador
Operador = ´+´ : Escribir (Operando1 + Operando2)
Operador = ´-´ : Escribir (Operando1 - Operando2)
Operador = ´*´ : Escribir (Operando1 * Operando2)
Operador = ´/´ : Escribir (Operando1 Div Operando2)
EN_OTRO_CASO : Escribir(“Operador no valido”)
FIN_SEGUN
FIN.

PROBLEMA: Comprobar si una fecha es correcta

LEXICO
dia, mes, año : Entero >0 ;
EsBisiesto, FechaValida : Booleano ;
AñoBisiesto : una acción
POST {AñoBisiesto V si año es bisiesto F en caso contrario}
ALGORITMO
EsBisiesto  (año Mod 400 = 0) o ((año Mod = 0)y(año mod 100<>0))
FIN AñoBisiesto
ALGORITMO
Leer (dia, mes, año);
SI (dia < 1 )
ENTONCES FechaValida  Falso
SI_NO
SEGÚN mes
mes = 1,3,5,7,8,10,12 : FechaValida  dia <= 31
// si los meses son los que se enumeran y el dia menor o igual a 31 es valida//
mes = 4,6,9,11 : FechaValida  dia <= 30
//en estos meses para ser valida el dia debe ser menor o igual a 30//
mes = 2 : AñoBisiesto; FechaValida dia <=28 o (AñoBisiesto y
dia<=29)
// en febrero debe verificar si el año es o no bisiesto//
EN_OTRO_CASO : FechaValida  Falso
// de no ocurrir nada de lo anterior, la fecha es falsa, no es una fecha valida//
FIN_SEGUN
FIN_SI
Escribir (´La fecha ´, dia, ´/´, mes,´/´.año, ´ : ´);
SI FechaValida
ENTONCES Escribir (“Es Valida”)
SI_NO Escribir(“No es valida”)
FIN_SI
FIN

A los efectos de simplificar la notación de los análisis de casos en una única forma de
escritura se podría utilizar la notación SI, con las variantes que correspondan y con la
sentencia equivalente según el lenguaje de programación.[J.D. Muchnik]

Formato Propuesta JDM


SI Condición Si Condición
ENTONCES S
S FIN_SI
FIN_SI

Formato Propuesta JDM


SI Condición SI
ENTONCES Condicion1 S;
S Condicion2 R;
SI_NO FIN_SI
R
FIN_SI

Formato Propuesta JDM


SEGÚN expr SI
V1 : S1 Condicion1 S1;
V2 : S2 Condicion2 S2;
FIN_SEGÚN FIN_SI

Formato Propuesta JDM


SEGÚN expr SI
V1 : S1 Condicion1 S1;
V2 : S2 Condicion2 S2;
EN_OTRO_CASO : Sn CondicionB Sn;
FIN_SEGÚN FIN_SI

Solución alternativa utilizando la propuesta Jorge Muchnik [JDM]


LEXICO
Operando1, Operando2 : Entero;
Operador : Carácter;
ALGORITMO
Leer (Operando1, Oprando, Operador);
SI
(Operador = ´+´) Escribir (Operando1 + Operando2);
(Operador = ´-´) Escribir (Operando1 - Operando2);
(Operador = ´*´) Escribir (Operando1 * Operando2)
(Operador = ´/´) Escribir (Operando1 Div Operando2)
FIN_SI
FIN.

La necesidad de iterar
Supongamos que de un determinado curso se dispone de cada uno de los alumnos de su
nota y se requiere conocer la nota promedio del curso.
Para ello será necesario conocer la nota de cada estudiante, se debe saber además la
suma de todas las notas de los estudiantes y, para calcular el promedio, es
imprescindible conocer la cantidad de estudiantes.
Podríamos pensar en un algoritmo con una estructura como la siguiente
LÉXICO
V : Real //Contiene la nota leída
S : Real //Contiene la sumatoria de las notas
N : Entero //Contiene el numero de alumnos
ALGORITMO
S <- 0 ;
N <- 0 ;
Leer (V) ;
S <- S + V ; //Suma las notas
N <- N + 1 ; // Cuenta los alumnos
Este bloque debe repetirse una cantidad de veces que no se puede determinar con
precisión, seria necesario escribir un texto diferente para cada caso particular, hasta
podría llegarse al extremo de tener que escribir un texto infinito, cosa imposible en la
implementación. El infinito se lo puede pensar desde un punto de vista teórico pero no
es posible implementarlo. Habíamos mencionado entre las características de los
algoritmos su finitud en tiempo y en instrucciones.
La necesidad de un mecanismo de iteración es clara si pensamos en computadoras como
maquinas de gran utilidad porque automatizan tareas, si solo se dispusiera de
composición secuencial o análisis de casos estaríamos realmente limitados y los
programas deberían tener una gran extensión para ejecutar muy pocas acciones si estas
se repiten.

Secuencias
Algunas operaciones tienen que ver con secuencia de datos. Una secuencia es una
colección de elementos del mismo tipo. En ella se puede aplicar algún tratamiento a
cada uno de los elementos que componen la secuencia o buscar alguno que cumpla con
cierta propiedad.
Las secuencias pueden representarse de diferentes formas y se caracteriza por el acceso
secuencial a cada elemento. Esto es, para acceder al elemento de la posición p, hay que
recorrer los p-1 elementos anteriores.
La secuencia debe contar con un elemento característico que indique que se ha
alcanzado el final de la misma en forma correcta. Esta marca puede determinarse según
una especificación precisa de la cantidad de elementos o por un valor particular que
indique la finalización. Las secuencias están vinculadas con las composiciones iterativas
y la reursion

Composiciones iterativas
Los componentes de una iteración son
1. Inicialización: instrucciones que se ejecutan para inicializar las variables que
participan de la iteración.
2. Condición de terminación: expresión booleana que determina cuando acaba la
iteración.
3. Cuerpo: conjunto de instrucciones que se ejecutan mientras no se cumple la
condición de terminación.
4. Finalización: conjunto de instrucciones que deben ejecutarse cuando la iteración
termina.
Se presentan dos composiciones iterativas en las que la finalización viene determinada
por una condición. Estas son la composición MIENTRAS, REPETIR. Lo que las
diferencia es el lugar donde se comprueba la condición: al principio o al final. Y
también si es una condición de terminación o de continuación. Existe además
composiciones iterativas en la que la finalización esta dada por la cantidad de elementos
que se deben evaluar

Diseño iterativo: noción de invariante


El invariante de un ciclo, INV, es una condición que se cumple al inicio y a cada paso de
la iteración. Cuando finaliza la iteración, el hecho de que se satisfaga el invariante y la
condición de terminación implica que se alcanzó la solución.
Dada una especificación de un problema de recorrido de secuencias, la estrategia de una
solución iterativa consta de los siguientes pasos:
1. Identificar qué variables son necesarias a partir de la poscondición.
2. Establecer el invariante del ciclo.
3. Aplicar un razonamiento inductivo.
4. Escribir el algoritmo iterativo.
La composición iterativa es el medio que permite escribir programas cortos que
involucran gran cantidad de acciones.
Existen varios formatos de iteración que varían entre lenguajes pero que, en general
responden a tres modelos de repetición: repetir mientras se cumpla una condición,
repetir hasta que se cumpla una condición y repetir una determinada cantidad de veces
Repetir mientras se cumpla una condición
MIENTRAS condición HACER
Acción
FIN_MIENTRAS
Repetir hasta que se cumpla una condición
REPETIR
acción
HASTA_QUE Condición
Repetir N veces
PARA i [1..n] HACER
acción
FIN_PARA

Composiciones iterativas No Exactas


No exacta o condicionales. Como vimos con las composiciones mientras y repetir,
puede ocurrir que un bloque de instrucciones se deba ejecutar desconociendo el numero
exacto de veces, en estos casos los lenguajes ofrecen estructuras de control iterativas
condicionales en las que las acciones se ejecutan dependiendo de la evaluación de una
condición.
Precondicionales

Formato Efecto
Mientras Cond. Hacer Ejecutar la secuencia S mientras la condición Cond tenga
S el valor de verdadero
FIN_MIENTRAS

Pre-condicionales. Estas evalúan la condición y si es verdadera se ejecuta el bloque de


acciones. Esto hace que dicho bloque pueda ejecutarse 0, 1, o varias veces. Es necesario
una asignación previa a la variable de la expresión lógica para poder evaluarla y una
asignación dentro del ciclo para evitar ciclos infinitos. Debe haber una asignación que
permita hacer falsa a la expresión lógica para salir del ciclo.

Poscondicionales

Formato Efecto
REPETIR Ejecutar la secuencia S hasta que la expresión Cond tome
S el valor de verdadero
HASTA Cond

Pos-condicionales. Primero se ejecuta el bloque de acciones y luego se evalúa la


condición, si es falsa se ejecuta nuevamente el bloque de acciones el ciclo finaliza
cuando la condición toma el valor de verdadero. A diferencia de la pre-condicional este
ciclo se ejecuta siempre, al menos una vez.

Composiciones iterativas Exactas

Formato Efecto
PARA i [Vi..Vf] HACER Ejecutar la secuencia S (Vf – Vi + 1) veces.
S
FIN_PARA

Exacta. Una forma natural de esta estructura consiste en repetir un bloque de acciones
una cantidad fija de veces o conocido de antemano.
Tiene definido un contador o índice como una variable de tipo ordinal que se
incrementa en una cantidad constante con cada iteración y permite controlar el numero
de veces que se ejecutara el ciclo.
En cada iteración el índice toma el valor siguiente de acuerdo al tipo de valor asociado
al ordinal. Hay lenguajes que solo permiten que la variación (que puede ser positiva o
negativa) sea solamente 1, y otros lenguajes que pueden modificar ese incremento. De
todas formas la variable índice se actualiza con cada iteración y se testea en cada ciclo
respecto del valor final deseado.
Si el incremento es uno la cantidad de repeticiones esta dada por:
Cantidad de repeticiones = Valor final del Índice – Valor inicial del Índice + 1.
En el caso de que valor inicial y valor final coincidan se ejecuta 1 vez, en el caso de que
el valor inicial es mayor que el final y por cada iteración se incrementa el índice el ciclo
no se ejecutara y no producirá errores en tiempo de ejecución.

El ejemplo planteado utilizando composición iterativa


LÉXICO
V : Real //Contiene la nota leída
S : Real //Contiene la sumatoria de las notas
N : Entero //Contiene el numero de alumnos
ALGORITMO // Composición iterativa mientras
S0;
N <- 0 ;
Leer (V) // asignación previa del dato de la expresión lógica
MIENTRAS V > 0 HACER
S  S + V ; //Suma las notas
N  N + 1 ; // Cuenta los alumnos
Leer (V) ;
FIN_MIENTRAS
SI N > 0
ENTONCES
Escribir (¨Promedio de las notas ¨, S/N)
SI_NO
Escribir (“No hay valores para procesar”)
FIN.

La composición iterativa mientras puede no ejecutarse por lo que es necesario al final del
proceso verificar si hubieron valores, el valor de n se puede utilizar para verificar esa condición.
ALGORITMO // Composición iterativa repetir hasta
S0;
N0;
Leer (v) // El primer valor leído debe ser valido ya que se evalúa
REPETIR
S  S + V ; //Suma las notas
‘ N  N + 1 ; // Cuenta los alumnos
Leer (V) ;
HASTA_QUE V = 0 //Cundo esto es verdadero finaliza la iteración
Escribir (¨Promedio de las notas ¨, S/N)
No requiere validar ya que al menos hay un valor
ALGORITMO // Composición iterativa para
S0;
N0;
Leer (N) // Lectura de la cantidad de elementos que contiene el lote de datos
PARA I [1..N] HACER
Leer (V)
S  S + V ; //Suma las notas
‘ N  N + 1 ; // Cuenta los alumnos
FIN_PARA
SI N > 0
ENTONCES
Escribir (¨Promedio de las notas ¨, S/N)
SI_NO
Escribir (¨No hay valores para procesar¨)

La composición iterativa para puede no ejecutarse ya que el valor leído de N puede ser
cero, en ese caso no corresponde la iteración por lo que es necesario al final del proceso
verificar si hubieron valores, el valor de n se puede utilizar para verificar esa condición.

Recursividad
La repetición puede relacionarse con el concepto de inducción matemática, este tipo de
razonamiento permite demostrar una proposición que depende de cierto parámetro N,
normalmente en el rango de los números naturales, una forma de demostrar si una
proposición P es cierta para todo natural N es:
Se demuestra que es cierta para N=0, inicio de la inducción o caso base
Se asume que P es cierto para n-1, hipótesis de inducción
Entonces se demuestra que también lo es para n, relación de inducción
También se puede utilizar la inducción para realizar definiciones matemáticas de
funciones y sucesiones.
Por ejemplo se puede definir el factorial introduciendo el valor cuando n = 0 e
introduciendo una relación para n > 0 a partir del factorial de n-1
0! = 1
N! = N * (N – 1)!
La primera igualdad corresponde al caso base, la siguiente es la relación d inducción
Otro ejemplo Es la conocida sucesión de Fibonacci
Fibo(0) = 1
Fibo(1) = 1
Fibo(N) = Fibo(n-1) + Fibo(N-2) para N>1
Los primeros valores de la serie son 1, 1, 2, 3, 5, 8, 11, 19, 21,......
Una función con sentencias entre las cuales se encuentra una llamada a si misma se dice
que es recursiva. Existen muchas aplicaciones matemáticas y computacionales de la
recursividad. Numerosas circunstancias de la vida común tiene naturaleza recursiva
Se debe tener en cuenta
1. Define el problema en términos de un problema más simple de la misma
naturaleza.
2. Debe disminuir el espacio del problema en cada llamada recursiva
Hay una instancia particular que se conoce como caso base o caso degenerado
3. Divide el problema original en subproblemas más pequeños. Cuando es lo
suficientemente chico se resuelve directamente y se combinan soluciones del
subproblema hasta que queda resuelto el problema

HANOI
Este es un juego que tiene su origen en la cultura oriental y en una leyenda sobre el
templo de Brama, cuya estructura simulaba una plataforma metálica con tres varillas y
discos en su interior. El problema suponía tres varillas Inicial, Central y Final en la que
se alojaban discos que podían trasladarse de una varilla a otra con la condición que cada
disco era ligeramente inferior en diámetro al que estaba justo debajo de el. Los discos
originalmente están en la varilla Inicial y se los desea trasladar a la final cumpliendo en
todos los pasos la condición del tamaño de los discos, el superior ligeramente menor al
inferior.
El analisis es simple desde el punto de vista
recursuivo es simple, si solo queda un disco en
Si n = 1 ENTONCES el poste inicial este puede ser movido
directamente al disco final. Si quedaran mas
de uno habria que mover los n-1 anteriores
del inicio al medio para poder mover el ultimo
al fina. Después habra que colocar todos los
del medio en el fina, esto recursivamentel
Mover n de I a F
SI_NO
Mov n-1 de I a C usando F
Mover n de I a F
Mover n-1 de C a F usando I
FIN_SI

El flujo de control de una función recursiva requiere tres condiciones para una
terminación normal:
1. Un test para detener o continuar con la recursion.
2. una llamada recursiva para continuar la recursion, con un problema de la misma
naturaleza pero de menor complejidad.
3. un caso base para terminar la recursion

ALGORITMO
SI Es el caso base
ENTONCES
Ejecutar la acción final
Terminar con la recursion
SI_NO
Disminuir el espacio del problema
Volver a invocar a la función
FIN_SI

Iteración vs. Recursion


Considerar solución recursiva solo si una solución iterativa sencilla no es posible.
Solo utilice recursividad dentro de limites aceptables de ejecución y uso de memoria.
Si son posibles las soluciones iterativas y recursivas esta ultima requerirá mas tiempo y
mas recursos de memoria.
En ciertos problemas la recursion produce soluciones mas simples de leer. Los
beneficios de la claridad compensan el costo extra
Ambas implican repetición. Iteración explícitamente y recursion con llamadas repetidas.
Ambas consideran un test de salida. Iteración en la condición, recursividad en el caso
base.

Muchas veces la resolución de problemas exige probar sistemáticamente todas las


posibilidades que pueden existir para encontrar la solución.
En este caso se utilizan algoritmos de vuelta atrás o backtraking, que utilizan la
recursividad para probar cada una de las posibilidades hasta encontrar la solución.
Una de las características principales de estos algoritmos es la búsqueda exhaustiva con
todas las posibilidades de soluciones parciales que conducen a la solución del problema,
otra característica es la vuelta atrás, en el sentido que si una solución parcial no conduce
a la solución total del problema se vuelve atrás para ensayar con otra solución posible
de solución.
El esquema general de estos algoritmos responden a

acción EnsayarSolucion
ALGORITMO
<Inicializar cantidad de posibles soluciones>
REPETIR
<tomar la siguiente selección>
<determinar si es selección valida>
SI Valido
ENTONCES
<anotar selección>
SI Problema Solucionado
ENTONCES
Éxito = Verdadero
SINO
EnsayarSolucion llamada para realizar otra tarea
SINO Éxito
ENTONCES
<Borrar anotación y probar otra solución>
FINSI
FINSI
FINSI
HASTA Éxito O <No mas posibilidades>
Si sale por éxito habrá encontrado la solución, si agota las posibilidades sin éxito, el
problema no tiene solución
FIN.

Algoritmos que utilizan secuencias


Algoritmos de búsqueda.
Dada una colección de elementos es posible extraer dos tipos diferentes de información:
Información relativa al conjunto de los datos que forman la colección
Información detallada de algún ítem particular
En el caso en que se requiera información particular de un determinado ítem de la
colección este debe ser ubicado y, eventualmente extraído de la misma para su
tratamiento particular.
El proceso de ubicar información particular en una colección de datos es conocido como
algoritmo de búsqueda.
Los procesos de búsqueda que consisten en ubicar un elemento particular deben
suministrar alguna información del resultado de la búsqueda. Existen búsquedas con
eficiencia optima (Ef =1), cuando se conoce el lugar preciso donde el dato se encuentra
y se denomina búsqueda directa, otras búsquedas de eficiencia logarítmica las que
reducen el espacio del problema a la mitad con cada intento llamada búsqueda binaria o
dicotomica, y existe una búsqueda menos eficiente de orden N donde n representa la
cantidad de elementos de la lista. El desarrollo de estos procesos será visto en capítulos
posteriores.

Máximos y mínimos
En lo que hace a la información relativa a un conjunto de datos puede ser necesario
encontrar cual o cuales de los elementos de ese conjunto cumplen con ciertas
características. Por ejemplo si se tiene información de los nombres de los estudiantes y
sus calificaciones puede ser posible conocer el nombre del mejor estudiante, según el
valor de sus notas, o conociendo el nombre de los atletas y el tiempo empleado en una
carrera de velocidad puede requerirse conocer el ganador. En el primer caso se busca al
estudiante cuyo promedio es el mayor, en el otro caso se busca el atleta cuyo tiempo es
el menor. Existen algoritmos puntuales que permiten buscar máximos y mínimos.
Buscar un máximo
Datos e entrada L1 ... L n Lista de n elementos
Datos de salida M identificador que contendrá al valor máximo
N pertenece a los números Naturales
N > 0 Por lo menos hay un elemento en la lista
Li pertenece a los números racionales para todo 1<= i <= n
Poscondición
M pertenece a los números racionales
m>=li para todo 1<=i<=n
LÉXICO
Valor : Entero // Identificador que contendrá las lecturas
Máximo : Entero // Identificador que contendrá el máximo del conjunto
Esta acción le asigna el primer valor al máximo que puede ser el primer valor
leído, si es que se tiene o un valor arbitrario. El valor arbitrario puede ser un valor
razonablemente bajo para que cualquier dato valido lo pueda desplazar o un valor
utilizado como valor centinela para indicar que corresponde a la primer lectura.
FIN Para inicializar el identificador que contendra el maximo se
ALGORITMO puede hacer con el primer valor leido o con un valor
Leer(Valor) ; razonablemente alto dentro del contexto delprpblema
Máximo,valor ;
MIENTRAS haya datos HACER
SI Valor > Máximo
ENTONCES
Máximo  valor
FIN_SI
FIN_MIENTRAS
SI hubodatos
ENTONCES
Escribir(¨El máximo del conjunto es ¨, máximo)
SI_NO
Escribir(¨No hubo datos para procesar¨)
FIN_SI
FIN
La búsqueda de un máximo requiere:
1. Determinar el conjunto de datos para poder definir que tipo de composición
iterativa es la mas adecuada para el problema planteado
2. Definir al menos dos identificadores del mismo tipo de dato, uno para las
sucesivas lecturas y el otro para contener el máximo del conjunto.
3. Se debe inicializar el máximo esto puede ser con un valor arbitrario,
razonablemente bajo o un valor particular que pueda ser utilizado como valor
centinela o con el valor de la primer lectura.
4. A continuación se compara la nueva entrada con el valor máximo, cada vez que
la nueva entrada lo supera, se conserva ese valor como el nuevo máximo.
La búsqueda de un mínimo utiliza el mismo criterio solamente que requiere :
1. La inicialización del mínimo si se hace con un valor arbitrario este debe ser lo
suficientemente alto coma para que cualquier dato del conjunto lo reemplace
2. Las comparaciones posteriores requieren simplemente cambia el operador de
relación. Si en el máximo se compra por mayor, en el mínimo debe hacerse por
menor.
Dado 100 valores buscar el máximo del conjunto y su posición relativa dentro del
mismo
//Búsqueda con una cantidad conocida de datos
LÉXICO
TAMAÑO_LOTE = 100;
I : Entero // variable para la iteración
V : Entero // para las distintas lecturas
Máximo : Entero // Contiene el máximo del conjunto
PosRel : Entero // Contiene la pos del máximo dentro del conjunto
ALGORITMO
Leer(V);
Máximo = V ;//Asigna al máximo la primer lectura
PosRel = 1
PARA i [2,TAMAÑO_LOTE] HACER
Leer(V);
SI (V > Máximo)
ENTONCES
Máximo  v;
PosRel  i
FIN_SI;
FIN_PARA;
Escribir(“Máximo es : “,Máximo. “ y su posición relativa “,PosRel);
FIN.

Dado 100 valores buscar el máximo del conjunto y su posición relativa dentro del
mismo
//Búsqueda con una cantidad conocida de datos con valor centinela
LÉXICO
TAMAÑO_LOTE = 100;
I : Entero // variable para la iteración
V : Entero // para las distintas lecturas
Máximo : Entero // Contiene el máximo del conjunto
PosRel : Entero // Contiene la pos del máximo dentro del conjunto
ALGORITMO
PARA i [1,TAMAÑO_LOTE] HACER
Leer(V); El maximo lo asigna cuando es la primer lectura, es
SI ( i = 1 o V > Máximo) decir cuando i es igual a uno, cosa que ocurre solo
ENTONCES una vez o cuando el valor leido reemplaza al
maximo. Los compiladores evaluan las expresiones
Máximo  v; logicas de izquierda a derecha, al ser verdadera la
PosRel  i primera proposicion por ser una disyuncion no
FIN_SI; necesita evaluar la segunda para saber su valor de
verdadero
FIN_PARA;
Escribir(“Máximo es : “,Máximo. “ y su posición relativa “,PosRel);
FIN.
Dado un conjunto de valores, que finalizan con un valor igual a 0 buscar el máximo del
conjunto y su posición relativa dentro del mismo
//Búsqueda con una cantidad indefinida de datos
LÉXICO
I : Entero // variable para la iteración
V : Entero // para las distintas lecturas
Máximo : Entero // Contiene el máximo del conjunto
PosRel : Entero // Contiene la pos del máximo dentro del conjunto
ALGORITMO
Leer(V);
Máximo = V ;//Asigna al máximo la primer lectura
PosRel = 1
MIENTRAS (V <> 0) HACER
SI (V > Máximo)
ENTONCES
Máximo  v;
PosRel  i
FIN_SI;
Leer(v);
FIN_MIENTRAS;
SI (Máximo <> 0)
ENTONCES
Escribir(“Máximo es : “,Máximo. “ y su posición relativa “,PosRel);
SI_NO
Escribir(“No se ingresaron valores”);
FN_SI;
FIN.

Dado un conjunto de valores, que finalizan con un valor igual a 0 buscar el máximo del
conjunto y su posición relativa dentro del mismo
//Búsqueda con una cantidad indefinida de datos
LÉXICO
I : Entero // variable para la iteración
V : Entero // para las distintas lecturas
Máximo : Entero // Contiene el máximo del conjunto
PosRel : Entero // Contiene la pos del máximo dentro del conjunto
ALGORITMO
Leer(V);
Máximo = 0; PosRel = 0;
MIENTRAS (PosRel = 0 o V <> 0) HACER
Leer(V);
SI (V > Máximo)
ENTONCES
Máximo  v;
PosRel  i
FIN_SI;
Leer(v);
FIN_MIENTRAS;
SI (Máximo <> 0)
ENTONCES
Escribir(“Máximo es : “,Máximo. “ y su posición relativa “,PosRel);
SI_NO
Escribir(“No se ingresaron valores”);

FIN.
Alternativa de solución reemplazando el análisis de casos con la notación [JDM]
ALGORITMO
Leer(V);
Máximo = 0; PosRel = 0;
MIENTRAS (PosRel = 0 o V <> 0) HACER
Leer(V);
SI
(V > Máximo)
Máximo  v;
PosRel  i
FIN_SI;
Leer(v);
FIN_MIENTRAS;
SI
(Máximo <> 0)
Escribir(“Máximo es : “,Máximo. “ y su posición relativa “,PosRel);
(Máximo = 0) Escribir(“No se ingresaron valores”);
FN_SI;
FIN.

Corte de control
La utilización de este algoritmo puntual permite resolver el análisis de una secuencia de
datos que cumplen con la precondición de tener una clave que se repite, están ordenados
o agrupados por esta clave y se requiere información de cada subconjunto formado por
todos los valores de la misma clave y además información sobre la totalidad de los
datos. Debe garantizarse que se evaluaran todos los datos y que los que corresponden al
mismo subgrupo serán evaluados agrupados.

LÉXICO
Importe,Suma : Entero;
NumeroCliente, Anterior : Entero;
AGORITMO
Leer(NumeroCliente)
// hace una lectura anticipada del dato de la expresión lógica//
MIENTRAS (NumeroCliente > 0) HACER
//garantiza la secuencia de lectura de todos los datos//
Suma = 0; //inicializa acumuladores//
Anterior = NumeroCliente; //guarda e valor a controlar//
MIENTRAS (NumeroCliente > 0 y Anterior = NumeroCliente;) HACER
//ciclo que garantiza estar en el mismo subgrupo y que aun haya datos//
Leer(Importe); // lectura del resto de los datos//
Suma = Suma + Importe;
Leer(NumeroCliente)//lectura del nuevo s es igual al anterior
Continua, sino sale del ciclo.
FIN_MIENTRAS
Escribir(“El Cliente : “,Anterior, “ Compro : “,Suma);
FIN_MIENTRAS
FIN.

Resumen:
En el presente se abordaron temas de solución de problemas. Para resolver problemas de
programación se requiere conocer las estructuras de datos que contienen la información
a procesar, las que se utilizarán como estructuras intermedias y las que servirán como
estructuras finales para mostrar los resultados obtenidos. además de las estructuras de
datos se requiere saber cuales son las acciones que deben seguirse para alcanzar la
solución propuesta.
En este capitulo el objeto de estudio fue justamente el conocimiento de estas
herramientas, allí se vio como tomar valores o mostrarlos, como hacer distintos análisis
de casos, como secuenciar las acciones de modo de encontrar la solución de problemas
que no se resuelven con una única acción, la necesidad de repetir acciones.
Se pudo observar como se puede repetir con repeticiones explicitas, con composiciones
de iteración, o repetir con invocaciones múltiples a través de recursividad. Por ultimo se
introdujo el concepto de secuencia y su utilidad
CAPITULO 3 Acciones y Funciones
Objetivos de aprendizaje
Dominando los temas del presente capitulo Usted podrá.
1. Entender la descomposición como forma de resolución de problemas.
2. Dar valor a la reusabilidad en búsqueda de la eficiencia en la escritura el
código.
3. Establecer comunicación entre módulos.
4. Comprender las ventajas de la descomposición
5. Diferenciar acciones de funciones y los distintos tipos de parámetros

Introducción
En este capitulo se analiza la descomposición como forma de alcanzar la solución de
problemas. Una regla básica de la programación indica que si existe un programa de
longitud L tal que L = L1 + L2, se dice que el esfuerzo de resolver L es mayor que la
suma de los esfuerzos de resolución de L1 y L2, aun cuando haya que derivar esfuerzo
para la integración de los módulos.
SI L = L 1 + L 2
Entonces
Esfuerzo(L) > Esfuerzo(L1) + Esfuerzo (L2)
Antes de analizar las particularidades de las acciones y funciones es necesaria la
definición de los términos que se utilizan.

Modularizacion
En general los problemas a resolver son complejos y extensos, puede incluso
presentarse situaciones en que una parte del problema deba ser modificada para
adaptarse a nuevos requerimientos. Se hace necesario conocer algunas herramientas que
permitan facilitar la solución de estos problemas, la abstracción y la descomposición
pueden ayudar a ello. La abstracción permitirá encontrar y representar lo relevante del
problema y la descomposición se basa en el paradigma de ¨ dividir para vencer ¨. La
descomposición tiene como objetivo dividir cada problema en subproblemas cada uno
de los cuales será de más simple solución.
Es conveniente, e importante descomponer por varias razones:
Una persona entiende un problema de características complejas partiendo la
información. Por esto para comprender un problema complejo del mundo real es
necesario dividirlo o modularizar.
Favorece el trabajo en equipo, cada programador recibe las especificaciones la tarea a
realizar y las restricciones con las que debe manejarse.
Favorece el mantenimiento. Las tareas involucradas en este mantenimiento, corregir
errores y modificar código, se hace mucho mas simple el análisis y control de una
porción o modulo que de la totalidad del problema.
Permite la reusabilidad del código. Siempre es deseable, de ser posible, hacer uso de
código ya escrito.

Módulos
Un problema debe ser descompuesto en subproblemas que se denominan módulos en
los que cada uno tendrá una tarea especifica, bien definida y se comunicaran entre si
adecuadamente para conseguir un objetivo común. Un modulo es un conjunto de
instrucciones mas un conjunto de datos que realizan una tarea lógica.

Alcance de los datos


El desarrollo de un programa complejo con muchos módulos en los que en cada uno de
ellos se deben definir los propios tipos de datos puede ocasionar algunos de los
inconvenientes siguientes:
Demasiados identificadores.
Conflictos entre los nombres de los identificadores de cada modulo.
Integridad de los datos, lo que implica que puedan usarse datos que tengan igual
identificador pero que realicen tareas diferentes.
La solución a estos problemas se logra con una combinación entre ocultamiento de
datos y uso de parámetros.

Datos locales y globales


Unos se declaran en la sección de declaración del programa principal, los otros en la
sección de declaración de cada modulo.
Local y global ha sido utilizado hasta ahora de manera absoluta pero los módulos
pueden anidarse las reglas que gobiernan el alcance de los identificadores son:
El alcance de un identificador es el bloque del programa donde se lo declara.
Si un identificador declarado en un bloque es declarado nuevamente en un bloque
interno al primero el segundo bloque es excluido del alcance de la primera sección.

Ocultamiento y protección de datos


Todo lo relevante para un modulo debe ocultarse a los otros módulos. De este modo se
evita que en el programa principal se declaren datos que solo son relevantes para un
modulo en particular y se protege la integridad de los datos.

Parámetros
Son variables cuya característica principal es que se utilizan para transferir información
entre módulos. En programas bien organizados toda información que viaja hacia o
desde módulos se hace a través de parámetros.
Hay dos tipos de parámetros, los pasados por valor y los pasados por referencia o
dirección.
Cuando existen datos compartidos entre módulos una solución es que un modulo pase
una copia de esos datos al otro. En este caso el pasaje se denomina pasaje por valor. El
modulo que recibe esta copia no puede efectuar ningún cambio sobre el dato original; el
dato original se encuentra de este modo protegido de modificación.
Los parámetros pasados por referencia o dirección no envían una copia del dato sino
envían la dirección de memoria donde el dato se encuentra por lo que tanto el proceso
que lo llama como el proceso llamado pueden acceder a dicho dato para modificarlo.
Razones por la que es conveniente la utilización de parámetros sobre las variables
globales.

Integridad de los datos


Es necesario conocer que datos utiliza con exclusividad cada modulo para declararlos
como locales al modulo ocultándolo de los otros, si los datos pueden ser compartidos
por ambos módulos debería conocerse cuales son, si el modulo los utiliza solo de lectura
o puede modificarlos y es aquí donde se utilizan los parámetros.
Protección de datos
Si una variable es local a un modulo se asegura que cualquier otro modulo fuera del
alcance de la variable no la pueda ver y por lo tanto no la pueda modificar. Dado que las
variables globales pueden ser accedidas desde distintos módulos, la solución para evitar
modificaciones no deseadas es el pasaje como parámetros valor.

Uso de parámetros para retornar valores


Si bien el pasaje por valor es útil para proteger a los datos, existen situaciones en las que
se requiere hacer modificaciones sobre los datos y se necesitan conocer esas
modificaciones. Para esto se deben utilizar parámetros pasados por referencia. Los
parámetros enviados por referencia son aptos además para enviar datos en ambos
sentidos.

Utilidad del uso de parámetros


El uso de parámetros independiza a cada modulo del nombre de los identificadores que
utilizan los demás. En el caso de lenguajes fuertemente tipiados solo importa la
correspondencia en cantidad tipo y orden entre los actuales del llamado y los formales
de la implementación con independencia del nombre del identificador.

Reusabilidad
El uso de parámetros permite separar el nombre del dato, del dato en si mismo, lo que
permite que el mismo código sea utilizado en distintas partes del programa simplemente
cambiando la lista de parámetros actuales.

Acciones
El léxico establece el nivel de abstracción de un algoritmo. Es decir, introduce las
variables, las constantes, los tipos de datos y las acciones con que se construye el
algoritmo. Ahora se profundizara sobre el estudio de las acciones.
El concepto de acción está muy ligado al concepto de abstracción. Se analiza como
abstracción por parametrización y abstracción por especificación.

Utilización de acciones
Una acción es una secuencia de instrucciones que se identifica por un nombre y que
puede ser invocada desde un algoritmo principal o desde otra acción. Cuando una
acción es invocada desde algún punto de un algoritmo, el flujo de ejecución se traslada a
la primera instrucción de la acción, entonces la acción se ejecuta hasta el final y cuando
acaba, el flujo se traslada de nuevo a la instrucción del algoritmo que sigue a aquella
que origino la invocación.
Una acción debe tener un efecto bien definido, lo que significa que debe ser cohesiva.
El nombre de la acción es conveniente que evoque la tarea que realiza. Hay que definir
acciones que sean aplicables a cualquier posible conjunto de valores de entrada y no a
un valor concreto.
Entre una acción y el algoritmo que la invoca se debe producir una comunicación de
valores: el algoritmo debe proporcionar los valores de entrada y la acción puede retornar
el resultado, o puede modificar el estado de alguno de ellos.
Puede haber acciones en las que la comunicación se realice mediante variables globales
definidas en el ámbito del algoritmo principal, que pueden ser manejadas por la acción.
Pero esta no es la forma mas apropiada. Una acción, en principio, nunca debería acceder
a variables globales.
Los parámetros son el mecanismo que posibilita escribir acciones generales, aplicables
a cualquier valor de la entrada, e independientes del léxico del algoritmo.

Acciones con parámetros


Un parámetro es un tipo especial de variable que permite la comunicación entre una
acción y el algoritmo que la invoca, ya sea que este pase a la acción un valor de entrada
o bien que la acción devuelva el resultado al algoritmo, o que pasen ambas cosas
simultáneamente.
A los parámetros que proporcionan un valor de entrada se los llaman Parámetros dato,
y a los que, además de recoger un valor, retornan un resultado, se los llama dato-
resultado. En la declaración de una acción, la lista de los parámetros se indica después
del nombre de la acción entre paréntesis y separados por punto y coma.
Nombre(dato p1 : TipoDato; dato-resultado p2 : TipoDto) : una acción
Un parámetro también cuenta con una característica: la dirección de memoria en la que
se realiza la transmisión de la información.
Se denomina paso de parámetros al modo en que se establece la comunicación entre los
argumentos pasados a la acción desde el algoritmo y los parámetros de la acción; en la
llamada se pasan los datos de entrada, y en el retorno se devuelven los resultados. Cada
argumento se liga con el parámetro que ocupa la misma posición en la declaración de la
acción y ambos deben coincidir en tipo.
En la invocación a una acción, el algoritmo debe proporcionar un valor para cada
parámetro dato, mientras que debe indicar para cada parámetro dato-resultado qué
variable de su léxico recogerá el valor.

Abstracción y acciones
El término abstracción se refiere al proceso de eliminar detalles innecesarios en el
dominio del problema y quedarse con aquello que es esencial para encontrar la solución.
Como resultado de aplicar la abstracción, se obtiene un modelo que representa la
realidad que nos ocupa. Este modelo no es la realidad, es una simplificación de esa
realidad que establece un nivel de abstracción mas apropiado para razonar sobre el
problema y encontrar la solución.
La abstracción también está relacionada con los lenguajes de programación. Estos
ofrecen mecanismos de abstracción que determinan la forma de razonar de un
programador.
El concepto de acción conjuga dos técnicas de abstracción que son la parametrización y
la especificación. La parametrización es un mecanismo por el cual se generaliza una
declaración para que no sea aplicado a un único caso, sino que sirva para cualquier valor
que pueda tomar cierto parámetro.
La abstracción por especificación es la separación entre la especificación (el Qué) y la
implementación (el cómo). En el caso de acciones, se refiere a distinguir entre la
descripción de qué hace la acción y el cómo se la implementa. Una vez que se define
una nueva acción, se las utiliza del mismo modo que si se tratase de una acción
primitiva.
Del mismo modo que el algoritmo, las acciones se especifican mediante una
precondición y una poscondición. La precondición establece las restricciones que
satisfacen los parámetros (datos de entrada) para que se pueda ejecutar la acción, y la
postcondición describe el resultado.
Cada acción debe ir siempre acompañada de una descripción textual de su efecto y de su
precondición y postcondición. De esta forma, cualquier programador podría conocer
qué hace y podría utilizarla sin conocer cómo lo hace.
El programador solo debe preocuparse por que se cumpla la precondición al invocar la
acción y tendrá la certeza de que la acción cumplirá su objetivo.

Tipos de Parámetros
Una acción se comunica con el algoritmo que lo invoca a través de los parámetros. Es
necesaria una comunicación en dos sentidos. El algoritmo debe proporcionar los datos
de entrada que manipulará durante su ejecución y la acción debe retornar los resultados
que obtiene.
El tipo de parámetro indica cómo los valores de los argumentos son ligados a los
parámetros.
El tipo de parámetro dato solo permite que el parámetro pueda recibir el valor de un
argumento mientras que el tipo de parámetro dato-resultado permite que el parámetro
pueda recibir un valor y pueda retornar un resultado. En la declaración de una acción
hay que indicar el tipo de cada parámetro. Si hay varios parámetros del mismo tipo y del
mismo tipo de dato, se pueden poner en la misma declaración, separados por comas. La
declaración de una acción se la denomina cabecera.
Se denomina paso por valor al paso de parámetros que corresponda al tipo de parámetro
dato y paso por referencia al que corresponda al tipo de parámetro dato-resultado.

Parámetro dato (o parámetro de entrada)


El valor del argumento es asignado al parámetro en el momento de la llamada. El
argumento puede ser una expresión del mismo tipo de dato que el parámetro. Se trata de
una comunicación unidireccional: solamente se transmite información desde el punto
del llamado hacia la acción.
Una regla de buen estilo de programación es no modificar el valor de parámetros tipo
dato, aunque ello no tenga efecto fuera de la acción.

Parámetro dato-resultado (o parámetro de entrada y salida)


El valor del argumento es asignado al parámetro en el momento de la llamada y al final
de la ejecución el valor del parámetro es asignado al argumento. Se trata de una
comunicación bidireccional. Si la ejecución de la acción provoca un cambio en el valor
del parámetro, en el momento del retorno el argumento tendrá el valor del parámetro al
finalizar la ejecución.
Un argumento para un parámetro dato-resultado debe ser una variable del mismo tipo de
dato que el parámetro y no puede ser una expresión.

Acción para el intercambio de dos variables: La acción recibe dos identificadores con
dos valores y debe retornar los identificadores con los valores cambiados

Intercambiar(dato-resultado a, b : Entero) : una acción


PRE { a, b : Entero, a = A, b = B }
POST { a = B, b = A }
LÉXICO Estos son los parámetros, que como
t : Entero deber ser modificados sus valores
Este identificador se por la accion se definen como Dato-
ALGORITMO necesita como variable Resultado, son las variables que
t = a; auxiliar para poder hacer el intercambian informacion entre el
a = b; intercambio. Su valor no lo programa principal y el modulo. Son
necesita el programa que parámetros variables
b = t invoca a la accion, solo
FIN interesa su visibilidad en el
modulo por tanto no es
parámetro, es una variable
local al modulo
Beneficios del uso de acciones
En la actualidad, las clases de los lenguajes orientados a objetos son los mecanismos
mas adecuados para estructurar los programas. No obstante, las acciones no desaparecen
con estos nuevos mecanismos porque dentro de cada clase se encuentran acciones.
Una acción tiene cuatro propiedades esenciales. Ellas son:
1. Generalidad
2. Ocultamiento de información
3. Localidad
4. Modularidad
De estas propiedades, se deducen una serie de beneficios muy importantes para el
desarrollo de algoritmos.
1. Dominar la complejidad
2. Evitar repetir código
3. Mejorar la legibilidad
4. Facilitar el mantenimiento
5. Favorecer la corrección
6. Favorecer la reutilización

Funciones
Si el propósito es calcular un valor a partir de otros que se pasan con argumentos y se
utilizan acciones, habrá que definir un parámetro dato-resultado para que retorne el
valor calculado. Las acciones que retornan un único valor no pueden ser utilizadas en
una expresión.
Para resolver este problema, los lenguajes de programación incorporan el concepto de
función. Las funciones devuelven un único valor. La función supone extender el
conjunto de operadores primitivos.
En cada declaración se especifica el nombre de la función, la lista de los parámetros y
finalmente el tipo de valor que retorna la función.
Nombre_funcion (par1 : td1 ; ... ; parn : tdn) : tr : una función
Una función no debe tener efectos laterales. Es decir, debe limitarse a calcular un valor
y no modificar ninguno de los que se describen en el momento de la invocación.
Las acciones se utilizan para extender el conjunto de acciones primitivas. Las funciones
permiten extender el conjunto de funciones u operadores primitivos. Siempre deben
utilizarse dentro de una expresión.

Función que obtenga el mayor de tres números


Las funciones siempre retornan un unico
Max(a, b, c : Entero)  Entero : una función valor. Es imprescindible asignarle valor
ALGORITMO al nombre e la funcion. El identificador
SI (a >= b) y (a >= c) del nombre de la funcion se puede utilizar
en una expresión como cualquier
ENTONCES identificador del tipo que retorna. Las
Max = a funciones pueden retornar un escalar un
SINO SI (b >= a) y (b >= c) apuntadoro un registro
ENTONCES
Max = b
SINO
Max = c
FIN_SI
FIN_SI
FIN.

Resumen:
En este capitulo se avanza sobre la necesidad de mayor abstracción procedural
introduciendo conceptos claves de programación como acciones y funciones como la
forma mas adecuada de estructurar problemas en el paradigma procedural. Es una
introducción a la abstracción procedural y de datos que servirá de base para sustentar
futuros conocimientos de estructuración de programas en clases cuando se aborde, en
otra instancia, la programación orientada a objetos.
Se dio valor a términos como reusabilidad, ocultamiento de datos y cohesión
CAPITULO 4 Tipos de datos y Operadores
Objetivos de aprendizaje
Dominando los temas del presente capitulo Usted podrá.
1. Conocer los valores y las operaciones aplicables a los tipos de datos.
2. Conocer los datos simples y las estructuras
3. Hacer análisis comparativos de las estructuras según su utilizacion
4. Definir colecciones de datos del mismo tipo o tipo diferente
5. Determinar como acceder a cada miembro de una estructura según su
tipo

Introducción:
Se introducen los conceptos de tipos de datos primitivos, derivados y abstractos. Se
definen los operadores aritméticos, lógicos, de relación y los de acceso a los miembros
particulares de las estructuras de datos con análisis comparativo de las distintas
estructuras de datos y una sugerencia para el modo correcto de selección de las mismas
en función de los resultados a obtener.

Variables
Denotan una magnitud o indicador del proceso. Se caracteriza por un nombre, un tipo de
dato y el valor que tiene en un momento determinado. Utilizadas para los datos de
entrada, intermedios y de salida.

Constante
Nombre asociado a un valor que permanece sin modificación durante todo el proceso

Tipos de Datos
Identifica o determina un dominio de valores y el conjunto de operaciones aplicables
sobre esos valores.
1. Primitivos.
2. Derivados.
3. Abstractos.
Los algoritmos operan sobre datos de distinta naturaleza, por lo tanto los programas que
implementan dichos algoritmos necesitan una forma de representarlos.
Tipo de dato es una clase de objeto ligado a un conjunto de operaciones para crearlos y
manipularlos, un tipo de dato se caracteriza por
1. Un rango de valores posibles.
2. Un conjunto de operaciones realizadas sobre ese tipo.
3. Su representación interna.
Al definir un tipo de dato se esta indicando los valores que pueden tomar sus elementos
y las operaciones que pueden hacerse sobre ellos.
Al definir un identificador de un determinado tipo el nombre del identificador indica la
localización en memoria, el tipo los valores y operaciones permitidas, y como cada tipo
se representa de forma distinta en la computadora los lenguajes de alto nivel hacen
abstracción de la representación interna e ignoran los detalles pero interpretan la
representación según el tipo.
Los tipos de datos pueden ser.
1. Estáticos: Ocupan una posición de memoria en el momento de la definición, no
la liberan durante el proceso solamente la liberan al finalizar la aplicación.
a. Simples: Son indivisibles en datos mas elementales, ocupan una única
posición para un único dato de un único tipo por vez.
i. Ordinales: Un tipo de dato es ordinal o esta ordenado
discretamente si cada elemento que es parte del tipo tiene un
único elemento anterior (salvo el primero) y un único elemento
siguiente (salvo el ultimo).
1. Enteros: Es el tipo de dato numérico mas simple.
2. Lógico o booleano: puede tomar valores entre dos
posibles: verdadero o falso.
3. Carácter: Proporcionan objetos de la clase de datos que
contienen un solo elemento como valor. Este conjunto de
elementos esta establecido y normatizado por el estándar
ASCII.
ii. No ordinales: No están ordenados discretamente, la
implementación es por aproximación
1. Reales: Es una clase de dato numérico que permite
representar números decimales.
b. Cadenas: Contienen N caracteres tratados como una única variable.
c. Estructuras: Tienen un único nombre para mas de un dato que puede ser
del mismo tipo o de tipo distinto. Permiten acceso a cada dato particular
y son divisibles en datos mas elementales.
Una estructura es, en definitiva, un conjunto de variables no
necesariamente del mismo tipo relacionadas entre si de diversas formas.
Si los datos que la componen son todas del mismo tipo son homogéneas,
heterogéneas en caso contrario.
Una estructura es estática si la cantidad de elementos que contiene es fija,
es decir no cambia durante la ejecución del programa
i. Registro: Es un conjunto de valores que tiene las siguientes
características:
Los valores pueden ser de tipo distinto. Es una estructura
heterogénea.
Los valores almacenados se llaman campos, cada uno de ellos
tiene un identificador y pueden ser accedidos individualmente.
El operador de acceso a cada miembro de un registro es l
operador punto ( . )
El almacenamiento es fijo.
ii. Arreglo: Colección ordenada e indexada de elementos con las
siguientes características:
Todos los elementos son del mismo tipo, un arreglo es una
estructura homogénea.
Los elementos pueden recuperarse en cualquier orden,
simplemente indicando la posición que ocupa dentro de la
estructura, esto indica que el arreglo es una estructura indexada.
El operador de acceso es el operador []
La memoria ocupada a lo largo de la ejecución del programa es
fija, por esto es una estructura estática.
El nombre del arreglo se socia a un área de memoria fija y
consecutiva del tamaño especificado en la declaración.
El índice debe ser de tipo ordinal. El valor del índice puede verse
como el desplazamiento respecto de la posición inicial del
arreglo.
Los arreglos pueden ser de varias dimensiones. Esta dimensión
indica la cantidad de índices necesarias para acceder a un
elemento del arreglo.
El arreglo lineal, con un índice, o una dimensión se llama vector.
El arreglo con 2 o mas índices o dimensiones es una matriz. Un
grupo de elementos homogéneo con un orden interno en el que se
necesitan 2 o mas índices para referenciar a un elemento de la
estructura.
iii. Archivos: Estructura de datos con almacenamiento físico en
memoria secundaria o disco.
Las acciones generales vinculadas con archivos son
Asignar, abrir, crear, cerrar, leer, grabar, Cantidad de elementos,
Posición del puntero, Acceder a una posición determinada, marca
de final del archivo, definiciones y declaraciones de variables.
Según su organización pueden ser secuenciales, indexados.
1. Archivos de texto: Secuencia de líneas compuestas por
cero uno o mas caracteres que finalizan con un carácter
especial que indica el final de la línea. Los datos internos
son representados en caracteres, son mas portables y en
general mas extensos.
2. Archivos de tipo o binarios: secuencia de bytes en su
representación interna sin interpretar. Son reconocidos
como iguales si son leídos de la forma en que fueron
escritos. Son menos portables y menos extensos.
2. Dinámicos: Ocupan direcciones de memoria en tiempo de ejecución y se
instancian a través de punteros. Esta s instancias pueden también liberarse en
tiempo de ejecución. El tema de puntadores y estructuras enlazadas (estructuras
relacionadas con este tipo de dato se analizan en detalle en capítulos siguentes)
a. Listas simplemente enlazadas: cada elemento sólo dispone de un
puntero, que apuntará al siguiente elemento de la lista o valdrá NULL si
es el último elemento.
b. Pilas: son un tipo especial de lista, conocidas como listas LIFO (Last In,
First Out: el último en entrar es el primero en salir). Los elementos se
"amontonan" o apilan, de modo que sólo el elemento que está encima de
la pila puede ser leído, y sólo pueden añadirse elementos encima de la
pila.
c. Colas: otro tipo de listas, conocidas como listas FIFO (First In, First Out:
El primero en entrar es el primero en salir). Los elementos se almacenan
en fila, pero sólo pueden añadirse por un extremo y leerse por el otro.
d. Listas circulares: o listas cerradas, son parecidas a las listas abiertas,
pero el último elemento apunta al primero. De hecho, en las listas
circulares no puede hablarse de "primero" ni de "último". Cualquier nodo
puede ser el nodo de entrada y salida.
e. Listas doblemente enlazadas: cada elemento dispone de dos punteros,
uno a punta al siguiente elemento y el otro al elemento anterior. Al
contrario que las listas abiertas anteriores, estas listas pueden recorrerse
en los dos sentidos.
f. Árboles: cada elemento dispone de dos o más punteros, pero las
referencias nunca son a elementos anteriores, de modo que la estructura
se ramifica y crece igual que un árbol.
g. Árboles binarios: son árboles donde cada nodo sólo puede apuntar a dos
nodos.
h. Árboles binarios de búsqueda (ABB): son árboles binarios ordenados.
Desde cada nodo todos los nodos de una rama serán mayores, según la
norma que se haya seguido para ordenar el árbol, y los de la otra rama
serán menores.
i. Árboles AVL: son también árboles de búsqueda, pero su estructura está
más optimizada para reducir los tiempos de búsqueda.
j. Árboles B: son estructuras más complejas, aunque también se trata de
árboles de búsqueda, están mucho más optimizados que los anteriores.
k. Tablas HASH: son estructuras auxiliares para ordenar listas.
l. Grafos: es el siguiente nivel de complejidad, podemos considerar estas
estructuras como árboles no jerarquizados.
m. Diccionarios.

Análisis Comparativo de estructuras

Atributo Archivo Array Estructura Enlazada


Almacenamiento Físico Electrónico Electrónico
Permanencia Persistente Volátil Volátil
Procesamiento Lento Veloz Veloz
Tamaño general Variable en ejecución
Fijo Variable
Búsquedas admitidas Directa Directa Conociendo dirección
Binaria Binaria Binaria en árboles
Secuencial Secuencial
Tamaño miembros Solo información Solo Información Info + Siguiente
Ordenamiento Solo en estructuras Existen métodos de Listas genera el orden,
auxiliares ordenamiento pila invierte, cola
mantiene

Operaciones elementales de acceso a archivos:

acción Efecto
Asignar(a,s) Asigna al identificador a la cadena s que representa un archivo
en disco
Abrir(a) Prepara el archivo asignado a la variable a para su utilización
Crear(a) Crea el archivo asignado al identificador a y lo prepara para su
acceso
Cerrar(a) Cierra el archivo apuntado por a, actualiza la marca de fin si
corresponde
LeerCaracter(a,c) Lee el siguiente carácter del flujo apuntado por a y lo almacena
en c
LeerLinea(a,s) Lee la siguiente línea del flujo apuntado por a y la almacena en
s
GrabarCaracter(a,c) Escribe secuencialmente en el flujo a el caracter c
GrabarCadena(a,s) Escribe en el flujo a la cadena s
LeerArchivo(a,r) Lee el siguiente tipo de dato (ejemplo registro) del flujo a y lo
almacena en r
GrabarArchivo(a,r) Graba el siguiente tipo de dato, r, en el flujo a
LeerPosicion(a,p,r) Lee del flujo a el valor contenido en l posición p y lo almacena
en r
GrabarPosicion(a,p,r) Graba en la posicion p del flujo a el valor contenido en r
NumeroElementos(a) Retorna el numero de elementos almacenados en el archivo
PosicionActual(a) Retorna la posición en la que se encuentra posicionado el
puntero actual del archivo.
ApuntarA(a,p) Accede a la posición indicada por p en el archivo a.
FinArchivo(a) Retorna verdadero si se ha alcanzado la marca de fin de archivo

Criterio de selección de estructuras


Priorizar utilización de memoria principal sobre disco, priorizar acceso directo,
ordenado con búsqueda binaria si directo no es posible y acceso secuencial solo en
memoria principal para buscar.

Tipos de ordenamientos y recomendación de uso de estructuras


Todos los algoritmos puntuales que permiten implementar el criterio de selección de las
estructuras que se detallan están desarrollados en capítulos siguientes.
1. Ordenamiento por un campo
a. Vector con acceso directo si es posible (si la clave de acceso es
posicional o la posición de la clave es única y predecible.
b. Vector sin repetición de clave y ordenado con la totalidad de los datos.
c. Vector sin repetición de clave y los datos imprescindibles mas la
referencia a los otros datos.
d. Vector cargado secuencialmente y búsqueda secuencial en el.
e. Lista ordenada con los datos y referencias
f. Lista paralela a archivo ordenado para agrupar, cuando en la lista no hay
espacio para la clave.
g. Si es paralela a una estructura con posición única y predecible de clave,
acceso al numero de nodo correspondiente a ese numero de clave
h. Si es paralela a una estructura ordenada sin PUP, primero buscar en la
estructura ordenada con búsqueda binaria y buscar el nodo
correspondiente a esa posición.
i. Lista paralela a estructura desordenada para buscar, el nodo solo permite
la clave y no alcanza el espacio disponible para la posición o referencia.
Cargar la lista como cola, conservando el orden original de la estructura,
colocar la clave de búsqueda y para procesar primero buscar la clave
contado los nodos y accediendo a la estructura directamente según ese
valor de nodo.
2. Ordenamiento por dos campos:
a. Array de dos dimensiones si es posible acceder directamente por los dos
índices.
b. Array de vectores si una clave es posicional y la otra no posicional pero
acotada.
c. Array de punteros si una clave es posicional y la otra no esta acotada
(vector de listas)
d. Array con carga sin repetición si la primer clave esta acotada pero no es
posicional y en cada posición del vector un registro en el que el primer
campo es la calve y el otro un puntero a una lista ordenada por la
segunda clave si no esta acotada.
e. Lista con una clave compuesta par la unión de dos claves.
f. Lista ordenada por dos campos.
g. Lista de listas, con la combinación del uso del algoritmo puntual de
Buscaeinserta en un nodo con la clave del primer ordenamiento y un
puntero a una sublista con un nodo con la clave del segundo
ordenamiento.

Comparación entre estructuras enlazadas lineales

Atributo Pilas Colas Listas


Punteros Inicio Inicio, final Inicio
Carga Delante del primero Después del ultimo En orden
Retiro Del primer lugar Del primer lugar De donde este si esta
Procedimientos para Meter Agregar Insertanodo
cargar BuscaOInserta
Procedimientos para Sacar Suprimir SuprimeNodo
retirar
Para el orden Lo invierte Lo mantiene Lo genera
Operaciones EsVacia EsVacia EsVacia
Crear Crear Crear
Meter Agregar Insertanodo
Sacar Suprimir InsertaPrimero
InsertaDelante
InsertaenMedio
InsertaAlFinal
Recorrer
Buscar
Operadores
Tipo C Pascal
De Acceso Invocación a módulos () ()
Subíndice Arreglo [] []
Acceso a estructura . .
-> ^.
Unarios Signo Positivo-Negativo + - + -
De dirección &
De in dirección * ^
De increm.-Decremen ++ --
Tamaño sizeof Sizeof
ConversionExplicita (tipo)
Multiplicativos Producto Cociente */ */
Aditivos Suma Resta +- +-
De igualdad == =! = <>
Relacionales > < <= >= > < <= >=
lógicos And And &&
Or Or ||
Not Not !

Tipos de datos
Tipos de datos primitivos
notación Pascal C
Entero Byte unsigned short int
Shortint short int
Integer int
Word unsigned int
Longint long int
unsigneg long int

Real Real double


Single float

Carácter char char


unsigned char

Booleano boolean
char[]
Cadena string char *
Tipo Registro
Tr = TIPO<c1:t1;c2:t2> Tr = Record typedef struct {
c1: t1; t1 c1;
c2: t2; t2 c2;
end; };

.
Operador acceso “ “
Tipo archivo
Ta=TIPO Archivo de texto Text Se define en apertura
colocando t o b en modo.
Ta=TIPO Archivo de tdato Ta = FILE of Tipo de
dato
Asignar (Ni, NExterno) Assign(NInt, NExt) FILE *f

Crear(NombreInterno) Rewrite(NombreInterno) f=fopen(Next,”w”)


o w+, o wb, o wb+

Abrir(NombreInterno) Reset(NombreInterno) f=fopen(Next,”r”)


o r+, o rb, o rb+

FinArchivo(NombreInterno) EOF(NombreInterno) feof(NombreInterno)


AccederA(Ni,Pos) Seek(Ni,pos) fseek(NI,deslazam,desde)
SEEK_SET, SEEK_CUR,
SEEK_END
ftell(NI) cantbytes de
desplazamiento del inicio.
LeerArchivo(arch,registro) read(archivo,registro) fread, fgetc, getc, fgets,
fscanf.
GrabarArchivo(Arch,Reg) write(archivo,registro) fwrite, fputs, fputc, fprintf
CantidadRegistros(Arch) filesize(archivo) ftell()/sizeof(reg) del final
PosicionPuntero(Archivo) filepos(archivo) ftell()/sizeof(r) Pos Actual
Tipo Array
1 dimensión
Tv = TIPO TABLA[i..f] de Tv=Array[i..f] of tipo de Tipo Nombre[cantidad]
Tipo de dato dato

Acceso [índice] El primer índice es 0

2 o mas dimensiones
Ta=TIPO TABLA [i..f; i..f] de Ta= Array [i..f, i..f] of tipo
Tipo de dato Tipo de Dato Nombre[cant1][cant2]

Acceso[ind1, ind2]
Acceso[ind1][ind2]
Punteros
Definición
TP = Apuntador a TipoDato TP = ^TipoDato tipoDato * identificador

Instancia
Nuevo(puntero) New(puntero) pro = malloc(sizeof(tipo))
Destruir(puntero) Dispose(puntero) free(pro)

Acceso
Punteros, o *punter Punteros *pro
Est. Autoreferenciadas

TipoInfo = TIPO tipodato


TipoInfo = tipodato Typedef tipo TipoInfo
TPuntero = Apuntador a
TipoNodo; TPuntero = ^TipoNodo

TipoNodo = TIPO
<info:TipoInfo,sgte:TPuntero> TipoNodo = Record typedef struct TN {
info:TipoInfo; TipoInfo info;
sgte:TPuntero struct TN sgte;
end; }TipoNodo;
Typedef TipoNodo *
Tpuntero;
Operador de dirección

DireccionDe(Puntero) &Puntero

ARCHIVOS
CLASIFICACION
Según la dirección del flujo de datos:
• De entrada:
• De salida:
• De entrada/salida:

Según el tipo de valores permitidos a cada byte:


• De texto:
• Binarios:

Según el tipo de acceso:


• Archivos secuenciales:
• Archivos de acceso aleatorio:

Según la longitud de registro


• Longitud variable:
• Longitud constante:
• Mixtos:

Estas propiedades se definen al abrir los archivos


• Si son binarios o de texto
• De entrada, salida o entrada/salida

Archivos secuenciales
Los archivos secuenciales tienen algunas características que hay que tener en cuenta:
1. La escritura de nuevos datos siempre se hace al final del archivo.
2. Para leer una zona concreta del archivo hay que avanzar siempre, si la zona está
antes de la zona actual de lectura, será necesario "rebobinar" el archivo.
3. Los ficheros sólo se pueden abrir para lectura o para escritura, nunca de los dos
modos a la vez.
C no distingue si los archivos que usamos son secuenciales o no, es el tratamiento que
hagamos de ellos lo que los clasifica como de uno u otro tipo. Hay archivos que se
comportan siempre como secuenciales de entrada y salida estándar: stdin, stdout, stderr.
En stdin, que suele ser el teclado. El programa sólo podrá abrir ese fichero como de
lectura, y sólo podrá leer los caracteres a medida que estén disponibles, y en el mismo
orden en que fueron tecleados.

Archivos de acceso aleatorio


Los archivos de acceso aleatorio son más versátiles, las operaciones de lectura y/o
escritura pueden hacerse en cualquier punto del archivo.
En general se suelen establecer ciertas normas para la creación, aunque no todas son
obligatorias:
1. Abrir el archivo en un modo que permita leer y escribir.
2. Abrirlo en modo binario, ya que algunos o todos los campos de la estructura
pueden no ser caracteres.
3. Usar funciones como fread y fwrite, que permiten leer y escribir registros de
longitud constante desde y hacia un fichero.
4. Usar la función fseek para situar el puntero de lectura/escritura en el lugar
apropiado de tu archivo.

Resumen:
En este capitulo el objeto de estudio el conocimiento de las estructuras de datos, sus
particularidades y ventajas y desventajas de cada una de ellas. Ya se ha visto como
seleccionar acciones y como dividir el problema para alcanzar la solución, aquí se
estudia en profundidad en que estructura de datos contener esa información.
SE estudiaron en profundidad estructuras estáticas con almacenamiento electrónico o
físico. Se estableció un criterio de selección que puede resumirse simplemente de este
modo, si se requiere que el dato persista el almacenamiento debe ser físico, es decir en
disco. Si esto no se requiere y lo que se prioriza es la velocidad de procesamiento el
almacenamiento debe ser electrónico, en estructuras de tipo tabla. Siempre priorizar
acceso o búsqueda directa, binaria en caso de no poder y secuencial solo con estructuras
en memoria.
CAPITULO 5 Estructuras Enlazadas
Objetivos de aprendizaje
Dominando los temas del presente capitulo Usted podrá.
1. Manejar estructuras complejas.
2. Introducirse a la semántica de direcciones
3. Comprender como se asigna memoria dinámicamente en tiempo de
ejecución.
4. Conocer estructuras enlazadas lineales
5. Diferenciar entre estructuras indexadas y estructuras enlazadas

Introducción:
En el presente capitulo se incorpora el concepto de asignación dinámica en memoria.
Esto permite romper con la limitación de tamaño fijo que proporcionan las tablas
cuando es necesario trabajar con colección de datos el mismo tipo en memoria.
Para esto se incorporan los conceptos de estructuras enlazadas, punteros y asignación
dinámica en memoria.

Estructuras enlazadas vs. estructuras indexadas


Como vimos una tabla es una secuencia de datos del mismo tipo donde a cada elemento
se puede acceder a través de un índice. En esta estructura las posiciones de memoria son
contiguas y se las puede ir recorriendo con solo incrementar el índice. En estas
estructuras el primer elemento lógico coincide con el primero físico, es decir se
comienza desde la primera posición, y el siguiente lógico coincide con el siguiente
físico, es decir al próximo elemento se accede incrementando en uno el índice. Estas
estructuras, con esta organización son estructuras indexadas. Las estructuras pueden
organizarse de forma diferente. El primer elemento lógico puede no coincidir con el
primero físico, en ese caso, para conocer donde comienza lógicamente se debe saber la
posición de ese primero. El siguiente elemento lógico no necesariamente debe estar en
la posición contigua, para saber donde esta será necesario tener algo que referencie la
posición del siguiente elemento. Una estructura con esta organización es una estructura
enlazada. Las estructuras enlazadas tienen la particularidad que se necesita conocer la
posición del primer elemento y en cada posición se tiene un registro, llamado nodo, con
al menos dos campos, uno que contenga la información y otro que indique o referencie
donde se encuentra el siguiente que no necesariamente debe ser el contiguo.
Por otro lado vimos que si se quiere definir en memoria una colección de datos del
mismo tipo mediante una tabla esta requiere establecer una capacidad máxima. Estas
son estructuras con tamaño fijo, el mismo no puede ser modificado en tiempo de
ejecución. Existe entonces riesgo que la cantidad de posiciones definidas en tiempo de
declaración no alcancen, o, que se utilicen muy pocas de las posiciones definidas
haciendo un uso poco eficiente de los recursos.

Estructuras enlazadas con asignación dinámica en memoria


Una estructura con asignación dinámica es un conjunto de elementos de tipo
homogéneo donde los mismos no ocupan posiciones contiguas de memoria, pueden
aparecer físicamente dispersos manteniendo un orden lógico y son instanciados y/o
liberados en tiempo de ejecución.
Operaciones con listas
1. Crear una estructura
2. Recorrerla, parcial o totalmente
3. Agregar o insertar nuevos elementos o nodos en distintos lugres y con criterio
variado.
4. Localizar un elemento en particular en la estructura
5. Borrar un elemento de posicion particular o previamente desconocida
La mayor facilidad de las listas es el “enlace dinámico” a través de punteros que
permiten intercalar o eliminar valores sin un movimiento masivo de memoria, con solo
pedir memoria o liberarla y hacer simplemente los enlaces de los nodos involucraos.
Punteros
Es posible definir estructuras de datos complejas cuyo tamaño puede cambiar en tiempo
de ejecución, por lo que son denominadas estructuras de datos dinámicas. Se instancias
a través de punteros.
Mediante la utilización de tablas se puede definir una secuencia de valores como:
TipoSecuencia = TIPO Tabla [1, LongitudMaxima] de elemento;
Con esta definición se reserva espacio en tiempo de ejecución para una cantidad fija de
elementos. Esta cantidad esta dada por el valor de LongitudMaxima.
Otra forma de definir una secuencia de valores seria por inducción o recursion:
La secuencia vacía es una secuencia
Un elemento concatenado a una secuencia es otra secuencia
De esta forma se declara el tipo sin especificar ninguna capacidad máxima, se define en
forma recursiva.
En esta definición es necesario que cada elemento referencie con precisión el siguiente
elemento si es que este existe.
Una secuencia, con esta organización, como ya dijimos es una concatenación de
registros llamados nodos de dos campos: uno para la información y otro para un
indicador al siguiente elemento. El siguiente elemento al que se referencia es un registro
del mismo tipo del actual, por referenciar a una estructura de su mismo tipo estas
estructuras se conocen como estructuras autoreferenciadas.
Para la asignación dinámica en memoria, los elementos se enlazan a través de un
determinado tipo de dato que disponen la mayoría de lenguajes de programación y que
se denominan puntero o apuntador, cuyos valores son referencias o punteros a variables
de un tipo.
TipoPunteroEntero = TIPO Apuntador a Entero;
Se hace una definición de un tipo de dato con la particularidad que tendrá como valor
una dirección de memoria. Se dirá entonces que es un puntero al tipo de dato o que
apunta a determinado tipo de dato
Si de define
PunteroEntero = TipoPunteroEntero
Los posibles valores de PunteroEntero no serán enteros, sino referencias a enteros; esto
sería la dirección del espacio de memoria en la que se almacena el entero.
Ya se definió variable como un identificador que denota un espacio de memoria en el
que se almacena un valor del tipo asociado en la dirección.
Un valor apuntador es la dirección de una variable “anónima”, se le asigna la dirección
a se crea en tiempo de ejecución.
Para indicar que una variable puntero no referencia o apunta a ninguna a otra variable
se le debe asignar el valor NULO.
TipoInfo = TIPO Entero
//define la información que tendrá el campo dato del nodo//
TipoPuntero = TIPO Apuntador a TipoNodo;
//Define un puntero que en cada instancia generara un nodo.
TipoNodo = TIPO < dato : TipoInfo; sgte : TipoPuntero >;
//Define un nodo con la información y la referencia al siguiente que es un puntero a la
siguiente dirección de memoria de la secuencia definida//
Se trata de una definición recursiva o autorreferenciada siguiendo la definición
inductiva de secuencia.
Una estructura enlazada utilizando punteros debe tener una variable que controle la
estructura, en general debe contener la dirección de memoria del primer nodo de la
misma, que debe ser de tipo puntero e inicialmente debe ser creada haciendo apuntar a
la misma al valor NULO, lo que indica que la secuencia esta vacía.
Con este tipo de estructura no es necesario especificar una capacidad máxima en la
declaración, sino que se asigna espacio en tiempo de ejecución, conforme se necesita, a
través de los apuntadores, por lo que se obtienen dos importantes beneficios.
1. No se desaprovecha espacio cuando la secuencia tiene menos elementos que el
tamaño máximo establecido.
2. El tamaño de la secuencia no está limitado.
Solo se podría mencionar como desventaja el hecho que cada elemento adema de tener
la información propia debe destinar recursos para referenciar al siguiente, situación que
puede estar a favor de las tablas. Con lar particularidades de cada estructura es
responsabilidad del programador seleccionar la más idónea para cada caso que deba
resolver.
Como ya mencionamos estas estructuras de datos cuyo tamaño puede variar en tiempo
de ejecución se denominan estructuras de datos dinámicas, que son básicas en
programación, como: listas, pilas, colas y árboles binarios de búsqueda que serán
abordados en este capitulo y el siguiente.

El tipo de dato Puntero


Se ha definido un tipo de datos puntero como aquel cuyo dominio de valores está
formado por referencias o punteros a variables (direcciones de memoria) de un tipo
existente.
Si los tipos de datos se definen por sus valores y sus operaciones los valores que pueden
tener los punteros son los de direcciones de memoria o el valor NULO en caso de no
estar apuntando a una dirección definida y, entre las operaciones se pueden mencionar
la asignación interna, la creación o la destrucción, los lenguajes de programación
ofrecen los mecanismos para estas operaciones.

Constantes y operaciones con punteros


No es posible el uso de literales para representar valores de tipo puntero salvo las
constantes simbólicas que representan el valor NULO con sintaxis diferente según el
lenguaje de programación que se trate. NULO representa solo eso y no contiene un
valor de dirección de memoria válido. Esta constante NULO es compatible de
asignación y comparación con cualquier tipo de dato apuntador, con independencia a
cualquiera sea el tipo de valores a los que apunta éste.
Los valores de tipo apuntador son datos internos propios de cada ejecución del
programa, solo aceptan asignación interna, no es correcto hacer asignaciones externas
leer o escribir con variables de tipo apuntador.
Las operaciones elementales de una variable de tipo apuntador son las de creación de
un nuevo objeto y destrucción del objeto apuntado.
Para la creación de un nuevo objeto apuntado dispondremos de la acción Nuevo. Esta
acción tiene un único parámetro de tipo Apuntador a T, cualquiera sea el tipo de T, y su
efecto es el siguiente:
Reserva espacio de memoria suficiente para alojar una variable de tipo T
Asigna al el valor de la dirección de memoria que acaba de reservar, si es que se
dispone de espacio en memoria o el valor NULO en caso de no poder ejecutar la acción
de creación.
El efecto de invocar a la acción Nuevo (Puntero) sería la creación de una nueva variable
anónima del tipo al que apunta el puntero, y la asignación a pe de la dirección de
memoria donde se encuentra dicha variable. La nueva variable se denomina anónima
porque no tiene identificador propio, y únicamente será accesible a través de la
dirección que la acción Nuevo ha asignado al parámetro puntero. La nueva variable está
recién creada y, por tanto, sin inicializar; no tiene un valor definido mientras no se le
asigne nada.
Aunque los lenguajes de programación tienden a aliviar al programador de la
responsabilidad de liberar el espacio reservado a variables de tipo apuntador e
incorporan rutinas de recolección de basuras para liberar los espacios de memoria
reservados que ya no están siendo utilizados, de cualquier modo es erróneo el realizar
reservas de espacios de memoria que posteriormente queden reservados e inaccesibles
por la pérdida de valor de la dirección donde se encuentran.
Cuando un espacio de memoria que ha sido reservado con la acción Nuevo no es ya
necesario, podemos destruir las variables en él contenidas y liberar ese espacio para que
quede disponible para futuras reservas. Para ello, utilizaremos la acción Destruir, que
ejecuta la operación inversa de Nuevo: recibe como parámetro una variable de tipo
apuntador que contiene una dirección válida que previamente ha sido reservada con la
acción Nuevo, y libera el espacio de memoria al que apunta este parámetro. Cuando
programemos, no debemos suponer que la acción Destruir modifica el valor del
parámetro que recibe, sino que una buena práctica es asignar el valor NULO a una
variable de tipo apuntador tras invocar Destruir. Si analizamos el siguiente fragmento de
algoritmo:
LÉXICO
TPEntero = TIPO Apuntador a Entero;
p, q = TPEntero;
...
ALGORITMO
...
Nuevo(p);
q ← p;
Destruir(q);
...
FIN
Se puede ver que con independencia de si Destruir modifica o no el valor de q, después
de ejecutar esas tres instrucciones el apuntador p tiene un valor de dirección de memoria
que no sería válida, pues ha sido liberada. Se dice que p queda como un apuntador
colgante, ya que no apunta a nada, o mejor dicho, apunta a una dirección que ya no es
válida, pues ha sido liberada. Es responsabilidad del programador el mantener de
manera conveniente los valores de los apuntadores y no acceder a direcciones que no
son válidas.
Acceso a datos mediante apuntadores
Sea TPEntero un tipo de apuntador que referencia a variables de un tipo cualquiera
TipoBase y p una variable apuntador del tipo TPuntero:
TipoPuntero = TIPO Apuntador a TipoBase;
p : TipoPuntero
Los lenguajes de programación utilizan el operador * en notación prefija (*Puntero) o el
operador ^ (Punteros)en notación posfija, en esta publicación se los utilizara en forma
indistinta, agregando el operador ↑ en notación postfija, es decir puntero↑, para
referenciar al objeto apuntado por el puntero. La expresión Puntero↑, *Puntero o
Puntrero^ hará referencia al contenido de la dirección apuntada por el puntero y será un
identificador de la variable de tipo TipoBase a que se refiere el apuntador puntero.
Respecto al tema de la asignación debe diferenciarse si lo que se asigna es el contenido
o lo que se asigna es la dirección. Si p y q so dos variables de tipo puntero que apuntan
al mismo tipo base hay que diferenciar que es lo que ocurre con las asignaciones
siguientes:
p↑ = q↑
En este caso se le asigna al contenido de p el contenido de q. P y Q siguen apuntando a
su propia dirección de memoria, i los contenidos de esas dos direcciones es lo que se
iguala.
Por otro lado la asignación:
p ← q
Hace que q apunte a la dirección apuntada por p.
En el caso en que el tipo base de un determinado tipo apuntador no sea un tipo simple,
sino un tipo estructurado, el acceso se realiza con los mismos operadores de acceso que
los datos estáticos.
TipoRegistro=TIPO <Campo1:TipoDeDato1;Campo2:TipoDeDato2>
TipoPunteroRegistro = TIPO Apuntador a TipoRegistro;
PunteroRegistro : TipoPunteroRegistro
PunteroRegistro es un puntero a un registro, contiene la dirección de memoria donde se
aloja un registro, pro lo tanto el contenido de la dirección de memoria a la que apunta es
un registro.
PunteroRegistro es un p TipoPuntero.
PunteroRegistro^ es un registro que se encuentra en la dirección apuntada por el
puntero.
Si PunteroRegistro^ es un registro, el operador de acceso a un miembro de un registro
es el operador punto, por lo tanto
PunteroRegistro^.Campo1 hace referencia al miembro Campo1 del registro que esta en
la dirección de memoria apuntada por PunteroRegistro
Algunos lenguajes de programación permiten utilizar apuntadores para referenciar
variables no anónimas declaradas en el léxico. Para ello disponen de un operador
dirección_de (el operador & en el caso del lenguaje de programación C) que dada una
variable, devuelve su dirección.

LÉXICO
TipoRegistro=TIPO <Campo1 : Entero ; Campo2 : Entero>
TipoPunteroRegistro = TIPO Apuntador a TipoRegistro;

Registro : TipoRegistro
PunteroRegistro : TipoPunteroRegistro
ALGORITMO
Registro.Campo1 ← 5; //asigna valores al registro
Registro.Campo2  10;
PunteroRegistro ← dirección_de(Registro);//
PunteroRegistro↑.Campo1 ← 35;
Escribir(Registro.Campo1); //imprime el valor 35
FIN
Obviamente, cuando una variable de tipo apuntador haga referencia a una variable
declarada en el léxico, como en el caso del ejemplo anterior, no será posible destruir su
contenido mediante la acción Destruir, ya que solo pueden destruirse las instancias que
se crean en tiempo de ejecución con la acción Nuevo.

Tipos de datos autorreferenciados o recursivos


Hemos mostrado que con punteros se pueden definir tipos que se autorreferencian, lo
que permite que el tamaño de una estructura enlazada sólo esté limitado por la
disponibilidad de memoria principal. Con una única variable de tipo puntero será
posible referenciar a una secuencia cuyo número de elementos podrá crecer o disminuir
en tiempo de ejecución.
En tipo de dato recursivo o autorreferenciado es un registro, llamado nodo, que contiene
entre sus elementos al menos dos campos, uno con la información y el otro es un
puntero a una estructura del mismo tipo. La bibliografía denomina nodo para referir a
ese elemento ya que es el término utilizado habitualmente para referirse a los elementos
de una estructura dinámica.
TPNodo = TIPO Apuntador a Nodo;
Nodo = TIPO < dato : Entero; sig : TPNodo >;
Cada vez que reservemos espacio para una variable se crea una nueva variable de tipo
Nodo. Es posible construir sucesivamente una secuencia indefinidamente larga de datos
de tipo Nodo, en la que el campo sig de cada uno de ellos apunta al siguiente elemento
de tipo Nodo creado. El último elemento de tipo Nodo contiene en su campo sig el valor
NULO para indicar que no existen más elementos siguientes.
El tipo Nodo es, por tanto, un tipo de dato autorreferenciado.
La siguiente función CrearSecuencia lee de la entrada n valores de tipo entero, crea una
secuencia con esos valores en el orden inverso de entrada y devuelve como resultado un
apuntador al primer elemento de la secuencia. La variable r se utiliza para apuntar al
nodo creado en cada paso y p apunta al primer elemento de la secuencia. Cada nuevo
nodo se añade delante del primero.
CrearSecuencia(n : Entero)  TPNodo : una función
LÉXICO Guarda en el registro apuntado por r el valor v y en el
p, q, r : TPNodo; campo siguiente el puntero p, este puntero contiene la
i, v : Entero; direccion del que estaba en primer lugar por lo que se
establece como siguiente el que era primero antes.
ALGORITMO
p ← NULO; //Crea la estructura
Leer(n);
i PARA [1, n] HACER
Escribir(“Ingrese un valor entero: ”);
Leer(v);
Nuevo(r); // instancia y pide memoria
r^ = < v, p >;
p ← r
FIN_PARA;
CrearSecuencia ← p
FIN

El ámbito de una variable local es la acción o función en la que se declara.


En cambio, aun cuando todas las variables apuntador usadas en la función
CrearSecuencia, es decir, p y r, son parte de su léxico local y, por tanto, desaparecen
tras concluir la llamada a ésta. No sucede así con las variables dinámicas creadas a
través de ellas con las llamadas a Nuevo. La secuencia creada por la función sería
accesible fuera de ella a través del valor TPNodo que retorna.
La siguiente acción recorre una secuencia y escribe los datos en ella contenidos:
EscribirSecuencia(dato p : TPNodo) : una acción
LÉXICO
q = TPNodo;
ALGORITMO
q = p;
MIENTRAS q ≠ NULO HACER
Escribir(q↑.dato);
q ← q↑.sig
FIN_MIENTRAS
FIN

DestruirSecuencia(dato-resultado p : TPNodo) : una acción


LÉXICO
q : TPNodo;
ALGORITMO
MIENTRAS p < > NULO HACER
q ← p;
p ← p↑.sig
Destruir(q)
FIN_MIENTRAS
FIN

Estructuras de datos dinámicas lineales


Dependiendo del número de punteros y de las relaciones entre nodos, podemos
distinguir varios tipos de estructuras dinámicas

El tipo pila
Una pila es una colección de elementos de un mismo tipo, posiblemente vacía, sobre la
que podemos hacer operaciones de inserción de un nuevo elemento, eliminación de un
elemento. Una pila es una estructura de tipo LIFO (del inglés, Last-Input, First-
Output), lo cual significa que los elementos siempre serán eliminados de ella en orden
inverso al que fueron colocados, de modo que el último elemento en entrar será el
primero en salir. A este tipo de colección de datos se la conoce por el nombre de “pila”
precisamente por esta característica. A la hora de retirar elementos, únicamente
podremos tomar el que está en la cima de la pila, que será el último que fue apilado.
Para definir el tipo de pila debemos, por tanto, diseñar las siguientes operaciones:
PilaVacia:  Pila
EsPilaVacia: Pila  Booleano
Apilar : Pila X TipoBase  Pila
Cima : Pila  TipoBase
Desapilar : Pila  Pila
La lista anterior de operaciones disponibles para un tipo se conoce con el nombre de
signatura, y en ella se establecen los nombres de las operaciones y el número y tipo de
parámetros de cada una de ellas. En la signatura de un cierto tipo T se distinguen tres
tipos de operaciones:
1. Operaciones constructoras: son aquellas en las que el tipo T aparece como
resultado devuelto, pero no como parámetro de la operación.
2. Operaciones modificadoras: son aquellas en las que el tipo T aparece tanto en la
lista de parámetros como en el resultado devuelto.
3. Operaciones de consulta: son aquellas en las que el tipo T aparece únicamente
en la lista de parámetros.
En el caso del tipo pila anterior, la única operación constructora sería PilaVacia; las
operaciones Apilar y Desapilar serían modificadoras, y las operaciones EsPilaVacia y
Cima serían de consulta. Las operaciones Cima y Desapilar tendrán como precondición
que la pila que reciben como parámetro no sea vacía, pues para una pila vacía no estaría
definida su cima ni se podría desapilar.
Se puede definir una pila de caracteres mediante las siguientes declaraciones:
TPNodoPilaCars = TIPO Apuntador a NodoPilaCars;
NodoPilaCars=Tipo < dato: Carácter; sig : TPNodoPilaCars >;
PilaCars = TPNodoPilaCars;
PilaVacia  PilaCars : una función
ALGORITMO
PilaVacia ← NULO
FIN

EsPilaVacia(p : PilaCars)  Carácter : una función


ALGORITMO
EsPilaVacia ← p = PilaVacia
FIN

Cima(p : PilaCars)  Carácter : una función


PRE ( no EsPilaVacia (p) )
ALGORITMO
Cima ← p↑.dato
FIN

Apilar(p : PilaCars; c : Carácter) PilaCars : una función


LÉXICO
q : TPNodoPilaCars;
ALGORITMO
Nuevo(q);
q↑ ← < c, p >;
Apilar ← q
FIN
Desapilar(p : PilaCars)  PilaCars : una función
PRE ( NO EsPilaVacia(p) )
LÉXICO
q : PilaCars;
ALGORITMO
q ← p↑ .sig;
Destruir(p);
Desapilar ← q
FIN

Es importante que se sepa que cuando se definen tipos de datos basados en estructuras
de datos complejas, las operaciones usuales de igualdad o desigualdad, que se realizan
por defecto mediante los operadores booleanos = y ≠, producirían resultados que no se
corresponden con la semántica del tipo implementado. Por ejemplo, suponga que
declaramos dos variables, p1 y p2 de tipo PilaCars, y efectuamos una comparación
como la siguiente:
SI p1 = p2 ENTONCES
...
FIN_SI;
Lo que estamos comparando en realidad no son las pilas, es decir, si constan de los
mismos elementos y están en idéntico orden, sino los apuntadores p1 y p2, puesto que
ambas variables son de tipo PilaCars el cual, a su vez, es un tipo apuntador. La
comparación p1 = p2 devolverá Verdadero si y sólo si los apuntadores p1 y p2
apuntan a la misma dirección de memoria.

Insertar elemento en una pila:

Apilar en una pila vacía:


Partiendo que ya tiene el nodo a insertar y un puntero que apunte a él, además el puntero
a la pila valdrá NULO:
El proceso es:
1. nodo^.siguiente apunte a NULO
2. Pila apunte a nodo.

Meter en una pila no vacía:


Se puede considerar el caso anterior como un caso particular de éste, la única diferencia
es el siguiente será pila. Se puede utilizar este procedimiento para ambos casos.
El proceso sigue siendo muy sencillo:
1. Hacemos que nodo^.siguiente apunte a Pila.
2. Hacemos que Pila apunte a nodo.

Desapilar: leer y eliminar un elemento


Sólo existe un caso posible, en las pilas sólo se puede leer y sacar desde le extremo de la
pila.
1. Se utiliza un puntero auxiliar.
2. Se hace apuntar al auxiliar al primer elemento de la pila, es decir a Pila.
3. Se avanza con la pila, asignando a Pila la dirección del segundo nodo de la
pila Pila^.siguiente.
4. Se conserva el contenido del nodo para devolverlo como retorno. La operación
sacar equivale a leer y borrar.
5. Se libera la memoria asignada al auxiliar, que es el nodo a liberar.
Si la pila sólo tiene un nodo, el proceso sigue siendo válido, ya que el valor de
Pila^.siguiente es NULO, y después de eliminar el último nodo la pila quedará vacía, y
el valor de Pila será NULO.
Algoritmo de la función "push" Apilar:
1. Se Crea un nodo para el valor que se colocara en la pila.
2. Se hace que nodo^.siguiente apunte a Pila.
3. Pila debe apuntar al nuevo nodo.

Algoritmo de la función "pop" Desapilar:


1. Se hace que nodo apunte al primer elemento de la pila, es decir a Pila.
2. Se asigna a Pila la dirección del segundo nodo de la pila: Pilasiguiente.
3. Se guarda el contenido del nodo retornarlo.
4. Se libera la memoria asignada a nodo.

El tipo cola
El tipo cola difiere del tipo pila únicamente en la forma de inserción y extracción de
elementos. Una cola es una estructurade tipo FIFO (del inglés, First-Input, First-
Output), es decir, primero en entrar, primero en salir. Se conoce con el nombre de cola
por ser la que habitualmente se utiliza en las colas de la vida cotidiana: el primero que
llega (que entra en la cola) es el primero en ser atendido (en ser eliminado de la cola).
TPNodoColaCars = TIPO Apuntador a NodoColaCars;
NodoColaCars = TIPO < dato: carácter; sig TPNodoColaCars >;
ColaCars = TPNodoColaCars;
Las operaciones propias del tipo cola son las siguientes:
ColaVacia :  Cola
EsColaVacia : Cola  Booleano
Encolar : Cola X TipoBase  Cola
Primero : Cola  TipoBase
EliminarPrimero : Cola  Cola

Operaciones básicas con colas


Por las restricciones a esta estructura tienen muy pocas operaciones disponibles. Las
colas sólo permiten añadir y leer elementos:
1. Añadir: Inserta un elemento al final de la cola.
2. Leer: Lee y elimina un elemento del principio de la cola.

Añadir un elemento Encolar:


Las operaciones con colas son muy sencillas, prácticamente no hay casos especiales,
salvo que la cola esté vacía.

Añadir elemento en una cola vacía:


Se parte de tener el nodo a insertar y un puntero que apunte a él, además los punteros
que definen la cola, primero y ultimo que valdrán NULO
El proceso es muy simple, bastará con que:
1. nodo^.siguiente apunte a NULO.
2. Y que los punteros primero y último apunten a nodo.

Añadir elemento en una cola no vacía:


De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una
cola, en este caso, al no estar vacía, los punteros primero y último no serán nulos:
El proceso sigue siendo muy sencillo:
1. Nodo^.siguiente apunte a NULO.
2. Ultimo^,siguiente apunte a nodo.
3. Y se actualiza último, haciendo que apunte a nodo.

Añadir elemento en una cola, caso general:


Para generalizar el caso anterior, sólo necesitamos añadir una operación:
Siempre nodo^.siguiente apunta a NULO.
Si ultimo no es NULO, entonces ultimo^.siguiente apunta a nodo.
Si no el primero también es NULO, significa que la cola estaba vacía, así que primero
también apunta a nodo
Y se actualiza último, haciendo que apunte a nodo.
.
Leer un elemento de una cola Eliminar primero:
Ahora también existen dos casos, que la cola tenga un solo elemento o que tenga más de
uno.

Leer un elemento en una cola con más de un elemento:


1. Se utilaza puntero auxiliar:
2. Se hace que auxiliar apunte al primer elemento de la cola, es decir a primero.
3. Se asigna a primero la dirección del segundo nodo de la cola:
primeroprimero^.siguiente.
4. Se guarda el contenido del auxiliar para devolverlo, la operación de lectura en
colas implican también borrar.
5. Se Libera la memoria asignada a nodo.

Leer un elemento en una cola con un solo elemento:


1. También se requiere un puntero auxiliar:
2. Se Hacemos que auxiliar apunte al primer elemento de la cola.
3. Se le asigna NULO
4. Se guarda el contenido del auxiliar para retornarlo.
5. Se libera la memoria asignada al primer nodo, el que queremos eliminar.
6. El último debe apuntar a NULO.

El tipo lista
El tipo lista es el caso más general de estructura de datos lineal. No existe una forma
prefijada de inserción y extracción de elementos, de modo que éstos pueden ser
insertados en, y también eliminados de, cualquier posición arbitraria.

Listas simplemente enlazadas

Con las listas existe un repertorio más amplio de operaciones básicas que se pueden
realizar:
1. Añadir o insertar elementos.
2. Buscar o localizar elementos.
3. Borrar elementos.
4. Moverse a través de una lista, anterior, siguiente, primero.

Insertar un elemento en una lista vacía:


El proceso es muy simple

1 Crear el nodo Nuevo(Nodo)


2 Guardar la información
Nodo^<Valor, NULO>
3 nodo^.siguiente apunte a NULO
4 Lista apunte a nodo Lista  Nodo

Insertar un elemento en la primera posición de una lista:


El proceso sigue siendo muy sencillo:
1. Crear el Nodo
2. Guardar la información
3. Nodo^.siguiente apunte a Lista, que contiene la dirección del anterior primero.
4. Lista debe apuntar al nuevo nodo.

Insertar un elemento en la última posición de una lista:


Se supone una lista no vacía, el proceso en este caso tampoco es complejo, se necesita
un puntero que señale al último elemento de la lista. La manera de conseguirlo es
empezar por el primero y avanzar hasta que el nodo que tenga como siguiente el valor
NULO.
1. Auxiliar  primero
2. Al Nodo se le asigna el valor y NULO como siguiente Nodo^.  <valor,
NULO>
3. Avanzar hasta que Auxiliar^.siguiente apunte al valor NULO.
4. Hacer que Auxiliar^.siguiente apunte al nuevo Nodo y así queda enlazado.

Insertar un elemento a continuación de un nodo cualquiera de una lista:


En este caso, siguiendo el criterio del ejemplo anterior se hace que el nodo "anterior"
sea aquel a continuación del cual insertaremos el nuevo nodo:
Si ya se dispone del anterior y del nuevo nodo, el proceso a seguir será:
1. Hacer que Nodo^.siguiente apunte a anterior^.siguiente.
2. Hacer que anterior^.siguiente señale Nodo.

Recorrer una lista:


Las listas simplemente enlazadas sólo pueden recorrerse en un sentido, ya que cada
nodo apunta al siguiente.
Para recorrer una lista se utiliza un puntero auxiliar como índice:
1. Se le asigna al puntero índice el valor de Lista.
2. Se recorre con un ciclo de repetición que al menos debe tener una condición, que
el índice no sea NULO.
3. Dentro del Ciclo se asigna al índice el valor del nodo siguiente al índice actual.

Eliminar elementos en una lista

Eliminar el primer nodo de una lista abierta:


Es el caso más simple. Se parte de una lista con uno o más nodos, y se utiliza un
puntero auxiliar, Nodo:
1. Se hace apuntar a Nodo al primer elemento de la lista, es decir a Lista.
2. Se le asigna a Lista la dirección del segundo nodo de la lista:
ListaLista^.siguiente.
3. Se libera la memoria asignada al primer nodo, apuntado por Nodo.
Si la lista sólo tiene un nodo, el proceso es también válido, ya que el valor de
Lista^.siguiente es NULO, y después de eliminar el primer nodo la lista quedará vacía, y
el valor de Lista será NULO.

Eliminar un nodo cualquiera de una lista simplemente enlazada:


En todos los demás casos, eliminar un nodo se puede hacer siempre del mismo modo.
Supongamos que tener una lista con al menos dos elementos, y un puntero al nodo
anterior al que queremos eliminar. Y un puntero auxiliar nodo.

El proceso es parecido al del caso anterior:


Se busca que Nodo apunte al nodo a eliminar(1).
Se asigna como nodo siguiente del nodo anterior, el siguiente al que se quiere eliminar:
anterior^.siguiente = nodo^.siguiente.(2)
Se elimina la memoria asociada al nodo a eliminar.(3)

Si el nodo a eliminar es el último, es procedimiento es igualmente válido, ya que


anterior pasará a ser el último, y anterior^.siguiente valdrá NULO.

Moverse a través de una lista


Sólo hay un modo de moverse a través de una lista abierta, hacia delante.
Aún así, a veces necesitaremos acceder a determinados elementos de una lista abierta.
Veremos ahora como acceder a los más corrientes: el primero, el último, el siguiente y
el anterior.

Primer elemento de una lista:


El primer elemento es el más accesible, ya que es a ese a que apunta el puntero que
define la lista.
1. Para obtener un puntero al primer elemento bastará con copiar el puntero Lista.

Elemento siguiente a uno cualquiera:


Supongamos que tenemos un puntero nodo que señala a un elemento de una lista. Para
obtener un puntero al siguiente bastará con asignarle el campo "siguiente" del nodo,
nodo^.siguiente.

Elemento anterior a uno cualquiera:


Ya hemos dicho que no es posible retroceder en una lista, de modo que para obtener un
puntero al nodo anterior a uno dado tendremos que partir del primero, e ir avanzando
hasta que el nodo siguiente sea precisamente nuestro nodo, teniendo un puntero auxiliar
para contener el anterior.
Nodo = Primero
MIENTRAS no sea el nodo buscado HACER
Auxiliar = Nodo
Nodo = Nodo^.siguiente
FIN_MIENTRAS
Auxiliar apuntara al nodo anterior al buscado
Último elemento de una lista:
Para obtener un puntero al último elemento de una lista se parte del primer nodo y se
avanza hasta que el nodo apunte a NULO, el auxiliar apuntara al último. También se
puede preguntar anticipadamente por el valor NULO evitándose de este modo el
puntero auxiliar
Suponiendo la lista no vacía
Nodo = Primero;
MIENTRAS Nodo^.siguiente <> NULO HACER
Nodo  Nodo^.siguiente
FIN_MIENTRAS
En este caso Nodo, al final del ciclo apunta l último nodo de la estructura

Como determinar si una lista está vacía:


Basta con comparar el puntero Lista con NULLO si Lista vale NULO la lista está vacía.

Borrar una lista completa:


El algoritmo genérico para borrar una lista completa consiste simplemente en borrar el
primer elemento sucesivamente mientras la lista no esté vacía.

Algoritmo de inserción:
2. El primer paso es crear un nodo para el dato que vamos a insertar.
3. Si Lista es NULO, o el valor del primer elemento de la lista es mayor que el del
nuevo, insertaremos el nuevo nodo en la primera posición de la lista.
4. En caso contrario, se debe buscar el lugar adecuado para la inserción, tenemos
un puntero "anterior". Lo inicializamos con el valor de Lista, y avanzaremos
mientras anterior^.siguiente no sea NULO y el dato que contiene
anterior^.siguiente sea menor que el dato a insertar.
5. Ahora anterior señalando al nodo previo al insertar, por lo que se enlazan lo
puntero de modo que el siguiente del nuevo sea el que era siguiente del anterior
y al siguiente del anterior se lo hace apuntar al nuevo nodo. Se debe recordar que
anterior contiene el valor inmediato anterior al valor insertado y el siguiente es
mayor. El nuevo nodo debe intercalarse entre ambos.
En el capitulo de algoritmos puntuales se ofrecen varios ejemplos para insertar nodos en
situaciones distintas y con distintos fines

Algoritmo para borrar un elemento:


Para eliminar un nodo se necesita disponer de un puntero al nodo anterior.
1. Lo primero será localizar el nodo a eliminar, si es que existe. Pero sin perder el
puntero al nodo anterior.
2. Se parte del nodo primero, y del valor NULO para anterior.
3. Se avanza mientras nodo no sea NULO o mientras que el valor almacenado en
nodo sea menor que el que buscamos.
4. Ahora pueden darse tres casos:
5. Que el nodo sea NULO, esto indica que todos los valores almacenados en la lista
son menores que el que buscamos y el nodo que buscamos no existe. Entonces
se retorna sin borrar nada.
6. Que el valor almacenado en nodo sea mayor que el buscado, en ese caso también
se retorna sin borrar nada, ya que esto indica que el nodo que buscado no existe.
7. Que el valor almacenado en el nodo sea igual al buscado, nuevo existen dos
casos:
a. Que anterior sea NULO. Esto indicaría que el nodo que se quiere borrar
es el primero, así que se modifica el valor de Lista para que apunte al
nodo siguiente al que se quiere borrar.
b. Que anterior no sea NULO, el nodo no es el primero, así que se asigna a
anterior^.siguiente la dirección de nodo^.siguiente.
Después de 7 , se libera la memoria de nodo.

Implementación de algoritmos para listas


TPNodoListaCars = TIPO Apuntador a NodoListaCars;
NodoListaCars=TIPO <dato : carácter; sig TPNodoListaCars >;
ListaCars = TPNodoListaCars;
Veamos como podemos implementar, haciendo uso de esta representación, las
funciones de una posible especificación:
ListaVacia :  Lista
EsListaVacia : Lista  Booleano
Long : Lista  Entero
InsertaEnPos : Lista X Entero X TipoBase  Lista
BorraDePos : Lista X Entero  Lista
BuscaPos : Lista X TipoBase  Entero
BuscaElem : Lista X Entero  TipoBase
InsertaEnPos(lis:ListaCars;pos:Entero;e:Carácter)
ListaCars : una función
PRE { 1 ≤ pos ≤ Long(lis) + 1 }
LÉXICO
Res, p, q : TPNodoListaCars;
I : Entero;
ALGORITMO
Nuevo(p);
p↑.dato ← e;
SI pos = 1 ENTONCES
res ← p;
p↑.sig ← lis;
SI_NO
res ← lis;
q ← lis;
PARA i [3, pos] HACER
q ← q↑.sig
FIN_PARA;
p↑.sig ← q↑.sig;
q↑.sig ← p
FIN_SI;
InstertaEnPos ← res
FIN

BorraDePos(lis : ListaCars; pos : Entero)  ListaCars: una


función
PRE { 1 ≤ pos ≤ Long(lis) }
LÉXICO
res, q, p : TPNodoListaCars;
i : Entero;
ALGORITMO
SI pos = 1 ENTONCES
res ← lis↑.sig;
Destruir(lis)
SI_NO
res ← lis;
p ← lis;
i RECORRIENDO [2, pos] HACER
q ← p;
p ← p↑.sig
FIN_RECORRIENDO;
q↑.sig ← p↑.sig;
Destruir(p)
FIN_SI;
BorraDePos ← res
FIN

Listas circulares
Una lista circular es una lista lineal en la que el último nodo a punta al primero.
Las listas circulares evitan excepciones en la operaciones que se realicen sobre ellas. No
existen casos especiales, cada nodo siempre tiene uno anterior y uno siguiente.
En algunas listas circulares se añade un nodo especial de cabecera, de ese modo se evita
la única excepción posible, la de que la lista esté vacía.
El nodo típico es el mismo que para construir listas abiertas es una estructura
autoreferenciada que tiene un campo con la información y otro campo con un puntero a
una estructura del mismo tipo:

Lista es el puntero para contener el inicio de las listas, tanto abiertas como circulares.
En el caso de las circulares, apuntará a un nodo cualquiera de la lista.

A pesar de que las listas circulares simplifiquen las operaciones sobre ellas, también
introducen algunas complicaciones. Por ejemplo, en un proceso de búsqueda, no es tan
sencillo dar por terminada la búsqueda cuando el elemento buscado no existe.
Por ese motivo se suele resaltar un nodo en particular, que no tiene por qué ser siempre
el mismo. Cualquier nodo puede cumplir ese propósito, y puede variar durante la
ejecución del programa.
Otra alternativa que se usa a menudo, y que simplifica en cierto modo el uso de listas
circulares es crear un nodo especial de hará la función de nodo cabecera. De este modo,
la lista nunca estará vacía, y se eliminan casi todos los casos especiales.
Operaciones básicas con listas circulares
A todos los efectos, las listas circulares son como las listas abiertas en cuanto a las
operaciones que se pueden realizar sobre ellas:
1. Añadir o insertar elementos.
2. Buscar o localizar elementos.
3. Borrar elementos.
4. Moverse a través de la lista.
Cada una de estas operaciones podrá tener varios casos especiales, por ejemplo,
tendremos que tener en cuenta cuando se inserte un nodo en una lista vacía, o cuando se
elimina el único nodo de una lista.

Añadir un elemento:
El único caso especial a la hora de insertar nodos en listas circulares es cuando la lista
esté vacía.

Añadir elemento en una lista circular vacía:


Partiremos de que ya tenemos el nodo a insertar y un puntero que apunte a él, además el
puntero que define la lista, que valdrá NULO:

El proceso es muy simple:


1. lista apunta a nodo.
2. lista^.siguiente apunte a nodo.

Añadir elemento en una lista circular no vacía:


De nuevo partiremos de un nodo a insertar, con un puntero que apunte a él, y de una
lista, en este caso, el puntero no será nulo:

El proceso sigue siendo muy sencillo:


1. Nodo^.siguiente apunte a lista^.siguiente.
2. lista^.siguiente apunte a nodo.
Añadir elemento en una lista circular, caso general:
Para generalizar los dos casos anteriores, sólo necesitamos añadir una operación:
1. Si lista está vacía hacemos que lista apunte a nodo.
2. Si lista no está vacía, hacemos que nodo^.siguiente apunte a lista^.siguiente.
3. Después que lista^.siguiente apunte a nodo.

Buscar o localizar un elemento de una lista circular


Para buscar elementos en una lista circular sólo hay que tener una precaución, es
necesario almacenar el puntero del nodo en que se empezó la búsqueda, para poder
detectar el caso en que no exista el valor que se busca. Por lo demás, la búsqueda es
igual que en el caso de las listas abiertas, salvo que podemos empezar en cualquier
punto de la lista.

Eliminar un elemento de una lista circular:


Para ésta operación podemos encontrar tres casos diferentes:
1. Eliminar un nodo cualquiera, que no sea el apuntado por lista.
2. Eliminar el nodo apuntado por lista, y que no sea el único nodo.
3. Eliminar el único nodo de la lista.
En el primer caso necesitamos localizar el nodo anterior al que queremos borrar. Como
el principio de la lista puede ser cualquier nodo, haremos que sea precisamente lista
quien apunte al nodo anterior al que queremos eliminar.
Esto elimina la excepción del segundo caso, ya que lista nunca será el nodo a eliminar,
salvo que sea el único nodo de la lista.
Una vez localizado el nodo anterior y apuntado por lista, hacemos que lista^.siguiente
apunte a nodo^.siguiente. Y a continuación borramos nodo.
En el caso de que sólo exista un nodo, será imposible localizar el nodo anterior, así que
simplemente eliminaremos el nodo, y haremos que lista valga NULO

Eliminar un nodo en una lista circular con más de un elemento:


Considerando los dos primeros casos como uno sólo.

El primer paso es conseguir que lista apunte al nodo anterior al que se quiere eliminar.
1. Esto se consigue haciendo que lista valga lista^.siguiente mientras
lista^.siguiente sea distinto de nodo.
2. Se hacemos que lista^.siguiente apunte a nodo^.siguiente.
3. Se elimina el nodo.
Eliminar el único nodo en una lista circular:
Este caso es mucho más sencillo. Si lista es el único nodo de una lista circular:
1. Borramos el nodo apuntado por lista.
2. Hacemos que lista valga NULO.

Otro algoritmo para borrar nodos:


Existe un modo alternativo de eliminar un nodo en una lista circular con más de un
nodo.
Si se quiere eliminar un nodo apuntado por nodo:

1. Se copia el contenido del nodo^.siguiente sobre el contenido de nodo.


2. Se hace que nodo^.siguiente apunte a nodo^.siguiente^.siguiente.
3. Se elimina nodo^.siguiente.
Si lista es el nodo^.siguiente, se hace lista = nodo.

Este método también funciona con listas circulares de un sólo elemento, salvo que el
nodo a borrar es el único nodo que existe, y hay que hacer que lista apunte a NULO.

En una lista circular desde cualquier nodo se puede alcanzar cualquier otro. En una lista
simplemente enlazada apuntando a un nodo no es posible volver a nodos anteriores, es
necesario volver a recorrer desde el principio. En una lista circular apuntando a un nodo
se puede volver al predecesor recorriendo hacia delante.
El riesgo con esta estructura es que de no ser riguroso se puede caer en ciclos infinitos
la lista esta sin orden el puntero a lista puede apuntar, como señalamos a cualquier
nodo.
Estas lista tienen código sencillo pero ineficiente. Para insertar un nodo en esta lista sin
orden:
1. Si se aceptan repeticiones simplemente habrá que insertar el nodo en el lugar
donde se encuentra el puntero.
2. Si no se aceptan repeticiones habrá que recorrer la lista, sin caer en ciclos
infinitos para ver si el elemento a insertar ya existe. Para evitar ciclos
infinitos se debe:
a. Guardar en un puntero auxiliar el nodo donde se encuentra el comienzo
del recorrido.
b. Detener el ciclo al encontrar el dato, si es que esta o al igualar el puntero
de recorrido con el auxiliar.

Listas Circulares Ordenadas


En estas estructuras, como en las listas simplemente enlazadas se señala con un puntero
el inicio de la estructura. El código es un poco mas complejo que en las listas sin orden
pero mas eficientes ya que se evitan recorridos innecesarios.
La bibliografía discute donde debe apuntar el puntero al inicio de esta listas para
simplificar el código.
En las listas simplemente enlazadas para insertar un nodo y tener la dirección del
anterior para hacer la inserción se podía recorrer con dos punteros, uno que señala al
nodo actual y otro que señala al nodo anterior. También es posible evitarse el uso de dos
punteros preguntando el forma avanzada por el dato contenido en el próximo nodo.
En una lista circular no vacía ningún siguiente es NULO y el recorrido se hace con un
puntero preguntando en forma avanzada. Como en el recorrido se pregunta en forma
avanzada, el puntero debe apuntar al predecesor del primer nodo, esto por si la inserción
debiera hacerse delante del primer nodo.
Si hay un único nodo, predecesor y sucesor apuntan a dicho nodo.

Lista circular con header


Es una lista especial donde hay un nodo especial llamado HEADER, que guarda
información genérica sobre los otros nodos.
Por ejemplo si los nodos tienen los nombres de las personas y los sueldos, el nodo
HEADER podría tener la cantidad de nodos de la lista, excluyéndolo, el sueldo
promedio y el mínimo.
Para definir este tipo de registros los lenguajes de programación permiten definir
registros variantes o uniones, estas estructuras tienen un tratamiento similar a los
registros y pueden tener una parte fija y una parte variante en la que se solapa la
información.
Las ventajas que pueden señalarse entre las listas circulares con header y sin header es:
1. El Header guarda información genérica sobre la lista circular. No es
necesario recorrer la lista para obtener información global. Se debe tener en
cuenta que al insertar o eliminar un nodo de la lista se debe actualizar la
información de header para que sea consistente.
2. El código se simplifica porque el header esta siempre, aunque la lista este
vacía. En este caso el problema de poner el puntero a la lista esta resuelto, se
coloca siempre en el header.

Lista Circular Ordenada con HEADER

Definiciones
Nodo = (header, común) // enumera dos valores posibles si es header o común.
TipoPunteroCircularCon Header = TIPO Apuntador a TipoNodo;
TipoNodo = <
Sig : TipoPuntero;
Según tag: Nodo
Header : (cant: Entero; promedio Real; mínimo Real);
// parte del nodo cabecera//
común : (nombre Cadena; Sueldo : Real)
// Parte común del resto de los nodos//
FIN_SEGUN
>;
TipoDato = <nombre : Cadena; sueldo : Rel>

CrearVacia (Dato_Resultadolch_TipoPunteroCircularConHeader): una acción


ALGORITMO
Nuevo(lch);
Lch^.tag  header
Lch^.cant  0;
Lch^.promedio  0;
Lch^.Minimo  ValorAlto;
Lch^.sig  lch
FIN

esVacia (DATO lch: TipoPunteroCircularConHeader): booleano UNA ACCION


// si el siguiente es el mismo nodo entonces solo hay un nodo y este es el header//
ALGORITMO
esVacia  lch = lch^.sig
FIN

insertarOrdenado(DATO_RESULTADO lch :TipoPunterCircularConHeader; DATO


dato: TipoDato)
ALGORITMO
SI (Dato.sueldo < lcj^.Minimo)
ENTONCES
Lch^.cant = Lch^.cant + 1
// el resto de las operaciones generales//
FIN_SI
Salir  Falso
REPETIR
SI (lch^.sig^.nombre < dato.nombre)
ENTONCES
Lch  lch^.sig
SINO
Salir  verdadero
FIN_SI
HASTA que (salir o lch^.sig.tag = header)
Nuevo(Aux)
Aux^.sig  lch^.sig
Lch^.sig  aux
Aux^.nombre  dato.nombre
Aux^.sueldo  dato.sueldo
Aux^.tag  común
FIN

ImprimirOrdenado(DATO lch :TipoPunterCircularConHeader)


ALGORITMO
REPETIR
Imprimir(lch^.sig^.nombre)
Imprimir(lch^.sig^.sueldo)
Lch  lch^.sig
HASTA QUE lch^.sig^.tag = header
FIN

Listas doblemente enlazadas


Una lista doblemente enlazada es una lista lineal en la que cada nodo tiene dos enlaces,
uno al nodo siguiente, y otro al anterior.
Las listas doblemente enlazadas no necesitan un nodo especial para acceder a ellas,
pueden recorrerse en ambos sentidos a partir de cualquier nodo, esto es porque a partir
de cualquier nodo, siempre es posible alcanzar cualquier nodo de la lista, hasta que se
llega a uno de los extremos.
El nodo típico es el mismo que para construir las listas que hemos visto, salvo que
tienen otro puntero al nodo anterior:

El movimiento a través de listas doblemente enlazadas es más sencillo, y como veremos


las operaciones de búsqueda, inserción y borrado, también tienen más ventajas.

Operaciones básicas con listas doblemente enlazadas


Es imilar al de listas:
1. Añadir o insertar elementos.
2. Buscar o localizar elementos.
3. Borrar elementos.
4. Moverse a través de la lista, siguiente y anterior.

Añadir un elemento:
La estructura s diferente a las vistas anteriormente por lo que se analizaran todos los
casos posibles de inserción.

Añadir elemento en una lista doblemente enlazada vacía:


también se parte que se dispone del nodo a inserta y el puntero que define la lista, que
valdrá NULO:

El proceso es muy simple:


lista apunta a nodo.
Lista^.siguiente y lista^.anterior apunten a NULO.
Insertar un elemento en la primera posición de la lista:
Se parte de una lista no vacía y lista apunta al primer elemento de la lista doblemente
enlazada:

El proceso es el siguiente:
Nodo^.siguiente debe apuntar a Lista.
Nodo^.anterior apuntará a Lista^.anterior.
Lista^.anterior debe apuntar a nodo.

Lista no tiene por qué apuntar a ningún miembro concreto de una lista doblemente
enlazada, cualquier miembro es igualmente válido como referencia.

Insertar un elemento en la última posición de la lista:


Igual que en el caso anterior, se parte de una lista no vacía, y de nuevo para simplificar,
que Lista está apuntando al último elemento de la lista:

El proceso es el siguiente:
1. Nodo^.siguiente debe apuntar a Lista^.siguiente (NULO).
2. Lista^.siguiente debe apuntar a nodo.
3. Nodo^.anterior apuntará a Lista.

Insertar un elemento a continuación de un nodo cualquiera de una lista:

Bien, este caso es más genérico, ahora partimos de una lista no vacía, e insertaremos un
nodo a continuación de uno nodo cualquiera que no sea el último de la lista:
El proceso sigue siendo muy sencillo:
1. Que Nodo^.siguiente apunte a lista^.siguiente.
2. Que Lista^.siguiente apunte a nodo.
3. Que nodo^.anterior apunte a lista.
4. Que nodo^.siguiente^.anterior apunte a nodo.
Añadir elemento en una lista doblemente enlazada, caso general:
1. Si lista está vacía se hace que Lista apunte a nodo, y nodo^.anterior y
nodo^.siguiente a NULO.
2. Si lista no está vacía, entonces que nodo^.siguiente apunte a Lista^.siguiente.
3. Luego que Lista^.siguiente apunte a nodo.
4. Que nodo^.anterior apunte a Lista.
5. Si nodo^.siguiente no es NULO, entonces nodo^.siguiente^.anterior apunte a
nodo.

Buscar un elemento de una lista doblemente enlazada


En muchos aspectos, una lista doblemente enlazada se comporta como dos listas
abiertas que comparten los datos. Pero además se tiene la ventaja que se puede avanzar
y retroceder desde cualquier nodo, sin necesidad de volver a uno de los extremos de la
lista.
Para recorrer una lista se procede de un modo parecido al que definió con las listas
simplemente enlazadas, ahora no se necesita un puntero auxiliar, y debe tenerse en
cuenta que Lista no tiene por qué estar en uno de los extremos:
Se retrocede hasta el comienzo de la lista, asignando a lista el valor de lista^.anterior
mientras lista^.anterior no sea NULO.
Se abre un ciclo de repetición que al menos debe controlar que el puntero no sea NULO.
Dentro del ciclo se le asigna a lista la dirección del nodo siguiente.

Eliminar un elemento de una lista doblemente enlazada:


Se Analizan cuatro casos diferentes:
1. Eliminar el único nodo de una lista doblemente enlazada.
2. Eliminar el primer nodo.
3. Eliminar el último nodo.
4. Eliminar un nodo intermedio.
Para los casos que lo permitan consideraremos dos casos: que el nodo a eliminar es el
actualmente apuntado por Lista o que no.

Eliminar el único nodo en una lista doblemente enlazada:

En este caso, ese nodo será el apuntado por Lista.


1. Eliminamos el nodo.
2. Hacemos que Lista apunte a NULL.
Eliminar el primer nodo de una lista doblemente enlazada:
Se tienen los dos casos posibles, que el nodo a borrar esté apuntado por Lista o que no.
Si lo está, simplemente hacemos que Lista sea Lista^.siguiente.

1. Si nodo apunta a Lista, se hace que Lista apunte a Lista^.siguiente.


2. que nodo^.siguiente^.anterior apunte a NULO
3. Borramos el nodo apuntado por nodo.

El paso 2 separa el nodo a borrar del resto de la lista, independientemente del nodo al
que apunte Lista.

Eliminar el último nodo de una lista doblemente enlazada:


De nuevo tenemos los dos casos posibles, que el nodo a borrar esté apuntado por Lista o
que no. Si lo está, simplemente hacemos que Lista sea Lista^.anterior.

1. Si nodo apunta a Lista, se hace que Lista apunte a Lista^.anterior.


2. que nodo^.anterior^.siguiente apunte a NULL
3. Se Borra el nodo apuntado por nodo.

Eliminar un nodo intermedio de una lista doblemente enlazada:


De nuevo tenemos los dos casos posibles, que el nodo a borrar esté apuntado por Lista o
que no.

Si lo está, simplemente hacemos que Lista sea Lista^.anterior o Lista^.siguiente


Se trata de un caso más general de los dos casos anteriores..
1. Si nodo apunta a Lista, se hace que Lista apunte a Lista^.anterior (o
Lista^.siguiente).
2. que nodo^.anterior^.siguiente apunte a nodo^.siguiente.
3. que nodo^.siguiente^.anterior apunte a nodo^.anterior.
4. Borrar el nodo apuntado por nodo.

Eliminar un nodo de una lista doblemente enlazada, caso general:


1. De nuevo están los dos casos posibles, que Si nodo apunta a Lista,
2. Si Lista^.anterior no es NULO se hace que Lista apunte a Lista^.anterior.
3. Si Lista^.siguiente no es NULO se hace que Lista apunte a Lista^.siguiente.
4. Si ambos son NULO, se hace que Lista sea NULO.
5. Si nodo^.anterior no es NULO, se hace que nodo^.anterior^.siguiente apunte
a nodo^.siguiente.
6. Si nodo^.siguiente no es NULO, Se hace que nodo^.siguiente^.anterior
apunte a nodo^.anterior.
7. Se borra el nodo apuntado por el nodo a borrar esté apuntado por Lista o que
no. Si lo está, simplemente hacemos que Lista sea Lista^.anterior, si no es
NULO o Lista^.siguiente en caso contrario.

A modo de síntesis con estructuras enlazadas


Listas simplemente enlazadas: cada elemento sólo dispone de un puntero, que
apuntará al siguiente elemento de la lista o valdrá NULO si es el último elemento.
Pilas: son un tipo especial de lista, conocidas como listas LIFO (Last In, First Out: el
último en entrar es el primero en salir).
Colas: otro tipo de listas, conocidas como listas FIFO (First In, First Out: El primero en
entrar es el primero en salir).
Listas circulares no ordenadas: el último elemento apunta al primero. Cualquier nodo
puede ser el nodo de entrada y salida.
Lista circular ordenada: por convención el puntero a la lista circular apunta al
predecesor del primer nodo.
Lista con Header: Existe un nodo especial donde se guarda información genérica sobre
los demás nodos.
Listas doblemente enlazadas: cada elemento dispone de dos punteros, uno a punta al
siguiente elemento y el otro al elemento anterior.
Árboles: cada elemento dispone de dos o más punteros, pero las referencias nunca son a
elementos anteriores, de modo que la estructura se ramifica y crece igual que un árbol.
Árboles binarios: son árboles donde cada nodo sólo puede apuntar a dos nodos.
Árboles binarios de búsqueda (ABB): son árboles binarios ordenados. Desde cada
nodo todos los nodos de una rama serán mayores, según la norma que se haya seguido
para ordenar el árbol, y los de la otra rama serán menores.
Árboles AVL: son también árboles de búsqueda, pero su estructura está más optimizada
para reducir los tiempos de búsqueda.
Árboles B: son estructuras más complejas, aunque también se trata de árboles de
búsqueda, están mucho más optimizados que los anteriores.
Tablas HASH: son estructuras auxiliares para ordenar listas.
Grafos: es el siguiente nivel de complejidad, podemos considerar estas estructuras
como árboles no jerarquizados.
Diccionarios. Se implementan con tablas Hash para la búsqueda.
CAPITULO 6 Árboles
Objetivos de aprendizaje
Dominando los temas del presente capitulo Usted podrá.
1. Utilizar estructuras enlazadas más complejas.
2. Comprender diferencias entre estructuras lineales y arbóreas

Introducción
En este capitulo se introduce el concepto de estructuras enlazadas arborescentes.
Estructuras de datos dinámicas arborescentes y el concepto de tablas hash.

Árboles
Una de las estructuras de datos no lineales más utilizadas en la práctica son los árboles.
Se puede definir el árbol como colección de elementos llamados nodos, con una
relación jerárquica entre los mismos, uno de los nodos es distinguido y se llama raíz.
Desde un punto de vista matemático, se puede definir un árbol como un caso particular
de grafo en el que:
1. Existe un nodo distinguido, que se llama nodo raíz
2. Los arcos que conectan los nodos de este grafo son dirigidos, y la dirección
de éstos establece las relaciones de predecesor y sucesor. Si en el árbol
existe un arco que va de un nodo A a un nodo B (AB), entonces se dice que
A es predecesor de B, y que B es sucesor de A.
3. Cada nodo tiene uno y sólo un nodo predecesor, con la única excepción del
nodo raíz, que no tiene predecesor.
4. Cada nodo puede tener cero o más nodos sucesores.
Si A es predecesor de B y B lo es de C (ABC), se dice que A es predecesor directo
de B y predecesor indirecto de C o que B es sucesor directo de A y que C es sucesor
indirecto de A.
De la definición de árbol se deducen las siguientes propiedades:
Un árbol es necesariamente acíclico, es decir, un nodo no puede ser sucesor (sucesor*)
ni predecesor (predecesor*) de sí mismo.
Una estructura árbol establece una partición entre sus nodos componentes: el nodo raíz,
y cada conjunto de sucesores por cada arco que parte el nodo raíz. Esta misma partición
se puede establecer con respecto a cada nodo y sus sucesores (el subárbol delimitado
por ese nodo).
Un árbol es una estructura no lineal en la que cada nodo puede apuntar a uno o varios
nodos. Se puede dar una definición recursiva: un árbol es una estructura en compuesta
por un dato y varios árboles.
Definiciones comunes
1. Nodo hijo: cualquiera de los nodos apuntados por uno de los nodos del
árbol. En el ejemplo, 'L' y 'M' son hijos de 'G'.
2. Nodo padre: nodo que contiene un puntero al nodo actual. En el ejemplo, el
nodo 'A' es padre de 'B', 'C' y 'D'.
3. Hermano: un nodo n1 se dice que es hermano de otro nodo n2 si ambos
comparten un mismo nodo predecesor.
Los árboles con los que trabajara tienen otra característica importante: cada
nodo sólo puede ser apuntado por otro nodo, es decir, cada nodo sólo tendrá
un padre. Esto hace que estos árboles estén fuertemente jerarquizados, y es lo
que en realidad les da la apariencia de árboles.
En cuanto a la posición dentro del árbol:
4. Nodo raíz: nodo que no tiene padre. Este es el nodo que usaremos para
referirnos al árbol. En el ejemplo, ese nodo es el 'A'.
5. Nodo hoja: nodo que no tiene hijos. En el ejemplo hay varios: 'F', 'H', 'I', 'K',
'L', 'M', 'N' y 'O'.
6. Nodo rama: aunque esta definición apenas la usaremos, estos son los nodos
que no pertenecen a ninguna de las dos categorías anteriores. En el ejemplo:
'B', 'C', 'D', 'E', 'G' y 'J'.
7. árbol completo árbol en el que en cada nodo o bien todos o ninguno de los
hijos existe.
8. Orden: es el número potencial de hijos que puede tener cada elemento de
árbol. De este modo, diremos que un árbol en el que cada nodo puede
apuntar a otros dos es de orden dos, si puede apuntar a tres será de orden
tres, etc.
Orden n: Existe un nodo con n descendientes y no hay nodos con mas
descendientes que el. No importa el orden.
n-ario: se dice que el árbol es n-ario si cada nodo puede tener como máximo
n nodos sucesores e importa el orden en el que están ubicados. Guardan un
solo elemento de información con n punteros como máximo
9. Grado: el número de hijos que tiene el elemento con más hijos dentro del
árbol. En el árbol del ejemplo, el grado es tres, ya que tanto 'A' como 'D'
tienen tres hijos, y no existen elementos con más de tres hijos.
10. Nivel: se define para cada elemento del árbol como la distancia a la raíz,
medida en nodos. El nivel de la raíz es cero y el de sus hijos uno. Así
sucesivamente. En el ejemplo, el nodo 'D' tiene nivel 1, el nodo 'G' tiene
nivel 2, y el nodo 'N', nivel 3.
11. Altura: la altura de un árbol se define como el nivel del nodo de mayor
nivel. Como cada nodo de un árbol puede considerarse a su vez como la raíz
de un árbol, también podemos hablar de altura de ramas. El árbol del
ejemplo tiene altura 3, la rama 'B' tiene altura 2, la rama 'G' tiene altura 1, la
'H' cero, etc.
12. Profundidad: la profundidad de un árbol es el máximo nivel de un nodo en
ese árbol. Profundidad de un nodo es la cantidad de arcos del nodo al
descendiente hoja más lejano.
13. Longitud: la longitud de un árbol es el máximo número de nodos en el
mismo nivel.
14. Recorrido: Existen 6 formas de recorrido Pre, In, Pos orden y Pre, In y Pos
Orden Inverso para recorrerlos primero en profundidad, también pueden ser
recorridos en amplitud primero.
15. árbol perfectamente balanceado: Es tal que el único nivel que puede estar
incompleto es el ultimo.
16. Factor de balance de un nodo: Diferencia entre la altura de su subárbol
izquierdo y su subárbol derecho.
17. árbol AVL: Es aquel en el cual la altura de los subárboles de cualquier nodo
no difieren en mas de uno. Todos los nodos del árbol AVL tienen factor de
balance –1, 0, 1.
18. Un árbol perfectamente balanceado es AVL, aunque para ser AVL no es
necesario que el árbol este perfectamente balanceado. Ver definiciones.

Operaciones básicas con árboles:


Las operaciones son de tipo bastante semejante a las que se hacen en listas, el repertorio
podrá ser:
1. Añadir o insertar elementos.
2. Buscar o localizar elementos.
3. Borrar elementos.
4. Moverse a través del árbol.
5. Recorrer el árbol completo.

Los algoritmos de inserción y borrado dependen en gran medida del tipo de árbol que se
implemente.

Recorridos por árboles:


Esto se hace a través de las ramas siguiendo los punteros, esos recorridos dependen en
gran medida del tipo y propósito del árbol.
Existen, en principio tres tipos de recorrido de un árbol completo:
1. Pre-orden: En este tipo de recorrido, el valor del nodo se procesa antes de
recorrer las ramas.
2. In-orden: En este tipo de recorrido, el valor del nodo se procesa después de
recorrer la primera rama y antes de recorrer la última.
3. Post-orden: En este tipo de recorrido, el valor del nodo se procesa después de
recorrer todas las ramas:

Eliminar nodos en un árbol:


El proceso general es muy sencillo pero sólo se pueden borrar nodos hoja:
El proceso consiste en:
1. Buscar el nodo padre del que se quiere eliminar.
2. Buscar el puntero del nodo padre que apunta al nodo que se quiere borrar.
3. Liberar el nodo.
Cuando el nodo a borrar no es un nodo hoja, se elimina el árbol cuya raíz es el nodo a
borrar. Se trata de un procedimiento recursivo, aplicando el recorrido PostOrden, y el
proceso será borrar el nodo.
1. El procedimiento es similar al de borrado de un nodo:
2. Buscar el nodo padre del que queremos eliminar.
3. Buscar el puntero del nodo padre que apunta al nodo que queremos borrar.
4. Podar el árbol cuyo padre es nodo.
Árboles ordenados:
Un árbol ordenado, en general, es aquel a partir del cual se puede obtener una secuencia
ordenada siguiendo uno de los recorridos posibles del árbol: inorden, preorden o
postorden.
En estos árboles es importante que la secuencia se mantenga ordenada aunque se añadan
o se eliminen nodos.
Existen varios tipos de árboles ordenados
1. árboles binarios de búsqueda (ABB): son árboles de orden 2 que
mantienen una secuencia ordenada si se recorren en inorden.
2. árboles AVL: son árboles binarios de búsqueda equilibrados, es decir, los
niveles de cada rama para cualquier nodo no difieren en más de 1.
3. árboles perfectamente equilibrados: son árboles binarios de búsqueda en
los que el número de nodos de cada rama para cualquier nodo no difieren en
más de 1. Son por lo tanto árboles AVL también.
4. árboles 2-3: son árboles de orden 3, que contienen dos claves en cada nodo y
que están también equilibrados. También generan secuencias ordenadas al
recorrerlos en inorden.
5. árboles-B: caso general de árboles 2-3, que para un orden M, contienen M-1
claves.

árbol binario de búsqueda


Se trata de árboles de orden 2 en los que se cumple que para cada nodo, el valor de la
clave de la raíz del subárbol izquierdo es menor que el valor de la clave del nodo y que
el valor de la clave raíz del subárbol derecho es mayor que el valor de la clave del nodo.

Operaciones en ABB
El repertorio de operaciones que se pueden realizar sobre un ABB es similar al que se
realiza sobre otras estructuras y además posee e un conjunto de operaciones propias
1. Buscar un elemento.
2. Insertar un elemento.
3. Borrar un elemento.
4. Movimientos a través del árbol:
5. Comprobar si un árbol está vacío.
6. Calcular el número de nodos.
7. Comprobar si el nodo es hoja.
8. Calcular la altura de un nodo.
9. Calcular la altura de un árbol
10. Rotaciones.

Buscar un elemento
1. Se parte del nodo raíz, y las operaciones mas claras y eficientes son
recursivas.
2. Si el árbol está vacío, Finaliza búsqueda, el valor buscado no esta en la
estructura.
3. Si el valor del nodo raíz es igual al elemento que buscamos, finaliza
exitosamente la búsqueda.
4. Si nada de esto ocurre se sigue la búsqueda recursivamente
5. Si el valor buscado es menor que la información del nodo se continúa la
búsqueda en el sub árbol izquierdo.
6. Si el valor es mayor continúa la búsqueda en el sub árbol derecho.
7. El valor de retorno de una función de búsqueda en un ABB puede ser un
puntero al nodo encontrado, o NULO, si no se ha encontrado.

Definiciones
Elem = TIPO Entero;
árbol = TIPO Apuntador a TipoNodo;
TipoNodo = TIPO <izq, der :árbol; info Elem

Buscar (DATO T : árbol; x elem): booleano UNA FUNCION


ALGORITMO Si es igual lo encontro y termina
SI (t <> NULO) la recursion,sino sigue buscando
SI (x<t^.info) Buscar(t^.izq,x); en la rama crrespondiente según
el valor
(x>t^.info) Buscar(t^.der,x);
(x=t^.info) Buscar = VERDADERO
FIN_SI Si apunta al valor nulo signifia que se
(t = NULO) Buscar = FALSO; recorrio la estructura hasta el final y el
FIN_SI valor buscado no se encuentra

FIN

Insertar un elemento
Las inserciones se hacen siempre en las hojas, se utiliza un criterio similar al de las
búsquedas, hasta encontrar el nodo hoja del cual insertar. Los valores deben ser
diferentes por lo que si ya esta el valor en el árbol no se ingresa y se informa error. Se
supone la misma definición de tipos que en el ejemplo anterior.

Insertar (DATO_RESULTADO t: árbol; x: Elem) UNA ACCION


ALGORITMO
SI (t <> NULO)
SI (x<t^.info) Insertar(t^.izq,x);
(x>t^.info) Insertar(t^.der,x);
(x=t^.info) Imprimir(ERROR)
FIN_SI
(t = NULO)
Nuevo(t)
t^.izq  NULO
t^.der  NULO
t^.info = x
FIN_SI
FIN
Observe la similitud con la búsqueda.
La inserción de valores ordenados en forma creciente o decreciente genera árbol con
solo una rama. Estas estructuras se conocen como árboles sesgados o degenerados, es
ineficiente para la búsqueda e ineficiente en el uso de recursos. Tiene l misma eficiencia
de una lista simplemente ordenada con la dificultad de contener un puntero más en cada
nodo.

Borrar un elemento
Para borrar un elemento también hay que basarse algoritmo de búsqueda.
1. Si el elemento no está en el árbol no se lo puede borrar.
2. Si está, puede ocurrir que:
a. Se trata de un nodo hoja: en ese caso se lo borra directamente.
b. Se trata de un nodo rama: en ese caso no se puede eliminar
directamente, pues se perderían todos los elementos del árbol del que es
el padre. Lo que debe hacerse es buscar el nodo más a la izquierda del
subárbol derecho, o el más a la derecha del subárbol izquierdo e
intercambiamos sus valores y a continuación se elimina ese nodo hoja.
Suponiendo el siguiente árbol de búsqueda en el que se harán los borrados

10

5 14

Caso 1 Borrado de un nodo hoja


Si se desea borrar el nodo que contiene el valor 8, que es un nodo hoja
1. Se libera el nodo que contiene el valor
2. Se actualiza el puntero del padre de ese nodo al valor NULO

El árbol original queda como en la figura


10

5 14

Caso 2 Borrado de un nodo con un solo hijo


Se borra del árbol original el nodo que contiene el valor 7
1. Se hace que el padre del nodo a borrar apunte al hijo de este, es decir se
“salta” el nodo a borrar
2. Se libera el nodo

Caso 3 Borrado de un nodo con dos hijos


1. Se reemplaza al nodo por el nodo lexiograficamente adyacente.
o Sucesor inorder, el mas chico de su subárbol derecho
o Predecesor inorder el mas grande de su subárbol izquierdo
2. Se elimina el nodo que lo reemplazo que a lo sumo tiene un solo hijo.

Borrar 10

10

5 14

Lexicograficamente adyacente así queda el árbol después del borrado


9

14
5

Borrar (DATO_RESULTADO t: árbol; x: Elem) UNA ACCION

LEXICO
Aux : árbol

BorrarInOrder(DATO_RESULTADO a: árbol) UNA ACCION


ALGORITMO
SI (a^.der <> NULO) BorrarInOrder(a^.der)
(a^.der = NULO)
Aux^.info = A^.Iinfo Aux es una variable global que llega con la
ireccion del nodo a eliminar. A es el
Aux = a lexicograficamente adyacente, se le asigna
A  a^.izq a l contenido de aux esos vlores. El nodo
FIN_SI con el dato borrar reemplaza sus valores
por esotos, luego a aux se le asigna a para
FIN después eliminarlo en el modulo principal

ALGORITMO
SI (T = NULO) Imprimir(ERROR)
(T <>NULO)
SI (x<t^.info) Borrar(t^.izq,x)
(x>t^.info) Borrar(t^.der,x)
(x=t^.info)
Aux = t

FIN_SI
FIN_SI
FIN

Comprobar si un árbol está vacío.


Un árbol está vacío si su raíz es NULO.

Comprobar si el nodo es hoja.


Solo basta con verificar que los punteros a los hijos izquierdo y derecho apunten a
NULO.

EsHoja (DATO t: árbol) : Entero UNA FUNCION


ALGORITMO
EsHoja  (t^.izq = NULO y t^.der = NULO)
FIN.

Calcular el número de nodos.


Para contar los nodos se puede recorrer u árbol en cualquiera de las formas de recorrido
e ir incrementando un contador

CantHojas (DATO t: árbol): UNA FUNCION


ALGORITMO
SI (t = NULO)
ENTONCES SI(EsHoja(t))
ENTONCES
CantHojas 1
SI:_NO
CantHojasCantHojas(t^.izq)+CantHojas(t¨.der)
FIN_SI
SI_NO
CantHojas  0
FIN.
.
Calcular la altura de un árbol.
La altura del árbol es la altura del nodo de mayor altura. Para buscar este valor habrá
que recorrer todo el árbol

Altura (DATO t: árbol): Entero UNA FUNCION


LEXICO
Máximo (a ,b : ENTERO) : Entero
ALGORITMO
SI (a>b)
ENTONCES
Máximo = a
SI_NO
Máximo = b
FIN_SI
FIN
ALGORITMO
SI t <> NULO
ENTONCES
Altura  1 + Máximo(Altura(t^..izq), Altura (t^.der))
SINO
Altura = -1
FIN_SI
FIN.

árbol AVL
Definición
Un árbol AVL (llamado así en homenaje a Adelson-Velskii y Landis) es un árbol
binario de búsqueda en el que para cada nodo, las alturas de sus subárboles izquierdo y
derecho no difieren en más de 1.
Los AVL son Árboles de búsqueda por lo que se mantienen las operaciones de los
mismas y se agregan operaciones equilibrio o rotación.

Factor de equilibrio
Cada nodo, además de la información y los punteros a los hijos derecho e izquierdo,
debe tener un campo para almacenar el factor de equilibrio.
El factor de equilibrio, como ya se definió, es la diferencia entre las alturas del árbol
derecho y el izquierdo:
FE = altura subárbol derecho - altura subárbol izquierdo;
Por definición, para un árbol AVL, este valor debe ser -1, 0 ó 1.

Inserciones en árboles AVL


Inserción en el subárbol izquierdo de la rama izquierda de A
rotación Simple Izquierda Izquierda

N1^.izquierdo  N1^.derecho (N es el nodo desbalanceado N1 su hijo izquierdo)


N1^.derecho  N1
N  N1

Inserción en el subárbol derecho de la rama izquierda de A


rotación Doble derecha Izquierda
N1^. izquierdo  N2^. derecho
N2^. derecho  N1
N^. derecho  N2^. izquierdo
N2^.izquierdo  N
N N2

Inserción en el subárbol derecho de la rama derecha de A


rotación Simple Derecha Derecha
N1^.derecho  N1^. izquierdo (N es el nodo desbalanceado N1 su hijo derecho)
N1^.izquierdo  N1
N  N1

Inserción en el subárbol izquierdo de la rama derecha de A


rotación Doble Izquierda derecha
N1^. derecho  N2^. izquierdo
N2^. izquierdo  N1
N^.izquierdo  N2^.derecho
N2^.derecho  N
N N2

Los árboles-B.
Desarrollado Rudolf Bayer y Eduard M. McCreight.
Los árboles-B no son binarios.
No son árboles de búsqueda, ya que en inglés se denominan B-trees.
No son, en general balanceados.
Los Árboles binarios de búsqueda
1. Son eficientes para la búsqueda si están balanceados.
2. No siempre entran completos en memoria.
Si la memoria es insuficiente para almacenar gran número de datos en un árbol se
pueden reemplazar punteros del árbol por direcciones de almacenamiento en disco
Los árboles-B son árboles de búsqueda de m ramas, y cada nodo puede almacenar un
máximo de m-1 claves.

Características de los árboles-B


1. Se define como orden de un árbol-B es el número máximo de ramas que
pueden partir de un nodo.
2. Si de un nodo de un árbol-B parten n ramas, ese nodo contendrá n-1 claves.
3. El árbol debe estar ordenado.
4. Todos los nodos terminales, (nodos hoja), están en el mismo nivel.
5. Todos los nodos intermedios, excepto el raíz, deben tener entre m/2 y m
ramas no nulas. Por lo tanto el máximo número de claves por nodo es m-1.
6. El mínimo número de claves por nodo es (m/2)-1.

Árboles Multicaminos: Los nodos guardan hasta M-1 clave con un máximo de M hijos.
Un árbol multicamino M-ario: Cada nodo se guarda en disco en un mismo bloque físico
por lo que se lo accede solo una vez. Cada clave de un cierto nodo será tal que las claves
almacenadas en su subárbol izquierdo serán menores y las almacenadas en el subárbol
derecho serán mayores a el.
Tienen la ventaja que compactan la información en una zona física (bloque físico de
disco) por lo que no se requieren muchos accesos para encontrar una clave entre
muchas.
Estos arboles resultan óptimos para la implementación de bases de datos.
Para que sean realmente útiles se los debe mantener balanceados

TABLAS HASHING (DISPERSION)

Las tablas hashing constituyen un TDA especial para la manipulación y


almacenamiento de información en memoria secundaria.
La idea básica consiste en transformar las llaves en direcciones de memoria mediante
una función de transformación.
La tablas de dispersión se aplican cuando el conjunto de llaves posibles es mucho
mayor que el de llaves reales a almacenar.
Hashing se traduce directamente por "picadillo" y se utiliza para referirse a una amplia
familia de funciones que permiten clasificar los datos (llaves) por claves (direcciones de
memoria).
Es seguro que para una función de transformación existan varias llaves distintas de todo
conjunto de llaves que se aplican en la misma dirección de memoria. En general, el
universo de claves es muy amplio, muy superior al de claves (datos) concretas que
aparecen en el conjunto de N datos que trata el programa en cada caso.
El método hashing utiliza tiempo constante por operación (insertar, buscar) en
promedio. En el peor caso se requiere para cada operación, un tiempo proporcional al
tamaño de la tabla; sin embargo con un diseño cuidadoso es posible hacer que sea
pequeña la probabilidad de que la dispersión demande más de un tiempo constante para
cada operación.
Un requisito básico de una buena función de transformación (hashing) es que distribuya
las llaves los más uniforme posible sobre la gama de valores índice.

Definiciones
Dado un conjunto de llaves posibles, X, y un conjunto de direcciones de memoria, D,
una función de transformación, H(x), es una aplicación de muchos contra uno del
conjunto de llaves posible en el conjunto de direcciones de memoria:
H (x) -> D
Lo que buscaremos será una función:
FUNCTION hash (K: clave): [0..H-1];
que clasifica cada dato en una clase.
La idea básica de este método consiste en aplicar una función que traduce un conjunto
de posibles valores llave en un rango de direcciones relativas. Un problema potencial
encontrado en este proceso, es que tal función no puede ser uno a uno.
El TDA Tabla de Dispersión es un tipo de datos homogéneo, denominadas celdas, de
tamaño fijo, Tabla, compuesto por un número fijo de componentes a las que se accede
mediante una dirección de memoria resultante de una función de transformación. Sobre
este TDA se definen los operadores Insertar, Buscar y Eliminar.
Se dice que dos llaves distintas X1 y X2 son sinónimas para una función de
transformación H(X) si H(X1) = H(X2). A dos llaves diferentes que les corresponda la
misma dirección relativa se les llama sinónimos.
Se dice que se ha producido desbordamiento cuando una nueva llave se aplica a una
dirección de memoria completamente ocupada, y se dice que se ha producido una
colisión cuando dos llaves distintas se aplican sobre la misma celda.

A las técnicas de cálculo de direcciones también se les conoce como :


• Técnicas de almacenamiento disperso
• Técnicas aleatorias
• Métodos de transformación de llave - a- dirección
• Técnicas de direccionamiento directo
• Métodos de tabla Hash
• Métodos de Hashing
Pero el término mas usado es el de hashing. Al cálculo que se realiza para obtener una
dirección a partir de una llave se le conoce como función hash.
Lo ideal sería disponer de tantas clases H como datos N, y una función "hash" que
justamente coloca a cada dato en una clase distinta. Se dice que tenemos un "hash
perfecto". Pero vamos adelantando que esto sólo es posible cuando todos los datos son
conocidos de antemano. Si no, tanto N como las claves concretas que nos encontremos
serán impredecibles.
a) Si tenemos más clases que datos (H > N), lo ideal es que no caiga más de un
dato por clase. Si al preparar el programa no sabemos qué datos concretos vamos a
procesar, este ideal es imposible. A todos los efectos prácticos, hay que estar preparado
para que se produzcan "colisiones", es decir que dos o más datos caigan en la misma
clase.
b) Si tenemos más datos que clases (N > H), lo único que podemos pretender es
que los datos se distribuyan uniformemente, a razón de N/H datos por clase. Las
colisiones son inevitables.
Todos estos comentarios imponen unos requisitos difíciles de precisar sobre la función
"hash". Para hacerlo aún más difícil, nótese que esta función está en el camino crítico de
cualquier operación de inserción, búsqueda o eliminación, pues en cualquiera de ellas lo
primero que hay que hacer es identificar a qué clase pertenece el dato. En consecuencia,
"hash" debe ser una función de rápida evaluación.
Si H > N, y "hash" se comporta bien, habremos reducido el problema de tamaño N a un
problema de O(1) (0 o 1 dato por clase).
Si N > H, y "hash" se comporta bien, habremos reducido el problema de tamaño N a un
problema de tamaño N/H (en promedio).
Todos estos requisitos hacen de la elección de "hash" un verdadero arte. Además, será
casi imprescindible analizar a posteriori (sobre datos reales) si la elección fue buena o
no.
El uso de una técnica de "hashing" exige tomar dos decisiones: elegir una función
"hash" y elegir un método para afrontar las colisiones. Vamos a describir en primer
lugar los dos métodos más habituales para enfrentarse a las colisiones. Más adelante
hablaremos sobre la función "hash".

Función Hash
La función hash debe ser fácil de calcular, es decir, la dirección de memoria debe ser
calculada con sencillez y rapidez a partir de la llave, pero además es importante que se
produzcan el menor número posible de colisiones.
Si la clave es un entero, puede utilizarse algo tan simple como
F(X) = hash (X) = X MOD H
Con esta función se divide la llave en uso X por M, y el resto será la dirección.
Esta función hash funciona bien si el rango de X es muy amplio comparado con H. A la
hora de elegir H, suele ser buena idea optar por un número primo.
A veces se utilizan técnicas de truncado (sólo se usan unos cuantos dígitos), o se eleva
el número al cuadrado y se eligen algunos dígitos del resultado (truncado, de nuevo), o
se pliega el número sobre sí mismo (y se suman los dígitos que coinciden), o ... la
imaginación tiene pocos límites.
Las funciones hash más comunes son:
• Residuo de la división
• Medio del cuadrado
• Pliegue
HASHING POR RESIDUO DE LA DIVISIÓN
Es la función de transformación más conocida es el resto de la división entera (mod)
F(x) = X mod M (tamaño d e tabla)
Con esta función se divide la llave en uso X por M y el resto será la dirección. Por lo
tanto el espacio de direcciones de los bloques será [0, M-1]
La idea de este método es la de dividir el valor de la llave entre un numero apropiado, y
después utilizar el residuo de la división como dirección relativa para el registro
(dirección = llave módulo divisor).
Ejemplo
Function H (x: array[1..10]) : 0 ... N-1
Var
i, suma: integer,
begin
suma := 0 ;
for i:= 1 to 10 do
suma := suma + ord ( x[i] );
h := suma MOD N
end,
HASHING POR MEDIO DEL CUADRADO
En esta técnica, la llave es elevada al cuadrado, después algunos dígitos específicos se
extraen de la mitad del resultado para constituir la dirección relativa. Si se desea una
dirección de n dígitos, entonces los dígitos se truncan en ambos extremos de la llave
elevada al cuadrado, tomando n dígitos intermedios. Las mismas posiciones de n dígitos
deben extraerse para cada llave.

HASHING POR PLIEGUE


En esta técnica el valor de la llave es particionada en varias partes. Estas particiones son
después plegadas una sobre otra y sumadas. El resultado, es la dirección relativa.
Ejemplo:
68 75 82 71 63 93 102 74
68+....+69 = 628
628 sería la dirección a colocar el dato.
En el caso de que la llave sea una cadena de caracteres, los dígitos de cada carácter
vendrán determinados como es habitual por su valor decimal (código ASCII).

TIPOS DE HASHING
Se considera dos formas de dispersión:
La primera llamada dispersión abierta (o externa), que permite que se almacene en un
espacio potencialmente ilimitado.
La segunda llamada dispersión cerrada (o interna), usa un espacio fijo para el
almacenamiento, por lo que limita el tamaño de los conjuntos.
Es decir, ante la aparición de colisiones hay dos soluciones fundamentales:
solución abierta: Se organizan de alguna forma los datos del mismo subproblema, de
forma que "hash" me lleva simplemente al subproblema correspondiente a una clave K,
y ahí se aplican de nuevo técnicas de gestión de tablas.
solución cerrada: Se retoca el valor de "hash (k)" hasta que no existe ninguna colisión.
De alguna forma, si la "clase natural" ya está ocupada se le busca al dato una clase
alternativa que esté libre. Esto sólo vale si H > N.
Hashing Abierto
El conjunto de datos que colisionan en una misma clase se organiza de alguna forma:
tabla, lista encadenada, árbol binario, etc.
Lo más habitual es la práctica es utilizar listas encadenadas. La razón es que estas listas
no ocupan nada si no hay datos en una clase, y van adaptándose dinámicamente a un
número variable de datos. Su complejidad es lineal; pero si logramos que trabajen sobre
listas breves, esto tiene poco impacto.
A efectos de código, tenemos
VAR TT: ARRAY [0..H-1] OF Pnodo;

La estructura de datos básica para esta dispersión es:

0
1

N-1
La idea fundamental es que el conjunto de miembros potenciales se divide en un
número finito de clases ; si se desea tener N clases, numeradas de 0 a N-1, se usa una
función de hashing H tal que para cada objeto X del tipo de datos de los miembros del
conjunto que se va a representar, h(X) sea uno de los enteros de 0 a N-1. El valor de
H(x) es la clase a la cual X pertenece.
A X se le llama la clave y a H(X) la función hashing o de dispersión.
En un arreglo llamado tabla de clases, indicado por los números de clases de 0 a N-1, se
tienen los encabezamientos de N listas.
Los elementos de la i-esima lista son los miembros del conjunto que se está
representando y que pertenecen a la clase i, esto es aquellos elementos X del conjunto
tales que H(x) = i.
Se espera que cada clases tenga el mismo tamaño. Entonces si hay M elementos en el
conjunto, en promedio cada clases tendrá M / N miembros.
También es posible agrupar por medio de árboles de desbordamiento en lugar de listas,
esto es, de organizar las llaves que tienen colisiones como estructuras de árboles. Cada
entrada de la tabla de hashing es la raíz de un árbol.

Hashing Cerrado
Una tabla de dispersión (hashing) cerrada guarda los miembros en la tabla de clases, en
vez de usar esa tabla para almacenar los encabezamientos de listas.
En consecuencia, parece que sólo es posible colocar un elemento en una clase; sin
embargo la dispersión cerrada tiene asociada una estrategia de redispersión.
Si se intenta colocar X en una clase H(x) y esta ya tiene un elemento (colisión) la
estrategia de redispersión elige una sucesión de localidades alternas H1(X), H2(X),...
dentro de la tabla de clases, en la cual es posible colocar X. Se prueba en todas las
localidades en orden, hasta encontrar una vacía.
H = H(x);
I := 0;
Repeat
If T[h].key := k then
elemento encontrado
Else IF T[h].key := vacío then
elemento no está en la tabla
else
*/ colisión
i := i+1
h := H(k) + G(i);
end
Until encontrado o no esta en la tabla o tabla llena

Si ante una clave


h0= hash (k)
nos encontramos que ya hay otro dato en la tabla, iremos probando alternativas
h1, h2, h3, ...
hasta que no colisione con nadie más.
El procedimiento de ir probando debe ser determinista para poder reproducirlo al
insertar, al buscar y al eliminar.
Una técnica fácil es recorrer linealmente la tabla:
hi= (h0 + i) MOD H
(si una entrada no vale, se prueba la siguiente; si llegamos al final, volvemos a empezar
por el principio). Pero la experiencia dice que este tratamiento no funciona bien en la
práctica: los datos tienden a agruparse, haciendo de la búsqueda de un hueco un penoso
deambular.
Por ello hay que buscar otros métodos.
El recorrido cuadrático es efectivo, y se utiliza habitualmente:
hi= (h0 + i*i) MOD H

Un algoritmo de búsqueda íntegro tiene esta FORM:


FUNCTION Busca (K: clave): Elemento;
BEGIN
h:= hash (K);
d:= 1;
WHILE (TRUE) DO
BEGIN
IF TT[h] = VACIA THEN "No está";
IF TT[h].k = K THEN
BEGIN
Busca:= TT[h];
EXIT;
END;
h:= (h+d) MOD H;
d:= d+2;
IF d > H THEN "No está"; (* no cabría *)
END;
END {Busca};

Al principio todas las posiciones están VACIAS, y se van llenando poco a poco. Si
eliminamos datos, no podemos simplemente vaciar la posición que ocupaban, pues el
método de búsqueda por saltitos podría dar resultados diferentes según se encuentre las
posiciones intermedias ocupadas o no. Es un poquito lioso de programar; pero nada
más.

MANEJO DE DESBORDAMIENTO O SOBRECARGA en Hashing Cerrado


Se dice que se ha producido desbordamiento cuando una nueva llave se aplica a una
dirección de memoria completamente ocupada, y se dice que se ha producido una
colisión cuando dos llaves distintas se aplican sobre la misma celda (se mapean en el
mismo índice). Por ende, un segundo sondeo es necesario, basado en un índice obtenido
en forma deterministica a partir de la llave dada.
Se cuentan con varios métodos para generar índices secundarios.
Prueba lineal (exploración lineal)
El método de manejo de sobrecargas más evidente es la exploración lineal, que consiste
en buscar en el bloque siguiente.
Este método es el más simple para resolver una colisión, y consiste en comenzar con la
dirección de cálculo de dirección (la localización donde la colisión ocurre) y hacer una
búsqueda secuencial por la llave buscada (deseada) o una localidad vacía.
La tabla debería considerarse circular, de tal modo que cuando la última localización se
alcance, la búsqueda prosiga con la primera localización de la tabla.
Se distinguen dos acciones: inserción y búsqueda.
Para la inserción se busca en el bloque siguiente una celda libre, si vuelve a producirse
sobrecarga se busca en el siguiente y así sucesivamente.
Para la búsqueda de una llave se busca en la dirección siguiente y así sucesivamente
hasta encontrar la llave, o encontrar que la celda del bloque está vacía o la tabla está
llena. En estos dos últimos casos la llave no se encuentra en la tabla.
En general la exploración puede expresarse como:
Dirección = ( F(x) + G(x)) mod TamañoTablaHash
Donde F(X) es la función de dispersión y G(X) es la función de tratamiento de
sobrecargas.
G(X) = i , i = 1...tamañoTabla
Este método (exploración lineal) tiene la desventaja de que las entradas tienden a
agruparse alrededor de las llaves primarias (o sea las que no han sufrido colisión
después de la inserción). Así las búsquedas secuenciales necesarias para encontrar una
posición vacía se tornan cada vez más largas.
Prueba cuadrática
Si hay una colisión en la dirección de cálculo de dirección h, este método prueba la
tabla en las localidades : h +1, h+4, h+9,... esto, es en la localidades h+i2 (mod
tamaño:calc) para i=1,2....
G(X) = i2 , i = 1...tamañoTabla
Esta función G es llamada cuadrática, reduce la agrupación pero una desventaja es que
no se realiza la búsqueda en toda la tabla, esto es, después de la inserción quizás no
encontraremos una ranura libre aunque todavía queden algunos.
En este tipo la función que se puede usar es: (tamaño_calc +1) div 2
Tanto la exploración lineal como la cuadrática requiere comparaciones sucesivas de la
llave con los elementos de la tabla hash.
Ejemplo:
Supóngase que N = 8, y que las claves a,b,c y d, tienen valores de dispersión h(a) = 3,
h(b) =0, H(c) = 4, H(d) = 3.
Se usará la estrategia de dispersión lineal, en la que Hi(X) = (H(X) + i) mod B.
Así cuando intentemos insertar "d", estará ocupada su posición, y luego se probará la
posición 4, H1(d) = 4, que también estará ocupada (por C), y deberá probarse hasta
localizarla, H2(d) = 5, que encuentra un espacio vacío, en este caso en la posición 5, y
"d" se coloca allí.
Ahora suponga que se desea probar si "e" está o no en este conjunto.
SI H(e) = 4... se prueba en las clases 4,5 y luego 6. La clase 6 esta vacía y como "e" no
se ha encontrado, la conclusión es que no está en el conjunto.
Incrementos dependientes de la llave
Otro método, más que hacer que el incremento dependa del número de pruebas ya
hechas , podemos permitirle ser alguna función simple de la llave misma.
Ejemplo:
Se puede truncar la llave hasta un solo carácter y utilizar su código como el incremento.
Incremento := ord(K[1])
La desventaja primaria de la técnica de tabla de transformación de llaves (hashing)
consiste en que el tamaño de la tabla ha de fijarse en un momento en que se ignoran los
números reales de entrada.
Por ello es posible idear un sistema con un mecanismo de asignación dinámica de
memoria, el cual permite obtener almacenamiento. Cuando la tabla hashing esta llena,
se genera una tabla más grande H', y todas las llaves contenidas en H son transferidas a
H'. Esto se llama retransformación de llaves (Rehashing).
EJEMPLO (algoritmo en Pascal):
INSERTAR un registro R, con una llave R.llave, en la tabla de hashing H.
Primero creamos una función de calculo de dirección (hashing) para transformar una
llave compuesta de 8 caracteres alfanuméricos en un entero en el intervalo: 0 ..
tamaño_calc-1
La tabla de hashing debe inicializarse al definir una llave especial denominada
"palabra_en_blanco", que conste de 8 blancos, y poner el campo de llave de cada
elemento de H en "palabra_en_blanco" ; para el desbordamiento usaremos la prueba
cuadrática.
Type
tipo_de_llave = array [1..8] of char;
const
tamaño_calc = 997 */ numero primo de tamaño apropiado */
calc_direccion_max = 996 / tamaño_calc - 1/
var
H: array [ 0 .. calc_direccion_max] of elemento */ tabla de hashing */

función Calculo_direccion ( x : tipo_de_llave) : integer;


var
i : 1..8;
h: integer:
begin
h:= 0;
for i:= 1 to 8 do
h:= h + ord ( x [ i ] );
calculo_direccion := h mod tamaño_calc
end;

procedure Insertar (r : elemento);

var
c: integer; / contador para asegurar que la tabla no este llena /
p: integer; / posición actualmente probada en H /

begin
p := Calculo_direccion ( r.llave);
c:=0

while ( H [ p ].llave < > palabra_en_blanco) / localidad vacía /


and ( H [ p ].llave < > r.llave) / llave
encontrada /
and ( c < (tamaño_calc +1) div 2) ho / desbordamiento /
begin
c := c + 1;
p := p + c;
if p > calc_direccion_max then
p := p mod tamaño_calc
end;

if H [ p ].llave = palabra_en_blanco then


H [ p ] := r / se insert a el nuevo
elemento /
else
If H [ p ].llave = r.llave then
ERROR / llave repetida /
Else
Desbordamiento / contador ha alcanzado su limite /
End;

ELIMINACIONES
Por ello cuando se deban hacer supresiones (eliminar), el enfoque más efectivo para
resolver este problema es colocar una constante "suprimido", cosa de que se siga
buscando un elemento al encontrar esta constante. Es importante que haya una
diferencia entre "suprimido" y "vacío" (donde no se ha colocado nada), dado que una
localización vacía se utiliza como señal para detener la búsqueda de una llave objetivo.
Para entender esto supongase que, antes de la eliminación, han tenido lugar una
colisión o varias y que algún elemento, cuya posicion de dirección de hashing (calculo
de dirección) es la posicion ahora eliminada, en realidad esta almacenado en otro lugar
de la tabla. Si intentamos recuperar ese elemento, la posicion ahora vacía detendrá la
búsqueda y será imposible encontrar el elemento aun cuando esté todavía en la tabla.
El método con que se supera esta dificultad es inventar otra llave espacial, para situarse
en cualquier posición eliminada. Esta llave especial indicaría que esta posición se
encuentra libre para recibir una inserción cuando se desee.
MANEJO DE DESBORDAMIENTO O SOBRECARGA en Hashing Abierto
MÉTODOS PARA RESOLVER EL PROBLEMA DE LAS COLISIONES
Considere las llaves K1 y K2 que son sinónimas para la función hash R. Si K1 es
almacenada primero en el archivo y su dirección es R(K1), entonces se dice que K1 esta
almacenado en su dirección de origen.
Existen dos métodos básicos para determinar donde debe ser alojado K2 :
Direccionamiento abierto.- Se encuentra entre dirección de origen para K2 dentro del
archivo.
Separación de desborde (Área de desborde).- Se encuentra una dirección para K2 fuera
del área principal del archivo, en un área especial de desborde, que es utilizada
exclusivamente para almacenar registro que no pueden ser asignados en su dirección de
origen
Los métodos mas conocidos para resolver colisiones son:
Sondeo lineal
Que es una técnica de direccionamiento abierto. Este es un proceso de búsqueda
secuencial desde la dirección de origen para encontrar la siguiente localidad vacía. Esta
técnica es también conocida como método de desbordamiento consecutivo.
Para almacenar un registro por hashing con sondeo lineal, la dirección no debe caer
fuera del límite del archivo, En lugar de terminar cuando el límite del espacio de
dirección se alcanza, se regresa al inicio del espacio y sondeamos desde ahí. Por lo que
debe ser posible detectar si la dirección base ha sido encontrada de nuevo, lo cual indica
que el archivo esta lleno y no hay espacio para la llave.
Para la búsqueda de un registro por hashing con sondeo lineal, los valores de llave de
los registros encontrados en la dirección de origen, y en las direcciones alcanzadas con
el sondeo lineal, deberá compararse con el valor de la llave buscada, para determinar si
el registro objetivo ha sido localizado o no.
El sondeo lineal puede usarse para cualquier técnica de hashing. Si se emplea sondeo
lineal para almacenar registros, también deberá emplearse para recuperarlos.
Doble hashing
En esta técnica se aplica una segunda función hash para combinar la llave original con
el resultado del primer hash. El resultado del segundo hash puede situarse dentro del
mismo archivo o en un archivo de sobre flujo independiente; de cualquier modo, será
necesario algún método de solución si ocurren colisiones durante el segundo hash.
La ventaja del método de separación de desborde es que reduce la situación de una
doble colisión, la cual puede ocurrir con el método de direccionamiento abierto, en el
cual un registro que no esta almacenado en su dirección de origen desplazara a otro
registro, el que después buscará su dirección de origen. Esto puede evitarse con
direccionamiento abierto, simplemente moviendo el registro extraño a otra localidad y
almacenando al nuevo registro en la dirección de origen ahora vacía.
Puede ser aplicado como cualquier direccionamiento abierto o técnica de separación de
desborde.
hda(x,i)=( h(x)+i*h'(x) ) mod m
h(x)=x mod m
h'(x)=1 + (x mod m') siendo m'<m

Abstracción y tipos Abstractos de datos


La ciencia informática es la ciencia de la abstracción, este término ya fue definido en los
primeros capítulos de la presente publicación.
La abstracción procedimental esta soportada por todos los lenguajes de programación.
El uso del paradigma orientado a objetos requiere que los desarrolladores traten con
objetos en todas las fases del desarrollo, el objeto puede representarse, en un primer
nivel por el tipo abstracto de dato.
Un tipo de dato se define por un conjunto de valores y las operaciones validad aplicadas
a esos valores. La definición de un TAD no especifica como se implementan el tipo de
dato. Estos detalles están ocultos en el uso del TAD. Cada operación se define por su
entrada y por su salida, los detalles están ocultos, este proceso de ocultamiento se
conoce como encapsulacion.
En un TAD el usuario no esta preocupado en como se hace sino sol importa que es lo
que hace.
El tipo abstracto de dato requiere de:
• La declaración del dato
• declaración de las operaciones
• Encapsulamiento de datos y operaciones.

Recordando definiciones ya desarrolladas y determinando que los TADs tienen una


instancia de especificación y otra de implementación, definimos
Especificación: Proceso de analizar los problemas del mundo real y determinar en
forma clara y concreta el objetivo que se desea alcanzar. El modelo debe ser
interpretado sin errores por un autómata.
Esta especificación debe hacerse en forma semi formal mediante Lenguajes Formales,
Gramáticas Expresiones, Autómatas, teniendo en cuenta la Precondición (Información
conocida como verdadera antes de iniciar una operación) y la poscondición
(Información que debiera ser verdadera al cumplir la operación, si se cumple
adecuadamente el requerimiento pedido)Como un contrato entre usuario y aplicación,
esta debe garantizar la poscondición si aquel garantiza la precondición.

Abstracción Proceso de análisis con el propósito de interpretar los aspectos esenciales


de un problema y expresarlo en términos precisos.

Implementación: Traducir a un conjunto de instrucciones permitidas y definidas por


sus reglas sintácticas y valor semántico lo que fue especificado

Respecto de las operaciones se pueden clasificar en operaciones de


• Creación.
• Mutación,
• Proyección,
• Consulta,
• Persistencia ,
• Evolución

La parte de especificación, sintaxis y semántica describe Que hace el TAD,


independientemente de cómo lo hace. La especificación consta de dos partes Sintaxis y
Semántica.
Sintaxis de un TAD: Especifica los formatos de las operaciones, este formato consta del
nombre de la operación, número y tipo de operandos y el valor devuelto por la
operación.
La semántica de un TAD especifica para cada operación cual es la salida que debe
producirse para cada posible entrada. También cualquier valor de entrada para los que
una operación que no este definida deba ser especificada.
La implementación de un TAD consta de una representación y de los algoritmos
asociados. Una representación especifica como los valores se almacenan en memoria y
los algoritmos como las operaciones se implementan tomando como base las
representaciones.
Un TAD puede tener varias implementaciones, las implementaciones apropiadas para
ser utilizadas en un contexto determinado están dadas por las prestaciones que se
requieran en cada caso.
En los TADs los datos se introducen, acceden, modifican o suprimen mediante las
interfaces de las operaciones. Solo el nombre de la operación y sus parámetros son
visibles al usuario.

Breve reseña de la abstracción


La abstracción es un concepto clave para el correcto desarrollo de software.
En los inicios de la programación los programadores enviaban instrucciones binarias a
las computadoras manipulando directamente interrupciones. Los nemotécnicos del
lenguaje ensamblador eran abstracciones con el propósito de evitar que el programador
tuviera que recordar las secuencias de bits que componían las instrucciones del
programa. El próximo nivel de abstracción se obtuvo agrupando instrucciones
primitivas para formar macroinstrucciones.
Los lenguajes de alto nivel permitieron a los programadores tomar distancia de la
arquitectura de la computadora lo que supuso un mayor nivel de abstracción. Esto
permitió escribir software de propósito general sin preocuparse en que maquina en
particular correría la aplicación.
Posteriormente secuencias de instrucciones de los lenguajes de alto nivel pudieron
agruparse en procedimientos que pueden ser invocados con una sola sentencia (el
nombre de ese procedimiento). La programación estructurada alienta el uso de
sentencias de control o repetición que fueron incorporadas a los lenguajes de alto nivel.
El método mas adecuado para poder controlar la complejidad es y será la abstracción.
Abstraes supone la capacidad de encapsular y aislar la información de diseño y de
ejecución.
La progresión de la abstracción va desde las estructuras de control, los procedimientos,
los módulos, los tipos abstracto de datos y los objetos.
Procedimientos y funciones son uno de los primeros mecanismos de abstracción,
proporcionaron la primera posibilidad de reutilizar código. No eran un mecanismo
adecuado para el ocultamiento de la información.
El modulo es una técnica que permitió dividir los datos y procedimientos en una parte
privada, solo accesible dentro del modulo, y una parte publica, accesibles desde afuera
del modulo. Los datos y procedimientos pueden definirse en cualquier lado. El criterio a
seguir es que si no se necesita algún tipo de información, no permitir el acceso a ella.
Esto colaboro mucho en el aspecto del ocultamiento de la información. Los módulos no
permiten instanciacion, es decir hacer múltiples copias en la zona de datos, a esto viene
a colaborar los TADs.
Un tipo abstracto de dato es un tipo de dato definido por el programador que se puede
manipular de modo similar a los datos definidos por el sistema.
Para poder definir un TAD se debe poder
• Exponer una definición del tipo
• Disponer de las operaciones aplicables a las instancias de ese tipo
• Proteger los datos asociados con el tipo de modo de solo poder manipularlo con
las operaciones definidas.
• Poder hacer instancias múltiples del tipo (poder crear).

U tipo abstracto de dato se puede definir mediante:


TAD: Representación(Datos) + Operaciones (procedimientos y funciones)

Ventajas de los TADs


• Permiten una mejor conceptualizacion y modelizacion
• Mejoran la robustez del sistema
• Mejoran el rendimiento.
• Separan la especificación de la implementación
• Permite extensibilidad del sistema
• Recoge mejor la semántica del tipo

Al diseñarlos se debe tener en cuenta:


Un TAD proporciona una manera conveniente de encapsular la información, la cual
puede entonces ser utilizada sin preocuparse de su implementación. Se debe abstraer del
como; saber qué es lo que se hace debe ser suficiente.
Ocultamiento de Información: No exponer detalles de implementación. No debe ser
conocido por quien invoca la función "como" es que la operación la tarea.
Alta Cohesión: las acciones se deben especializar en una única tarea.
Bajo Acoplamiento: Diseñe con independencia del resto del sistema

Especificaciones de los TADs


La especificación consta de dos partes:
• La descripción formal del conjunto de datos
• Las operaciones definidas a ciertos elementos de ese conjunto de datos

Especificación informal
Se describen los datos y las operaciones en un lenguaje informal y esta descripción
consta de dos partes
• Detallar en los datos del tipo los valores que pueden tomar
• Describir las operaciones relacionadas con los valores

TAD Nombre del tipo (valores y su descripción)


Operación(argumentos). Descripción funcional.
Describe los datos del tipo y las operaciones según la funcionalidad. No se siguen
normas rígidas, solo se describe, en forma comprensible, lo que hace cada operación

Especificación formal
La especificación formal proporciona un conjunto de axiomas que describen el
comportamiento de las operaciones. La descripción debe incluir_
• Una parte de sintaxis: en cuanto a los tipos d argumentos y los tipos de
resultados.
Operación(Tipo de argumentos) -> Tipo Resultado
Retirar (Conjunto, Elemento) -> Conjunto
• Una parte de semántica: detallando para determinados valores particulares de
argumentos la expresión del resultado que se obtiene.
Pertenece (Conjunto, e1)-> Falso

Debe cumplir con el objetivo de poder verificar la implementación correcta del TAD.
CAPITULO 7 ALGORITMOS

Objetivos de aprendizaje
Dominando los temas del presente capitulo Usted podrá.
1. Evaluar algoritmos puntuales aplicables a distintas estructuras de datos.
2. Conocer las precondiciones para la utilización de algoritmos puntuales

Introducción
Se describen a continuación un conjunto de algoritmos puntuales para el manejo de
estructuras estáticas y dinámicas. Con una descripción breve de las precondiciones y
poscondiciones, utilizando definiciones genéricas para todos los casos

Definiciones de tipos
Definiciones de los tipos de datos utilizados en los algoritmos que se describen
MAX_FIL = 100;
Tinfo = TIPO Entero;
TinfoC = TIPO <C1 : Td1, C2 : Td2>
TVector = TIPO Tabla [1,MAX_FIL] de Tinfo;
TPNodo = TIPO Apuntador a Nodo;
Nodo = TIPO <Info : Tinfo; sgte : TPNodo>
Arbol = TIPO <Info : Tinfo; Izq, Der : TPArbol>

Acciones y funciones para vectores

BusqSecEnVector(Dato V: Tvector; Dato N: Entero; Dato Clave:Tinfo;


Dato_resultado Posic: Entero): una accion
Usar este algoritmo si alguna de las otras búsquedas en vectores mas eficientes no son
posibles, recordando que búsqueda directa tiene eficiencia 1, búsqueda binaria es
logarítmica y búsqueda secuencial es de orden N
PRE: V: Vector en el que se debe buscar
Clave : Valor Buscado
N: Tamaño lógico del vector
POS: Posic: Posición donde se encuentra la clave, 0 (Cero) si no esta.
LEXICO
j : Entero; Controla No superar el tamaño fisico del vector j<= MAX_FIL
No leer mas alla del ultimo elemento logico cargado j <= N
ALGORITMO Salir si es que encuentra la clave buscada V[j] <> clave
Posic = 0;
j = 1; //Pone el indice en la primera posición para recorrer el vector//
MIENTRAS (j <= MAX_FIL y j <= N y V[j] <> Clave) HACER
Inc (j) //Incrementa el indice para avanzar en la estructura//
FIN_MIENTRAS;
SI (j > N)
ENTONCES
Posic =0 // No encontró la clave buscada
SI_NO
Posic = j // Encontró la clave en la posición de índice j
FIN_SI;

FIN. // Búsqueda secuencial En Vector

BusqMaxEnVector(Dato V: Tvector; Dato N: Entero; Dato_resultado Maximo :Tinfo;


Dato_resultado Posic: Entero): una acccion
PRE: V: Vector en el que se debe buscar (sin orden)
N : Tamaño lógico del vector
POS: Posic: Posición donde se encuentra el máximo
Maximo : Valor máximo del vector.
LEXICO
j : Entero; Supone que el maximo es el primer valor del vector por lo que le asigna ese
ALGORITMO valor a maximo y la posición 1 a la posición del maximo. Al haber leido solo un
elemento supone ese como maximo
Posic = 1;
Maximo = V[1];
PARA j [2, N] HACER Recorre ahora las restantes posiciones del vector, a partir de la
SI (v[j] > Maximo) segunda y lcada vez que el valor leido supera al maximo
contiene ese valor como maximo y el indice actual como posición
ENTONCES del maximo
Posic = j;
Maximo = v[j];
FIN_SI;
FIN_PARA;

FIN. // Búsqueda máximo En Vector

BusqMinDistCeroEnVector(Dato V: Tvector; Dato N: Entero; Dato_resultado


Minimo :Tinfo; Dato_resultado Posic: Entero): una acccion
PRE: V: Vector en el que se debe buscar (sin orden)
N : Tamaño lógico del vector, existe al menos un valor <> de cero
POS: Posic: Posición donde se encuentra el minimo distinto de cero
Minimo : Valor minimo distinto de cero del vector.
LEXICO
i,j : Entero; Recorre el vector hasta encontrar el primero distinto de cero.
ALGORITMO Al encontrarlo supone ese valor como minimo y el valor del
indice como posición del minimo
//
J = 1;
Mientras (J<=N) Y (V[j] = 0) Hacer
Incrementar[j];
Posic = J;
Minimo = V[j];
PARA j [Posic.+1 , N] HACER Recorre el vector desde la posición inmediata
siguiente hasta la ultima desplazando el minimo
SI (v[j]<> 0 Y v[j] < Minimo) solo si el valor es distinto de cero y, ademas,
ENTONCES menor que el minimo
Posic = j;
Minimo = v[j];
FIN_SI;
FIN_PARA;

FIN. // Búsqueda minimo distinto de cero En Vector


BusqMaxySiguienteEnVector(Dato V: Tvector; Dato N: Entero; Dato_resultado
Maximo :Tinfo; Dato_resultado Posic: Entero, Dato_resultado Segundo :Tinfo;
Dato_resultado PosicSegundo: Entero): una accion
PRE: V: Vector en el que se debe buscar
N : Tamaño lógico del vector mayor o gual a 2
POS: Posic: Posición donde se encuentra el máximo, PosicSegundo: Posición donde se
encuentra el siguiente al máximo
Maximo : Valor máximo del vector. Segundo : Valor del siguiente al máximo del vector
LEXICO
j : Entero;
ALGORITMO
SI V[1] > V[2]
ENTONCES
Posic = 1;
Maximo = V[1]; Se tiene como precondicion que al menos hay dos
PosicSegund = 2; valores. Se verifica el valor que esta en la primera
posición y se lo compara con el que esta en la
Segundo = V[2]; segunda posición, en el caso de ser mayor, el
SINO maximo es ese valor, posición del máximo es uno,
Posic = 2; el segundo el valor que esta en segundo lugar y
posición del segundo es 2. En caso contrario se
Maximo = V[2]; establece como maximo el valor de la segunda
PosicSegund =1; posición y segundo el de la primera.
Segundo = V[1];

FIN_SI

PARA j [3, N] HACER Se verifica luego desde la tercera


SI (v[j] > Maximo) posición hasta el final. En el caso
que el nuevo valor sea mayor que
ENTONCES el maximo, se debe contener el
Segundo = Maximo; anterior maximo en el segundo y al
PosicSegundo = Posic; maximo se le asigna el nuevo
valor. Cosa similar hay que hacer
Posic = j; con las posiciones. Si esto no
Maximo = v[j]; ocurriera se debe verificar si el
SINO nuevo valor supera al segundo, en
ese caso debera desplazarlo
SI Maximo>Segundo
ENTONCES
Segundo = V[j];
PosicSegundo = j
FIN_SI
FIN_SI;
FIN_PARA;

FIN. // Búsqueda máximo En Vector

CargaSinRepetirEnVectorV1(Dato_Resultado V: Tvector; Dato_Resultado N: Entero;


Dato Clave:Tinfo; Dato_resultado Posic: Entero; Dato_resultado Enc : Booleano): una
accion
Utilizar este algoritmo si la cantidad de claves diferentes es fija, se dispone de memoria
suficiente como para almacenar el vector y la clave no es posicional(es decir clave e
índice no se corresponden directamente con posición única y predecible.Si la posición
fuera unica y predecible la busqueda debe ser directa
PRE: V: Vector en el que se debe buscar
Clave : Valor Buscado
N : Tamaño lógico del vector
POS: Posic: Posición donde se encuentra la clave, o donde lo inserta si no esta.
Retorna
0 (cero) en caso que el vector esta completo y no lo encuentra
Enc : Retorna True si estaba y False si lo inserto con esta invocación
Carga vector sin orden
LEXICO
j : Entero; Controla No superar el tamaño fisico del vector j<= MAX_FIL
ALGORITMO/ No leer mas alla del ultimo elemento logico cargado j <= N
Posic = 0; Salir si es que encuentra la clave buscada V[j] <> clave
J = 1;
MIENTRAS (j <= MAX_FIL y j <= N y V[j] <> Clave) HACER
Inc (j)
FIN_MIENTRAS; Si debio superar el tamaño fisico maximo del vector no pudo
SI j > MAX_FIL cargarlo y retorna cero como señal de error
ENTONCES
Posic = 0 Si encontro un dato o lo debe cargar esto es en el indice j por lo
que a pos se se asigna ese valor. En el caso que j sea mayor que
SI_NO n significa que recorrio los n elemntos cargados del vector, tiene
Posic = j: estpacio por lo que debe cargar el nuevo en la posición j. En este
SI (j > N) caso, y al haber un elemento nuevo debe incrementar n que es el
identificador que controla el tamaño logico del vector
ENTONCES
Enc =FALSE; // No encontró la clave buscada
Inc(N);
V[N] = Clave;
SI_NO
Enc = True // Encontró la clave en la posición de índice j
FIN_SI;
FIN_SI
FIN. // Carga sin repetir en vector

BusquedaBinariaEnVectorV1(Dato V: Tvector; Dato N: Entero; Dato Clave:Tinfo;


Dato_resultado Posic: Entero; Dato_resultado Pri : Entero): una accion
Utilizar este algoritmo si los datos en el vector están ordenados por un campo clave y
se busca por ese campo. Debe tenerse en cuenta que si la clave es posicional se deberá
utilizar búsqueda directa ya que la diferencia en eficiencia esta dada entre 1, para la
búsqueda directa y log2N para la binaria
PRE: V: Vector en el que se debe buscar con clave sin repetir
Clave : Valor Buscado
N : Tamaño lógico del vector
POS: Posic: Posición donde se encuentra la clave, o 0 (cero) si no esta
Pri : Retorna la posición del limite inferior

LEXICO Establece valores para las posiciones de los elementos del vector, Pri
j : Entero; contiene el indice del primero, es decir el valor 1, U el indice del ultimo
elemento logicio, es decir N. Ademas se coloca en Posic. El valor cero,
u,m : Entero; utilizando este valor como bandera para salir del ciclo cuando encuentar
ALGORITMO el valor buscado
Posic = 0;
Pri = 1;
U = N;
MIENTRAS (Pri < = U y Pos = 0) HACER
M = (Pri + U ) div 2 Permanece en el ciclo mientras no encuentre lo
SI V[M] = Clave buscado, al encontrarlo le asigna a pos el indice
donde lo encontro, como es un valor > que cero
ENTONCES hace false la expresión logica y sale del ciclo. Si no
Posic = M; lo encuentra, y para evirtar ciclo infinito verifica
SI_NO que el primero no tome un valor mayor que el
SI Clave > V[M] ultimo. Si eso ocurre es que el dato buscado no esta
y se debe salir
ENTONCES
Pri = M+1
SI_NO
U=M–1 Si el dato buscado lo encuentra le asigna a posición
FIN_SI el indice para salir. Si no lo encuentra verifica si
esmayor el dato buscabo a lo que se encuentra
FIN_SI revisa en la mitad de los mayores por lo que le
FIN_MIENTRAS; asigna al primero el indice siguiente al de la mitad
dado que alli no estab y vuelve a dividir el conjunto
de datos en la mitas, de ser menor pone como tome
FIN. // Búsqueda binaria en vector ultimo el anterior al de la mitad actual

BusquedaBinariaEnVectorV2(Dato V: Tvector; Dato N: Entero; Dato Clave:Tinfo;


Dato_resultado Posic: Entero; Dato_resultado Pri : Entero): una acccion
PRE: V: Vector en el que se debe buscar clave puede estar repetida
Clave : Valor Buscado
N : Tamaño lógico del vector
POS: Posic: Posición donde se encuentra la primera ocurrencia de la clave.
0 (cero) si no esta.
Pri : Retorna la posición del limite inferior

LEXICO
j : Entero;
u,m : Entero;
ALGORITMO
Posic = 0; La busqueda es bastante parecida a lo
Pri = 1; desarrollado anteriormente, pero en pos debe
tener la primera aparicion de la clave buscada
U = N; que puede repetirse.
MIENTRAS (Pri < U ) HACER En la busqueda anterior utilizabamos esta pos
M = (Pri + U ) div 2 como bandera, para saber cuando Sali si lo
encontro. En este caso si lo utilizamos con el
SI V[M] = Clave mismo proposito saldria cuando encuentra un
ENTONCES valor oincidente con la clave que no
Posic = M; necesariamente es el primero, por lo que esa
condicion se elimina. Al encontrarlo en m se le
U = M; asigna ese valoe a pos, alli seguro esta. No
SI_NO sabemos si mas arriba vuelve a estar por lo que
SI Clave > V[M] se asigna tambien esa posición al ultimo para
seguir iterando y ver si lo vuelve a encontrar.
ENTONCES Debe modificarse el operador de relacion que
Pri = M+1 compara primero con ultimo para evitar un
SI_NO ciclo infinito, esto se hace eliminando la
relacion por igual. Insisto en el concepto de los
U=M–1 valores de retorno de la busqueda binaria. Una
FIN_SI particularidad de los datos es que si lo que se
FIN_SI busca no esta puede retornal en el primero la
posición donde esa clave deberia estar
FIN_MIENTRAS;
FIN. // Búsqueda binaria en vector

CargaSinRepetirEnVectorV2(Dato_Resultado V: Tvector; Dato_Resultado N: Entero;


Dato Clave:Tinfo; Dato_resultado Posic: Entero; Dato_resultado Enc : Booleano): una
acccion
PRE: V: Vector en el que se debe buscar ordenado por clave
Clave : Valor Buscado
N : Tamaño lógico del vector
La busqueda es bastante parecida a lo desarrollado anteriormente, pero en pos debe tener la primera
aparicion de la clave buscada que puede repetirse.
En la busqueda anterior utilizabamos esta pos como bandera, para saber cuando Sali si lo encontro.
En este caso si lo utilizamos con el mismo proposito saldria cuando encuentra un valor oincidente
con la clave que no necesariamente es el primero, por lo que esa condicion se elimina. Al encontrarlo
en m se le asigna ese valoe a pos, alli seguro esta. No sabemos si mas arriba vuelve a estar por lo que
se asigna tambien esa posición al ultimo para seguir iterando y ver si lo vuelve a encontrar. Debe
modificarse el operador de relacion que compara primero con ultimo para evitar un ciclo infinito,
esto se hace eliminando la relacion por igual. Insisto en el concepto de los valores de retorno de la
busqueda binaria. Una particularidad de los datos es que si lo que se busca no esta puede retornal en
el primero la posición donde esa clave deberiaestar
POS: Posic: Posición donde se encuentra la clave, o donde lo inserta si no esta.
Retorna
0 (cero) en caso que el vector esta completo y no lo encuentra
Enc : Retorna True si estaba y False si lo inserto con esta invocación
Carga vector Ordenado
LEXICO Al estar el vector ordenadola busqueda puede ser binaria, si lo
encuentra retorna en posición un valor mayor a cero. Si no lo
j : Entero; encuentra el valor de posición sera cero. En este caso, se conoce
ALGORITMO que en pri es en la posición donde este valor debe estar
Enc = True;
BusquedaBinariaEnVectorV(V; N; Clave; Posic; Pri)
SI (Posic = 0)
ENTONCES
Enc = False ; Se produce un desplazamiento de los valores
desde el ultimo hasta el valor de pri
Posic = Pri; corriendolos un lugar para poder insertar en
PARA j [N, Pri](-) HACER la posición pri el nuevo valos. Al pasar por
V[j+1] = V[j]; aquí se inserto un nuevo elemento por lo que
n, que contiene la cantidad de elementos del
FIN_PARA; vector debe incrementarse en uno
V[Pri] = Clave;
Inc(N);
FIN_SI
FIN. // Carga sin repetir en vector Versión 2. con vector ordenado

OrdenarVectorBurbuja(Dato_Resultado V: Tvector; Dato N: Entero): una acccion


Pre: V: Vector en el que se debe ordenar, se supone dato simple
N : Tamaño lógico del vector
POS: Vector ordenado por clave creciente
Usar este algoritmo cuando los datos contenidos en un vector deben ser ordenados. Se
podría por ejemplo cargar los datos de un archivo al vector, ordenar el vector
recorrerlo y generar la estructura ordenada. Para esto la cantidad de elementos del
archivo debe ser conocida y se debe disponer de memoria suficiente como para
almacenar los datos. Una alternativa, si la memoria no alcanza para almacenar todos
los datos podría ser guardar la clave de ordenamiento y la referencia donde encontrar
los datos, por ejemplo, la posición en el archivo.
La idea general es ir desarrollando pasos sucesivos en cada uno de los cuales ir
dejando el mayor de los elementos en el último lugar. En el primer paso se coloca el
mayor en la ultima posición, en el paso siguiente se coloca el que le sigue sobre ese
y asi hasta que queden dos elemntos. En ese caso al acomodar el segundo el otro
LEXICO queda acomodado el primer ciclo cuenta los pasos, son uno menos que la cantidad
I,J, : Entero; de elementos porque el ultimo paso permite acomodar 2 elementos, por eso el ciclo
Aux : Tinfo; se hace entre 1 y N – 1 siendo n la cantidad de elementos
ALGORITMO
PARA i [1, N - 1] HACER
PARA j [1, N - i] HACER
SI (v[j] > v[j + 1])
ENTONCES
Aux = v[j]; Para poder colocar el mayor al final es necesario
hacer comparaciones. Se compara el primero con el
V[j] = v[j + 1]; segundo y si corresponde se intercambian. Asi hasta
V[j + 1] = Aux; llegar al ante ultimo eleemento que se lo compara
FIN_SI; con el últim.
Al ir recorriendo los distintos pasos, y dado que en
FIN_PARA; cada uno se acomoda un nuevo elemento
FIN_PARA; corresponde hacer una comparación menos en cada
FIN avance, como los pasos los recorremos con i, las
comparaciones seran n – i. disminuye en 1 en cada
paso

OrdenarVectorBurbujaMejorado(Dato_Resultado V: Tvector; Dato N: Entero): una


acccion
PRE: V: Vector en el que se debe ordenar, se supone dato simple
N : Tamaño lógico del vector
POS: Vector ordenado por clave creciente

LEXICO
I,J, : Entero;
Aux : Tinfo;
Ord : Boolean;
ALGORITMO El algoritmo es similar al anterior, solo que
I = 0; el ciclo de repetición externo no lo hace si es
que tiene la certeza, en el paso anterior que
REPETIR los datos ya estan ordenados. Es por eso que
Inc(i); agrega una bandera para verificar si ya esta
Ord = TRUE; ordenado y cambia el cilo exactp por un
ciclo pos condicional. Es decir reemplaza la
PARA j [1, N - i] HACER composición para por la composición repetir
SI (v[j] > v[j + 1]) hasta
ENTONCES
Ord = False;
Aux = v[j];
V[j] = v[j + 1];
V[j + 1] = Aux;
FIN_SI;
FIN_PARA;
HASTA ( Ord o I = N – 1);
FIN

OrdenarVectorInserion(Dato_Resultado V: Tvector; Dato N: Entero): una accion


PRE: V: Vector en el que se debe ordenar, se supone dato simple
N : Tamaño lógico del vector
POS: Vector ordenado por clave creciente
Este algoritmo consta de los siguientes pasos
El primer elemento A[0] se lo considera ordenado; es decir se considera el array con
un solo elemento.
Se inserta A[1] en la posicion correcta, delante o detras de A[0] segun sea mayor o
menor.
Por cada iteracion, d i desde i=1 hasta n-1, se explora la sublista desde A[i-1] hasta
A[0],buscando la posicion correcta de la insercion ; a la vez se mueve hacia abajouna
posicion todos los elementos mayores que el elemento a insertar A[i] para dejar vacia
la posicion.
Insertar el elemento en l posicion correcta.

LEXICO
I,J, : Entero;
Aux : Tinfo;

ALGORITMO
PARA I[1..N-1]
J = I;
Aux = A[i] ;
MIENTRAS (J >0 Y AUX < A[J - 1])HACER
A[J] = A[J - 1] ;
Dec(J) ;
FIN MIENTRAS ;
A[J] = Aux ;
FIN PARA;
FIN

OrdenarVectorShell(Dato_Resultado V: Tvector; Dato N: Entero): una acccion


PRE: V: Vector en el que se debe ordenar, se supone dato simple
N : Tamaño lógico del vector
POS: Vector ordenado por clave creciente
Este algoritmo consta de los siguientes pasos
Dividir la lista original en n/2 grupos de dos, considerando un incremento o salto entre
los elementos en n/2.
Analizar cada grupo por separado comparandolas parejas de elementos, y si no estan
ordenados, se intercambian .
Se divide ahora la lista en la mitad n/4, con un incremento tambien en n/4 y nuevamente
se clasifica cada grupo por separado.
Se sigue dividiendo la lista en la mitad de grupos que en el paso anterior y se clasifica
cada grupo por separado.
El algoritmo termina cuando el tamaño del salto es 1.

ALGORITMO
Intervalo = n / 2;

MIENTRAS Intervalo > 0 HACER


PARA I [Intervalo + 1 .. N] HACER
MIENTRAS (J > 0) HACER
K = J + Intervalo;
SI (A[J] <= A[K]
ENTONCES
J = -1
SINO
Intercambio(A[J] ,A[K]) ;
J = J – Intervalo ;
FINSI ;
FIN PARA ;
FIN MIENTRAS;
FIN.

CorteDeControlEnVector(Dato V:Tvector; Dato N: Entero): una acccion


Usar este procedimiento solo si se tienen los datos agrupados por una clave común y se
requiere procesar emitiendo información por cada subconjunto correspondiente a cada
clave.
PRE: V: Vector en el que se debe Recorrer con corte de control
Debe tener un elemento que se repite y estar agrupado por el.
N : Tamaño lógico del vector
POS: Recorre agrupando por una clave

LEXICO
I : Entero;

ALGORITMO
I = 1;
Anterior = TipoInfo;
// Inicializar contadores generales
MIENTRAS (I<=N) Hacer
//inicializar contadores de cada sublote
Anterior = V[i]
MIENTRAS (I<=N Y Anterior = V[i] HACER
// Ejecutar acciones del ciclo

I = I+1 // avanza a la siguiente posición


FIN_MIENTRAS
// Mostrar resultados del sublote
FIN_MIENTRAS
// Mostrar resultados generales
FIN

ApareoDeVectores(Dato V1,v2:Tvector; Dato N1,N2: Entero): una acccion


Utilizar este procedimiento si se tiene mas de una estructura con un campo clave por el
que se los debe procesar intercalado y esas estructuras están ORDENADAS por ese
campo común.
PRE: V1,V2: Vectores a Recorrer mezclados o intercalados
Los vectores deben estar ordenados.
N1,N2 : Tamaño lógico de los vectores
POS: Muestra la totalidad de los datos con el orden de las estructuras
LEXICO
I,J : Entero;
ALGORITMO
I = 1;
J = 1;

MIENTRAS (I<=N1 o J<=N2) Hacer


SI((J > N2) o ((I<=N1) y (V1[I]<V2[J])) HACER
ENTONCES
Imprimir (V1[I]);
I = I + 1;
SINO
Imprimir(V2[J]);
J = J + 1;
FIN_SI;
FIN_MIENTRAS

FIN

CargaNMejoresEnVector(Dato_Resultado V: Tvector; Dato_Resultado N: Entero;


Dato Clave:Tinfo): una acccion
PRE: V: Vector en el que se debe buscar e insertar los mejores
Clave: Valor Buscado
N: Tamaño lógico del vector
POS: Vector con los N mejores sin orden
LEXICO
j : Entero;
Maximo: Tinfo
Posic: Entero;
ALGORITMO
SI (N < MAX-FIL)
ENTONCES
Inc (N);
V[n] = Clave
SI_NO
BusqMaxEnVector(V; N; Maximo :Tinfo; Posic: Entero);
SI (Clave > Maximo)
ENTONCES
V[Posic] = Clave;
FIN_SI;
FIN_SI;
FIN. // Carga los N mejores en vector

Acciones y funciones para archivos

Se identifican dos tipos de archivos, archivos de texto y archivos biarios. Los archivos
de texto son un conjunto de lineas las cuales tienen 0, 1 o mas caracteres que finalizan
con un carácter particular que representa el fin de la linea. Los datos son interpretados
como caracteres. <los archivos binario, en cambio, son una secuencia de bytes
lmacenados según su representación interna y sin interpretar, en este caso es necesario
que sean leidos los datos tal como fueron guardados para poder ser reconocidos como
iguales.
Operaciones elementales de acceso a archivos:

Accion Efecto
Asignar(a,s) Asigna al idntificador a la cadena s que representa ub archivo
en disco
Abrir(a) Prepara el archivo asignado a la variable a para su utilizacion
Crear(a) Crea el archivo asignado al identificador a y lo prepra para su
acceso
Cerrar(a) Cierra el arhivo apuntado por a, actualiza la marca de fin si
corresponde
LeerCaracter(a,c) Lee el siguiente carácter del flujo apuntado por a y lo almacena
en c
LeerLinea(a,s) Lee la siguente linea del flujo apuntado por a y la almacena en s
GrabarCaracter(a,c) Escribe secuencialmente en el flujo a el caracter c
GrabarCadena(a,s) Escribe en el flujo a la cadena s
LeerArchivo(a,r) Lee el siguiente tipo de dato (ejemplo registro) del flujo a y lo
almacena en r
GrabarArchivo(a,r) Graba el siguiente tipo de dato, r, en el flujo a
LeerPosicion(a,p,r) Lee del flujo a el valor contenido en l posición p y lo almacena
en r
GrabarPosicion(a,p,r) Graba en la posicion p del flujo a el valor contenido en r
NumeroElementos(a) Retorna el numero de elementos almacenados en el archivo
PosicionActual(a) Retorna la posición en la que se encuentra posicionado el
puntero actual del archivo.
ApuntarA(a,p) Accede a la posición indicada por p en el archivo a.
FinArchivo(a) Retorna verdadero si se ha alcanzado la marca de fin de archivo

Definiciones de tipos
TipoArchivoEntero = TIPO Entero;
TipoRegistro = TIPO <Clave : Entero ; Nombre : Cadena>;
TipoArchivoRegistro = TIPO Archivo de TipoRegistro;

Declaracion de variables
ArchTexto : Texto //declra una varible de tipo archivo de texto
ArchEntero : TipoArchivoEntero // declara una variable de tipo archivo entero
ArchRegistro: TipoArchivoRegistro //declara una variable de tipo archivo registro

CrearArchivoEnteros(Dato_Resultado Archivo: TipoArchivoEntero): una Accion


Crea un archivo y almacena en el un conjunto de enteros, el proceso termina cuando se
ingresa un entero menor o igual a cero.
LEXICO
I : Entero;
ALGORITMO
Asignar(Archivo,”Archivo.dat”);
Crear(Archivo);
Imprimir(‘Ingrese un valor entero’);
Leer(i);
MIENTRAS (i <> 0) HACER
GrabarArchivo(Archivo,i);
Imprimir(‘Ingrese un valor entero’);
Leer(i);
FIN_MIENTRAS
Cerrar(Archivo);
FIN.

RecorrerArchivoEnteros(Dato_Resultado Archivo: TipoArchivoEntero): una Accion


Recorre un archivo existente que contiene valores enteros y los muestra por pantalla.
LEXICO
I : Entero;
ALGORITMO
Asignar(Archivo,”Archivo.dat”);
Abrir(Archivo);
MIENTRAS (No Sea FinArchivo(Archivo)) HACER
LeerArchivo(Archivo,i);
Imprimir(‘El valor almacenado es : ’,i);
FIN_MIENTRAS
Cerrar(Archivo);
FIN.

CrearArchivodeRegistros(Dato_Resultado Archivo: TipoArchivoRegistro): una


Accion
Crea un archivo y almacena en el un conjunto deregistros, el proceso termina cuando
se ingresa un entero menor o igual a cero en el campo clave del registro.
LEXICO
La lectura de cada miembro de un registro
R : TipoRegistro; cuando el dispositivo es el teclado debe hacerse
ALGORITMO por seprado. Es decir la lectura de un registro por
Asignar(Archivo,”ArchivoR.dat”); teclado se hace camo a campo
Crear(ArchivoR);
Imprimir(‘Ingrese un valor entero’);
Leer(R.clave); La escritura de un registro en un archivo (Grabar
elarchivo)puede hacersecampo a campo o por la
MIENTRAS (R.clave <> 0) HACER etructura completa. Hay lenguajes de
Imprmir(‘Ingrese un nombre’); programación omo Pascal que solo permiten esta
Leer(R.nombre); ultima opcion
GrabarArchivo(Archivo,R);
Imprmir(‘Ingrese un valor entero’);
Leer(R.Clave);
FIN_MIENTRAS
Cerrar(Archivo);
FIN.

RecorrerArchivoRegistros(Dato_Resultado Archivo: TipoArchivoRegistro): una


Accion
Recorre un archivo exsistente que contiene registros y los muestra por pantalla.
LEXICO
La lectura de un registro cuando el dispositivo es
R : TipoRegistro; un archivo se hace por estructura completa
ALGORITMO
Asignar(Archivo,”ArchivoR.dat”);
Abrir(Archivo);
MIENTRAS (No Sea FinArchivo(ArchivoR)) HACER
LeerArchivo(Archivo,R); Imprimir los dtos de un registro
Imprimir(‘La Clave es : ’,R.clave); cuando el dispositivo es la
Imprimir(‘El Nombre es : ’,R.nombre); pantalla se hace miembro a
miembro. Es decir por pantalla
FIN_MIENTRAS los registros se imprimen campo
Cerrar(Archivo); a campo
FIN.

AgregaArchivodeRegistros(Dato_Resultado Archivo: TipoArchivoRegistro): una


Accion
Agrega datos a un archivo y almacena en el un conjunto deregistros, el proceso termina
cuando se ingresa un entero menor o igual a cero en el campo clave del registro.
LEXICO El archivo existe por lo que no hay que crearlo.
R : TipoRegistro; Para agragar valores debe colocarse el punrtero
en la ultima posición por lo que hay que
ALGORITMO desplazarlo tantos registro como el valor
Asignar(Archivo,”ArchivoR.dat”); contenidoen cantidad de elementos
Abrir(ArchivoR);
ApuntarA(Archivo,CantidadElementos(Archivo))
Imprimir(‘Ingrese un valor entero’);
Leer(R.clave);
MIENTRAS (R.clave <> 0) HACER
Imprmir(‘Ingrese un nombre’);
Leer(R.nombre);
GrabarArchivo(Archivo,R);
Imprmir(‘Ingrese un valor entero’);
Leer(i);
FIN_MIENTRAS
Cerrar(Archivo);
FIN.

AgregaArchivoAlFinalDeOtro(Dato_Resultado Anterior, Actual:


TipoArchivoRegistro): una Accion
Agrega los datos del archivo actual al final del archivo anterior.
LEXICO
R : TipoRegistro; Ambos archivos existen por lo que se deben abrir
ALGORITMO y no crear, para grabar todos los registros del
actual al anterior se debe colocar el puntero del
Asignar(Anterior,”ArchivoN.dat”); anterior al final del archivo y recorriendo el
Abrir(Anterior); actual se lee y graba en anterior
Asignar(Actual,”ArchivoA.dat”);
Abrir(Actual);
ApuntarA(Anterior,CantidadElementos,Anterior)
MIENTRAS (No FinArchivo(Actual)) HACER
LeerArchivo(Actual,R);
GrabarArchivo(Anterior,R);
FIN_MIENTRAS
Cerrar(Anterior);
Cerrar(Actual);
FIN.

Acciones y funciones para pilas

Pila estructura del tipo LIFO el ultimo en entrar es el primero en salir, en general el
uso de esta estructura es adecuado cuando se persigue el propósito de invertir el orden
de una estructura dada. Su uso es reservado para los compiladores que los utilizan
para recursividad y las notaciones sufijas, por ejemplo.

CrearPila(Dato_Resultado Pila: TPNodo): una Accion


Las estructuras deben ser creadas, esto es hacer apuntar al valor NULO, o inicializar la
estructura antes de comenzar a trabajar con ellas.
ALGORITMO
Pila = NULO; Simplemente asigna el valor NULO al puntero
que controla el inicio de la estructura
FIN

EsVacia(Dato Puntero: TPNodo) : Boolean una Funcion


Esta función determina si hay nodos en la pila, es una forma de verificar la existencia
de elementos en la estructura para no producir errores en tiempo de ejecución como
por ejemplo intentar sacar cuando ya no hay datos.

ALGORITMO Retona verdadero si el puntero a punta al valor


EsVaciaPila  Puntero = NULO; NULO y falso en caso contrario. La asignación d
una expresión logica asigna verdadero al
FIN identificador booleano si se cumple la condicion y
falso en caso contrario

Meter(Dato_Resultado Pila: TPNodo; Dato Valor: Tinfo) una accion


Este procedimiento inserta un nuevo nodo en la pila, se encarga de pedir memoria,
guardar la información y actualizar los punteros. En esta estructura siempre la pila
retorna el valor del primer nodo, y como se agrega adelante, el procedimiento siempre
retorna en pila el nodo que acaba de crear.
LEXICO
Pre Pila Apunta al Inicio o a NULO si esta vacía
Pos Pila apunta al primer elemento de la estructura (coincide con el creado
La estructura queda perfectamente enlazada.
Ptr : TPNodo;
ALGORITMO Pide memoria, guarda el valor en el nuevo nodo,
Nuevo(Ptr); hace que el siguiente de este nodo sea pila, es ex
primero (porque agrega delante del primero) y
Ptr^  <Valor, Pila>; pila apunta al nodo creado
Pila  Ptr
FIN.

Sacar(Dato_Resultado Pila: TPNodo; Dato_Resultado Valor: Tinfo) una accion


Este procedimiento libera el nodo apuntado por pila, se encarga de guardar la
información contenida en el primer nodo en valor, por lo que este es un parámetro
variable y actualizar los punteros.
LEXICO
PRE: Pila Apunta al Inicio y no esta vacía
POS: Libera el nodo de la cima y retorna en valor la informacion.
Pila apunta al nuevo primer elemento de la estructura o a NULO si queda
Vacia.
La estructura queda perfectamente enlazada.
Ptr : TPNodo;
ALGORITMO
Ptr  Pila; Utiliza un puntero auxiliar para conservar el
inicio de la pila, guarda el valor contenido en ese
Valor Ptr^ .info; puntero, avanza con la estructura y libera el
Pila  Pila^.sgte; puntero auxiliar.
Destruir(Ptr);
FIN

Acciones y funciones para Colas

CrearCola(Dato_Resultado ColaFte,ColaFin: TPNodo): una Accion


ALGORITMO
ColaFte = NULO;
ColaFin = NULO;
FIN

Agregar(Dato_Resultado Colafte, Colafin: TPNodo; Dato Valor: Tinfo) una accion


Este procedimiento inserta un nuevo nodo en la cola, se encarga de pedir memoria,
guardar la información y actualizar los punteros. En esta estructura siempre ColaFte
retorna el valor del primer nodo, y como se agrega después del último, el
procedimiento siempre retorna en colafin el nodo que acaba de crear.

LEXICO
PRE Colafte Apunta al Inicio o a NULO si esta vacía, Cola Fin al final o
NULO
POS ColaFte apunta al primer elemento de la estructura.
ColaFin apunta al ultimo elemento de la estructura (coincide con el
creado)
La estructura queda perfectamente enlazada.
Ptr : TPNodo;
ALGORITMO Se pide memoria se guarda la información y el
Nuevo(Ptr); siguiente del nuevo nodo siempre es el valor
NULO. Para enlazarlo, como vimos, hay dos
Ptr^  <Valor, NULO>; situaciones posibles cuando es el primer nodo, en
SI (Colafte = NULO) ese caso el puntero al inicio apunta al nuevo nodo
ENTONCES y cuando ya hay por lo menos un nodo, en ese
caso se debe enlazar el nuevo nodo a
Colafte  Ptr continuación del anterior ultimo. En todos los
SI_NO casos el puntero al ultimo siempre apuntara al
ColaFin^.sgte = Ptr; nodo creado.
FIN_SI;
ColaFin  Ptr
FIN.

Suprimir(Dato_Resultado Colafte, ColaFin: TPNodo; Dato_Resultado Valor: Tinfo)


una accion
Este procedimiento libera el nodo apuntado porcolafte, se encarga de guardar la
información contenida en el primer nodo en valor, por lo que este es un parámetro
variable y actualizar los punteros
LEXICO
PRE ColaFte Apunta al Inicio y no esta vacia
POS Libera el nodo de la cima y retorna en valor la informacion.
Colafte apunta al nuevo primer elemento de la estructura o a NULO si queda
Vacia, en ese caso, ColaFin tambien apuntara a nulo.
La estructura queda perfectamente enlazada.
Ptr : TPNodo;
ALGORITMO Utiliza un puntero auxiliar para conservar el
Ptr  Colafte; inicio de lacolaa, guarda el valor contenido en
ese puntero, avanza con la estructura y libera el
Valor Ptr^ .info; puntero auxiliar Si el puntero al inicio apunta a
ColaFte  ColaFte^.sgte; NULO (cuando saca el ultimo valor), entonces
SI (ColaFte = NULO) tambien se hace apuntar al puntero auxiliar al
valor NULO. La unica vez que el puntero al final
ENTONCES se modifica, cuando se saca, es en este ultimo
ColaFin = NULO caso.
FIN_SI;
Destruir(Ptr);
FIN

Acciones y funciones para Listas Ordenadas enlazadas

CrearLista(Dato_Resultado Lista: TPNodo): una Accion


ALGORITMO
Lista = NULO;
FIN

InsertaNodo(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una accion

LEXICO
PRE Lista Apunta al Inicio o a NULO si esta vacia.
POS Lista apunta al primer elemento de la estructura.
No retorna la direccion del nodo creado, salvo si es el primero
La estructura queda perfectamente enlazada y ordenada creciente.
Ptr, PtrNuevo : TPNodo;
ALGORITMO

SI (lista = NULO o Valor < Lista^.info


ENTONCES Si la lista esta vacia, o el valor a guardar es
Nuevo(Ptr); menor que el que esta en la primera posición se
Ptr^  <Valor, Lista>; coloca delante del primero con un procedimiento
Lista  Ptr identico al de insertar en una pila.

SI_NO
Se pide memoria y se guarda la informacion
Nuevo(PtrNuevo);
PtrNuevo^  <Valor, NULO>;

Ptr = Lista;
MIENTRAS (Ptr^.sgte <>NULO y Valor > Ptr^.sgte^.info)
HACER
Pregunta anticipadamente por el valor contenido en el
Ptr  ptr^.sgte nodo siguiente al apuntado por ptr. Si la informacióna
FIN_MIENTRAS ingresar es mayor a la del nodo siguiente debe avanzar
una posicion

PtrNuevo`.sgte  Ptr^.sgte; Se enlazan los punteros, como se pregunta por


Ptr^.sgte  Ptrnuevo; adelantado, el siguiente del nuevo sera el que es siguiente
FIN_SI; al nodo donde salimos que es el inmediato anterior al
nueo valor. El siguiente de ese nodo sea ahora el nuevo.
El nuevo lo colocamos entre el que señalamos con ptr y el
FIN. siguiente

InsertaPrimero(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una accion

LEXICO
PRE Lista Apunta a NULO porque esta vacia.
POS Lista apunta al primer elemento de la estructura.

Ptr: TPNodo;
ALGORITMO
Nuevo(Ptr);
Ptr^  <Valor, NULO>;
Lista  Ptr
FIN.

InsertaDelante(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una accion

LEXICO
PRE Lista Apunta al Inicio y noesta vacia.
POS Lista apunta al primer elemento de la estructura.

Ptr: TPNodo;
ALGORITMO
Nuevo(Ptr);
Ptr^  <Valor, Lista>;
Lista  Ptr
FIN.

InsertaEnMedio(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una accion


LEXICO
PRE Lista Apunta al Inicio no esta vacia.
POS Lista apunta al primer elemento de la estructura.
No retorna la direccion,queda perfectamente enlazada y ordenada.
Ptr, PtrNuevo : TPNodo;
ALGORITMO
Nuevo(PtrNuevo);
PtrNuevo^  <Valor, NULO>;
Ptr = Lista;
MIENTRAS (Ptr^.sgte <>NULO y Valor > Ptr^.sgte^.info)
HACER
Ptr  ptr^.sgte
FIN_MIENTRAS
PtrNuevo`.sgte  Ptr^.sgte;
Ptr^.sgte  Ptrnuevo;
FIN.

InsertaAl Final(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una accion

LEXICO
PRE Lista Apunta al Inicio o a NULO si esta vacia.
POS Lista apunta al primer elemento de la estructura.
No retorna la direccion del nodo creado.
Ptr, PtrNuevo : TPNodo;
ALGORITMO
SI (lista = NULO )
ENTONCES
Nuevo(Ptr);
Ptr^  <Valor, Lista>;
Lista  Ptr
SI_NO
Nuevo(PtrNuevo);
PtrNuevo^  <Valor, NULO>;
Ptr = Lista;
MIENTRAS (Ptr^.sgte <>NULO)
HACER
Ptr  ptr^.sgte
FIN_MIENTRAS
PtrNuevo`.sgte  Ptr^.sgte;
Ptr^.sgte  Ptrnuevo;
FIN_SI;

FIN.

InsertaNodoPorDosCampos(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una


accion

LEXICO
PRE Lista Apunta al Inicio o a NULO si esta vacia.
POS Lista apunta al primer elemento de la estructura.
Info es un rec¡gistro con al menos dos campos para ordenar
No retorna la direccion del nodo creado, salvo si es el primero
La estructura queda perfectamente enlazada y ordenada creciente.
Ptr, PtrNuevo : TPNodo;
ALGORITMO
SI (lista = NULO) o (Valor.C1 < Lista^.info.C1) o
(Valor.C1 =Lista^.info.C1)y(Valor.C2<Lista^.info.c2)
ENTONCES
Nuevo(Ptr);
Ptr^  <Valor, Lista>;
Lista  Ptr
SI_NO
Nuevo(PtrNuevo);
PtrNuevo^  <Valor, NULO>;
Ptr = Lista;
MIENTRAS (Ptr^.sgte <>NULO) y ((Valor > Ptr^.sgte^.info) o
((valor.C1 = Lista*.info.C1)y(Valor.C2 >Lista*.info.C2))
HACER
Ptr  ptr^.sgte
FIN_MIENTRAS
PtrNuevo`.sgte  Ptr^.sgte;
Ptr^.sgte  Ptrnuevo;
FIN_SI;

FIN.

BuscarNodo(Dato Lista: TPNodo; Dato Valor: Tinfo): TPNodo una funcion

LEXICO
PRE Lista Apunta al Inicio y no esta vacia.
Se busca encontrar el nodo que contenga valor como informacion
POS Lista apunta al primer elemento de la estructura.
Retorna la direccion del nodo con el valor buscado o NULO esta

Ptr : TPNodo;
ALGORITMO
Ptr = NULO;
MIENTRAS (Lista <>NULO Y Ptr = NULO) HACER
SI valor = Lista ^.info
ENTONCES
Ptr = Lista {lo encontro y sale}
SINO
Lista = Lista ^.sgte {avanza al proximo nodo}
FIN_SI
FIN_MIENTRAS
BuscarNodo = Ptr;

FIN.

BuscaOInserta(Dato_Resultado Lista,Ptr: TPNodo; Dato Valor: Tinfo) una accion

LEXICO
PRE Lista Apunta al Inicio o a NULO si esta vacia.
POS Lista apunta al primer elemento de la estructura.
Retorna la direccion del nodo creado, salvo si es el primero
La estructura queda perfectamente enlazada y ordenada creciente.
No se repite la clave
PtrNuevo : TPNodo;
ALGORITMO
SI (lista = NULO o Valor < Lista^.info
ENTONCES
Nuevo(PtrNuevo);
PtrNuevo^  <Valor, Lista>;
Lista  Ptr
SI_NO
Ptr = Lista;
MIENTRAS (Ptr^.sgte <>NULO y Valor >= Ptr^.sgte^.info)
HACER
Similar al procedimiento inserta nodo, pero como no
Ptr  ptr^.sgte debe insertar siempre se cambia el orden de las
FIN_MIENTRAS accines cuando se debe insertar en el medio. En este
SI (Ptr^.Info <> Valor) caso se busca primero y si no esta se crea el nodo y se
enlazan los punteros. Como ptr en este estado no
ENTONCES puede valer NULO y el siguiente si el ciclo de
PtrNuevo^.sgte  Ptr^.sgte; repetición se hace modificando el operador de relacion
Ptr^.sgte  Ptrnuevo; por >= en lugar de >. Al salir del ciclo si el valor
contenido en el nodo actual es distinto al buscado
Ptr  PtrNuevo; significa de ese dato no esta en la lista y se debe
FIN_SI; inserta, de lo contrario no se inserta.
FIN_SI;

FIN.

InsertaNodo2(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una accion

LEXICO
PRE Lista Apunta al Inicio o a NULO si esta vacia.
POS Lista apunta al primer elemento de la estructura.
No retorna la direccion del nodo creado, salvo si es el primero
La estructura queda perfectamente enlazada y ordenada creciente.
P,Q,Ptr : TPNodo;
ALGORITMO
Nuevo(Ptr);
Ptr^  <Valor, NULO>;
P  Lista;
Q  NULO;
MIENTRAS P <> NULO y Valor > p^.info HACER
Q  P;
P  P^.sgte;
FIN_MIENTRAS;

SI (P = Lista)
ENTONCES
Lista  Ptr;
SI_NO
Q^.sgte  Ptr;
FIN_SI;
Ptr^.sgte  P;
FIN.

SuprimeNodo(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una accion


LEXICO
PRE Lista Apunta al Inicio y no esta vacia
POS Libera el nodo si encentra el valor.
P, Q : TPNodo;
ALGORITMO
P  Lista;
Q  NULO:
MIENTRAS (P <> NULO y Valor > P^.info) HACER
Q  P;
P  P^.sgte:
FIN_MIENTRAS;
SI (P <> NULO y Valor = P^.info
ENTONCES
SI Q <> NULO
ENTONCES
Q^.sgte  P^.sgte
SI_NO
Lista  P^.sgte;
FIN_SI:
Destruir(P):
FIN_SI;
FIN

InsertaNodoListaDoblementeEnlazada(Dato_Resultado Lista: TPNodo; Dato Valor:


Tinfo) una accion
// El Nodo tiene un puntero al nodo anterior y un puntero al nodo siguiente
LEXICO
PRE Lista Apunta al Inicio o a NULO si esta vacia.
<Valor,Siguiente,Anterior>
POS Lista apunta al primer elemento de la estructura.
No retorna la direccion del nodo creado, salvo si es el primero
La estructura queda perfectamente enlazada y ordenada creciente.
Se la puede recorrer en ambas direcciones
Ptr, PtrNuevo : TPNodo;
ALGORITMO

SI (lista = NULO o Valor < Lista^.info


ENTONCES
Nuevo(Ptr);
Ptr^  <Valor, Lista,NULO>;
SI lista <> NULO
ENTONCES
Lista^.anterior Ptr
Lista  Ptr
SI_NO
Nuevo(PtrNuevo);
PtrNuevo^  <Valor, NULO,NULO>;
Ptr Lista;
MIENTRAS (Ptr^.sgte <>NULO y Valor > Ptr^.sgte^.info)
HACER
Ptr  ptr^.sgte
FIN_MIENTRAS
PtrNuevo^.sgte  Ptr^.sgte;
PtrNuevo^.ant  Ptr;
Ptr^.sgte  Ptrnuevo;
SI PtrNuevo^.sgte <> NULO
ENTONCES
PtrNuevo^.sgte.ant = PtrNuevo;
FIN_SI;

FIN_SI;

FIN.

ApareoDeListas(Dato_Resultado ListA,ListB,ListAB) una accion

LEXICO
PRE ListA y ListB Listas ordenadas simplemente enlazadas.
POS ListAB Lista enlazada producto del apareo de las listas dato.
Ptr : TPNodo; Puntero auxiliar para insertar como cola
VA,VB,VAB: TipoInfo el valor asociado a cada nodo de la lisra
ALGORITMO

ListAB = NULO;
Ptr = NULO;
MIENTRAS (ListA <> NULO) O (ListB <> NULO) HACER
SI ((ListB = NULO)O((ListA<>NULO)Y(ListA^.info<ListB^.info)))
ENTONCES
VAB = VA
SI_NO
VAB = VB
FIN_SI
Agregar(ListAB,Ptr,VAB);

FIN_MIENTRAS;
Ptr = NULO;
FIN.

ListaParalelaParaAgrupar(Dato_Resultado ListA, Dato ArchDatos,ArchProceso :


TipoArchivo) una accion

LEXICO
PRE ListA Lista Paralela al archivo de datos, con tantos nodos como registros tiene
ese archivo. Las restricciones del nodo no alcanzan para poner la clave de ordenamiento
y solo se puede poner un campo que agrupe datos del archivo de proceso.
ArchDatos es el archivo de datos personales, contiene todas las claves, esta ordenada
por esa por lo que es posible hacer búsqueda binaria. ArchProceso es el archivo a
procesar contiene la clave por la que se vincula con el archivo de datos con lo que esta
ordenado.
Se busca hacer una lista paralela para agrupar evitando busquedas secuenciales en
archivos.
POS ListA contendra el valor acumulado de cada clave del archivo de datos
relacionados posicionalmente.

ALGORITMO

ListA = NULO;
PARA I [1 .. CantidadRegistros(ArchDatos)] HACER
InsertaDelante(ListA, 0);
//Crea la lista con los contadores en cero//
FIN_PARA

MIENTRAS (HayaDatos(ArchProceso)) HACER


LeerRegistro(ArchProceso, Registro);
BusquedaBinaria(ArchDatos, Registro.Clave; Pos);
Ptr = Lista;
Para J [1 .. Pos] HACER
Ptr = Ptr*.sgte;
FIN_PARA
Incrementar(Ptr*.info, Registro.Agregado);
FIN_MIENTRAS;

FIN.

ListaParalelaParaBuscar(Dato_Resultado ListA, Dato ArchDatos,ArchProceso :


TipoArchivo) una accion

LEXICO
PRE ListA Lista Paralela al archivo de datos, con tantos nodos como registros tiene
ese archivo. Las restricciones del nodo no alcanzan para poner la posición en el
archivoclave de ordenamiento y solo se puede poner un campo que agrupe datos del
archivo de proceso.
ArchDatos es el archivo de datos personales, contiene todas las claves, No esta
ordenado lo que no es posible hacer busqueda directa ni búsqueda binaria.Se lo carga
en una lista como cola para poder buscar en la lista y luego accedre al archivo según el
numero de nodo
ArchProceso es el archivo a procesar contiene la clave por la que se vincula con el
archivo y hay que ir a buscar un valor.
Se busca hacer una lista paralela, cargada como cola para buscar evitando busquedas
secuenciales en archivos.
POS ListA contendra el la clave de busqueda y para cada clave del archivo de datos
se relacionan posicionalmente según el numero de nodo.

ALGORITMO

ListA = NULO;
Aux = NULO
PARA I [1 .. CantidadRegistros(ArchDatos)] HACER
Leer(Registro(ArchDatos, Registro)
Agregar(ListA, Aux, Registro.clave);
//Crea la lista con la clave a buscar//
FIN_PARA
Aux = NULO // Para que el resultado sea una lista y no una cola
MIENTRAS (HayaDatos(ArchProceso)) HACER
LeerRegistro(ArchProceso, Registro);
Ptr = Lista;
NumeroNodo = 0;
MIENTRAS Ptr*.info <> Registro.clave HACER
Ptr = Ptr*.sgte;
Incrementar(NumeroNodo);
FIN_MIENTRAS
AccesoDirectoArchivo(ArchDatos, NumeroNodo);
LeerRegistro(ArchDatos, RegistroDatos);
Imprimir(Registrodatos.Otra clave);
FIN_MIENTRAS;

FIN.

InsertaNodoListaCircular(Dato_Resultado Lista: TPNodo; Dato Valor: Tinfo) una


accion
//El Puntero a una lista circular apunta al predecesor al primer elemento
LEXICO
PRE Lista Apunta al al predecesor al primer elemento.
Lista no vacia, al menos un nodo
Si hay un solo nodo apunta a si mismo
POS Lista apunta al primer elemento de la estructura.
No retorna la direccion del nodo creado, salvo si es el primero
La estructura queda perfectamente enlazada y ordenada creciente.
Ptr, Auxiliar : TPNodo; Salir Booleano;
ALGORITMO

Nuevo(Auxiliar);
Aux^  <Valor, NULO>;
Lista  Ptr
SI (lista = NULO)
ENTONCES
Auxiliar^.sgte = Auxiliar;
Lista = Auxiliar;
SI_NO {La lista no esta vacia}
Ptr = Lista;
Salir = FALSO

REPETIR
SI (Ptr^.sgte^.info< Valor)
ENTONCES
Ptr  ptr^.sgte
SINO
Salir = Verdadero
FIN_SI
HASTA Salir o Ptr = Lista

Auxiliar^.sgte  Ptr^.sgte;
Ptr^.sgte  Auxiliar;
SI (Salir = FALSO)
ENTONCES
Lista = Auxiliar
FIN_SI; {de la lista no vacia}

FIN.

Acciones y funciones para arboles

RecorrerEnOrdenArbol(Dato Arbol: TPNodo; Dato Valor: Tinfo) una accion


//El Nodo tiene puntero al hijo izquierdo Y derecho
LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.
POS Recorre la estructura completa en orden.
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
RecorrerEnOrdenArbol(Arbol^.izquierdo);
Imprimir(Arbol*.info);
RecorrerEnOrdenArbol(Arbol^.Derecho);
FIN_SI;

FIN.

RecorrerPreOrdenArbol(Dato Arbol: TPNodo; Dato Valor: Tinfo) una accion


//El Nodo tiene puntero al hijo izquierdo Y derecho
LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.
POS Recorre la estructura completa en pre orden.
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
RecorrerPreOrdenArbol(Arbol^.izquierdo);
Imprimir(Arbol*.info);
RecorrerPreOrdenArbol(Arbol^.Derecho);
FIN_SI;

FIN.

RecorrerPosOrdenArbol(Dato Arbol: TPNodo; Dato Valor: Tinfo) una accion


//El Nodo tiene puntero al hijo izquierdo Y derecho
LEXICO
Pre Arbol Apunta al nodo raiz o es Nulo.
POS Recorre la estructura completa pos orden.
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
RecorrerPosOrdenArbol(Arbol^.izquierdo);
Imprimir(Arbol*.info);
RecorrerPosOrdenArbol(Arbol^.Derecho);
FIN_SI;

FIN.

RecorrerEnOrdenInArbol(Dato Arbol: TPNodo; Dato Valor: Tinfo) una accion


//El Nodo tiene puntero al hijo izquierdo Y derecho
LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.
POS Recorre la estructura completa en orden inverso.
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
RecorrerEnOrdenInArbol(Arbol^.derecho);
Imprimir(Arbol*.info);
RecorrerEnOrdenArbol(Arbol^.Izquierdo);
FIN_SI;

FIN.

RecorrerPreOrdenInArbol(Dato Arbol: TPNodo; Dato Valor: Tinfo) una accion


//El Nodo tiene puntero al hijo izquierdo Y derecho
LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.
POS Recorre la estructura completa en pre orden inverso.
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
RecorrerPreOrdenInArbol(Arbol^.Derecho);
Imprimir(Arbol*.info);
RecorrerPreOrdenArbol(Arbol^.Izquierdo;
FIN_SI;

FIN.

RecorrerPosOrdenInArbol(Dato Arbol: TPNodo; Dato Valor: Tinfo) una accion


//El Nodo tiene puntero al hijo izquierdo Y derecho
LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.
POS Recorre la estructura completa pos orden inverso.
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
RecorrerPosOrdenArbol(Arbol^.derecho);
Imprimir(Arbol*.info);
RecorrerPosOrdenArbol(Arbol^.izquierdo;
FIN_SI;

FIN.

RecorrerEnAnchoPrimero(Dato Arbol: TPNodo; Dato Valor: Tinfo) una accion


//El Nodo tiene puntero al hijo izquierdo Y derecho
LEXICO
PRE Arbol Apunta al nodo raiz no es Nulo.
POS Recorre la estructura completa ancho primero.
Fte, Fin: TPNodo
// Saca el nodo del árbol y lo va cargando en una cola, un nodo hay. Luego recorre hasta
que la cola quede vacía. Por cada nodo que va recorriendo, y como solo puede tener dos
hijos por ser binario, si hay nodo izquierdo lo agrega en la cola, si hay derecho también
lo agrega (esto asegura el recorrido en ancho primero). Al colocar el nodo completo del
árbol en la cola, cuando se saca se tienen los enlaces a los hijos, si es que tiene.//
ALGORITMO
Fte = NULO;
Fin = NULO;
Agregar(Fte, Fin, Arbol);
Mientras (Fte <> NULO) HACER
Suprimir(Fte, Fin, Arbol);
Imprimir(Arbol*.info);
SI (Arbol*.izq<>NULO
ENTONCES
Agregar(Fte, Fin, Arbol*.izq)
FIN_SI
SI (Arbol*.der<>NULO
ENTONCES
Agregar(Fte, Fin, Arbol*.der)
FIN_SI;

FIN_MIENTRAS;

FIN.

InsertarNodoArbol(Dato_Resultado Arbol: TPNodo; Dato Valor: Tinfo) una accion


LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.<izquierdo, info, derecho>
Las Inserciones se hacen siempre en las hojas
POS Inserta un nodo ordenado
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
Nuevo(Arbol);
Arbol^ <NULO, Valor, NULO>;
SINO SI (Valor<Arbol^.info)
ENTONCES
InsertarNodoArbol(Arbol^.izquierdo, Valor)
SINO SI (Valor>Arbol^.info)
ENTONCES
InsertarNodoArbol(Arbol^.izquierdo, Valor)
SINO ERROR
FIN_SI
FIN_SI
FIN_SI;
FIN.
CantidadHojasArbol(Dato Arbol: TPNodo): Entero una funcion;
Eshoja(Dato Arbol): Booleano una funcion;
ALGORITMO
EsHoja  (Arbol^.izquierdo = NULO) Y (Arbol^.derecho = NULO;
FIN /*EsHoja
ALGORITMO
SI (Arbol <> NULO)
ENTONCES SI (EsHoja(Arbol))
ENTONCES
CantidadHojas = 1;
SINO
CanidadHojas CantidadHojas(Arbol^.izquierdo) +
CantidadHojas(Arbol^.derecho);
SINO
CantidadHojas = 0;
FIN_SI;
FIN.

AlturaArbol(Dato Arbol: TPNodo): Entero una funcion;


Maximo(Dato A, B : Entero): Entero una funcion;
ALGORITMO
SI (A > B)
ENTONCES
Maximo = A;
SINO
Maximo = B;
FIN_SI
FIN /*Maximo
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
AlturaArbol 1 +
Maximo(AlturaArbol(Arbol^.izquierdo),AlturaArbol(Arbol^.derecho))

SINO
AlturaArbol -1
FIN_SI;
FIN.

BuscarEnArbol(Dato Arbol: TPNodo; Dato Valor: Tinfo): Booleano una funcion


LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.<izquierdo, info, derecho>
Busca el dato contenido en Valor
POS Retorna Verdadero si lo encuentra o falso en caso contrario.
ALGORITMO
SI (Arbol <> NULO)
ENTONCES
Si (Valor< Arbol^.info)
ENTONCES
BuscarEnArbol  BuscarEnArbol(Arbol^.izquierda, valor)
SINO SI (Valor>Arbol^.info)
ENTONCES
BuscarEnArbol  BuscarEnArbol(Arbol^.derecho, valor)
SINO
BuscarEnArbol  VERDADERO
FIN_SI
FIN_SI

SINO
BuscarEnArbol  FALSO;
FIN_SI;
FIN.

Borrar(Dato_Resultado Arbol: TPNodo; Dato Valor: Tinfo) una accion


LEXICO
PRE Arbol Apunta al nodo raiz o es Nulo.<izquierdo, info, derecho>
POS Busca el dato contenido en Valor y si lo encuentra libera el nodo
BorrarInOrder(Dato_Resultado Arbol)
ALGORITMO
SI (Arbol^.derecho<> NULO
ENTONCES
BorrarInOrder(Arbol^.derecho)
SINO
Auxiliar^.info  Arbol^.info;
Auxiliar Arbol;
Arbol  Arbol^.izquierdo;
FIN_SI
FIN

ALGORITMO
SI (Arbol <> NULO)
ENTONCES
Si (Valor< Arbol^.info)
ENTONCES
Borrar(Arbol^.izquierda, Valor)
SINO SI (Valor>Arbol^.info)
ENTONCES
Borrar(Arbol^.derecho, valor)
SINO /* en este estado lo encontro*/
Auxiliar = Arbol;
SI (Arbol^.derecha = NULO)/* No tiene hijo derecho
ENTONCES
Arbol = Arbol^.Izquierda
SINO SI(Arbol^.izquierda = NULO)
ENTONCES
Arbol = Arbol^.derecha
SINO
BorrarInOrder(Arbol^.izquierdo)
FIN_SI
FIN_SI
Liberar(Auxiliar);
FIN_SI
FIN_SI

SINO
ERROR;
FIN_SI;
FIN.

Inserciones en arboles AVL


Insercion en el sularbol izquierdo de la rama izquierda de A
Rotacion Simple Izquierda Izquierda

N1^.izquierdo  N1^.derecho (N es el nodo desbalanceado N1 su hijo izquierdo)


N1^.derecho  N1
N  N1

Insercion en el sularbol derecho de la rama izquierda de A


Rotacion Doble derecha Izquierda
N1^. izquierdo  N2^. derecho
N2^. derecho  N1
N^. derecho  N2^. izquierdo
N2^.izquierdo  N
N N2

Insercion en el sularbol derecho de la rama derecha de A


Rotacion Simple Derecha Derecha
N1^.derecho  N1^. izquierdo (N es el nodo desbalanceado N1 su hijo derecho)
N1^.izquierdo  N1
N  N1

Insercion en el sularbol izquierdo de la rama derecha de A


Rotacion Doble Izquierda derecha
N1^. derecho  N2^. izquierdo
N2^. izquierdo  N1
N^.izquierdo  N2^.derecho
N2^.derecho  N
N N2
Funciones recursivas

El flujo de control de una función recursiva requiere tres condiciones para una
terminación normal:
1. Un test para detener o continuar con la recursion.
2. Una llamada recursiva para continuar la recursion
3. Un caso base para terminar la recursion

Recursividad
SI (Es el caso base)
ENTONCES
Ejecutar la accion final
Terminr recursion
SINO
Ejecutar accion para cercar a la solucion
Invocar nuevamente a la funcion

Factorial
(Dato N : Entero): Entero una funcion;
ALGORITMO
SI (N = 0)
ENTONCES
Factorial = 1
SINO
Factorial = N * Factorial(N-1)
FINSI
FIN.

Fibonacci
(Dato N : Entero): Entero una funcion;
ALGORITMO
SI (N = 0) O (N = 1)
ENTONCES
Fibonacci = N
SINO
Fibonacci = Fibonaci(N-2)+Fibonacci(N-1)
FINSI
FIN.

EuclidesMaxCD
(Dato M,N : Entero): Entero una funcion;
ALGORITMO
SI (N <= M) Y (M Modulo N = 0)
ENTONCES
EuclidesMaxCD = N
SINO SI (M<N)
ENTONCES
EuclidesMaxCD(N,M)
SINO
EuclidesMaxCD(N, M Mod N)
FINSI
FIN.

Hanoi
(Dato Inicio, Medio, Centro : Cráter: N : Entero): NULO una funcion;
ALGORITMO
SI (N = 1)
ENTONCES
Imprimir(Mover Disco N de inicial a final)
SINO
Hanoi(Inicial, Final, Central, N- 1);
Imprimir(Mover Disco N de inicial a final)
Hanoi(Central, Final, Inicial, N- 1);
FINSI
FIN.

Recursividad indirecta

Funcion mutuamente reursiva implementdas en C

Imprimir los carcteres del alfabeto ingles


# incluye <stdio.h>
void FuncionA(char c);
void FuncionB(char c);
int main()
{
FuncionA(‘Z’);
Return 0;
}

void FuncionA(char c)
{
if( c > ‘A’ )
FuncionB(c);
Printf(“%c”,c);
}
void FuncionB(char c)
{
FuncionA(--c);
}

Determinar si un numero es par


# incluye <stdio.h>
void FuncionPar(int n);
void FuncionImpar(int n);
int main()
{
if (FuncionPr(9))
printf(“Es par”);
else
printf(“Es Impar”);
return 0;
}

int FuncionPar(int n)
{
if (n == 0)
return 1;
else
return FuncionImpar(n-1);

int FuncionImpar(int n)
{
if (n == 0)
return 0;
else
return FuncionPar(n-1);
}

Archivos en pascal
Pascal Dispone del tipo de dato text que permite vincular a una variable interna con un
archivo de texto.
En el caso de C los nombres internos se declaran como un puntero a una struct FILE, y
la discriminacion del tipo, texto o binario se realiza en la apertura
Para trabajar con archivos de texto en Pascal se debe:
1. Declarar el nombre lógico del archivo con un identificador de tipo text.
2. Relacionar el nombre interno o lógico con l externo o físico, mediante
Assign.
3. Abrirlo con Reset o Rewrite según corresponda.
En el ejemplo que sigue se desarrollan las acciones para lectura completa de un archivo
de texto. Se lee
1. Carácter a carácter.
2. Por línea completa.
3. Con formato
a. En datos simples
b. En registros

program LeerCaracteres;
{Lee un archivo de texto caracter a caracter y lo muestra por pantalla}
Arch : text;
Letra : char;
begin
Assign (Arch,'C:\....\archivo.txt');
Reset(Arch);
while not EOF(Arch) do
begin
{Lectura caracter a caracter}
read(Arch, Letra);
write(Letra);
end;
Close(Arch);
End.

program LeerPorLineas;
Arch : text;
Nombre : string;

begin
Assign (Arch,'C:\...\archivo.txt');
Reset(Arch);
while not EOF(Arch) do
begin
{Lectura por linea}
readln(Arch, nombre);
writeln(nombre);
end;
Close(Arch);
End.

program LeerConFormato;
var
Arch : text;
Entero : Integer;
Texto : string[10];
begin
Assign (Arch,'C:\...\archivo.txt');
Reset(Arch);
while not EOF(Arch) do
begin
{Lectura con formato}
readln(Arch, Entero, Texto);
writeln('Este es el entero : ', Entero:8);
writeln('Este es el texto : ', Texto:8);
end;
Close(Arch);
End.

program LeerConFormatoEnRegistro;
TipoRegistro = Record
Numero : Integer;
Cadena : string[10];
End;
var
Arch : text;
Registro : TipoRegistro;
begin
Assign (Arch,'C:\...\archivo.txt');
Reset(Arch);
while not EOF(Arch) do
begin
{Lectura con formato en regitro}
readln(Arch, Registro.Numero, Registro.Cadena);
writeln('El entero del reg.: ', Registro.Numero:8);
writeln('El texto del re. : ', Registro.Cadena:8);
end;
Close(Arch);
End.

Los archivos de tipo o de acceso directo es una colección de datos del mismo tipo con
almacenamiento sin interpretar y guardadas según su representación interna. Solo se
pueden comparar como iguales si son leídos del modo en que fueron escritos Requieren
en pascal que para poder utilizarlos se deba hacer y en orden los siguientes pasos
1. El la cláusula Type definir los tipos
a. Para el registro si corresponde, o podria evitarse se se utilizan archivo de
de tipos de datos primitivos
b. Para el archivo NombreDelTipo = FILE OF TIPO DE DATO
2. Declarar las variables en la clausula Var
a. Una para el archivo
b. Otra para el tipo de dato de cada posición.
3. Asignar y abrir los archivos
a. La asignación a traves de ASSIGN vinculando el nombre interno con el
externo.
b. La apertura según corresponda con Reset o Rewrite

Lo disponible para archivos, una vez en condiciones de ser operados es:

1. Lectura Read(Archivo, TipoDeDato) En el caso de ser un registro se lee


completo.
2. Grabar Write(NombreInterno, TipoDeDato)
3. Seek(NombreInterno,Posición), Ubica el Puntero a Pos Registros
desplazados desde el inicio del mismo
4. Flesize(NombreInterno) Indica cantidad de registros.
5. Filepos(NombreInterno) retorna la posición actual del puntero. Tambien
cantidad de registros de desoplazamiento desde el inicio del archivo.
6. Tanto filepos como filesize retonan un tipo de dato entero largo, pero pueden
ser contenido en un entero de menor jerarquia mientras no se produzca
desbordamiento del mismo.
7. EOF(NombreInterno) Retorna true si se alcanzo la marca de fin de archivo y
False en caso contrario.
8. Truncate(NombreInterno) pone la marca de fin de archivo en el lugar donde
esta el puntero. Trunca el archivo

program ConvierteTextoABinario;
Type
TipoRegistro = Record
Numero : Integer;
Cadena : string[10];
End;
TipoArchivo = FILE OF TipoRegistro;
var
Arch : text;
Registro : TipoRegistro;
Binario : TipoArchivo;
I : Integer;
begin
clrscr;
Assign (Arch,'C:\borlandc\archivo.txt');
Reset(Arch);
Assign (Binario,'C:\...\Binario.Dat');
Rewrite(Binario);
while not EOF(Arch) do
begin
{Lectura con formato en regitrode un archico de texto
y lo almacena en un archivo binario}
readln(Arch, Registro.Numero, Registro.Cadena);
write(Binario,Registro);
end;
Close(Arch);
Close(Binario);
End.

program RecorrerBinario;
uses crt;
Type
TipoRegistro = Record
Numero : Integer;
Cadena : string[10];
End;
TipoArchivo = FILE OF TipoRegistro;
var
Arch : text;
Registro : TipoRegistro;
Binario : TipoArchivo;
begin
Assign (Binario,'C:\...\Binario.Dat');
Reset(Binario);
while not EOF(Binario) do
begin
{Lectura con formato en regitro del archivo Binario}
read(Binario, Registro);
writeln('El entero del Bin.: ', Registro.Numero:8);
writeln('El texto del Bin. : ', Registro.Cadena:8);
end;
Close(Binario);
end.
program RecorreBinarioInverso;
Type
TipoRegistro = Record
Numero : Integer;
Cadena : string[10];
End;
TipoArchivo = FILE OF TipoRegistro;
Var
Binario : TipoArchivo;
Registro : TipoRegistro;
I : Integer;
Begin
Assign (Binario,'C:\...\Binario.Dat');
Reset(Binario);
For i := FileSize(Binario) - 1 downto 0 do
begin
{Lectura con formato en regitro del archivo Binario
en orden inverso}
Seek(Binario,I);
read(Binario, Registro);
writeln('El entero del Bin.: ', Registro.Numero:8);
writeln('El texto del Bin. : ', Registro.Cadena:8);
end;
Close(Arch);
Close(Binario);
end.
Ejemplo en C con aplicaciones de estructuras enlzadas
/* Toma una cadena que representa una expresión aritmética y la convierte a notación
sufijo usando procedimientos de pilas*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

typedef char TipoInfo;

typedef struct tiponodo {


TipoInfo Info;
struct tiponodo *sgte;
} TipoNodo;

typedef TipoNodo* TipoPuntero;

void Meter(TipoPuntero *pila , TipoInfo valor);


void Sacar(TipoPuntero *pila , TipoInfo *valor);
void Sufijo(char * cadena, char * suf);

int main()
{

char cad1[10];
clrscr();
Sufijo("5*8+3",cad1);
printf("%s\n", cad1);
getchar();
return 0;
}

void Sufijo(char * cadena, char * suf)


{
char c;
int i = 0,j=0;
TipoPuntero pila=NULL;
c = cadena[i];
while (c) {
switch(c) {
case '+':
case '-':
case '*':
case '(':
case '/': Meter(&pila, c); break;
case ')': Sacar(&pila, &c);
while (c != '(') {
suf[j++] = c;
Sacar(&pila, &c);
} break;
default : suf[j++]= c; break;
}
i++;
c=cadena[i];
}
while(pila){
Sacar(&pila,&c);
suf[j++]=c;
}
suf[j] = '\0';
}

void Meter(TipoPuntero *pila , TipoInfo valor)


{
TipoPuntero ptr;
ptr =(TipoPuntero) malloc(sizeof(TipoNodo));
ptr -> Info = valor;
ptr -> sgte = *pila;
*pila = ptr;
}

void Sacar(TipoPuntero *pila , TipoInfo *valor)


{
TipoPuntero ptr;
ptr = *pila;
*pila = (*pila)->sgte;
*valor = ptr->Info;
free(ptr);
}
ANEXO 1 Representaciones gráficas de algoritmos

Diagrama de Nassi-Sneiderman

Condición 1

T F

Acción 1 Acción 5
Acción 2 Mientras condición 3
Condición 2 Acción 6
T F
Acción 7
Acción 3
Acción 8
Acción 4 Acción 9
Acción 10
Acción 11
Acción 12

Repetir hasta condición 4

Diagramas de Jackson
Diagramas de Lindsay.

PASCAL DIAGRAMA DE DETALLE

Variable = expresión Variable ← expresión

READ (var1, var2,....var n) var1, var2,....

WRITELN var 1 var 2 var 1 var 2


(ó ó , ..... ) ó ó ,....
WRITE liter1 liter2 liter1 liter2

IF condición
THEN Condición
sentencia 1
ELSE Instrucción 1 Instrucción 2
sentencia 2

WHILE condición Condición


DO
sentencia Instrucción

REPEAT
sentencia; Instrucción 1
: :
: :
sentencia n Instrucción n
Condición
UNTIL condición

FOR variable: expres1 TO expres2


ó Instrucción
Var: exp1, exp2
DOWNTO
DO sentencia

CASE expresión
OF Expresión
const1....... const n : instrucción1
Const1... Const n Const1... Const p
:
: Instrucción1 Instrucción m
const1...... const p : instrucción m
END
Cualquier sentencia puede ser reemplazada por Correspondiendo en el diagrama de detalle
un bloque: BEGIN la secuencia:
sentencias 1; instrucción 1;
: :
: :
sentencias n instrucción n
END
Llaves de Warniel

HacerUnaCosa)
Si HacerOtraCosa

Condición Exclusión

No Vacio
Algoritmo 1
Case
Ordinal 2
HacerUnaCosa;
Mientras HacerOtraCosa;
Condición HacerAlgoMas

Notación Algoritmica
LEXICO {Léxico Global del algoritmo}
{Declaración de tipos, constantes, variables y acciones}
Acción 1
PRE {Precondición de la acción 1}
POS {Poscondición de la acción 1}
LEXICO {Léxico local, propio de la acción 1}
Declaraciones locales
ALGORITMO {Implementación de la acción 1}
{Secuencia de instrucciones de la acción 1}
FIN {Fin implementación algoritmo de la acción 1}

ALGORITMO
PRE {Precondición del algoritmo principal}
POS {Poscondición del algoritmo principal}
{Secuencia de instrucciones del algoritmo principal}
FIN {Fin del algoritmo principal}
Equivalencias entre notación algorítmica y lenguajes de programación
CONDICIONAL
Formato Pascal C
SI Condicion If expresion condicional If (expresion)
ENTONCES Then S;
S S Else
SI_NO Else R;
R R;
FIN_SI

Formato Pascal C
SEGÚN expr Case selector of switch (selector) {
V1 : S1 Valor1 : S1; case etiqueta:S; break;
V2 : S2 ......................... ..............................
EN_OTRO_CASO : Sn else Ve: Se; default: R;
FIN_SEGÚN end; }

ITERACION
Formato Pascal C
Mientras Cond. Hacer While Expresion logica do while(expresion)
S S; S;
FIN_MIENTRAS

Formato Efecto
REPETIR Repeat do
S S S;
HASTA Cond Until expresion logica while(expresion)

Formto Efecto C
PARA i [Vi..Vf] HACER For i:=Vi to vf do for(i=0;i<vf;i++)
S S; S;
FIN_PARA

Estilos de Indentación

Recomendacion de estilos de indentacion para desarrollos más claros y legibles.(Ing. J.


M. Sola)

Estilo The One True Brace Style


while( SeaVerdad() ) {
HacerUnaCosa();
HacerOtraCosa();
}
HacerUnaUltimaCosaMas();

BSD/Allman.
while( SeaVerdad() )
{
HacerUnaCosa();
HacerOtraCosa();
}
HacerUnaUltimaCosaMas();

Estilo Whitesmiths
while( SeaVerdad() )
{
HacerUnaCosa();
HacerOtraCosa();
}
HacerUnaUltimaCosaMas();

Estilo GNU
while( SeaVerdad() )
{
HacerUnaCosa();
HacerOtraCosa();
}
HacerUnaUltimaCosaMas();

Estilo Pico
while( SeaVerdad()
{ HacerUnaCosa();
HacerOtraCosa(); }
HacerUnaUltimaCosaMas();

Estilo Banner
while( SeaVerdad() ) {
HacerUnaCosa();
HacerOtraCosa();
}
HacerUnaUltimaCosaMas();
BIBLIOGRAFIA
Behrouz Forouzan. Introducción a la ciencia de la computación. 2005. Ciencias e
Ingenieria.
De Giusti. Algoritmos datos y programas. 2001. Prentice Hall.
Garcia Molina. Una introducción a la programación. 2005. Thomson.
Kernighan - Ritchie. El lenguaje de programacion C. 1988. Pearson
Kerighan – Pike. La practica de la programacion. 2000. Pearson
Perkins David. La bañera de Arquimedes y otras historias del descubrimiento
cientifico. Paidos 2004
Iranzo, j. Logica simbolica para informaticos. 2006. Alfaomega.
Perez M. Matematica discreta y algoritmos. 2005. Answer.
Joyanes Aguilar, L.Algoritmos y estructura de datos, una perspectiva en C. 2004
Mc Graw Hill

El Autor
Oscar Ricardo Bruno
Formación Academica
Doctorando en educación UnTref
Licenciado en Sistemas (Instituto Tecnologico de Buenos Aires)
Magíster en Docencia Universitaria UTN FRBA
Especialista en Ingenieria en Sistemas (Maestrando) UTN FRBA
Especialista en Docencia Universitaria UTN FRBA
Profesor en Docencia Universitaria en Sistema INSPT UTN FRBA
Espeialista en Investigación Operativa ESIO DIGID
Docente Investigador UTN.

Actividad Docente
Profesor Adjunto Algoritmos y Estructura de Datos y Sintaxis y Semántica de Lengujes
UTN FRBA .
Profesor Adjunto Estructura de Datos Univeridad del Salvador
Profesor Programación II CENT 24.
Profesor Titular Laboratorio II y III Escuela Tecnica Nro 1 Otto Krause
Coordinador Area Computación EIOK
Director y jurado de tesis UTN FRBA
Ex Consejero Departamental docente UTN FRBA departamento Sistemas.

Menciones
Finalista premios Sadosky 2006 a la inteligencia argentina categoría calidad docente

You might also like