二叉搜索树是以一棵二叉树来组织的,如下图所示。这样一棵树可以使用一个链表数据结构来表示,其中每个结点就是一个对象。除了key和卫星数据之外,每个结点还饮食属性left, right 和 p, 它们分别指向结点的左孩子,右孩子和双新。如果某个孩子结点和父结点不存在,则相应属性的值为NIL。根结点是树中唯一父指针为NIL的结点 。
二叉搜索树中的关键字总是以满足二叉搜索树性质的方式来存储:
设x是二叉搜索树中的一个结点。如果y是x左子树中的一个结点,那么y.key<=x.key。如果y是x右子树中的一个结点,那么y.key >= x.key。
设x是二叉搜索树中的一个结点。如果y是x左子树中的一个结点,那么y.key<=x.key。如果y是x右子树中的一个结点,那么y.key >= x.key。
所以创建一个关于它节点的类Node:
结构图可参看:
参考代码:
public class Node{
//依次保存左右节点以及父节点
Node left;
Node right;
Node parent;
//存放数据值
private Object data;
public Node(){
}
public Node(Object data){
this.data = data;
}
public Node(Object data, Node parent, Node left, Node right){
this.data = data;
this.parent = parent;
this.left = left;
this.right = right;
}
public String toString(){//只返回data的值
return data + "";
}
public boolean equals(Object obj){//判断两个对象是否相等
if(this == obj){
return true;
}
if(obj.getClass() == Node.class){ //getClass()进行类型判断 instanceof: 判断左边对象是否是右边类的实例 --boolean类型的值
Node target = (Node)obj;
return data.equals(target.data)
&& left == target.left
&& right == target.right
&& parent == target.parent;
}
return false;
}
}
遍历:
这里采用的是中序遍历的方法。可参考:
点击打开链接。
参考代码:
//中序
public List<Node> in_order(){
return in_order(root);
}
public List<Node> in_order(Node node){
List<Node> list = new ArrayList<Node>();
if(node.left != null){
list.addAll(in_order(node.left));
}
list.add(node);
if(node.right != null){
list.addAll(in_order(node.right));
}
return list;
}
查找:
就是根据它的存储特点去不断搜寻,因为他遵循左节点小于根节点,右节点大于根结点的特点。所以沿一条简单路径向下,然后找到相等为止。
//查询
public Node tree_search(T data){
return tree_search(root, data);
}
private Node tree_search(Node node, T data){// 根据元素去访问这个值
if(node == null){
return null;
}
//x.compareTo(y) x=y -- 0, x > y - 1, x < y - -1
int cmp = data.compareTo(node.data);
if(cmp < 0){
return tree_search(node.left, data);
}
else if(cmp > 0){
return tree_search(node.right,data);
}
else{
return node;
}
}
最大/最小关键值元素:
以最小值为例:
如果结点x没有左子树,那么由于x右子树中的每个关键字都至少大于或等于x.key,则以x为根的子树中的最小关键字是x.key。如果结点x有左子树,那么由于其右子树中没有关键字小于x.key,且在左子树中的每个关键字不大于x.key,则以x为根的子树中的最小关键字一定在经x.left为根的子树中。
//返回最大 和最小关键字元素
public Node mininum(){
return mininum(root);
}
public Node mininum(Node node){
while(node.left != null){
node = node.left;
}
return node;
}
public Node maxinum(){
return maxinum(root);
}
public Node maxinum(Node node){
while(node.right != null){
node = node.right;
}
return node;
}
前驱和后继:
这里是根据它排序后的顺序,根据指定的值找到它的前驱和后继
//寻找前驱和后继 简单的遵循一条路径沿树向下 或者 沿树向上。
public Node successor(Node node){
if(node.right != null){
return mininum(node.right);
}
Node newNode = node.parent;
while(newNode != null && node == newNode.right){
node = newNode;
newNode = newNode.parent;
}
return newNode;
}
public Node presuccessor(Node node){
if(node.left != null){
return mininum(node.left);
}
Node newNode = node.parent;
while(newNode != null && node == newNode.left){
node = newNode;
newNode = newNode.parent;
}
return newNode;
}
插入:
因为要保证二叉搜索树的特点,所以先根据根结点的位置, 和输入的值进行比较,如果大于必然在右子树,如果小于必然在左子树里,不断更新这个根节点,直到找到能插入这个新的元素的位置,在根据它的特点判断是它的右节点还是左节点即可。
//插入数据
public Node insert(T data) {
// 如果根节点为空
if(root == null){
root = new Node(data, null, null, null);
}
else{
Node current = root;
Node parent = null;
int cmp = 0;
//搜索到合适的叶子节点,以该节点为父节点添加新的节点
while(current != null){//找到合适的位置 假定的"null"会被我指定的值替换
parent = current;
cmp = data.compareTo(current.data);
if(cmp < 0){
current = current.left;
}
else{
current = current.right;
}
}
//创建新节点, 通过直接的比较知道了它的父节点的值, 接下来就是通过比较 判断是他父节点的 左还是右节点
Node newNode = new Node(data, parent, null, null);
if(cmp > 0){
parent.right = newNode;
}
else{
parent.left = newNode;
}
return newNode;
}
return null;
}
删除:
- 如果z没有左孩子(图12-4(a)),那么用其右孩子来替换z,这个右孩子可以是NIL,也可以不是。当z的右孩子是NIL时,此时这种情况归为z没有孩子结点的情形。当z的右孩子非NIL时,这种情况就是z仅有一个孩子结点的情形,该孩子是其右孩子。
- 如果z仅有一个孩子且为其左孩子(图12-4(b)),那么用其左孩子来替换z。
- 否则,z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树中并且没有左孩子(见练习12.2-5)。现在需要将y移出原来的位置进行拼接,并替换树中的z。
- 如果y是z的右孩子(图12-4(c)),那么用y替换z,并仅留下y的右孩子。
- 否则,y位于z的右子树中但并不是z的右孩子(图12-4(d))。在这种情况下,先用y的右孩子替换y,然后再用y替换z。
我在这里使用一个transplate()方法,是用一棵"子树"替换另一棵"子树"并成为其双亲孩子节点的方法。讨论了输入的其中一个子树为根(第一个if)、左节点(else if())中、右节点(else)中不断的对其更新。
//删除指定节点
public void delete(T element){
//获取要删除的节点 注意这里是通过查找而不是new
Node target = tree_search(element);
if(target == null){
return;
}
else{
Node newNode = null;
//没有左孩子, 删除改节点之后把自己的右节点补上来即可
if(target.left == null){
transplant(target, target.right);
}
//由一个左孩子, 没有右孩子
else if(target.right == null){
transplant(target, target.left);
}
else{
//如果它有右子树(或者叫右子树非空) 那么它的后继一定是他右子树的最小值
newNode = mininum(target.right);
if(newNode.parent != target){
transplant(newNode, newNode.right);
newNode.right = target.right;
newNode.right.parent = newNode;
}
transplant(target, newNode);
newNode.left = target.left;
newNode.left.parent = newNode;
}
}
}
private void transplant(Node node1, Node node2) {
if(node1.parent == null){
Node current = root;
current = node2;
}
else if(node1 == node1.parent.left){
node1.parent.left = node2;
}
else{
node1.parent.right = node2;
}
if(node2 != null){
node1.parent = node2.parent;
}
}
测试部分和截图:
构建二叉搜索树:
//构建二叉排序树
public SortBinTree(){
root = null;
}
public SortBinTree(T data){
root = new Node(data, null, null, null);
}
测试:
public static void main(String[] args) {
SortBinTree<Integer> sbt = new SortBinTree<Integer>(15);
SortBinTree.Node node = sbt.insert(5);
sbt.insert(20);
sbt.insert(10);
sbt.insert(3);
sbt.insert(8);
sbt.insert(30);
System.out.println(sbt.in_order());
System.out.println("最小关键元素值" + sbt.mininum());
System.out.println("最大关键元素值" +sbt.maxinum());
sbt.insert(17);
System.out.println(sbt.in_order());
System.out.println("节点5的前驱是:" + sbt.presuccessor(node));
System.out.println("节点5的后继是:" + sbt.successor(node));
sbt.delete(8);
System.out.println(sbt.in_order());
}
参考:
《算法导论》
以上就是这篇的内容了,如果您有什么觉得改进的地方或者发现了错误的部分,请指教。谢谢!