Data Structures and Algorithms Six Trees

An introduction to binary trees

In the symbol table we implemented before, it is not difficult to see that the addition, deletion, and query operations of the symbol table, as the number of elements N increases, the time-consuming also increases linearly, and the time complexity is O(n). In order to improve the operation efficiency , next we learn the data structure of the tree.

1.1 Basic definition of tree

The tree is a very important data structure in our computer. At the same time, using the data structure of the tree can describe many things in real life, such as family tree, organizational structure of the unit, and so on.

A tree is a set of hierarchical relationships composed of n (n>=1) finite nodes. It's called a "tree" because it looks like an upside-down tree, meaning it has the roots pointing up and the leaves pointing down.

insert image description here

Trees have the following characteristics:

  1. Each node has zero or more child nodes;
  2. A node without a parent node is the root node;
  3. Each non-root node has only one parent node;
  4. Each node and its descendant nodes can be regarded as a tree as a whole, which is called a subtree of the parent node of the current node;

1.2 Terms related to trees

Degree of a node:
The number of subtrees contained in a node is called the degree of the node;

Leaf node:
A node with a degree of 0 is called a leaf node, and it can also be called a terminal node

Branch node:
A node whose degree is not 0 is called a branch node, and can also be called a non-terminal node

The level of the node:
starting from the root node, the level of the root node is 1, the direct successor level of the root is 2, and so on

Layer sequence numbering of nodes:
Arrange the nodes in the tree into a linear sequence from the upper layer to the lower layer, and from left to right in the same layer, and compile them into continuous natural numbers.

The degree of the tree:
the maximum degree of all nodes in the tree

The height (depth) of the tree:
the maximum level of nodes in the tree

Forest:
A collection of m (m>=0) disjoint trees, delete the root node of a non-empty tree, and the tree becomes a forest; add a unified root node to the forest, and the forest becomes Become a tree
insert image description here
child node:
the direct successor node of a node is called the child node of the node

Parent node (parent node):
The direct predecessor of a node is called the parent node of the node

Brother node:
the child nodes of the same parent node are called brother nodes

1.3 Basic definition of binary tree

A binary tree is a tree with a degree of no more than 2 (each node has at most two sub-nodes).
insert image description here
Full binary tree:
a binary tree. If the node tree of each layer reaches the maximum value, the binary tree is a full binary tree. Complete binary tree
insert image description here
:
leaf nodes Binary tree that can only appear in the bottom and second bottom layers, and the nodes in the bottom layer are all concentrated in the leftmost positions of the layer
insert image description here

1.4 Creation of binary search tree

1.4.1 Node class of binary tree

According to the observation of the graph, we found that the binary tree is actually composed of one node and the relationship between them. According to the object-oriented thinking, we design a node class to describe the node.

Node class API design:
insert image description here
code implementation:

private class Node<Key,Value>{
    
    
	//存储键
	public Key key;
	//存储值
	private Value value;
	//记录左子结点
	public Node left;
	//记录右子结点
	public Node right;
	public Node(Key key, Value value, Node left, Node right) {
    
    
		this.key = key;
		this.value = value;
		this.left = left;
		this.right = right;
	}
}

1.4.2 Binary search tree API design

insert image description here

1.4.3 Binary search tree implementation

Insertion method put implements ideas:

  1. If there is no node in the current tree, use the new node directly as the root node
  2. If the current tree is not empty, start from the root node:
    2.1 If the key of the new node is less than the key of the current node, continue to find the left child node of the current node;
    2.2 If the key of the new node is greater than the current node 2.3 If the key of the
    new node is equal to the key of the current node, such a node already exists in the tree, just replace the value of the node.

insert image description here
insert image description here

Query method get implementation idea:

Starting from the root node:

  1. If the key to be queried is smaller than the key of the current node, continue to find the left child node of the current node;
  2. If the key to be queried is greater than the key of the current node, continue to find the right child node of the current node;
  3. If the key to be queried is equal to the key of the current node, the value of the current node will be returned in the tree.

Delete method delete implementation idea:

  1. Find the deleted node;
  2. Find the smallest node minNode in the right subtree of the deleted node
  3. Delete the smallest node in the right subtree
  4. Let the left subtree of the deleted node be called the left subtree of the minimum node minNode, and let the right subtree of the deleted node be called the right subtree of the minimum node minNode
  5. Let the parent node of the deleted node point to the minimum node minNode
    insert image description here
    insert image description here
    insert image description here

code:

//二叉树代码
public class BinaryTree<Key extends Comparable<Key>, Value> {
    
    
	//记录根结点
	private Node root;
	//记录树中元素的个数
	private int N;
	//获取树中元素的个数
	public int size() {
    
    
		return N;
	}
	//向树中添加元素key-value
	public void put(Key key, Value value) {
    
    
		root = put(root, key, value);
	}
	//向指定的树x中添加key-value,并返回添加元素后新的树
	private Node put(Node x, Key key, Value value) {
    
    
		if (x == null) {
    
    
			//个数+1
			N++;
			return new Node(key, value, null, null);
		}
		int cmp = key.compareTo(x.key);
		if (cmp > 0) {
    
    
			//新结点的key大于当前结点的key,继续找当前结点的右子结点
			x.right = put(x.right, key, value);
		} else if (cmp < 0) {
    
    
			//新结点的key小于当前结点的key,继续找当前结点的左子结点
			x.left = put(x.left, key, value);
		} else {
    
    
			//新结点的key等于当前结点的key,把当前结点的value进行替换
			x.value = value;
		}
		return x;
	}
	//查询树中指定key对应的value
	public Value get(Key key) {
    
    
		return get(root, key);
	}
	//从指定的树x中,查找key对应的值
	public Value get(Node x, Key key) {
    
    
		if (x == null) {
    
    
			return null;
		}
		int cmp = key.compareTo(x.key);
		if (cmp > 0) {
    
    
			//如果要查询的key大于当前结点的key,则继续找当前结点的右子结点;
			return get(x.right, key);
		} else if (cmp < 0) {
    
    
			//如果要查询的key小于当前结点的key,则继续找当前结点的左子结点;
			return get(x.left, key);
		} else {
    
    
			//如果要查询的key等于当前结点的key,则树中返回当前结点的value。
			return x.value;
		}
	}
	//删除树中key对应的value
	public void delete(Key key) {
    
    
		root = delete(root, key);
	}
	//删除指定树x中的key对应的value,并返回删除后的新树
	public Node delete(Node x, Key key) {
    
    
		if (x == null) {
    
    
			return null;
		}
		int cmp = key.compareTo(x.key);
		if (cmp > 0) {
    
    
			//新结点的key大于当前结点的key,继续找当前结点的右子结点
			x.right = delete(x.right, key);
		} else if (cmp < 0) {
    
    
			//新结点的key小于当前结点的key,继续找当前结点的左子结点
			x.left = delete(x.left, key);
		} else {
    
    
			//新结点的key等于当前结点的key,当前x就是要删除的结点
			//1.如果当前结点的右子树不存在,则直接返回当前结点的左子结点
			if (x.right == null) {
    
    
				return x.left;
			}
			//2.如果当前结点的左子树不存在,则直接返回当前结点的右子结点
			if (x.left == null) {
    
    
				return x.right;
			}
			//3.当前结点的左右子树都存在
			//3.1找到右子树中最小的结点
			Node minNode = x.right;
			while (minNode.left != null) {
    
    
				minNode = minNode.left;
			}
			//3.2删除右子树中最小的结点
			Node n = x.right;
			while (n.left != null) {
    
    
				if (n.left.left == null) {
    
    
					n.left = null;
				} else {
    
    
					n = n.left;
				}
			}
			//3.3让被删除结点的左子树称为最小结点minNode的左子树,让被删除结点的右子树称为最小结点
			minNode的右子树
			minNode.left = x.left;
			minNode.right = x.right;
			//3.4让被删除结点的父节点指向最小结点minNode
			x = minNode;
			//个数-1
			N--;
		}
		return x;
	}
	private class Node {
    
    
		//存储键
		public Key key;
		//存储值
		private Value value;
		//记录左子结点
		public Node left;
		//记录右子结点
		public Node right;
		public Node(Key key, Value value, Node left, Node right) {
    
    
			this.key = key;
			this.value = value;
			this.left = left;
			this.right = right;
		}
	}
}

//测试代码
public class Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		BinaryTree<Integer, String> bt = new BinaryTree<>();
		bt.put(4, "二哈");
		bt.put(1, "张三");
		bt.put(3, "李四");
		bt.put(5, "王五");
		System.out.println(bt.size());
		bt.put(1,"老三");
		System.out.println(bt.get(1));
		System.out.println(bt.size());
		bt.delete(1);
		System.out.println(bt.size());
	}
}

1.4.4 Other convenient methods of binary search tree

1.4.4.1 Find the smallest key in a binary tree

In some cases, we need to find out the minimum value of the keys that store all elements in the tree. For example, our tree stores the ranking and name data of students, so we need to find out what is the lowest ranking? Here we design the following two methods to accomplish this:
insert image description here

//找出整个树中最小的键
public Key min(){
    
    
	return min(root).key;
}
//找出指定树x中最小的键所在的结点
private Node min(Node x){
    
    
	if (x.left!=null){
    
    
		return min(x.left);
	}else{
    
    
		return x;
	}
}

1.4.4.2 Find the largest key in a binary tree

In some cases, we need to find the maximum value of the keys that store all elements in the tree. For example, our tree stores the grades and names of students, so what is the highest score that needs to be found? Here we also design two methods to complete:
insert image description here

//找出整个树中最大的键
public Key max(){
    
    
	return max(root).key;
}
//找出指定树x中最大键所在的结点
public Node max(Node x){
    
    
	if (x.right!=null){
    
    
		return max(x.right);
	}else{
    
    
		return x;
	}
}

1.5 Basic traversal of binary tree

In many cases, we may need to traverse the tree like traversing an array of arrays, so as to take out each element stored in the tree. Since the tree structure is different from the linear structure, it cannot traverse backwards from the beginning, so there is how Traversal is the question of what kind of search path to traverse.
insert image description here
We simply draw the tree as shown in the figure above, which consists of a root node, a left subtree, and a right subtree. According to when the root node is accessed, we can divide the traversal of the binary tree into the following three ways:

  1. Preorder traversal;
    first visit the root node, then visit the left subtree, and finally visit the right subtree
  2. In-order traversal;
    first visit the left subtree, visit the root node in the middle, and finally visit the right subtree
  3. Post-order traversal;
    first visit the left subtree, then visit the right subtree, and finally visit the root node

If we use three traversal methods to traverse the tree below, the results are as follows:
insert image description here

1.5.1 Preorder traversal

On the tree we created in 4.4, add the API for preorder traversal:

public Queue<Key> preErgodic():使用前序遍历,获取整个树中的所有键
private void preErgodic(Node x,Queue<Key> keys):使用前序遍历,把指定树x中的所有键放入到keys队列中

In the implementation process, we traverse through the preorder, take out the key of each node, put it in the queue and return it.
Implementation steps:

  1. Put the key of the current node into the queue;
  2. Find the left subtree of the current node, if not empty, recursively traverse the left subtree
  3. Find the right subtree of the current node, if not empty, recursively traverse the right subtree

code:

//使用前序遍历,获取整个树中的所有键
public Queue<Key> preErgodic(){
    
    
	Queue<Key> keys = new Queue<>();
	preErgodic(root,keys);
	return keys;
}

//使用前序遍历,把指定树x中的所有键放入到keys队列中
private void preErgodic(Node x,Queue<Key> keys){
    
    
	if (x==null){
    
    
		return;
	}
	//1.把当前结点的key放入到队列中;
	keys.enqueue(x.key);
	//2.找到当前结点的左子树,如果不为空,递归遍历左子树
	if (x.left!=null){
    
    
		preErgodic(x.left,keys);
	}
	//3.找到当前结点的右子树,如果不为空,递归遍历右子树
	if (x.right!=null){
    
    
		preErgodic(x.right,keys);
	}
}

//测试代码
public class Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		BinaryTree<String, String> bt = new BinaryTree<>();
		bt.put("E", "5");
		bt.put("B", "2");
		bt.put("G", "7");
		bt.put("A", "1");
		bt.put("D", "4");
		bt.put("F", "6");
		bt.put("H", "8");
		bt.put("C", "3");
		Queue<String> queue = bt.preErgodic();
		for (String key : queue) {
    
    
			System.out.println(key+"="+bt.get(key));
		}
	}
}

1.5.2 Inorder traversal

On the tree we created in 1.4, add the API for preorder traversal:

public Queue<Key> midErgodic():使用中序遍历,获取整个树中的所有键
private void midErgodic(Node x,Queue<Key> keys):使用中序遍历,把指定树x中的所有键放入到keys队列中

Implementation steps:

  1. Find the left subtree of the current node, if not empty, recursively traverse the left subtree
  2. Put the key of the current node into the queue;
  3. Find the right subtree of the current node, if not empty, recursively traverse the right subtree

code:

//使用中序遍历,获取整个树中的所有键
public Queue<Key> midErgodic(){
    
    
	Queue<Key> keys = new Queue<>();
	midErgodic(root,keys);
	return keys;
}

//使用中序遍历,把指定树x中的所有键放入到keys队列中
private void midErgodic(Node x,Queue<Key> keys){
    
    
	if (x==null){
    
    
		return;
	}
	//1.找到当前结点的左子树,如果不为空,递归遍历左子树
	if (x.left!=null){
    
    
		midErgodic(x.left,keys);
	}
	//2.把当前结点的key放入到队列中;
	keys.enqueue(x.key);
	//3.找到当前结点的右子树,如果不为空,递归遍历右子树
	if (x.right!=null){
    
    
		midErgodic(x.right,keys);
	}
}

//测试代码
public class Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		BinaryTree<String, String> bt = new BinaryTree<>();
		bt.put("E", "5");
		bt.put("B", "2");
		bt.put("G", "7");
		bt.put("A", "1");
		bt.put("D", "4");
		bt.put("F", "6");
		bt.put("H", "8");
		bt.put("C", "3");
		Queue<String> queue = bt.midErgodic();
		for (String key : queue) {
    
    
			System.out.println(key+"="+bt.get(key));
		}
	}
}

1.5.3 Postorder traversal

On the tree we created in 1.4, add the API for preorder traversal:

public Queue<Key> afterErgodic():使用后序遍历,获取整个树中的所有键
private void afterErgodic(Node x,Queue<Key> keys):使用后序遍历,把指定树x中的所有键放入到keys队列中

Implementation steps:

  1. Find the left subtree of the current node, if not empty, recursively traverse the left subtree
  2. Find the right subtree of the current node, if not empty, recursively traverse the right subtree
  3. Put the key of the current node into the queue;

the code

//使用后序遍历,获取整个树中的所有键
public Queue<Key> afterErgodic(){
    
    
	Queue<Key> keys = new Queue<>();
	afterErgodic(root,keys);
	return keys;
}
//使用后序遍历,把指定树x中的所有键放入到keys队列中
private void afterErgodic(Node x,Queue<Key> keys){
    
    
	if (x==null){
    
    
		return;
	}
	//1.找到当前结点的左子树,如果不为空,递归遍历左子树
	if (x.left!=null){
    
    
		afterErgodic(x.left,keys);
	}
	//2.找到当前结点的右子树,如果不为空,递归遍历右子树
	if (x.right!=null){
    
    
		afterErgodic(x.right,keys);
	}
	//3.把当前结点的key放入到队列中;
	keys.enqueue(x.key);
}
//测试代码
public class Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		BinaryTree<String, String> bt = new BinaryTree<>();
		bt.put("E", "5");
		bt.put("B", "2");
		bt.put("G", "7");
		bt.put("A", "1");
		bt.put("D", "4");
		bt.put("F", "6");
		bt.put("H", "8");
		bt.put("C", "3");
		Queue<String> queue = bt.afterErgodic();
		for (String key : queue) {
    
    
			System.out.println(key+"="+bt.get(key));
		}
	}
}

1.6 Level order traversal of binary tree

The so-called layer order traversal is to start from the root node (first layer) and go down in order to obtain the values ​​of all nodes in each layer. There is a binary tree as follows: Then the result of
insert image description here
layer order traversal is: EBGADFHC
we created in 4.4 On the tree, add an API for layer order traversal:

public Queue<Key> layerErgodic():使用层序遍历,获取整个树中的所有键

Implementation steps:

  1. Create a queue to store the nodes of each layer;
  2. Use a loop to pop a node from the queue:
    2.1 Get the key of the current node;
    2.2 If the left child node of the current node is not empty, put the left child node into the queue
    2.3 If the right child node of the current node If the child node is not empty, put the right child node into the queue

insert image description here
insert image description here
the code

//使用层序遍历得到树中所有的键
public Queue<Key> layerErgodic(){
    
    
	Queue<Key> keys = new Queue<>();
	Queue<Node> nodes = new Queue<>();
	nodes.enqueue(root);
	while(!nodes.isEmpty()){
    
    
		Node x = nodes.dequeue();
		keys.enqueue(x.key);
		if (x.left!=null){
    
    
			nodes.enqueue(x.left);
		}
		if (x.right!=null){
    
    
			nodes.enqueue(x.right);
		}
    }
	return keys;
}

//测试代码
public class Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		BinaryTree<String, String> bt = new BinaryTree<>();
		bt.put("E", "5");
		bt.put("B", "2");
		bt.put("G", "7");
		bt.put("A", "1");
		bt.put("D", "4");
		bt.put("F", "6");
		bt.put("H", "8");
		bt.put("C", "3");
		Queue<String> queue = bt.layerErgodic();
		for (String key : queue) {
    
    
			System.out.println(key+"="+bt.get(key));
		}
	}
}

1.7 The maximum depth problem of binary tree

Requirement:
Given a tree, please calculate the maximum depth of the tree (the number of nodes on the longest path from the root node of the tree to the farthest leaf node)
insert image description here

The tree above has a maximum depth of 4.

Implementation:
We add the following API to find the maximum depth on the tree created in 1.4:
public int maxDepth(): calculate the maximum depth of the entire tree
private int maxDepth(Node x): calculate the maximum depth of the specified tree x

Implementation steps:

  1. If the root node is empty, the maximum depth is 0;
  2. Calculate the maximum depth of the left subtree;
  3. Calculate the maximum depth of the right subtree;
  4. The maximum depth of the current tree = the greater of the maximum depth of the left subtree and the maximum depth of the right subtree + 1

code:

//计算整个树的最大深度
public int maxDepth() {
    
    
	return maxDepth(root);
}
//计算指定树x的最大深度
private int maxDepth(Node x) {
    
    
	//1.如果根结点为空,则最大深度为0;
	if (x == null) {
    
    
		return 0;
	}
	int max = 0;
	int maxL = 0;
	int maxR = 0;
	//2.计算左子树的最大深度;
	if (x.left != null) {
    
    
		maxL = maxDepth(x.left);
	}
	//3.计算右子树的最大深度;
	if (x.right != null) {
    
    
		maxR = maxDepth(x.right);
	}
	//4.当前树的最大深度=左子树的最大深度和右子树的最大深度中的较大者+1
	max = maxL > maxR ? maxL + 1 : maxR + 1;
	return max;
}

//测试代码
public class Test {
    
    
	public static void main(String[] args) throws Exception {
    
    
		BinaryTree<String, String> bt = new BinaryTree<>();
		bt.put("E", "5");
		bt.put("B", "2");
		bt.put("G", "7");
		bt.put("A", "1");
		bt.put("D", "4");
		bt.put("F", "6");
		bt.put("H", "8");
		bt.put("C", "3");
		int i = bt.maxDepth();
		System.out.println(i);
	}
}

1.8 The origami problem

Requirement:
Please put a strip of paper vertically on the table, then fold it in half from the bottom to the top once, press out the crease and unfold it. At this time, the crease is concave, that is, the direction of the crease protrusion points to the back of the paper strip. If the paper strip is folded in half continuously from the bottom to the top twice, and then unfolded after pressing out the creases, there are three creases at this time, which are the lower crease, the lower crease and the upper crease from top to bottom.

Given an input parameter N, it means that the paper strips are folded in half continuously from the bottom to the top N times, please print the direction of all creases from top to bottom. For example: when N=1, print: down; when N=2, print: down down up

insert image description here
Analysis:
We turn over the paper after half-folding and let the pink face down. At this time, the crease generated by the first half-fold is regarded as the root node, and the lower crease generated by the second half-fold is the left side of the node. child node, and the upper crease generated by the second half-fold is the right child node of the node, so that we can use a tree data structure to describe the crease generated after half-fold.

This tree has such characteristics:

  1. The root node is the lower crease;
  2. The left child node of each node is the lower crease;
  3. The right child node of each node is the upper crease;

insert image description here
Implementation steps:

  1. define node class
  2. Build a crease tree with depth N;
  3. Use inorder traversal to print out the contents of all nodes in the tree;

Build a crease tree of depth N:

  1. Fold in half for the first time, only one crease, create a root node;
  2. If it is not the first half fold, use the queue to save the root node;
  3. Loop through the queue:
    3.1 Take out a node from the queue;
    3.2 If the left child node of this node is not empty, add this left child node to the queue;
    3.3 If the right child node of this node If it is not empty, add the right child node to the queue;
    3.4 Determine whether the left child node and the right child node of the current node are not empty, if so, you need to create a value for the current node with the value down A left child node with value up and a right child node with value up.

code:

public class PaperFolding {
    
    
	public static void main(String[] args) {
    
    
		//构建折痕树
		Node tree = createTree(3);
		//遍历折痕树,并打印
		printTree(tree);
	}
	
	//3.使用中序遍历,打印出树中所有结点的内容;
	private static void printTree(Node tree) {
    
    
		if (tree==null){
    
    
			return;
		}
		printTree(tree.left);
		System.out.print(tree.item+",");
		printTree(tree.right);
	}
	
	//2.构建深度为N的折痕树;
	private static Node createTree(int N) {
    
    
		Node root = null;
		for (int i = 0; i <N ; i++) {
    
    
		if (i==0){
    
    
			//1.第一次对折,只有一条折痕,创建根结点;
			root = new Node("down",null,null);
		}else{
    
    
				//2.如果不是第一次对折,则使用队列保存根结点;
				Queue<Node> queue = new Queue<>();
				queue.enqueue(root);
				//3.循环遍历队列:
				while(!queue.isEmpty()){
    
    
					//3.1从队列中拿出一个结点;
					Node tmp = queue.dequeue();
					//3.2如果这个结点的左子结点不为空,则把这个左子结点添加到队列中;
					if (tmp.left!=null){
    
    
						queue.enqueue(tmp.left);
					}
					//3.3如果这个结点的右子结点不为空,则把这个右子结点添加到队列中;
					if (tmp.right!=null){
    
    
						queue.enqueue(tmp.right);
					}
					//3.4判断当前结点的左子结点和右子结点都不为空,如果是,则需要为当前结点创建一个值为down的左子结点,一个值为up的右子结点。
					if (tmp.left==null && tmp.right==null){
    
    
						tmp.left = new Node("down",null,null);
						tmp.right = new Node("up",null,null);
					}
				}
			}
		}
		return root;
	}
	//1.定义结点类
	private static class Node{
    
    
	//存储结点元素
	String item;
	//左子结点
	Node left;
	//右子结点
	Node right;

 //结点类
    private static class Node<T>{
    
    
        public T item;//存储元素
        public Node left;
        public Node right;

        public Node(T item, Node left, Node right) {
    
    
            this.item = item;
            this.left = left;
            this.right = right;
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_33417321/article/details/121982459