树形结构----二分搜索树

本文详细介绍了二分搜索树的定义、数据结构实现,包括增删改查操作,以及前中后序遍历和层序遍历的迭代与递归方法。此外,还展示了如何在二分搜索树中查找、删除特定元素,以及获取最小值和最大值。代码示例以Java实现,涵盖了二分搜索树的主要功能。
摘要由CSDN通过智能技术生成

1. 二分搜索树的定义:二分搜索树本身是二叉树,只不过对于二分搜索树的每个结点而言,大于其左子树的所有结点的值,小于其右子树的所有结点的值。同样,其子树也是一颗二分搜索树,那么该树中元素必须要具有可比性,并且不包含重复元素。如图就是一颗二分搜索树:

 

2. 二分搜索树数据结构的实现:包括二分搜索树中数据的增删改查,二分搜索树的遍历(前中后序遍历,及层序遍历的迭代和递归方式 )等方法。

//二分搜索数的定义 
/*
 * 二分搜索数的特点:任意根节点的右子树所有节点又要小于根节点 左子树节点所有节点都要大于根节点
 * 因此使用中序遍历二分搜索树时 会得到一个有序 递增的数据结构
 * 向二分搜索树中添加元素时 都要对当前根节点与要添加节点进行比较
 * 因此二分搜索树中的数据必须具有可比性 因此二分搜索树中的数据必须都是Comparable实现子类
 */
public class BinarySearchTree<E extends Comparable<E>> implements Iterable<E>{

	//定义二分搜索树节点的信息 
	//由三部分组成 数据 指向左孩子的指针 指向右孩子的指针
	private class Node{
		E data; //数据域
		Node leftChild; //左孩子指针域 指向当前节点左孩子
		Node rightChild; //右孩子指针域 指向当前节点右孩子
		
		public Node(E data) {
			this.data = data;
			this.leftChild = null;
			this.rightChild = null;
		}
		
		@Override
		public String toString() {
			return data.toString();
		}
	}
	
	private Node root; //根节点的指针 如果二分搜索树为空 则root==null
	
	private int size; //二分搜索树中元素的个数(节点的个数)
	
	public BinarySearchTree() {
		this.root = null;
		this.size = 0;
	}
	
	//获取二分搜索树节点的个数
	public int size() {
		return size;
	}
	
	//判断二分搜索树是否为空
	public boolean isEmpty() {
		return size == 0 && root == null;
	}
	
	//向二分搜索树中添加元素 迭代的方式
	/*
	 * 如果二分搜索树为空 则使根节点指向要添加节点即可
	 * 如果二分搜索树不为空 就判断要添加元素 与 当前节点的大小
	 * 		若新元素大于当前节点数据 再判断当前节点的右孩子是否为空
	 * 			若为空 则使当前节点的右孩子指针指向要添加元素 终止循环即可 
	 * 			若不为空 则更新当前节点cur为当前节点的右孩子节点 继续循环
	 * 		若新元素小于当前节点数据 再判断当前节点的左孩子是否为空
	 * 			若为空 则使当前节点的左孩子指针指向要添加元素 终止循环即可 
	 * 			若不为空 则更新当前节点cur为当前节点的左孩子节点 继续循环
	 * 		若新元素等于于当前节点数据 直接终止循环即可 因为二分搜索树中不存储重复元素
	 */
	public void addIt(E element) {
		Node node = new Node(element);
		if(isEmpty()) {
			root = node;
			size++;
		}
		Node cur = root;
		while(true) {
			//当新元素大于当前节点数据时
			if(node.data.compareTo(cur.data) > 0) {
				if(cur.rightChild == null) {
					cur.rightChild = node;
					size++;
					break;
				}else {
					cur = cur.rightChild;
				}
			//当新元素小于当前节点数据时
			}else if(node.data.compareTo(cur.data) < 0){
				if(cur.leftChild == null) {
					cur.leftChild = node;
					size++;
					break;
				}else {
					cur = cur.leftChild;
				}
			//当新元素等于当前节点数据时
			}else {
				break;
			}
		}
	}
	
	//向二分搜索树中添加元素 递归的方式 该方法是向外提供的
	public void addRe(E element) {
		root = addRe(root, element);
	}
	
	//向以node为根节点的二分搜索树中添加元素 并返回添加元素后新树的根节点 递归的方式
	/*
	 * 先判断当前节点是否为空 若为空 则创建出要添加元素的节点
	 * 反之 则判断当前根节点与要添加节点的大小
	 * 若小于当前根节点 则向当前根节点的左子树递归 并使当前根节点的左孩子指针指向要添加的节点
	 * 若大于当前根节点 则向当前根节点的右子树递归 并使当前根节点的右孩子指针指向要添加的节点
	 * 添加结束后 要向上一层返回添加新节点后新树的根
	 */
	private Node addRe(Node node, E element) {
		if(node == null) {
			size++;
			return new Node(element);
		}
		if(element.compareTo(node.data) < 0) {
			node.leftChild = addRe(node.leftChild, element);
		}else if(element.compareTo(node.data) > 0) {
			node.rightChild = addRe(node.rightChild, element);
		}
		return node;
	}
	
	//判断二分搜索树中是否有指定元素 迭代的方法
	/*
	 * 定义出一个游标表示当前节点 从根节点开始 
	 * 判断要添加元素与当前节点的大小
	 * 若添加元素小于当前节点 在判断当前节点的左孩子是否为空
	 * 		若左孩子为空 则表示没有指定元素 返回false即可
	 * 		反之 继续向左循环 更新当前节点为当前节点的左孩子节点
	 * 若添加元素大于当前节点 在判断当前节点的右孩子是否为空
	 * 		若右孩子为空 则表示没有指定元素 返回false即可
	 * 		反之 继续向右循环 更新当前节点为当前节点的右孩子节点
	 * 若添加元素等于当前节点 则返回true即可
	 */
	public boolean containsIt(E element) {
		Node cur = root;
		while(true) {
			if(element.compareTo(cur.data) < 0) {
				if(cur.leftChild == null) {
					return false;
				}
				cur = cur.leftChild;
			}else if(element.compareTo(cur.data) > 0) {
				if(cur.rightChild == null) {
					return false;
				}
				cur = cur.rightChild;
			}else {
				return true;
			}
		}
	}

	//判断二分搜索树中是否有指定元素 递归的方法 向外提供的
	public boolean containsRe(E element) {
		return containsRe(root, element);
	}
	
	//判断以node为根节点的二分搜索树中是否有指定元素 递归的方法
	/*
	 * 先判断递归临界条件 若当前根节点为空 则表示二分搜索树中没有指定元素
	 * 判断要添加元素与当前根节点的大小
	 * 若要添加元素小于当前根节点 则向当前根节点的左子树递归 使当前根节点为当前根节点的左孩子节点
	 * 若要添加元素大于当前根节点 则向当前根节点的右子树递归 使当前根节点为当前根节点的右孩子节点
	 * 若要添加元素等于当前根节点 则表示二分搜索树中包含指定元素 返回true即可
	 */
	private boolean containsRe(Node node, E element) {
		if(node == null) {
			return false;
		}
		if(element.compareTo(node.data) < 0) {
			return containsRe(node.leftChild, element);
		}else if(element.compareTo(node.data) > 0) {
			return containsRe(node.rightChild, element);
		}else {
			return true;
		}
	}

	//对二分搜索树进行前序遍历 该方法向外提供
	public void preOrder() {
		preOrder(root);
	}
	
	//对二分搜索树进行前序遍历 使用递归的方法以node为根节点进行前序遍历
	/*
	 * 前序遍历(DLR):D表示当前元素 L表示左孩子或者左子树 R表示右孩子或者右子树
	 * 判断递归临界条件 若当前根节点为空时 则表示不能在向下递归
	 * 先打印当前元素
	 * 再向左边递归 直到当前节点为空 则表示没有左孩子了
	 * 如果有右子树 再向右边递归 直到当前节点为空 则表示没有右孩子了 
	 * 再向上回溯
	 */
	private void preOrder(Node node) {
		if(node == null) {
			return;
		}
		System.out.println(node.data);
		preOrder(node.leftChild);
		preOrder(node.rightChild);
		
	}

	//前序遍历 迭代的方式
	/*
	 * 迭代的方式进行前序遍历需要借助栈来完成
	 * 先将根节点进栈
	 * 弹栈一个元素 输出该元素
	 * 再判断该元素是否有右孩子 若有则右孩子进栈
	 * 反之 左孩子进栈
	 * 直到栈为空为止
	 */	
	public void preOrederNR() {
		LinkedList<Node> stack = new LinkedList<Node>();
		stack.push(root);
		while(!stack.isEmpty()) {
			Node cur = stack.pop();
			System.out.println(cur.data);
			if(cur.rightChild != null) {
				stack.push(cur.rightChild);
			}
			if(cur.leftChild != null) {
				stack.push(cur.leftChild);
			}
		}
	}
	
	//对二分搜索树进行中序遍历 该方法向外提供
	public void inOrder() {
		inOrder(root);
	}
	
	//对二分搜索树进行中序遍历 使用递归的方法以node为根节点进行中序遍历
	/*
	 * 二分搜索树中中序遍历得到的结果是一个有序递增的数据结构
	 * 中序遍历(LDR):L表示左孩子或者左子树 D表示当前元素 R表示右孩子或者右子树
	 * 判断递归临界条件 若当前根节点为空时 则表示不能在向下递归
	 * 先向左边递归 直到当前节点为空 则表示没有左孩子了
	 * 然后打印当前元素 并向上回溯 回溯到当前节点的父节点
	 * 如果有右子树 再向右边递归 直到当前节点为空 则表示没有右孩子了 
	 * 再向上回溯
	 */
	private void inOrder(Node node) {
		if(node == null) {
			return;
		}
		inOrder(node.leftChild);
		System.out.println(node.data);
		inOrder(node.rightChild);
		
	}

	//中序遍历 迭代的方式
	/*
	 * 迭代的方式进行中序遍历需要借助栈来完成
	 * 先将根节点的所有左孩子进栈
	 * 弹栈一个元素 并输出该元素
	 * 判断该元素的右孩子是否为空 
	 * 若不为空 就循环将以该元素右孩子为根节点的所有左孩子进栈
	 * 直到栈为空为止 
	 */
	public void inOrderNR() {
		LinkedList<Node> stack = new LinkedList<Node>();
		Node p = root;
		while(p != null) {
			stack.push(p);
			p = p.leftChild;
		}
		while(!stack.isEmpty()) {
			Node cur = stack.pop();
			System.out.println(cur.data);
			if(cur.rightChild != null) {
				Node n = cur.rightChild;
				while(n != null) {
					stack.push(n);
					n = n.leftChild;
				}
			}			
		}
	}
	
	//对二分搜索树进行后序遍历 该方法向外提供
	public void lastOrder() {
		lastOrder(root);
	}
	
	//对二分搜索树进行后序遍历 使用递归的方法以node为根节点进行后序遍历
	/*
	 * 判断递归临界条件 若当前根节点为空时 则表示不能在向下递归
	 * 后序遍历(LRD):L表示左孩子或者左子树 R表示右孩子或者右子树 D表示当前元素
	 * 先向左边递归 直到当前节点为空 则表示没有左孩子了
	 * 如果有右子树 再向右边递归 直到当前节点为空 则表示没有右孩子了 
	 * 最后打印当前元素 并向上回溯 回溯到当前节点的父节点
	 */
	private void lastOrder(Node node) {
		if(node == null) {
			return;
		}
		lastOrder(node.leftChild);
		lastOrder(node.rightChild);
		System.out.println(node.data);
	}
		
	//层序遍历 迭代的方式
	/*
	 * 层序遍历需要借助队列来完成
	 * 先将根节点入队 
	 * 出队一个元素 并打印该元素
	 * 判断该元素是否有左孩子 若有则将该元素左孩子入队
	 * 再判断该元素是否有右孩子 若有则将该元素右孩子入队
	 * 直到队列为空 循环结束
	 */
	public void levelOrder() {
		LinkedList<Node> queue = new LinkedList<Node>();
		queue.offer(root);
		while(!queue.isEmpty()) {
			Node cur = queue.poll();
			System.out.println(cur.data);
			if(cur.leftChild != null) {
				queue.offer(cur.leftChild);
			}
			if(cur.rightChild != null) {
				queue.offer(cur.rightChild);
			}
		}
	}
	
	//获取二分搜索树中的最小值 向外提供的
	public E getMinNum() {
		if(isEmpty()) {
			throw new IllegalArgumentException("BST is null");
		}
		return getMinNum(root).data;
	}
	
	//获取以node为根节点的二分搜索树中的最小值节点
	/*
	 * 若当前根节点的左孩子为空 则当前根节点就是最小值节点 返回即可
	 * 反之 以当前根节点的左孩子为根节点向下递归
	 */
	private Node getMinNum(Node node) {
		if(node.leftChild == null) {
			return node;
		}
		return getMinNum(node.leftChild);
	}
	
	//获取二分搜索树中的最大值 向外提供的
	public E getMaxNum() {
		if(isEmpty()) {
			throw new IllegalArgumentException("BST is null");
		}
		return getMaxNum(root).data;
	}
	
	//获取以node为根节点的二分搜索树中的最大值节点
	/*
	 * 若当前根节点的左孩子为空 则当前根节点就是最小值节点 返回即可
	 * 反之 以当前根节点的左孩子为根节点向下递归
	 */
	private Node getMaxNum(Node node) {
		if(node.rightChild == null) {
			return node;
		}
		return getMaxNum(node.rightChild);
	}
	
	//删除二分搜索树中的最小值 向外提供的
	public E removeMin() {
		E ret = getMinNum();
		root = removeMin(root);
		return ret;
	}
	
	//删除以node为根节点的二分搜索树中的最小值 并返回删除后新树的根
	/*
	 * 先判断当前根节点是否有左孩子 若没有 则说明当前根节点为最小值的节点
	 * 获取最小值节点的右子树的根 
	 * 使最小值节点的右孩子指针置空
	 * 向上一层返回最小值节点的右子树的根(即使最小值节点的父节点的左孩子指针 指向删除后新子树的根)
	 * 若还有左孩子 则说明还没有找到最小值 就继续向当前根节点的左孩子递归
	 * 返回删除后新树的根
	 */
	private Node removeMin(Node node) {
		if(node.leftChild == null) {
			Node right = node.rightChild;
			node.rightChild = null;
			size--;
			return right;
		}
		node.leftChild = removeMin(node.leftChild);
		return node;
	}
	
	//删除二分搜索树中的最大值 向外提供的
	public E removeMax() {
		E ret = getMaxNum();
		root = removeMax(root);
		return ret;
	}
	
	//删除以node为根节点的二分搜索树中的最大值的节点 并返回删除后新树的根
	/*
	 * 先判断当前根节点是否有右孩子 若没有 则说明当前根节点为最大值的节点
	 * 获取最大值节点的左子树的根 
	 * 使最大值节点的左孩子指针置空
	 * 向上一层返回最大值节点的左子树的根(即使最大值节点的父节点的右孩子指针 指向删除后新子树的根)
	 * 若还有右孩子 则说明还没有找到最大值 就继续向当前根节点的右孩子递归
	 * 返回删除后新树的根
	 */
	private Node removeMax(Node node) {
		if(node.rightChild == null) {
			Node left = node.leftChild;
			node.leftChild = null;
			size--;
			return left;
		}
		node.rightChild = removeMax(node.rightChild);
		return node;
	}
	
	//删除二分搜索树中任意的指定元素
	public void remove(E e) {
		root = remove(root, e);
	}
	
	//删除以node为根节点的二分搜索树中任意的指定元素 并返回删除后新树的根
	/*
	 * 当当前根节点为空时 说明二分搜索树中没有要删除的元素
	 * 判断当前根节点与要删除节点的大小
	 * 若当前根节点大于要删除节点 就向当前根节点的左子树递归
	 * 若当前根节点小于要删除节点 就向当前根节点的右子树递归
	 * 若当前根节点等于要删除节点 说明找到了要删除的元素 就判断当前根节点是否右左右孩子
	 * 		若当前根节点只有右孩子(即当前根节点的左孩子为空 类似删除最小值节点)
	 * 		若当前根节点只有左孩子(即当前根节点的右孩子为空 类似删除最大值节点)
	 * 		若当前根节点既有右孩子也有左孩子
	 * 我们就取该根节点的左子树中最大值 或 右子树中最小值来代替该根节点的位置
	 * 先获取该根节点右子树中最小值successor
	 * 再将该根节点右子树中最小值删除 会返回删除后新树的根
	 * 使该根节点右子树中最小值successor的右孩子指针指向删除最小值后新树的根
	 * 将删除节点的左孩子指针的地址赋值给该根节点右子树中最小值successor的左孩子指针
	 * 使要删除节点的左右孩子指针置空
	 * 返回删除后新树的根 即为successor
	 */
	private Node remove(Node node, E e) {
		//如果node为空 则表示二分搜索树中没有指定元素 返回null即可
		if(node == null) {
			return null;
		}
		if(e.compareTo(node.data) < 0) {
			node.leftChild = remove(node.leftChild, e);
			return node;
		}else if(e.compareTo(node.data) > 0) {
			node.rightChild = remove(node.rightChild, e);
			return node;
		}else {
			if(node.leftChild == null) {
				Node rightNode = node.rightChild;
				node.rightChild = null;
				size--;
				return rightNode;
			}else if(node.rightChild == null) {
				Node leftNode = node.leftChild;
				node.leftChild = null;
                size--;
				return leftNode;
			}
			
			Node successor = getMinNum(node.rightChild);
			successor.rightChild = removeMin(node.rightChild);
			successor.leftChild = node.leftChild;
			node.leftChild = null;
			node.rightChild = null;
			return successor;
		}
	}
	
	//规定二分搜索树的输出格式
	/*
	 * 借助迭代器来遍历元素 每遍历带一个元素
	 * 就将其拼接到字符串后 再拼接一个","
	 * 最后将字符串末尾的"," 替换成"]"
	 */
	@Override
	public String toString() {
		StringBuilder str = new StringBuilder();
		str.append("[");
		if(isEmpty()) {
			str.append("]");
		}
		Iterator it = iterator();
		while(it.hasNext()) {
			str.append(it.next());
			str.append(",");
		}
		str.setCharAt(str.length() - 1, ']');
		return str.toString();
	}

	//迭代二分搜索树
	@Override
	public Iterator<E> iterator() {		
		return new BinarySearchTreeeIterator();
	}

	//定义二分搜索树的迭代器
	/*
	 * 先将二分搜索树中的数据存放在一个线性表中 再对线性表进行迭代
	 * 可以通过前中后遍历将数据存入线性表中
	 */
	private class BinarySearchTreeeIterator implements Iterator<E>{
		//定义一个线性表
		LinkedList<E> list = new LinkedList<E>();
		
		//在构造方法中 将二分搜索树中的数据存入线性表中
		public BinarySearchTreeeIterator() {
			LinkedList<Node> stack = new LinkedList<Node>();
			Node p = root;
			while(p != null) {
				stack.push(p);
				p = p.leftChild;
			}
			while(!stack.isEmpty()) {
				Node cur = stack.pop();
				list.offer(cur.data);
				if(cur.rightChild != null) {
					Node n = cur.rightChild;
					while(n != null) {
						stack.push(n);
						n = n.leftChild;
					}
				}
			}
		}

		//判断是否有下一个元素
		@Override
		public boolean hasNext() {
			return !list.isEmpty();
		}

		//获取下一个元素
		@Override
		public E next() {			
			return list.poll();
		}

		
		
	}
}

3. 二分搜索树数据结构的测试:

public class TestBinarySearchTree {
	public static void main(String[] args) {
		BinarySearchTree<Integer> bst = new BinarySearchTree<Integer>();
	    bst.addRe(1);
	    bst.addRe(4);
	    bst.addRe(3);
	    bst.addRe(5);
	    bst.addRe(2);
	    bst.addRe(6);
	    System.out.println(bst.containsRe(3));
        System.out.println(bst.containsRe(7));
        System.out.println("======");
        bst.preOrder();
        System.out.println("======");
        bst.inOrder();
        System.out.println("======");
        bst.lastOrder();
        System.out.println("======");
        bst.levelOrder();
        
        System.out.println(bst.getMinNum());
        System.out.println(bst.getMaxNum());
        
        System.out.println("======");
        for(Integer i : bst) {
        	System.out.print(i + " ");
        }
        System.out.println();
        
        bst.removeMin();
        System.out.println(bst);
        bst.removeMax();
        System.out.println(bst);
        bst.remove(4);
        System.out.println(bst);
        
	}
}

4. 运行结果:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值