数据结构的Java实现(十)—— 二叉树

目录

二叉树


树(tree)是一种抽象数据类型(ADT),用来模拟具有树状结构性质的数据集合。它是由n(n>=0)个有限节点组成一个具有层次关系的集合。节点一般代表一些实体,在java中节点一般代表对象。连接节点的线称为边,一般从一个节点到另一个节点的唯一方法就是沿着一条顺着有边的道路前进,在Java中边通常表示引用。

特点:每个节点有零个或多个子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点;除了根节点外,每个子节点可以分为多个不相交的子树。

相关术语:

  1. 路径:顺着节点的边从一个节点走到另一个节点,所经过的节点的顺序排列就称为“路径”。
  2. :树顶端的节点称为根。一棵树只有一个根,如果要把一个节点和边的集合称为树,那么从根到其他任何一个节点都必须有且只有一条路径。A是根节点。
  3. 父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点;
  4. 子节点:一个节点含有的子树的根节点称为该节点的子节点;
  5. 兄弟节点:具有相同父节点的节点互称为兄弟节点;
  6. 叶子节点:度为0的节点称为叶节点;
  7. 树的度:一棵树中,最大的节点的度称为树的度;
  8. 节点的度:一个节点含有的子树的个数称为该节点的度;
  9. 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  10. 树的高度或深度:树中节点的最大层次;

二叉树

二叉树:每个节点最多含有两个子树的树称为二叉树。

二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树,是指具有下列性质的二叉树:

  1. 若左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  2. 若右子树不空,则右子树上所有节点的值均大于它的根节点的值;
  3. 左、右子树也分别为二叉排序树;
  4. 没有键值相等的节点。

Node类:

package yrwan09;

/**
 * 二叉树结点
 * 
 * @author Wyran
 *
 */
public class Node {
	public int data;// 节点数据
	public Node leftChild;// 左子节点
	public Node rightChild;// 右子节点

	public Node(int data) {
		this.data = data;
	}
}

二叉树的方法有:插入节点、查找节点、删除节点、前序遍历、中序遍历、后序遍历等。

查找节点,要从根节点开始遍历,待查找的值比当前节点值大,则搜索右子树;待查找的值小于当前节点值,则搜索左子树;待查找的值等于当前节点值,则停止搜索。

插入节点,要先找到插入的位置,待插入的节点要从根节点开始进行比较,小于根节点则与根节点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父节点的信息以及待插入的位置是父节点的左子树还是右子树,才能插入到正确的位置。

删除节点,是二叉搜索树中最复杂的操作,删除的节点有三种情况:1、该节点是叶节点 2、该节点有一个子节点 3、该节点有两个子节点。前两种比较简单,第三种却很复杂。

遍历树,是根据一种特定的顺序访问树的每一个节点。比较常用的有前序遍历,中序遍历和后序遍历。而二叉搜索树最常用的是中序遍历。

 

二叉树遍历的应用:

  • 前序遍历:根节点——》左子树——》右子树
  • 中序遍历:左子树——》根节点——》右子树
  • 后序遍历:左子树——》右子树——》根节点

(1)前序遍历:可以用来实现目录结构的显示。

(2)中序遍历:可以用来做表达式树,在编译器底层实现的时候用户可以实现基本的加减乘除,比如 a*b+c。

(3)后序遍历可以用来实现计算目录内的文件占用的数据大小。

表达式求值也可以使用后缀表达式。后缀表达式求值比中缀表达式更方便,可以先把中缀表达式变成后缀表达式,然后再根据后缀表达式求值。

1.对于前序遍历,可以用来实现输出某个文件夹下所有文件名称(可以有子文件夹),就是目录结构的显示。

输出文件名称的过程如下:

如果是文件夹,先输出文件夹名,然后再依次输出该文件夹下的所有文件(包括子文件夹),如果有子文件夹,则再进入该子文件夹,输出该子文件夹下的所有文件名。这是一个典型的先序遍历过程。

2.对于后序遍历,可以用来统计某个文件夹的大小(该文件夹下所有文件的大小)

统计文件夹的大小过程如下:

若要知道某文件夹的大小,必须先知道该文件夹下所有文件的大小,如果有子文件夹,若要知道该子文件夹大小,必须先知道子文件夹所有文件的大小。这是一个典型的后序遍历过程。

package yrwan09;

import java.util.ArrayDeque;
import java.util.Queue;

/**
 * 二叉树:插入、查找、删除、遍历
 * 
 * @author Wyran
 *
 */
public class Tree {
	public Node root;// 根节点

	/**
	 * 插入节点:从根节点开始查找相应节点,这个节点作为新插入节点的父节点。
	 * 
	 * @param value
	 */
	public void insert(int value) {
		Node newNode = new Node(value);
		if (root == null) {
			root = newNode;
			return;
		} else {
			Node current = root;
			Node parentNode = null;
			while (true) {
				parentNode = current;
				if (value < current.data) {
					current = current.leftChild;
					if (current == null) {
						parentNode.leftChild = newNode;
						return;
					}
				} else {
					current = current.rightChild;
					if (current == null) {
						parentNode.rightChild = newNode;
						return;
					}
				}
			}
		}
	}

	/**
	 * 查找结点:从根节点开始遍历
	 * ①、查找值小于当前节点值,则搜索左子树;
	 * ②、查找值大于当前节点值,则搜索右子树;
	 * ③、查找值等于当前节点值,停止搜索(终止条件);
	 * 
	 * @param value
	 * @return
	 */
	public Node find(int value) {
		Node current = root;
		while (current != null) {
			if (current.data > value) {// 当前值比查找值大,搜索左子树
				current = current.leftChild;
			} else if (current.data < value) {// 当前值比查找值小,搜索右子树
				current = current.rightChild;
			} else {
				return current;
			}
		}
		return null;// 没找到,则返回null
	}

	/**
	 * 前序遍历:根节点——>左子树——>右子树
	 * 
	 * @param node
	 */
	public void preOrder(Node current) {
		if (current != null) {
			System.out.print(current.data + " ");
			preOrder(current.leftChild);
			preOrder(current.rightChild);
		}
	}

	/**
	 * 中序遍历:左子树——>根节点——>右子树
	 * 
	 * @param node
	 */
	public void inOrder(Node current) {
		if (current != null) {
			inOrder(current.leftChild);
			System.out.print(current.data + " ");
			inOrder(current.rightChild);
		}
	}

	/**
	 * 后序遍历:左子树——>右子树——>根节点
	 * 
	 * @param node
	 */
	public void postOrder(Node current) {
		if (current != null) {
			postOrder(current.leftChild);
			postOrder(current.rightChild);
			System.out.print(current.data + " ");
		}
	}

	/**
	 * 层序遍历
	 * 
	 * @param current
	 */
	public void bfsOrder(Node current) {
		if(current != null){
			Queue<Node> queue = new ArrayDeque<>();
			queue.offer(current);
			while(!queue.isEmpty()){
				current = queue.poll();
				System.out.print(current.data + " ");
				if(current.leftChild != null){
					queue.offer(current.leftChild);
				}
				if(current.rightChild != null){
					queue.offer(current.rightChild);
				}
			}
		}
	}

	/**
	 * 删除结点
	 * 
	 * @param value
	 */
	public boolean delete(int value) {
		Node current = root;// 待删除节点
		Node parentNode = root;
		boolean isLeftChild = false;// 判断待删除节点是左孩子还是右孩子

		while (current.data != value) {
			parentNode = current;
			if (current.data > value) {// 当前值比查找值大,搜索左子树
				current = current.leftChild;
				isLeftChild = true;
			} else {// 当前值比查找值小,搜索右子树
				current = current.rightChild;
				isLeftChild = false;
			}
			if (current == null) {// 待删除值不存在
				return false;
			}
		}

		// ①该节点没有子节点,是叶子结点
		if (current.leftChild == null && current.rightChild == null) {
			if (current == root) {
				root = null;
			} else if (isLeftChild) {
				parentNode.leftChild = null;
			} else {
				parentNode.rightChild = null;
			}
		} else if (current.rightChild == null && current.leftChild != null) {// ②该节点只有一个右子节点
			if (current == root) {
				root = current.leftChild;
			} else if (isLeftChild) {
				parentNode.leftChild = current.leftChild;
			} else {
				parentNode.rightChild = current.leftChild;
			}
		} else if (current.leftChild == null && current.rightChild != null) {// ②该节点只有一个左子节点
			if (current == root) {
				root = current.rightChild;
			} else if (isLeftChild) {
				parentNode.leftChild = current.rightChild;
			} else {
				parentNode.rightChild = current.rightChild;
			}
		} else {// ③该节点有两个子节点
			Node successor = getSuccessor(current);
			if (current == root) {
				root = successor;
			} else if (isLeftChild) {
				parentNode.leftChild = successor;
			} else {
				parentNode.rightChild = successor;
			}
			successor.leftChild = current.leftChild;
		}

		return true;
	}

	/**
	 * 查找待删除节点的中序后继节点
	 * 
	 * @param delNode 待删除节点
	 * @return 中序后继节点
	 */
	public Node getSuccessor(Node delNode) {
		Node successor = delNode;// 中序后继节点
		Node successorParent = delNode;// 中序后继节点的父节点
		Node current = delNode.rightChild;// 待删除节点的右子节点开始

		while (current != null) {// 向左子树遍历到一个没有左孩子的节点
			successorParent = successor;
			successor = current;
			current = current.leftChild;
		}

		// 如果中序后继节点不是待删除节点的右孩子 把待移动的子树全部移动完
		if (successor != delNode.rightChild) {
			successorParent.leftChild = successor.rightChild;
			successor.rightChild = delNode.rightChild;
		}
		return successor;
	}
}

 

1. 什么是二叉树二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值