Universidad de los Andes Informe proyecto diseño de algoritmos Grupo S1PY 3 David Arteaga., Código Luis Páez., 200422567 Bogotá Diciembre 1 del 2011 Contenido 1 Algoritmo de solución 1.1 Explicación informal del algoritmo elegido. 1.2 Anotación (contexto, pre- y poscondición) métodos. 2 Análisis de complejidades espacial y temporal 2.1 Complejidad espacial 2.2 Complejidad temporal 3 Representación de datos 3.1Descripción de estructuras de información para guardar los datos de entrada. 4 Arquitectura de la solución java 4.1 Paquetes 4.2 Clases 4.3Ubicación del método main. 5 Comentarios finales 1 Algoritmo de solución 1.1 Explicación informal del algoritmo elegido. El algoritmo de solución que utilizamos consiste en leer de un archivo en el cual se encuentra el nombre de la ecuación, la ecuación a resolver en notación posfija, la incógnita que queremos despejar y el número de pasos máximo para hacerlo. Después de leer este archivo, tomamos la ecuación y la dividimos en sus partes, que son sus variables y operaciones, y los guardamos en un arreglo de Strings con el cual después armamos un árbol binario que representa la ecuación. El árbol de la ecuación se arma leyendo el arreglo de Strings de atrás hacia adelante, quedando el nodo raíz con el elemento “=”, luego los elementos se van insertando como hijos derechos consecutivamente a menos de que sean o una operación binaria, o una operación especial, en cuyo caso se siguen las siguientes reglas: 1. Si es una operación binaria (“+”,“-”,“*”,“/”) se hace un nodo con elemento correspondiente a la operación binaria leída en el arreglo, luego con los dos elementos siguientes a la operación binaria en el arreglo se crean dos nodos con sus valores respectivos y estos se asignan como hijo derecho e hijo izquierdo al nodo que contiene la operación. 2. Si es una operación especial (“2^”,“log”,“^2”,“rz”) se crea el nodo de la operación especial y se le asigna solo un hijo al nodo, en este caso siempre se lo asignamos al hijo derecho del nodo con la operación especial Después de armar el árbol, tomamos la aproximación de apretar el cerco según si la ecuación es lineal, cuadrática o si tiene potencias y logaritmos. Esto lo implementamos en el proyecto al evaluar la ecuación que está en el árbol binario con diferentes métodos, lo que nos permite identificar qué tipo de ecuación es. A continuación se describe brevemente como identificamos cada tipo de ecuación: Verificación ecuación lineal 1. Se verifica que todas las apariciones de la incógnita a resolver en el árbol de la ecuación tengan grado 1, es decir que la incógnita no se encuentre elevada al cuadrado 2. Se verifica que al aplicar tácticas de colección y atracción para la incógnita a resolver en el árbol de la incógnita siga siendo de grado 1 3. Se verifica que en ningún caso algún nodo en el árbol de la ecuación que tenga las operaciones “*” y “/” tengan como hijo izquierdo y derecho la incógnita a resolver. Verificación ecuación cuadrática 1. Se verifica que el único operador especial que puede haber en el árbol en el árbol de la ecuación sea el operador de elevar al cuadrado 2. Se verifica que el árbol de la ecuación se tenga el operador de elevar al cuadrado en un nodo y como su hijo la incógnita a resolver 3. Se verifica si el árbol de la ecuación corresponde a una ecuación de la forma (x +- a)*(x +- b)=0 Verificación ecuación con operaciones especiales 1. Se busca en el árbol de la ecuación un nodo que contenga al menos un operador especial que sea diferente al de elevar al cuadrado. Cuando la ecuación es identificada satisfactoriamente, se empieza a transformar el árbol de la ecuación para llegar a una solución. Para esto nosotros utilizamos algoritmos de agenda en cada caso que van transformando el árbol según la incógnita que se quiera despejar. Como el número de nodos del árbol binario que representa la ecuación en el árbol binario es finito se asegura que el proceso de solucionar la ecuación debe terminar. Para hallar una solución de una ecuación lineal se logra duplicando nodos y/o subárboles dependiendo de la operación que tengan como elemento y se les cambia la operación que tienen por su opuesta según las siguientes reglas: Si se tiene un subárbol con elemento “+” y nodos hijos izquierdo y derecho se suplica el subárbol cambiando el elemento “+” por “-” Si se tiene un subárbol con elemento “-” y nodos hijos izquierdo y derecho se suplica el subárbol cambiando el elemento “-” por “*” Si se tiene un subárbol con elemento “*” y nodos hijos izquierdo y derecho se suplica el subárbol cambiando el elemento “*” por “/” Si se tiene un subárbol con elemento “/” y nodos hijos izquierdo y derecho se suplica el subárbol cambiando el elemento “/” por “*” Luego se añaden los nodos creados a los subárboles derecho e izquierdo de la raíz y aplicando tácticas de colección y atracción se transforma el árbol de forma recursiva de tal forma que el nodo que tiene como elemento a la incógnita a resolver quede de un lado del nodo raíz, y del otro un nodo o un árbol que representa una expresión o un valor. Para una ecuación cuadrática miramos que el árbol de la ecuación tenga la forma de una ecuación igualada a 0(en nuestro caso el hijo derecho debe ser igual a 0), es decir uno de los nodos hijos de la raíz con el elemento “=” es 0, y de allí ya podemos sacar los valores de los coeficientes de la ecuación para resolverlo aplicando la formula cuadrática. En el caso en el que el árbol de la ecuación no cumpla con la condición anteriormente mencionada se aplicaran tácticas de colección y atracción hasta llevar el árbol de la ecuación a la forma igualada a 0. 1.2 Anotación (contexto, pre- y poscondición) métodos. 1. Método que mete la ecuación en un arreglo de strings y llama el método para armar el árbol correspondiente a la ecuación y verifica la ecuación para tratar el árbol armado. public static void resolverEcua(String ecuacion, int limite) Ctx: ecuacion: ecuación en notación posfija, limite es entero Pre: T Pos: se parte el String ecuacion en sus elementos y se mete cada elemento en un arreglo de strings 2. Método que arma el árbol binario correspondiente a la ecuación public static void armarArbol(String[] laEcuacion) Ctx: laEcuacion: array[0..n-1] of D Pre: el arreglo de strings es correcto en notación posfija Pos: se arma un árbol binario que representa la ecuación con los elementos del arreglo de strings 3. Método que hace la recursión utilizada para armar el árbol correspondiente a la ecuación public static void recursion(Nodo nod, String[] ecuacion, int pos ) Ctx: nod: Nodo es un nodo binario que representa el árbol de la ecuación , laEcuacion: array[0..n-1] of D, pos es entero Pre: El nodo raíz tiene como elemento “=” Pos: se encadenan los nodos para formar el árbol binario que representa la ecuación 4. método que realiza una búsqueda de un elemento en el árbol de una ecuación public static boolean buscarElementoArbol(Nodo nod, String buscado) Ctx: nod: Nodo es un nodo binario que representa el árbol de la ecuacion, String es un elemento Pre: el árbol representado por el nodo es correcto Pos: retorna un booleano true encontró el carácter buscado, de lo contrario retorna false 5. Recorrido en Postorden para imprimir la ecuación representada en el árbol public static String imprimirArbol(Nodo nod) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: retorna un String con la ecuacion representada por el arbol 6. Método de "despeje" de "*" public static void despejeMultDivSimple(Nodo nod) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: el árbol es transformado para dejar la incógnita sola como hijo derecho del nodo raíz 7. Método de "despeje" de "+" y "-" public static void despejeMasMenosSimple(Nodo nod) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: el árbol es transformado para dejar la incógnita sola como hijo derecho del nodo raíz 8. Atracción simple R op R' = R'' donde op{+,-,*,/} public static void atraccionSimple(Nodo nodo) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: el árbol es transformado para dejar la incógnita sola como hijo derecho del nodo raíz 9. Resuelve ecuaciones cuadráticas que sean iguales a "0" public static double[] resolverCuadraticaSimple(int aa, int bb, int cc) Ctx:aa,bb,cc son enteris Pre:T Pos: se retorna un arreglo de valores double que contiene el cálculo de las raíces de la incógnita utilizando la formula cuadrática 10. Verifica si la ecuación es de tipo ax^2 + b*x + c = 0 public static boolean verificarCuadratica(Nodo nodo) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: retorna un booleano true si la verificación fue exitosa, de lo contrario retorna false 11. Método que verifica si un árbol que representa una ecuación cuadrática es correcto forma (xopa)*(xopb) donde op{+,-} public static boolean checkFormaCuadritica(Nodo elNodo) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: retorna un booleano true si la verificación fue exitosa, de lo contrario retorna false 12. Método que revisa si se puede "pasar" un más o menos que no involucre la incógnita; del lado izquierdo al lado derecho del árbol public static boolean checkSumaRestaSimple(Nodo nod) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: retorna un booleano true si la verificación fue exitosa, de lo contrario retorna false 13. Método que revisa si se puede pasar una multiplicación o una división puede involucre la incógnita; del lado izquierdo al lado derecho del árbol public static boolean checkMultDivisionSimple(Nodo nod) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: retorna un booleano true si la verificación fue exitosa, de lo contrario retorna false 14. Método que forma una ecuación cuadrática igualada a 0 public static void formarCuadratica(Nodo nod) Ctx: Nodo es un nodo binario que representa el árbol de la ecuación, Pre: el árbol representado por el nodo es correcto Pos: retorna un booleano true si la verificación fue exitosa, de lo contrario retorna false 2 Análisis de complejidades espacial y temporal 2.1 Complejidad espacial Cada ecuación que recibe el programa tiene un número n de elementos (los elementos son números, letras u operadores) En la solución propuesta se utilizan principalmente 2 estructuras de datos, que son un arreglo de Strings y un árbol binario Complejidad del arreglo de Strings: O(n) Complejidad del árbol binario: O(log n) 2.2 Complejidad temporal 1.public static void resolverEcua(String ecuacion, int limite) O(n log n) 2.public static void armarArbol(String[] laEcuacion) O(log n) 3.public static void recursion(Nodo nod, String[] ecuacion, int pos ) O(log n) 4.public static boolean buscarElementoArbol(Nodo nod, String buscado) O(log n) 5.public static String imprimirArbol(Nodo nod) O(n) 6.public static void despejeMultDivSimple(Nodo nod) O(log n) 7.public static void despejeMasMenosSimple(Nodo nod) O(log n) 8.public static void atraccionSimple(Nodo nodo) O(log n) 9.public static double[] resolverCuadraticaSimple(int aa, int bb, int cc) O(1) 10.public static boolean verificarCuadratica(Nodo nodo) O(1) 11.public static boolean checkFormaCuadritica(Nodo elNodo) O(1) 12.public static boolean checkSumaRestaSimple(Nodo nod) O(1) 13.public static boolean checkMultDivisionSimple(Nodo nod) O(1) 14. public static void formarCuadratica(Nodo nod) O(log n) 3 Representación de datos En el proyecto la ecuación la obtenemos de un archivo externo que tiene la siguiente estructura: Línea 1: nombre de la ecuación (ejemplo: “eca”) Línea 2: ecuación en notación posfija (ejemplo: “a x 1 + log x 1 – log + a =”) Línea 3: incógnita la cual se va a despejar (ejemplo “x”) Línea 4: número máximo de pasos para resolver la ecuación (ejemplo “25”) La ecuación luego de ser leída, es dividida en cada uno de sus elementos por el parser, y todos sus elementos son guardados en un Arreglo de Strings, el cual luego será utilizado para armar un árbol binario que representara la ecuación. La estructura del árbol binario que se utilizó se basa en nodos, cuyo contenido es: 1. Un atributo String, que guarda 1 elemento de la ecuación 2. Un atributo Nodo, que modela el hijo derecho del nodo (si no tiene hijo entonces nodo=null) 3. Un atributo Nodo, que modela el hijo izquierdo del nodo (si no tiene hijo entonces nodo=null) 4 Arquitectura de la solución 4.1 Paquetes La solución implementada tiene un solo paquete llamado mundo. 4.2 Clases La solución implementada consta de 2 clases: · La clase Nodo, la cual modela un árbol binario · La clase mundo, la cual es la encargada de leer y hacer las validaciones y transformaciones al árbol de la ecuación para encontrar la solución. 4.3 Ubicación del método main El método main del el proyecto se encuentra en la clase mundo 5 Comentarios finales Como alternativa de estructura de datos se podía haber utilizado una pila en vez de el árbol binario, pero se escogió el árbol binario, ya que se podía modelar la ecuación que nos dan en un árbol sintáctico y con este se tiene mucha más claridad de cómo se pueden hacer los chequeos y los cambios para resolver la ecuación paso por paso. Se intentó también utilizar el algoritmo de Shunting yard, pero para el enfoque que teníamos nos resultaba muy complejo, así que decidimos implementar nuestro propio parser. En la implementación de la solución hay que tener en cuenta muchas reglas de transición y realizar muchos chequeos, y dada la cantidad de casos y reglas que se debían tener en cuenta no fue posible implementar la solución para operaciones especiales. El proyecto se puede extender muchas más funcionalidades de resolución de ecuaciones, como derivadas e integrales y muchas más operaciones, pero como se menciona anteriormente se tendrían que implementar nuevos chequeos y reglas por cada operación que se habilite.