Перевод из рекурсивной формы в итеративную (на примере 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;
}
}
И тут совершенно очевидно, что все три - практически одно и то же. Но вот как ---из мухи сделать слона--- от любой из этих трёх форм придти чередой последовательных переделок к любой из двух итеративныйх?
Для меня тут какой-то логический скачок, полный пропущенных звеньев (как говорят математики "если вы забыли десяток страниц доказательства - просто скажите "очевидно, что" и продолжите как ни в чём не бывало) и непонятно, как-то вообще можно переделать из одной формы в другую?
Возможно, эта тема где-то в книжках подробно расписана? Если кто сталкивался -- ссылки на книги тоже приветствуются (можно на английском).