Del básico al intermedio: Colas, listas y árboles (V)
Introducción
En el artículo anterior, Del básico al intermedio: Colas, listas y árboles (IV), implementamos una lista simplemente enlazada o doblemente enlazada. Aunque implementamos una lista enlazada, podemos mejorar diversos aspectos de ese modelo de código y simplificar considerablemente el uso de lo explicado. Con estas modificaciones, la lista enlazada resulta mucho más natural y el código es más fácil de leer e interpretar; al menos, así lo entiendo yo.
Para mejorar el código, debemos utilizar algunos recursos de MQL5 que todavía no he comentado. Como no quiero complicar aún más algo que, para muchos, ya resulta bastante confuso y difícil de comprender, mostraré más adelante cómo aplicar esos cambios a una lista enlazada.
Por ahora, concentrémonos en nuestro tema principal: entender las características y las diferencias entre las colas, las listas y los árboles. Como ya nos acercamos al último punto que trataremos aquí, puedo analizar algunos aspectos interesantes de estas estructuras de datos. Para ello, nos centraremos en el tema principal de esta breve serie de artículos.
Colas, listas y árboles (V)
Muy bien, hemos llegado a nuestro quinto artículo, dedicado a explicar las colas, las listas y los árboles. Antes de abordar el problema central de este artículo, hagamos una breve revisión de lo que hemos visto hasta ahora.
Primero vimos qué eran las colas, cómo podían implementarse y qué ventajas ofrecían para organizar determinados tipos de datos o incluso eventos en general. Sin embargo, las colas tienen una gran desventaja: no podemos insertar nuevos elementos ni eliminar elementos existentes de forma arbitraria. Es decir, si deseas eliminar algo que se encuentra en medio de una cola, no tendrás forma de hacerlo, al menos no de manera rápida y eficiente. Tampoco es muy rápido añadir nuevos elementos en medio de una cola.
Por esta razón, se creó otro tipo de estructura de datos: la lista enlazada. Una lista enlazada puede ser de dos tipos: simplemente enlazada o doblemente enlazada. Los punteros vinculan los elementos, algo que no ocurre cuando utilizamos colas, y permiten añadirlos o eliminarlos con gran eficiencia en cualquier posición de la lista. Conviene recordar que, en muchos casos, una lista funciona como una cola mejorada y sirve para los mismos fines, aunque requiere un poco más de memoria que una cola con el mismo número de elementos.
Bien, si la lista resuelve el problema de las colas e incluso puede sustituirlas en muchas situaciones, ¿por qué necesitamos los árboles? ¿Dónde demonios encajan los árboles en toda esta historia? Pues bien, mi querido lector, los árboles son la evolución natural de las listas y tienen un propósito aún más noble. De acuerdo, pero todavía no lo entiendo. ¿Podrías explicarlo mejor antes de que empecemos a ver el código? Claro que sí. Creo que entender el propósito de los árboles antes de ver el código te ayudará a comprender el propio código. Para entender los árboles, necesitas comprender un problema de las listas. Y sí, aunque resuelven el problema de las colas, las propias listas tienen sus problemas. Por esta razón, se creó otra estructura de datos que permite buscar elementos con mayor eficiencia. ¿No lo entiendes? ¿Cómo es eso?
Para entenderlo, piensa en listas con miles, quizá miles de millones de elementos, una cantidad que para la mayoría de las personas puede resultar impensable. Sin embargo, incluso las listas con algunos miles de elementos pueden plantear problemas al buscar. El principal problema es el tiempo que requiere la búsqueda.
Piensa en lo siguiente: decides colocar un diccionario completo en una lista para crear un corrector de textos e implementas un sistema que busca desde la letra A y avanza palabra por palabra hasta la última palabra de la letra Z. A primera vista, este método parece práctico y viable. Sin embargo, en la práctica no es así, porque encontrar determinadas palabras puede llevar bastante tiempo. Ahora bien, si modificas la implementación para que unas veces la búsqueda comience por la letra A y otras por la letra Z, en el peor de los casos tendrás que recorrer la mitad de la lista hasta encontrar la palabra buscada. Esta mejora puede parecer muy positiva y, de hecho, puede serlo. No obstante, podemos mejorarlo aún más. Si, en lugar de comenzar unas veces por el principio y otras por el final, pudiéramos empezar por el centro de la lista, el número de elementos que habría que examinar durante la búsqueda sería mucho menor, incluso en una lista bastante grande. En este caso, tendríamos que recorrer aproximadamente una cuarta parte de la lista, ya que, según la posición elegida como centro, puede haber más o menos elementos a uno u otro lado.
Creo que ahora has empezado a entender qué dio origen a los árboles. En teoría, una lista no constituye un árbol. Sin embargo, en la práctica, una lista con pocos elementos distribuidos de una determinada manera sí constituye un árbol, aunque sea un árbol desequilibrado.
Ahora debes comprender un aspecto muy importante, mi querido lector. Existen diversos tipos de árboles y cada uno sirve para un propósito específico. Según su objetivo, un árbol puede diferir mucho del que veremos aquí, ya que explicar todos los tipos y sus posibles implementaciones requeriría una gran cantidad de artículos. Como aquí solo pretendo presentar esta estructura de datos para utilizarla más adelante, cada lector deberá investigar los demás tipos de árboles y determinar cuándo conviene utilizar cada uno. Aquí mostraré y explicaré un modelo genérico de árbol. Sin embargo, como mencioné hace poco, te sugiero que estudies este tema y busques otros modelos de implementación de árboles. Según el problema que debas resolver, probablemente encuentres un modelo más adecuado.
Bien, comencemos con la siguiente representación teórica de un árbol muy simple.

Imagen 01
Qué cosa tan extraña. Creo que ya he visto algo parecido a esta imagen 01. En efecto, mi querido lector, esta imagen 01, que representa un árbol en teoría, puede verse en otros temas relacionados con la programación. Muy probablemente ya la hayas visto si has estudiado estructuras de datos aplicadas a la inteligencia artificial. Sin embargo, en este ámbito suele utilizarse otra estructura de datos aún más avanzada que los árboles, conocida precisamente como GRAFOS. Por ahora, no trataremos los GRAFOS, ya que tendríamos que adentrarnos en un enfoque y una metodología que, en mi opinión, muchos todavía no están preparados para comprender.
No obstante, podemos implementar una inteligencia artificial con árboles sin profundizar en los grafos. Para ser sincero, es perfectamente posible hacerlo. Pero, como apenas comenzamos a tratar el tema, avancemos paso a paso.
Muy bien, hay un problema de clasificación muy importante que debes entender, directamente relacionado con esta misma imagen 01. El problema es el siguiente: al observar esta imagen 01, y después de haber visto qué es una cola y qué es una lista, tú, mi querido lector, podrías cuestionar mi afirmación de que la estructura representada en esta imagen 01 es un árbol. Y no te equivocarías; al contrario, demostrarías que has entendido perfectamente las características de las colas y las listas, ya que la estructura representada en esta misma imagen 01 podría implementarse mediante estructuras de datos que no están necesariamente vinculadas a una implementación arbórea. Bien, entonces, ¿qué me dirías sobre la imagen que se muestra a continuación?

Imagen 02
Ahora sí tenemos un árbol. En realidad, todavía no, mi querido lector. Al igual que la estructura representada en la imagen 01, la de la imagen 02 también puede implementarse con una estructura de datos que no sea arbórea. Ahora presta atención: estas dos imágenes muestran un problema que muchos principiantes no advierten: suelen buscar soluciones mucho más complicadas para problemas relativamente simples. Necesitamos implementar un árbol cuando, al modelar los datos o elementos analizados, obtenemos una estructura como la representada en la siguiente imagen 03.

Imagen 03
Ahora redobla tu atención, porque esto te ayudará a entender lo que implementaremos. Observa que, en las dos primeras imágenes, había una sola línea que conectaba cada uno de sus elementos. Aunque existen árboles construidos de esa manera, eso también puede implementarse mediante estructuras de datos más simples, como una cola o incluso una lista. Sin embargo, la estructura representada en la imagen 03 es diferente: en un momento determinado aparece una bifurcación que nos permite seguir otro camino sin mover los demás elementos de su ubicación correspondiente. Y es precisamente esta bifurcación la que da origen al árbol. Normalmente, en implementaciones simples como la que veremos aquí, solo tenemos dos caminos posibles. Sin embargo, en algunos sistemas, como cuando se utiliza inteligencia artificial, puede haber varios caminos que parten de un único elemento. Pero seguimos trabajando con árboles.
Para comprenderlo y explicarlo con más facilidad, utilizaremos algunos términos. Cuando tenemos un punto en el que hay algún elemento, llamaremos a ese punto node, o nodo. Al camino que seguimos hacia otro elemento lo llamaremos rama. Existe además otro término para designar el elemento inicial, o nodo inicial, de un árbol. Este elemento recibe un nombre especial: raíz.
Ahora, antes de ver cualquier código, analicemos otro aspecto de la estructura. Los términos nodo y rama son simples y prácticos. Sin embargo, no ocurre lo mismo con la raíz. Definir uno u otro nodo como raíz cambia por completo las características de un árbol. Muchas veces, cambiar el nodo raíz agiliza considerablemente la búsqueda en el árbol. En otros casos, no nos interesa la velocidad de búsqueda, sino clasificar los elementos. De ahí que existan implementaciones de inteligencia artificial que utilizan árboles. Pero, nuevamente, avancemos con calma. Todavía es pronto para hablar de ello.
Como el objetivo es didáctico y la imagen 03 ya muestra nuestro primer diagrama de un árbol, podemos implementar en código la estructura que representa. Recuerda que aquí mostraré cómo hacerlo de la forma más genérica posible.
Para empezar, al menos en un principio, un árbol no es muy diferente de una lista doblemente enlazada. Pronto, mi querido lector, podrás entenderlo mejor. Por lo tanto, podemos comenzar con el código que vimos en el artículo anterior, reproducido a continuación.
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. template <typename T> class stList 005. { 006. private: 007. //+----------------+ 008. T info; 009. stList <T> *prev, 010. *start; 011. uint counter; 012. //+----------------+ 013. public: 014. //+----------------+ 015. stList(void) 016. :prev(NULL), 017. start(NULL), 018. counter(0) 019. {} 020. //+----------------+ 021. void Store(T arg, const uint index = 0xFFFFFFFF) 022. { 023. stList <T> *loc, 024. *ptr1 = start, 025. *ptr2 = NULL; 026. 027. for (uint c = 0; (ptr1 != NULL) && (c < index); ptr2 = ptr1, ptr1 = (*ptr1).start, c++); 028. 029. loc = new stList <T>; 030. (*loc).info = arg; 031. (*loc).start = (ptr2 != NULL ? (*ptr2).start : ptr1); 032. (*loc).prev = (ptr1 != NULL ? (*ptr1).prev : ptr2); 033. if (ptr2 != NULL) (*ptr2).start = loc; else start = loc; 034. if (ptr1 != NULL) (*ptr1).prev = loc; else prev = loc; 035. 036. counter++; 037. } 038. //+----------------+ 039. bool Restore(T &arg, const uint index = 0xFFFFFFFF) 040. { 041. if ((prev == NULL) || (start == NULL)) 042. return false; 043. 044. stList <T> *loc = (index < counter ? start : prev), 045. *ptr = NULL; 046. 047. for (uint c = 0; (loc != NULL) && (c < index) && (index < counter); ptr = loc, loc = (*loc).start, c++); 048. if (loc == NULL) return false; 049. 050. if (index == 0) 051. { 052. start = (*loc).start; 053. if (start != NULL) (*start).prev = NULL; 054. } else if (index >= (counter - 1)) 055. { 056. prev = (*loc).prev; 057. if (prev != NULL) (*prev).start = NULL; 058. } 059. else 060. { 061. (*ptr).start = (*loc).start; 062. (*loc).start.prev = ptr; 063. } 064. arg = (*loc).info; 065. delete loc; 066. counter--; 067. 068. return true; 069. } 070. //+----------------+ 071. bool Exclude(const uint index) 072. { 073. T tmp; 074. 075. return Restore(tmp, index); 076. } 077. //+----------------+ 078. void Debug(void) 079. { 080. Print("===== DEBUG ====="); 081. for (stList <T> *loc = start; loc != NULL; loc = (*loc).start) 082. PrintFormat("0x%06X ->> 0x%06X <<- 0x%06X = [%d]", (*loc).start, loc, (*loc).prev, (*loc).info); 083. Print("================="); 084. } 085. //+----------------+ 086. }; 087. //+------------------------------------------------------------------+ 088. void OnStart(void) 089. { 090. stList <char> list; 091. 092. list.Store(10); 093. list.Store(84); 094. list.Store(-6); 095. list.Store(47, 0); 096. 097. list.Debug(); 098. 099. list.Exclude(3); 100. list.Store(35, 2); 101. 102. list.Debug(); 103. 104. for (char info; list.Restore(info, 0);) 105. Print(info); 106. }; 107. //+------------------------------------------------------------------+
Código 01
Evidentemente, este código 01 implementa una lista doblemente enlazada. Sin embargo, si modificamos ligeramente este mismo código, podremos implementar el árbol que se muestra en la imagen 03. Modificarlo es sencillo para los programadores con experiencia, pero resulta más difícil para los principiantes o quienes tienen poca experiencia, como probablemente sea el caso de muchos de ustedes. Y, por favor, no te sientas mal al leer esto, mi querido lector, ya que yo mismo, en algún momento, estuve en la misma situación en la que tú te encuentras ahora. Sin embargo, con estudio y bastante tiempo de práctica, alcancé un buen nivel de conocimientos.
Como transformar este código 01, que implementa una lista doblemente enlazada, en un árbol exige comprender diversos aspectos que todavía no se han explicado en esta serie, seguiremos otro enfoque. Así, mantendremos el carácter didáctico de la explicación.
Por lo tanto, partiremos de una implementación muy sencilla y funcional, aunque todavía no admita todas las operaciones de un árbol. Este código se muestra a continuación.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stTree 05. { 06. public: 07. int info; 08. stTree *left, 09. *right; 10. }; 11. //+------------------------------------------------------------------+ 12. stTree *store(stTree *root, stTree *r, int info) 13. { 14. if (r == NULL) 15. { 16. r = new stTree; 17. 18. (*r).left = NULL; 19. (*r).right = NULL; 20. (*r).info = info; 21. if (root == NULL) return r; 22. if (info < (*root).info) (*root).left = r; 23. else (*root).right = r; 24. 25. return r; 26. } 27. if (info < (*r).info) return store(r, (*r).left, info); 28. return store(r, (*r).right, info); 29. } 30. //+------------------------------------------------------------------+ 31. void inorder(stTree *root) 32. { 33. if (root == NULL) return; 34. 35. inorder((*root).left); 36. if (root != NULL) Print((*root).info); 37. inorder((*root).right); 38. } 39. //+------------------------------------------------------------------+ 40. void OnStart(void) 41. { 42. stTree *root = NULL; 43. 44. root = store(root, root, 10); 45. store(root, root, -6); 46. store(root, root, 47); 47. store(root, root, 35); 48. store(root, root, 85); 49. 50. inorder(root); 51. } 52. //+------------------------------------------------------------------+
Código 02
No tienes que inquietarte por este código 02, mi querido lector. Sé que probablemente estés un poco asustado por la forma en que funciona. Sin embargo, aquí no hacemos nada realmente nuevo. Quizá el código solo muestre esos elementos de una manera un poco diferente. Todas las partes que aparecen en este código 02 ya se explicaron en otros artículos de esta misma serie. Cuando lo ejecutes, verás en el terminal de MetaTrader 5 lo que se muestra a continuación.

Imagen 04
En esta imagen 04, destaco dos datos. El dato en amarillo es una advertencia de MetaTrader 5 que indica que la memoria asignada mediante la instrucción de la línea 16 del código 02 no se devuelve al sistema. Sin embargo, por ahora podemos convivir con estas advertencias, ya que no afectan en absoluto al resultado obtenido. El dato resaltado en verde es el que realmente nos interesa, porque corresponde al resultado generado al ejecutar el código que implementa el árbol representado en la imagen 03.
Pero espera un momento. ¿Cómo es eso? En la imagen 03 vemos un árbol, pero esta zona verde destacada en la imagen 04 me parece una lista de valores. En efecto, es cierto, mi querido lector. Sin embargo, existe una forma de demostrar que aquí no trabajamos con una lista, sino con un árbol. Podemos demostrarlo recorriendo el árbol en distintos órdenes sin demasiado esfuerzo. Pero, antes de demostrarlo, entendamos rápidamente cómo funciona este código 02. Si observas, verás que, dentro del procedimiento OnStart, utilizamos una función para construir la estructura del árbol y un procedimiento para recorrerla y mostrar sus valores.
Pero ¿cómo funciona todo esto? Para empezar, observa la declaración de la estructura en la línea cuatro. Presta atención, porque, aunque aquí utilizamos la palabra class, debes entenderla como una struct. Observa que tenemos dos punteros: uno para la rama derecha y otro para la izquierda. Podríamos tener muchos más, pero aquí lo abordaremos de forma básica y sencilla. Una vez definida esta estructura, muy similar a la del código 01, podemos implementar en la cláusula private de la línea seis la función que crea el árbol, declarada en la línea 12 del código 02.
Esta función es recursiva. En el artículo Del básico al intermedio: Recursividad, expliqué qué es la recursividad. Las llamadas recursivas se encuentran precisamente en las líneas 27 y 28. El bloque de la sentencia if de la línea 14 asigna memoria e inserta nuevos nodos en el árbol. Básicamente, este bloque de código funciona de manera muy similar al bloque comprendido entre las líneas 29 y 34 del código 01. Sin embargo, aquí, en el código 02, las instrucciones comprendidas entre las líneas 14 y 26 crean e insertan un nuevo nodo en el árbol. Pero presta atención a un detalle. A diferencia de lo que se hacía en la lista enlazada, donde indicábamos la posición de inserción del elemento, aquí comparamos el valor que se insertará con los valores almacenados en los nodos existentes del árbol. Esto permite construir un árbol binario de búsqueda.
Mmm, creo que lo entiendo. Por eso dijiste que, al implementar un árbol, debíamos definir qué queríamos conseguir. Por eso, cuando recorremos el árbol, vemos los datos en el orden correcto.
Bien, es más o menos eso, mi querido lector. Sin embargo, los elementos del árbol no están necesariamente ordenados de la forma que imaginas. En realidad, el procedimiento de lectura, declarado en la línea 31, devuelve los elementos en orden ascendente al recorrer el árbol. Pero espera un momento. ¿Cómo es eso? Ahora sí que estoy completamente confundido. Acabas de decir que la función de la línea 12 ordena los elementos a medida que se construye el árbol. En efecto, al observar las condiciones, las asignaciones y las llamadas recursivas de las líneas 22, 23, 27 y 28, puedo notar que los elementos con un valor menor quedarán en las ramas de la izquierda, mientras que los elementos con un valor mayor se dirigirán a la rama de la derecha. Imaginé que esto ya sería suficiente para ordenar los elementos dentro del árbol. Pero ahora dices que el procedimiento de recorrido devuelve los elementos ordenados. Para mí, esto no tiene el menor sentido.
Calma, mi querido lector, pronto lo entenderás. Pero antes observa esto: el procedimiento recursivo, declarado en la línea 31, comenzará en el nodo raíz y buscará el elemento situado más a la izquierda hasta que ya no encuentre ninguno. La llamada recursiva de la línea 35 continúa la búsqueda en el subárbol izquierdo. Cuando esa llamada retorne, imprimiremos el valor del nodo actual. A continuación, iniciaremos una nueva búsqueda, pero esta vez hacia la derecha, mediante la llamada recursiva de la línea 37. Al entrar en el subárbol derecho, el procedimiento volverá a ejecutar la llamada de la línea 35, lo que permitirá inspeccionar una nueva rama a la izquierda.
Aquí recorremos el árbol y examinamos sus elementos en un orden determinado. Al construir el árbol, colocamos los elementos de menor valor a la izquierda de los de mayor valor y establecemos una relación de orden que solo percibimos al recorrerlo de una manera determinada.
Sin embargo, si cambiamos la forma de recorrer el árbol, podemos obtener un resultado completamente diferente. Para comprobarlo, veamos el siguiente código.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class stTree 05. { 06. public: 07. int info; 08. stTree *left, 09. *right; 10. }; 11. //+------------------------------------------------------------------+ 12. stTree *store(stTree *root, stTree *r, int info) 13. { 14. if (r == NULL) 15. { 16. r = new stTree; 17. 18. (*r).left = NULL; 19. (*r).right = NULL; 20. (*r).info = info; 21. if (root == NULL) return r; 22. if (info < (*root).info) (*root).left = r; 23. else (*root).right = r; 24. 25. return r; 26. } 27. if (info < (*r).info) return store(r, (*r).left, info); 28. return store(r, (*r).right, info); 29. } 30. //+------------------------------------------------------------------+ 31. string inorder(stTree *root) 32. { 33. static string sz0 = "In Order: "; 34. 35. if (root == NULL) return sz0; 36. 37. inorder((*root).left); 38. sz0 += (root != NULL ? StringFormat("%d ", (*root).info) : ""); 39. inorder((*root).right); 40. 41. return sz0; 42. } 43. //+------------------------------------------------------------------+ 44. string preorder(stTree *root) 45. { 46. static string sz0 = "Pre Order: "; 47. 48. if (root == NULL) return sz0; 49. 50. sz0 += (root != NULL ? StringFormat("%d ", (*root).info) : ""); 51. preorder((*root).left); 52. preorder((*root).right); 53. 54. return sz0; 55. } 56. //+------------------------------------------------------------------+ 57. string postorder(stTree *root) 58. { 59. static string sz0 = "Post Order: "; 60. 61. if (root == NULL) return sz0; 62. 63. postorder((*root).left); 64. postorder((*root).right); 65. sz0 += (root != NULL ? StringFormat("%d ", (*root).info) : ""); 66. 67. return sz0; 68. } 69. //+------------------------------------------------------------------+ 70. void OnStart(void) 71. { 72. stTree *root = NULL; 73. 74. root = store(root, root, 10); 75. store(root, root, -6); 76. store(root, root, 47); 77. store(root, root, 35); 78. store(root, root, 85); 79. 80. Print(inorder(root)); 81. Print(preorder(root)); 82. Print(postorder(root)); 83. } 84. //+------------------------------------------------------------------+
Código 03
Ahora presta mucha, muchísima atención, mi querido lector. Observa que el código 03 no ha cambiado respecto al código 02. Sin embargo, añadimos dos nuevas funciones para recorrer el árbol de manera diferente y visualizar su contenido. Estas funciones se declaran en las líneas 44 y 57. Como imprimiremos una gran cantidad de datos, modifiqué ligeramente el código para que devuelva una cadena y así facilitar su lectura.
En este caso, observa el procedimiento OnStart. Sigue siendo prácticamente igual que en el código 02. Sin embargo, cuando ejecutamos este código 03, obtenemos el resultado que se muestra en la siguiente imagen.

Imagen 05
Nuevamente, la parte que nos interesa aparece resaltada en verde en la imagen 05. Observa que, aunque construyamos el árbol de la misma manera, el resultado dependerá tanto del tipo de recorrido como del orden en que insertemos los valores. Pero ¿cómo es eso? No he entendido lo que acabas de decir. Calma, mi querido lector. Ten paciencia, ya llegaremos a ello. Primero, analicemos la información de la imagen 05. Observa que contiene una línea con los valores en orden ascendente, generada por la llamada contenida en la instrucción de la línea 80 del código 03. También tenemos los valores en preorden y una línea en la que aparecen en posorden. Cada una de estas líneas corresponde a un recorrido diferente del árbol. Para explicar por qué el orden de inserción hace que estos recorridos generen secuencias diferentes, modificaremos un pequeño detalle del código 03. Este detalle se muestra en el siguiente fragmento.
. . . 69. //+------------------------------------------------------------------+ 70. void OnStart(void) 71. { 72. stTree *root = NULL; 73. 74. root = store(root, root, 10); 75. store(root, root, -6); 76. store(root, root, 85); 77. store(root, root, 35); 78. store(root, root, 47); 79. 80. Print(inorder(root)); 81. Print(preorder(root)); 82. Print(postorder(root)); 83. } 84. //+------------------------------------------------------------------+
Fragmento 01
Ahora observa el resultado que se muestra a continuación.

Imagen 06
Es para morirse de risa. Observa que el cambio en el código es muy sutil, prácticamente imperceptible. Sin embargo, el resultado puede ser muy diferente de lo esperado. Sufrí mucho cuando empezaba a programar y me encontraba con este tipo de situaciones. Muchos no se dan cuenta de esto o no lo entienden. Pero, si comparas los elementos que genera el recorrido indicado en la línea Pre Order, observarás que aparecen exactamente en el orden en que los añadimos. Una locura, ¿verdad? Por eso mencioné al principio del artículo que conviene estudiar los árboles con mucha calma y atención.
No creas que ya has entendido el tema solo porque sabes implementar uno u otro código. Este tipo de estructura de datos es realmente muy interesante y tiene numerosas aplicaciones en diferentes áreas y campos relacionados con el análisis de datos.
Consideraciones finales
En este artículo, implementamos los primeros componentes de una estructura arbórea. Como sé que esta estructura puede resultar extremadamente compleja cuando se empieza a aprender, no profundizaré más en ella en este artículo. Lo veremos poco a poco, aunque al final solo implementemos y demostremos una estructura arbórea básica y genérica.
Sin embargo, quiero despertar tu curiosidad, mi querido lector, para que comprendas que existen estructuras de datos sencillas que permiten operar con gran rapidez y obtener resultados concretos. Para conseguirlo, necesitas entender muy bien cómo funciona un árbol. Así podrás comprender una estructura de datos para inteligencia artificial que implementaremos próximamente: será muy divertida y sencilla, y su funcionamiento resultará realmente sorprendente.
Por lo tanto, estudia con calma este artículo y los anteriores para asimilar plenamente las estructuras y los procedimientos descritos aquí. En el próximo artículo implementaremos más componentes de esta estructura arbórea genérica.
| MQ5 | Descripción |
|---|---|
| Código 01 | Árbol simple |
| Código 02 | Árbol simple |
| Código 03 | Árbol simple |
Traducción del portugués realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/pt/articles/16710
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Redes neuronales en el trading: Modelo multidimensional de extremo a extremo para la previsión de series temporales (Final)
Simulación de mercado: Position View (XII)
Particularidades del trabajo con números del tipo double en MQL4
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 28): Herramienta de ruptura del rango de apertura
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso