数据结构 - 二叉查询树的Java实现

数据结构 - 二叉查询树的Java实现


关于树及二叉树的概念和性质,请参考我的这篇文章:https://blog.csdn.net/funnyrand/article/details/81662602

本节将介绍二叉查询树(又称二叉排序树,二叉搜索树)的Java实现以及相关操作,所有的分析和算法都基于Java代码。

代码

先定义两个接口,IBinaryTreeNode 和 IBinaryTree,分别表示二叉树的结点类型和二叉树的类型。

package com.my.study.algorithm.tree.bstree;

/**
 * Binary tree node interface.
 *
 * @param <E>
 *            Node type
 */
public interface IBinaryTreeNode<E extends Comparable<E>> {

	/**
	 * Get left node.
	 * 
	 * @return left node
	 */
	IBinaryTreeNode<E> getLeftNode();

	/**
	 * Get right node.
	 * 
	 * @return right node
	 */
	IBinaryTreeNode<E> getRightNode();

	/**
	 * Get parent node.
	 * 
	 * @return parent node
	 */
	IBinaryTreeNode<E> getParentNode();

	/**
	 * Set left node.
	 * 
	 * @param leftNode
	 *            left node
	 */
	void setLeftNode(IBinaryTreeNode<E> leftNode);

	/**
	 * Set right node.
	 * 
	 * @param rightNode
	 *            right node
	 */
	void setRightNode(IBinaryTreeNode<E> rightNode);

	/**
	 * Set parent node.
	 * 
	 * @param parentNode
	 *            parent node
	 */
	void setParentNode(IBinaryTreeNode<E> parentNode);

	/**
	 * Get value.
	 * 
	 * @return node value
	 */
	E getValue();

	/**
	 * Set node value.
	 * 
	 * @param value
	 *            node value
	 */
	void setValue(E value);

}
package com.my.study.algorithm.tree.bstree;

import java.util.List;

/**
 * Binary tree interface.
 * 
 * @param <E>
 *            Node type
 */
public interface IBinaryTree<E extends Comparable<E>> {

	/**
	 * Get root node
	 * 
	 * @return root node
	 */
	IBinaryTreeNode<E> getRoot();

	/**
	 * Find element from root node.
	 * 
	 * @param e
	 *            element
	 * @return null if cannot find specified element or object IBinaryTreeNode<E> if
	 *         find it
	 */
	IBinaryTreeNode<E> find(E e);

	/**
	 * Find element from a specified node.
	 * 
	 * @param e
	 *            element
	 * @return null if cannot find specified element or object IBinaryTreeNode<E> if
	 *         find it
	 */
	IBinaryTreeNode<E> find(IBinaryTreeNode<E> startNode, E e);

	/**
	 * Insert an element under root node.
	 * 
	 * @param e
	 *            element
	 * 
	 * @return IBinaryTreeNode<E> object
	 */
	IBinaryTreeNode<E> insert(E e);

	/**
	 * Remove an node under root node.
	 * 
	 * @param e
	 *            element
	 * @return null if cannot find e, or e if remove successfully
	 */
	E remove(E e);

	/**
	 * Find previous node by a specified node.
	 * 
	 * @param startNode
	 *            start node
	 * @return null if cannot find previous node or an IBinaryTreeNode<E> object
	 */
	IBinaryTreeNode<E> findPre(IBinaryTreeNode<E> startNode);

	/**
	 * Find next node by a specified node.
	 * 
	 * @param startNode
	 *            start node
	 * @return null if cannot find next node or an IBinaryTreeNode<E> object
	 */
	IBinaryTreeNode<E> findNext(IBinaryTreeNode<E> startNode);

	/**
	 * Preorder traversal from root node.
	 * 
	 * @return List<E> object.
	 */
	List<E> preorderTraversal();

	/**
	 * Preorder traversal from a specified node.
	 * 
	 * @param startNode
	 *            start node
	 * @return List<E> object
	 */
	List<E> prerderTraversal(IBinaryTreeNode<E> startNode);

	/**
	 * Inorder traversal from root node.
	 * 
	 * @return List<E> object.
	 */
	List<E> inorderTraversal();

	/**
	 * Inorder traversal from a specified node.
	 * 
	 * @param startNode
	 *            start node
	 * @return List<E> object
	 */
	List<E> inorderTraversal(IBinaryTreeNode<E> startNode);

	/**
	 * Postorder traversal from root node.
	 * 
	 * @return List<E> object
	 */
	List<E> postorderTraversal();

	/**
	 * Postorder traversal from a specified node.
	 * 
	 * @param startNode
	 *            start node
	 * @return List<E> object
	 */
	List<E> postorderTraversal(IBinaryTreeNode<E> startNode);

	/**
	 * Get height from a specified node.
	 * 
	 * @param startNode
	 *            start node
	 * @return node height
	 */
	int getHeight(IBinaryTreeNode<E> startNode);

	/**
	 * Get tree height.
	 * 
	 * @return tree height
	 */
	int getHeight();

	/**
	 * Get size of a specified node.
	 * 
	 * @param startNode
	 *            start node
	 * @return node size
	 */
	int getSize(IBinaryTreeNode<E> startNode);

	/**
	 * Get tree size.
	 * 
	 * @return tree size
	 */
	int getSize();

}

接着编写二叉查询树的实现类,BinarySearchTree。

package com.my.study.algorithm.tree.bstree;

import java.util.ArrayList;
import java.util.List;

/**
 * This class demonstrates how to generate a binary search tree, and its related
 * operations, such as insert, remove, search, traversal, height, size, etc.
 * 
 * @param <E>
 *            element type
 */
public class BinarySearchTree<E extends Comparable<E>> implements IBinaryTree<E> {

	// Root node
	private IBinaryTreeNode<E> root;

	@Override
	public IBinaryTreeNode<E> getRoot() {
		return root;
	}

	/**
	 * Empty constructor, will generate a empty tree.
	 */
	public BinarySearchTree() {
	}

	/**
	 * Constructor method, generate tree by array.
	 * 
	 * @param data
	 *            array data
	 */
	public BinarySearchTree(E[] data) {
		if (data == null || data.length == 0) {
			throw new IllegalArgumentException("Parameter data cannot be null or empty.");
		}
		generateBinarySearchTree(data);
	}

	@Override
	public IBinaryTreeNode<E> find(E e) {
		IBinaryTreeNode<E> node = search(root, e);
		if (node != null && node.getValue().compareTo(e) == 0) {
			return node;
		}
		return null;
	}

	@Override
	public IBinaryTreeNode<E> find(IBinaryTreeNode<E> startNode, E e) {
		if (startNode == null) {
			return null;
		}
		IBinaryTreeNode<E> node = search(startNode, e);
		if (node != null && node.getValue().compareTo(e) == 0) {
			return node;
		}
		return null;
	}

	@Override
	public IBinaryTreeNode<E> insert(E e) {
		// Empty tree, then create root node.
		if (root == null) {
			root = new TreeNode<E>(e);
			return root;
		}
		return insert(root, e);
	}

	@Override
	public E remove(E e) {
		return remove(root, e);
	}

	@Override
	public IBinaryTreeNode<E> findPre(IBinaryTreeNode<E> startNode) {
		if (startNode == null) {
			return null;
		}
		// Previous node exists on the right side in the left sub tree of specified node
		IBinaryTreeNode<E> selectedNode = startNode.getLeftNode();
		if (selectedNode == null) {
			return null;
		}
		while (selectedNode.getRightNode() != null) {
			selectedNode = selectedNode.getRightNode();
		}
		return selectedNode;
	}

	@Override
	public IBinaryTreeNode<E> findNext(IBinaryTreeNode<E> startNode) {
		if (startNode == null) {
			return null;
		}
		// Next node exists on the left side in the right sub tree of specified node
		IBinaryTreeNode<E> selectedNode = startNode.getRightNode();
		if (selectedNode == null) {
			return null;
		}
		while (selectedNode.getLeftNode() != null) {
			selectedNode = selectedNode.getLeftNode();
		}
		return selectedNode;
	}

	@Override
	public List<E> preorderTraversal() {
		return prerderTraversal(root);
	}

	@Override
	public List<E> prerderTraversal(IBinaryTreeNode<E> startNode) {
		List<E> resultList = new ArrayList<>();
		if (startNode == null) {
			return resultList;
		}
		// Parent->Left->Right
		resultList.add(startNode.getValue());
		resultList.addAll(prerderTraversal(startNode.getLeftNode()));
		resultList.addAll(prerderTraversal(startNode.getRightNode()));
		return resultList;
	}

	@Override
	public List<E> inorderTraversal() {
		return inorderTraversal(root);
	}

	@Override
	public List<E> inorderTraversal(IBinaryTreeNode<E> startNode) {
		List<E> resultList = new ArrayList<>();
		if (startNode == null) {
			return resultList;
		}
		// Left->Parent->Right
		resultList.addAll(inorderTraversal(startNode.getLeftNode()));
		resultList.add(startNode.getValue());
		resultList.addAll(inorderTraversal(startNode.getRightNode()));
		return resultList;
	}

	@Override
	public List<E> postorderTraversal() {
		return postorderTraversal(root);
	}

	@Override
	public List<E> postorderTraversal(IBinaryTreeNode<E> startNode) {
		List<E> resultList = new ArrayList<>();
		if (startNode == null) {
			return resultList;
		}
		// Left->Right->Parent
		resultList.addAll(postorderTraversal(startNode.getLeftNode()));
		resultList.addAll(postorderTraversal(startNode.getRightNode()));
		resultList.add(startNode.getValue());
		return resultList;
	}

	@Override
	public int getHeight(IBinaryTreeNode<E> startNode) {
		if (startNode == null) {
			return 0;
		}
		// Height equals max(left_height, right_height) + 1
		int height = Math.max(getHeight(startNode.getLeftNode()), getHeight(startNode.getRightNode())) + 1;
		return height;
	}

	@Override
	public int getHeight() {
		return getHeight(root);
	}

	@Override
	public int getSize(IBinaryTreeNode<E> startNode) {
		int size = 0;
		if (startNode == null) {
			return 0;
		}
		// Size equals left_size + 1(self) + right_size
		size = getSize(startNode.getLeftNode()) + 1 + getSize(startNode.getRightNode());
		return size;
	}

	@Override
	public int getSize() {
		return getSize(root);
	}

	/*
	 * Generate binary search tree by array.
	 */
	private void generateBinarySearchTree(E[] data) {
		for (int i = 0; i < data.length; i++) {
			insert(data[i]);
		}
	}

	/*
	 * Search node by specified element. This is an very import method of binary
	 * search tree, this method will return a node which value equals to e or the
	 * value is the most similar to e. This method provide a way to find a position
	 * to insert element e.
	 * 
	 * @param startNode start node
	 * 
	 * @param e element
	 * 
	 * @return null if tree is empty or a node object which value equals to e or the
	 * value is the most similar to e.
	 */
	private IBinaryTreeNode<E> search(IBinaryTreeNode<E> startNode, E e) {
		if (root == null) {
			return null;
		}
		IBinaryTreeNode<E> returnedNode = startNode;
		// Equals then return
		if (startNode.getValue().compareTo(e) == 0) {
			return returnedNode;
			// Search from left node
		} else if (startNode.getValue().compareTo(e) > 0) {
			if (startNode.getLeftNode() != null) {
				returnedNode = search(startNode.getLeftNode(), e);
			}
			// Search from right node
		} else {
			if (startNode.getRightNode() != null) {
				returnedNode = search(startNode.getRightNode(), e);
			}
		}
		return returnedNode;
	}

	/*
	 * Insert an element under a specified node.
	 * 
	 * @param startNode Start node
	 * 
	 * @param e element
	 * 
	 * @return IBinaryTreeNode<E> object or null if cannot insert the element
	 */
	private IBinaryTreeNode<E> insert(IBinaryTreeNode<E> startNode, E e) {
		if (startNode == null) {
			return null;
		}
		IBinaryTreeNode<E> seledtedNode = startNode;
		while (true) {
			seledtedNode = search(seledtedNode, e);
			IBinaryTreeNode<E> newNode = new TreeNode<E>(e);
			// e is lesser than searched node, so need to create a new left leaf node
			if (seledtedNode.getValue().compareTo(e) > 0) {
				seledtedNode.setLeftNode(newNode);
				break;
				// e is greater than searched node, so need to create a new right leaf node
			} else if (seledtedNode.getValue().compareTo(e) < 0) {
				seledtedNode.setRightNode(newNode);
				break;
			}
			// e equals to selected node, there are 3 scenarios
			if (seledtedNode.getLeftNode() == null) {
				seledtedNode.setLeftNode(newNode);
				break;
			} else if (seledtedNode.getRightNode() == null) {
				seledtedNode.setRightNode(newNode);
				break;
			} else {
				// continue to search a node which value is e
				seledtedNode = seledtedNode.getLeftNode();
			}
		}
		return seledtedNode;
	}

	/*
	 * Remove an element under a specified node.
	 * 
	 * @param startNode
	 *            start node.
	 * @param e
	 *            element
	 * @return null if cannot find e, or e if remove successfully
	 */
	private E remove(IBinaryTreeNode<E> startNode, E e) {
		// Just return null if cannot find e
		IBinaryTreeNode<E> node = find(startNode, e);
		if (node == null) {
			return null;
		}
		// When left node is null, remove this node and use its right node to instead
		if (node.getLeftNode() == null) {
			if (root == node) {
				root = node.getRightNode();
			} else {
				if (node.getParentNode().getLeftNode() == node) {
					node.getParentNode().setLeftNode(node.getRightNode());
				} else {
					node.getParentNode().setRightNode(node.getRightNode());
				}
			}
			node.setLeftNode(null);
			node.setRightNode(null);
			return e;
		}
		// When left node is not null, find its previous node
		IBinaryTreeNode<E> preNode = findPre(node);
		// Change node value
		node.setValue(preNode.getValue());
		// Remove previous node and use its left node to instead.
		// At this time, preNode has no right node, so no need to check right node
		if (preNode.getParentNode().getLeftNode() == preNode) {
			preNode.getParentNode().setLeftNode(preNode.getLeftNode());
		} else {
			preNode.getParentNode().setRightNode(preNode.getLeftNode());
		}
		return e;
	}

	/*
	 * Tree node class.
	 */
	private static class TreeNode<E extends Comparable<E>> implements IBinaryTreeNode<E> {
		// Value
		private E value;
		// Parent node reference
		private IBinaryTreeNode<E> parent;
		// Left node reference
		private IBinaryTreeNode<E> left;
		// Right node reference
		private IBinaryTreeNode<E> right;

		public TreeNode(E value) {
			this.value = value;
		}

		@Override
		public IBinaryTreeNode<E> getLeftNode() {
			return left;
		}

		@Override
		public IBinaryTreeNode<E> getRightNode() {
			return right;
		}

		@Override
		public IBinaryTreeNode<E> getParentNode() {
			return parent;
		}

		@Override
		public void setLeftNode(IBinaryTreeNode<E> leftNode) {
			this.left = leftNode;
			if (this.left != null) {
				this.left.setParentNode(this);
			}
		}

		@Override
		public void setRightNode(IBinaryTreeNode<E> rightNode) {
			this.right = rightNode;
			if (this.right != null) {
				this.right.setParentNode(this);
			}
		}

		@Override
		public void setParentNode(IBinaryTreeNode<E> parentNode) {
			this.parent = parentNode;
		}

		@Override
		public E getValue() {
			return value;
		}

		@Override
		public void setValue(E value) {
			this.value = value;
		}
	}
}

为了打印出二叉树的结构,我们编写了一个工具类,BinaryTreePrinter。

package com.my.study.algorithm.tree.bstree;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Print structure of binary tree.
 */
public class BinaryTreePrinter {

	/**
	 * Print tree structure.
	 * 
	 * @param tree IBinaryTree<E>
	 */
	public static <E extends Comparable<E>> void printTree(IBinaryTree<E> tree) {
		IBinaryTreeNode<E> root = tree.getRoot();
		int maxLevel = BinaryTreePrinter.maxLevel(root);
		printNodeInternal(Collections.singletonList(root), 1, maxLevel);
	}

	private static <E extends Comparable<E>> void printNodeInternal(List<IBinaryTreeNode<E>> nodes, int level,
			int maxLevel) {
		if (nodes.isEmpty() || BinaryTreePrinter.isAllElementsNull(nodes))
			return;

		int floor = maxLevel - level;
		int endgeLines = (int) Math.pow(2, (Math.max(floor - 1, 0)));
		int firstSpaces = (int) Math.pow(2, (floor)) - 1;
		int betweenSpaces = (int) Math.pow(2, (floor + 1));

		BinaryTreePrinter.printWhitespaces(firstSpaces);

		List<IBinaryTreeNode<E>> newNodes = new ArrayList<IBinaryTreeNode<E>>();
		for (IBinaryTreeNode<E> node : nodes) {
			if (node != null) {
				System.out.print(node.getValue());
				newNodes.add(node.getLeftNode());
				newNodes.add(node.getRightNode());
			} else {
				newNodes.add(null);
				newNodes.add(null);
				System.out.print("");
			}
			int valueLength = (node == null) ? 0 : node.getValue().toString().length();
			BinaryTreePrinter.printWhitespaces(betweenSpaces - valueLength);
		}
		System.out.println("");

		for (int i = 1; i <= endgeLines; i++) {
			for (int j = 0; j < nodes.size(); j++) {
				BinaryTreePrinter.printWhitespaces(firstSpaces - i);
				if (nodes.get(j) == null) {
					BinaryTreePrinter.printWhitespaces(endgeLines + endgeLines + i + 1);
					continue;
				}
				if (nodes.get(j).getLeftNode() != null) {
					System.out.print("/");
				} else {
					BinaryTreePrinter.printWhitespaces(1);
				}

				BinaryTreePrinter.printWhitespaces(i + i - 1);

				if (nodes.get(j).getRightNode() != null) {
					System.out.print("\\");
				} else {
					BinaryTreePrinter.printWhitespaces(1);
				}
				BinaryTreePrinter.printWhitespaces(endgeLines + endgeLines - i);
			}
			System.out.println("");
		}
		printNodeInternal(newNodes, level + 1, maxLevel);
	}

	private static void printWhitespaces(int count) {
		for (int i = 0; i < count; i++)
			System.out.print(" ");
	}

	private static <E extends Comparable<E>> int maxLevel(IBinaryTreeNode<E> node) {
		if (node == null) {
			return 0;
		}
		int level = Math.max(BinaryTreePrinter.maxLevel(node.getLeftNode()),
				BinaryTreePrinter.maxLevel(node.getRightNode())) + 1;
		return level;
	}

	private static <E> boolean isAllElementsNull(List<E> list) {
		for (Object object : list) {
			if (object != null) {
				return false;
			}
		}
		return true;
	}
}

最后是一个测试类,BinarySearchTreeTest,用于测试二叉查询树的各种操作。

package com.my.study.algorithm.tree.bstree;

import java.util.Date;
import java.util.List;

public class BinarySearchTreeTest {

	private static final Integer[] ARRAY = { 5, 10, 13, 66, -3, -7, 9, 1, 9, -8, 23 };

	public static void main(String[] args) {
		testGenerateBSTreeByArray();
		// testGenerateBSTreeByInserting();
		// testRemoveElementFromTree();
		// testSearchData();
		// testPerformance();
		// testLinkedStyleTree();
		// testLinkedStyleTreeByDeleting();
	}

	private static void testGenerateBSTreeByArray() {
		IBinaryTree<Integer> tree = new BinarySearchTree<>(ARRAY);
		printTreeInfo(tree);
	}

	private static void testGenerateBSTreeByInserting() {
		IBinaryTree<Integer> tree = new BinarySearchTree<>();
		for (int data : ARRAY) {
			tree.insert(data);
			System.out.println("Inserted data: " + data);
			printTreeInfo(tree);
		}
	}

	private static void testRemoveElementFromTree() {
		IBinaryTree<Integer> tree = new BinarySearchTree<>(ARRAY);
		printTreeInfo(tree);
		for (int data : ARRAY) {
			tree.remove(data);
			System.out.println("Removed data: " + data);
			printTreeInfo(tree);
		}
	}

	private static void testSearchData() {
		IBinaryTree<Integer> tree = new BinarySearchTree<>(ARRAY);
		Integer[] findData = { 10, -7, 100, 8, -8 };
		for (int data : findData) {
			IBinaryTreeNode<Integer> node = tree.find(data);
			System.out.println("Data: " + data + ", exist?  " + (node != null));
		}
	}

	private static void testPerformance() {
		// Original data size
		int originalDataSize = 10000000;
		int minNum = 0;
		int maxNum = originalDataSize;
		Integer[] data = generateRandomArrays(minNum, maxNum, originalDataSize);

		// Generate tree
		long time1 = new Date().getTime();
		IBinaryTree<Integer> tree = new BinarySearchTree<>(data);
		long time2 = new Date().getTime();
		System.out.println("Generate tree takes: " + (time2 - time1) / 1000 + " seconds, tree size: " + tree.getSize());

		// Selected data size
		int selectedDataSize = originalDataSize;
		Integer[] selectedData = generateRandomArrays(minNum, maxNum, selectedDataSize);

		// Find data by tree
		long time3 = new Date().getTime();
		long findDataCount = 0;
		for (int v : selectedData) {
			if (tree.find(v) != null) {
				findDataCount++;
			}
		}
		long time4 = new Date().getTime();
		System.out.println(
				"Find data by tree takes: " + (time4 - time3) / 1000 + " seconds, data size: " + selectedDataSize);
		System.out.println("Find data percentage: " + (findDataCount * 1.0 / selectedDataSize));

		// Find data by array, traditional way
		long time5 = new Date().getTime();
		long findDataCount2 = 0;
		for (int i = 0; i < selectedData.length; i++) {
			for (int k : data) {
				if (selectedData[i] == k) {
					findDataCount2++;
					break;
				}
			}
			if (i % 100000 == 0) {
				System.out.println("i / 100000 = " + i / 100000);
			}
		}
		long time6 = new Date().getTime();
		System.out.println(
				"Find data by array takes: " + (time6 - time5) / 1000 + " seconds, data size: " + selectedDataSize);
		System.out.println("Find data percentage: " + (findDataCount2 * 1.0 / selectedDataSize));

		/*
		 * Result analyze:
		 * 
		 * When tree size is 1,000,000 and selected data size is 10,000.
		 * 
		 * Generate tree takes: 1 seconds, tree size: 1000000
		 * 
		 * Find data by tree takes: 0 seconds, data size: 10000
		 * 
		 * Find data percentage: 0.6349
		 * 
		 * Find data by array takes: 49 seconds, data size: 10000
		 * 
		 * Find data percentage: 0.6349
		 * 
		 * 
		 * When tree size 1s 10,000,000, selected data size is 10,000,000
		 * 
		 * Generate tree takes: 19 seconds, tree size: 10000000
		 * 
		 * Find data by tree takes: 22 seconds, data size: 10000000
		 * 
		 * Find data percentage: 0.6321235
		 * 
		 * Find data by array takes: 151971 seconds, data size: 10000000
		 * 
		 * Find data percentage: 0.6321235
		 * 
		 */
	}

	public static void testLinkedStyleTree() {
		Integer[] array = { 1, 2, 3, 4, 5 };
		IBinaryTree<Integer> tree = new BinarySearchTree<>(array);
		printTreeInfo(tree);
		Integer[] array2 = { 5, 4, 3, 2, 1 };
		tree = new BinarySearchTree<>(array2);
		printTreeInfo(tree);
	}
	
	public static void testLinkedStyleTreeByDeleting() {
		Integer[] data = generateRandomArrays(0, 100, 100);
		IBinaryTree<Integer> tree = new BinarySearchTree<>(data);
		while(tree.getSize() > 6) {
			List<Integer> treeList = tree.inorderTraversal();
			int randomIndex = (int) (Math.random() * (treeList.size()));
			tree.remove(treeList.get(randomIndex));
		}
		printTreeInfo(tree);
	}

	private static Integer[] generateRandomArrays(int min, int max, int size) {
		Integer[] data = new Integer[size];
		for (int i = 0; i < size; i++) {
			int num = (int) (Math.random() * (max - min + 1)) + min;
			data[i] = num;
		}
		return data;
	}

	private static <E extends Comparable<E>> void printTreeInfo(IBinaryTree<E> tree) {
		System.out.println("Tree height: " + tree.getHeight());
		System.out.println("Tree size:   " + tree.getSize());
		System.out.println("Pre order traversal:  " + tree.preorderTraversal());
		System.out.println("In order traversal:   " + tree.inorderTraversal());
		System.out.println("Post order traversal: " + tree.postorderTraversal());
		System.out.println("Tree structure:");
		BinaryTreePrinter.printTree(tree);
		System.out.println("-----------------------------------------------");
	}
}

、二叉查询树的重要操作

1. 查询

参考方法:private IBinaryTreeNode<E> search(IBinaryTreeNode<E> startNode, E e) {...}。

该操作是最基本的一个操作,用于查询给定结点下面值和e相等或者最接近的那个结点。值相等的结点很好理解,直接比较数据大小即可,如果存在多个值相等的结点,则返回第一个。最接近的结点表示如果将e插入到二叉查询树里面,该结点可以作为新结点的父结点。查询的基本思路:从给定结点开始遍历,如果值相等则返回当前结点,如果e小于当前结点则从当前结点的左子树递归搜索,如果e大于当前结点则从当前结点的右子树递归搜索。这类似于二分查找,只不过二分查找在查找不到的情况下返回null,而此时需要返回最接近的那个结点。search操作只有当root为null时(空树)返回null,其它任何时候都不能返回null,因为总存在一个最接近的结点。

2. 插入

参考方法:private IBinaryTreeNode<E> insert(IBinaryTreeNode<E> startNode, E e) {...}。

该操作用于在以startNode为根节点的子树下面插入新的结点e,使得插入完毕后以startNode为根结点的子树还是二叉查询树。插入的基本思路:首先调用search方法,找到插入点。如果e小于插入点,那么在插入点下面创建一个新的左子结点,如果大于则创建一个新的右子结点。如果e等于插入点,有三种场景,如果插入点没有左结点,则创建新的左子结点,如果插入点没有右结点,则创建新的右子节点,如果左右结点都存在,那么从左结点开始继续搜索(当然也可以从右结点开始搜索)。

3. 删除

参考方法:private E remove(IBinaryTreeNode<E> startNode, E e) {...}

该操作用于在以startNode为根节点的的子树下面删除某个值为e的结点,使得删除完成后以startNode为根结点的子树还是二叉查询树。删除的基本思路:首先调用search方法,找到删除点。如果删除点的值不等于e,那么说明不存在值为e的结点,返回null。如果删除点的值等于e,那么分两种情况,判断其左结点是否存在。如果左结点不存在,那么移除删除点,以删除点的右结点取而代之。如果左结点存在,则找到删除点的直接前驱结点,交换删除点和前驱结点的值,并移除原前驱结点,以原前驱结点的左结点取而代之。注:此时原前驱结点不存在右结点,故不需要判断。

4. 几种遍历操作

前序遍历public List<E> preorderTraversal() {...}。

中序遍历:public List<E> inorderTraversal() {...}。

后序遍历:public List<E> postorderTraversal() {...}。

5. 其它操作

IBinaryTree里面还定义了其它操作,例如:find, getRoot, getSize, getHeight等,算法比较简单,就不一一说明,参考源码即可。

三、二叉查询树的测试

参考测试类代码:BinarySearchTreeTest,并逐一执行main方法里面的方法:

方法名 说明
testGenerateBSTreeByArray 用于演示通过给定一个数组来生成二叉查询树。
testGenerateBSTreeByInserting

用于演示二叉查询树的插入过程。

testRemoveElementFromTree 用于演示二叉查询树的删除过程。
testSearchData 用于演示二叉查询树的查询结果。
testPerformance 用于演示二叉查询树的查询性能。
testLinkedStyleTree 用于演示二叉查询树蜕变成链表的场景。
testLinkedStyleTreeByDeleting 用于演示二叉查询树在经过多次删除操作后蜕变成近似链表的场景。

1. testGenerateBSTreeByArray

通过数组生成二叉查询树,本质上是将给定数组的元素逐一插入到二叉查询树里面。

输出结果:

Tree height: 5
Tree size:   11
Pre order traversal:  [5, -3, -7, -8, 1, 10, 9, 9, 13, 66, 23]
In order traversal:   [-8, -7, -3, 1, 5, 9, 9, 10, 13, 23, 66]
Post order traversal: [-8, -7, 1, -3, 9, 9, 23, 66, 13, 10, 5]
Tree structure:
               5                               
              / \               
             /   \              
            /     \             
           /       \            
          /         \           
         /           \          
        /             \         
       /               \        
       -3              10              
      / \             / \       
     /   \           /   \      
    /     \         /     \     
   /       \       /       \    
   -7      1       9       13      
  /               /         \   
 /               /           \  
 -8              9           66  
                            /   
                            23  
                                                                
-----------------------------------------------

2. testGenerateBSTreeByInserting

演示了每次插入元素后二叉查询树的结构变化过程,需要结合上面的插入算法来分析。

输出结果:

Inserted data: 5
Tree height: 1
Tree size:   1
Pre order traversal:  [5]
In order traversal:   [5]
Post order traversal: [5]
Tree structure:
5 
    
-----------------------------------------------
Inserted data: 10
Tree height: 2
Tree size:   2
Pre order traversal:  [5, 10]
In order traversal:   [5, 10]
Post order traversal: [10, 5]
Tree structure:
 5   
  \ 
  10
        
-----------------------------------------------
Inserted data: 13
Tree height: 3
Tree size:   3
Pre order traversal:  [5, 10, 13]
In order traversal:   [5, 10, 13]
Post order traversal: [13, 10, 5]
Tree structure:
   5       
    \   
     \  
     10  
      \ 
      13
                
-----------------------------------------------
Inserted data: 66
Tree height: 4
Tree size:   4
Pre order traversal:  [5, 10, 13, 66]
In order traversal:   [5, 10, 13, 66]
Post order traversal: [66, 13, 10, 5]
Tree structure:
       5               
        \       
         \      
          \     
           \    
           10      
            \   
             \  
             13  
              \ 
              66
                                
-----------------------------------------------
Inserted data: -3
Tree height: 4
Tree size:   5
Pre order traversal:  [5, -3, 10, 13, 66]
In order traversal:   [-3, 5, 10, 13, 66]
Post order traversal: [-3, 66, 13, 10, 5]
Tree structure:
       5               
      / \       
     /   \      
    /     \     
   /       \    
   -3      10      
            \   
             \  
             13  
              \ 
              66
                                
-----------------------------------------------
Inserted data: -7
Tree height: 4
Tree size:   6
Pre order traversal:  [5, -3, -7, 10, 13, 66]
In order traversal:   [-7, -3, 5, 10, 13, 66]
Post order traversal: [-7, -3, 66, 13, 10, 5]
Tree structure:
       5               
      / \       
     /   \      
    /     \     
   /       \    
   -3      10      
  /         \   
 /           \  
 -7          13  
              \ 
              66
                                
-----------------------------------------------
Inserted data: 9
Tree height: 4
Tree size:   7
Pre order traversal:  [5, -3, -7, 10, 9, 13, 66]
In order traversal:   [-7, -3, 5, 9, 10, 13, 66]
Post order traversal: [-7, -3, 9, 66, 13, 10, 5]
Tree structure:
       5               
      / \       
     /   \      
    /     \     
   /       \    
   -3      10      
  /       / \   
 /       /   \  
 -7      9   13  
              \ 
              66
                                
-----------------------------------------------
Inserted data: 1
Tree height: 4
Tree size:   8
Pre order traversal:  [5, -3, -7, 1, 10, 9, 13, 66]
In order traversal:   [-7, -3, 1, 5, 9, 10, 13, 66]
Post order traversal: [-7, 1, -3, 9, 66, 13, 10, 5]
Tree structure:
       5               
      / \       
     /   \      
    /     \     
   /       \    
   -3      10      
  / \     / \   
 /   \   /   \  
 -7  1   9   13  
              \ 
              66
                                
-----------------------------------------------
Inserted data: 9
Tree height: 4
Tree size:   9
Pre order traversal:  [5, -3, -7, 1, 10, 9, 9, 13, 66]
In order traversal:   [-7, -3, 1, 5, 9, 9, 10, 13, 66]
Post order traversal: [-7, 1, -3, 9, 9, 66, 13, 10, 5]
Tree structure:
       5               
      / \       
     /   \      
    /     \     
   /       \    
   -3      10      
  / \     / \   
 /   \   /   \  
 -7  1   9   13  
        /     \ 
        9     66
                                
-----------------------------------------------
Inserted data: -8
Tree height: 4
Tree size:   10
Pre order traversal:  [5, -3, -7, -8, 1, 10, 9, 9, 13, 66]
In order traversal:   [-8, -7, -3, 1, 5, 9, 9, 10, 13, 66]
Post order traversal: [-8, -7, 1, -3, 9, 9, 66, 13, 10, 5]
Tree structure:
       5               
      / \       
     /   \      
    /     \     
   /       \    
   -3      10      
  / \     / \   
 /   \   /   \  
 -7  1   9   13  
/       /     \ 
-8      9     66
                                
-----------------------------------------------
Inserted data: 23
Tree height: 5
Tree size:   11
Pre order traversal:  [5, -3, -7, -8, 1, 10, 9, 9, 13, 66, 23]
In order traversal:   [-8, -7, -3, 1, 5, 9, 9, 10, 13, 23, 66]
Post order traversal: [-8, -7, 1, -3, 9, 9, 23, 66, 13, 10, 5]
Tree structure:
               5                               
              / \               
             /   \              
            /     \             
           /       \            
          /         \           
         /           \          
        /             \         
       /               \        
       -3              10              
      / \             / \       
     /   \           /   \      
    /     \         /     \     
   /       \       /       \    
   -7      1       9       13      
  /               /         \   
 /               /           \  
 -8              9           66  
                            /   
                            23  
                                                                
-----------------------------------------------

3. testRemoveElementFromTree

演示了每次删除元素后,二叉查询树的结构变化过程,需要结合上面的删除算法来分析。

输出结果:

Tree height: 5
Tree size:   11
Pre order traversal:  [5, -3, -7, -8, 1, 10, 9, 9, 13, 66, 23]
In order traversal:   [-8, -7, -3, 1, 5, 9, 9, 10, 13, 23, 66]
Post order traversal: [-8, -7, 1, -3, 9, 9, 23, 66, 13, 10, 5]
Tree structure:
               5                               
              / \               
             /   \              
            /     \             
           /       \            
          /         \           
         /           \          
        /             \         
       /               \        
       -3              10              
      / \             / \       
     /   \           /   \      
    /     \         /     \     
   /       \       /       \    
   -7      1       9       13      
  /               /         \   
 /               /           \  
 -8              9           66  
                            /   
                            23  
                                                                
-----------------------------------------------
Removed data: 5
Tree height: 5
Tree size:   10
Pre order traversal:  [1, -3, -7, -8, 10, 9, 9, 13, 66, 23]
In order traversal:   [-8, -7, -3, 1, 9, 9, 10, 13, 23, 66]
Post order traversal: [-8, -7, -3, 9, 9, 23, 66, 13, 10, 1]
Tree structure:
               1                               
              / \               
             /   \              
            /     \             
           /       \            
          /         \           
         /           \          
        /             \         
       /               \        
       -3              10              
      /               / \       
     /               /   \      
    /               /     \     
   /               /       \    
   -7              9       13      
  /               /         \   
 /               /           \  
 -8              9           66  
                            /   
                            23  
                                                                
-----------------------------------------------
Removed data: 10
Tree height: 5
Tree size:   9
Pre order traversal:  [1, -3, -7, -8, 9, 9, 13, 66, 23]
In order traversal:   [-8, -7, -3, 1, 9, 9, 13, 23, 66]
Post order traversal: [-8, -7, -3, 9, 23, 66, 13, 9, 1]
Tree structure:
               1                               
              / \               
             /   \              
            /     \             
           /       \            
          /         \           
         /           \          
        /             \         
       /               \        
       -3              9               
      /               / \       
     /               /   \      
    /               /     \     
   /               /       \    
   -7              9       13      
  /                         \   
 /                           \  
 -8                          66  
                            /   
                            23  
                                                                
-----------------------------------------------
Removed data: 13
Tree height: 4
Tree size:   8
Pre order traversal:  [1, -3, -7, -8, 9, 9, 66, 23]
In order traversal:   [-8, -7, -3, 1, 9, 9, 23, 66]
Post order traversal: [-8, -7, -3, 9, 23, 66, 9, 1]
Tree structure:
       1               
      / \       
     /   \      
    /     \     
   /       \    
   -3      9       
  /       / \   
 /       /   \  
 -7      9   66  
/           /   
-8          23  
                                
-----------------------------------------------
Removed data: 66
Tree height: 4
Tree size:   7
Pre order traversal:  [1, -3, -7, -8, 9, 9, 23]
In order traversal:   [-8, -7, -3, 1, 9, 9, 23]
Post order traversal: [-8, -7, -3, 9, 23, 9, 1]
Tree structure:
       1               
      / \       
     /   \      
    /     \     
   /       \    
   -3      9       
  /       / \   
 /       /   \  
 -7      9   23  
/               
-8              
                                
-----------------------------------------------
Removed data: -3
Tree height: 3
Tree size:   6
Pre order traversal:  [1, -7, -8, 9, 9, 23]
In order traversal:   [-8, -7, 1, 9, 9, 23]
Post order traversal: [-8, -7, 9, 23, 9, 1]
Tree structure:
   1       
  / \   
 /   \  
 -7  9   
/   / \ 
-8  9 23
                
-----------------------------------------------
Removed data: -7
Tree height: 3
Tree size:   5
Pre order traversal:  [1, -8, 9, 9, 23]
In order traversal:   [-8, 1, 9, 9, 23]
Post order traversal: [-8, 9, 23, 9, 1]
Tree structure:
   1       
  / \   
 /   \  
 -8  9   
    / \ 
    9 23
                
-----------------------------------------------
Removed data: 9
Tree height: 3
Tree size:   4
Pre order traversal:  [1, -8, 9, 23]
In order traversal:   [-8, 1, 9, 23]
Post order traversal: [-8, 23, 9, 1]
Tree structure:
   1       
  / \   
 /   \  
 -8  9   
      \ 
      23
                
-----------------------------------------------
Removed data: 1
Tree height: 3
Tree size:   3
Pre order traversal:  [-8, 9, 23]
In order traversal:   [-8, 9, 23]
Post order traversal: [23, 9, -8]
Tree structure:
   -8      
    \   
     \  
     9   
      \ 
      23
                
-----------------------------------------------
Removed data: 9
Tree height: 2
Tree size:   2
Pre order traversal:  [-8, 23]
In order traversal:   [-8, 23]
Post order traversal: [23, -8]
Tree structure:
 -8  
  \ 
  23
        
-----------------------------------------------
Removed data: -8
Tree height: 1
Tree size:   1
Pre order traversal:  [23]
In order traversal:   [23]
Post order traversal: [23]
Tree structure:
23
    
-----------------------------------------------
Removed data: 23
Tree height: 0
Tree size:   0
Pre order traversal:  []
In order traversal:   []
Post order traversal: []
Tree structure:
-----------------------------------------------

4. testSearchData

判断二叉查询树里面是否包含给定的数据。

输出结果:

Data: 10, exist?  true
Data: -7, exist?  true
Data: 100, exist?  false
Data: 8, exist?  false
Data: -8, exist?  true

5. testPerformance

通过与数组遍历查询进行比较,演示了二叉查询树的平均算法复杂度是O(\log_{2}n)

当originalDataSize=1,000,000,selectedDataSize=10,000时,

输出结果:

Generate tree takes: 1 seconds, tree size: 1000000
Find data by tree takes: 0 seconds, data size: 10000
Find data percentage: 0.6349
Find data by array takes: 49 seconds, data size: 10000
Find data percentage: 0.6349

当originalDataSize=10,000,000,selectedDataSize=10,000,000时,

输出结果:

Generate tree takes: 19 seconds, tree size: 10000000
Find data by tree takes: 22 seconds, data size: 10000000
Find data percentage: 0.6321235
Find data by array takes: 151971 seconds, data size: 10000000
Find data percentage: 0.6321235

可以看出,在平均情况下,二叉查询树的性能在数据量比较大时要远远大于传统数组遍历查询时的性能。特别是第二种场景,性能快了近 3706 倍,完全不是一个数量级。

6. testLinkedStyleTree

当输入的数据已经排好序,此时的二叉查询树会蜕变成链表,所有操作的算法复杂度会变为O(n),与链表的算法复杂度相同。造成该现象的原因是二叉查询树在生成过程中没有考虑平衡性,平衡二叉查询树会解决此问题。

输出结果:

Tree height: 5
Tree size:   5
Pre order traversal:  [1, 2, 3, 4, 5]
In order traversal:   [1, 2, 3, 4, 5]
Post order traversal: [5, 4, 3, 2, 1]
Tree structure:
               1                               
                \               
                 \              
                  \             
                   \            
                    \           
                     \          
                      \         
                       \        
                       2               
                        \       
                         \      
                          \     
                           \    
                           3       
                            \   
                             \  
                             4   
                              \ 
                              5 
                                                                
-----------------------------------------------
Tree height: 5
Tree size:   5
Pre order traversal:  [5, 4, 3, 2, 1]
In order traversal:   [1, 2, 3, 4, 5]
Post order traversal: [1, 2, 3, 4, 5]
Tree structure:
               5                               
              /                 
             /                  
            /                   
           /                    
          /                     
         /                      
        /                       
       /                        
       4                               
      /                         
     /                          
    /                           
   /                            
   3                               
  /                             
 /                              
 2                               
/                               
1                               
                                                                
-----------------------------------------------

7. testLinkedStyleTreeByDeleting

可能二叉查询树在刚生成的时候是比较平衡的(可通过随机方式插入数据实现),但如果进行了频繁的删除操作,其将变得不再平衡,因为删除操作都是将左子树的前驱节点上移,使得左侧结点变少,右侧结点变多。

输出结果(每次运行结果不一定相同):

Tree height: 5
Tree size:   6
Pre order traversal:  [4, 46, 36, 82, 97, 93]
In order traversal:   [4, 36, 46, 82, 93, 97]
Post order traversal: [36, 93, 97, 82, 46, 4]
Tree structure:
               4                               
                \               
                 \              
                  \             
                   \            
                    \           
                     \          
                      \         
                       \        
                       46              
                      / \       
                     /   \      
                    /     \     
                   /       \    
                   36      82      
                            \   
                             \  
                             97  
                            /   
                            93  
                                                                
-----------------------------------------------

四、二叉查询树操作的算法复杂度

操作 平均复杂度 最坏复杂度
空间 O(n) O(n)
查询 O(\log_{2}n) O(n)
插入 O(\log_{2}n) O(n)
删除 O(\log_{2}n)​​​​​​​ O(n)​​​​​​​

猜你喜欢

转载自blog.csdn.net/funnyrand/article/details/81665445
今日推荐