🏆 作者简介,愚公搬代码 🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。 🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
数据结构是计算机科学中的一个重要概念,它描述了数据之间的组织方式和关系,以及对这些数据的访问和操作。常见的数据结构有:数组、链表、栈、队列、哈希表、树、堆和图。
二叉树是一种数据结构,它由节点和连接节点的边组成。每个节点最多有两个子节点,即左子节点和右子节点。二叉树可以用来表示排序算法、搜索算法、哈夫曼编码等。
二叉树的基本思想是将一个问题或数据集合逐步分解为小问题或子集合,最终实现对整个问题或数据集合的处理。一般而言,二叉树有以下几个基本操作:
二叉树是一种重要的数据结构,它广泛应用于计算机科学领域中的算法和数据处理中。
/* 二叉树节点类 */
class TreeNode {
int val; // 节点值
TreeNode? left; // 左子节点引用
TreeNode? right; // 右子节点引用
TreeNode(int x) { val = x; }
}
public class binary_tree {
[Test]
public void Test() {
/* 初始化二叉树 */
// 初始化节点
TreeNode n1 = new TreeNode(1);
TreeNode n2 = new TreeNode(2);
TreeNode n3 = new TreeNode(3);
TreeNode n4 = new TreeNode(4);
TreeNode n5 = new TreeNode(5);
// 构建引用指向(即指针)
n1.left = n2;
n1.right = n3;
n2.left = n4;
n2.right = n5;
Console.WriteLine("\n初始化二叉树\n");
PrintUtil.PrintTree(n1);
/* 插入与删除节点 */
TreeNode P = new TreeNode(0);
// 在 n1 -> n2 中间插入节点 P
n1.left = P;
P.left = n2;
Console.WriteLine("\n插入节点 P 后\n");
PrintUtil.PrintTree(n1);
// 删除节点 P
n1.left = n2;
Console.WriteLine("\n删除节点 P 后\n");
PrintUtil.PrintTree(n1);
}
}
public class Tree<T>
{
public Tree()
{
Deep = 1;
}
public int Deep { get; set; }
public string Name { get; set; }
public T Value { get; set; }
public Tree<T> Perent { get; set; }
private List<Tree<T>> Child = new List<Tree<T>>();
public void AddChild(Tree<T> tree)
{
if (GetByName(tree.Name) != null)
{
return;
}
tree.Perent = this;
tree.Deep = Deep + 1;
Child.Add(tree);
}
public void RemoveChild()
{
Child.Clear();
}
public Tree<T> GetByName(string name)
{
Tree<T> root = GetRootTree(this);
List<Tree<T>> list = GatAll(root);
var result = list.Where(c => c.Name == name).ToList();
if (result.Count <= 0)
{
return null;
}
else
{
return result[0];
}
}
public List<Tree<T>> GatAll(Tree<T> tree)
{
List<Tree<T>> list = new List<Tree<T>>();
list.Add(tree);
if (tree.Child == null)
{
return null;
}
list.AddRange(tree.Child);
foreach (var tree1 in tree.Child)
{
list.AddRange(GatAll(tree1));
}
return list.Distinct().ToList();
}
public Tree<T> GetRootTree(Tree<T> tree)
{
if (tree.Perent == null)
{
return tree;
}
return GetRootTree(tree.Perent);
}
public int GetDeep(Tree<T> tree)
{
List<Tree<T>> list = GetDeepTree(tree);
return list.Max(c => c.Deep);
}
public List<Tree<T>> GetDeepTree(Tree<T> tree)
{
List<Tree<T>> list = new List<Tree<T>>();
if (tree.Child.Count <= 0)
{
list.Add(tree);
}
else
{
foreach (var tree1 in tree.Child)
{
if (tree1.Child.Count <= 0)
{
list.Add(tree1);
}
else
{
foreach (var tree2 in tree1.Child)
{
list.AddRange(GetDeepTree(tree2));
}
}
}
}
return list;
}
}
满二叉树是一种特殊的二叉树,每个节点要么没有子节点(为叶子节点),要么有两个子节点,且所有叶子节点都在同一层级上。满二叉树的节点数为 $2^h-1$,其中 $h$ 为树的高度。满二叉树具有以下特点:
满二叉树的一个例子如下所示:
1
/ \
2 3
/ \ / \
4 5 6 7
其中,根节点为 1,它的两个子节点为 2 和 3,2 又有两个子节点 4 和 5,3 也有两个子节点 6 和 7。这棵满二叉树的高度为 3。
满二叉树在计算机科学中应用广泛,例如堆排序、线段树、哈夫曼树等算法和数据结构中都会用到。
完全二叉树是一种特殊的二叉树,它满足以下两个条件:
其中,第k层可以有1~2^(k-1)个结点,而1至k-1层结点数都是满的,即2^i-1个(i=1,2,...,k-1)。
完全二叉树的特点以及性质:
完全二叉树的应用:
完满二叉树是一棵二叉树,其中每个非叶子节点都有两个子节点,并且所有叶子节点都在同一层。换句话说,完满二叉树是一个深度为d且恰好有2^d−1个节点的二叉树。
例如,下面就是一棵完满二叉树:
1
/ \
2 3
/ \
4 5
完满二叉树在数据结构和算法中非常常见,因为它的结构良好且易于实现。它的一个特殊用途是在堆排序中使用。
平衡二叉树(AVL树)是一种特殊的二叉搜索树,它的高度平衡性比一般的二叉搜索树更好,可以使得树的高度保持在O(logn)。平衡二叉树的本质是二叉搜索树,所以它具有二叉搜索树的所有特点,即左子树上的所有节点的值都比根节点小,右子树上的所有节点的值都比根节点大。
平衡二叉树的特点:
平衡二叉树的优点:
平衡二叉树的缺点:
当二叉树的每层节点都被填满时,达到“完美二叉树”;而当所有节点都偏向一侧时,二叉树退化为“链表”。
二叉树层序遍历是二叉树的一种遍历方式,也叫广度优先遍历。它按照从上到下、从左到右的顺序遍历二叉树的所有节点,可以得到二叉树所有节点的层次信息。层序遍历可以用来实现二叉树的序列化和反序列化,也可以用来解决一些树的问题,如二叉树的最小深度、二叉树的最大深度、二叉树的宽度等问题。
二叉树层序遍历的实现需要用到队列这一数据结构,具体实现方法如下:
首先,我们将根节点入队。接下来,进入一个循环,每次取出队首的节点,并将该节点的左右子节点入队。然后再从队列中取出下一个节点,直到队列为空。在过程中,我们需要将每个节点的值存储下来,以便于最后输出。
层序遍历的时间复杂度为 $O(n)$,其中 $n$ 是二叉树的节点数。这是因为我们需要遍历每个节点且每个节点仅会入队一次。而空间复杂度为 $O(w)$,其中 $w$ 是二叉树的最大宽度。因为在最坏情况下,队列中存储的节点数最多不会超过二叉树的最大宽度,即二叉树最后一层的节点数。
public class binary_tree_bfs {
/* 层序遍历 */
public List<int> levelOrder(TreeNode root) {
// 初始化队列,加入根节点
Queue<TreeNode> queue = new();
queue.Enqueue(root);
// 初始化一个列表,用于保存遍历序列
List<int> list = new();
while (queue.Count != 0) {
TreeNode node = queue.Dequeue(); // 队列出队
list.Add(node.val); // 保存节点值
if (node.left != null)
queue.Enqueue(node.left); // 左子节点入队
if (node.right != null)
queue.Enqueue(node.right); // 右子节点入队
}
return list;
}
[Test]
public void Test() {
/* 初始化二叉树 */
// 这里借助了一个从数组直接生成二叉树的函数
TreeNode? root = TreeNode.ListToTree(new List<int?> { 1, 2, 3, 4, 5, 6, 7 });
Console.WriteLine("\n初始化二叉树\n");
PrintUtil.PrintTree(root);
List<int> list = levelOrder(root);
Console.WriteLine("\n层序遍历的节点打印序列 = " + string.Join(",", list));
}
}
在二叉树中,遍历指的是按照一定顺序依次访问树中所有节点的过程。常见的二叉树遍历方式包括前序遍历、中序遍历和后序遍历。
注:以上三种遍历方式的顺序均为节点的访问顺序,即访问左、右子树部分时仍然按照对应遍历方式的顺序进行。例如,在前序遍历中,先访问左子树的根节点,然后遍历左子树的左子树,最后是左子树的右子树。
public class binary_tree_dfs {
List<int> list = new();
/* 前序遍历 */
void preOrder(TreeNode? root) {
if (root == null) return;
// 访问优先级:根节点 -> 左子树 -> 右子树
list.Add(root.val);
preOrder(root.left);
preOrder(root.right);
}
/* 中序遍历 */
void inOrder(TreeNode? root) {
if (root == null) return;
// 访问优先级:左子树 -> 根节点 -> 右子树
inOrder(root.left);
list.Add(root.val);
inOrder(root.right);
}
/* 后序遍历 */
void postOrder(TreeNode? root) {
if (root == null) return;
// 访问优先级:左子树 -> 右子树 -> 根节点
postOrder(root.left);
postOrder(root.right);
list.Add(root.val);
}
[Test]
public void Test() {
/* 初始化二叉树 */
// 这里借助了一个从数组直接生成二叉树的函数
TreeNode? root = TreeNode.ListToTree(new List<int?> { 1, 2, 3, 4, 5, 6, 7 });
Console.WriteLine("\n初始化二叉树\n");
PrintUtil.PrintTree(root);
list.Clear();
preOrder(root);
Console.WriteLine("\n前序遍历的节点打印序列 = " + string.Join(",", list));
list.Clear();
inOrder(root);
Console.WriteLine("\n中序遍历的节点打印序列 = " + string.Join(",", list));
list.Clear();
postOrder(root);
Console.WriteLine("\n后序遍历的节点打印序列 = " + string.Join(",", list));
}
}
/ 使用 int? 可空类型 ,就可以使用 null 来标记空位
int?[] tree = { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
/* 数组表示下的二叉树类 */
public class ArrayBinaryTree {
private List<int?> tree;
/* 构造方法 */
public ArrayBinaryTree(List<int?> arr) {
tree = new List<int?>(arr);
}
/* 节点数量 */
public int size() {
return tree.Count;
}
/* 获取索引为 i 节点的值 */
public int? val(int i) {
// 若索引越界,则返回 null ,代表空位
if (i < 0 || i >= size())
return null;
return tree[i];
}
/* 获取索引为 i 节点的左子节点的索引 */
public int left(int i) {
return 2 * i + 1;
}
/* 获取索引为 i 节点的右子节点的索引 */
public int right(int i) {
return 2 * i + 2;
}
/* 获取索引为 i 节点的父节点的索引 */
public int parent(int i) {
return (i - 1) / 2;
}
/* 层序遍历 */
public List<int> levelOrder() {
List<int> res = new List<int>();
// 直接遍历数组
for (int i = 0; i < size(); i++) {
if (val(i).HasValue)
res.Add(val(i).Value);
}
return res;
}
/* 深度优先遍历 */
private void dfs(int i, string order, List<int> res) {
// 若为空位,则返回
if (!val(i).HasValue)
return;
// 前序遍历
if (order == "pre")
res.Add(val(i).Value);
dfs(left(i), order, res);
// 中序遍历
if (order == "in")
res.Add(val(i).Value);
dfs(right(i), order, res);
// 后序遍历
if (order == "post")
res.Add(val(i).Value);
}
/* 前序遍历 */
public List<int> preOrder() {
List<int> res = new List<int>();
dfs(0, "pre", res);
return res;
}
/* 中序遍历 */
public List<int> inOrder() {
List<int> res = new List<int>();
dfs(0, "in", res);
return res;
}
/* 后序遍历 */
public List<int> postOrder() {
List<int> res = new List<int>();
dfs(0, "post", res);
return res;
}
}
public class array_binary_tree {
[Test]
public void Test() {
// 初始化二叉树
// 这里借助了一个从数组直接生成二叉树的函数
List<int?> arr = new List<int?> { 1, 2, 3, 4, null, 6, 7, 8, 9, null, null, 12, null, null, 15 };
TreeNode root = TreeNode.ListToTree(arr);
Console.WriteLine("\n初始化二叉树\n");
Console.WriteLine("二叉树的数组表示:");
Console.WriteLine(arr.PrintList());
Console.WriteLine("二叉树的链表表示:");
PrintUtil.PrintTree(root);
// 数组表示下的二叉树类
ArrayBinaryTree abt = new ArrayBinaryTree(arr);
// 访问节点
int i = 1;
int l = abt.left(i);
int r = abt.right(i);
int p = abt.parent(i);
Console.WriteLine("\n当前节点的索引为 " + i + " ,值为 " + abt.val(i));
Console.WriteLine("其左子节点的索引为 " + l + " ,值为 " + (abt.val(l).HasValue ? abt.val(l) : "null"));
Console.WriteLine("其右子节点的索引为 " + r + " ,值为 " + (abt.val(r).HasValue ? abt.val(r) : "null"));
Console.WriteLine("其父节点的索引为 " + p + " ,值为 " + (abt.val(p).HasValue ? abt.val(p) : "null"));
// 遍历树
List<int> res = abt.levelOrder();
Console.WriteLine("\n层序遍历为:" + res.PrintList());
res = abt.preOrder();
Console.WriteLine("前序遍历为:" + res.PrintList());
res = abt.inOrder();
Console.WriteLine("中序遍历为:" + res.PrintList());
res = abt.postOrder();
Console.WriteLine("后序遍历为:" + res.PrintList());
}
}
二叉搜索树,也称为二叉查找树(Binary Search Tree,BST),是一种特殊的二叉树。它的特殊之处在于,对于每个节点,它的左子树中的所有节点值都小于它本身的值,而它的右子树中的所有节点值都大于它本身的值。因此,二叉搜索树可以用来实现动态的查找、插入和删除操作。
二叉搜索树的性质:
基本操作:搜索、插入、删除。搜索操作的时间复杂度为 O(log n),其中 n 是树中节点的个数。插入和删除操作的时间复杂度也是 O(log n)。
注意:二叉搜索树不一定是平衡的,如果存在极端情况,比如插入完全有序的序列,二叉搜索树可能会退化成一条链,此时时间复杂度就会退化为 O(n)。为了解决这个问题,可以采用平衡二叉搜索树,比如 AVL 树和红黑树。
class BinarySearchTree {
TreeNode? root;
public BinarySearchTree() {
// 初始化空树
root = null;
}
/* 获取二叉树根节点 */
public TreeNode? getRoot() {
return root;
}
/* 查找节点 */
public TreeNode? search(int num) {
TreeNode? cur = root;
// 循环查找,越过叶节点后跳出
while (cur != null) {
// 目标节点在 cur 的右子树中
if (cur.val < num) cur =
cur.right;
// 目标节点在 cur 的左子树中
else if (cur.val > num)
cur = cur.left;
// 找到目标节点,跳出循环
else
break;
}
// 返回目标节点
return cur;
}
/* 插入节点 */
public void insert(int num) {
// 若树为空,则初始化根节点
if (root == null) {
root = new TreeNode(num);
return;
}
TreeNode? cur = root, pre = null;
// 循环查找,越过叶节点后跳出
while (cur != null) {
// 找到重复节点,直接返回
if (cur.val == num)
return;
pre = cur;
// 插入位置在 cur 的右子树中
if (cur.val < num)
cur = cur.right;
// 插入位置在 cur 的左子树中
else
cur = cur.left;
}
// 插入节点
TreeNode node = new TreeNode(num);
if (pre != null) {
if (pre.val < num)
pre.right = node;
else
pre.left = node;
}
}
/* 删除节点 */
public void remove(int num) {
// 若树为空,直接提前返回
if (root == null)
return;
TreeNode? cur = root, pre = null;
// 循环查找,越过叶节点后跳出
while (cur != null) {
// 找到待删除节点,跳出循环
if (cur.val == num)
break;
pre = cur;
// 待删除节点在 cur 的右子树中
if (cur.val < num)
cur = cur.right;
// 待删除节点在 cur 的左子树中
else
cur = cur.left;
}
// 若无待删除节点,则直接返回
if (cur == null)
return;
// 子节点数量 = 0 or 1
if (cur.left == null || cur.right == null) {
// 当子节点数量 = 0 / 1 时, child = null / 该子节点
TreeNode? child = cur.left != null ? cur.left : cur.right;
// 删除节点 cur
if (cur != root) {
if (pre.left == cur)
pre.left = child;
else
pre.right = child;
} else {
// 若删除节点为根节点,则重新指定根节点
root = child;
}
}
// 子节点数量 = 2
else {
// 获取中序遍历中 cur 的下一个节点
TreeNode? tmp = cur.right;
while (tmp.left != null) {
tmp = tmp.left;
}
// 递归删除节点 tmp
remove(tmp.val);
// 用 tmp 覆盖 cur
cur.val = tmp.val;
}
}
}
public class binary_search_tree {
[Test]
public void Test() {
/* 初始化二叉搜索树 */
BinarySearchTree bst = new BinarySearchTree();
// 请注意,不同的插入顺序会生成不同的二叉树,该序列可以生成一个完美二叉树
int[] nums = { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 };
foreach (int num in nums) {
bst.insert(num);
}
Console.WriteLine("\n初始化的二叉树为\n");
PrintUtil.PrintTree(bst.getRoot());
/* 查找节点 */
TreeNode? node = bst.search(7);
Console.WriteLine("\n查找到的节点对象为 " + node + ",节点值 = " + node.val);
/* 插入节点 */
bst.insert(16);
Console.WriteLine("\n插入节点 16 后,二叉树为\n");
PrintUtil.PrintTree(bst.getRoot());
/* 删除节点 */
bst.remove(1);
Console.WriteLine("\n删除节点 1 后,二叉树为\n");
PrintUtil.PrintTree(bst.getRoot());
bst.remove(2);
Console.WriteLine("\n删除节点 2 后,二叉树为\n");
PrintUtil.PrintTree(bst.getRoot());
bst.remove(4);
Console.WriteLine("\n删除节点 4 后,二叉树为\n");
PrintUtil.PrintTree(bst.getRoot());
}
}
二叉树是一种重要的数据结构,其优点和缺点如下:
优点:
缺点:
二叉树是一种常见的数据结构,在许多应用程序中使用。下面是一些二叉树的应用场景:
这只是二叉树的一部分应用场景,实际上在数据结构和算法中,二叉树被广泛应用。