professional documents
home
Profile
Upload
docsters
Blogs
Upload
about me
contact me
user photo
Manuel Arce Garcia
submit clear
Acrobat PDF

Programacion Avanzada center doc

Programación Avanzada 2 INDICE CAPITULO I Introducción a la Programación Orientada a Objetos I.1 Introducción ....................... 2 I.2 Introducción al lenguaje de programación ....................... 4 I.3 conceptos básicos ....................... 5 Objeto ....................... 5 Clases ....................... 5 Funciones miembro ....................... 6 Funciones en línea ....................... 6 Constructores y Destructores ....................... 9 Herencia ....................... 10 Mensajes ....................... 11 Encapsulamiento ....................... 11 Polimorfismo ....................... 12 CAPITULO II Recursividad II.1 Introducción ....................... 17 II.2 Escritura de funciones recursivas ....................... 18 II.3. Método de las tres preguntas ....................... 19 II.3.1 Aplicación ....................... 19 II.4 Tipos de recursión ....................... 20 II.5 Recusividad vs. Iteración ....................... 22 II.6 La pila de recursión ....................... 23 II.7 Ventajas e inconvenientes ....................... 23 CAPITULO III Métodos de Ordenamiento y Búsqueda III.1. Introducción ....................... 24 III.2 Clasificación interna ....................... 24 III.2.1 Clasificación por intercambio directo ........ 25 III.2.2 Clasificación por inserción ........ 28 III.2.3 Clasificación por selección directa ........ 29 III.3 Métodos de clasificación avanzados ....................... 29 III.3.1 Clasificación por partición ....................... 30 III.4 Búsqueda lineal ....................... 31 III.5 Búsqueda Binaria ....................... 31 CAPITULO IV Estructuras de Datos IV.1. Introducción ....................... 32 IV.2 Pilas ....................... 32 IV.2.1 Representación ....................... 33 IV.2.2 Operaciones PUSH ....................... 34 IV.2.3 Operaciones POP ....................... 34 IV.3 Colas ....................... 37 IV.3.1 Representación ....................... 37 IV.3.2 Operaciones ....................... 38 IV.3.3 Comportamiento de los apuntadores ........ 38 IV.3.4 Operación de inserción ....................... 38 IV.3.5 Operación de eliminación ....................... 39 IV.4 Cola Circular ....................... 40 IV.5 Listas ....................... 42 IV.5.1 Operaciones ....................... 42 IV.5.2 Notación ....................... 42 IV.5.3 Listas sencillamente ligadas ....................... 43 IV.5.3.1 Operación de Inserción ....................... 44 IV.5.3.1 Operación de Eliminación ....................... 46 Bibliografía ....................... 52Programación Avanzada 3 ,ntroducción a la 3rogramación 2rientada a 2bjetos Capitulo I I.1 INTRODUCCIÓN Aunque ya han pasado algunos años la Orientación a Objetos sigue siendo un término de moda. En 1967 fue desarrollado SIMULA, un lenguaje para aplicaciones de simulación, considerado por muchos el precursor en la introducción del concepto de objeto. Lamentablemente este nacimiento en Noruega pasó inadvertido para gran parte de la familia de los programadores. Resulta que ahora tenemos un “niño” de más de 25 años que muchos se apuran en querer rebautizar. Por aquel entonces se hablaba de la “crisis del software” que provocaría el surgimiento de la Programación Estructurada. Las “grandes” brigadas de programadores que programaban los “grandes” sistemas para las “grandes” máquinas enfrentaban serios problemas de organización y productividad. La respuesta fue una metodología que promovía una solución jerárquica, disciplinada, organizada y ¡planificada! para desarrollar el software. Algunas mejoras fueron introducidas en los lenguajes de programación para facilitar esto: mejores estructuras de control para obviar el goto, algunos recursos de modularidad y de organización de bibliotecas de programas. Esto pareció ser suficiente durante los 70’s. No obstante, pocos sistemas lograban terminarse, pocos se terminaban cumpliendo los requerimientos iniciales y no todos los que se terminaban cumpliendo los requisitos iniciales se usaban según lo planificado. El problema (mal llamado de mantenimiento) consistía en cómo adaptar el software a nuevos requerimientos imposibles de haber sido planificados inicialmente. La “estructuración” facilitaba en todo caso la zambullida de los “programadores de mantenimiento” en los mares de líneas de programa fuente, pero no la impedía. Este alto grado de planificación y previsión es contrario a la propia realidad. El hombre aprende y crea a través de la experimentación, no de la planificación. Se necesitan medios que faciliten laProgramación Avanzada 4 experimentación y no que exijan que tenga que ser planificado un proyecto entero antes de poder escribir una línea de programa para que luego este proyecto se convierta en una camisa de fuerza en el desarrollo y evolución del sistema. El desarrollo de SMALLTALK a finales de los 70’s fue una respuesta a este problema. El desarrollo técnico del hardware, su disminución de precios y la explosión de las computadoras personales fue el detonante final. La Programación Orientada a Objetos (P00), que por todo lo anterior algunos han dado en la Programación Estructurada de los 90s, encuentra aquí un terreno fértil. ¿Es la POO un mejor paradigma que otros? En cierto sentido sí lo es. ¿Es una cura de todos nuestros problemas? No lo es. ¿Significa que la arriba mencionada “crisis del software” desaparecerá? Probablemente no. Pero entonces, ¿qué es lo grande de la Programación Orientada a Objetos?. En lugar de tratar de modelar un problema en algo familiar a la computadora se trata ahora de acercar la computadora al problema. Es decir, modelar la realidad del problema a través de entidades independientes pero que interactúan entre sí y cuyas fronteras no estén determinadas por su instrumentación computacional sino por la naturaleza del problema. Estas entidades serán denominadas objetos, por analogía con el concepto de objeto en el mundo real. Resolver problemas consiste en definir objetos y sus acciones y entonces invocar las acciones enviando mensajes a los objetos que ocultan las características internas dé como llevan a cabo estas acciones. Esta forma de resolver problemas luce familiar y lógica y ha sido utilizada por otras disciplinas científicas ya que en nuestra experiencia diaria nosotros “manipulamos objetos”. Programar en un lenguaje de programación orientado a objetos es definir clases (nuevos tipos de datos) que “expresan” una determinada funcionalidad la cual es común a todos los individuos de una clase. A través de esta funcionalidad los objetos o instancias concretas de una clase dan respuesta a las solicitudes (mensajes) que les envían otros objetos. Las clases deben ser lo suficientemente CERRADAS como para que cada objeto pueda ocultar la información (datos) que lo caracteriza como individuo. Es un problema interno del objeto cómo llevar a cabo la funcionalidad descrita en la clase. Visto en la óptica de las arquitecturas convencionales de computadoras, en la POO los datos no circulan abiertamente por todo el sistema como en la programación tradicional. Los programas no se dividen en declaraciones pasivas de estructuras de datos y funciones (que tal vez actúen sobre algunas de las estructuras de datos). Los datos están ahora encerrados dentro de cada objeto y éste es quien decide internamente cómo trabajar con ellos para dar respuesta a una solicitud de otro objeto. Por otra parte estas clases deben ser lo suficientemente ABIERTAS para permitir la reutilización, adaptación y extensión de las mismas a nuevas funcionalidades, sin correr el riesgo de afectar el funcionamiento de lo que ya está correcto. Esta aparente contradicción, de lo que se conoce como principio ABIERTO – CERRADO, es la piedra angular de la POO. En ausencia de una definición formal de qué es la POO, algunos prefieren identificarla por sus objetivos. Tres objetivos fundamentales persigue la POO para facilitar la experimentación: · La robustez de las partes que garanticen su integridad y funcionamiento propio (lo cual a su vez facilita “arrinconar” las fallas). Esto se facilita en los lenguajes orientado aProgramación Avanzada 5 objetos (LOO) a través de recursos como el encapsulamiento y el manejo de excepciones. · La reusabilidad y la extensibilidad. Que se pueda “derivar” (más que definir) nuevas clases de objetos a partir de las ya existentes, facilitándole con ello al programador el desarrollo de prototipos y una rápida exploración en las nuevas ideas. Esto se facilita en los LOO mediante la redefinición de operadores y funciones, la generalidad, la herencia y el polimorfismo. Esta búsqueda de la comunidad, de aprovechar lo existente, es una de las características básicas de la POO. Algunos de estos recursos como el polimorfismo y la jerarquía de clases a través de la herencia nos dotan de recursos para clasificar lógicamente a los objetos, evitando las redundancias y obviando restricciones que otros paradigmas de programación nos imponen. I.1 LOS LENGUAJES DE PROGRAMACIÓN ORIENTADOS A OBJETOS Un gran número de lenguajes ha contribuido a la proliferación de las técnicas orientadas a objetos. Las versiones iniciales de Lisp en la década de los cincuenta y Simula en la década de los sesenta, sentaron las bases para el nacimiento de Smalltalk que al principio de los setenta supuso el asentamiento definitivo de la filosofía orientada a objetos. Smalltalk-80, la versión más popular de Smalltalk, y la aparición de extensiones orientada a objetos de dos lenguajes populares, C y Pascal (recientemente se ha incorporado Visual Basic), han acelerado rápidamente la implantación de la POO dentro de lo que hoy se conoce ingeniería del software. Los lenguajes orientados a objetos (LOO) se dividen en dos grandes grupos: puros e híbridos. Los lenguajes OO puros incluyen Smalltalk, Eiffel y Actor principalmente, mientras que los lenguajes OO híbridos son aquellos que añaden las propiedades OO a las propiedades intrínsecas de tipo procedimental. Entre los lenguajes orientados a objetos híbridos se destacan Objetive-C, Object-Pascal, Turbo Pascal (versiones 5.0, 6.0 y 7.0) y C++ (versiones Turbo C++, Microsoft C/C++ 7.0, Borland C++ 3.1, Zortech C++ 3.1, Visual C++, Symantec C++ 6.0, etc.). I.2 INTRODUCCION AL LENGUAJE DE PROGRAMACION C++ se deriva del lenguaje C. Estrictamente hablando es un superconjunto de C. El gráfico 1.1 muestra los conjuntos C++. Lenguaje C++ Lenguaje C Otras características útiles Características de C no utilizadas normalmente en C++ Características de C++ que no residen en C. Características comunes a C y C++Programación Avanzada 6 Figura 1.1. C++ es un superconjunto de C. C++ fue desarrollado por Bjarne Stroustrup en Bell Laboratories. Comenzó su trabajo a principios de los ochenta en respuesta a la necesidad de diseñar un lenguaje de simulación que tuviera las características de programación orientada a objetos; por aquel entonces un relativamente nuevo paradigma de programación. En lugar de diseñar un nuevo lenguaje, decidió añadir las características deseadas al lenguaje C, que ya estaba bien definido. Tras desarrollar C con clases (así se llamo la primera versión de C++) tomó una decisión histórica: desarrollar el compilador C++ como un programa traductor que procesa lenguaje fuente C++ en un lenguaje fuente C. El código fuente C traducido se compilaba entonces en cualquier computadora que soportase C. Llamo a este programa traductor Cfront . El código fuente de C++ ha pertenecido a AT&T, así como las versiones posteriores. I.3 CONCEPTOS BÁSICOS POO (Programación orientada a objetos) POO es un importante conjunto de técnicas que se pueden utilizar para hacer el desarrollo de programas más eficiente mientras se mejora la fiabilidad de los programas resultantes. En esencia, POO es un nuevo, medio de enfocar el trabajo de programación. OBJETO Un objeto es, primero que todo, una abstracción del mundo real. Un objeto es, en términos computacionales, la representación en memoria de la abstracción mencionada, y como tal, un ente encapsulado que contiene datos y métodos, y que es capaz de recibir mensajes del exterior. CLASES A pesar de que los términos Clase y Objeto se mezclan entre sí en la literatura dedicada, a la programación Orientada a Objetos, para nuestros propósitos habrá una semántica definida y diferenciada de cada uno de ellos. Una clase es un tipo definido por el usuario. Generaliza el concepto de estructura y lo reemplaza con ventaja. En otras palabras, una clase es un prototipo que define los métodos y datos que serán incluidos en un tipo de objeto particular. Esta última definición, señala ya la distinción entre Clases y Objetos, pues los últimos no son más que instancias de los primeros. Las clases son estructuras que contienen no sólo declaraciones de datos, sino también declaraciones de funciones. Las funciones se conocen como funciones miembro, e indican qué tipos de cosas puede hacer una clase. Para usar una clase, primero tiene que declararla, como se hace en el caso de estructuras. La declaración completa de una clase puede aparecer sólo una vez en un programa, tal y como en las estructuras. Esta es la sintaxis de una declaración de una clase : class nombre_de_la_Clase { funciones y datos privadosProgramación Avanzada 7 public:métodos(funciones) y datos públicos } lista_de_objetos; La lista_de_objetos, puede no aparecer si lo único que se desea es declarar una determinada Clase. Ejemplo de una clase es: class persona { public: char nombre[25]; char apellidos[30]; char ciudad[20]; char provincia[20]; char codpostal[10]; char leer_infor( ); char visualizar( ); Muestra_Infor( ); }; y un objeto, una variable o una instancia de la clase Persona: Persona comprador; FUNCIONES MIEMBRO Los métodos de la clase también se denominan funciones miembros si son la única forma de accesar a los datos privados de la clase. (::) Operador de resolución de alcance quien le indica al compilador que la versión de la función cuyo identificador precede al mencionado operador, es la de la Clase en sí. FUNCIONES EN LINEA Una función en línea (inline), es una función que es expandida en línea cuando es llamada, en lugar de realizar el proceso de construir un llamado a función. La razón para usar funciones en línea es la eficiencia y existen dos formas de declarar una función en línea: La forma explicita y la forma implícita. include class Vector { int c[10]; int e; public: //definimos la función inicia en linea //en forma implicita void inicia(void) {e=0;} void pon_dato(int i); void suma(Vector x, Vector y ); void Visualizar(void);}; /* Código en linea para la función inicia de la clase vector, forma explicita */Inline void Vector::inicia (void) { e=0; }Programación Avanzada 8 Control del acceso a una clase La tarea de una clase consiste en ocultar la mayor cantidad de información posible. Por lo tanto es necesario imponer ciertas restricciones a la forma en que se puede manipular una clase, y la forma en que se pueden utilizar los datos y el código contenidos en la misma. Existen tres tipos de usuarios de una clase: 1. La clase misma 2. Usuarios genéricos 3. Clases derivadas Cada tipo de usuario tiene diferentes privilegios de acceso. Cada nivel de privilegio de acceso está asociado con una palabra clave. Como existen tres niveles, hay tres palabras clave: 1. private 2. public 3. protected Cada declaración contenida en el cuerpo de una clase define de manera implícita un privilegio de acceso apareciendo en una sección precedida de una de estas tres palabras clave. Si no se utiliza ninguna de esas palabras, todo es private por omisión. class test { private: int datos; //datos privados public: void leer datos(); //interfaz público }; El especificador private es opcional y no es necesario ponerlo en la sintaxis de la clase. class articulo { float precio; //privado por defecto char nombre; //privado por defecto public: void leer indicar(); }; Miembros de Clase private //Ejemplo Programación orientada a objetos //Clase Private #include //Definición de una clase class PublicExample { int variable; void function(); }; void PrivateExample::function() {} void main()Programación Avanzada 9 {PrivateExample object; object.variable = 1; //variable no accesible int i = object.variable; //variable no accesible object.function(); //variable no accesible printf(" i=%d ",i); }Miembros de Clase public //Ejemplo Programación orientada a objetos //Clase Public #include class PublicExample {public: int variable; void function(); }; void PublicExample::function() { } void main() { PublicExample object; object.variable = 1; int i = object.variable; object.function(); printf(" i=%d ",i); } Miembros de Clase protected Cuando se define una clase que se utiliza subsiguientemente como clase de base para otras clases, se puede hacer que los miembros estén accesibles sólo para funciones de las clases derivadas mediante el uso de la palabra clave protected. Considere una jerarquía de objetos, como se ilustra en la figura 1.2. Figura 1.2. Jerarquía de clases. La jerarquía de la figura 1.2 se puede expresar con código de esta manera: class A{protected: int value_a; }; class A: protected: int value_a; class B: public A public: void FB(); class C: public B public: void FC();Programación Avanzada 10 class B: public A{ public: void FB(); }; class C: public B{ public: void FC(); }; La propiedad de ser protected se extiende indefinidamente hacia abajo en un árbol de herencia, en tanto que se declare que las clases derivadas tengan clases de base public. Con este hecho en mente, cualquier función miembro de la clase C o B puede tener acceso al miembro de datos protected, value_a, de su clase de base A; pero sólo a través del apuntador this. Por ejemplo, el código siguiente es aceptable: void B: :FB { value_a =0; //se accede a través de este apuntador }void C::FC(){ value_a=0 //también se accede a través de este apuntador }Operador this Nunca se puede llamar a una función miembro de una clase a menos que se asocie con un objeto (una instancia de la clase). En ANSI C cada vez que se utiliza un puntero para acceder a los miembros de una estructura, debe utilizarse el operador de puntero (->) para acceder a los objetos de datos. ¿Cómo sabe una función miembro cuál es la instancia de una clase (el objeto específico) asociada con ella? El método utilizado por C++ es añadir un argumento extra oculto a las funciones miembro. Este argumento es un puntero al objeto de la clase que lo enlaza con la función asociada y recibe un nombre especial denominado this. CONSTRUCTORES Y DESTRUCTORES C++ permite a los Objetos inicializarse por sí mismos en el momento de su creación. Esta inicialización automática se lleva a cabo a través del uso de las funciones Constructores. Un Constructor, es una función especial que es miembro de la Clase y que lleva el mismo nombre que ella. Un Constructor no debe regresar ningún tipo. Un Destructor es similar al constrictor pero se le antepone el símbolo ~#include //Clase pila_car para modelar una Pila de caracteres class pila_car { int size; char* tope, s; public: pila_car(int sz) {tope = s = new char[size = sz];} //CONSTRUCTORProgramación Avanzada 11 ~pila_car() {delete s;} //DESTRICTOR void push(char c) { *tope++ = c;} char pop() { return *--tope;} }; HERENCIA Es un mecanismo por el cual, una clase de Objetos puede expresarse como un caso especial de una Clase más general, con lo cual incluye automáticamente toda la definición de datos y métodos de la clase general. La herencia es probablemente la característica de C++ que da mayor poder al concepto de clase. En C++, el término herencia se aplica sólo a clases y sus características. Las variables no se pueden heredar de otras variables, y las funciones tampoco pueden heredar de otras funciones. La herencia le permite construir y extender continuamente clases desarrolladas por usted o por alguien más, básicamente sin límite. Partiendo de la clase más simple, se pueden derivar clases cada vez más complejas que no sólo son fáciles de depurar, sino que también son simples por si solas. Cada vez que se deriva una nueva clase partiendo de la anterior, se pueden heredar algunas o todas las características de las clases primarias o progenitoras, agregando nuevas según necesiten. Un proyecto completo puede tener clasificaciones o cientos de clases básicas. C++ es diferente de algunos lenguajes orientados por objetos, ya que no sólo admite la herencia individual, sino también la herencia múltiple, la cual permite la derivación de una clase de más de una clase del mismo tipo, heredando comportamientos de todos sus ancestros. CREACIÓN DE UNA CLASE DERIVADA Cada clase derivada se debe referir a una clase base declarada anteriormente. Esta clase base puede ser precompilada o puede ser definida en el mismo programa en que la clase derivada se crea. Si no se define la clase antes que la clase derivada, se debe, al menos, declarar el nombre de la clase base primero como una referencia anticipada. De hecho, sintácticamente la clase base es como cualquier otra clase, y por consiguiente, cualquier clase puede servir como base. La declaración de una clase derivada tiene la siguiente sintaxis: class tipo clase_derivada : clase base { declaraciones de miembros }; Los especificadores de acceso pueden ser los ya conocidos public, protected o private. Una clase derivada de la clase base comida es la clase hamburguesa: class hamburguesa : public comida { declaraciones de miembrosProgramación Avanzada 12 }; En este caso la etiqueta de la clase derivada es hamburguesa. La clase padre o base tiene visibilidad pública y su etiqueta (nombre) es comida. Es especificador public significa que todos los miembros públicos de la clase base (alfa) son también miembros públicos de beta. ACCESO A LA CLASE BASE Cuando una clase hereda de otra, la clase derivada hereda todos los datos y funciones públicos de la clase base. Por consiguiente, sólo tiene que especificar los datos y funciones adicionales a la clase derivada. Se pueden especificar tres modificadores de una clase: public, private, protected. Colocando la palabra reservada public delante del nombre de la clase base, significa que todos los miembros public y protected de la clase base serán miembros public y protected de la clase derivada. Se puede especificar también una palabra reservada private delante del nombre de la clase base. En este caso, todos los miembros public y protected de la clase base se convierten en miembros privados de la clase derivada. Por último cuando la palabra reservada protected se sitúa antes del nombre de la clase, todos los miembros public y protected se convierten en miembros protegidos de la clase derivada. La siguiente tabla resume todos los especificadores del acceso. Especificadores de acceso MENSAJES La forma en que los Objetos interactúan entre si, es enviándose mensajes pidiendo que se active (ejecute) un Método específico. Un mensaje consiste simplemente del nombre del Objeto a quien va dirigido, seguido del nombre del Método que el receptor sabe como ejecutar. Si el método requiere información adicional, el mensaje incluye parámetros. Conceptos tales como Transmisor y Receptor, identifican a los Objetos que envían y reciben los mensajes. Atributos Descripción public clase base: Los miembros públicos de la clase base son miembros públicos de la clase derivada. Los miembros protegidos de la clase base son miembros protegidos de la clase derivada. Los miembros privados de la clase base permanecen privados a la clase derivada. private clase base: Todos los miembros públicos y protegidos de la clase base son miembros privados de la clase derivada. Los miembros privados de la clase base son privados en la clase derivada. protected clase base: Los miembros public y protected de la clase base son miembros protegidos de la clase derivada. Los miembros privados de la clase base son privados de la clase derivada.Programación Avanzada 13 Un mensaje, en términos conocidos, es una llamada a un procedimiento inmerso en un Objeto. ENCAPSULAMIENTO Mecanismo por medio del cual, un Objeto esconde sus Datos y Métodos al mundo exterior. El encapsulamiento permite proteger los Datos de transformaciones no deseadas y solo bajo autorización del propio objeto, los métodos propios y los procedimientos externos, pueden alterar valores de los datos de un objeto. Los paquetes del lenguaje ADA son un buen ejemplo de encapsulamiento, y mucha de la facilidad para la reusabilidad del código – carta de presentación de ADA-, se debe al encapsulamiento donde no es necesario saber como está construido para sacar provecho de él. Es claro que una de las técnicas del empaquetamiento del software, las bibliotecas de funciones, salen beneficiadas con la posibilidad de obtener reusabilidad del código ya elaborado. POLIMORFISMO La posibilidad de que un nombre (identificador), pueda ser usado con diferentes propósitos, aunque relacionados semánticamente, se denomina polimorfismo. Polimorfismo es el término usado para describir el proceso por el cual, diferentes implementaciones de una función, pueden ser accesadas usando el mismo nombre. La frase una interfaz, múltiples métodos, caracterizan este proceso. El polimorfismo es soportado en tiempo de compilación por algunos lenguajes, en tiempo de ejecución por otros, y en ámbos, por otros más. El polimorfismo en tiempo de compilación, identifica a términos tales como: Sobrecarga de Operadores, Sobrecarga de funciones y Sobrecarga de Constructores. Por su parte, el polimorfismo de Herencia, y las Funciones Virtuales. SOBRECARGA DE FUNCIONES El primer mecanismo de Polimorfismo en un lenguaje de programación, lo constituye la sobrecarga de funciones. En C++, dos o más funciones pueden tener el mismo nombre, difiriendo solamente en los parámetros de su declaración. Las funciones que comparten nombre, pero que declaran parámetros diferentes, se dicen que están Sobrecargadas, y al proceso de construir estas funciones se le denomina Sobrecarga de Funciones. Por lo general, casi todos los lenguajes contienen sobrecarga de funciones (en realidad como veremos después, sobrecarga de operadores), pues por ejemplo, en Pascal, la función (operador) suma, está sobrecargado y soporta diferentes parámetros, como se observa en los ejemplos siguientes: integer + integer real + real string + string Pero no sólo los operadores intrínsecos de un lenguaje están sobrecargados, el usuario de C++ puede crear sus propias funciones sobrecargadas, como se ilustra con la función pow(x,y) definida como xy , a la que sobrecargaremos para los siguientes tipos: int pow (int, int); double pow(double, double); //existe en Programación Avanzada 14 complex pow(double, complex); //existe en complex pow(complex, int); complex pow(complex, double); complex pow(complex, complex); Una función k que invoque algunos de los casos anteriores, se presenta a continuación, en ella se señalarán las funciones asociadas. void k (complex z) { int i = pow (2,2); //invoca a pow (int, int) double d = pow (2.0,2); //invoca a pow (double, double) complex z1 = pow (2,z); //invoca a pow (double, complex) complex z2 = pow (z,2); //invoca a pow (complex, int) complex z3 = pow (z,z); //invoca a pow (complex, complex) }SOBRECARGA DE CONSTRUCTORES Siendo los constructores por naturaleza funciones, independientemente del servicio único que presentan a los Objetos, también pueden ser sobrecargados. Para sobrecargar el constructor de una Clase, simplemente debemos declarar las diferentes formas que deberán tomar, y definir su acción relativa a cada una de ellas. El siguiente ejemplo muestra la sobrecarga de los constructores de una Clase timer donde mostraremos además, una característica importante del lenguaje C++ denominada inicialización dinámica. Como en el caso de las variables, Inicialización Dinámica significa asignar un valor a un parámetro que sólo es conocido en tiempo de ejecución y por lo tanto, desconocido al momento de la compilación. # include # include # include class timer { int segundos; public: //Segundos especificados como una cadena timer (char *t) { segundos = atoi (t); } //Segundos especificados como una entero timer (int t) { segundos = (t); } //Tiempo especificado en minutos y segundos timer (int min, int seg) { segundos = min * 60 + seg; } void run (void); }; void timer::run (void) { clock_t t1,t2; t1 = t2 =clock ( )/CLK_TCK; while (segundos) { if (t1 /CLK_TCK + 1 <= (t2 = clock ( )/CLK_TCK)) { segundos --; t1 = t2; } } cout << “ \a ” ; //Código ASCII de la campana }Programación Avanzada 15 main (void) { timer a (10); a.run ( ); cout << “Dar número de segundos: “ ; char str [80]; //Especificación de variable cin >> str; timer b (str); //Inicialización Dinámica b.run( ); cout << “Dar tiempo en minutos y segundos: “ ; int min,seg; //Especificación de variables cin >> min >> seg; timer c (min,seg); //Inicialización Dinámica c.run( ); return ( ); } Otra de las mejoras de C++ sobre C, se puede observar en el código anterior, donde se han señalado dos casos de especificaciones de variables, en cualquier parte del código, y no únicamente en el encabezado como es tradicional. SOBRECARGA DE OPERADORES La Sobrecarga de Operadores, es otro modo de implementar sobrecarga de funciones (Polimorfismo), aunque por la sintaxis de los operadores, debemos hacer algunas consideraciones adicionales. Para sobrecargar un operador, debemos definir su significado, relativo a la Clase sobre la cual se aplica. Es decir, la sobrecarga de operadores redefinen la semántica de un operador relativo a una Clase, en la que se encuentra definido. La forma general de una función operador es: tipo nombre_de_la_Clase :: operator# (lista de argumentos) { //operación definida relativa a la Clase } Las funciones operadores, deberán ser funciones miembros o funciones amigas de la Clase en la que serán usadas. Aunque similares, existen diferencias importantes entre la manera en que es sobrecargado un operador, como función miembro y como función amiga. El siguiente ejemplo, muestra la Clase punto (en el plano) y tres operadores asociados a ella: el operador suma (+), el operador de igualdad (=) y el operador de autoincremento posterior (++). Construiremos el código, en cada caso, requerido para sobrecargar estos operadores como funciones miembros y como funciones amigas. # include //Clase punto con sobrecarga de operadores como funciones //miembros class punto {Programación Avanzada 16 int x, y; //Coordenadas del punto public: punto operador+ (punto op2); //op1 esta implícito punto operador= (punto op2); //op1 esta implícito punto operador++ (void); //op1 esta implícito también void muestra (void); void asigna (int nx, int ny); }; //Clase punto mostrando la sobrecarga del operador + //como una función # include class punto { int x, y; //Coordenadas del punto public: friend punto operador + (punto op1, punto op2); //(Igual que en el caso anterior) }; La definición de los operadores se presenta a continuación: //Operador + como una función miembro punto punto:: operador + (punto op2); { punto temp; temp.x = op1.x + op2.x; temp.y = op1.y + op2.y; return temp; }; //Operador + como una función amiga punto operador + (punto op1, punto op2); { punto temp; temp.x = op1.x + op2.x; temp.y = op1.y + op2.y; return temp; }; Los otros dos operadores se mantienen igual en los dos casos. Es importante señalar que en el código de ellos aparecerá un apuntador especial, denominado this. Cada vez que una función miembro es invocada, se le pasa de manera automática un apuntador al Objeto que la invoca, el mencionado apuntador está disponible a la función invocada usando el identificador this. El apuntador this es, en esencia un apuntador implícito a todas las funciones miembros. //Operador = como una función miembro punto punto::operador = (punto op2);Programación Avanzada 17 { x = op2.x; y = op2.y; return *this; }; //Sobrecarga de un operador unario (operador ++) punto punto::operador ++ (void); { x ++; y ++; return *this; }; Si observamos el código de los operadores anteriores (=,++), notaremos en primer término expresiones de la forma: x = op2.x que en realidad corresponden a la notación compacta de la expresión: this -> x = op2.x Por otro lado, el regreso de los operadores (=, ++), está especificado por la proposición: return *this Si asociamos los dos casos anteriores, veremos que en efecto, el apuntador this es un apuntador al Objeto mismo, por lo que puede usarse como apuntador al valor de regreso (en este caso el Objeto mismo) de ambos operadores. Para clarificar un poco más los conceptos anteriores, analicemos, por ejemplo, el significado de la proposición:a++ Digamos que expresada en palabras, la semántica de la expresión anterior se describe como: “tome el valor de la variable a, e increméntelo en uno, indique el nuevo valor”. Si asociamos ahora el operador autoincremento a un Objeto O, el significado de la operación O++, deberá ser algo semejante al siguiente: “tome el objeto O e incremente sus datos en uno, indique el nuevo valor del Objeto O”. Visto así, es ahora claro el significado de la proposición return *this, encontrada en el código de los operadores de igualdad y de autoincremento, para cada uno de los Objetos punto. En forma similar, la expresión a = b, significa: “tome el valor de la variable b y almacénelo en la variable a, indique el valor de la variable a”. Expresada en dos Objetos O1 y O2, la proposición O1 = O2,significa: “tome el valor del Objeto O1, almacénelo en el objeto O2 e indique el valor del Objeto O1”. Otro detalle que hay que notar es que en realidad, el uso de un operador (unario o binario), es prerrogativa de un Objeto, por lo que O1 = O2 deberás interpretarse como: “El Objeto O1 llamará al operador de igualdad (=)(que es una función), con el parámetro O2”. Análogamente O1++ significará que el Objeto O1 llama al operador de autoincremento para operar sobre si mismo. Con las condiciones anteriores, es ahora simple entender el valor de un apuntador al Objeto mismo y la importancia de poder contar con él.Programación Avanzada 18 Dos detalles deben considerarse, conociendo el significado de la expresión op op es un operador cualquiera, es claro que la expresión O = O + 10, para cualquier Objeto O, es unja expresión que no tiene problema alguno, pero que por su parte, si rescribimos la expresión anterior como O = 10 + O, obtenemos una expresión que no funciona. La razón de la falla en la expresión anterior, está en el significado de la operación, en este caso, la constante 10 no puede llamar al operador +. La solución al problema anteriormente planteado, está dada si usamos funciones amigas para implementar el operador +, ya que una implementación de esa naturaleza, permite expresar los dos parámetros con sus respectivos tipos.Programación Avanzada 19 5ecursividadCapitulo II II.1 INTRODUCCIÓN La recursividad es una técnica en la que una función se hace llamadas así misma en el proceso de la realización de sus tareas. La recursividad da al programador una herramienta potente para resolver ciertos tipos de problemas reduciendo complejidad u ocultando los detalles del problema. Una función recursiva es una función que se llama así misma, ya sea directa, o indirecta, por lo cual se puede considerar Primero consideraremos la recursividad directa, que es cuando una función se llama así misma la cual considera dos elementos: el Caso Base ( fin de recursión ) y el Caso General (Parte puramente recursiva). Caso base: Es el caso más simple de una función recursiva, y simplemente devuelve un resultado. ( el caso base se puede considerar como una salida no recursiva). Caso general : Relaciona el resultado del algoritmo con resultados de casos más simples. Dado que cada caso de problema aparenta o se ve similar al problema original, la función llama una copia nueva de si misma, para que empiece a trabajar sobre el problema más pequeño y esto se conoce como una llamada recursiva y también se llama el paso de recursión. El paso de recursión también incluye la palabra reservada return, porque su resultado será combinado con la parte del problema que la función resolvió, para formar un resultado que será regresado al llamado original, posiblemente main. La recursividad se puede definir mejor mediante el clásico ejemplo de la función factorial.Programación Avanzada 20 Ejemplo 1. Factorial 0! = 1, 1!=1 (Caso Base) Sea n! = n * (n-1) * (n-2) *...... * 1, si n>O (Caso General) Supongamos que buscamos el valor de 4!, como 4 >0 entonces usamos la segunda parte de la definición: 4! = 4 * 3 * 2 * 1 = 24 A continuación se presentan las funciones en modo iterativo y recursivo ITERATIVO: int Factorial( int n ) { int i, res=1; for(i=1; i<=n; i++ ) res = res*i; return res; } RECURSIVO: int Factorial( int n ) { if(n==0) return(1); return(n*Factorial(n-1)); } Podemos observar que incluso la función recursiva utiliza menos código de programación y no emplea ninguna estructura de repetición. II.2 ESCRITURA DE FUNCIONES RECURSIVAS MÉTODO 1. Primero, obtener una definición exacta del problema a resolver. (Esto, por supuesto, es el primer paso en la resolución de cualquier problema de programación) 2. A continuación, determinar el tamaño del problema completo que hay que resolver. Este tamaño determinará los valores de los parámetros en la llamada inicial a la función. 3. Tercero, resolver el caso base en el que el problema puede expresarse no recursivamente. Por último, resolver el caso general correctamente en términos de un caso más pequeño del mismo problema, una llamada recursiva. Ejemplo: Analizar la función factorial utilizando el método anterior. DEFINICIÓN : Obtener el factorial de un número entero, utilizando la formula TAMAÑO : Los enteros (rango máximo de los enteros) CASO BASE: 1) Cuando n--0 nfact = retum (1) CASO GENERAL: 2) Cuando se llama a la función con un parámetro disminuido nfact (n-1)Programación Avanzada 21 3) nfact <-n * nfact( n-1) una vez aclarado quien es el caso base y quien el caso general, la codificación ya es simple como se observa al realizar e programa: //Programa que calcula el factorial de un entero en C++ #include < iostream.h> int nfact(int n); int main() { int n, factorial; cout « "Dame el número al cual deseas obtener su factorial » ; cout « "\n\n" factorial = nfact(n); cout « n « "! Es igual a " « factorial « "\n"; retum 0; } int nfact(int n) { if (n==O) retum(I); caso base elseretum(n * nfact(n-1); //caso general }La siguiente figura muestra, como la sucesión de llamadas van regresando un valor al evaluar 4! 4! = 4* 3 = 24 es regresado 3! = 3*2 = 6 es regresado 2!= 2 *1= 2 es regresado 1! = 1 1 es regresado (a) (b) a)Secuencia de llamadas recursivas b) Valores regresados de cada llamada recursiva. II.3 MÉTODO DE LAS TRES PREGUNTAS Utilizaremos este método para verificar que una solución recursiva funciona, Para comprobar que la solución funciona, debe ser capaz de contestar "si' a las siguientes preguntas 1. La pregunta Caso-Base: ¿ Hay una salida no recursiva de la función, y la rutina funciona correctamente para este caso «base»? . 2. La pregunta llamador-Más Pequeño : ¿Cada llamada recursiva se refiere a un caso más pequeño del problema original? 3. La pregunta Caso-General : Suponiendo que la(s) llamada(s) recursiva(s) funcionan correctamente, ¿funciona correctamente toda la función? II.3.1 APLICACIÓNProgramación Avanzada 22 1. La pregunta Caso Base: El caso base ocurre cuando n=0 a nfact se le asigna el valor de 1. (respuesta es si) 2. La pregunta Llamador-Más Pequeño: La respuesta a esta cuestión debe buscarse en los parámetros pasados en la llamada recursiva. En la función nfact, la llamada recursiva para n-1. Cada llamada recursiva posterior envía un valor decrementado del parámetro, hasta que el valor enviado es finalmente cero. En este punto hemos alcanzado el caso más pequeño, y no se hacen más llamadas recursivas. (respuesta es si) 3. La pregunta Caso General : En el caso de la función nfact, necesitamos verificar que la fórmula que estamos usando realmente obtiene una solución correcta. Suponiendo que la llamada recursiva nfact(n-1) nos da el valor correcto de (n-I)!, hacemos la asignación de n * (n-I)! a nfact. Esta es la definición de un factorial (formula (2), por lo tanto, sabemos que la función va bien para todo entero positivo. (Respuesta si) II.4 TIPOS DE RECURSIÓN a) Recursividad simple: Aquella en cuya definición sólo aparece una llamada recursiva. Se puede transformar con facilidad en algoritmos iterativos. b) Recursividad múltiple: Se da cuando hay más de una llamada a sí misma dentro del cuerpo de la función, resultando más difícil de hacer de forma iterativa. Ejemplo: La serie Fibonacci 0, 1, 1, 2, 3, 5, 8, 13, 21........ empieza con los términos 0 y 1 y tienen la propiedad de cada término siguiente es la suma de los dos términos precedentes. Escriba un programa que calcule dichos serie realizando la función recursiva fibonacci(n) de orden n. Función: int Fib( int n ) { if (n<=1) return(1); return (Fib(n-1) + Fib(n-2)); }b) Recursividad anidada: En algunos de los argumentos de la llamada recursiva hay una nueva llamada a sí misma. Ejemplo de Ackerman int Ack( int n, int m ) { if(n==0 ) return(m+1); else If (m==0) return(Ack(n-1,1)); else return(Ack(n-1, Ack(n,m-1))); }Programación Avanzada 23 c) Recursividad cruzada o indirecta: Son algoritmos donde una función provoca una llamada a sí misma de forma indirecta, a través de otras funciones. Es decir es aquella en la que una función es llamada a otra función y esta a su vez llama a la función que la llamó. Ejemplo 1 : Par o Impar: int par( int nump ) { if(nump==0) return(1); return( impar(nump-1)); }int impar( int numi ) { if(numi==0) return(0); return( par(numi-1)); } Ejemplo2: Hacer un programa que escriba en pantalla los números del 1 al 101 utilizando recursividad indirecta. //Programa que escribe los números del 1 al 1 0 1 //utilizando dos funciones que se llaman a si mismas #include void paso1 (int i); void paso2 (int i); main () { int i=l; paso1 (i); retum (); }void paso 1 (int i) { i++; cout « i « "\n"; paso2(i); }void paso2(int i) { i++; cout « i « "\n"; if (i < 100) paso 1 (i); }Programación Avanzada 22 II.5 RECUSIVIDAD VS. ITERACIÓN Existen varios factores a considerar en la decisión de usar o no una solución recursiva a un problema. Las principales cuestiones son la claridad y la eficiencia de la solución. En general : Una solución no recursiva es más eficiente en términos de tiempo y espacio de computadora. La solución recursiva puede requerir gastos considerables, y deben guardarse copias de variables locales y temporales. Aunque el gasto de una llamada a una función recursiva no es peor, esta llamada original puede ocultar muchas capas de llamadas recursivas internas. El sistema puede no tener suficiente espacio para ejecutar una solución recursiva de algunos problemas. Otro problema : Una solución recursiva particular puede tener una ineficiencia inherente. Tal ineficiencia no es debida a la elección de la implementación del algoritmo; más bien, es un defecto del algoritmo en si mismo. Un problema inherente es que algunos valores son calculados una y otra vez causando que la capacidad de la computadora se exceda antes de obtener una respuesta. La cuestión de la claridad en la solución es, no obstante, un factor importante. En algunos problemas una solución recursiva es más simple y más natural de escribir, como se observa en las siguientes funciones. En algunos casos una solución recursiva es más simple y más natural de escribir Vamos a comparar la función factorial tanto recursiva como no recursiva //función recursiva //función no recursiva int factr (int n) int facti(int n) { { if (n==O) int i,factorial =1; retum(I); if (n==0) else return 1; else return(n * factr(n -1) for (i=1; i<=n; i++) } factorial*=i; return(factorial); } Podemos observar que la función realizada con recursividad es más sencilla que la iterativa, en donde no es necesario utilizar el ciclo for como en la función iterativa, por lo tanto en este caso es mejor utilizar la recursividad que la iteración. Con la disminución del costo de memoria y tiempo de computadora y el crecimiento del costo de tiempo del programador, merece la pena usar soluciones recursivas para tales programas como a acabarnos de observar. La recursividad es una herramienta que puede ayudar a reducir la complejidad de un programa ocultando algunos detalles de la implementación. Como una guía general, si la solución no recursiva es más corta o no mucho más larga que la función recursiva, no usar recursividad.Programación Avanzada 23 II.6 LA PILA DE RECURSIÓN La memoria del ordenador se divide de manera lógica en varios segmentos (4): 1. Segmento de código: Parte de la memoria donde se guardan las instrucciones del programa en código Máquina. 2. Segmento de datos: Parte de la memoria destinada a almacenar las variables estáticas. 2. Montículo: Parte de la memoria destinada a las variables dinámicas. 4. Pila del programa: Parte destinada a las variables locales y parámetros de la función que está siendo ejecutada. Llamada a una función: · Se reserva espacio en la pila para los parámetros de la función y sus variables locales. · Se guarda en la pila la dirección de la línea de código desde donde se ha llamado a la función. · Se almacenan los parámetros de la función y sus valores en la pila. · Al terminar la función, se libera la memoria asignada en la pila y se vuelve a la instrucción actual. Llamada a una función recursiva: En el caso recursivo, cada llamada genera un nuevo ejemplar de la función con sus correspondientes objetos locales: · La función se ejecutará normalmente hasta la llamada a sí misma. En ese momento se crean en la pila nuevos parámetros y variables locales. · El nuevo ejemplar de función comieza a ejecutarse. · Se crean más copias hasta llegar a los casos bases, donde se resuelve directamente el valor, y se va saliendo liberando memoria hasta llegar a la primera llamada (última en cerrarse) II.7 VENTAJAS E INCONVENIENTES 1. La principal ventaja es la simplicidad de comprensión y su gran potencia, favoreciendo la resolución de problemas de manera natural, sencilla y elegante; y facilidad para comprobar y convencerse de que la solución del problema es correcta. 2. El código de una función recursiva es menor que el de una función iterativa por lo cual es más fácil su implementación. 3. El principal inconveniente es la ineficiencia tanto en tiempo como en memoria, dado que para permitir su uso es necesario transformar el programa recursivo en otro iterativo, que utiliza pilas para almacenar las variables.Programación Avanzada 24Programación Avanzada 25 2rdenamiento y %úsqueda Capitulo III III.1 INTRODUCCIÓN La clasificación se define en general como el proceso de rearreglar un conjunto de objetos en un orden específico. Su finalidad es facilitar la búsqueda posterior de los miembros pertenecientes al conjunto clasificado. En todos los lugares donde se guardan objetos que luego es necesario recuperar se lleva a cabo la clasificación, por ejemplo, en una biblioteca, en los diccionarios, en las bodegas, etc. Existe una gran dependencia entre el algoritmo a utilizar y la estructura de los datos por procesar, de aquí que los métodos de clasificación se dividen en: clasificación de arreglos y clasificación de archivos. La clasificación de arreglos o interna se produce en la memoria de la computadora. La clasificación de archivos o externa es necesaria cuando el número de objetos es demasiado grande que no cabe en la memoria principal. III.2 CLASIFICACIÓN INTERNA Existen muchos métodos de clasificación interna, algunos directos y otros más refinados. En general los algoritmos más simples requieren más movimientos de elementos y comparaciones que los métodos más refinados, pero estos últimos son más complejos. Vamos a suponer que los objetos a clasificar son registros que contienen uno o más campos. Uno de los campos, llamado clave, es de un tipo para el cual está definida la relación de ordenamiento lineal £. La clave puede ser entera, real y de tipo carácter o cualquier otro tipo para la cual la relacion “menos o igual que” o “menor que” este definida.Programación Avanzada 26 El problema de la clasificación consisten en ordenar una secuencia de registros de tal forma que los valores de sus claves formen una secuencia no decreciente. Esto es, dados los registros r1, r2, ..., rn, con valores de clave k1, k2, ..., kn, respectivamente, debe resultar la misma secuencia de registros en orden ri1, ri2, ..., rin, tal que ki1 £ ki2 £ ... £ kin. Los métodos de clasificación interna pueden dividirse en tres categorías principales según el método en el que se basan: 1. Clasificación por inserción 2. Clasificación por selección 3. Clasificación por intercambio III.2.1 CLASIFICACIÓN POR INTERCAMBIO DIRECTO Uno de los métodos de clasificación más simples que puede haber es el llamado “clasificación de burbuja” (bubblesort). La idea básica de este algoritmo es imaginar que los elementos están como burbujas en un tanque de agua con pesos correspondientes a sus claves, cada pase sobre el arreglo produce el ascenso de una burbuja hasta su nivel adecuado de peso. Vamos a suponer que tenemos un arreglo de registros A, el número de registros a ordenar es n, y que el campo clave contiene la clave del registro. El algoritmo de burbuja en su versión más sencilla se presenta a continuación: Procedimiento burbuja Inicio Para i 1 a n-1 hacer Para j n a i+1 hacer Si A[j].clave < A[j-1].clave entonces Intercambia (A[j], A[j-1]). Fin_si Fin_para Fin_para Fin El algoritmo intercambiar se presenta a continuación: Procedimiento intercambia (x, y) Inicio Temp. x X y Y Temp. Fin Si aplicamos el algoritmo a un arreglo de enteros como el siguiente, los recorridos que se obtienen son: Arreglo A={6, 9, 12, 67, 3, 0 ,2}, n=7. Obsérvese que en este caso no es necesario declarar un arreglo de registros. Recorridos Resultados i=1 0, 6, 9, 12, 67, 3, 2 i=2 0, 2, 6, 9, 12, 67, 3 i=3 0, 2, 3, 6, 9, 12, 67 i=4 0, 2, 3, 6, 9, 12, 67 i=5 0, 2, 3, 6, 9, 12, 67 Este algoritmo admite un poco de mejoramiento. En el ejemplo anterior se puede observar que en las últimas 3 iteraciones ya no hay intercambio, tomando en cuenta esto, el algoritmo se puede mejorar. El algoritmo por vibración es una variante del algoritmo burbuja peroProgramación Avanzada 27 mejorado. Este algoritmo consiste en “recordar” cuál fue el último intercambio realizado y en qué momento. Procedimiento shakesort Inicio L 2 r n k n Repetir Para j r a l hacer Si A[j-1] > A[j] entonces Intercambia (A[j-1], A[j]) K j Fin_si Fin_para L k+1 Para j l a r hacer Si A[j-1] > A[j] entonces Intercambia(A[j-1], A[j]) K j Fin_si Fin_para R k-1 Hasta l > r Fin El algoritmo de burbuja y shakesort en c++ podrían quedar así: #include #include class objeto { int llave; public: objeto(int x=0){ llave=x; } void leer_llave(); void ver_llave(); int obtenllave(); void ponerllave(int x); }; void objeto::leer_llave() { cin >> llave; } void objeto::ver_llave() { cout << llave; } int objeto::obtenllave() { return llave; } void objeto::ponerllave(int x) { llave=x; }Programación Avanzada 28 class ordena { int num; /* No se pueden iniciar aqui variables pj num=0 */short ban; objeto elementos[100]; public: ordena(int ne=0){ num=ne; ban=0; //objeto elementos[100] ; /* inicia(); se supone que ya se iniciaron */} void lectura(); void burbuja(); void shakesort(); void despliega(); private: int verifica(); void intercambia(objeto *a, objeto *b); }; void ordena::lectura(){ ban=1; for (int i=0; i"; elementos[i].leer_llave(); cout << "\n"; } } int ordena::verifica(){ if ((ban ==0) || (num <= 0)) { cout << "No existen elementos a ordenar\n"; return 0; } return 1; } void ordena::burbuja(){ if (verifica()!=0) { for (int i=0; i=i+1; j--) if (elementos[j].obtenllave() < elementos[j-1].obtenllave()) intercambia(&elementos[j], &elementos[j-1]); } } void ordena::shakesort(){ int l, r, k, j; l=1; r=num; k=num; do{ for(j=r-1; j>=l; j--) if(elementos[j-1].obtenllave() > elementos[j].obtenllave()){ intercambia(&elementos[j-1], &elementos[j]); k=j; } l=k+1;Programación Avanzada 29 for(j=l; j elementos[j].obtenllave()) { intercambia(&elementos[j-1], &elementos[j]); k=j; } r=k-1; }while (l<=r); } void ordena::despliega(){ for(int i=0; iobtenllave(); a->ponerllave(b->obtenllave()); b->ponerllave(aux); } void main(){ ordena ele(5), ele2(5); ele.lectura(); ele.burbuja(); ele.despliega(); ele2.lectura(); ele2.shakesort(); ele2.despliega(); } III.2.2 CLASIFICACIÓN POR INSERCIÓN Este método consiste en reubicar en el lugar correcto cada uno de los elementos a ordenar, es decir, en el i-ésimo recorrido se “inserta” el iéssim elemento A[i] en el lugar correcto, entre A[1], A[2], ..., A[i-1], los cuales fueron ordenados previamente. Existen dos condiciones distintas que podrían dar terminado el proceso de clasificación: 1. Se encuentra un elemento A[j] que tiene una llave menor que la de A[i] 2. El extremo izquierdo de la secuencia destino es alcanzado Para facilitar el proceso de mover A[i], es útil utilizar la técnica del centinela, en A[0], cuya clave es igual a la de A[i]. El algoritmo se muestra a continuación: Procedimiento inserciondirecta Inicio Para i 2 a n hacer A[0] A[i] j i/* Los miembros de la clase por default son privados */Mientras A[j] < A[j-1] hacer Intercambia (A[j], A[j-1]) J j-1 Fin_mientras Fin_paraProgramación Avanzada 30 Fin inserciondirecta Si notamos que la secuencia destino A[2]...A[i-1] donde se debe insertar el elemento, ya está ordenada Este algoritmo puede ser mejorado determinando rápidamente el punto de inserción. La elección es una búsqueda binaria que prueba la secuencia destino en la mitad y continúa buscando hasta encontrar el punto de inserción. Este algoritmo de inserción modificado recibe el nombre de inserción binaria y se presenta a continuación: Procedimiento insercionbinaria Inicio Para i 2 a n hacer X A[i] L 1 r i Mientras L < r hacer M (L+r) div 2 Si A[m] <= x entonces L L+1 Sino r m Fin_si Para j i a r+1 (decremento en 1) hacer A[j] A[j-1] Fin_para A[r] x Fin_para Fin insercionbinaria En el algoritmo anterior se observa que a veces en lugar de mejorar el algoritmo, este se deteriora, puesto que hay que realizar más pasos por la inserción de un elemento, esto implica mayor tiempo que el utilizado por la clasificación de inserción directa. III.2.3 CLASIFICACIÓN POR SELECCIÓN DIRECTA Este método se basa en los siguientes principios: 1. Seleccionar el elemento que tenga la llave menor 2. Intercambiarlo con el primer elemento a1 3. Repetir después estas operaciones con los n-1 elementos restantes, luego con n-2 elementos hasta que no quede más que un elemento (el más grande) Procedimiento seleccióndirecta Inicio Para i 1 a n-1 hacer K i x a[i] Para j i+1 a n hacer If a[j] < x entonces k j x a[k] fin_si fin_para a[k] a[i] a[i] x fin_para fin selecciondirecta III.3 MÉTODOS DE CLASIFICACIÓN AVANZADOS Inserción por decremento decrecienteProgramación Avanzada 31 Un refinamiento de la inserción directa fue propuesto por D.L. Shell en 1959. Procedimiento shellsort Inicio H[1] 9 h[2] 5 h[3] 3 h[4] 1 Para m 1 a t hacer K h[m] s -k Para I k+1 a n hacer X a[i] j i-k si s=0 entonces s -k S s+1 a[s] x Mientras x j Si L < J entonces quicksort(L,j) Si i < R entonces quicksort(i,R Fin sort Inicio Sort(1,n) Fin quicksortProgramación Avanzada 32 III.4 BÚSQUEDA LINEAL La tarea de búsqueda es una de las más frecuentes en programación. Para los siguientes algoritmos vamos a suponer que la colección de los datos en donde vamos a buscar, es fija, y que es de tamaño n. Comúnmente cada elemento de la colección es un registro con un campo que actúa como una clave. La tarea consiste en hallar un elemento cuya clave sea igual al argumento de búsqueda. Búsqueda lineal Cuando los elementos no llevan un orden y no existe información sobre ellos se utiliza la búsqueda lineal, es decir, comparar uno a uno los elementos hasta encontrar el deseado. Existen dos condiciones que ponen fin a la búsqueda. 1. Se encuentra el elemento 2. Se ha rastreado toda la colección y no se encuentra el elemento Procedimiento búsquedalineal (elemento) Inicio I 0 Mientras (i< N) y (a[i] <> elemento) hacer I i+1 Fin búsquedalineal Si i al final es N entonces el elemento no fue encontrado, pero sino entonces quiere decir que el elemento esta en la posición i-ésima del arreglo. III.5. BÚSQUEDA BINARIA Para utilizar este algoritmo es necesario que la colección este ordenada, así se puede acelerar el proceso de búsqueda. La idea clave consiste en inspeccionar un elemento elegido al azar, por ejemplo a[m] y compararlo con el elemento de búsqueda x. Si es igual a x, la búsqueda termina; si es menor que x, inferimos que todos los elementos con índices menores que o iguales a m pueden ser eliminados, y nuestra búsqueda ahora se centra en los demás elementos. Esto se repite mientras el índice inicial sea menor o igual que el final (L y R) y el elemento no sea encontrado. Generalmente el elemento no se elige al azar, sino que se toma el elemento que se encuentra a la mitad del arreglo. Procedimiento búsquedabinaria(x) Inicio L 0 R N Found false Mientras L 1 entonces Tope Tope -1 Pila[Tope] Dato Sino Escribir “Pila llena” Fin_si IV.2.3. OPERACIÓN POP Esta operación elimina elementos en la pila si ésta aún no está “vacía” . La pila esta vacía cuando no hay elementos en el arreglo (como en el estado inicial). 12345 Tope 12345 15 Tope 12345 7 15 TopeProgramación Avanzada 36 Por ejemplo: Suponga que max = 5 y que la pila ya tiene 2 elementos Si se elimina un elemento entonces : El elemento se “saca” del tope de la pila (DatoPila[Tope]) y el Tope se incrementa. Entonces Pop quedaría: Pop ( ) Inicio Si TOPE <= max entonces Dato Pila[Tope] Tope Tope -1 Sino Escribir “Pila vacía” Fin_si Retorna Dato Fin /* Programa para el manejo de una PILA estatica Programacion Avanzada Abril 2002*/#include #include enum estados{VACIA,NORMAL,SIN_ESPACIO}; class pila //Clase pila estática {int stack[100]; int tope, max; enum estados edo; public : pila(void) { tope =0; } void tamanio(int t) { max=t;} void push(int dato); int pop(void); void visualizar(void); void ver_estado(void); void pon_estado(void); enum estados obten_estado(void) { return edo;} }; /* Definicion de metodos */void pila::push(int dato) { max= 12345 12345 7 15 Tope 15 TopeProgramación Avanzada 37 stack[tope++]=dato; }int pila::pop(void) {return(stack[--tope]); }void pila::visualizar(void) {cout <<" DATOS "; for (short i=0; i> opc; cout << endl; return opc; }/* Programa para el manejo de una pila estática */int main() { pila pila1; int x; short op,tam; cout << "\n\nDa el tama¤o de la pila: "; cin>>tam; pila1.tamanio(tam); pila1.pon_estado(); do{ op=menu(); switch(op)Programación Avanzada 38 { case 1 : if(pila1.obten_estado()!=SIN_ESPACIO) { cout <<"Da el elemento a insertar[100] : "; cin >> x; pila1.push(x); pila1.pon_estado(); } else pila1.ver_estado(); break; case 2 : if (pila1.obten_estado()!=VACIA) {x = pila1.pop(); cout <<"El dato extraido es : "<= min entonces Dato Cola[Frente] Si Frente = Fondo entonces [si se elimina el último elemento entonces se vuelve al estado inicial (cola Vacía)] Fondo min-1 Frente Fondo sino Frente Frente + 1 Fin_si Sino Escribir “Cola Vacía” Fin_si Fin Ejemplo programa de cola estática: //Manejo de una Cola estatica //Programacion Avanzada //BUAP-Marzo 2002 #include //Clase cola class Cola {int c[100]; int e,s; int tamanio; public: void inicia (void); void pondato_cola (int i); int sacadato_cola (void); void pon_tamanio(int t){tamanio =t;} }; /*Definicion de metodos */void Cola::inicia(void) { e=s=0; }; void Cola::pondato_cola(int i) {if (e == tamanio) { cout << " La cola esta llena \n"; return; }Programación Avanzada 41 c[e++]=i; }; int Cola::sacadato_cola(void) {if (e == s) { cout << " La cola esta vacia "; return 0; } return c[s++]; }; /* Caracterizaci¢n de la clase Cola */int main(void) { Cola a,b; //Objetos de la clase Cola a.inicia(); b.inicia(); a.pon_tamanio(5); b.pon_tamanio(8); a.pondato_cola(10); a.pondato_cola(20); b.pondato_cola(100); b.pondato_cola(200); cout << "COLA A : " << a.sacadato_cola() << "\n"; cout << "COLA A : " << a.sacadato_cola() << "\n"; cout << "COLA A : " << a.sacadato_cola() << "\n"; cout << "COLA B : " << b.sacadato_cola() << "\n"; cout << "COLA B : " << b.sacadato_cola() << "\n"; return 0; } IV.4. COLA CIRCULAR Suponga que se tiene la siguiente cola : 15 12 21 13 17 42 1 2 3 4 5 6 y que se realiza sobre ella dos operaciones de eliminación: 21 13 17 42 1 2 3 4 5 6 Y ahora una operación de inserción: Según el algoritmo inserción_cola anteriormente citado, la condición para insertar es : Fondo < max, la cual no se Frente Fondo Frente FondoProgramación Avanzada 42 cumple aún cuando existe espacio para la inserción y entonces la inserción no se efectúa. Se ha llegado a una situación en la que se tiene espacio pero no se puede insertar. Esto puede solucionarse manejando la cola como una estructura circular, donde el elemento siguiente al de la última posición, es el primero. De esta forma, bajo el esquema anterior, la inserción de 47, se comportará de la siguiente manera: 47 21 13 17 42 1 2 3 4 5 6 Y la condición para la inserción será: si (Fondo=max y Frente = min ) o (Fondo+1=Frente) entonces escribir “Cola Llena” Así se tiene el siguiente algoritmo para la inserción en cola circular: Inserta_cola_circular(Dato) Inicio Si (Fondo=max y Frente=min) o (Fondo+1=Frente) entonces Escribir “Cola Llena” sinoSi Fondo=max entonces Fondo 1 Sino Fondo Fondo +1 Fin_si Colacir[Fondo] Dato Si Frente < min entonces Frente min Fin_si Fin_si Fin En cuanto a la eliminación, esta también considera que al llegar a la posición max, la siguiente posición es min. De esta manera el algoritmo es el siguiente: Elimina_cola_circular Inicio Si Frente < min entonces Escribir “Cola Vacía” Sino Dato Colacir[Frente] Si Frente = Fondo entonces [Si solo hay un elemento] Frente min-1 Fondo Frente Sino Si Frente = max entonces Frente FondoProgramación Avanzada 43 Frente 1 Sino Frente Frente + 1 Fin_si Fin_si Fin_si Fin IV.5. LISTAS En el caso de las Pilas y Colas se emplean estructuras estáticas en dónde la manipulación de datos es a través de posiciones localizadas secuencialmente. Para declarar estas estructuras se debe definir un tamaño determinado el cual no puede modificarse posteriormente. En algunas ocasiones es más conveniente utilizar estructuras dinámicas, en las cuales se puede aumentar o disminuir de tamaño de la estructura de acuerdo a los requerimientos específicos de la aplicación. Una lista ligada es un grupo de datos organizados secuencialmente, pero a diferencia de los arreglos, la organización no esta dada implícitamente por su posición en el arreglo. En una lista ligada cada elemento es un nodo que contiene el dato y además una liga al siguiente dato. Estas ligas son simplemente variables que contienen la(s) dirección(es) de los datos contiguos o relacionados. Para manejar una lista es necesario contar con un apuntador al primer elemento de la lista denominado "cabeza" . La ventaja de las listas ligadas es que: · Permiten que sus tamaños cambien durante la ejecución del programa Así, las listas se conocen como ”estructuras de datos dinámicas”. IV.5.1. OPERACIONES En una lista ligada se pueden realizar básicamente 4 operaciones: · Recorrer: moverse sobre los elementos de la lista, partiendo del inicio y llegando al final de la misma. · Insertar: añadir elementos a la lista. · Eliminar: quitar elementos de la lista. · Buscar: verificar la existencia de un elemento dado dentro de la lista. La inserción y la eliminación se pueden llevar a cabo: · Al inicio de la lista · Al final de la lista · Entre dos elementos de la listaProgramación Avanzada 44 IV.5.2. NOTACIÓN Los nodos son almacenados en un espacio de memoria denominado “Memoria del Montón (Heap)”, esta memoria es asignada por el procesador, a solicitud del programa, en tiempo de ejecución. Cuando la memoria es “asignada” se obtiene un apuntador (dirección) al elemento, así, se usa Crea_nodo(P) para indicar la asignación de memoria dinámica que es apuntada por P, esto es: info sig P Para referirse al dato almacenado en info o en sig se utiliza la notación: P^.info P^.sig Para indicar que el apuntador sig anota a nada se utiliza: Info sig P y se denota P^.sig NULO Así, para crear una lista de dos elementos, donde el primero es apuntado por Cabeza, se tiene la siguiente secuencia: Crea_nodo(P) P Cabeza P Cabeza Crea_nodo(P) P P^.sig NULO Cabeza Cabeza^.sig P cabeza P PProgramación Avanzada 45 IV.5.3. LISTAS SENCILLAMENTE LIGADAS Una lista sencillamente ligada es un grupo de datos en dónde cada dato contiene un apuntador hacia el siguiente dato en la lista, es decir, una liga hacia el siguiente elemento. IV.5.3.1. OPERACIÓN DE INSERCIÓN Para insertar un dato en una lista sencillamente ligada es necesario crear un nuevo nodo, recorrer la lista nodo por nodo hasta encontrar la posición adecuada y actualizar las ligas para insertar el nuevo nodo. Lista Desordenada Supóngase que se tiene una lista donde los datos se insertan siempre al final de la misma sin importar el orden. En esta caso el algoritmo de inserción es el siguiente: Inserción_lista_sencilla_desordenada(dato) Inicio Crea_nodo(P) P^.info dato P^.sig NULO Si cabeza =NULO entonces [Se coloca apuntador cabeza al primer elemento de la lista] cabeza P Sino [Se recorre la lista para encontrar la última posición] recorre cabeza Mientras recorre^.sig <> NULO hacer recorre recorre^.sig Fin_mientras [Se liga el nuevo nodo en la última posición] recorre^.sig P Fin_si Fin Lista Ordenada Si lo que se quiere es tener una lista donde los datos están ordenados ascendente o descendentemente, entonces el algoritmo cambia ligeramente. Aquí, durante el recorrido se verifica que los nodos que se recorren tengan datos menores o mayores (según el caso) que el dato que se va a insertar. CabezaProgramación Avanzada 46 El algoritmo para una lista de datos ordenados de manera ascendente, es el siguiente: Inserción_lista_sencilla_ordenada(dato) Inicio Crea_nodo(P) P^.info dato [Si es el primer elemento de la lista] Si cabeza =NULO entonces [Se coloca apuntador cabeza al primer elemento de la lista] cabeza P cabeza^.sig NULO Sino [Se recorre la lista para encontrar la posición correcta donde debe insertarse el nuevo nodo] recorre cabeza anterior recorre Mientras(recorre^.sig<>NULO) y (recorre^.info < dato) hacer anterior recorre recorre recorre^.sig Fin_mientras [Se verifica si la inserción es al inicio de la lista, al final o entre dos nodos para realizar las operaciones pertinentes] Si recorre^.info < dato entonces [Se inserta al final] recorre^.sig p p^.sig NULO Sino Si recorre = cabeza entonces [insertar al inicio] cabeza p cabeza^.sib recorre sino [Se inserta entre dos nodos] anterior^.sig P P^.sig recorre Fin_si Fin_si Fin_si FinProgramación Avanzada 47 Es importante notar el uso de dos apuntadores durante el recorrido: anterior y recorre. Anterior siempre esta anotando al nodo que precede al anotado por recorre, de tal forma que cuando se deba insertar entre dos nodos, se tengan apuntadores a los dos nodos entre los que se insertará, esto se ilustra a continuación: Suponga que se tiene la lista Cabeza y que se desea insertar 10, entonces el proceso es: Cabeza Cabeza Cabeza En este momento la condición (recorre^.sig<>NULO) y (recorre^.info < dato) ya no se cumple y termina el recorrido, entonces se verifica si debe insertarse al inicio, al final o entre dos nodos. No es al inicio (recorre <> cabeza), no es al final (recorre^.sig = NULO , pero recorre^.info no es menor que dato), entonces es entre dos nodos: IV.5.3.2. OPERACIÓN DE ELIMINACIÓN 5 7 11 5 7 11 anterior recorre 5 7 11 anterior recorre 5 7 11 anterior recorre 5 7 11 anterior recorre cabeza 10Programación Avanzada 48 Para eliminar un dato de una lista sencillamente ligada es necesario recorrer la lista haciendo una búsqueda del elemento a eliminar: Mientras(recorre^.sig<>NULO) y (recorre^.info <> dato) hacer anterior recorre recorre recorre^.sig Fin_mientras Aquí se pueden tener dos opciones: A) Encontrar el elemento al inicio, al final o entre dos nodos. · Suponga que se desea eliminar 5 (inicio de la lista): Entonces recorre se queda en la cabeza y la eliminación se realiza de la siguiente manera: cabeza 5 7 11 anterior recorre Esto es: Si recorre^.info = dato entonces SI recorre = cabeza entonces [se elimina de la cabeza] cabeza recorre.sig · Suponga que desea eliminar 11 (final de la lista): Cabeza 5 7 11 anterior recorre entonces recorre anota al último nodo de la lista y la eliminación se realiza de la siguiente manera: Cabeza 5 7 11 anterior recorre Esto es: Si recorre^.info = dato entoncesProgramación Avanzada 49 SI recorre = cabeza entonces [se elimina de la cabeza] cabeza recorre.sig Sino Si (recorre^.sig = NULO)) entonces [esta al final de la lista] Anterior^.sig NULO · Suponga que desea eliminar 7 (entre dos nodos): Cabeza 5 7 11 anterior recorre Cabeza 5 7 11 Anterior recorre Esto es: Si recorre^.info = dato entonces SI recorre = cabeza entonces [se elimina de la cabeza] cabeza recorre.sig Sino Si (recorre^.sig = NULO)) entonces [esta al final de la lista] anterior^.sig NULO Sino [Esta entre dos nodos] anterior^.sig recorre^.sig pero como el último elemento de la lista apunta a NULO, entonces se puede reducir el algoritmo: Si recorre^.info = dato entoncesProgramación Avanzada 50 SI recorre = cabeza entonces [se elimina de la cabeza] cabeza recorre.sig Sino [si esta al final o entre dos nodos, se recorren las ligas] anterior^.sig recorre^.sig Fin_si B) Llegar al final de la lista sin encontrar el dato a eliminar . Si recorre^.info = dato entonces SI recorre = cabeza entonces [se elimina de la cabeza] cabeza recorre.sig Sino [si esta al final o entre dos nodos, se recorren las ligas] anterior^.sig recorre^.sig Fin_si Sino [llego al final sin encontrar el dato] Escribir “Dato no encontrado” Fin_si Entonces el algoritmo completo queda de la siguiente manera: Eliminación_lista_sencilla(dato) Inicio Si cabeza <> NULO entonces recorrecabeza Mientras(recorre^.sig<>NULO) y (recorre^.info <> dato) hacer anterior recorre recorre recorre^.sig Fin_mientras Si recorre^.info = dato entonces [lo encontro] SI recorre = cabeza entonces [se elimina de la cabeza] cabeza recorreProgramación Avanzada 51 libera_nodo(recorre) Sino [si esta al final o entre dos nodos, se recorren las ligas] anterior^.sig recorre^.sig libera_nodo(recorre) Fin_si Sino [no lo encontro] Escribir “Dato no encontrado” Fin_si Sino Escribir “Lista Vacia” Fin Programa ejemplo Lista Ligada simple /* Programa para el manejo de una lista dinamica Benemerita Universidad Autonoma de Puebla Facultad de Ciencias de la Computacion Abril 2002 */#include #include /* Estructura de un nodo */struct INFO { int dato; struct INFO *sig; }; /* Clase lista ligada */class Listas { struct INFO *raiz; public: void Insertar(int); void Mostrar(void); void Eliminar_todo(void); void Eliminar(int); Listas() { raiz=NULL; } }; /* Definicion de metodos */void Listas::Insertar(int dato) {struct INFO *temp; struct INFO *ant; struct INFO *pos; temp=new (struct INFO); (*temp).dato=dato; if(raiz==NULL) { raiz=temp; (*raiz).sig=NULL; }else { if((*raiz).dato>dato) /* temp.dato */{Programación Avanzada 52 (*temp).sig=raiz; raiz=temp; } else { ant=raiz; pos=(*raiz).sig; while( ((*pos).dato
rate this doc
email this doc
embed this doc
add to folder
digg reddit stumble delicious
flag this doc
722
58
not rated
0
12/24/2007
Spanish
search termpage on Googletimes searched
Preview

programacion logica

arcenal 12/24/2007 | 425 | 50 | 0 | educational
Preview

Programacion logica y funcional incluye Ejemplos en Prolog

arcenal 12/24/2007 | 3302 | 117 | 0 | educational
Preview

Programacion Logica Prolog

arcenal 12/24/2007 | 585 | 55 | 0 |
Preview

Programacion practica en prolog

arcenal 12/24/2007 | 731 | 63 | 0 | educational
Preview

Introduccion a la Programacion Funcional

arcenal 12/22/2007 | 291 | 23 | 0 | educational
Preview

Aprendizaje automatico programacion logica inductiva

arcenal 12/10/2007 | 285 | 37 | 0 | educational
Preview

Tecnicas basicas de programacion en Prolog

arcenal 12/29/2007 | 1182 | 67 | 0 | educational
Preview

MANUAL DE JAVA

arcenal 12/22/2007 | 1522 | 101 | 0 | educational
Preview

Psicoprofilaxis

dagmi19 4/11/2008 | 551 | 6 | 0 | educational
Preview

tutorial de delphi español

arcenal 12/29/2007 | 2433 | 70 | 1 | educational
Preview

Cuaderno de trabajo para la capacitaci n avanzada

EPADocs 5/18/2008 | 128 | 0 | 0 | legal
Preview

Seguridad para los Consumidores de Edad Avanzada

CPSC 6/22/2008 | 23 | 0 | 0 | legal
Preview

Freile Introduccion al lenguage VHDL

arcenal 12/12/2007 | 363 | 14 | 0 | educational
Preview

Cuaderno de trabajo para la capacitaci n avanzada Archivo LEAME

EPADocs 5/18/2008 | 14 | 0 | 0 | legal
Preview

figuras de ejemplos de UML

arcenal 1/2/2008 | 670 | 53 | 0 | educational
Preview

automatas de pila y lenguajesd independientes de contexto

arcenal 8/13/2008 | 21 | 1 | 0 |
Preview

Apuntes de analisis numerico

arcenal 2/9/2008 | 979 | 107 | 3 | educational
Preview

manual de matlab 7 0 español

arcenal 2/9/2008 | 10553 | 357 | 7 | educational
Preview

criptografia y seguridad

arcenal 2/9/2008 | 1570 | 92 | 3 | educational
Preview

criptografia-matematicas

arcenal 2/9/2008 | 1025 | 74 | 0 | educational
Preview

norma rs232

arcenal 2/9/2008 | 2148 | 46 | 0 | educational
Preview

cienematica de un robot

arcenal 2/9/2008 | 834 | 34 | 0 | educational
Preview

topologia

arcenal 1/2/2008 | 204 | 2 | 0 | educational
Preview

Teoria y praxis - EMMANUEL KANT

arcenal 1/2/2008 | 536 | 8 | 0 | educational
Preview

Romero y julieta - Willian Shakespeare

arcenal 1/2/2008 | 1126 | 6 | 0 | educational
 
review this doc