【问题标题】:Binary search tree IEnumerator.MoveNext() non recursive in order traversal implementation. How to?二叉搜索树 IEnumerator.MoveNext() 在顺序遍历实现中是非递归的。如何?
【发布时间】:2016-06-29 16:51:37
【问题描述】:

在构建了一个由 BSTNode<Tkey,TValue> 节点组成的二叉搜索树 BST<Tkey,TValue> 之后,我正在尝试为其实现 IEnumerable 接口。

这就是我构造BSTNodeEnumrator<Tkey,TValue>的方式:

public class BSTNodeEnumerator<TKey, TValue> : IEnumerator<BSTNode<TKey, TValue>> where TKey : IComparable<TKey>
{
    private Stack<BSTNode<TKey, TValue>> _stack;

    public BSTNodeEnumerator(BSTNode<TKey, TValue> root)
    {
        _stack = new Stack<BSTNode<TKey, TValue>>();
        _current = null;
        _root = root;
    }

    // ... rest of the implementation
}

我传入root节点,_current是枚举的结果。我也在尝试为此使用堆栈,因为我没有像 AVL BST 那样跟踪父节点。

现在我希望枚举器以非递归方式按顺序 + 遍历树。由于 bst 的属性,这也应该导致排序枚举,这很好,因为这正是我想要实现的。

伪代码中顺序遍历的非递归算法,如wikipedia article

    iterativeInorder(node)
  s ← empty stack
  while (not s.isEmpty() or node ≠ null)
    if (node ≠ null)
      s.push(node)
      node ← node.left
    else
      node ← s.pop()
      visit(node)
      node ← node.right

我们可以把算法转换成这段c#代码:

public BSTNode<Tkey,TValue> Next() 
{
   while (_stack.Count > 0 || _current != null) 
    {
         if (_current != null)
         {
          _stack.Push(_current);
          _current = _current.left;
         }
         else
         {
          _current = _stack.Pop();
          BSTNode<Tkey,TValue> result = _current;
          _current = _current.Right;
         }
    }
    return result;
}

但这不是必需的bool MoveNext() 实现,因为我必须返回一个布尔值。如果我确实将_current 设置为适当的节点,则为真,如果我在最后,则为假。

我应该如何实施 public bool MoveNext() ?我无法理解的主要事情是,如果我想将BSTNode&lt;Tkey,TValue&gt; Next() 转换为bool MoveNext(),我必须return true 而不是简单地访问节点BSTNode&lt;Tkey,TValue&gt; result = _current;,并且只有在那之后设置_current = _current.Right;我显然做不到。

【问题讨论】:

  • 为什么不直接使用 HashSet 或 Dictionary 呢?
  • 你需要自己制作IEnumerator吗?为什么不只是让您的 GetEnumerator 函数返回您的实现,而是在 BSTNode&lt;Tkey,TValue&gt; result = _current; 行上执行 yield return _current;。我将发布一个实施作为答案。
  • @MatthewWhited - 当然我可以简单地使用 .Net 中已有的内容,我只是想学习。到目前为止,我还在与 AVL 二叉树和跳过列表作斗争。我相信这是可以理解的。我的意思是 .Net 不只是从天而降。
  • 阅读@ScottChamberlain 评论。迭代器方法和yield 语句是专门为非平凡的枚举器制作的。

标签: c# algorithm binary-search-tree ienumerable enumerator


【解决方案1】:

老实说,对于像这样的非平凡枚举器,最好只使用 .NET 内置的工具。只需返回IEnumerator&lt;BSTNode&lt;Tkey,TValue&gt;&gt; 并使用yield return 关键字,它就可以自动将您编写的代码转换为枚举器。

class BSTNode<TKey, TValue> : IEnumerable<BSTNode<TKey, TValue>>
     where TKey : IComparable<TKey>
{
    public IEnumerator<BSTNode<TKey, TValue>> GetEnumerator()
    {
        var stack = new Stack<BSTNode<TKey, TValue>>();
        var current = this;
        while (stack.Count > 0 || current != null)
        {
            if (current != null)
            {
                stack.Push(current);
                current = current.Left;
            }
            else
            {
                current = stack.Pop();
                yield return current;
                current = current.Right;
            }
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public BSTNode<TKey, TValue> Left { get; set; }

    public BSTNode<TKey, TValue> Right { get; set; }

    public TKey Key { get; set; }

    public TValue Value { get; set; }
}

如果你好奇,这里是编译器为它在幕后制作的 IEnumerator 类生成的代码

[CompilerGenerated]
  private sealed class <GetEnumerator>d__0 : IEnumerator<BSTNode<TKey, TValue>>, IDisposable, IEnumerator
  {
    private int <>1__state;
    private BSTNode<TKey, TValue> <>2__current;
    public BSTNode<TKey, TValue> <>4__this;
    private Stack<BSTNode<TKey, TValue>> <stack>5__1;
    private BSTNode<TKey, TValue> <current>5__2;

    BSTNode<TKey, TValue> IEnumerator<BSTNode<TKey, TValue>>.Current
    {
      [DebuggerHidden] get
      {
        return this.<>2__current;
      }
    }

    object IEnumerator.Current
    {
      [DebuggerHidden] get
      {
        return (object) this.<>2__current;
      }
    }

    [DebuggerHidden]
    public <GetEnumerator>d__0(int <>1__state)
    {
      base.\u002Ector();
      this.<>1__state = param0;
    }

    [DebuggerHidden]
    void IDisposable.Dispose()
    {
    }

    bool IEnumerator.MoveNext()
    {
      switch (this.<>1__state)
      {
        case 0:
          this.<>1__state = -1;
          this.<stack>5__1 = new Stack<BSTNode<TKey, TValue>>();
          this.<current>5__2 = (BSTNode<TKey, TValue>) null;
          goto label_8;
        case 1:
          this.<>1__state = -1;
          this.<current>5__2 = this.<current>5__2.Right;
          break;
        default:
          return false;
      }
label_7:
label_8:
      if (this.<stack>5__1.Count <= 0 && this.<current>5__2 == null)
        return false;
      if (this.<current>5__2 != null)
      {
        this.<stack>5__1.Push(this.<current>5__2);
        this.<current>5__2 = this.<current>5__2.Left;
        goto label_7;
      }
      else
      {
        this.<current>5__2 = this.<stack>5__1.Pop();
        this.<>2__current = this.<current>5__2;
        this.<>1__state = 1;
        return true;
      }
    }

    [DebuggerHidden]
    void IEnumerator.Reset()
    {
      throw new NotSupportedException();
    }
  }

【讨论】:

  • 我明白了,yield return 基本保留了枚举的当前状态,让我的生活轻松多了。
  • @pijemcolu here is the msdn page for it 如果您想了解更多信息。你可以用yield 做的其他很酷的技巧,如果你的代码中有try/finally,如果调用者处理了IEnumerator&lt;T&gt;,如果finally 块中的代码正在等待@987654331,它将被执行@ 在try 块内。
【解决方案2】:

调用者正在遍历枚举(可能在 foreach 循环中)。因此,您可以在每次想要返回结果时中止循环。问题就来了,因为_current = _current.Right;必须在结果确定后执行。因此我引入了一个新变量_result

private BSTNode<TKey, TValue> _result;

bool IEnumerator.MoveNext()
{
    while (_stack.Count > 0 || _current != null)
    {
        if (_current != null)
        {
            _stack.Push(_current);
            _current = _current.left;
        }
        else
        {
            _current = _stack.Pop();
            _result = _current;
            _current = _current.Right;
            return true;
        }
    }
    return false;
}

BSTNode<TKey, TValue> IEnumerator<BSTNode<TKey, TValue>>.Current
{
    get { return _result; }
}

请注意,循环枚举包括首先调用MoveNext() 并测试布尔结果。如果返回了true,则使用Current 返回的值。

【讨论】:

  • 啊,就是这样。我试图弄清楚很长的路要走,我没想到要添加结果变量。
  • 也许_current 最好命名为_cursor_temp 之类的,而_result 应该变成_current,因为它是属性Current 的支持字段。
猜你喜欢
  • 2020-09-25
  • 2020-07-31
  • 2013-05-12
  • 2014-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-25
相关资源
最近更新 更多