模拟栈的几个方法

模拟栈的几个方法

任何递归操作都是通过操作系统提供的函数栈实现的,所以我们如果自己模拟一个栈,同样可以将递归操作转换为栈上的非递归操作。

计算机函数调用栈的原理

SS:栈段寄存器(stack-segment register),用于指示栈所在的内存块

SP:栈顶寄存器(stack pointer register),指向栈顶位置。

注意:X86 的栈顶是向下增长的,如果向栈中 PUSH 数据,则 SP 指向的内存地址是更低位置

以下都是以 X86 指令集为例。

请先阅读此文:当我们调用一个函数的时候,发生了什么?

以中序遍历为例

递归的代码如下:

 1private:
 2    void _process(TreeNode *root, vector<int> &ret)
 3    {
 4        if (root == nullptr)
 5        {
 6            return;
 7        }
 8        _process(root->left, ret);
 9        ret.push_back(root->val);        
10        _process(root->right, ret);
11    }
12
13public:
14    vector<int> inorderTraversal(TreeNode *root)
15    {
16        vector<int> ret;
17        _process(root, ret);
18        return ret;
19    }

要改成非递归模式,我们需要模拟出一个调用栈。由于调用栈是随时可以访问的,所以应该将其设置为共有的,所以我们将它作为一个参数传入。

 1-    void _process(TreeNode *root, vector<int> &ret)
 2+    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 3    {
 4        if (root == nullptr)
 5        {
 6            return;
 7        }
 8        _process(root->left, ret);
 9        ret.push_back(root->val);        
10        _process(root->right, ret);
11    }

此外由于是改成非递归调用,需要将所有调用本函数的形式写成一样的。下面就是全部写成了:

1_process(root, ret, callstack);

而为了保证形式的一致,我们加上了诸如 root = root->left; 的语句:

 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3        if (root == nullptr)
 4        {
 5            return;
 6        }
 7-       _process(root->left, ret);
 8+		root = root->left;
 9+       _process(root, ret, callstack);
10        ret.push_back(root->val);        
11-       _process(root->right, ret);
12+		root = root->right;
13+       _process(root, ret, callstack);
14    }

但是这样又会导致原本的 root 丢失。怎么办?很简单,保存上下文!将其压入栈。所以开头加上一句

1callstack.push(root);

而执行完还要恢复上下问,所以还得加上对应的 pop 语句:

 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3        if (root == nullptr)
 4        {
 5            return;
 6        }
 7+       callstack.push(root);
 8        root = root->left;
 9        _process(root, ret, callstack);
10+       root = callstack.top();
11+       callstack.pop();
12        ret.push_back(root->val);
13        root = root->right;
14        _process(root, ret, callstack);
15	}

pop 自然要考虑为空的情况。如果调用栈为空就可以退出程序了:

 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3        if (root == nullptr)
 4        {
 5            return;
 6        }
 7        callstack.push(root);
 8        root = root->left;
 9        _process(root, ret, callstack);
10+       if(callstack.empty()){
11+        return;
12+       }
13        root = callstack.top();
14        callstack.pop();
15        ret.push_back(root->val);
16        root = root->right;
17        _process(root, ret, callstack);
18	}

另外,原本的 return 条件应该改成恢复执行:

 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3        if (root == nullptr)
 4        {
 5+            goto resume;
 6        }
 7        callstack.push(root);
 8        root = root->left;
 9        _process(root, ret, callstack);
10+ resume:
11        if(callstack.empty()){ 
12          return;
13        }
14        root = callstack.top();
15        callstack.pop();
16        ret.push_back(root->val);
17        root = root->right;
18        _process(root, ret, callstack);
19	}

两个 _process 调用也可以直接跳转到函数头:

 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3+ proc:
 4        if (root == nullptr)
 5        {
 6            goto resume;
 7        }
 8        callstack.push(root);
 9        root = root->left;
10+       goto proc;
11resume:
12        if(callstack.empty()){ 
13          return;
14        }
15        root = callstack.top();
16        callstack.pop();
17        ret.push_back(root->val);
18        root = root->right;
19+       goto proc;
20	}

至此,改造基本完毕。运行程序也能得到正确的答案。只不过代码的 goto 语句太多,造成逻辑上的混乱。我们一个一个消掉:

 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3    proc:
 4        if (root != nullptr)
 5        {
 6            callstack.push(root);
 7            root = root->left;
 8            goto proc;
 9        }
10        if (callstack.empty())
11        {
12            return;
13        }
14        root = callstack.top();
15        callstack.pop();
16        ret.push_back(root->val);
17        root = root->right;
18        goto proc;
19    }

再整理:

 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3    proc:
 4        if (root != nullptr)
 5        {
 6            callstack.push(root);
 7            root = root->left;
 8            goto proc;
 9        }
10        if (!callstack.empty())
11        {
12
13            root = callstack.top();
14            callstack.pop();
15            ret.push_back(root->val);
16            root = root->right;
17            goto proc;
18        }
19    }
 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3    proc:
 4        if (root != nullptr)
 5        {
 6            callstack.push(root);
 7            root = root->left;
 8            goto proc;
 9        }
10        else if (!callstack.empty())
11        {
12
13            root = callstack.top();
14            callstack.pop();
15            ret.push_back(root->val);
16            root = root->right;
17            goto proc;
18        }
19        else
20        {
21            return;
22        }
23    }
 1    void _process(TreeNode *root, vector<int> &ret, stack<TreeNode *> &callstack)
 2    {
 3    proc:
 4        if (root != nullptr)
 5        {
 6            callstack.push(root);
 7            root = root->left;
 8        }
 9        else if (!callstack.empty())
10        {
11            root = callstack.top();
12            callstack.pop();
13            ret.push_back(root->val);
14            root = root->right;
15        }
16        else
17        {
18            return;
19        }
20        goto proc;
21    }

最终答案:

 1    vector<int> inorderTraversal(TreeNode *root)
 2    {
 3        vector<int> ret;
 4        stack<TreeNode *> stack;
 5        while (!(root == nullptr && stack.empty()))
 6        {
 7            if (root != nullptr)
 8            {
 9                stack.push(root);
10                root = root->left;
11            }
12            else if (!stack.empty())
13            {
14                root = stack.top();
15                stack.pop();
16                ret.push_back(root->val);
17                root = root->right;
18            }
19        }
20        return ret;
21    }

以等差数列求和为例

上面的比较简单,因为不涉及返回值。试试这个:

剑指 Offer 64. 求1+2+…+n - 力扣(LeetCode) (leetcode-cn.com)

 1#include <iostream>
 2#include <cstdio>
 3#include <vector>
 4
 5using namespace std;
 6
 7class Solution
 8{
 9public:
10    int sumNums(int n)
11    {
12        if(n == 0) return 0;
13        return n + sumNums(n - 1);
14    }
15};
16
17int main(int argc, char const *argv[])
18{
19    Solution s;
20    printf("%d\n", s.sumNums(100));
21    return 0;
22}

实际上这种类型是尾递归。所有尾递归都可以转换为这样的形式:

1var stack = [];
2stack.push(first);
3
4while (!stack.empty()) {
5    o = stack.pop();
6    // ...
7}

因此高斯求和问题可以转换为:

 1#include <cstdio>
 2#include <stack>
 3
 4using namespace std;
 5
 6stack<int> s;
 7
 8void push(stack<int> &s, int v)
 9{
10    s.push(v);
11}
12
13int pop(stack<int> &s)
14{
15    int v = s.top();
16    s.pop();
17    return v;
18}
19
20int sum(int n)
21{
22    push(s, n);
23    int eax, o;
24    while (!s.empty())
25    {
26        o = pop(s);
27        eax = eax + o;
28        if (o == 0)
29            break;
30        else
31            push(s, o - 1);
32    }
33    return eax;
34}
35
36int main()
37{
38    int a = sum(100);
39    printf("%d\n", a);
40    return 0;
41}

以斐波那契数列为例

1    int fib(int n)
2    {
3        if(n == 2 || n == 1) return 1;
4        if(n <= 0) return 0;        
5        return fib(n - 2) + fib(n - 1);
6    }

将其转化为栈将变成这样:

 1#include <cstdio>
 2#include <stack>
 3
 4using namespace std;
 5
 6stack<int> s;
 7
 8void push(stack<int> &s, int v)
 9{
10    //printf("push %d\n", v);
11    s.push(v);
12}
13
14int pop(stack<int> &s)
15{
16    int v = s.top();
17    //printf("pop %d\n", v);
18    s.pop();
19    return v;
20}
21int fib0(int n)
22{
23    if (n == 2 || n == 1)
24        return 1;
25    if (n <= 0)
26        return 0;
27    return fib0(n - 2) + fib0(n - 1);
28}
29
30int fib(int n)
31{
32    int eax = 0, o;
33    push(s, n);
34    while (!s.empty())
35    {
36        o = pop(s);
37        if (o == 2 || o == 1)
38        {
39            eax += 1;
40        }
41        else if (o <= 0)
42        {
43            eax += 0;
44        }
45        else
46        {
47            push(s, o - 1);
48            push(s, o - 2);
49        }
50    }
51    return eax;
52}
53
54int main()
55{
56    int n = 15;
57    int a = fib(n);
58    int b = fib0(n);
59    printf("%d, expect %d\n", a, b);
60    return 0;
61}

不过实际上你会发现,自己用栈实现反而性能会大幅度下降。不要迷信“非递归”