0
点赞
收藏
分享

微信扫一扫

Go语言并发精髓:深入理解和运用go语句

一,递归序和递归理解

#include<iostream>
//递归的基础样例,用来理解递归。
struct TreeNode
{
int value;
TreeNode* left;
TreeNode* right;
};
void f(TreeNode* root)
{
if (root == NULL) return;
//1
//当到达该节点时的操作。
f(root->left);
//去遍历根节点的左子树。
//2
//遍历完左子树后进行的操作
f(root->right);
//3
//遍历完右子树后进行的操作。

//每个节点会被访问三次,两次是返回时访问的。
//理解递归,调用函数本身,一定有某一时刻会返回调用处。
}

这是在B站左神的视频。

1,三种遍历树的方式

先序,中序和后序遍历。规律就是都是先遍历左子树,再遍历右子树。根据本身(中序)访问的次序分的类,如先,中,后,就叫作中序遍历。

也可以这样理解:根据第几次到达节点进行操作来分类,第一次到达就进行操作就叫作先序遍历。

二,二叉树的基础知识

二叉树(Binary Tree)是数据结构中的一种树形结构,其中每个节点最多有两个子节点,通常分别称为 左子节点(left child)和 右子节点(right child)。二叉树是一种非常重要且广泛应用的数据结构,在许多算法和问题的解决中都有重要作用。

1. 二叉树的基本概念

  • 节点:二叉树的每一个元素都被称为节点(Node)。每个节点包含一个值(或数据),以及指向其左右子节点的指针。
  • 根节点(Root):二叉树的最顶层节点,树的起点,通常是整个二叉树的访问起点。
  • 子节点:根节点或其他节点的下级节点。
    • 左子节点(Left Child):节点的左子树。
    • 右子节点(Right Child):节点的右子树。
  • 叶子节点(Leaf Node):没有子节点的节点,通常是树的末端。
  • 父节点:每个节点的直接上级节点。
  • 深度:节点到根节点的路径长度。
  • 高度:从节点到叶子节点的最长路径长度。

2. 二叉树的类型

  • 满二叉树(Full Binary Tree):每个节点要么是叶子节点,要么有两个子节点。
  • 完全二叉树(Complete Binary Tree):除了最后一层外,其他层的节点都被填满,且最后一层的节点从左到右依次填充。
  • 平衡二叉树(Balanced Binary Tree):左右子树的高度差不超过 1 的二叉树。例如 AVL 树、红黑树。
  • 二叉搜索树(Binary Search Tree, BST):对于树中的每个节点,左子树所有节点的值小于当前节点的值,右子树所有节点的值大于当前节点的值。

3. 二叉树的遍历

二叉树遍历是访问树中所有节点的过程,常见的遍历方法有:

  • 前序遍历(Pre-order Traversal):根节点 → 左子树 → 右子树
  • 中序遍历(In-order Traversal):左子树 → 根节点 → 右子树
  • 后序遍历(Post-order Traversal):左子树 → 右子树 → 根节点
  • 层序遍历(Level-order Traversal):逐层从上到下,从左到右访问所有节点,通常使用队列实现。

4. 二叉树的性质

  • 节点数:二叉树中,深度为 d 的完全二叉树最多有 2^d - 1 个节点。
  • 高度与节点数:一个二叉树的高度(或深度)与节点数有关系。例如,完全二叉树的高度 h 与节点数 n 的关系是 h = log2(n + 1)
  • 二叉树的最大节点数:在给定高度 h 下,二叉树最多有 2^h - 1 个节点。

5. 二叉树的应用

二叉树是许多复杂数据结构和算法的基础,具有广泛的应用,常见的应用包括:

  • 二叉搜索树(BST)用来实现快速查找、插入和删除操作。
  • (Heap):一种完全二叉树,通常用于实现优先队列。
  • 表达式树:用于表示数学表达式,支持高效的运算。
  • 红黑树、AVL 树等平衡二叉树用于实现高效的排序和查找。
  • 决策树:用于机器学习中的分类和回归问题。

6. 二叉树的实现

常见的二叉树通常使用 链式存储 方式来实现。每个节点通常包含:

  • 数据(值)
  • 左子节点指针
  • 右子节点指针

一个简单的二叉树节点定义如下(用 C++ 实现):

struct TreeNode {
int value; // 节点值
TreeNode* left; // 左子节点指针
TreeNode* right; // 右子节点指针

TreeNode(int x) : value(x), left(nullptr), right(nullptr) {}
};

7. 复杂度分析

  • 查找、插入、删除:在最坏情况下,如果树的高度接近 n,这些操作的时间复杂度为 O(n)。在平衡二叉树中,最坏情况下的时间复杂度为 O(log n)
  • 遍历:所有遍历方法的时间复杂度都是 O(n),其中 n 是树中的节点数。

总结

二叉树是计算机科学中的一种基础数据结构,广泛应用于各种算法和数据结构中。通过合理的遍历、搜索与更新操作,二叉树在查找、排序、表达式解析、优先队列等多种场景中都扮演着重要角色。

二叉树的插入和遍历

第一次尝试:

#include<iostream>

using namespace std;

struct TreeNode
{
int value;
TreeNode* left;
TreeNode* right;

TreeNode(int x) : value(x),left(NULL),right(NULL){}
};

TreeNode* create_Node(int);
void Tree_insert(TreeNode*, int);
void preOrder_traverse(TreeNode*);

int main()
{
TreeNode* root = create_Node(0);
for (int i = 1; i < 5; i++)
{
int j = i * i;
int k = 2 * i;
Tree_insert(root, j);
Tree_insert(root, k);
}

preOrder_traverse(root);
return 0;
}


TreeNode* create_Node(int x)
{
TreeNode* node = new TreeNode(x);
return node;
}


void Tree_insert(TreeNode* root, int x)
{
TreeNode* current = root;
TreeNode* prev = root;

while (current != NULL)
{
if (x < current->value)
{
prev = current;
current = current->left;

}
else if (x = current->value);
else
{
prev = current;
current = current->right;
}
}

if (x < prev->value) prev->left = create_Node(x);
else prev->right = create_Node(x);
}

void preOrder_traverse(TreeNode* current)
{
//具体函数里面操作自己设置
if (current == NULL) return;
cout << current->value << " ";
preOrder_traverse(current->left);
preOrder_traverse(current->right);
//至于中序,后序就是将访问该节点的操作放到访问完左节点后面和右节点后面。
}

改进:

指针传递 vs 引用传递

void insert(TreeNode* root) {
root = new TreeNode(5); // 修改的是root的副本
}

int main() {
TreeNode* root = nullptr;
insert(root); // root没有被修改
// root仍然是nullptr
}

使用引用传递(按引用传递指针

void insert(TreeNode*& root) {  // 使用引用传递
root = new TreeNode(5); // 修改的是root的引用,实际修改了外部root
}

int main() {
TreeNode* root = nullptr;
insert(root); // root被修改为新的节点
// root现在指向一个新创建的节点
}

为什么推荐使用引用传递:

  • 正确修改根节点:在二叉树插入时,特别是当树为空时(根节点是 nullptr),你希望在 insert() 函数内部修改根节点的指向。通过引用传递,你可以直接修改根节点,并且修改会反映到外部。
  • 避免副本问题:使用按值传递(指针的副本)会导致对指针的修改没有影响,从而无法正确操作树的结构。

结论:

  • 按值传递指针:在函数内部修改指针时,不会影响外部的指针。对于需要修改外部指针(如根节点)的操作,按值传递不合适。
  • 按引用传递指针:能够确保在函数内部修改指针时,修改会反映到外部,因此更加适用于需要修改根节点或其他外部指针的场景。

因此,在处理二叉树插入时,推荐使用引用传递来确保修改能够正确地反映到外部变量中。

补充:

TreeNode*& root 是 C++ 中对指针的引用传递的写法。这种语法结合了指针(*)和引用(&),它的含义是:传递一个指向 TreeNode 类型对象的指针,并且该指针是通过引用传递的。这意味着在函数内部可以修改指针本身的值,并且这种修改会影响到外部的变量。

1. *(指针)与 &(引用)的组合

  • * 表示指针,指针是一个变量,它保存的是另一个变量的地址。在 TreeNode* root 中,root 是一个指向 TreeNode 类型对象的指针。

  • & 表示引用,引用是一种别名,它让你能够直接操作原始变量而不是它的副本。通过引用传递时,函数内部对变量的修改会反映到外部。

2. 为什么 * 和 & 可以连用?

结合 * 和 & 使用时,意味着我们传递的是指针的引用。这两者结合起来,实际上是实现了按引用传递指针。具体来说,TreeNode*& root 表示:

  • root 是一个指向 TreeNode 的指针(TreeNode*)。
  • & 表示引用,意味着我们将传递指针的引用。这样,在函数内部对 root 的修改会直接影响外部的 root

第二次

void Tree_insert(TreeNode*& root, int x)
{
TreeNode* current = root;
TreeNode* prev = root;

if (root == NULL)
{
root = new TreeNode(x);
return;
}

while (current != NULL)
{
if (x < current->value)
{
prev = current;
current = current->left;

}
else if (x == current->value) return;
else
{
prev = current;
current = current->right;
}
}

if (x < prev->value) prev->left = create_Node(x);
else prev->right = create_Node(x);
}
举报

相关推荐

0 条评论