Перевод из рекурсивной формы в итеративную (на примере Preorder Traversal)

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

Например, я хотел бы зная алгоритм рекурсивного обхода бинарного дерева (для простоты - скажем, Preorder Traversal а то например итеративный postorder совсем сложно понять, как делается) переделать его итеративный. Именно не тупо запоминая, а понимая что я делаю и как.

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

И вот у меня например записаны два образца итеративного алгоритма:

public class Solution
{
    public IList<int> PreorderTraversal(TreeNode root)
    {
        var result = new List<int>();
        
        if (root == null)
            return result;

        var stack = new Stack<TreeNode>();
        stack.Push(root);
        while (stack.Count > 0)
        {
            var node = stack.Pop();
            
            result.Add(node.val);
            
            if (node.right != null)
                stack.Push(node.right);
                
            if (node.left != null)
                stack.Push(node.left);
        }
        return result;
    }
}

// Looks similar as InorderTraversal Iterative
public class Solution1
{
    public IList<int> PreorderTraversal(TreeNode root)
    {
        var result = new List<int>();

        var stack = new Stack<TreeNode>();
        var node = root;

        while (stack.Count != 0 || node != null)
        {
            if (node != null)
            {
                result.Add(node.val);
                stack.Push(node);
                node = node.left;
            }
            else
            {
                node = stack.Pop();
                node = node.right;
            }
        }
        return result;
    }
}

Я примерно вижу, что они - об одном и том же. Не совсем понятно, как от одной к другой перейти, но логика понятна и пожалуй через серию рефакторингов можно из одной сделать другую.

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

public class Solution
{
    public IList<int> PreorderTraversal(TreeNode root)
    {
        var result = new List<int>();

        if (root == null)
            return result;

        result.Add(root.val);

        if (root.left != null)
            result.AddRange(PreorderTraversal(root.left));
            
        if (root.right != null)
            result.AddRange(PreorderTraversal(root.right));

        return result;
    }
}

public class Solution1
{
    public IList<int> PreorderTraversal(TreeNode root)
    {
        var result = new List<int>();
        PreorderTraversal(root, result);
        return result;
    }

    private void PreorderTraversal(TreeNode node, List<int> result)
    {
        if (node == null)
            return;

        result.Add(node.val);
        
        if (node.left != null)
            PreorderTraversal(node.left, result);
            
        if (node.right != null)
            PreorderTraversal(node.right, result);
    }
}

public class Solution2
{
    public IList<int> PreorderTraversal(TreeNode root)
    {
        var result = new List<int>();
        
        if(root != null)
            result = PreorderTraversalHelper(root).ToList();
            
        return result;
    }

    private static IEnumerable<int> PreorderTraversalHelper(TreeNode node)
    {
        yield return node.val;
        
        if (node.left != null)
            foreach (var nod in PreorderTraversalHelper(node.left))
                yield return nod;
                
        if (node.right != null)
            foreach (var nod in PreorderTraversalHelper(node.right))
                yield return nod;
    }
}

И тут совершенно очевидно, что все три - практически одно и то же. Но вот как ---из мухи сделать слона--- от любой из этих трёх форм придти чередой последовательных переделок к любой из двух итеративныйх?

Для меня тут какой-то логический скачок, полный пропущенных звеньев (как говорят математики "если вы забыли десяток страниц доказательства - просто скажите "очевидно, что" и продолжите как ни в чём не бывало) и непонятно, как-то вообще можно переделать из одной формы в другую?

Возможно, эта тема где-то в книжках подробно расписана? Если кто сталкивался -- ссылки на книги тоже приветствуются (можно на английском).


Ответы (0 шт):