二叉排序树详解

一、为什么要使用二叉排序树

对于线性存储的数组来说,通过二分查找等算法其查找效率为log2n是不错的。但是其插入删除数据的效率是比较低的,为了维护数组的有序性,插入删除时必须将一些数据"挪"位置。此算法复杂度为O(n)。如果不需要维护数组的有序性的话,添加数据直接在数组尾添加,删除数据时,也只需要用数组尾数据覆盖删除的数据,并使长度-1即可,但是无序的序列对于查找来说,是十分不好的。所以引入了二叉排序树,这种数据结构插入删除效率都还可以,并且查找效率也不错。
二叉排序树中序遍历的结果为关键字从小到大排序的结果。

package suanfa;

import java.util.Stack;

class Node{
	int data;
	Node leftChild;
	Node rightChild;
	public Node(int data, Node leftChild, Node rightChild) {
		super();
		this.data = data;
		this.leftChild = leftChild;
		this.rightChild = rightChild;
	}
}
public class Main {
	private static Node tree;
	public static void main(String[] args) {
		int[] arr ={62,88,58,47,35,73,51,99,37,93};
		tree = new Node(arr[0], null, null);
		for(int i=1;i<arr.length;i++){
			insert(tree,arr[i]);
		}
		inTranverse();
	}
	public static Node find(int key){
		return find(tree,key);
	}
	//递归构建二叉排序树
	public static Node insert(Node node,int key){
		//递归结束条件, 找到了插入的位置,进行插入操作
		if(node == null){
			node = new Node(key, null, null);
			return node;
		}
		//key小于node的关键字的话,应放在node的左子树里,左子树情况未知,按给定递归逻辑处理即可
		if(node.data > key){
			//递归处理左子树
			node.leftChild = insert(node.leftChild, key);
		}else if(node.data < key){
			//递归处理右子树
			node.rightChild = insert(node.rightChild, key);
		}
		return node;
	}
	//查找操作
	private static Node find(Node node,int key){
		if(node==null) return null;
		if(node.data > key){
			return find(node.leftChild,key);
		}else if(node.data < key){
			return find(node.rightChild,key);
		}else {
			return node;
		}
	}
	//非递归中序遍历(深度优先思想)
	public static void inTranverse(){
		Node tempTree = tree;
		if(tempTree == null) return;
		Stack<Node> stack = new Stack<Node>();
		//tempTree!=null,表示右子树可以开始左子树遍历
		//右子树为空的话,!stack.isEmpty()条件继续对路径上的点回溯
		while(!stack.isEmpty()||tempTree!=null){
			//最左子树,并经过的路径
			while(tempTree!=null){
				stack.push(tempTree);
				tempTree = tempTree.leftChild;
			}
			if(!stack.isEmpty()){
				Node node = stack.pop();
				System.out.println(node.data);
				//进入右子树,开始新的一轮左子树遍历(这是递归的自我实现)  
				tempTree = node.rightChild;
			}
		}
	}
	//后序遍历 非递归
	public static void afterTranverse(){
		if(tree == null) return;
		Node currentTree = tree;
		Node preNode = null;
		//保存经过的路径
		Stack<Node> stack = new Stack<Node>();
		//来到最左子树,并保存经过的路径
		while(currentTree!=null){
			stack.push(currentTree);
			currentTree = currentTree.leftChild;
		}
		//弹出走过的路径
		while(!stack.isEmpty()){
			currentTree = stack.pop();
			//若当前根节点的右子树为空或者刚遍历完其右子树,则输出当前根节点
			if(currentTree.rightChild==null||currentTree.rightChild == preNode){
				System.out.println(currentTree.data);
				preNode = currentTree;
			}else{//当前节点有右子树需要遍历
				
				//把当前根节点先还回栈去,并递归遍历右子树
				stack.push(currentTree);
				Node node = currentTree.rightChild;
				while(node!=null){
					stack.push(node);
					node = node.leftChild;
				}
			}
		}
	}
	//前序遍历非递归(深度优先思想)
	public static void preTranverse(){
		if(tree == null) return;
		Node tempTree = tree;
		//保存经过的路径
		Stack<Node> stack = new Stack<Node>();
		//tempTree is or not is null 判断右子树是否可以继续递归遍历
		//stack.isEmpty()继续回溯路径
		while(!stack.isEmpty()||tempTree!=null){
			while(tempTree!=null){
				System.out.println(tempTree.data);
				stack.push(tempTree);
				tempTree = tempTree.leftChild;
			}
			if(!stack.isEmpty()){
				Node node = stack.pop();
				tempTree = node.rightChild;
			}
		}
	}
	//递归中序遍历
	public static void inTranverse(Node tree){
		if(tree == null) return;
		inTranverse(tree.leftChild);
		System.out.println(tree.data);
		inTranverse(tree.rightChild);
	}
	public static void buildTree(int key){
		//若根节点为空,则建立根节点
		if(tree == null){
			tree = new Node(key,null,null);
			return;
		}
		//根节点不为空
		Node temp = tree;
		while(true){
			//要插入的关键字小于根节点,则插到左子树去
			if(temp.data > key){
				//若左子树为空的话,直接插入
				if(temp.leftChild == null){
					Node node = new Node(key,null,null);
					temp.leftChild = node;
					return;
				}
				//若左子树不为空,则进入左子树,继续判断
				temp = temp.leftChild;
			}else if(temp.data < key){
				//若右子树为空的话,直接插入
				if(temp.rightChild == null){
					Node node = new Node(key,null,null);
					temp.rightChild = node;
					return;
				}
				//若左子树不为空,则进入左子树,继续判断
				temp = temp.rightChild;
			}else{
				System.out.println("请不要插入相等的值");
				return;
			}
		}
	}

}
//删除
public static void deleteTranverse(Node node,int key){
		if(tree == null){
			return;
		}
		if(node.data == key){
			delete(node);
		}else if(node.data > key){
			deleteTranverse(node.leftChild,key);
		}else{
			deleteTranverse(node.rightChild,key);
		}
	}
	
	public static void delete(Node node){
		//要删除的节点的左子树为空,则把指向欲删除的节点的指针指向其右子树的根节点
		if(node.leftChild == null){
			if(node.isRightChild){
				node.parent.rightChild = node.rightChild;
			}else{
				node.parent.leftChild = node.rightChild;
			}
		//要删除的节点的右子树为空,则把指向欲删除的节点的指针指向其左子树的根节点
		}else if(node.rightChild == null){
			if(node.isRightChild){
				node.parent.rightChild = node.leftChild;
			}else{
				node.parent.leftChild = node.leftChild;
			}
		//要删除的节点的左右子树
		}else{
			Node preNode = node;
			Node tempNode = node.leftChild;
			//找到要删除节点的左子树的最右子树tempNode,preNode是tempNode的上一个节点
			while(tempNode.rightChild!=null){
				preNode= tempNode;
				tempNode = tempNode.rightChild;
			}
			node.data = tempNode.data;
			//要删除节点的左子树没有右子树
			if(node.leftChild == tempNode){
				tempNode.parent.leftChild = tempNode.leftChild;
			}else{//要删除节点的左子树有右子树
				preNode.rightChild = tempNode.leftChild;
			}
		}
	}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
折半查找和二叉排序树都是解决查找问题的常用算法,在实现上有一些不同。 折半查找是一种基于有序序列的查找算法,它的时间复杂度为O(log n)。具体实现时,折半查找将待查找的元素与序列的中间元素进行比较,如果相等则返回,如果小于中间元素,则在左边继续查找,否则在右边继续查找。重复这个过程直到找到目标元素或者确定目标元素不存在。 二叉排序树是一种基于二叉树结构的查找算法,它的时间复杂度取决于树的平衡情况,通常为O(log n)。具体实现时,二叉排序树将元素插入到树中的合适位置,保证左子树的元素小于当前节点,右子树的元素大于当前节点。查找时从根节点开始,与当前节点进行比较,如果相等则返回,如果小于当前节点,则在左子树中继续查找,否则在右子树中继续查找。重复这个过程直到找到目标元素或者确定目标元素不存在。 在实际应用中,折半查找常用于静态查找,即数据集合不会发生变化的情况下;而二叉排序树适用于动态查找,即数据集合可能随时发生变化的情况下。同时,在二叉排序树中,为了避免树的不平衡造成时间复杂度的恶化,可以采用平衡二叉树的算法,如红黑树、AVL树等。 总体来说,折半查找和二叉排序树都是非常常用的查找算法,具体选择哪种算法取决于数据集合的特点和实际应用的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值