JAVA数据结构(五)

二分搜索树

一、 二叉树
在这里插入图片描述
二叉树特点: 二叉树具有天然的递归结构—>每个节点的左子树也是二叉树;每个节点的右子树也是二叉树

  • 二分搜索树
    在这里插入图片描述
  • 二分搜索树的初始化
package cn.itcast.day5;

// 由于二分搜索树必须进行比较,因此对于天然不具备比较的数据。因此需要令泛型E实现Comparable接口,
// 并通过compareTo方法来进行大小的比较
public class BST<E extends Comparable<E>>{		
	private class Node{
		public E e;
		public Node left,right;
		public Node(E e) {
			this.e = e;
			left = null;
			right = null;
		}
	}
	private Node root;
	private int size;
	
	public BST() {
		root = null;
		size = 0;
	}
	
	public int size() {
		return size;
	}
	
	public boolean isEmpty() {
		return size == 0;
	}

}

  • 二分搜索树添加新元素,添加两个方法:add(E e)和add(Node node,E e)
	public void add(E e) {
		if(root == null) {
			root = new Node(e);
			size ++;
		}else {
			add(root,e);
		}
	}
	//向以node为根的二分搜索树中插入元素E,递归算法
	private void add(Node node,E e) {
		if(e.equals(node.e)) {				//终止条件较为复杂,有三种情况需要进行判断
			return;
		}else if(e.compareTo(node.e)<0 && node.left == null) {	//不是基础类型,因此不能直接用大于号或者小于号进行比较
			node.left = new Node(e);
			size ++;
			return;
		}else if(e.compareTo(node.e)>0 && node.right == null) {
			node.right = new Node(e);
			size ++;
			return;
		}
		if(e.compareTo(node.e)<0) {
			add(node.left,e);
		}else {
			add(node.right,e);
		}
	}
  • 改写递归函数
//返回插入新节点后二分搜索树的根
	private Node add(Node node,E e) {
		if(node == null) {
			size ++;
			return new Node(e);
		}
		if(e.compareTo(node.e)<0) {
			node.left = add(node.left,e);
		}else if(e.compareTo(node.e)>0) {
			node.right = add(node.right,e);
		}
		return node;
	}
  • 二分搜索树查询元素:添加方法contains(E e)和contains(Node node,E e)
	//看二分搜索树中是否包含元素e(用户调用)
	public boolean contains(E e) {
		return contains(root,e);
	}
	//看以node为根的二分搜索树中是否包含元素e,递归算法(由公有的contains进行调用)
	private boolean contains(Node node,E e) {
		if(node == null) {
			return false;
		}
		if(e.compareTo(node.e)==0) {
			return true;
		}else if(e.compareTo(node.e)<0) {
			return contains(node.left,e);
		}else {
			return contains(node.right,e);
		}
	}

二、二分搜索树前向遍历

  • 前序遍历:先访问根节点,再分别访问左节点、右节点
    在这里插入图片描述
  • 代码实现
	//二分搜索树的前序遍历(用户调用的方法)
	public void preOrder() {
		preOrder(root);
	}
	
	//程序执行时实际的调用
	public void preOrder(Node node) {
		if(node == null) {
			return;
		}
		System.out.println(node.e);
		preOrder(node.left);
		preOrder(node.right);
	}
//	前序遍历的打印
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		generateBSTString(root,0,res);		//需要打印的树、深度
		return res.toString();
	}
	//生成以node为根节点,深度为depth的描述二叉树的字符串
	private void generateBSTString(Node node,int depth,StringBuilder res) {
		if(node == null) {
			res.append(generateDepthString(depth)+"null\n");
			return;
		}
		res.append(generateDepthString(depth)+node.e+"\n");
		generateBSTString(node.left,depth+1,res);
		generateBSTString(node.right,depth+1,res);
	}
	private String generateDepthString(int depth) {
		StringBuilder res = new StringBuilder();
		for(int i=0;i<depth;i++) {
			res.append("--");
		}
		return res.toString();
	}

三、二分搜索树中、后序遍历

  • 中序遍历
	public void inOrder() {
		inOrder(root);
	}
	
	//程序执行时实际的调用
	public void inOrder(Node node) {
		if(node == null) {
			return;
		}
		inOrder(node.left);
		System.out.println(node.e);
		inOrder(node.right);
	}
  • 后序遍历
	public void postOrder() {
		postOrder(root);
	}
	
	//程序执行时实际的调用
	public void postOrder(Node node) {
		if(node == null) {
			return;
		}
		postOrder(node.left);
		postOrder(node.right);
		System.out.println(node.e);
	}

四、前序遍历的非递归实现(使用栈)

  • 思路:利用栈的后进先出特性,每当访问到一个节点时,打印出该节点的元素后,将该节点的右子树先入栈,再将左子树入栈(由于栈先进先出,因此左子树后入栈时,出栈比右子树提前)。下图中的执行逻辑是:先将根节点数值28入栈,28为栈顶,读取28节点的左右子树,将右子树30入栈,然后16入栈;下一步执行出栈,此时16出栈,读取16的左右子树,则22先入栈,再将13入栈;下一步执行出栈。。。。以此类推
    在这里插入图片描述
  • 代码
	//二分搜索树的非递归前序遍历
	public void preOrderNR() {
		Stack<Node> stack = new Stack<>();
		stack.push(root);
		while(!stack.isEmpty()) {
			Node cur = stack.pop();
			System.out.println(cur.e);
			
			if(cur.right!=null) {
				stack.push(cur.right);
			}
			if(cur.left!=null) {
				stack.push(cur.left);
			}
		}
	}

五、二分搜索树的层序遍历

  • 使用队列完成操作
    思路:利用队列先进先出的规特性,每当访问到一个节点时,打印出该节点的数值后,再将其左右节点入队。由于先进先出,因此先将左节点入对,再将右节点入队。如下例:初始时根节点为28,进行出队操作,出队后,先将16进行入队,再让30入队;此时,令16出队(30成为队首),然后防卫16的左右节点,并按顺序将13、22进行入队操作。完成13、22的入队操作后,令队首元素(30)出队,然后访问其左右节点,再按照顺序将29、42进行入队操作,以此类推。。。
    在这里插入图片描述
  • 代码实现
	//二叉树的层序遍历
	public void levelOrder() {
		Queue<Node> queue = new LinkedList<>();	//由于java提供的Queue是接口,应该使用其实现类来进行操作
		queue.add(root);
		while(!queue.isEmpty()) {
			Node cur = queue.poll();
			System.out.println(cur.e);
			if(cur.left!=null) {
				queue.add(cur.left);
			}
			if(cur.right!=null) {
				queue.add(cur.right);
			}
		}
	}
  • 广度优先遍历的意义
    在这里插入图片描述
    六、二分搜索树删除节点
  • 从简单开始,删除二分搜索树的最小值和最大值
    要执行删除操作,首先需要找到最小值和最大值。最小值:在二分查找树的最左端;最大值:在二分查找树的最右端。
	//寻找二分搜索树的最小元素(由用户调用)
	public E minimum() {
		if(size == 0) {
			throw new IllegalArgumentException("错误");
		}
		return minimum(root).e;
	}
	private Node minimum(Node node) {
		if(node.left == null) {
			return node;
		}
		return minimum(node.left);
	}
	
	//寻找二分搜索树的最大元素(由用户调用)
	public E maxmum() {
		if(size == 0) {
			throw new IllegalArgumentException("错误");
		}
		return maxmum(root).e;
	}
	private Node maxmum(Node node) {
		if(node.right == null) {
			return node;
		}
		return maxmum(node.right);
	}

找到最小最大值后,要进行删除操作。如下,22是最小的节点,删除掉22这个节点后,只需要将22的右子树变为41的左子树即可。对于最大值,同理。
在这里插入图片描述

  • 代码实现
	//从二分搜索树中删除最小值所在的节点,返回最小值(用户端的执行)
	public E removeMin() {
		E ret = minimum();
		root = removeMin(root);		//用于得到所删除的节点的右子树的信息
		return ret;
	}
	
	//删除操作时程序的实际执行操作
	//删除以node为根的二分搜索树中的最小节点,返回删除节点后新的二分搜索树的根
	private Node removeMin(Node node) {
		if(node.left == null) { //当节点已经不存在左节点时已经到达树的最左端(即要删的最小值)	
			Node rightNode = node.right;	//保存需要删除的最小值节点的右子树的信息
			node.right = null;	//删除此最小值节点
			size --;
			return rightNode;
		}
		node.left = removeMin(node.left);	//将删除的节点的右子树挂到新节点的左子树上
		return node;	//只会在程序最后一步执行
	}
	
	//从二分搜索树中删除最大值所在的节点,返回最小值(用户端的执行)
	public E removeMax() {
		E ret = maxmum();
		root = removeMax(root);		//用于得到所删除的节点的右子树的信息
		return ret;
	}
	
	//删除操作时程序的实际执行操作
	//删除以node为根的二分搜索树中的最大节点,返回删除节点后新的二分搜索树的根
	private Node removeMax(Node node) {
		if(node.right == null) { //当节点已经不存在左节点时已经到达树的最右端(即要删的最大值)	
			Node leftNode = node.left;	//保存需要删除的最大值节点的左子树的信息
			node.left = null;	//删除此最大值节点
			size --;
			return leftNode;
		}
		node.right = removeMin(node.right);
		return node;	//只会在程序最后一步执行
	}
	

七、二分搜索树删除任意节点

  • 删除只有右孩子的节点操作
    在这里插入图片描述
  • 删除左右都有孩子的节点:删除此节点后,需要找到一个节点来替代该节点的位置。如下图,删除58,则找的节点应该时58这个节点的后继(找到58的左右孩子中,离58最近的同时比58大的节点来取代58的位置,即59------>58的右子树中的最小值节点) 。下一步是在d的右子树中删除这个最小的值,再让剩下的子树成为这个d后继的孩子节点。如下图
    在这里插入图片描述
    最后,再让d原本的左子树成为59这个节点的左子树,然后将d删除。
    在这里插入图片描述
    在这里插入图片描述
    除后继外,也可以使用前驱(左子树中最大的节点)来替代待删除的节点
    在这里插入图片描述
  • 代码实现
	//删除以node为根的二分搜索树中值为e的节点,递归算法
	//返回删除节点后新的二分搜索树的根
	private Node remove(Node node,E e) {
		if(node == null) {
			return null;
		}
		if(e.compareTo(node.e)<0) {
			node.left = remove(node.left,e);
			return node;
		}
		else if(e.compareTo(node.e)>0) {
			node.right = remove(node.right,e);
			return node;
		}
		else {	//e == node.e
			//开始删除
			//待删除节点的左子树为空的情况
			if(node.left == null) {
				Node rightNode = node.right;
				node.right = null;
				size --;
				return rightNode;
			}
			//待删除节点的右子树为空的情况
			if(node.right == null) {
				Node leftNode = node.left;
				node.left = null;
				size --;
				return leftNode;
			}
			
			//待删除节点的左右子树均不为空的情况
			//找到比待删除节点大的最小节点,即待删除节点右子树的最小节点
			//用这个节点顶替待删除节点的位置
			Node successor = minimum(node.right);	//node的后继节点
			//removeMin中进行了size--操作,因此不用再继续维护size
			successor.right = removeMin(node.right); //将node.right中的最小节点去掉后,整个树作为后继的右节点
			successor.left = node.left;
			node.left = node.right = null;
			return successor;
		}
	}

八、完整代码

package cn.itcast.day5;
import java.util.Stack;
import java.util.LinkedList;
import java.util.Queue;
public class BST<E extends Comparable<E>>{
	private class Node{
		public E e;
		public Node left,right;
		public Node(E e) {
			this.e = e;
			left = null;
			right = null;
		}
	}
	private Node root;
	private int size;
	
	public BST() {
		root = null;
		size = 0;
	}
	
	public int size() {
		return size;
	}
	
	public boolean isEmpty() {
		return size == 0;
	}
	public void add(E e) {
		if(root == null) {
			root = new Node(e);
			size ++;
		}else {
			add(root,e);
		}
	}
	//向以node为根的二分搜索树中插入元素E,递归算法
/*	private void add(Node node,E e) {
		if(e.equals(node.e)) {
			return;
		}else if(e.compareTo(node.e)<0 && node.left == null) {	//不是基础类型,因此不能直接用大于号或者小于号进行比较
			node.left = new Node(e);
			size ++;
			return;
		}else if(e.compareTo(node.e)>0 && node.right == null) {
			node.right = new Node(e);
			size ++;
			return;
		}
		if(e.compareTo(node.e)<0) {
			add(node.left,e);
		}else {
			add(node.right,e);
		}
	}*/
	//返回插入新节点后二分搜索树的根
	private Node add(Node node,E e) {
		if(node == null) {
			size ++;
			return new Node(e);
		}
		if(e.compareTo(node.e)<0) {
			node.left = add(node.left,e);
		}else if(e.compareTo(node.e)>0) {
			node.right = add(node.right,e);
		}
		return node;
	}
	//看二分搜索树中是否包含元素e(用户调用)
	public boolean contains(E e) {
		return contains(root,e);
	}
	//看以node为根的二分搜索树中是否包含元素e,递归算法(由公有的contains进行调用)
	private boolean contains(Node node,E e) {
		if(node == null) {
			return false;
		}
		if(e.compareTo(node.e)==0) {
			return true;
		}else if(e.compareTo(node.e)<0) {
			return contains(node.left,e);
		}else {
			return contains(node.right,e);
		}
	}
	//二分搜索树的前序遍历(用户调用的方法)
	public void preOrder() {
		preOrder(root);
	}
	
	//程序执行时实际的调用
	public void preOrder(Node node) {
		if(node == null) {
			return;
		}
		System.out.println(node.e);
		preOrder(node.left);
		preOrder(node.right);
	}
	
	public void inOrder() {
		inOrder(root);
	}
	
	//程序执行时实际的调用
	public void inOrder(Node node) {
		if(node == null) {
			return;
		}
		inOrder(node.left);
		System.out.println(node.e);
		inOrder(node.right);
	}
	
	public void postOrder() {
		postOrder(root);
	}
	
	//程序执行时实际的调用
	public void postOrder(Node node) {
		if(node == null) {
			return;
		}
		postOrder(node.left);
		postOrder(node.right);
		System.out.println(node.e);
	}
	//二分搜索树的非递归前序遍历
	public void preOrderNR() {
		Stack<Node> stack = new Stack<>();
		stack.push(root);
		while(!stack.isEmpty()) {
			Node cur = stack.pop();
			System.out.println(cur.e);
			
			if(cur.right!=null) {
				stack.push(cur.right);
			}
			if(cur.left!=null) {
				stack.push(cur.left);
			}
		}
	}
	//二叉树的层序遍历
	public void levelOrder() {
		Queue<Node> queue = new LinkedList<>();	//由于java提供的Queue是接口,应该使用其实现类来进行操作
		queue.add(root);
		while(!queue.isEmpty()) {
			Node cur = queue.poll();
			System.out.println(cur.e);
			if(cur.left!=null) {
				queue.add(cur.left);
			}
			if(cur.right!=null) {
				queue.add(cur.right);
			}
		}
	}
	//寻找二分搜索树的最小元素(由用户调用)
	public E minimum() {
		if(size == 0) {
			throw new IllegalArgumentException("错误");
		}
		return minimum(root).e;
	}
	private Node minimum(Node node) {
		if(node.left == null) {
			return node;
		}
		return minimum(node.left);
	}
	
	//寻找二分搜索树的最大元素(由用户调用)
	public E maxmum() {
		if(size == 0) {
			throw new IllegalArgumentException("错误");
		}
		return maxmum(root).e;
	}
	private Node maxmum(Node node) {
		if(node.right == null) {
			return node;
		}
		return maxmum(node.right);
	}
	
	//从二分搜索树中删除最小值所在的节点,返回最小值(用户端的执行)
	public E removeMin() {
		E ret = minimum();
		root = removeMin(root);		//用于得到所删除的节点的右子树的信息
		return ret;
	}
	
	//删除操作时程序的实际执行操作
	//删除以node为根的二分搜索树中的最小节点,返回删除节点后新的二分搜索树的根
	private Node removeMin(Node node) {
		if(node.left == null) { //当节点已经不存在左节点时已经到达树的最左端(即要删的最小值)	
			Node rightNode = node.right;	//保存需要删除的最小值节点的右子树的信息
			node.right = null;	//删除此最小值节点
			size --;
			return rightNode;
		}
		node.left = removeMin(node.left);	//将删除的节点的右子树挂到新节点的左子树上
		return node;	//只会在程序最后一步执行
	}
	
	//从二分搜索树中删除最大值所在的节点,返回最小值(用户端的执行)
	public E removeMax() {
		E ret = maxmum();
		root = removeMax(root);		//用于得到所删除的节点的右子树的信息
		return ret;
	}
	
	//删除操作时程序的实际执行操作
	//删除以node为根的二分搜索树中的最大节点,返回删除节点后新的二分搜索树的根
	private Node removeMax(Node node) {
		if(node.right == null) { //当节点已经不存在左节点时已经到达树的最右端(即要删的最大值)	
			Node leftNode = node.left;	//保存需要删除的最大值节点的左子树的信息
			node.left = null;	//删除此最大值节点
			size --;
			return leftNode;
		}
		node.right = removeMin(node.right);
		return node;	//只会在程序最后一步执行
	}
	
	// 从二分搜索树中删除元素为e的节点(用户进行调用)
	public void remove(E e) {
		root = remove(root,e);
	}
	
	//删除以node为根的二分搜索树中值为e的节点,递归算法
	//返回删除节点后新的二分搜索树的根
	private Node remove(Node node,E e) {
		if(node == null) {
			return null;
		}
		if(e.compareTo(node.e)<0) {
			node.left = remove(node.left,e);
			return node;
		}
		else if(e.compareTo(node.e)>0) {
			node.right = remove(node.right,e);
			return node;
		}
		else {	//e == node.e
			//开始删除
			//待删除节点的左子树为空的情况
			if(node.left == null) {
				Node rightNode = node.right;
				node.right = null;
				size --;
				return rightNode;
			}
			//待删除节点的右子树为空的情况
			if(node.right == null) {
				Node leftNode = node.left;
				node.left = null;
				size --;
				return leftNode;
			}
			
			//待删除节点的左右子树均不为空的情况
			//找到比待删除节点大的最小节点,即待删除节点右子树的最小节点
			//用这个节点顶替待删除节点的位置
			Node successor = minimum(node.right);	//node的后继节点
			//removeMin中进行了size--操作,因此不用再继续维护size
			successor.right = removeMin(node.right); //将node.right中的最小节点去掉后,整个树作为后继的右节点
			successor.left = node.left;
			node.left = node.right = null;
			return successor;
		}
	}
	
	
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		generateBSTString(root,0,res);		//需要打印的树、深度
		return res.toString();
	}
	//生成以node为根节点,深度为depth的描述二叉树的字符串
	private void generateBSTString(Node node,int depth,StringBuilder res) {
		if(node == null) {
			res.append(generateDepthString(depth)+"null\n");
			return;
		}
		res.append(generateDepthString(depth)+node.e+"\n");
		generateBSTString(node.left,depth+1,res);
		generateBSTString(node.right,depth+1,res);
	}
	private String generateDepthString(int depth) {
		StringBuilder res = new StringBuilder();
		for(int i=0;i<depth;i++) {
			res.append("--");
		}
		return res.toString();
	}

}

  • 测试代码
package cn.itcast.day5;

import java.util.ArrayList;
import java.util.Random;

public class MAIN {
	public static void main(String[] args) {
		BST<Integer> bst = new BST<>();
		int n = 500;
		Random random = new Random();
		for(int i=0;i<n;i++) {
			bst.add(random.nextInt(10000));
		}
		ArrayList<Integer> nums = new ArrayList<>();
		while(!bst.isEmpty()) {
			nums.add(bst.removeMin());
		}
		System.out.println(nums);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值