一、完全二叉树

1、什么是完全二叉树

完全二叉树是这样定义的:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)

上面的定义看起来比较复杂。事实上,完全二叉树的节点满足如下规律:

  1. 所有节点只存在3种情况:1、有左右2个孩子;2、只有左孩子;3、没有孩子(叶节点),即不存在只有右孩子的情况;
  2. 我们采用层序遍历的方式遍历节点,当遍历到某一个节点,它只有左孩子,或者没有孩子,则后序所有节点一定都是叶节点。

如下所示是完全二叉树

2、怎么判断是否是完全二叉树

        根据上述规律,我们可以设计算法:采用层序(广度优先)方法遍历节点,每一个节点都要满足上诉条件1。当遍历到某个节点不是有左右孩子时,开始检测后面节点是否全部是叶节点,如果满足,则是完全二叉树。

至于怎么写层序遍历,可以参考下面文章二叉树的宽度(广度)优先遍历C++_二叉树广度优先遍历c语言-CSDN博客

代码如下

bool IsCST(TreeNode* root)
{
    bool flag = false; //一个flag,记录已经遍历到不是左右孩子双全的节点
    if (root == nullptr)
    {
        return true;
    }

    std::queue<TreeNode*> node_que;
    node_que.push(root);
    while(!node_que.empty())
    {
        // 弹出队首节点
        TreeNode* node = node_que.front();
        node_que.pop();

        // flag为true
        if(flag)
        {
            // 条件二:flag是true,存在任何孩子都不是完全二叉树
            if(node->left || node->right) return false;
        }
        else
        {
            if (node->left)
            {
                // 左孩子入队
                node_que.push(node->left);
                //条件二:有左孩子没有右孩子,则flag置true
                if (!node->right)
                {
                    flag = true;
                }
                else
                {
                    // 右孩子入队
                    node_que.push(node->right);
                }
            }
            else
            {
                // 条件一:无左孩子但有右孩子,不是完全二叉树
                if(node->right) return false;
                // 条件二:无左孩子无右孩子,则flag同样置true
                flag = true;
            }
        }
    }
    return true;
}

二、搜索二叉树

1、什么是搜索二叉树

一个二叉搜索树要满足如下条件:

  • 节点的左子树只包含 小于 当前节点的数
  • 节点的右子树只包含 大于 当前节点的数
  • 所有左子树和右子树自身必须也是二叉搜索树。

空树也是二叉搜索树。

例如下面是一个二叉搜索树:

2、怎么判断是否是搜索二叉树

        二叉搜索树要求任意节点的左子树都要比该节点小,而右子树比该节点大。那么,我们如果采用中序遍历的方式遍历一个二叉搜索树,理应得到一个严格递增的数列。所以我们可以考虑结合中序遍历的算法实现对搜索二叉树的判断。

        我们改变一下中序遍历的代码,就可以得到如下算法。

int s_val = 0; // 记录上一个节点的值
bool b_first = true; //记录是不是中序第一个节点
bool IsBST(TreeNode *root) {
	// 根节点为空,直接返回
	if (nullptr == root) {
		return true;
	}
	// 1)判断左子树是不是搜索二叉树
	if (!IsBST(root->left)) {
		return false;
	}

	// 比较当前值与左子树最后一个值
	if (!b_first && root->val < s_val) {
		return false;
	}
	else {
		s_val = root->val;
		b_first = false;
	}
	// 3)判断右子树是不是搜索二叉树
	return IsBST(root->right);
}

三、平衡二叉树

1、什么是平衡二叉树

一棵平衡二叉树必须要满足如下条件:

  • 每个节点左右两个子树的高度差不超过1
  • 每个左子树和右子树自身必须也是平衡二叉树

空树也是平衡二叉树。

例如下图就是一棵平衡二叉树

2、怎么判断是否是平衡二叉树

根据平衡二叉树的性质,我们只要判断左右子树分别是平衡二叉树,并且左右子树的高度差在1以内即可。

我们很自然的想到用递归法求解。在递归中我们需要提取2个信息:一是子树是否是平衡的;二是子树的高度,方便返回判断是否和兄弟子树的高度相差在1以内。

代码如下

struct Result{
    int height; //树的高度
    bool is_balance; //是否平衡
    Result(int l=0, bool is_b=true):height(l), is_balance(is_b){}
};

Result IsBalanceTree(TreeNode* root)
{
    if (root == nullptr)
    {
        return Result();
    }

    Result ret_left = IsBalanceTree(root->left);
    Result ret_right = IsBalanceTree(root->right);
    int lev = std::max(ret_left.height, ret_right.height) + 1;
    int lv_diff = std::abs(ret_left.height - ret_right.height);

    if (!ret_left.is_balance || !ret_right.is_balance || lv_diff > 1)
    {
        return Result(lev, false);
    }

    return Result(lev, true);
}

四、满二叉树

1、什么是满二叉树

如果一棵树除最后一层无任何子节点外,每一层上的所有节点都有两个子节点,则这棵树是满二叉树。

2、怎么判断是否是满二叉树

        假设根节点所在是1层,则一棵满二叉树的每k层共有2^(k-1)个节点,一棵满二叉树的总结点个数为(2^k) -1。根据这个数学特征,我们采用层序遍历的方法可以很方便地判断是否是满二叉树。

代码如下

bool IsFullTree(TreeNode* root)
{
    if (root == nullptr) return true;

    std::queue<TreeNode*> node_que;
    std::unordered_map<TreeNode*, int> node_lv; //记录每个节点所在层数
    int cur_lv = 1; //记录当前遍历到的层数
    int node_count = 0; // 当前层节点计数
    node_que.push(root);
    node_lv[root] = 1;
    while(!node_que.empty())
    {
        // 弹出队首节点
        TreeNode* node = node_que.front();
        node_que.pop();
        //换层
        if (node_lv[node] != cur_lv)
        {
            // 计算上一层是否满足条件,不满足数学条件,则返回false
            if(node_count != pow(2, cur_lv-1)) return false;
            // 重置此层节点计数
            node_count = 0;
            cur_lv = node_lv[node];
        }
        node_count++;
        // 左孩子进队列
        if (node->left)
        {
            node_que.push(node->left);
            node_lv[node->left] = cur_lv + 1;
        }
        // 右孩子进队列
        if (node->right)
        {
            node_que.push(node->right);
            node_lv[node->right] = cur_lv + 1;
        }
    }
    //检测最后一层是否满足节点个数条件
    if(node_count != pow(2, cur_lv-1)) return false;
    return true;
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐