二叉查找树

二叉查找树

二叉查找树是一棵二叉树,其结点含有Comparable的对象,并以如下方式组织。对于树的每一个结点:
  • 结点中的数据大于结点的左子树中的数据
  • 结点中的数据小于结点的右子树中的数据
二叉查找树的性质:对二叉查找树进行中序遍历,即可得到有序的数列。
二叉查找树的时间复杂度:它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是 平衡查找树设计的初衷。
二叉查找树的高度决定了二叉查找树的查找效率。

查找与检索

二叉查找树的查找类似于进行数组的折半查找:在二叉查找树的其中一棵子树中继续查找,而折半查找则是在数组的其中一半中进行查找。
公有方法getEntry调用了一个私有递归方法findEntry来实现查找。
public T getEntry(T entry){
	return findEntry(grtRootNode(), entry);	
}	
private T findEntry(BinaryNode<T> rootNode,T entry){
	T result=null;
	if(rootNode!=null){
		T rootEntry=rootNode.getData();
		if(entry.compareTo(rootEntry)==0)
			result=rootEntry;
		else if(entry.compareTo(rootEntry)<0)
			result=findEntry((BinaryNode<T>) rootNode.getLeftChild(), entry);
		else
			result=findEntry((BinaryNode<T>) rootNode.getRightChild(), entry);
	}
	return result;
}

插入元素

二叉查找树的每次插入都为这棵树加上一个新的叶子结点。
插入过程如下:
  • 若当前的二叉查找树为空,则插入的元素为根节点
  • 若插入的元素值小于根节点值,则将元素插入到左子树中
  • 若插入的元素值不小于根节点值,则将元素插入到右子树中
public T add(T newEntry){
	T result=null;
	if(isEmpty())	//设置根结点
		setRootNode(new BinaryNode<T>(newEntry));
	else
		result=addEntry(grtRootNode(), newEntry);
	return result;
}

private T addEntry(BinaryNode<T> rootNode,T newEntry){
	if(newEntry==null)
		return null;
	T result=null;
	int comparison=newEntry.compareTo(rootNode.getData());
	if(comparison==0){
		result=rootNode.getData();
		rootNode.setData(newEntry);
	}else if(comparison<0){
		if(rootNode.hasLeftChild())
			result=addEntry((BinaryNode<T>) rootNode.getLeftChild(), newEntry);
		else
			rootNode.setLeftChild(new BinaryNode<T>(newEntry));
	}else{
		if(rootNode.hasRightChild())
			result=addEntry((BinaryNode<T>) rootNode.getRightChild(), newEntry);
		else
			rootNode.setRightChild(new BinaryNode<T>(newEntry));
	}
	return result;
}

删除元素

删除元素比插入元素还要棘手,需要考虑3种情况:
  • 该结点没有孩子,即叶子结点
  • 该结点有一个孩子
  • 该结点有两个孩子
(1)删除叶子结点
若删除的是叶子结点,则将双亲的相应孩子的引用置为null即可。


(2)删除有一个孩子的结点
若删除的是有一个孩子C的结点N,若N是P的左孩子,则让C成为P的左孩子;类似地,若N是P的右孩子,则让C成为P的右孩子。

(3)删除有两个孩子的结点
假若 e是结点N的元素,由于删除的结点N有两个孩子,则 e就不可能是树中最小元素,也不可能是最大元素。假若元素按升序排序,则必有
... a < e < b ...
a称为 e中序前驱,表示结点N左子树中最大的元素; b称为 e中序后驱,表示结点N右子树中最小的元素。

假定删除了含有a的结点,并用a代替e,那么N的左子树中剩下的元素都小于a,而右子树中的元素都大于e>a,因此仍是二叉树。

寻找适合的元素a,a必须满足一下要求:
  • a的结点至多有一个孩子
  • a是结点N的左子树中最大的元素
显然,a位于左子树最右侧的结点。
基于上述分析,可得出两套解决方案:
  • 方案一:删除结点N时,先找出N的左子树中最右侧结点R,以结点R中的元素替代结点N中的元素,最后删除结点R
  • 方案二:删除结点N时,先找出N的右子树中最左侧结点L,以结点L中的元素替代结点N中的元素,最后删除结点L
下面是删除元素为12的结点的例子。

public T remove(T entry){
	ReturnObject oldEntry=new ReturnObject<T>(null);
	BinaryNode<T> newRoot=removeEntry(grtRootNode(), entry, oldEntry);
	setRootNode(newRoot);
	return (T) oldEntry.getEntry();
}	
private BinaryNode<T> removeEntry(BinaryNode<T> rootNode,T entry,ReturnObject oldEntry){
	if(rootNode!=null){
		T rootData =rootNode.getData();
		int comparison=entry.compareTo(rootData);
		if(comparison==0){
			oldEntry.setEntry(rootData);
			rootNode=removeFromRoot(rootNode);
		}else if(comparison<0){
			BinaryNode<T> leftChild=(BinaryNode<T>) rootNode.getLeftChild();
			rootNode.setLeftChild(removeEntry(leftChild, entry, oldEntry));
		}else{
			BinaryNode<T> rightChild=(BinaryNode<T>) rootNode.getRightChild();
			rootNode.setRightChild(removeEntry(rightChild, entry, oldEntry));
		}
	}
	return rootNode;
}
/**
 * Task:在指定树中找含最大元素节点
 * @param rootNode
 * @return
 */
private BinaryNode<T> findLargest(BinaryNode<T> rootNode){
	if(rootNode.hasRightChild())
		rootNode=findLargest((BinaryNode<T>) rootNode.getRightChild());
	return rootNode;
}

/**
 * Task:在指定树中删除含最大元素节点
 * @param rootNode
 * @return
 */
private BinaryNode<T> removelargest(BinaryNode<T> rootNode){
	if(rootNode.hasRightChild()){
		BinaryNode<T> root=removelargest((BinaryNode<T>) rootNode.getRightChild());
		rootNode.setRightChild(root);
	}else
		rootNode=(BinaryNode<T>) rootNode.getLeftChild();
	return rootNode;
}

/**
 * Task:删除指定树根节点元素
 * @param rootNode
 * @return
 */
private BinaryNode<T> removeFromRoot(BinaryNode<T> rootNode){
	if(rootNode.hasLeftChild() && rootNode.hasRightChild()){
		BinaryNode<T> leftSubtreeRoot=(BinaryNode<T>) rootNode.getLeftChild();
		BinaryNode<T> largestNode=findLargest(leftSubtreeRoot);
		rootNode.setData(largestNode.getData());
		rootNode.setLeftChild(removelargest(leftSubtreeRoot));
	}else if(rootNode.hasRightChild())
		rootNode=(BinaryNode<T>) rootNode.getRightChild();
	else
		rootNode=(BinaryNode<T>) rootNode.getLeftChild();
	return rootNode;
}

private BinaryNode<T> removeEntry(BinaryNode<T> rootNode,T entry,ReturnObject oldEntry){
	if(rootNode!=null){
		T rootData =rootNode.getData();
		int comparison=entry.compareTo(rootData);
		if(comparison==0){
			oldEntry.setEntry(rootData);
			rootNode=removeFromRoot(rootNode);
		}else if(comparison<0){
			BinaryNode<T> leftChild=(BinaryNode<T>) rootNode.getLeftChild();
			rootNode.setLeftChild(removeEntry(leftChild, entry, oldEntry));
		}else{
			BinaryNode<T> rightChild=(BinaryNode<T>) rootNode.getRightChild();
			rootNode.setRightChild(removeEntry(rightChild, entry, oldEntry));
		}
	}
	return rootNode;
}

操作的效率

插入、删除、查找操作都需要从树根开始查找。插入元素时,若该元素不在树中,则查找终止于叶子,否则,查找可以终止得更快。同样删除操作亦如此。 因此,每个操作所需要的比较次数与树的高度成正比,即O(h)。
但是,在某些极端的情况下(如在插入的序列是有序的时),二叉搜索树将退化成近似链或链,此时,其操作的时间复杂度将退化成线性的,即O(n)。
若结点的数目相同,则满二叉查找树是最矮的树,它的性能也是最高效的。含有n个结点的满二叉树的高度是 log 2 ( n +1),因此满二叉查找树的操作都是O(log n)。


  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值