普通二叉树

1.概念

1.1什么是二叉树

二叉树是一种树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。每个节点包含一个值,并且可能有一个指向其左子节点和右子节点的指针(引用)。
二叉树具有以下特性:

1.根节点(Root): 树的顶部节点,没有父节点。
2.节点(Node): 树中的每个元素。
3.父节点(Parent): 一个节点的直接上级节点。
4.子节点(Child): 一个节点的直接下级节点,分为左子节点和右子节点。
5.叶节点(Leaf): 没有子节点的节点称为叶节点或叶子。
6.深度(Depth): 从根节点到当前节点的唯一路径上的边数。
7.高度(Height): 从当前节点到叶节点的最长路径上的边数。
8.层级(Level): 根节点的层级为1,其子节点的层级为2,依此类推。
9.二叉搜索树(Binary Search Tree,BST): 一种特殊类型的二叉树,其中左子树的所有节点的值小于根节点的值,右子树的所有节点的值大于根节点的值,且左右子树也分别是二叉搜索树。

二叉树在计算机科学中应用广泛,常用于实现搜索、排序、解析树等算法和数据结构。它们可以高效地支持各种操作,例如插入、删除和搜索。

当然这也是一颗二叉树

1.2 二叉树的应用

普通二叉树基本没有什么价值,有价值的二叉树如(搜索二叉树,红黑树,AVL树,哈夫曼树......)

这些二叉树在计算机科学和软件工程中有许多重要的应用。以下是一些常见的应用场景:

1.搜索和排序: 二叉搜索树(BST)是一种重要的数据结构,它支持高效的插入、删除和搜索操作。由于其有序性质,BST经常用于实现搜索和排序算法。
2.解析树(Parse Tree): 二叉树被广泛用于编译器中的语法分析阶段,用于表示编程语言的语法结构。解析树也称为语法树,它将源代码解析为树形结构,以便进一步分析和处理。
3.表达式求值: 二叉树可以用于表示算术表达式,例如数学表达式或布尔表达式。这些表达式树可以用于进行求值、优化和转换。
4.文件系统: 文件系统中的目录结构通常可以用二叉树表示,其中每个目录是一个节点,包含指向其子目录的指针。
5.数据库索引: 数据库中常用的B树和B+树是二叉树的变体,它们用于构建索引以支持快速的数据检索操作。
6.网络路由: 在计算机网络中,路由表通常用二叉树或其它树形结构来表示,以确定数据包的最佳路径。
7.游戏编程: 二叉树可以用于构建游戏中的场景图、行为树或寻路算法等。
8.哈夫曼编码(Huffman Coding): 二叉树被用于哈夫曼编码中,通过构建最优二叉树来实现数据压缩。
9.机器学习和决策树: 在机器学习中,决策树是一种常用的模型,它可以用于分类和回归任务,每个节点代表一个特征,每个分支代表一个特征值。
10.线段树(Segment Tree): 线段树是一种特殊的二叉树,用于高效地解决一维区间查询问题,如最小值、最大值、区间和等。

这些只是二叉树应用的一部分,它们在计算机科学的许多领域都发挥着关键作用,为问题的建模、算法设计和系统实现提供了有效的解决方案。

1.3 为什么要学习普通的二叉树

那么普通的二叉树既然没有用,还需要去学习吗。答案是有必要去学习

尽管二叉树在某些特定的应用中可能没有直接的用处,但学习它仍然是值得的。以下是一些学习二叉树的理由:

1.基础数据结构: 二叉树是许多更复杂数据结构的基础,例如二叉搜索树、AVL树、红黑树等。了解二叉树的基本概念和操作可以为学习这些更高级的数据结构打下良好的基础。
2.算法和编程: 学习二叉树可以帮助您理解许多经典的算法和编程问题,例如树的遍历、树的构建、深度优先搜索(DFS)、广度优先搜索(BFS)等。
3.编程面试准备: 在技术面试中,二叉树问题经常被用作考察面试者的编程能力和数据结构知识。因此,熟悉二叉树可以帮助您在面试中更好地表现自己。
4.计算机科学教育: 二叉树通常被用作计算机科学课程的一部分,作为介绍数据结构和算法的一种方式。通过学习二叉树,您可以加深对计算机科学基础概念的理解。
5.扩展思维: 学习二叉树可以培养抽象思维和问题求解能力。尽管某些二叉树问题可能在实际开发中很少出现,但通过解决这些问题,您可以锻炼自己的思维方式,为解决更复杂的问题做好准备。

因此,即使在某些情况下二叉树可能不直接应用于您的工作或项目中,学习它仍然是有价值的,因为它可以帮助您建立更广泛的计算机科学知识和编程技能。

2.代码编程

2.1 前中后序的遍历

可以看之前的博客二叉树的(前,中,后序)遍历-CSDN博客

2.2 二叉树节点的个数

计算二叉树节点的个数可以通过递归或迭代两种方式实现。以下是两种方法的介绍:
1. 递归方法:
递归方法是一种直观且简洁的方式,它利用了二叉树的递归结构。

1.基本思路: 二叉树节点的个数等于根节点的个数加上左子树节点的个数和右子树节点的个数之和。
2.递归函数: 编写一个递归函数,该函数接受一个二叉树节点作为参数,返回以该节点为根的子树中节点的个数。
3.递归终止条件: 当节点为空时,返回 0。

下面是递归方法的示例代码:

//计算二叉树中节点的数量,包括根节点在内。
int BinaryTreeSize(BTNode* root)
{

/*
	1. 如果输入的根节点 `root` 为空(即指向 `NULL`),说明这棵树是空树,此时返回节点数量为0。
	2. 如果根节点不为空,则递归地计算左子树和右子树的节点数量,并将它们相加。
	同时,根节点本身也算作一个节点,所以要额外加1。
	3. 最终返回的值就是整棵树的节点数量。

	这种实现利用了递归的思想,通过不断地向下递归计算子树的节点数量,最终得到整棵树的节点数量。
*/
	return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

2. 迭代方法:
迭代方法通常使用广度优先搜索(BFS)或深度优先搜索(DFS)来遍历二叉树,并在遍历的过程中统计节点个数。

4.基本思路: 使用队列或栈来辅助遍历二叉树,并在遍历过程中统计节点个数。
5.迭代算法: 使用迭代算法,遍历二叉树的每个节点,并在遍历过程中统计节点个数。下面是迭代方法(使用广度优先搜索)的示例代码:

def countNodes(root):
    if root is None:
        return 0

    count = 0
    queue = [root]

    while queue:
        node = queue.pop(0)
        count += 1

        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

    return count

无论是递归方法还是迭代方法,都能够有效地计算出二叉树节点的个数。选择哪种方法取决于个人偏好和实际情况。

2.3 二叉树的叶子节点数量

计算二叉树的叶子节点数量可以使用递归或迭代两种方法,与计算节点总数类似。叶子节点是指没有子节点的节点。
1. 递归方法:
递归方法同样是一种直观且简洁的方式,它利用了二叉树的递归结构。

1.基本思路: 叶子节点的数量等于根节点是否为空的判断,如果为空则返回 0;否则,如果根节点没有左右子节点,则返回 1;否则,返回左子树和右子树叶子节点数量之和。
2.递归函数: 编写一个递归函数,该函数接受一个二叉树节点作为参数,返回以该节点为根的子树中叶子节点的数量。
3.递归终止条件: 当节点为空时,返回 0;当节点没有左右子节点时,返回 1。下面是递归方法的示例代码:

//叶子节点的数量
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	//没有子节点的节点称为叶子节点,也称为终端节点。
	//所以我们只需要确定有一个节点的左子节点和右子节点都为空就返回1
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//最后将遍历后得到的左右子节点数相加
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

2. 迭代方法:
迭代方法同样可以使用广度优先搜索或深度优先搜索来统计叶子节点数量。

4.基本思路: 使用队列或栈来辅助遍历二叉树,并在遍历的过程中统计叶子节点数量。
5.迭代算法: 使用迭代算法,遍历二叉树的每个节点,并在遍历过程中判断节点是否为叶子节点。下面是迭代方法(使用广度优先搜索)的示例代码:

def countLeaves(root):
    if root is None:
        return 0

    count = 0
    queue = [root]

    while queue:
        node = queue.pop(0)
        if node.left is None and node.right is None:
            count += 1
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

    return count

递归方法和迭代方法都能够有效地计算出二叉树的叶子节点数量。选择哪种方法取决于个人偏好和实际情况。

2.4 二叉树销毁

销毁二叉树意味着释放二叉树中所有节点所占用的内存,并将树的根节点指针置为空。通常,可以使用递归方法来实现二叉树的销毁。

void BinaryTreeDestory(BTNode** root) {
	if (*root == NULL)
	{
		return;
	}
	BinaryTreeDestory(&(*root)->left);
	BinaryTreeDestory(&(*root)->right);
	free(*root);
	*root = NULL;
}

2.5 前序遍历构建一颗二叉树

以此为例前序遍历的数组"ABD##E#H##CF##G##"构建二叉树

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTdatatype* a, int n, int* pi)
{
	if (*pi >= n || a[*(pi)] == '#'){
		(*pi)++;
		return NULL;
	}
	BTNode* cur = (BTNode*)malloc(sizeof(BTNode));
	cur->data = a[*pi];
	(*pi)++;
	cur->left = BinaryTreeCreate(a, n, pi);
	cur->right = BinaryTreeCreate(a, n, pi);
	return cur;
}

通过构建这么一颗二叉树,我们调用一下前面的接口进行测试代码

2.6 二叉树第k层节点个数

要计算二叉树第 k 层节点的个数,可以通过递归或迭代两种方法实现。
1. 递归方法:
递归方法是一种直观且简洁的方式,它利用了二叉树的递归结构。

1.基本思路: 递归地遍历二叉树,对于每个节点,如果该节点所在的层级等于 k,则将其计数;否则,递归遍历其左右子树。
2.递归函数: 编写一个递归函数,该函数接受一个二叉树节点和当前层级作为参数,返回以该节点为根的子树中第 k 层节点的数量。
3.递归终止条件: 当节点为空或当前层级大于 k 时,返回 0;当当前层级等于 k 时,返回 1。

下面是递归方法的示例代码:

//子问题:要找k层的节点个数,可看做要找每一层的k-1层的节点个数
//结束条件:当要找的层数为1的时候或者其节点为空时结束
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (0 == k)
		return 0;
	if (NULL == root)
		return 0;
	if (1 == k)
		return 1;
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

2. 迭代方法:
迭代方法可以使用广度优先搜索或深度优先搜索来遍历二叉树,并在遍历的过程中统计第 k 层节点的数量。

4.基本思路: 使用队列或栈来辅助遍历二叉树,同时记录节点所在的层级,当层级等于 k 时,统计节点数量。
5.迭代算法: 使用迭代算法,遍历二叉树的每个节点,并在遍历过程中判断节点所在的层级,当层级等于 k 时,统计节点数量。下面是迭代方法(使用广度优先搜索)的示例代码:

def countNodesAtKLevel(root, k):
    if root is None or k < 1:
        return 0

    count = 0
    level = 1
    queue = [(root, level)]

    while queue:
        node, current_level = queue.pop(0)
        if current_level == k:
            count += 1
        elif current_level > k:
            break

        if node.left:
            queue.append((node.left, current_level + 1))
        if node.right:
            queue.append((node.right, current_level + 1))

    return count

无论是递归方法还是迭代方法,都能够有效地计算出二叉树第 k 层节点的个数。选择哪种方法取决于个人偏好和实际情况。

2.7 二叉树查找值为x的节点

要查找二叉树中值为 x 的节点,可以通过递归或迭代两种方法实现。
1. 递归方法:
递归方法是一种直观且简洁的方式,它利用了二叉树的递归结构。

1.基本思路: 递归地遍历二叉树,对于每个节点,如果节点的值等于 x,则返回该节点;否则,递归地在其左右子树中查找。
2.递归函数: 编写一个递归函数,该函数接受一个二叉树节点和要查找的值 x 作为参数,返回值为 x 的节点(如果存在)。
3.递归终止条件: 当节点为空时,返回 None;当节点的值等于 x 时,返回该节点。

下面是递归方法的示例代码:

BTNode* BinaryTreeFind(BTNode* root, BTdatatype x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* left = BinaryTreeFind(root->left, x);
	if (left != NULL)
		return left;
	BTNode* right = BinaryTreeFind(root->right, x);
	if (right != NULL)
		return left;

		return NULL;
}

2. 迭代方法:
迭代方法通常使用广度优先搜索(BFS)或深度优先搜索(DFS)来遍历二叉树,并在遍历的过程中查找值为 x 的节点。

4.基本思路: 使用队列或栈来辅助遍历二叉树,同时在遍历过程中查找值为 x 的节点。
5.迭代算法: 使用迭代算法,遍历二叉树的每个节点,并在遍历过程中判断节点的值是否等于 x。下面是迭代方法(使用广度优先搜索)的示例代码:

def findNode(root, x):
    if root is None:
        return None

    queue = [root]

    while queue:
        node = queue.pop(0)
        if node.val == x:
            return node

        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)

    return None

无论是递归方法还是迭代方法,都能够有效地在二叉树中查找值为 x 的节点。选择哪种方法取决于个人偏好和实际情况。

2.8  层序遍历

二叉树的层序遍历是一种广泛使用的遍历方法,它按层级(从上到下、从左到右)访问树中的每个节点。最常用的实现方式是使用队列来进行迭代处理。这里详细介绍层序遍历的基本思路及其实现。

基本思路

层序遍历的核心思想是使用一个队列来存储即将访问的节点,保证节点按照层的顺序被处理。具体步骤如下:

  1. 初始化队列: 将根节点入队。
  2. 遍历队列: 只要队列不为空,执行以下操作:
    • 从队列中取出一个节点。
    • 访问该节点(通常是输出节点值或将节点值添加到结果列表中)。
    • 如果该节点有左子节点,将左子节点入队。
    • 如果该节点有右子节点,将右子节点入队。
  3. 重复以上过程,直到队列为空。

这种方法确保每个节点都按从上到下和从左到右的顺序被访问,并且每个节点只被访问一次

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

void BinaryTreeLevelOrder(TreeNode* root) {
    if (root == NULL) return;

    queue<TreeNode*> q;
    q.push(root);

    while (!q.empty()) {
        TreeNode* node = q.front();
        q.pop();
        cout << node->val << " "; // 访问节点

        if (node->left != NULL) {
            q.push(node->left);
        }
        if (node->right != NULL) {
            q.push(node->right);
        }
    }
}

2.9 判断二叉树是否是完全二叉树

这个函数的基本思路是使用广度优先搜索(BFS)来遍历二叉树,并在遍历的过程中判断是否满足完全二叉树的性质。

1.广度优先搜索(BFS): 通过队列实现广度优先搜索,从根节点开始,逐层遍历二叉树节点。
2.判断条件:


3.在遍历过程中,如果遇到一个节点的左子节点为空,但右子节点不为空,或者遇到一个节点的左子节点不为空,但右子节点为空,那么该二叉树一定不是完全二叉树。
4.如果遍历过程中遇到一个节点的左右子节点不全为空,而后续的节点中出现了非空节点,那么该二叉树也不是完全二叉树。


5.遍历结束条件:


6.当队列为空时,遍历结束。


7.返回结果:


8.如果遍历结束后没有触发上述条件,说明二叉树满足完全二叉树的性质,返回 true,否则返回 false。

这个函数的实现基于上述思路,利用队列实现了广度优先搜索,同时在遍历的过程中根据特定条件进行判断。

bool isComplete(BTNode* root);

// 判断是否是完全二叉树的函数
int BinaryTreeComplete(BTNode* root) {
    if (isComplete(root)) {
        return 1; // 是完全二叉树
    } else {
        return 0; // 不是完全二叉树
    }
}

// 辅助函数:判断是否是完全二叉树
bool isComplete(BTNode* root) {
    if (root == NULL) {
        return true;
    }

    // 创建一个队列用于层序遍历
    BTNode* queue[1000];
    int front = 0, rear = 0;
    queue[rear++] = root;

    while (front < rear) {
        BTNode* node = queue[front++];
        
        if (node == NULL) {
            // 遇到空节点时,检查队列中是否还有非空节点
            while (front < rear) {
                if (queue[front++] != NULL) {
                    return false;
                }
            }
            return true;
        }

        // 将当前节点的左右子节点加入队列
        queue[rear++] = node->left;
        queue[rear++] = node->right;
    }

    return true;
}

3.每期一问

int tmp(int *a, int* b)
{
    return *a - *b;
}
int triangleNumber(int* nums, int numsSize) {
    // int* copynums = nums;
    int ret = 0;
    /* 排序 */
    qsort(nums, numsSize, sizeof(int), tmp);
    /* 二分查找 */
    for(int i = 0;i < numsSize; i++)
    {
        for(int j = i + 1; j < numsSize; j++)
        {
            int left = j + 1;
            int right = numsSize - 1;
            int k = j;
            while(left <= right)
            {
                int mid = (left + right) / 2;
                if(nums[mid] < nums[i] + nums[j])
                {
                    k = mid;
                    left = mid + 1;
                }
                else
                {
                    right = mid - 1;
                }
            }
            ret += k - j;
        }
    } 

    return ret;
}

本期问题:. - 力扣(LeetCode)

  • 34
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值