编程我只用CPP 发布的文章

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/range-sum-query-immutable

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

示例:

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()。

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

说明:

  1. 你可以假设数组不可变。
  2. 会多次调用 sumRange 方法。

二、题解

题目很迷幻,一眼看上去可能会误解成就是求指定区间的和,如果只是求指定区间内的和,除了遍历就没有其他办法了。而实际上整道题目的重点就是会多次调用,也就是说同一份数据,会有多个测试案例。

很容易能猜出最容易出现问题的地方就是超时了,因为实际的问题很简单,就是求和,求和只能遍历,不可能就这么简单每次就遍历求和。因此考虑的重点就在于如何在多次调用中减少时间占用。

减少计算时间的办法:缓存数据,即缓存下来部分数据的和,再根据缓存的和来计算特定范围的和。

以测试数据为例,以sum[i]表示下标i以前(不包括i)的所有数据之和,那么sum数组的值为:

0, -2, -2, 1, -4, -2, -3

sumRange(0, 2)就相当于计算sum[2 + 1] - sum[0] ,值为1。

sumRange(2, 5)就相当于计算sum[5 + 1] - sum[2],值为-2。

在这种情况下能总结出来的规律是:sumRange(i, j) = sum[j + 1] - sum[i]。

三、代码

逻辑很简单,但关键点是:sum[i]表示的是i以前所有元素的和,所以要给sum[0]赋值上一个0值。

class NumArray {
private:
    vector<int> sum;
public:
    NumArray(vector<int>& nums) {
        // 构造函数中初始化sum数组
        size_t i;
        // 给sum[0]附上一个0值
        sum.push_back(0);
        for (i = 0; i< nums.size(); i++) {
            sum.push_back(sum[i] + nums[i]);
        }
    }

    int sumRange(int i, int j) {
        return sum[j + 1] - sum[i];
    }
};

优化

可以看到,时间消耗还是很大的,排名才击败了38%。代码中主要的耗时是vector数组的扩容导致的,因为够早的时候需要频繁push_back导致数组扩容和内存拷贝,比较费时。可以把vector改成数组形式,手动分配内存空间,减少vector频繁扩容。

class NumArray {
private:
    int *sum;
public:
    NumArray(vector<int>& nums) {
        size_t i;
        sum = new int[nums.size() + 1];
        sum[0] = 0;
        for (i = 0; i< nums.size(); i++) {
            sum[i + 1] = sum[i] + nums[i];
        }
    }

    int sumRange(int i, int j) {
        return sum[j + 1] - sum[i];
    }
};

重新提交后,时间消耗击败了86%的用户。

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/binary-search

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的 target,如果目标值存在返回下标,否则返回-1

示例1:

  • 输入:nums = [-1,0,3,5,9,12], target = 9
  • 输出:4
  • 解释:9出现在nums中并且下标为 4

示例 2:

  • 输入:nums = [-1,0,3,5,9,12], target = 2
  • 输出:-1
  • 解释:2不存在nums中因此返回 -1

提示:

  1. 你可以假设 nums 中的所有元素是不重复的。
  2. n 将在 [1, 10000]之间。
  3. nums 的每个元素都将在 [-9999, 9999]之间。

二、二分查找

二分查找的前提要求是数组有序,一趟二分查找的算法描述为:

  1. 确定数组上下边界,然后取中间值mid。
  2. 对比中间值mid和目标值target,此时有两种情况:

    • 如果target小于mid,说明目标值在下边界区,继续在下半边界执行步骤1。
    • 如果target大于mid,说明目标值在上边界区,继续在上半边界执行步骤1。
  3. 如果上下边界一直找完都没有找到目标值,说明数组中不存在指定数据。

图文描述

在数组[2, 3, 4, 5, 6, 7, 8]中查找3。第一步,计算mid是下标为3的值5,此时5比目标值3要大,说明3出现在5左边,要在[low, mid)进行下一步查找:

第二步,重新定位修改区域,查找的下标范围是[0, 2]。此时mid计算出来等于1,下标等于1的元素值为3,刚好就是我们要找的目标值,查找完成:

三、代码

需要注意的点已经在代码中标出:

  1. 注意有符号、无符号以及数据是否会溢出。当前题目中数据不大,所以直接使用int。
  2. 循环结束的条件是low > high,所以循环条件一定是low <= high,不要少了等于符号。
  3. 没有找到目标值的时候,返回-1。
这么简单的一个题目,提交了4次才通过。。。。基础很重要!!!
/*
* 二分查找法法
* @nums 数组
* @target 待查找的目标元素
* @return 找到返回数组下标,没找到返回-1
*/
int search(vector<int> &nums, int target) {
    // [1] 注意数据溢出
    int low, high, mid;

    low = 0;
    high = nums.size() - 1;

    // [2] 判断条件是小于等于,而不是小于
    while (low <= high) {
        mid = (low + high) / 2;
        if (target < nums[mid]) {
            high = mid - 1;
        } else if (target > nums[mid]) {
            low = mid + 1;
        } else {
            return mid;
        }
    }
    // [3] 没找到默认返回-1
    return -1;
}

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/paint-fence

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

有 k 种颜色的涂料和一个包含 n 个栅栏柱的栅栏,每个栅栏柱可以用其中一种颜色进行上色。

你需要给所有栅栏柱上色,并且保证其中相邻的栅栏柱 最多连续两个 颜色相同。然后,返回所有有效涂色的方案数。

注意:n 和 k 均为非负的整数。

示例

  • 输入:n = 3,k = 2
  • 输出:6
  • 解析:用 c1 表示颜色 1,c2 表示颜色 2,所有可能的涂色方案有

                柱 1    柱 2   柱 3     
     -----      -----  -----  -----       
       1         c1     c1     c2 
       2         c1     c2     c1 
       3         c1     c2     c2 
       4         c2     c1     c1  
       5         c2     c1     c2
       6         c2     c2     c1

二、题解

leetcode标注是简单题目,但是对我这种脑袋不太灵光的人来说,并不太简单,所以多参考了几种解题思路。

2.1 递推

假设前一个栅栏的颜色数量为f(n - 1),再前一个栅栏的颜色数量为f(n - 2

递推的思路:

  1. 如果当前栅栏颜色和前一个栅栏颜色一样,它的选择是k - 1(因为当前栅栏和前一个栅栏颜色一样,所以前一个栅栏颜色和更前一个展览的颜色不能一样,少一种颜色选择),这种情况的颜色数量为f(n - 2) * (k - 1)
  2. 如果当前栅栏颜色和前一个栅栏颜色不一样,那么当前栅栏的颜色选择就只有k - 1种,总的颜色数量为f(n - 1) * (k - 1)

总结出来的递推公式:f(n) = f(n - 1) * (k - 1) + f(n - 2) * (k - 1)

代码:

class Solution {
public:
     int numWays(int n, int k) {
        int prev, pprev, rs, i;
        if (n <= 0 || k <= 0)
            return 0;

        if (n == 1)
            return k;

        pprev = k;
        prev = k * k;
        rs = prev;
        for (i = 2; i < n; i++) {
            rs = pprev * (k - 1) + prev * (k - 1);
            pprev = prev;
            prev = rs;
        }

        return rs;
    }
};

2.2 动态规划

以前面两个栅栏的颜色来确定当前栅栏的颜色:

  1. 如果前两个栅栏的颜色相同,那么当前栅栏的颜色有k - 1种。
  2. 如果前两个栅栏的颜色不同,那么当前栅栏的颜色有k种。

分别以dp1和dp2保存颜色相同和不同的种数,可以总结出来的状态转移方程:

  • dp1 = prev_dp1
  • dp2 = (k - 1) * (dp1 + dp2)

dp1是相同的颜色数量,它和上一个栅栏颜色相同,所以就等于上一个栅栏颜色数量。dp2是不同的颜色数量,它等于上一个栅栏颜色总量的和乘以k - 1

代码:

class Solution {
public:
    int numWays(int n, int k) {
        int dp1, dp2, rs, i;
        if (n <= 0 || k <= 0)
            return 0;

        if (n == 1)
            return k;

        dp1 = k;
        dp2 = k * (k - 1);
        for (i = 2; i < n; i++) {
            rs = (dp1 + dp2) * (k - 1);
            dp1 = dp2;
            dp2 = rs;
        }
        return dp1 + dp2;
    }
};

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/binary-tree-postorder-traversal

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个二叉树,返回它的 后序 遍历。

例如输入[1,null,2,3] :

   1
    \
     2
    /
   3 

输出:

[3,2,1]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?

二、题解

参考:二叉树的后序遍历

三、代码

3.1 通过栈+链表实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode *> s;
        list<TreeNode *> l;
        vector<int> v;
        
        TreeNode *p;
        if (root == nullptr) {
            return v;
        }
        
        s.push(root);
        while (!s.empty()) {
            p = s.top();
            s.pop();
            l.push_front(p);
            if (p->left) {
                s.push(p->left);
            }
            if (p->right) {
                s.push(p->right);
            }
        }
        
        while(!l.empty()) {
            v.push_back(l.front()->val);
            l.pop_front();
        }
        
        return v;
    }
};

3.2 通过2次压栈实现

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode *p;
        vector<int> v;
        stack<TreeNode *> st;
        if (root == nullptr) {
            return v;
        }
        
        st.push(root); st.push(root);
        while (!st.empty()) {
            p = st.top();
            st.pop();
            if (!st.empty() && st.top() == p) {
                if (p->right) {
                    st.push(p->right); st.push(p->right);
                }
                if (p->left) {
                    st.push(p->left); st.push(p->left);
                }
            } else {
                v.push_back(p->val);
            }
        }

        return v;
    }
};

一、后序遍历

后序遍历逻辑:优先访问左、右子节点,然后访问当前节点。

一个后序遍历的示例,它的后序遍历结果为[4, 2, 5, 6, 3, 1]:

二、非递归实现

后序遍历的非递归实现比前序和中序的非递归实现要复杂很多,因为每个节点都可能有2个子节点,遍历的时候节点中并没有保存当前的状态,无法确定两个子节点是否已经访问过了。因此,下面采用两个方法来实现非递归访问。

2.1 实现方式一

通过栈+链表配合,流程如下:

  1. 根节点入栈,然后循环遍历访问栈顶元素。
  2. 每访问一个元素,把该元素插入到链表开头,然后分别把左、右子节点压栈。
  3. 循环1-2步,完成后把链表中的节点顺序输出。

代码实现:

void postorder_traversal2(tree_node<T> *node, vector<T> &v) {
    stack<tree_node<T> *> s;
    list<tree_node<T> *> l;
    typename list<tree_node<T> *>::iterator it;
    tree_node<T> *p;
    if (node == nullptr)
        return;

    s.push(node);
    while (!s.empty()) {
        // 取栈顶元素
        p = s.top();
        s.pop;
        // 插入到链表的开头
        l.push_front(p);
        // 左子树入栈
        if (p->lchild)
            s.push(p->lchild);
        // 右子树入栈
        if (p->rchild)
            s.push(p->rchild);
    }

    // 结果放到vector中去
    while (!l.empty()) {
        p = l.front();
        l.pop_front();
        v.push_back(p->val);
    }
}

2.2 实现方式二

非递归的后序遍历和前、中序遍历一样,也依赖栈这个数据结构,原理:

  1. 把根节点压栈两次,然后循环判断,当栈不为空的时候,取出栈顶元素。
  2. 把取出的栈顶元素和栈当前的栈顶元素对比,如果相等,则把右子节点和左子节点也分别压入栈两次;如果不相等,则访问当前元素。
压栈两次的作用:判断第几次访问节点,第一次访问到节点的时候要先访问子节点,第二次才是访问自己。

一个特别要注意的地方是:栈是先进后出的,所以在压入左右子节点的时候,要先压入右子节点。

代码实现:

void postorder_traversal(tree_node<T> *node, vector<T> &v) {
    stack<tree_node<T> *> st;
    tree_node<T> *p;
    
    if (node == nullptr)
        return;

    // 根元素压栈2次
    st.push(node);
    st.push(node);
    while (!st.empty()) {
        // 弹出栈顶元素
        p = st.top();
        st.pop();
        if (!st.empty() && p != st.top()) { // 如果栈顶元素和刚取出的相等,说明还没有访问过
            // 先压入右子节点
            if (p->rchild) {
                st.push(p->rchild);
                st.push(p->rchild);
            }
            // 再压入左子节点
            if (p->lchild) {
                st.push(p->lchild);
                st.push(p->lchild);
            }
        } else { // 已经访问过了
            v.push_back(p->data);
        }
    }
}

三、递归实现

/*
 * 后序遍历的递归实现
 * @node 需要遍历的节点
 * @v 保存遍历结果的数组
 */
void postorder_traversal(tree_node<T> *node, vector<T> &v) {
    if (node == nullptr)
        return;
    
    if (node->lchild)
        postorder_traversal(node->lchild);
    if (node->rchild)
        postorder_traversal(node->rchild);
    v.push_back(node->data);
}

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/binary-tree-inorder-traversal

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

一、题目描述

给定一个二叉树,返回它的中序遍历。

例如输入: [1,null,2,3]

   1
    \
     2
    /
   3

输出:

[1,3,2]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?

二、题解

参考:二叉树的中序遍历(递归和非递归实现)

三、代码

3.1 非递归代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> v;
        stack<TreeNode *> st;
        TreeNode *p;
        p = root;
        while (p || !st.empty()) {
            // 循环遍历左子树
            while (p) {
                st.push(p);
                p = p->left;
            }

            if (!st.empty()) {
                // 弹出栈顶节点
                p = st.top();
                st.pop();
                // 访问节点
                v.push_back(p->val);
                // 访问右子树
                p = p->right;
            }
        }
        return v;
    }
};

3.2 递归代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
private:
    void inorderTraversal(TreeNode *root, vector<int> &v) {
        if (root == nullptr) {
            return ;
        }
        if (root->left) {
            inorderTraversal(root->left, v);
        }
        v.push_back(root->val);
        if (root->right) {
            inorderTraversal(root->right, v);
        }
    }
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> v;
        inorderTraversal(root, v);
        return v;
    }
};

一、中序遍历

中序遍历过程:先访问左子节点,然后访问当前节点,最后访问右子节点。

以下试一次中序遍历过程:

二、非递归实现

非递归方式遍历依赖栈来实现,因为要先访问子节点,然后访问父节点,因此必须要有一个数据结构来保存已经父节点。

实现原理:

  1. 每次遍历到一个节点,不访问它,先压栈,然后对左子树循环做这个操作。
  2. 遍历左节点直到左节点为空时,取出栈顶元素,访问它。
  3. 然后对这个栈顶元素的右节点重复执行第一个步骤。

大致的逻辑和前序遍历差不多,相对于前序遍历而言,中序遍历知识把访问节点的时机延后了一些。前序遍历可以参考二叉树的先序遍历

代码描述:

/*
 * 中序遍历的非递归实现
 * @node 需要遍历的节点
 * @v 保存遍历结果的数组
 */
void in_order_traversal_by_stack(tree_node<T> *node, vector<T> &v) {
    stack<tree_node<T> *> st;
    tree_node<T> *p;

    p = node;
    while (p || !st.empty()) {
        // 左子树压栈
        while (p) {
            st.push(p);
            p = p->lchild;
        }

        if (!st.empty()) {
            // 取出栈顶元素
            p = st.top();
            st.pop();
            // 访问节点
            v.push_back(p->data);
            // 设置指针为右子节点
            p = p->rchild;
        }
    }
}

三、递归实现

递归实现很简单,不做详细描述:

/*
 * 中序遍历的递归实现
 * @node 需要遍历的节点
 * @v 保存遍历结果的数组
 */
void in_order_traversal(tree_node<T> *node, vector<T> &v) {
    if (node == nullptr)
        return;

    if (node->lchild)
        in_order_traversal(node->lchild, v);

    v.push_back(node->data);

    if (node->rchild)
        in_order_traversal(node->rchild, v);
}

一、题目描述

给定一个二叉树,返回它的前序遍历结果。

例如输入二叉树[1,null,2,3]

   1
    \
     2
    /
   3 

输出:

[1,2,3]
进阶: 递归算法很简单,你可以通过迭代算法完成吗?

二、题解

二叉树的前序遍历,递归和非递归方式。

参考:二叉树的前序遍历

三、代码

3.1 递归代码

class Solution {
    void preorderTraversal(TreeNode *root, vector<int> &v) {
        if (root == nullptr) {
            return;
        }
        v.push_back(root->val);
        if (root->left) {
            preorderTraversal(root->left, v);
        }
        if (root->right) {
            preorderTraversal(root->right, v);
        }
    }
public:
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> v;
        preorderTraversal(root, v);
        return v;
    }
};

3.2 非递归代码

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode *> st;
        TreeNode *p;
        vector<int> v;

        p = root;
        while (p || !st.empty()) {
            while (p) {
                // 遍历当前节点
                v.push_back(p->val);
                st.push(p);
                // 遍历左子树
                p = p->left;
            }

            // 通过栈来遍历右子树
            if (!st.empty()) {
                p = st.top()->right;
                st.pop();
            }
        }

        return v;
    }
};

一、先序遍历

先序遍历的意思是:先遍历当前节点,再分别遍历左、右子节点。

例如一棵二叉树为:

它的先序遍历序列(红色虚线标出来的)为:[1, 2, 4, 3, 5 6]。

二、递归实现

递归的实现很简单,先访问当前节点,然后分别递归访问左右子节点。

/*
 * 先序遍历的递归实现
 * @node 需要遍历的节点
 * @v 保存遍历结果的数组
 */
void pre_order_traversal(tree_node<T> *node, vector<T> &v) {
    if (node == nullptr)
        return;

    // 遍历当前节点
    v.push_back(node->data);

    // 遍历左子树
    if (node->lchild)
        pre_order_traversal(node->lchild);

    // 遍历右子树
    if (node->rchild)
        pre_order_traversal(node->rchild);
}

三、非递归实现

非递归的方式依赖栈来实现,逻辑:

  1. 访问根节点,把当前节点压栈。然后设置当前节点为左子节点,重复过程,直到左子节点为空。
  2. 设置当前节点为栈顶节点的右子节点,重复1过程。

3.1 图文描述

以上面的二叉树为例,首先访问根元素1,压栈:

然后继续访问1节点的左节点2,压栈:

节点2没有左子节点,退出左树遍历。然后取出栈顶元素2,访问它的右子节点4:

右子节点没有子节点,因此继续从栈顶取元素1,访问它的右子节点3并压栈:

节点3有左子节点,顺着遍历左子节点5,节点5入栈:

此时5没有没有左子节点了,于是取出栈顶元素5,访问它的右子节点。但是它没有右子节点,继续取出栈顶元素3,访问它的右子节点6:

访问完6后,因为没有子节点了。所以继续从栈里取元素,但是此时栈也为空了,遍历结束。

3.2 代码描述

/*
 * 先序遍历的非递归实现
 * @node 需要遍历的节点
 * @v 保存遍历结果的数组
 */
void pre_order_traversal(tree_node<T> *root, vector<T> &v) {
    stack<tree_node<T> *> st;
    tree_node<T> *p;

    p = root;
    while (p != nullptr || !st.empty()) {
        // 遍历左子树
        while (p) {
            // 访问当前节点
            v.push_back(p->data);
            // 当前节点入栈
            st.push(p);
            // 遍历左子节点
            p = p->lchild;
        }

        // 取栈顶元素,访问父亲节点的右子节点
        if (!st.empty()) {
            p = st.top()->rchild;
            st.pop();
        }
    }
}

一、题目

给定一颗二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?树中的节点除了有两个分别指向左右子节点的指针,还有一个指向父节点的指针。

以下面的二叉树为例,它的中序遍历序列是:[2, 4, 1, 5, 3, 6],节点4的下一个节点是1,节点3的下一个节点是5。

二、分析

画出上面这棵二叉树的中序遍历过程为:

根据中序遍历的特性,可以整理出来的下一个节点的规律:

  1. 如果节点有右子节点,那么下一个节点一定在右子树中,这种又分成了2种情况。

    • 右儿子没有子节点,那么下一个节点就是子节点(节点2
    • 如果右子节点有子节点,下一个节点就是右子节点种最左的节点(节点1)。
  2. 如果节点没有右子节点,并且是父节点的左子节点,那么下一个节点就是父亲节点(节点5)。
  3. 如果节点没有右子节点,并且是父节点的右子节点,那就递归查找父节点,直到一个父节点是祖父节点的左子树(节点4)。如果找到根节点都没有找到,就说明该节点已经是最后一个了(节点6)。

三、代码

类节点定义:

template<typename T>
class tree_node {
private:
    T data;
    tree_node *lchild;
    tree_node *rchild;
    tree_node *parent;
};

查找中序遍历序列中下一个节点的成员函数:

// 获取中序遍历的下一个节点
const tree_node<T> *get_next_node_in_order() const {
    const tree_node<T> *p, *father;
    if (rchild) { // 有右节点,在右子树中查找
        p = rchild;
      
        // 循环遍历左子树
        while (p->lchild)
            p = p->lchild;
      
        return p;
    } else if (parent) {
        p = this;
        father = parent;
      
        // 只要不是父节点的左子节点,一直往上找
        while (father && p != father->lchild) {
            p = father;
            father = father->parent;
        }

        // 返回父亲节点
        return father;
    }
    return nullptr;
}

四、单元测试

单元测试主要覆盖几个点:

  • 节点有右子节点。
  • 节点没有右子节点,存在一个父节点是祖先节点的左子节点。
  • 节点没有右子节点,不存在一个父节点是祖先节点的左子节点。
/*
 * 测试案例:查找二叉树中序遍历的下一个节点
 *         1
 *      /     \
 *     2       3
 *      \     / \
 *       4   5   6
 */
TEST(bin_tree, get_next_node_in_order) {
    bin_tree<int> tree;
    const tree_node<int> *node;
    int in[] = {2, 4, 1, 5, 3, 6}; // 先序遍历序列
    int pre[] = {1, 2, 4, 3, 5, 6}; // 中序遍历序列
    
    // 根据先序遍历序列和中序遍历序列生成二叉树
    tree.construct(pre, in, 6);
    EXPECT_TRUE(tree.get_root());

    // 定位到中序遍历的开始节点2,然后依次获取下一个节点,和in数组对比
    node = tree.get_root()->get_lchild();
    for (int i = 0; i < 6; i++) {
        EXPECT_TRUE(node) << i;
        EXPECT_EQ(node->get_data(), in[i]);
        node = tree.get_next(node);
    }
    EXPECT_EQ(node, nullptr);
}

测试结果: