Как преобразовать алгоритм рекурсии «Сортированный массив в сбалансированный BST» в итеративный алгоритм?

Я искал, но не могу понять или найти помощь, так как для этого итеративного алгоритма потребуется два стека (чтобы содержать left_Index и right_Index).

Основной рекурсивный способ заключается в том, чтобы иметь одну сторону до left_Index> = right_Index, и делать это рекурсивно для обеих сторон и для каждого подраздела (если это имеет смысл), что я не понимаю, как это сделать именно так, поскольку я поддерживаю два стеки и нужно увидеть, как именно они соотносятся друг с другом.

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


Предыстория того, почему я это делаю: Пытаясь решить проблему словарной лестницы, чтобы перейти от A к B, я решил создать BST, в котором соединения связаны различиями и длинами единичных символов. Я получаю слова из текстового файла, содержащего много словаря, и поскольку я использую BST в качестве основного списка со всеми вершинами, тот факт, что это словарь, означает, что каждая вставка будет в порядке, поэтому дерево наклоняется вправо (поэтому скорости медленные для вставки O (n ^ 2), что является препятствием большой). Я планировал хранить данные в массиве, а затем сделать из этого сбалансированный BST, поскольку я считаю, что скорость должна быть быстрее, поскольку вставка будет O (n * logn), что кажется отличным. Проблема в том, что я не могу использовать рекурсивный подход, поскольку существует много данных, ведущих к переполнению стека, поэтому мне нужно делать это итеративно со стеками и циклами, но мне это слишком сложно.


Моя неудачная попытка для начала:

while (lindx.the_front() < rindx.the_back())
{
mid =(lindx.the_front() + rindx.the_back()) / 2;
dictionary.addVertex(vector[mid]);
std::cout << "Pushed " << vector[mid] << '\n'; rindx.push(mid - 1);
}


Это в основном получает 1/2 левой половины программы из созданного мной связанного стека. the_front () - первая вставка, the_back () - последняя / последняя вставка в список. Основная проблема, с которой я столкнулся, - это понимание того, как заставить его повторяться за половину, чтобы получить все значения.


Мне нужно найти мою предыдущую домашнюю работу, где я это сделал, но код похож на ...

void array2balanced(int array[], int lIndex, int rIndex) 
{  
  //base case
  if (lIndex > rIndex) 
  {
    return; 
  } 
  //recursive cals
  else 
  {  
    mid = (lIndex+rIndex)/2;  
    tree.insert(array[mid]);  
    array2balanced(array, lIndex, mid-1);  
    array2balanced(array, mid+1, rIndex); 
  } 
}

Обновлено:Прогресс на данный момент

void balancedTree(std::vector<std::string> vector, dictionaryGraph &dictionary) // divide and conquer into tree?
{
    linkedStack<int> lindx, rindx, midX;
    unsigned int l_Index{ 0 }, r_Index{ vector.size() - 1 }, mid{ (l_Index + r_Index) / 2 };;
    lindx.push(l_Index);
    rindx.push(r_Index);
    midX.push(mid);
    int testCount{ 0 };
    std::cout << "There are " << vector.size() << " words.\n";

    while (!midX.empty())
    {
        mid = midX.pop();
        l_Index = lindx.pop();
        r_Index = rindx.pop();
        std::cout << "inputted " << vector[mid] << '\n';
        dictionary.addVertex(vector[mid]);
        testCount++;

        if (r_Index > l_Index)
        {

            midX.push((l_Index + mid) / 2);
            lindx.push(l_Index);
            rindx.push(mid - 1);
        }
        if (l_Index < r_Index)
        {
            midX.push((mid + r_Index) / 2);
            lindx.push(mid + 1);
            rindx.push(r_Index);
        }
    }
    std::cout << testCount << " words were inputted...\n"; // To see how many were inserted
    system("pause");
}

У меня проблема в том, что некоторые входные данные повторяются, а некоторые пропускаются.

Не могли бы вы опубликовать псевдокод для рекурсивного алгоритма, который вы видели?

Martin Broadhurst 01.05.2018 21:40

1) Получите середину массива и сделайте его корневым. 2) Рекурсивно проделайте то же самое для левой и правой половин. a) Получите середину левой половины и сделайте ее левым потомком корня, созданного на шаге 1. б) Получите середину правой половины и сделайте ее правым потомком корня, созданного на шаге 1. - geeksforgeeks.org

fabian 01.05.2018 21:42

Это предварительный обход, поэтому вам нужен вариант итеративного алгоритма обхода перед порядком (en.wikipedia.org/wiki/Tree_traversal#Pre-order), но где вы складываете позиции вместе с узлами.

Martin Broadhurst 01.05.2018 21:59

Спасибо! У меня все еще возникают проблемы, поскольку моя реализация не работает (поскольку некоторые индексы отсутствуют, а некоторые повторяются), но я думаю, что приближаюсь!

fabian 01.05.2018 23:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
600
2

Ответы 2

This problem is mostly due to me not understanding the way the normal recursive method words, although when looking at them side by side to see how to approach it, I always get stuck on what to do.

Это требует практики ... и, возможно, анализа работ других людей.

require two stacks (to contain a left_Index and right_Index).

Приношу свои извинения, я не понимаю, почему ОП так думает. В моей демонстрации ниже есть только 1 стек под названием «todo», возможно, вы найдете эту идею полезной.

#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
#include <cassert>

#include "./BTree.hh"  // code not provided, used in this MCVE to 
                       // conveniently provide "showTallTreeView()" 

typedef std::vector<int>  IVec_t;  

class T607_t
{
   IVec_t m_sortedIVec;    // sorted - created with for loop
   IVec_t m_recursiveIVec; // extract from sorted by recursion
   IVec_t m_iterativeIVec; // extract from sorted by iteration

public:

   T607_t() = default;
   ~T607_t() = default;

   int exec(int , char**  )
      {
         fillShowSortedIVec();

         fillShowRecursiveIVec();

         fillShowIterativeIVec();

         showResults();

         return 0;
      }

private: // methods

Векторы находятся в классе T607_t, поэтому каждый доступен для любой функции-члена.

Для этого MCVE я просто создаю «IVec_t m_sortedIVec;» и заполните простым циклом for:

   void fillShowSortedIVec()
      {
         for (int i=0; i<15; ++i)
            m_sortedIVec.push_back (i*100);  // create in sorted order

         showIVec(m_sortedIVec, "\n  m_sortedIVec   :");   
      }

Далее (в этом MCVE) идет рекурсивная заливка и отображение и моя адаптация рекурсивного метода OP для создания рекурсивной последовательности вставки:

   // ///////////////////////////////////////////////////////////////
   void fillShowRecursiveIVec()
      {
         assert(m_sortedIVec.size() > 0);
         int max = static_cast<int>(m_sortedIVec.size()) - 1;

         // use OP's recursive insert
         array2balancedR (m_sortedIVec, 0, max);  
         // NOTE - 'sequence' is inserted to 'm_recursiveIVec'
         //        instead of into tree the op did not share

         showIVec(m_recursiveIVec, "\n  m_recursiveIVec:");
      }

   // recursive extract from: m_sortedIVec  to: m_recursiveIVec
   // my adaptation of OP's recursive method
   void array2balancedR(IVec_t& array, int lIndex, int rIndex)
      {
         //base case
         if (lIndex > rIndex)
         {
            return;
         }
         else    //recursive calls
         {
            int mid = (lIndex+rIndex)/2;
            m_recursiveIVec.push_back(array[mid]);  // does this
            // tree.insert(array[mid]);             // instead of this

            array2balancedR(array, lIndex, mid-1);  // recurse left
            array2balancedR(array, mid+1, rIndex);  // recurse right
         }
      }

Примечание. Я оставил "IVec_t & array" в качестве параметра этой функции, потому что он есть в коде OP. Внутри этой оболочки класса функция не должна передавать массив «через рекурсию», потому что каждый метод имеет доступ к данным экземпляра.

Далее (в этом MCVE) выполняется действие заполнения и отображения с использованием одного возможного итеративного подхода. Я тщательно разработал этот итеративный подход, чтобы он соответствовал рекурсивным усилиям OP.

Во-первых, я добавил «инструмент» (IndxRng_t), чтобы упростить «стековый» захват итераций для последующей обработки. (т.е. «todo»).

   // //////////////////////////////////////////////////////////////
   // iterative extract from  m_sortedIVec  to: m_iterativeIVec
   class IndxRng_t  // tool to simplify iteration
   {
   public:
      IndxRng_t() = delete; // no default
      IndxRng_t(int li, int ri)
         : lIndx (li)
         , rIndx (ri)
         {}
      ~IndxRng_t() = default;

      // get'er and set'er free.  also glutton free.  gmo free.
      bool            done() { return (lIndx > rIndx); } // range used up
      int              mid() { return ((lIndx + rIndx) / 2); } // compute
      IndxRng_t   left(int m) { return {lIndx, m-1}; }  // ctor
      IndxRng_t  right(int m) { return {m+1, rIndx}; }  // ctor
   private:
      int lIndx;
      int rIndx;
   };


   void fillShowIterativeIVec()
      {
         assert(m_sortedIVec.size() > 0);
         int max = static_cast<int>(m_sortedIVec.size()) - 1;

         array2balancedI(m_sortedIVec, 0, max); 
         // 'sequence' inserted to 'm_iterativeIVec'

         showIVec(m_iterativeIVec, "\n  m_iterativeIVec:");
      }


   void array2balancedI(IVec_t& array, int lIndex, int rIndex)
      {
         std::vector<IndxRng_t>  todo;
         todo.push_back({lIndex, rIndex}); // load the first range

         // iterative loop (No recursion)
         do
         {
            if (0 == todo.size()) break; // exit constraint
            // no more ranges to extract mid from

            // fetch something to do
            IndxRng_t  todoRng = todo.back();
            todo.pop_back(); // and remove from the todo list

            if (todoRng.done()) continue; // lIndex > rIndex 

            int mid = todoRng.mid();
            m_iterativeIVec.push_back(array[mid]);  // do this
            // tree.insert(array[mid]);             // instead of this

            todo.push_back(todoRng.right(mid) ); // iterate on right
            todo.push_back(todoRng.left(mid)  ); // iterate on left

         }while(1);
      }

И этот mcve генерирует отображение результатов:

   void showResults()
      {
         assert(m_recursiveIVec.size() == m_sortedIVec.size());
         assert(m_iterativeIVec.size() == m_sortedIVec.size());

         std::cout << std::endl;

         std::stringstream ss; // for btree use only

         std::cout << "\n  demo:\n     create a BTree, "
                   << std::flush;
         std::cout << "\n     Insert IVec_t " << std::endl;

         BBT::BTree_t btree(ss);
         std::cout << std::flush;

         for (size_t i=0; i<m_iterativeIVec.size(); ++i)
            btree.insertPL(m_iterativeIVec[i]);

         std::cout << "\n iterative result:\n\n" 
                   << btree.showTallTreeView();
      }


   void showIVec(IVec_t& ivec, std::string lbl)
   {
      std::cout << lbl << std::endl;
      for (auto it : ivec)
         std::cout << std::setw(5) << it << std::flush;
      std::cout << std::endl;
   }

}; // class T607_t


int main(int argc, char* argv[])
{
   T607_t  t607;
   return  t607.exec(argc, argv);
}

Мой вывод (на Ubuntu 17.10, g ++ 7.2.0),

  m_sortedIVec   :
    0  100  200  300  400  500  600  700  800  900 1000 1100 1200 1300 1400

  m_recursiveIVec:
  700  300  100    0  200  500  400  600 1100  900  800 1000 1300 1200 1400

  m_iterativeIVec:
  700  300  100    0  200  500  400  600 1100  900  800 1000 1300 1200 1400


  demo:
     create a BTree, 
     Insert IVec_t 

 iterative result:

  BTree_t::showTallTreeView():  (balance: 0  sz: 15)

                     0 
               100 
                    200 
          300 
                    400 
               500 
                    600 
     700 
                    800 
               900 
                    1000 
          1100 
                    1200 
               1300 
                    1400 

-----------------

showTallTreeView () отображает дерево сбоку ... вершиной слева.

2785528 10.05.2018 19:29

Итеративная реализация JavaScript для преобразования отсортированного массива в двоичное дерево поиска (BST):

function sortedArrayToBstIteratively(nums) {
    // use stack to iteratively split nums into node tuples and reuse values
    const stack = []

    // add root node to tree
    const tree = { first: 0, last: nums.length - 1 }
    stack.push(tree)

    // split array in the middle and continue with the two halfs
    while (stack.length > 0) {
        const node = stack.pop()

        if (node.last >= node.first) {
            if (node.last === node.first) {
                // node reaches a single leaf value (last == first)
                node.value = nums[node.first]
            } else {
                // node has still valid indices to further split the array (last > first)
                const middle = Math.ceil((node.first + node.last) / 2)
                node.value = nums[middle]
                node.left = { first: node.first, last: middle - 1 }
                node.right = { first: middle + 1, last: node.last }
                stack.push(node.left)
                stack.push(node.right)
            }
        } else {
            // node has no more valid indices (last < first), create empty leaf
            node.value = null
        }

        delete node.first
        delete node.last
    }

    // console.info(JSON.stringify(tree))

    return tree
}

Другие вопросы по теме