user-0 提问: 有大佬可以用C语言给我讲解Morris遍历吗？

回复:
user: user-1
content: 因为栈的思想是最基础的思想，基础掌握了，再去扩展优化就容易多了。 如果没有基础思想做引导，可能一上来很难想到Morris遍历，因为刚开始推导时不容易发现其中规律，所以一般都用基础的栈思想先建模尝试，然后在这基础上找出新规律，最后才能整理总结新的思想。

user: user-0
content: 引用 1 楼 user-1 的回复：这种算法怎么想出来，其实也不难，主要是你能发现其中的规律 举个例子，斐波那契数列 1，1，2，3，5，8，13，21，34。。。 F(1)=1，F(2)=1, F(n)=F(n - 1)+F(n - 2)（n ≥ 3） 题目：给出任意n，求出n对应的斐波那契数 首先，最容易就是想到就是按公式递归算法 long long fb1(int n) {     if (n<3) return 1L;     return fb1(n-2) + fb1(n-1); } 然后又会想到，把递归改成非递归算法（通常就是用数组会栈来代替） long long fb2(int n) {     int i;     long long *a = (long long*)malloc(sizeof(long long)*n), res;     a[0]=a[1]=1;     for (i=2;i<n;i++) {         a[i] = a[i-2]+a[i-1]; //这里用数组很符合公式的思想     }     res = a[n-1];     free(a);     return res; } 于是，有人就觉得，这种非递归的算法有点浪费空间，通过上面的数组来看，有用的数据其实a[0]，a[1]和a[n-1]，其它的都是中间过程，纯属占用空间，所以能不能把这些空间省掉，中间过程只用a[0]和a[1]来完成？就好比以下图的循环 数列a： 1，1，2，3，5，8，13，21，34。。。 循环1：a，b 循环2：-，a，b 循环3：-，-，a，b 循环n：-，-，-，-，a，b 可以看到，中间过程其实只用a，b两个变量就够了，n对应的斐波那契数就是循环结束后的b 所以非递归改为 long long fb3(int n) {     int i;     long long a=1, b=1; //只用a，b两个变量     for (i=2; i<n; i++) {         b = a+b;         a = b-a;     }     return b; //最终返回循环结束后的b } 就这样，非递归算法即能省空间，而时间又和原来的非递归算法一样，只循环n次。 所以这种想法的产生就是对非递归算法优化的结果（在原来非递归的基础上节省空间，只需要用常量个变量，即O(1)空间复杂度）  同样的，Morris遍历也是在这种思想上产生的。 我们知道，二叉树的非递归遍历通常用栈，每次都把父节点入栈，然后把指针变量指向左节点，继续循环遍历左子树，可以发现，在循环过程中，只有一个当前节点的指针变量有意义，循环中不断修改该指针变量指向左节点或右节点就可以了，然后遍历到头（指针为NULL）后，就开始从栈中弹出父节点，然后把指针变量修改为父节点的右节点，继续循环遍历右子树。所以不难发现，整个过程，只需要两个变量，一个是当前节点指针变量，一个保存子树遍历完（也就是当前节点指针为NULL）后该回退的哪个父节点的指针变量，其它在栈内出栈入栈的指针变量都是中间过程变量，所以有人就会有和上面的斐波那契数列一样的想法，能不能只用两个变量，中间过程变量都省略呢？答案也是肯定。这就是Morris遍历。  Morris遍历随便谷歌百度一下就有说明和代码例子，这里就不具体展开代码来说明了。这里是想说明一下这种想法的来源（如上述，在非递归算法上优化），以及说明一下它的两个变量的意义。首先第一个当前节点指针就不多说了，因为循环中不断遍历节点，只要修改该指针的值为指向左节点或右节点就可以了。重要是的是当前节点指向NULL时，怎么让他跳转回父节点（也就是怎么保存这个父节点）？我们知道，二叉树都是先遍历左，再遍历右（父节点在前中后被访问决定是前中后序遍历，也就是父节点访问只是插在其中，不影响先左后右的顺序），所以一个子树遍历到最后一个节点必然是最右的叶子节点。知道了这个规律，我们就可以在子树的最右一个叶节点的右节点保存父节点就可以了，这样我们遍历到最右的叶子节点，再往右取节点时就会取到父节点。当然，如果右子树也是这样处理的话，右子树的最右的叶节点的右节点也保存父节点的话，右子树就会陷入无限循环，所以分开左右子树来分别对待。即如果当前（父）节点的左子树存在则把左子树的最右叶节点的右节点修改为指向当前（父）节点，然后把当前节点指针修改为当前（父）节点的左节点继续遍历左子树。当前节点遍历到左子树的最右叶节点后会重新指向父节点（因为上面修改了最右叶节点的有节点指向父节点），说明左子树已经遍历完了，此时就把原来左子树的最右叶节点的右节点还原回NULL，然后修改当前节点指针为父节点的右节点继续遍历右子树。我还有一个疑问，既然这种思想这么好，为什么教科书上没有提过呢？反而让我们专门学习栈的思想？相比Morris遍历，用栈来解决非递归岂不是浪费空间？

user: user-0
content: 引用 1 楼 user-1 的回复：这种算法怎么想出来，其实也不难，主要是你能发现其中的规律 举个例子，斐波那契数列 1，1，2，3，5，8，13，21，34。。。 F(1)=1，F(2)=1, F(n)=F(n - 1)+F(n - 2)（n ≥ 3） 题目：给出任意n，求出n对应的斐波那契数 首先，最容易就是想到就是按公式递归算法 long long fb1(int n) {     if (n<3) return 1L;     return fb1(n-2) + fb1(n-1); } 然后又会想到，把递归改成非递归算法（通常就是用数组会栈来代替） long long fb2(int n) {     int i;     long long *a = (long long*)malloc(sizeof(long long)*n), res;     a[0]=a[1]=1;     for (i=2;i<n;i++) {         a[i] = a[i-2]+a[i-1]; //这里用数组很符合公式的思想     }     res = a[n-1];     free(a);     return res; } 于是，有人就觉得，这种非递归的算法有点浪费空间，通过上面的数组来看，有用的数据其实a[0]，a[1]和a[n-1]，其它的都是中间过程，纯属占用空间，所以能不能把这些空间省掉，中间过程只用a[0]和a[1]来完成？就好比以下图的循环 数列a： 1，1，2，3，5，8，13，21，34。。。 循环1：a，b 循环2：-，a，b 循环3：-，-，a，b 循环n：-，-，-，-，a，b 可以看到，中间过程其实只用a，b两个变量就够了，n对应的斐波那契数就是循环结束后的b 所以非递归改为 long long fb3(int n) {     int i;     long long a=1, b=1; //只用a，b两个变量     for (i=2; i<n; i++) {         b = a+b;         a = b-a;     }     return b; //最终返回循环结束后的b } 就这样，非递归算法即能省空间，而时间又和原来的非递归算法一样，只循环n次。 所以这种想法的产生就是对非递归算法优化的结果（在原来非递归的基础上节省空间，只需要用常量个变量，即O(1)空间复杂度）  同样的，Morris遍历也是在这种思想上产生的。 我们知道，二叉树的非递归遍历通常用栈，每次都把父节点入栈，然后把指针变量指向左节点，继续循环遍历左子树，可以发现，在循环过程中，只有一个当前节点的指针变量有意义，循环中不断修改该指针变量指向左节点或右节点就可以了，然后遍历到头（指针为NULL）后，就开始从栈中弹出父节点，然后把指针变量修改为父节点的右节点，继续循环遍历右子树。所以不难发现，整个过程，只需要两个变量，一个是当前节点指针变量，一个保存子树遍历完（也就是当前节点指针为NULL）后该回退的哪个父节点的指针变量，其它在栈内出栈入栈的指针变量都是中间过程变量，所以有人就会有和上面的斐波那契数列一样的想法，能不能只用两个变量，中间过程变量都省略呢？答案也是肯定。这就是Morris遍历。  Morris遍历随便谷歌百度一下就有说明和代码例子，这里就不具体展开代码来说明了。这里是想说明一下这种想法的来源（如上述，在非递归算法上优化），以及说明一下它的两个变量的意义。首先第一个当前节点指针就不多说了，因为循环中不断遍历节点，只要修改该指针的值为指向左节点或右节点就可以了。重要是的是当前节点指向NULL时，怎么让他跳转回父节点（也就是怎么保存这个父节点）？我们知道，二叉树都是先遍历左，再遍历右（父节点在前中后被访问决定是前中后序遍历，也就是父节点访问只是插在其中，不影响先左后右的顺序），所以一个子树遍历到最后一个节点必然是最右的叶子节点。知道了这个规律，我们就可以在子树的最右一个叶节点的右节点保存父节点就可以了，这样我们遍历到最右的叶子节点，再往右取节点时就会取到父节点。当然，如果右子树也是这样处理的话，右子树的最右的叶节点的右节点也保存父节点的话，右子树就会陷入无限循环，所以分开左右子树来分别对待。即如果当前（父）节点的左子树存在则把左子树的最右叶节点的右节点修改为指向当前（父）节点，然后把当前节点指针修改为当前（父）节点的左节点继续遍历左子树。当前节点遍历到左子树的最右叶节点后会重新指向父节点（因为上面修改了最右叶节点的有节点指向父节点），说明左子树已经遍历完了，此时就把原来左子树的最右叶节点的右节点还原回NULL，然后修改当前节点指针为父节点的右节点继续遍历右子树。讲得太清楚，太感人了！！

user: user-1
content: 这种算法怎么想出来，其实也不难，主要是你能发现其中的规律 举个例子，斐波那契数列 1，1，2，3，5，8，13，21，34。。。 F(1)=1，F(2)=1, F(n)=F(n - 1)+F(n - 2)（n ≥ 3） 题目：给出任意n，求出n对应的斐波那契数 首先，最容易就是想到就是按公式递归算法 long long fb1(int n) {     if (n<3) return 1L;     return fb1(n-2) + fb1(n-1); } 然后又会想到，把递归改成非递归算法（通常就是用数组会栈来代替） long long fb2(int n) {     int i;     long long *a = (long long*)malloc(sizeof(long long)*n), res;     a[0]=a[1]=1;     for (i=2;i<n;i++) {         a[i] = a[i-2]+a[i-1]; //这里用数组很符合公式的思想     }     res = a[n-1];     free(a);     return res; } 于是，有人就觉得，这种非递归的算法有点浪费空间，通过上面的数组来看，有用的数据其实a[0]，a[1]和a[n-1]，其它的都是中间过程，纯属占用空间，所以能不能把这些空间省掉，中间过程只用a[0]和a[1]来完成？就好比以下图的循环 数列a： 1，1，2，3，5，8，13，21，34。。。 循环1：a，b 循环2：-，a，b 循环3：-，-，a，b 循环n：-，-，-，-，a，b 可以看到，中间过程其实只用a，b两个变量就够了，n对应的斐波那契数就是循环结束后的b 所以非递归改为 long long fb3(int n) {     int i;     long long a=1, b=1; //只用a，b两个变量     for (i=2; i<n; i++) {         b = a+b;         a = b-a;     }     return b; //最终返回循环结束后的b } 就这样，非递归算法即能省空间，而时间又和原来的非递归算法一样，只循环n次。 所以这种想法的产生就是对非递归算法优化的结果（在原来非递归的基础上节省空间，只需要用常量个变量，即O(1)空间复杂度）  同样的，Morris遍历也是在这种思想上产生的。 我们知道，二叉树的非递归遍历通常用栈，每次都把父节点入栈，然后把指针变量指向左节点，继续循环遍历左子树，可以发现，在循环过程中，只有一个当前节点的指针变量有意义，循环中不断修改该指针变量指向左节点或右节点就可以了，然后遍历到头（指针为NULL）后，就开始从栈中弹出父节点，然后把指针变量修改为父节点的右节点，继续循环遍历右子树。所以不难发现，整个过程，只需要两个变量，一个是当前节点指针变量，一个保存子树遍历完（也就是当前节点指针为NULL）后该回退的哪个父节点的指针变量，其它在栈内出栈入栈的指针变量都是中间过程变量，所以有人就会有和上面的斐波那契数列一样的想法，能不能只用两个变量，中间过程变量都省略呢？答案也是肯定。这就是Morris遍历。  Morris遍历随便谷歌百度一下就有说明和代码例子，这里就不具体展开代码来说明了。这里是想说明一下这种想法的来源（如上述，在非递归算法上优化），以及说明一下它的两个变量的意义。首先第一个当前节点指针就不多说了，因为循环中不断遍历节点，只要修改该指针的值为指向左节点或右节点就可以了。重要是的是当前节点指向NULL时，怎么让他跳转回父节点（也就是怎么保存这个父节点）？我们知道，二叉树都是先遍历左，再遍历右（父节点在前中后被访问决定是前中后序遍历，也就是父节点访问只是插在其中，不影响先左后右的顺序），所以一个子树遍历到最后一个节点必然是最右的叶子节点。知道了这个规律，我们就可以在子树的最右一个叶节点的右节点保存父节点就可以了，这样我们遍历到最右的叶子节点，再往右取节点时就会取到父节点。当然，如果右子树也是这样处理的话，右子树的最右的叶节点的右节点也保存父节点的话，右子树就会陷入无限循环，所以分开左右子树来分别对待。即如果当前（父）节点的左子树存在则把左子树的最右叶节点的右节点修改为指向当前（父）节点，然后把当前节点指针修改为当前（父）节点的左节点继续遍历左子树。当前节点遍历到左子树的最右叶节点后会重新指向父节点（因为上面修改了最右叶节点的有节点指向父节点），说明左子树已经遍历完了，此时就把原来左子树的最右叶节点的右节点还原回NULL，然后修改当前节点指针为父节点的右节点继续遍历右子树。