如何仅使用堆栈操作对堆栈进行排序?

Posted

技术标签:

【中文标题】如何仅使用堆栈操作对堆栈进行排序?【英文标题】:How to sort a stack using only stack operations? 【发布时间】:2011-06-17 02:43:14 【问题描述】:

我在网上找到了这个问题。

给定一个栈S,编写一个C程序对栈进行排序(升序 命令)。 我们不允许对堆栈的实现方式做出任何假设。 唯一要使用的功能是:

Push
Pop
Top
IsEmpty
IsFull

我认为我们可以构建堆并对其进行排序。什么是最佳解决方案?

【问题讨论】:

请提供链接。如前所述,您可以复制到任何其他结构,对其进行排序,然后将其复制回。O(1) 额外的内存使用是关键要求。 O(1) 额外的内存被证明是不可能的。如果堆栈底部的两个元素需要交换,则上面的所有元素都需要移动到额外的存储空间。这是 O(N)。 为什么要对堆栈进行排序? @MSalters:是的。我认为这个问题的唯一好答案是“做不到”和“duh”。 对我来说这听起来像是“河内塔”问题:en.wikipedia.org/wiki/Towers_of_Hanoi。任务有点不同,但我认为你可以从它开始。 【参考方案1】:

如果您没有关于堆栈内容的任何额外信息,那么您几乎只能将所有数据取出、排序并放回原处。Heapsort 在这里会很棒,quicksort 或 introsort 也一样.

【讨论】:

如果想在面试的时候写个C程序,冒泡排序是个不错的选择。 @Potatoswatter- 你能详细说明这背后的理由吗?我认为任何其他 O(n^2) 排序都更容易编写(例如,插入或选择),并且像 heapsort 或 mergesort 这样的 O(n lg n) 排序会比较困难。跨度> 【参考方案2】:

鉴于这些堆栈操作,您可以编写递归插入排序。

void sort(stack s) 
    if (!IsEmpty(s)) 
        int x = Pop(s);
        sort(s);
        insert(s, x);
    


void insert(stack s, int x) 
    if (!IsEmpty(s))   
        int y = Top(s);
        if (x < y) 
            Pop(s);
            insert(s, x);
            Push(s, y);
         else 
            Push(s, x);
        
     else 
        Push(s, x); 
    

【讨论】:

+1 我喜欢这个解决方案,它很好地滥用了调用堆栈作为附加数据结构:-)【参考方案3】:

假设这里唯一允许的数据结构是堆栈,那么您可以使用 2 个堆栈。

迭代直到原始堆栈为空,并且在每次迭代中,从原始堆栈中弹出一个元素,而第二个堆栈中的顶部元素大于删除的元素,弹出第二个堆栈并将其推送到原始堆栈。现在您可以将最初从原始堆栈中弹出的元素推送到第二个堆栈。

这种方法的时间复杂度是O(N^2)。

实现此算法的 C 代码将是(请原谅我生疏的 C 技能):

void SortStack(struct Stack * orig_stack)

  struct Stack helper_stack;
  while (!IsEmpty(orig_stack))
  
    int element = Pop(orig_stack);
    while (!IsEmpty(&helper_stack) && Top(&helper_stack) < element)
    
      Push(orig_stack, Pop(&helper_stack));
    
    Push(&helper_stack, element);
  
  while (!IsEmpty(&helper_stack))
  
    Push(orig_stack, Pop(&helper_stack));
  

【讨论】:

那不就是手动使用栈来处理递归的插入排序吗? 这里的O(n^2) 复杂度值得怀疑,因为单个元素可以多次进入和离开orig_stack。因此,外部while 循环的迭代次数将远远超过n @Nikita:让我们尝试看看最坏的情况(根据元素在堆栈中来回移动的次数)。这将是原始堆栈已经排序的时候。现在让我们看一下“旅行”最多的元素。这将是原始堆栈中最顶层的元素,它将“移动”O(n) 次。扩展到其余元素,我们将有大约 2 * (1+2+3+...+n) 次“旅行”。这意味着我们得到 O(n^2)。 @OrenD 您是如何确定最坏情况的?例如,对于标准快速排序(中间有枢轴),最坏的情况O(n^2) 场景是非常棘手的。排序数组和逆排序数组都将在O(n*logn) 中运行,但这并不能证明什么。 @OrenD 我怀疑这是O(n^2),但你不能在没有证据的情况下声称它。【参考方案4】:

可以使用相同的堆栈递归地完成。 O(n^2) 我已经用 C++ 对其进行了编码,但转换为 C 是微不足道的。我只是喜欢模板,而您确实将您的问题标记为 C++

template<typename T>
void Insert(const T& element, Stack<T>& stack)

  if(element > stack.Top())
  
    T top = stack.Pop();
    Insert(element, stack);
    stack.Push(top);
  
  else
  
    stack.Push(element);
  


template<typename T>
void StackSort(Stack<T>& stack)

  if(!stack.IsEmpty())
  
    T top = stack.Pop();
    StackSort(stack);
    Insert(top, stack);    
      

已编辑以收回我的投票! :))

【讨论】:

不,您的代码不正确。你的第一元素永远不会改变他的位置。 抱歉,您的解决方案完全正确。但是现在我无法撤消我的反对票,直到您在帖子中编辑某些内容。编辑一些东西,我会支持你的解决方案。 @Ashot Martirosyan - 我已编辑。谢谢。我没有编译,但想法应该是正确的。 我会说,还有 implicit 另一个堆栈,尽管这个想法似乎可行。【参考方案5】:

煎饼排序是另一种有趣的方法:http://en.wikipedia.org/wiki/Pancake_sorting#cite_note-4。

【讨论】:

【参考方案6】:

如果没有使用其他数据结构的限制,我会说最小堆。 每当从堆栈中弹出一个元素时,将其放入堆中。弹出完成后,从堆顶取出元素(其余元素的最小值)并将其推入堆栈。重复这样的过程,直到堆为空。

对于创建一个堆,平均时间是O(nlogn);从堆中删除元素并按升序放回元素,平均时间也是O(nlogn)。

它看起来怎么样?

【讨论】:

【参考方案7】:

请注意 Michael J. Barber 的回答(见上文)不正确,当元素为空时忘记将元素推回堆栈。正确的版本如下:

void sort(stack s) 
    if (!IsEmpty(s)) 
        int x = Pop(s);
        sort(s);
        insert(s, x);
    


void insert(stack s, int x) 
    if (!IsEmpty(s))   
        int y = Top(s);
        if (x < y) 
            Pop(s);
            insert(s, x);
            Push(s, y);
         else 
            Push(s, x);
        
     else 
        Push(s, x); // !!! must step, otherwise, the stack will eventually be empty after sorting !!!
    

【讨论】:

【参考方案8】:

这是基于@OrenD给出的答案的javascript解决方案

var org = [4,67,2,9,5,11,3];
var helper = [];

while(org.length > 0)
    var temp = org.pop();
    while((helper.length > 0) && (helper[helper.length-1] < temp))
        org.push(helper.pop());
    
    helper.push(temp);


while(helper.length > 0)
    org.push(helper.pop());

【讨论】:

【参考方案9】:
/* the basic idea is we go on
 *  popping one element (o) from the original stack (s) and we
 *  compare it with the new stack (temp)
 * if o (the popped element from original stack)
 *  is < the peek element from new stack temp
 * then we push the new stack element to original stack
 *  and recursively keep calling till temp is not empty
 *  and then push the element at the right place.
 * else we push the element to the new stack temp
 *  (o >= new temp stack.
 *
 * Entire logic is recursive.
 */

public void sortstk( Stack s )

    Stack<Integer> temp = new Stack<Integer>();

    while( !s.isEmpty() )
    
        int s1 = (int) s.pop();

        while( !temp.isEmpty() && (temp.peek() > s1) )
        
            s.push( temp.pop() );
        
        temp.push( s1 );
    

    // Print the entire sorted stack from temp stack
    for( int i = 0; i < temp.size(); i++ )
    
        System.out.println( temp.elementAt( i ) );
    

【讨论】:

【参考方案10】:

三叠游戏

有了三个栈S(源栈、未排序元素的栈)、A、B就可以开始玩类似于归并排序的游戏了。

我认为很明显,如果您在 A 和 B 上对元素进行了排序(在同一方向,都升序或降序),您可以使用 S 合并排序 A 和 B,然后将结果移动到,对于例如,B。

你在 A、B 或 S 上有一些元素这一事实并不能阻止你能够使用 A、B 或 S 进行合并(只要你有 A、B 和S 所以你不会过冲)。如果您的程序将有序元素放在 S 上,那么使用 A 和 B 将已排序的部分从您喜欢的任何方向移至 A 或 B 是不明智的:您可以直接将其以与 A 或 B 的相反顺序放置,或者,例如,将所有元素放置到 A 中,然后再次反转到 B。

假设 A 上有 4 个已排序的元素,B 上有 16 个元素,S 上有一些未排序的元素。

A.push(S.pop) 现在创建一个 2 元素排序合并。再次 B.push(S.pop) 并创建另一个 2 元素排序合并。如果合并没有分开和/或方向不同,您可以以任何您喜欢的方式操作元素,直到在 B(甚至 S)上达到 4 元素排序合并。现在合并来自 A 的初始 4 元素排序合并和 B 上的那部分,直到达到 8 元素排序合并。

您重复所有操作,直到创建另一个 8 元素排序合并。您将它按正确的顺序放在 B(或 S)上并合并以获得 16 个元素的排序合并。现在您将它放在 A(或 S)上并与我们一直在 B 上的 16 元素合并合并。

优化的实现很棘手,因为您需要减少从一个堆栈到另一个堆栈的移动(恢复)排序合并。但是,如果您修复并决定要使用 A、B 和 S 的内容并强制例如:A 以降序包含较小的合并部分和 B 较大的合并部分,那么算法会更简单。

您需要将一些顶部元素从一个堆栈移动到另一个堆栈的事实为复杂性增加了一个常数因素,但它永远不会超过 2。除此之外,复杂性是 n*log(n)(您达到2n-element sorted merge通过合并2个n-element sorted merge,一次合并不超过2n步。)

用 C# 实现(其他类似的语言很明显)

    void Stacking(Stack<int> sourceStack, Stack<int> mainStack, Stack<int> childStack, int n)
    
        if (n == 0) return;

        if (n == 1)
        
            mainStack.Push(sourceStack.Pop());
            return;
        

        int mainSize = n/2;
        int childSize = n - n/2;

        Stacking(sourceStack, mainStack, childStack, mainSize);
        Stacking(sourceStack, childStack, mainStack, childSize);

        while (true)
        
            while (mainSize > 0 && mainStack.Peek() >= childStack.Peek())
            
                sourceStack.Push(mainStack.Pop());
                mainSize--;
            

            if (mainSize == 0) break;

            while (childSize > 0 && childStack.Peek() >= mainStack.Peek())
            
                sourceStack.Push(childStack.Pop());
                childSize--;
            

            if (childSize == 0) break;
        

        while (mainSize > 0)
        
            sourceStack.Push(mainStack.Pop());
            mainSize--;
        

        while (childSize > 0)
        
            sourceStack.Push(childStack.Pop());
            childSize--;
        

        for (int i = 0; i < n; i++)
            mainStack.Push(sourceStack.Pop());
    

    void SortStack(Stack<int> sourceStack)
    
        int n = sourceStack.Count();

        // possible optimization: if (n < 2) return;

        Stack<int> mainStack = new Stack<int>();
        Stack<int> childStack = new Stack<int>();

        Stacking(sourceStack, mainStack, childStack, n);

        for (int i = 0; i < n; i++)
            sourceStack.Push(mainStack.Pop());
    

log(n) 堆栈游戏

如果您最多可以使用 log(n) 堆栈,则可以简化上述过程。这个堆栈数并不意味着您将使用大于 n 阶的额外空间。您只需标记将用于合并 1、2、4... 元素的堆栈。在这种情况下,您不必关心顺序,因为 2^n 的合并部分将始终按相同方向排序。但是,此解决方案只是规避了您仅限于使用 push 和 pop 操作的事实;除此之外它在所有方面都相当于归并排序。本质上,如果堆栈数量不受限制,那么您也可以轻松模拟快速排序。

【讨论】:

【参考方案11】:

使用多相归并排序的3栈排序

这应该是实现 3 堆栈排序的最快方法。时间复杂度为 O(n log(n))。目标是在从排序堆栈中弹出项目时以升序结束。这种方法的起源是在旧的大型机磁带驱动器上使用多相合并排序,它可以向后读取(以避免倒带时间),类似于堆栈,因为在排序期间向前写入和向后读取。

多相归并排序的维基文章(使用数组):

http://en.wikipedia.org/wiki/Polyphase_merge_sort

用于 3 堆栈多相排序的示例 C++ 代码,使用指针,一个指针作为每个堆栈的堆栈指针,以及一个指向每次运行结束和每个堆栈结束的指针。运行大小指针用于跟踪运行大小何时在堆栈中增加或减少。当数据在堆栈之间传输时,降序序列标志用于跟踪序列是降序还是升序。它在开始时被初始化,然后在每对运行合并后交替。

typedef unsigned int uint32_t;

static size_t fibtbl[48] =
            0,         1,         1,         2,         3,         5,
             8,        13,        21,        34,        55,        89,
           144,       233,       377,       610,       987,      1597,
          2584,      4181,      6765,     10946,     17711,     28657,
         46368,     75025,    121393,    196418,    317811,    514229,
        832040,   1346269,   2178309,   3524578,   5702887,   9227465,
      14930352,  24157817,  39088169,  63245986, 102334155, 165580141,
     267914296, 433494437, 701408733,1134903170,1836311903,2971215073;

// binary search: return index of largest fib() <= n
size_t flfib(size_t n)

size_t lo = 0;
size_t hi = sizeof(fibtbl)/sizeof(fibtbl[0]);
    while((hi - lo) > 1)
        size_t i = (lo + hi)/2;
        if(n < fibtbl[i])
            hi = i;
            continue;
        
        if(n > fibtbl[i])
            lo = i;
            continue;
        
        return i;
    
    return lo;


// poly phase merge sort array using 3 arrays as stacks, a is source
uint32_t * ppmrg3s(uint32_t a[], uint32_t b[], uint32_t c[], size_t n)

    if(n < 2)
        return a;
    uint32_t *asp = a;                  // a stack ptr
    uint32_t *aer;                      // a end run
    uint32_t *aes = a + n;              // a end
    uint32_t *bsp = b + n;              // b stack ptr
    uint32_t *ber;                      // b end run
    uint32_t *bes = b + n;              // b end
    uint32_t *csp = c + n;              // c stack ptr
    uint32_t *ces = c + n;              // c end
    uint32_t *rsp = 0;                  // run size change stack ptr
    size_t ars = 1;                     // run sizes
    size_t brs = 1;
    size_t scv = 0-1;                   // size change increment / decrement
    bool dsf;                           // == 1 if descending sequence
                                       // block for local variable scope
        size_t f = flfib(n);            // fibtbl[f+1] > n >= fibtbl[f]
        dsf = ((f%3) == 0);             // init compare flag
        if(fibtbl[f] == n)             // if exact fibonacci size, move to b
            for(size_t i = 0; i < fibtbl[f-1]; i++)
                *(--bsp) = *(asp++);
         else                         // else move to b, c
            // update compare flag
            dsf ^= (n - fibtbl[f]) & 1;
            // move excess elements to b
            aer = a + n - fibtbl[f];
            while (asp < aer)
                *(--bsp) = *(asp++);
            // move dummy count elements to c
            aer = a + fibtbl[f - 1];
            while (asp < aer)
                *(--csp) = *(asp++);
            rsp = csp;                  // set run size change ptr
        
                                       // end local variable block
    while(1)                           // setup to merge pair of runs
        if(asp == rsp)                 // check for size count change
            ars += scv;                 //   (due to dummy run size == 0)
            scv = 0-scv;
            rsp = csp;
        
        if(bsp == rsp)
            brs += scv;
            scv = 0-scv;
            rsp = csp;
        
        aer = asp + ars;                // init end run ptrs
        ber = bsp + brs;
        while(1)                       // merging pair of runs
            if(dsf ^ (*asp <= *bsp))   // if a <= b
                *(--csp) = *(asp++);    //   move a to c
                if(asp != aer)          //   if not end a run
                    continue;           //     continue back to compare
                do                      //   else move rest of b run to c
                    *(--csp) = *(bsp++);
                while (bsp < ber);
                break;                  //     and break
             else                     // else a > b
                *(--csp) = *(bsp++);    //   move b to c
                if(bsp != ber)          //   if not end b run
                    continue;           //     continue back to compare
                do                      //   else move rest of a run to c
                    *(--csp) = *(asp++);
                while (asp < aer);
                break;                  //     and break
            
                                       // end merging pair of runs
        dsf ^= 1;                       // toggle compare flag
        if(bsp == bes)                 // if b empty
            if(asp == aes)              //   if a empty, done
                break;
            std::swap(bsp, csp);        //   swap b, c
            std::swap(bes, ces);
            brs += ars;                 //   update run size
         else                         // else b not empty
            if(asp != aes)              //   if a not empty
                continue;               //     continue back to setup next merge
            std::swap(asp, csp);        //   swap a, c
            std::swap(aes, ces);
            ars += brs;                 //   update run size
        
    
    return csp;                          // return ptr to sorted array

使用包含交换函数的自定义堆栈类 (xstack) 进行排序的示例 java 代码。

static final int[] FIBTBL =
        0,         1,         1,         2,         3,         5,
         8,        13,        21,        34,        55,        89,
       144,       233,       377,       610,       987,      1597,
      2584,      4181,      6765,     10946,     17711,     28657,
     46368,     75025,    121393,    196418,    317811,    514229,
    832040,   1346269,   2178309,   3524578,   5702887,   9227465,
  14930352,  24157817,  39088169,  63245986, 102334155, 165580141,
 267914296, 433494437, 701408733,1134903170,1836311903;

// return index of largest fib() <= n
static int flfib(int n)

int lo = 0;
int hi = 47;
    while((hi - lo) > 1)
        int i = (lo + hi)/2;
        if(n < FIBTBL[i])
            hi = i;
            continue;
        
        if(n > FIBTBL[i])
            lo = i;
            continue;
        
        return i;
    
    return lo;


// poly phase merge sort using 3 stacks
static void ppmrg3s(xstack a, xstack b, xstack c)

    if(a.size() < 2)
        return;
    int ars = 1;                        // init run sizes
    int brs = 1;
    int asc = 0;                        // no size change
    int bsc = 0;
    int csc = 0;
    int scv = 0-1;                      // size change value
    boolean dsf;                        // == 1 if descending sequence
                                       // block for local variable scope
        int f = flfib(a.size());        // FIBTBL[f+1] > size >= FIBTBL[f]
        dsf = ((f%3) == 0);             // init compare flag
        if(FIBTBL[f] == a.size())      // if exact fibonacci size,
            for (int i = 0; i < FIBTBL[f - 1]; i++)  //  move to b
                b.push(a.pop());
            
         else                         // else move to b, c
            // update compare flag
            dsf ^= 1 == ((a.size() - FIBTBL[f]) & 1);
            // i = excess run count
            int i = a.size() - FIBTBL[f];
            // j = dummy run count
            int j = FIBTBL[f + 1] - a.size();
            // move excess elements to b
            do
                b.push(a.pop());
            while(0 != --i);
            // move dummy count elements to c
            do
                c.push(a.pop());
            while(0 != --j);
            csc = c.size();
        
                                       // end block
    while(true)                        // setup to merge pair of runs
        if(asc == a.size())            // check for size count change
            ars += scv;                 //   (due to dummy run size == 0)
            scv = 0-scv;
            asc = 0;
            csc = c.size();
        
        if(bsc == b.size())
            brs += scv;
            scv = 0-scv;
            bsc = 0;
            csc = c.size();
        
        int arc = ars;                  // init run counters
        int brc = brs;
        while(true)                    // merging pair of runs
            if(dsf ^ (a.peek() <= b.peek()))
                c.push(a.pop());        // move a to c
                if(--arc != 0)          // if not end a
                    continue;           //   continue back to compare
                do                     // else move rest of b run to c
                    c.push(b.pop());
                while(0 != --brc);
                break;                  //   and break
             else 
                c.push(b.pop());        // move b to c
                if(0 != --brc)          // if not end b
                    continue;           //   continue back to compare
                do                     // else move rest of a run to c
                    c.push(a.pop());
                while(0 != --arc);
                break;                  //   and break
            
                                       // end merging pair of runs
        dsf ^= true;                    // toggle compare flag
        if(b.empty())                  // if end b
            if(a.empty())               //   if end a, done
                break;
            b.swap(c);                  //   swap b, c
            brs += ars;
            if (0 == asc)
                bsc = csc;
         else                         // else not end b
            if(!a.empty())              //   if not end a
                continue;               //     continue back to setup next merge
            a.swap(c);                  //   swap a, c
            ars += brs;
            if (0 == bsc)
                asc = csc;
        
    
    a.swap(c);                          // return sorted stack in a

java自定义栈类:

class xstack
    int []ar;                               // array
    int sz;                                 // size
    int sp;                                 // stack pointer
    public xstack(int sz)                  // constructor with size
        this.ar = new int[sz];
        this.sz = sz; 
        this.sp = sz;
    
    public void push(int d)
        this.ar[--sp] = d;
    
    public int pop()
        return this.ar[sp++];
    
    public int peek()
        return this.ar[sp];
    
    public boolean empty()
        return sp == sz;
    
    public int size()
        return sz-sp;
    
    public void swap(xstack othr)
        int []tempar = othr.ar;
        int tempsz = othr.sz;
        int tempsp = othr.sp;
        othr.ar = this.ar;
        othr.sz = this.sz;
        othr.sp = this.sp;
        this.ar = tempar;
        this.sz = tempsz;
        this.sp = tempsp;
    

【讨论】:

【参考方案12】:

修改代码来自T33C's answer (在 Svante 将语言标签从 c++ 更正为 c 之前给出):stack.top() 只能在 !stack.empty() 时检查

void insert(int element, stack<int> &stack) 
    if (!stack.empty() && element > stack.top()) 
        int top = stack.top();
        stack.pop();
        insert(element, stack);
        stack.push(top);
    
    else 
        stack.push(element);
     


void sortStack(stack<int> & stack) 
    if (!stack.empty()) 
        int top = stack.top();
        stack.pop();
        sortStack(stack);
        insert(top, stack);
    

【讨论】:

(除了不一致的缩进-)这篇文章的意图是什么? 当栈中只有一个元素时,T33c的源代码在插入时不检查栈是否为空,这会在Insert函数中抛出异常。 top() stack 可能为空:不错的捕获。【参考方案13】:

我认为冒泡排序可以通过递归在堆栈上工作。

   void sortStack(stack<int>& st)
        bool flag = true;
        int n = st.size();
        for(int i = 1; i <= n - 1 && flag; i++)
            flag = false;
            bubbleSortStack(st, flag, i);
        
    
    void bubbleSortStack(stack<int>& st, bool& flag, int endSize)
        if(st.size() == endSize)
            return;
        int val = st.top();
        st.pop();
        if(val > st.top())
            flag = true;
            int tmp = st.top();
            st.push(val);
            val = tmp;
        
        bubbleSortStack(st, flag);
        st.push(val);
    

【讨论】:

【参考方案14】:
class Program

    static void Main(string[] args)
    
        Stack<int> stack = new Stack<int>();
        stack.Push(8);
        stack.Push(5);
        stack.Push(3);
        stack.Push(4);
        stack.Push(7);
        stack.Push(9);
        stack.Push(5);
        stack.Push(6);
        Stack<int> tempStack = new Stack<int>();
        while (stack.Count > 0)
        
            int temp = stack.Pop();
            while(tempStack.Count>0 && tempStack.Peek() > temp)
            
                stack.Push(tempStack.Pop());
            
            tempStack.Push(temp);
        
        while (tempStack.Count > 0)
        
            Console.Write(tempStack.Pop() + " ");
        
        Console.ReadLine();
    

【讨论】:

【参考方案15】:

由于没有说明可以使用多少额外的堆栈,所以我假设可以使用任意数量的堆栈来尽可能有效地解决排序问题。

如果您暂时忘记堆栈约束并假设这是一个正常的排序问题。那你怎么解决呢? (提示:归并排序)

使用辅助堆栈对堆栈执行正常的合并排序。时间复杂度 - N*log(n)。

【讨论】:

以上是关于如何仅使用堆栈操作对堆栈进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

如何在汇编中使用堆栈对数组进行排序

如何使用 thunk 操作对 mapDispatchToProps 进行单元测试

如何使用 Redux connect 中的操作对测试组件进行快照?

Stack

集合之Stack

排序堆栈的算法