概念:
树(Tree)是一种非线性的数据结构,它以分层的方式存储和组织数据。树由节点(Node)和边(Edge)组成。每个节点可以有零个或多个子节点,而除了根节点外,每个节点都有一个父节点。在一棵树中,根节点是位于顶部的唯一一个特殊节点。从根开始,通过边连接到其他子级节点,并且这些子级又可以作为父级来连接更多的子级。没有任何子级的节点称为叶子(Leaf),而具有至少一个子节点的元素被称为内部或非叶子节 (Internal or Non-Leaf) 节点。
特点:
- 层次结构:每层都从上至下递增地排列,并且父级与其子级之间存在明确关联性。
- 唯一路径:从根到每个叶子只存在唯一路径。
- 无循环性:不能形成环路。
优点:
- 层次性:树以层级的方式存储数据,这使得在查找、插入和删除等操作中具有良好的效率。相比于线性结构如数组或链表,树能更快速地定位到特定节点。
- 快速搜索和检索:树可以通过使用适当的搜索算法(比如二叉搜索树)来快速查找和检索特定元素。这对于处理大量数据并需要高效率查询或排序的场景非常重要。
- 灵活性:树可以动态增长和缩小,并且支持动态调整节点之间的连接关系。这使得它在各种应用中都能够灵活适应不同需求。
缺点:
- 平衡问题:某些类型的树(如二叉搜索树)可能会因为插入或删除操作而导致不平衡,从而降低了其性能。需要采取平衡策略来解决这个问题,例如红黑树、AVL 树等。
- 内存开销较大:相比于线性结构,图形化表示以及需要额外指针引用子节点会占用更多内存空间。
- 操作复杂度不均衡:如果一棵树非常不平衡,可能会导致某些操作效率较低。例如,在一棵高度不平衡的二叉搜索树中进行查找操作时,时间复杂度可能最差达到 O(n)。
适用场景:
- 层次化数据模型需求(例如文件系统)。
- 编译器和解析器中的语法分析。
- 网络路由算法等领域。
二叉搜索树实现代码:
class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
private TreeNode root;
public BinarySearchTree() {
root = null;
}
// 判断树是否为空
public boolean isEmpty() {
return root == null;
}
// 计算树的高度
public int getHeight() {
return getHeight(root);
}
private int getHeight(TreeNode node) {
if (node == null)
return 0;
int leftHeight = getHeight(node.left);
int rightHeight = getHeight(node.right);
return Math.max(leftHeight, rightHeight) + 1;
}
// 插入节点
public void insert(int value) {
root = insertNode(root, value);
System.out.println("root is " + root.val);
}
private TreeNode insertNode(TreeNode node, int value){
if (node == null)
return new TreeNode(value);
if (value < node.val)
node.left=insertNode(node.left,value);
else if (value > node.val)
node.right=insertNode(node.right,value);
return node;
}
public void deleteNode(int key) {
root = deleteRec(root, key);
}
private TreeNode deleteRec(TreeNode currentNode, int key) {
if (currentNode == null) {
return null;
}
if (key < currentNode.val) { // 如果要删除的键小于当前结点,则继续在左侧递归查找并更新左侧孩子
currentNode.left = deleteRec(currentNode.left, key);
} else if (key > currentNode.val) { // 如果要删除的键大于当前结点,则继续在右侧递归查找并更新右侧孩子
currentNode.right = deleteRec(currentNode.right, key);
} else { // 当前结点是要被删掉的结点
// 第一种和第二种情况: 没有左/右孩子或只有一个孩子
if (currentNode.left == null) {
return currentNode.right;
} else if (currentNode.right == null) {
return currentNode.left;
}
// 第三种情况: 有两个孩子
// 找到右子树中的最小键值节点
currentNode.val = minValue(currentNode.right);
// 在右子树中递归删除最小键值节点
currentNode.right = deleteRec(currentNode.right, currentNode.val);
}
return currentNode; // 返回更新后的结点
}
private int minValue(TreeNode node) {
int minv = node.val;
while (node.left != null) {
minv=node.left.val;
node=node.left;
}
return minv ;
}
// 查找节点
public boolean search(int key) {
return searchKey(root, key);
}
private boolean searchKey(TreeNode currentNode ,int key){
if(currentNode==null)
return false;
if(currentNode.val==key)
return true;
// 如果查找的值比当前节点小,则递归地在左子树中查找
if(key<currentNode.val)
return searchKey(currentNode.left,key);
// 如果查找的值比当前节点大,则递归地在右子树中查找
return searchKey(currentNode.right,key);
}
// 前序遍历二叉搜索树
public void preorderTraversal() {
preorder(root);
}
private void preorder(TreeNode node) {
if (node != null) {
System.out.print(node.val + " ");
preorder(node.left);
preorder(node.right);
}
}
// 中序遍历二叉搜索树
public void inorderTraversal() {
inorder(root);
}
private void inorder(TreeNode node) {
if (node != null) {
inorder(node.left);
System.out.print(node.val + " ");
inorder(node.right);
}
}
// 后序遍历二叉搜索树
public void postorderTraversal() {
postorder(root);
}
private void postorder(TreeNode node) {
if (node != null) {
postorder(node.left);
postorder(node.right);
System.out.print(node.val + " ");
}
}
}
常见操作示例:
1、创建二叉搜索树
BinarySearchTree bst = new BinarySearchTree();
2、插入元素
// 构建二叉搜索树
bst.insert(5);
bst.insert(3);
bst.insert(7);
bst.insert(2);
bst.insert(4);
bst.insert(6);
bst.insert(8);
bst.insert(1);
bst.insert(9);
bst.insert(10);
//插入10个节点后,构建了一个如下结构的二叉搜索树:
// 5
// / \
// 3 7
// / \ / \
// 2 4 6 8
// / \
// 1 9
// \
// 10
3、获取高度
int height =bst.getHeight();
System.out.println("树的高度: " +height );
4、判断是否为空
System.out.println("是否为空: " +bst.isEmpty());
5、前序遍历
System.out.print("前序遍历结果: ");
bst.preorderTraversal();
6、中序遍历
System.out.println("中序遍历结果 :");
bst.inorderTraversal();
7、后序遍历
System.out.println("后序遍历结果: ");
bst.postorderTraversal();
8、节点搜索
System.out.println("当前树种是否存在节点5: " +bst.search(5));
9、删除节点
bst.deleteNode(3);