【问题标题】:What real world uses of the "Stack" object (.Net) have you used您使用过“Stack”对象(.Net)的实际用途吗
【发布时间】:2010-01-13 16:05:36
【问题描述】:

我们都阅读或听说过堆栈类,但我们中的许多人可能从未找到使用 LIFO 对象的理由。我很想知道使用此对象的现实世界解决方案以及原因。

http://msdn.microsoft.com/en-us/library/system.collections.stack.aspx

我最近看到一个例子,程序员在遍历分层数据源时使用堆栈来跟踪他的当前位置。当他向下移动层次结构时,他将他的位置标识符推到堆栈上,当他向后移动时,他将项目从堆栈中弹出。我认为这是一种非常有效的方式来跟踪他在庞大的层次结构中的当前位置。我以前从未见过。

其他人有例子吗?

【问题讨论】:

  • 是的-首先使用堆栈作为深度。您知道您首先使用队列是为了广度吗?
  • 这不是一个真正的.net 特定问题。堆栈是一种抽象数据类型,人们(可能)在每种语言中都使用它们。
  • 我一直使用堆栈。您只需在必要时使用它们。
  • 这应该是一个社区维基吗?
  • 同意。选择 .Net 是个坏主意

标签: algorithm language-agnostic data-structures


【解决方案1】:

我使用它们来跟踪撤消和重做操作。

我使用类似这样的界面:

interface ICommand
{
    void Execute();
    void Undo();
    string Description { get; }
}

撤消和重做都是Stack<ICommand> 类型。然后我为给定的动作创建一个具体的类。在类的构造函数中,我传递了我需要保留的任何信息。 Execute 最初执行该操作,然后重新执行该操作; Undo 显然会撤消它。它的工作原理是这样的:

  • 撤消操作:弹出撤消堆栈并添加到重做堆栈。
  • 重做已撤消的操作:弹出重做堆栈并再次添加到撤消堆栈。
  • 执行新操作:添加到 Undo 堆栈并清除 Redo 堆栈(因为状态不再一致)。

我发现您必须小心,您确实在撤消已完成的操作。例如,假设您有一个带有两个列表框的 UI,每个列表框有五个项目。您的操作可能是单击一个按钮,将左侧列表中的所有内容移动到右侧列表(因此现在有十个,左侧列表为零)。

撤消操作不是将所有内容移回;撤消操作是仅将您实际移动的五个移回,而留下其他的。

【讨论】:

  • 这是一个很好的例子
【解决方案2】:

Stacks are used whenever a stored procedure / sub-routine is called to store local variables and return address.

堆栈用于表达式评估(例如在计算器或您的编译器中),首先将表达式转换为RPN,然后使用简单的堆栈机器进行评估。当您看到一个操作数将其压入堆栈时,其工作原理如下。当你看到一个操作符弹出操作数并求值时。

example

5 6 + 3 *

steps-
 see 5 push 5
 see 6 push 6
 see + pop 2 times and apply + get 11 push 11
 see 3 push 3
 see * pop 2 times and apply get 33 push 33

result is on the top of the stack.   

【讨论】:

  • 您可以通过概括语言解释器经常使用堆栈来扩展此答案。
  • err... 我说它们用于表达式评估——因此任何进行表达式评估的东西都会使用它们,然后我举了两个例子。
【解决方案3】:

如果您有递归算法,您通常可以使用堆栈重写它们。 (因为递归算法已经隐式使用了堆栈)

【讨论】:

  • 是的,但有时它可以节省您传递大量参数的时间,您现在可以将这些参数保留在本地
  • -1,这根本不是真实世界。如果它是尾递归的,你只需使用一个while循环;否则,与使用递归相比,使用 Stack 对象的工作量更大(而且速度更慢)。
  • @BlueRaja:我不认为这是真的,它仍然是相同的 O 值——它是如何工作的......它是一样的。
  • @BlueRaja:对于深度递归调用,递归几乎总是更慢并且更容易发生堆栈溢出。我想“现实世界”这个词是主观的,你可能从来没有发现自己处于编码深度递归函数的情况,无法简单地转换为循环。
【解决方案4】:

您可以验证需要平衡令牌的字符串输入。想想 LISP:

(+ (- 3 2) (+ (+ 4 5) 11))

当你击中左括号时:

stack.Push("(")

然后当你碰到一个结束括号时:

stack.Pop()

如果完成后堆栈中还有任何标记,则说明它不平衡。

您可以在诸如 HTML 之类的输入中获得更好的并验证正确的嵌套。在一个高度做作的例子中:

//see opening body
stack.Push("body")

//see opening div
stack.Push("div")

//see opening p
stack.Push("p")

///see closing div
if(!stack.Pop().Equal("div")){
    //not balanced
}

【讨论】:

  • 这也适用于解析 XML 文档
【解决方案5】:

我使用堆栈进行图像处理,其中“处理语言”必须在 URL 中指定。基于堆栈的表单可让您以易于解析、易于思考的形式表示操作树。

见:

http://www.hackification.com/2008/10/29/stack-based-processing-part-1/

http://www.hackification.com/2008/10/29/stack-based-processing-part-2/

【讨论】:

    【解决方案6】:

    在实际使用中,postscript 生成器类具有“current_font”状态,用作绘制文本的任何操作的字体。有时一个函数需要临时设置字体,然后让它回到原来的样子。我们可以只使用一个临时变量来保存和恢复字体:

    def draw_body
      old_font = ps.get_font
      ps.set_font('Helvetica 10')
      draw_top_section
      draw_bottom_section
      ps.set_font(old_font)
    end
    

    但是当你第三次这样做时,你会想要停止重复自己。所以让我们让 ps 对象为我们保存和恢复字体:

    class PS
    
      def save_font
        old_font = get_font
      end
    
      def restore_font
        set_font(old_font)
      end
    
    end
    

    现在调用者变成:

    def draw_body
      ps.save_font
      ps.set_font('Helvetica 10')
      draw_top_section
      draw_bottom_section
      ps.restore_font
    end
    

    这很好,直到我们在 draw_page 调用的子例程之一中使用相同的模式:

    def draw_top_section
      ps.save_font
      ps.set_font('Helvetica-bold 14')
      # draw the title
      ps.restore_font
      # draw the paragraph
    end
    

    当 draw_top_section 调用“save_font”时,它会破坏由 draw_page 保存的字体。是时候使用堆栈了:

    def PS
    
      def push_font
        font_stack.push(get_font)
      end
    
      def pop_font
        set_font(font_stack.pop)
      end
    
    end
    

    在调用者中:

    def draw_top_section
      ps.push_font
      ps.set_font('Helvetica-bold 14')
      # draw the title
      ps.pop_font
      # draw the body
    end
    

    还有进一步的改进可能,例如让 PS 类自动保存和恢复字体,但没有必要进入这些来查看堆栈的值。

    【讨论】:

      【解决方案7】:

      我发现堆栈在多线程应用程序中非常有用,可以以反时限的方式跟踪状态...

      每个线程都会在同步的共享堆栈中放置一条状态消息,并且您对所发生的事情有一种“面包屑”。

      不完全是 .NET 但...这是我的意见 =)

      【讨论】:

      • 因为在这个例子中事情的顺序并不重要,队列也可以工作
      • 如果你想以面包屑的方式调试东西,这很重要。就像你倒退的每一步一样。我想他问的是我们中的任何人都觉得有用的例子... =)
      【解决方案8】:

      这是一个深度比较的实现,其中堆栈用于跟踪当前被比较对象的路径。

      C# implementation of deep/recursive object comparison in .net 3.5

      我还在类似类型的代码中使用它,为特定的 xml 节点生成 xpath 语句。

      【讨论】:

        【解决方案9】:

        为了提供一个具体的例子来说明其他人的评论:要实现一个 Z 机器解释器,应该使用三个不同的堆栈。一个调用堆栈和几种不同类型的对象堆栈。 (具体要求可以在here. 找到)请注意,与所有这些示例一样,虽然不是严格必需,但它是显而易见的选择。

        调用堆栈跟踪对子例程的递归调用,而对象堆栈用于跟踪内部项。

        【讨论】:

          【解决方案10】:

          在计算机图形类(不是 .NET)中,我们使用堆栈来跟踪在屏幕上绘制的对象。这允许在每次刷新时在屏幕上重新绘制所有对象,并跟踪每个对象的顺序或“z 层”,因此当它们移动时,它们可以与其他对象重叠。

          【讨论】:

            猜你喜欢
            • 2012-01-28
            • 2014-10-03
            • 2018-02-02
            • 1970-01-01
            • 2014-06-19
            • 2011-06-16
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多