为什么C#中的Stack <t>类允许ElementAt(索引),而它是ADT?

时间:2018-05-23 07:44:53

标签: c# data-structures stack adt

Stack是一种抽象数据类型(ADT),它应该有一个密封的操作列表,如Push(),Pop(),Peek(),......来强制执行后进先出(LIFO) )原则。

但它有ElementAt(索引),允许我访问堆栈中的任何元素。根据我的理解,Stack应该对不在表面的元素具有较少的可见性。不是吗?

3 个答案:

答案 0 :(得分:5)

ElementAt()是通过Linq的Enumerable.ElementAt()提供的,而不是通过Stack<T>界面提供的。

这是有效的,因为Stack<T>实现了IEnumerable<T>,这是实现ElementAt()所需的全部,因为它所做的只是遍历通过IEnumerable<T>提供的所有元素,直到N他们已被访问过。

对于Stack<T>,这是O(N)操作。如果您使用ElementAt(),例如List<T>,那么内部优化会将其转换为O(1)操作。

至于为什么Stack<T>实现IEnumerable<T> - 只有一位设计师才能真正回答这个问题。由于它是一个非变异操作,因此它并没有真正违反堆栈的任何基本内容。我会猜测它是为了方便而提供的。

as / u / Damien_The_Unbeliever在他的回答中指出,代码可以通过将N个元素弹出到另一个堆栈然后以相反的顺序将它们全部推回来确定没有IEnumerable接口的第N个元素,以便原始堆栈不变。

美中不足的是,Microsoft 没有记录 Stack IEnumerable返回其元素的顺序。您可以检查源代码以确定它确实以LIFO顺序返回元素 - 但这根本没有记录。

这在this question的答案中讨论。

在任何情况下,为您正在讨论的抽象堆栈定义的ADT接口在哪里?我不认为这是一个明确的答案。严格来说,您可以说堆栈只有Push()Pop()。然而,大多数实现还提供Count

作为the article about Stack on Wikipedia states

  

在许多实现中,堆栈的操作多于&#34; push&#34;和&#34; pop&#34;。一个例子是&#34;堆栈顶部&#34;或&#34; peek&#34;,它观察最顶层的元素而不将其从堆栈中移除。

     

因为这可以用&#34; pop&#34;和#34;推&#34;使用相同的数据,这不是必需的。下溢条件可能发生在堆栈顶部&#34;堆栈为空时的操作,与&#34; pop&#34;相同。此外,实现通常具有一个函数,该函数只返回堆栈是否为空。

从根本上说,你的问题的答案是:

图书馆设计师决定除了改变方法Push()Pop()之外,还添加了一些非变异的便利方法。

答案 1 :(得分:4)

  

Stack是一种抽象数据类型(ADT)

Stack的一般概念也是如此,但System.Collections.Generic.Stack<T>从未承诺(只是)ADT。

它必须至少提供ADT功能(不辜负名称),但可以免费提供。

因此它不会尝试隐藏Contains(),Count,TryPeek()等。

答案 2 :(得分:3)

在伪代码中,ElementAt位于Stack的外部,仅使用ADT操作:

T ElementAt<T>(Stack<T> items, int index)
{
    var tmp = new Stack<T>();
    while(!items.Empty && index > 0)
    {
       tmp.Push(items.Peek());
       items.Pop();
       index --
    }
    try {
        return items.Peek(); //Presumed to throw an appropriate exception if empty
    }
    finally {
       while(!tmp.Empty)
       {
          items.Push(tmp.Peek());
          tmp.Pop();
       }
    }
}

(上面假设变异Stack - 一个稍微不同的实现可以用于不可变堆栈,并且不需要笨拙的撤消操作)

当然,问题在于.NET中的Stack类型是为实际问题解决构建的,而不是为了一些抽象的纯度,并且(通过{ {1}}提供允许枚举),这里实际上可以使用更多高效的实现。我们不强迫人们将这些方法复制到“utils”库中。