Data Structures and Algorithms Week5: Tree

4612 字
23 分钟
Data Structures and Algorithms Week5: Tree

Tree
#

Content
目录#

  • Binary Trees
    二叉树
  • Recursive techniques with trees
    基于树的递归技术
  • Traversals
    遍历

What is a Tree (Graph Theory)?
什么是树(图论)#

  • In graph theory, a tree is an undirected graph that contains no cycles (i.e. no paths that take you back to the node on which you started).
    在图论中,树是一种不包含环的无向图(即不存在一条路径能让你回到起始节点)。

What is a Tree (Computer Science)?
什么是树(计算机科学)#

  • In computer science we generally think of trees as being rooted trees.
    在计算机科学中,我们通常讨论的是有根树
    • One node is designated as the root.
      其中一个节点被指定为根节点
    • The nodes adjacent to it are the roots of its subtrees
      与其相邻的节点分别是其子树的根。

Example#

Example: XML/HTML documents#

Binary trees
二叉树#

  • Binary trees have two sub-trees:
    二叉树有两个子树

Which is Binary tree?
哪个是二叉树?

Basic Terminology for Binary Trees
二叉树基础术语#

Terminology 术语


Root: 根节点

Left-subtree: 左子树

Right-subtree: 右子树

leaves: 叶子节点

How do we represent binary trees?
如何表示二叉树呢?#

  • A (binary) tree is
    (二叉)树可以定义为:
    • either empty, 
      要么是空树,
    • or consists of a node, containing some data, along with references to two sub-trees, known as the left and right sub trees
      要么由一个包含数据的节点组成,并带有指向两棵子树的引用,分别称为 leftright

⬆️
Note the recursive definition.
注意这个递归定义。

Represent Binary Trees of Integers
用整数表示二叉树#

public class BinaryTree {
public Member data;
public BinaryTree left; //null if there is no left subtree
public BinaryTree right; //null if there is no right subtree
public BinaryTree(Member val) {
data = val;
left = null;
right = null;
}
}
BinaryTree t;
// A tree can be represented by a reference to its root.
// This reference is null if the tree is empty.

Recursive Tree Algorithms
递归树算法#

Since trees are defined recursively, it is often simplest to process them using recursive algorithms. Here is an example:
由于树本身是递归定义的,通常使用递归算法处理它最简单。示例如下:

/** Count the number of nodes in a binary tree **/
public static int count(BinaryTree t) {
if (t == null) {
return 0;
} else {
return 1 + count(t.left) + count(t.right);
}
}

Traversals
遍历#

Traverse 遍历

  • A traversal is a systematic way of going through all the data in a data structure.
    遍历是系统化地访问数据结构中全部数据的一种方式。
  • So, for example, for a list of type ArrayList<String>, this is a traversal:
    例如,对于 ArrayList<String> 类型的列表,下面就是一次遍历:
for (String s : list) {
// makes s successively each value in list
............
}

  • For trees there are several traversals, including:
    对树而言,常见遍历方式包括:
    • pre-order traversal
      先序遍历
    • in-order traversal
      中序遍历
    • post-order traversal
      后序遍历

Pre-order traversal
先序遍历#

  • The pre-order traversal algorithm is recursive:
    If tree is not empty:
    先序遍历算法是递归的:
    若树非空:
    • visit root
      先访问根节点
    • then traverse the left subtree
      再遍历左子树
    • then traverse the right subtree
      最后遍历右子树

traversals are done recursively, in pre-order fashion
遍历以递归方式按先序执行

It is called a pre-order traversal because “pre” means “before”, and the root is visited before the subtrees.
之所以称为先序遍历,是因为 “pre” 表示“之前”,即根节点在子树之前被访问

Pre-order Example
先序示例#

If tree is not empty:
若树非空:

  • visit root
    访问根节点
  • then traverse the left subtree
    遍历左子树
  • then traverse the right subtree
    遍历右子树

Pre-order traversal of graph:
该图的先序遍历:
A,B,D,H,E,C,F,I,J,G

An implementation of pre-order traversal
先序遍历实现#

public static void preOrderTraversal(BinaryTree t) {
if (t != null) {
System.out.println(t.data);
preOrderTraversal(t.left);
preOrderTraversal(t.right);
}
}

In-order traversal
中序遍历#

  • The in-order traversal algorithm is recursive:
    中序遍历算法是递归的:
    • If tree is not empty:
      若树非空:
      • traverse the left subtree
        先遍历左子树
      • then visit root
        再访问根节点
      • then traverse the right subtree
        最后遍历右子树

traversals are done recursively, in in-order fashion
遍历以递归方式按中序执行

For an in-order traversal, the root is visited “in between” the subtrees.
中序遍历中,根节点在两棵子树“之间”被访问。

In-order Example
中序示例#

If tree is not empty:
若树非空:

  • traverse the left subtree
    遍历左子树
  • then visit root
    访问根节点
  • then traverse the right subtree
    遍历右子树

In-order traversal of graph:
该图的中序遍历:
H,D,B,E,A,I,F,J,C,G

An implementation of in-order traversal
中序遍历实现#

public static void inOrderTraversal(BinaryTree t) {
if (t != null) {
inOrderTraversal(t.left);
System.out.println(t.data);
inOrderTraversal(t.right);
}
}

Post-order traversal
后序遍历#

  • The post-order traversal algorithm is recursive:
    后序遍历算法是递归的:
    • If tree is not empty:
      若树非空:
      • traverse the left subtree
        先遍历左子树
      • then traverse the right subtree
        再遍历右子树
      • visit root
        最后访问根节点

traversals are done recursively, in post-order fashion
遍历以递归方式按后序执行

It is called a post-order traversal because “post” means “after”, and the root is visited after the subtrees.
之所以称为后序遍历,是因为 “post” 表示“之后”,根节点在子树之后被访问。

Post-order Example
后序示例#

If tree is not empty:
若树非空:

  • traverse the left subtree
    遍历左子树
  • then traverse the right subtree
    遍历右子树
  • then visit root
    访问根节点

Post-order traversal of graph:
该图的后序遍历:
H,D,E,B,I,J,F,G,C,A

An implementation of post-order traversal
后序遍历实现#

public static void postOrderTraversal(BinaryTree t) {
if (t != null) {
postOrderTraversal(t.left);
postOrderTraversal(t.right);
System.out.println(t.data);
}
}

Requirements for this week
本周学习要求#

You should achieve by the end of this week’s work:
完成本周学习后,你应当能够:

  • understand binary trees
    理解二叉树
  • able to store data in binary trees
    能够在二叉树中存储数据
  • understand pre-order, post-order and in-order traversals on binary trees
    理解二叉树的先序、后序和中序遍历
  • able to write recursive functions on trees.
    能够在树结构上编写递归函数。

Binary search tree
二叉搜索树#

A Binary Search Tree
二叉搜索树示例#

  • This is an example of Binary Search Tree
    这是一个二叉搜索树示例。
  • What property does it have that makes it a binary tree?
    它具备什么性质使它成为二叉树?
  • What additional property does it have?
    它还具备什么额外性质?
  • Why might that property be useful?
    这个额外性质为什么有用?

Binary Search Trees
二叉搜索树#

  • These are a special kind of binary tree
    它是二叉树的一种特殊类型。
    • the data they contain must be a comparable type (where you can do <, >, ==, .equals comparisons), e.g. int, char, String
      (.compareTo)
      其中存储的数据必须是可比较类型(可进行 <>==.equals 比较),如 int、char、String(可用 .compareTo)。
    • The data at the root node is greater than all the data in the left subtree
      节点数据大于左子树中所有数据
    • The data at the root node is less than all data in the right subtree
      节点数据小于右子树中所有数据
    • … and so on, all the way down the tree (recursive definition)
      该性质在整棵树上递归成立(递归定义

  • Usually binary search trees represent a set of values
    二叉搜索树通常表示一组集合值。
    • there are no duplicate values in the tree
      树中没有重复
    • Of course, any binary search tree is also a binary tree
      当然,任何二叉搜索树也都是二叉树

Invented by P.F. Windley, A.D. Booth, A.J.T. Colin, and T.N. Hibbard in 1960 (Wikipedia)
由 P.F. Windley、A.D. Booth、A.J.T. Colin 和 T.N. Hibbard 于 1960 年提出(Wikipedia)。

Is this a Binary Search Tree?
这是二叉搜索树吗?#

Datatype Invariants
数据类型不变式#

  • A datatype invariant is a property of an abstract data type which holds for all objects of that type.
    数据类型不变式是某个抽象数据类型中对所有对象都成立的性质。
  • The datatype invariant for binary search trees is:
    二叉搜索树的数据类型不变式是:
    • The data at the root node is greater than all the data in the left sub-tree
      根节点数据大于左子树所有数据
    • The data at the root node is less than all the data in the right sub-tree
      根节点数据小于右子树所有数据
    • … and so on, all the way down the tree
      该性质在整棵树中逐层递归成立
    • no duplicate values in the tree - so any implementation of such a tree is also an implementation of the concept of a set with operations union-with-singleton and membership.
      树中无重复值,因此这类树本质上也实现了集合概念(支持单元素并集与成员判断)。

A Search Tree class (for integers)
一个搜索树类(用于整数)#

public class SearchTree {
public int data; // or whatever
public SearchTree left;
public SearchTree right;
// Reminder:
// reference-type fields are initialized to null by default.
public SearchTree(int data) {
this.data = data;
}
}

Insertion into a Search Tree
插入到搜索树中#

public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
} // do nothing -- val is already in tree
return tree;
}

Does it use recursive method?
它是否使用了递归方法?

What is the base case?
它的递归基线条件是什么?

Insertion into a Search Tree: Example
搜索树插入示例#

public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

We have some variable SearchTree t. The initial call is t = insert(t,9) where t points to the root of the tree.
设有变量 SearchTree t,初始调用为 t = insert(t,9),其中 t 指向树根。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

9 > 7 so insert on right
9 > 7,因此向右侧插入。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

Start of recursive call
开始递归调用。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

9 < 12 so insert to left
9 < 12,因此向左侧插入。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

Start of recursive call
开始递归调用。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

9 > 8 so insert to right
9 > 8,因此向右侧插入。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

Start of recursive call
开始递归调用。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

Create a new tree and return it
创建新节点(新树)并返回。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

Return to third invocation of insert() and update right subtree
返回到第三层 insert() 调用,并更新右子树。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

Return to second invocation of insert().
The assignment statement does not change location of left subtree
返回到第二层 insert() 调用。
赋值语句不会改变左子树的位置。


public SearchTree insert( SearchTree tree, int val) {
if (tree == null) {
tree = new SearchTree(val);
} else if (val > tree.data) {
tree.right = insert(tree.right, val);
} else if (val < tree.data) {
tree.left = insert(tree.left, val);
}
return tree;
}

Return to original statement t=insert(t,9)
The location of tree has not changed, but it has an extra node.
返回最初语句 t=insert(t,9)
树的根位置未改变,但树中新增了一个节点。

Searching a Search Tree
搜索一个搜索树#

public static boolean contains(SearchTree tree, int val) {
if (tree == null) {
return false;
} else if (val > tree.data) {
return contains(tree.right, val);
} else if (val < tree.data) {
return contains(tree.left, val);
} else { // tree.data==val
return true;
}
}

Does it use recursive method?
它是否使用了递归方法?

What is the base case?
它的递归基线条件是什么?

Exercise#

  • Sketch out the tree that would be produced by inserting the values 4, 5, 10, 25, 39 in that order, using the insert method shown earlier.
    使用前面给出的 insert 方法,按顺序插入 4、5、10、25、39,画出最终生成的树。
  • Using the search method shown earlier, how many method calls do you need to make in order to find out whether 39 is in the resulting tree?
    使用前面的查找方法,要判断结果树中是否存在 39,需要调用多少次方法?
  • How many method calls do you need to make to find out whether 100 is in the tree?
    若判断 100 是否在树中,需要调用多少次方法?

Special situation with binary search trees
二叉搜索树的特殊情况#

If a tree
is lopsided,
a search can still
take up to n steps,
for a tree with n nodes.
Said to have degenerated into a linked list.
如果一棵树
偏斜的,
那么查找仍可能
需要最多 n 步,
(对于有 n 个节点的树)。
这种情况称为退化为链表。

Best to make the class contain a tree, not be one
最好让类“包含”一棵树,而不是“本身就是”一棵树#

  • Make a class BinarySearchTree contain a class Node.
    BinarySearchTree 类内部“包含” Node 类。
  • BinarySearchTree can then have as fields:
    BinarySearchTree 可包含如下字段:
    • root - a Node
      root - 一个节点引用
    • numEntries - an integer
      numEntries - 整数计数

A Search Tree class (for integers)
一个搜索树类(用于整数)#

public class BinarySearchTree {
private class Node {
private int data;
private Node left;
private Node right;
public Node(int data) {
this.data = data;
}
}
private Node root;
private int numEntries;
public BinarySearchTree() {
root = null;
numEntries = 0;
}

Recursive addNode
递归版 addNode#

public Node addNode(Node tree, int val) {
if (tree == null) {
tree = new Node(val);
} else if (val > tree.data) {
tree.right = addNode(tree.right, val);
} else if (val < tree.data) {
tree.left = addNode(tree.left, val);
} // val is already in tree; take action
return tree;
}

BinarySearchTree.insert using recursive addNode
使用递归 addNodeBinarySearchTree.insert#

public void insert (int val) {
root = addNode(root, val);
numEntries++;
} // BinarySearchTree.insert

Iterative addNode
循环版 addNode#

public void addNode(int key, String name) { // does not use recursive way
Node p, parent;
p = root;
while (p != null && p.key != key) {
parent = p;
if (key < p.key) p = p.left; else p = p.right;
}
if (p == null) {
Node newNode = new Node(key, name);
if (p == root) root = newNode;
else if (p == parent.left) parent.left = newNode;
else parent.right = newNode;
numEntries++;
}
else // p != null - duplicate node detected! Take appropriate action
}

BinarySearchTree.insert using iterative addNode
使用循环 addNodeBinarySearchTree.insert#

public void insert (int val) {
addNode(val);
} // BinarySearchTree.insert

Perfectly balanced trees
完全平衡树#

A perfectly balanced tree with 2(k+1)12^{(k+1)} - 1 nodes, will have depth×(height) kdepth \times (height)~k so searches take at most kk steps
对于一个包含 2(k+1)12^{(k+1)} - 1 个节点的完全平衡树,其深度(高度)为 kk,因此查找最多只需 kk 步。

  • The depth of a tree defined to be
    树的深度定义为:
    • the length of the longest path from the root to a leaf,
      从根到叶子最长路径的长度;
    • so a tree consisting only of the root node has a depth of 0.
      因此只有根节点的树深度为 0;
    • The depth of an empty tree can be taken to be -1.
      空树深度可定义为 -1。

A perfectly balanced tree
一棵完全平衡

For n nodes, searches take no more than (1+log n)\underline{\textcolor{red}{(1 + log~n)}} steps.
对于 n 个节点,查找步数不超过 (1+log n)\underline{\textcolor{red}{(1 + log~n)}}

  • This is described as a O(log n)\textcolor{red}{O(log~n)} algorithm and it is a lot faster than an algorithm with nn steps, which would be O(n)O(n).
    这被称为 O(log n)\textcolor{red}{O(log~n)} 级别算法,明显快于需要 nn 步的 O(n)O(n) 算法。

Self-balancing trees
自平衡树#

  • A balanced tree offers better access time, but re-balancing a tree takes time, so it is a trade-off.
    平衡树查询更快,但维护平衡本身也需要成本,这是一种权衡。
  • This has led to a variety of engineering solutions for self-balancing trees.These include:
    因此工程上出现了多种自平衡树方案,包括:
    • “AVL” trees: G. M. Adelson-Velskii and E. M. Landis, 1962
      “AVL 树”:G. M. Adelson-Velskii 与 E. M. Landis(1962)
    • “Red-black” trees: Rudolf Bayer (1972).
      “红黑树”:Rudolf Bayer(1972)
    • “Symmetric binary B-Trees: Data structure and maintenance algorithms”. Acta Informatica. 1 (4): 290-306
      “对称二叉 B 树:数据结构与维护算法”,发表于 Acta Informatica 1(4): 290-306

TreeMap#

  • The provided Java collection class TreeMap implements the interface Map.
    Java 提供的集合类 TreeMap 实现了 Map 接口。
  • It uses a Red-Black tree and offers O(log n)\textcolor{red}{O(log~n)} access time.
    它基于红黑树,访问复杂度为 O(log n)\textcolor{red}{O(log~n)}
    • Note: HashMap can offer O(1)\textcolor{orange}{O(1)} access time.
      注意:HashMap 访问可达 O(1)\textcolor{orange}{O(1)} 级别。
  • But it is possible to iterate through a TreeMap accessing the values in order; this is not possible with a HashMap.
    TreeMap 可以按有序顺序迭代访问值,而 HashMap 通常无法做到这一点。

Access Time
访问时间#

  • Searching is faster in a binary search tree, just as binary search in a sorted array is faster than linear search.
    在二叉搜索树中查找通常更快,正如有序数组中的二分查找快于线性查找。
  • There is a trade-off: cost of keeping tree balanced versus cost of searches.
    存在权衡:维护平衡的代价与查找代价之间需要取舍。
  • It is easy to sort numbers using a binary search tree - why and how?
    用二叉搜索树做排序很自然,为什么?怎么做?

Requirements#

  • What you want to achieve by the end of this week’s work:
    本周结束时你应达成:
    • understand binary trees
      理解二叉树
    • understand binary search trees
      理解二叉搜索树
    • know the difference!
      明确两者差异
    • be able to write recursive functions on trees.
      能在树结构上编写递归函数
Thingking Question

以下二叉树的区别:

完美二叉树(perfect binary tree)

完全二叉树(complete binary tree)

完满二叉树(full binary tree)

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
Data Structures and Algorithms Week5: Tree
https://firefly.anka2.top/posts/obu/level5/semester2/dsa/week5/lecture/
作者
🐦‍🔥不死鸟Anka
发布于
2026-04-07
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
A-n-k-a
Over the Frontier / Into the Front
看这里~
合作翻译官绝赞招募中!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
59
分类
6
标签
20
总字数
550,118
运行时长
0
最后活动
0 天前

目录