Español Português
preview
От начального до среднего уровня: Очереди, списки и деревья (V)

От начального до среднего уровня: Очереди, списки и деревья (V)

MetaTrader 5Примеры |
76 0
CODE X
CODE X

Введение

В предыдущей статье От начального до среднего уровня: Очереди, списки и деревья (IV) мы реализовали односвязный или двусвязный список. Хотя мы и реализовали связный список, мы можем улучшить различные аспекты этой модели кода и значительно упростить использование того, что объяснялось. Благодаря этим изменениям связный список стал выглядеть гораздо естественнее, а код — проще для чтения и интерпретации; по крайней мере, я так считаю.

Для улучшения кода нам необходимо использовать некоторые ресурсы MQL5, о которых я ещё не упоминал. Поскольку я не хочу ещё больше усложнять то, что для многих и так довольно запутанно, я позже покажу, как применить данные изменения к связному списку.

А теперь давайте сосредоточимся на нашей главной теме: понимании характеристик и различий между очередями, списками и деревьями. Поскольку мы уже приближаемся к последнему пункту, который мы здесь рассмотрим, я могу проанализировать некоторые интересные аспекты этих структур данных. Для этого мы сосредоточимся на основной теме этой короткой серии статей.


Очереди, списки и деревья (V)

Итак, мы добрались до пятой статьи, посвященной очередям, спискам и деревьям. Прежде чем перейти к главной проблеме сегодняшней статьи, давайте кратко повторим то, что мы уже рассмотрели.

Сначала мы увидели, что такое очереди, как они могут быть реализованы и какие преимущества они дают для организации определённых типов данных или даже событий в целом. Однако у очередей есть один существенный недостаток: мы не можем произвольно вставлять новые элементы или удалять существующие. Иными словами, если хотите удалить элемент, находящийся в середине очереди, вы не сможете этого сделать, по крайней мере, быстро и эффективно. Добавить новые элементы в середину очереди также получится не очень быстро.

Поэтому был создан ещё один тип структуры данных: связный список. Связный список может быть двух типов: односвязный или двусвязный. Указатели связывают элементы, чего не происходит при использовании очередей, и позволяют добавлять или удалять их с высокой эффективностью в любой позиции списка. Стоит помнить, что во многих случаях список функционирует как улучшенная очередь и выполняет те же функции, хотя и требует немного больше памяти, чем очередь с тем же количеством элементов.

Итак, если список решает проблему очередей и даже может заменить их во многих ситуациях, зачем нам нужны деревья? А какое место во всем этом они занимают? Что ж, уважаемый читатель, деревья — это естественная эволюция списков, и у них есть ещё более важное назначение. "Хорошо, но я всё ещё не понимаю. Не могли бы вы объяснить это подробнее, прежде чем мы начнём рассматривать код?" Конечно могу. Я думаю, что понимание назначения деревьев перед тем, как смотреть код, поможет вам понять сам код. Чтобы понять деревья, нужно понять проблему списков. Да, и хотя они решают проблему очередей, сами списки имеют свои проблемы. По этой причине была создана другая структура данных, позволяющая выполнять поиск элементов с более высокой эффективностью. Не понимаете? Как это возможно?

Чтобы понять это, представьте списки с тысячами, возможно, миллиардами элементов — количество, которое для большинства людей может показаться немыслимым. Однако даже списки, содержащие несколько тысяч элементов, могут создавать проблемы при поиске. Основная проблема заключается во времени, которое требует поиск.

Подумайте о следующем: вы решаете поместить полный словарь в список, чтобы создать средство проверки текста, и реализуете систему, которая выполняет поиск, начиная с буквы А и продвигаясь слово за словом до последнего слова на букву Z. На первый взгляд, данный метод кажется практичным и жизнеспособным. Однако на практике это не так, потому что поиск определённых слов может занимать довольно много времени. Теперь, если вы измените реализацию так, чтобы поиск иногда начинался с буквы А, а иногда с буквы Z, то в худшем случае вам придётся пройти половину списка, прежде чем будет найдено искомое слово. Данное улучшение может показаться очень позитивным, и, возможно, так оно и есть. Однако мы можем улучшить его ещё больше. Если бы вместо того, чтобы начинать иногда с начала, а иногда с конца, мы могли бы начинать с середины списка, то количество элементов, которые пришлось бы проверять в ходе поиска, было бы значительно меньше, даже в довольно большом списке. В этом случае нам пришлось бы пройти примерно четверть списка, поскольку в зависимости от выбранной позиции в качестве центра по ту или иную сторону может оказаться больше или меньше элементов.

Я думаю, теперь вы начали понимать, что послужило причиной появления деревьев. Теоретически, список не образует дерево. Однако на практике список с небольшим количеством элементов, распределённых определённым образом, действительно представляет собой дерево, хотя и несбалансированное.

Теперь вы должны понять очень важный аспект, уважаемый читатель. Существуют различные типы деревьев, и каждый из них служит для определённой цели. В зависимости от цели дерево может сильно отличаться от того, которое мы рассмотрим здесь, поскольку объяснение всех типов и их возможных реализаций потребовало бы большого количества статей. Поскольку здесь я лишь намерен представить эту структуру данных для использования в дальнейшем, каждому читателю придётся самостоятельно изучить остальные типы деревьев и определить, когда целесообразно применять каждый из них. Здесь я покажу и объясню обобщённую модель дерева. Однако, как я недавно упоминал, я рекомендую вам изучить эту тему и поискать другие модели реализации деревьев. В зависимости от задачи, которую вам предстоит решить, вы, вероятно, найдёте более подходящую модель.

Хорошо, давайте начнём со следующего теоретического представления очень простого дерева.

Изображение 01

"Как странно. Мне кажется, я уже видел что-то похожее на это изображение 01". Действительно, уважаемый читатель, это изображение 01, которое теоретически представляет собой дерево, можно увидеть и в других темах, связанных с программированием. Вы уже, вероятно, встречали подобную схему, если изучали структуры данных, применяемые в искусственном интеллекте. Однако в этой области обычно используется другая, ещё более продвинутая структура данных, чем деревья, известная как ГРАФЫ. Пока мы не будем рассматривать ГРАФЫ, поскольку для этого пришлось бы углубиться в подход и методологию, которые, на мой взгляд, многие ещё не готовы понять.

Тем не менее, мы можем реализовать искусственный интеллект с помощью деревьев, не углубляясь в графы. Честно говоря, это вполне возможно сделать. Но поскольку мы только начали обсуждать данную тему, давайте двигаться шаг за шагом.

Хорошо, вам нужно понять очень важную задачу классификации, напрямую связанную с изображением 01. Проблема в следующем: рассматривая изображение 01 и после того, как вы поняли, что такое очередь и что такое список, вы можете усомниться в моем утверждении, что структура, представленная на этом изображении 01, является деревом. И вы бы не ошиблись; напротив, вы показали бы, что прекрасно поняли характеристики очередей и списков, поскольку структура, представленная на этом же изображении 01, может быть реализована с помощью структур данных, которые не обязательно связаны с древовидной реализацией. Хорошо, тогда что бы вы сказали об изображении, представленном ниже?

Изображение 02

Теперь у нас есть дерево. На самом деле, пока нет, уважаемый читатель. Так же, как и структура, представленная на изображении 01, структура на изображении 02 также может быть реализована с помощью структуры данных, не являющейся древовидной. А теперь обратите внимание: на этих двух изображениях показана проблема, которую многие новички не замечают: они часто ищут гораздо более сложные решения для относительно простых задач. Нам нужно реализовать дерево, когда при моделировании данных или анализируемых элементов мы получаем структуру, подобную представленной на следующем изображении 03:

Изображение 03

Теперь будьте вдвойне внимательны, потому что это поможет вам понять то, что мы будем реализовывать. Обратите внимание, что на первых двух изображениях была только одна линия, соединяющая каждый из их элементов. Хотя существуют деревья, построенные таким образом, их можно реализовать и с использованием более простых структур данных, таких как очередь или даже список. Однако структура, представленная на изображении 03, отличается: в определённый момент появляется разветвление, которое позволяет нам пойти по другому пути, не перемещая остальные элементы с их соответствующих позиций. Именно данное разветвление и приводит к образованию дерева. Обычно в простых реализациях, подобных той, которую мы рассмотрим здесь, у нас есть только два возможных пути. Но в некоторых системах, например, при использовании искусственного интеллекта, может существовать несколько путей, начинающихся от одного элемента. Но мы продолжаем работать с деревьями.

Чтобы легче понять и объяснить это, мы воспользуемся некоторыми терминами. Если в точке находится какой-либо элемент, мы будем называть эту точку узлом. Путь, по которому мы движемся к другому элементу, мы будем называть ветвью. Существует также другой термин для обозначения начального элемента, или начального узла, дерева. Данный элемент имеет особое название: корень.

Прежде чем рассматривать код, давайте проанализируем ещё один аспект структуры. Термины узел и ветвь просты и практичны. Однако это не относится к корню. Определение того или иного узла в качестве корня полностью меняет характеристики дерева. Зачастую изменение корневого узла значительно ускоряет поиск в дереве. В других случаях нас интересует не скорость поиска, а классификация элементов. Отсюда и существование реализаций искусственного интеллекта, использующих деревья. Но, опять же, давайте будем действовать спокойно. Ещё слишком рано об этом говорить.

Поскольку цель является учебной и изображение 03 уже показывает нашу первую диаграмму дерева, мы можем реализовать в коде структуру, которую представляет это изображение. Помните, что здесь я покажу, как сделать это в максимально общем виде.

Для начала, по крайней мере на первых порах, дерево не сильно отличается от двусвязного списка. Вскоре вы сможете лучше это понять. Поэтому мы можем начать с кода, который мы видели в предыдущей статье, и он воспроизведён ниже.

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. //+------------------------------------------------------------------+

Код 01

Очевидно, что код 01 реализует двусвязный список. Но, если мы немного изменим этот же код, мы сможем реализовать дерево, показанное на изображении 03. Внести в него изменения несложно для опытных программистов, но это сложнее для новичков или тех, у кого мало опыта, как, вероятно, обстоит дело у многих из вас. И, пожалуйста, не расстраивайтесь, читая это, потому что я сам когда-то был в той же ситуации, что и вы сейчас. Однако благодаря учёбе и значительному времени практики я достиг хорошего уровня знаний.

Поскольку преобразование этого кода 01, реализующего двусвязный список, в дерево требует понимания различных аспектов, которые ещё не были объяснены в этой серии, мы будем следовать другому подходу. Таким образом, мы сохраним учебный характер объяснения.

Поэтому мы начнём с очень простой и функциональной реализации, хотя она пока не поддерживает все операции с деревьями. Данный код показан ниже:

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. //+------------------------------------------------------------------+

Код 02

Не беспокойтесь о коде 02. Я понимаю, наверное, вас немного пугает то, как это работает. Однако мы здесь не делаем ничего принципиально нового. Возможно, код просто покажет эти элементы немного иначе. Все части, которые появляются в этом коде 02, уже были объяснены в других статьях этой же серии. После запуска программы в терминале MetaTrader 5 вы увидите следующее:

Изображение 04

На этом изображении 04 я выделяю два момента. Данные, выделенные жёлтым цветом, являются предупреждением MetaTrader 5 о том, что память, выделенная с помощью инструкции в строке 16 кода 02, не возвращается системе. Однако пока мы можем смириться с этими предупреждениями, поскольку они никак не влияют на полученный результат. Данные, выделенные зелёным цветом, — это то, что нас действительно интересует, поскольку они соответствуют результату, полученному при выполнении кода, реализующего дерево, что представлено на изображении 03.

"Но подождите секунду. Как такое возможно? На изображении 03 мы видим дерево, но эта область, выделенная зелёным на изображении 04, представляется мне списком значений". И это действительно так, уважаемый читатель. Однако есть способ продемонстрировать, что мы работаем здесь не со списком, а с деревом. Мы можем это продемонстрировать, обходя дерево в разных порядках без особых усилий. Но прежде чем это продемонстрировать, давайте быстро разберемся, как работает код 02. Если вы обратите внимание, то увидите, что внутри процедуры OnStart мы используем функцию для построения структуры дерева и процедуру для его обхода и отображения его значений.

Но как всё это работает? Для начала обратите внимание на объявление структуры в строке 04. Обратите внимание, что, хотя мы используем здесь слово class, вы должны понимать его как структуру. При этом, у нас есть два указателя: один для правой ветви, а другой для левой. У нас могло бы быть гораздо больше таких указателей, но здесь мы ограничимся самым простым и базовым вариантом. После определения этой структуры, очень похожей на структуру из кода 01, мы можем перейти к реализации функции построения дерева, объявленной в строке 12 из кода 02.

Данная функция является рекурсивной. В статье От начального до среднего уровня: Рекурсия мы рассмотрели, что такое рекурсия. Рекурсивные вызовы находятся точно в строках 27 и 28. Блок операторов if в строке 14 выделяет память и вставляет новые узлы в дерево. По сути, этот блок кода работает очень похоже на блок, заключённый между строками 29 и 34 кода 01. Однако здесь, в коде 02, инструкции, заключённые между строками 14 и 26, создают и вставляют новый узел в дерево. Но обратите внимание на один момент. В отличие от связного списка, где мы указывали позицию вставки элемента, здесь мы сравниваем вставляемое значение со значениями, которые хранятся в существующих узлах дерева. Это позволяет построить бинарное дерево поиска.

"Хм, кажется, я понял. Вот почему вы сказали, что при построении дерева следует определить, что именно мы хотим достичь. Поэтому, когда мы обходим дерево, мы видим данные в правильном порядке".

Ну, примерно так, уважаемый читатель. Однако элементы дерева не обязательно расположены в том порядке, в котором вы себе это представляете. На самом деле процедура чтения, объявленная в строке 31, возвращает элементы в порядке возрастания при обходе дерева. "Но подождите секунду. Как такое возможно? Теперь я совершенно запутался. Вы только что сказали, что функция в строке 12 упорядочивает элементы по мере построения дерева. Действительно, если посмотреть на условия, присваивания и рекурсивные вызовы в строках 22, 23, 27 и 28, можно заметить, что элементы с меньшим значением останутся в левых ветвях, а элементы с большим значением будут направлены в правую ветвь. Я решил, что этого будет достаточно, чтобы упорядочить элементы внутри дерева. Но теперь вы говорите, что процедура обхода возвращает элементы в порядке возрастания. Для меня это совершенно бессмысленно".

Успокойтесь, уважаемый читатель, вы скоро всё поймете. Но сначала обратите внимание на следующее: рекурсивная процедура, объявленная в строке 31, начнет с корневого узла и будет искать самый левый элемент до тех пор, пока не найдет ни одного. Рекурсивный вызов в строке 35 продолжает поиск в левом поддереве. Когда этот вызов завершится, мы выведем значение текущего узла. Далее мы начнем новый поиск, но на этот раз вправо, используя рекурсивный вызов в строке 37. При входе в правое поддерево процедура снова выполнит вызов в строке 35, что позволит проверить новую ветвь слева.

Здесь мы пройдемся по дереву и рассмотрим его элементы в определенном порядке. При построении дерева мы размещаем элементы с меньшим значением слева от элементов с большим значением и устанавливаем отношение порядка, которое воспринимается только при обходе определённым образом.

Однако, если мы изменим способ обхода дерева, мы можем получить совершенно другой результат. Чтобы это проверить, давайте посмотрим на следующий код.

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. //+------------------------------------------------------------------+

Код 03

А теперь будьте внимательны, уважаемый читатель. Обратите внимание, что код 03 не изменился по сравнению с кодом 02. Однако мы добавили две новые функции для обхода дерева по-разному и визуализации его содержимого. Данные функции объявлены в строках 44 и 57. Поскольку теперь вывод будет более объёмным, я немного изменил код, чтобы он возвращал строку и так его было удобнее читать.

В этом случае следуйте процедуре OnStart. Практически всё осталось таким же, как и в коде 02. Однако, при выполнении кода 03 мы получаем результат, показанный на следующем изображении:

Изображение 05

Снова интересующая нас часть выделена зелёным на изображении 05. Обратите внимание, что даже если мы строим дерево одинаковым способом, результат будет зависеть как от типа обхода, так и от порядка, в котором мы вставляем значения. "Но почему так? Я не понял, что вы только что сказали". Не беспокойтесь. Наберитесь терпения, мы сейчас к этому подойдём. Сначала давайте проанализируем информацию на изображении 05. Обратите внимание, что там содержится строка со значениями в порядке возрастания, сгенерированная вызовом, содержащимся в инструкции строки 80 кода 03. Также у нас есть значения в прямом порядке (preorder) и значения в постпорядке (postorder). Каждая из этих строк соответствует разному обходу дерева. Чтобы объяснить, почему порядок вставки приводит к тому, что эти обходы порождают разные последовательности, мы изменим одну небольшую деталь в коде 03. Этот момент показан в следующем фрагменте:

                   .
                   .
                   .
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. //+------------------------------------------------------------------+

Фрагмент 01

Теперь взгляните на показанный ниже результат:

Изображение 06

Это очень смешно. Обратите внимание, что изменение в коде очень незначительное, практически незаметное, но результат может сильно отличаться от ожидаемого. Я много страдал, когда только начинал программировать и сталкивался с подобными ситуациями. Многие люди этого не осознают или не понимают. Но если сравнить элементы, которые генерирует обход, указанный в строке Pre Order, вы заметите, что они появляются точно в том порядке, в котором мы их добавляли. Безумно, не так ли? Поэтому в начале статьи я упомянул, что деревья стоит изучать очень спокойно и внимательно.

Не думайте, что вы уже поняли тему только потому, что умеете реализовать тот или иной код. Этот тип структуры данных действительно очень интересен и имеет множество применений в различных областях и сферах, связанных с анализом данных.


Заключительные идеи

В этой статье мы реализовали первые компоненты древовидной структуры. Поскольку я знаю, что эта структура может показаться крайне сложной, когда только начинаешь её изучать, я не буду углубляться в неё дальше в этой статье. Мы будем рассматривать это постепенно, хотя в итоге реализуем и продемонстрируем лишь базовую и универсальную древовидную структуру.

Однако я хочу пробудить ваше любопытство, уважаемый читатель, чтобы вы поняли, что существуют простые структуры данных, которые позволяют выполнять операции с высокой скоростью и получать конкретные результаты. Для этого необходимо очень хорошо понимать, как устроено дерево. Таким образом, вы сможете понять структуру данных для искусственного интеллекта, которую мы вскоре реализуем: она будет очень увлекательной и простой, а её работа окажется поистине удивительной.

Поэтому внимательно изучите эту статью и предыдущие, чтобы полностью усвоить описанные здесь структуры и процедуры. В следующей статье мы реализуем больше компонентов этой обобщённой древовидной структуры.

МQ5 Описание
Код 01  Простое дерево
Код 02   Простое дерево
Код 03   Простое дерево

Перевод с португальского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/pt/articles/16710

Прикрепленные файлы |
Anexo.zip (2.18 KB)
Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Автоматизация торговых стратегий в MQL5 (Часть 45): Инверсный разрыв справедливой стоимости (IFVG) Автоматизация торговых стратегий в MQL5 (Часть 45): Инверсный разрыв справедливой стоимости (IFVG)
В этой статье мы создадим в MQL5 систему, которая обнаруживает разрывы справедливой стоимости (FVG) и отслеживает их переход в состояние инвертированного FVG (IFVG). Она выявляет бычьи и медвежьи FVG на последних сформированных барах с фильтрацией по минимальному размеру разрыва, отслеживает состояния FVG Normal, Mitigated и Inverted в зависимости от взаимодействия цены с зоной (митигация при пробое дальней границы, возврат цены в зону после митигации и инверсия фиксируется, когда после повторного входа цена закрывается за дальней границей, выходя изнутри зоны), игнорирует новые FVG, ценовые диапазоны которых перекрываются с уже существующими зонами, и ограничивает число отслеживаемых FVG.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Изучение Стандартной библиотеки MQL5 (Часть 4): Создание библиотеки пользовательских сигналов Изучение Стандартной библиотеки MQL5 (Часть 4): Создание библиотеки пользовательских сигналов
Сегодня мы используем стандартную библиотеку MQL5 для создания пользовательских классов сигналов и позволяем мастеру MQL5 собрать для нас профессиональный советник. Такой подход упрощает разработку, позволяя даже начинающим программистам создавать надежные советники без глубоких знаний в области программирования, сосредоточившись на настройке параметров и оптимизации стратегии. Давайте шаг за шагом разберем этот процесс.