转载 http://www.cnblogs.com/CherishFX/p/4625382.html

二叉查找树的定义:

  二叉查找树或者是一颗空树,或者是一颗具有以下特性的非空二叉树:

    1. 若左子树非空,则左子树上所有节点关键字值均小于根节点的关键字;

    2. 若右子树非空,则右子树上所有节点关键字值均大于根节点的关键字;

    3. 左、右子树本身也分别是一颗二叉查找树。

二叉查找树的实现,功能有:

  1. 用一个数组去构建二叉查找树

  2. 二叉查找树的中序遍历和层次遍历

  3. 插入节点

  4. 查找节点

5. 查找二叉树中的最大值和最小值

  6.  得到节点的直接父节点

  7. 得到节点的直接前驱和直接后继节点

  8. 删除节点

package dataStructures;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack; public class BinarySearchTree {
private TreeNode<Integer> root = null; // 根节点 public BinarySearchTree() {
} // 用一个数组去构建二叉查找树
public TreeNode<Integer> buildBST(Integer[] array) {
if (array.length == 0) {
return null;
} else {
root = null; // 初始化树为空树
for (int i = 0; i < array.length; i++) { // 依次将每个元素插入
root = insertNode(root, array[i]);
}
return root;
}
} // 在二叉查找树中插入一个数据域为data的结点,新插入的结点一定是某个叶子节点
private TreeNode<Integer> insertNode(TreeNode<Integer> node, Integer data) {
if (node == null) { // 原树为空,新插入的记录为根节点
node = new TreeNode<Integer>(data, null, null);
} else {
if (node.data != data) { // 树中不存在相同关键字的结点
if (node.data > data) { // 根节点>插入数据,插入到左子树中
node.lchild = insertNode(node.lchild, data);
} else { // 根节点<插入数据,插入到右子树中
node.rchild = insertNode(node.rchild, data);
}
}
}
return node;
} // 二叉查找树的中序遍历,可以得到一个递增的有序数列
public void inOrder(TreeNode<Integer> node) {
if (node != null) {
inOrder(node.lchild);
System.out.print(node.data + " ");
inOrder(node.rchild);
}
} // 二叉查找树的层次遍历
public void levelOrder(TreeNode<Integer> root) {
Queue<TreeNode<Integer>> nodeQueue = new LinkedList<TreeNode<Integer>>();
TreeNode<Integer> node = null;
nodeQueue.add(root); // 将根节点入队
while (!nodeQueue.isEmpty()) { // 队列不空循环
node = nodeQueue.peek();
System.out.print(node.data + " ");
nodeQueue.poll(); // 队头元素出队
if (node.lchild != null) { // 左子树不空,则左子树入队列
nodeQueue.add(node.lchild);
}
if (node.rchild != null) { // 右子树不空,则右子树入队列
nodeQueue.add(node.rchild);
}
}
} // 查找数据域为data的结点,若不存在,返回null
public TreeNode<Integer> searchNode(TreeNode<Integer> node, Integer data) {
while (node != null && node.data != data) {
if (node.data > data) {
node = node.lchild; // 根节点>数据,向左走
} else {
node = node.rchild; // 根节点<数据,向右走
}
}
return node;
} // 查找最大值:不断地寻找右子节点
public TreeNode<Integer> getMaxData(TreeNode<Integer> node) {
if (node.rchild == null) {
return node;
} else {
return getMaxData(node.rchild);
}
} // 查找最小值:不断地寻找左子节点
public TreeNode<Integer> getMinData(TreeNode<Integer> node) {
if (node.lchild == null) {
return node;
} else {
return getMinData(node.lchild);
}
} // 得到数据域为data的结点的直接父节点parentNode
public TreeNode<Integer> getParentNode(TreeNode<Integer> root, Integer data) {
TreeNode<Integer> parentNode = root;
if (parentNode.data == data) { // 根节点的父节点返回为null
return null;
}
while (parentNode != null) {
// 查找当前节点的父节点的左右子节点,若是相等,则返回该父节点
if ((parentNode.lchild != null && parentNode.lchild.data == data)
|| (parentNode.rchild != null && parentNode.rchild.data == data)) {
return parentNode;
} else {
if (parentNode.data > data) { // 向左查找父节点
parentNode = parentNode.lchild;
} else {
parentNode = parentNode.rchild; // 向右查找父节点
}
}
}
return null;
} /**
* 得到结点node的直接前趋 a.该节点左子树不为空:其前驱节点为其左子树的最大元素
* b.该节点左子树为空:其前驱节点为其祖先节点(递归),且该祖先节点的右孩子也为其祖先节点 (就是一直往其parent找,出现左拐后的那个祖先节点)
*/
public TreeNode<Integer> getPrecessor(TreeNode<Integer> root, TreeNode<Integer> node) {
if (node == null) {
return null;
}
// a.该节点左子树不为空:其前驱节点为其左子树的最大元素
if (node.lchild != null) {
return getMaxData(node.lchild);
} else { // b.该节点左子树为空: 其前驱节点为其祖先节点(递归)
TreeNode<Integer> parentNode = getParentNode(root, node.data);
while (parentNode != null && node == parentNode.lchild) {
node = parentNode;
parentNode = getParentNode(root, parentNode.data);
}
return parentNode;
}
} /**
* 得到结点node的直接后继(后继节点就是比要删除的节点的关键值要大的节点集合中的最小值) a.该节点右子树不为空,其后继节点为其右子树的最小元素
* b.该节点右子树为空,则其后继节点为其祖先节点(递归),且此祖先节点的左孩子也是该节点的祖先节点,
* 就是说一直往上找其祖先节点,直到出现右拐后的那个祖先节点:
*/
public TreeNode<Integer> getSuccessor(TreeNode<Integer> root, TreeNode<Integer> node) {
if (node == null) {
return null;
}
// a.该节点右子树不为空,其后继节点为其右子树的最小元素
if (node.rchild != null) {
return getMinData(node.rchild);
} else { // b.该节点右子树为空,则其后继节点为其最高祖先节点(递归)
TreeNode<Integer> parentNode = getParentNode(root, node.data);
while (parentNode != null && node == parentNode.rchild) {
node = parentNode;
parentNode = getParentNode(root, parentNode.data);
}
return parentNode;
}
} /**
* 删除数据域为data的结点 按三种情况处理: a.如果被删除结点z是叶子节点,则直接删除,不会破坏二叉查找树的性质
* b.如果节点z只有一颗左子树或右子树,则让z的子树成为z父节点的子树,代替z的位置
* c.若结点z有左、右两颗子树,则令z的直接后继(或直接前驱)替代z,
* 然后从二叉查找树中删去这个直接后继(或直接前驱),这样就转换为第一或第二种情况
*
* @param node
* 二叉查找树的根节点
* @param data
* 需要删除的结点的数据域
* @return
*/
public boolean deleteNode(TreeNode<Integer> node, Integer data) {
if (node == null) { // 树为空
throw new RuntimeException("树为空!");
}
TreeNode<Integer> delNode = searchNode(node, data); // 搜索需要删除的结点
TreeNode<Integer> parent = null;
if (delNode == null) { // 如果树中不存在要删除的关键字
throw new RuntimeException("树中不存在要删除的关键字!");
} else {
parent = getParentNode(node, data); // 得到删除节点的直接父节点
// a.如果被删除结点z是叶子节点,则直接删除,不会破坏二叉查找树的性质
if (delNode.lchild == null && delNode.rchild == null) {
if (delNode == parent.lchild) { // 被删除节点为其父节点的左孩子
parent.lchild = null;
} else { // 被删除节点为其父节点的右孩子
parent.rchild = null;
}
return true;
}
// b1.如果节点z只有一颗左子树,则让z的子树成为z父节点的子树,代替z的位置
if (delNode.lchild != null && delNode.rchild == null) {
if (delNode == parent.lchild) { // 被删除节点为其父节点的左孩子
parent.lchild = delNode.lchild;
} else { // 被删除节点为其父节点的右孩子
parent.rchild = delNode.lchild;
}
delNode.lchild = null; // 设置被删除结点的左孩子为null
return true;
}
// b2.如果节点z只有一颗右子树,则让z的子树成为z父节点的子树,代替z的位置
if (delNode.lchild == null && delNode.rchild != null) {
if (delNode == parent.lchild) { // 被删除节点为其父节点的左孩子
parent.lchild = delNode.rchild;
} else { // 被删除节点为其父节点的右孩子
parent.rchild = delNode.rchild;
}
delNode.rchild = null; // 设置被删除结点的右孩子为null
return true;
}
// c.若结点z有左、右两颗子树,则删除该结点的后继结点,并用该后继结点取代该结点
if (delNode.lchild != null && delNode.rchild != null) {
TreeNode<Integer> successorNode = getSuccessor(node, delNode); // 得到被删除结点的后继节点
deleteNode(node, successorNode.data); // 删除该结点的后继结点
delNode.data = successorNode.data; // 用该后继结点取代该结点
return true;
}
}
return false; } /**
* 某些方法的非递归实现 1. 插入节点insertNode(): 2. 二叉查找树的中序遍历: 3. 得到二叉查找树的最大值和最小值:
*/
// 1. 在二叉查找树中插入一个数据域为data的结点,新插入的结点一定是某个叶子节点
public TreeNode<Integer> insertNode2(TreeNode<Integer> node, Integer data) {
TreeNode<Integer> newNode = new TreeNode<Integer>(data, null, null);
TreeNode<Integer> tmpNode = node; // 遍历节点
TreeNode<Integer> pnode = null; // 记录当前节点的父节点 if (node == null) { // 原树为空,新插入的记录为根节点
node = newNode;
return node;
}
while (tmpNode != null) {
pnode = tmpNode;
if (tmpNode.data == data) { // 树中存在相同关键字的结点,什么也不做
return node;
} else {
if (tmpNode.data > data) { // 根节点>插入数据,插入到左子树中
tmpNode = tmpNode.lchild;
} else { // 根节点<插入数据,插入到右子树中
tmpNode = tmpNode.rchild;
}
}
}
if (pnode.data > data) {
pnode.lchild = newNode;
} else {
pnode.rchild = newNode;
}
return node;
} // 2. 二叉查找树的中序遍历LNR,可以得到一个递增的有序数列
public void inOrder2(TreeNode<Integer> node) {
Stack<TreeNode<Integer>> nodeStack = new Stack<TreeNode<Integer>>();
TreeNode<Integer> tempNode = node; // 遍历指针
while (tempNode != null || !nodeStack.isEmpty()) {
if (tempNode != null) {
nodeStack.push(tempNode);
tempNode = tempNode.lchild;
} else {
tempNode = nodeStack.pop();
System.out.print(tempNode.data + " ");
tempNode = tempNode.rchild;
}
}
} // 3.1 查找最大值:不断地寻找右子节点
public TreeNode<Integer> getMaxData2(TreeNode<Integer> node) {
TreeNode<Integer> tempNode = node;
while (tempNode.rchild != null) {
tempNode = tempNode.rchild;
}
return tempNode;
} // 3.2 查找最小值:不断地寻找左子节点
public TreeNode<Integer> getMinData2(TreeNode<Integer> node) {
TreeNode<Integer> tempNode = node;
while (tempNode.lchild != null) {
tempNode = tempNode.lchild;
}
return tempNode;
} public static void main(String[] args) {
Integer[] array = { 8, 3, 10, 1, 6, 14, 4, 7, 13 };
BinarySearchTree bst = new BinarySearchTree();
TreeNode<Integer> root = bst.buildBST(array);
System.out.print("层次遍历:");
bst.levelOrder(root); System.out.print("\n" + "中序遍历:");
bst.inOrder(root); System.out.println();
System.out.print("得到最大值:");
System.out.println(bst.getMaxData(root).data);
System.out.print("得到最小值:");
System.out.println(bst.getMinData(root).data); System.out.print("向二叉查找树中插入一个节点,请输入需插入节点的数据域:");
Scanner input = new Scanner(System.in);
int data = input.nextInt();
System.out.print("插入节点" + data + "后,中序遍历的结果:");
root = bst.insertNode(root, data);
bst.inOrder(root); System.out.println("\n" + "在二叉查找树中查找元素," + "请输入需要查找的结点值:");
data = input.nextInt();
if (bst.searchNode(root, data) == null) {
System.out.println("false");
} else {
System.out.println("true");
} System.out.println("查找节点的直接父节点," + "请输入需要查找的结点值:");
data = input.nextInt();
System.out.print("节点" + data + "的父节点是:");
if (bst.getParentNode(root, data) == null) {
System.out.println("null");
} else {
System.out.println(bst.getParentNode(root, data).data);
} System.out.println("删除结点," + "请输入需要删除的结点值:");
data = input.nextInt();
if (bst.deleteNode(root, data)) {
System.out.print("删除结点后的层次遍历:");
bst.levelOrder(root);
System.out.print("\n" + "删除结点后的中序遍历:");
bst.inOrder(root);
} } }

逻辑图

数据结构实现(四)二叉查找树java实现的更多相关文章

  1. JVM(四):深入分析Java字节码-下

    JVM(四):深入分析Java字节码-下 在上文中,我们讲解了 Class 文件中的文件标识,常量池等内容.在本文中,我们就详细说一下剩下的指令集内容,阐述其分别代表了什么含义,以及 JVM 团队这样 ...

  2. 四、Android学习第四天——JAVA基础回顾(转)

    (转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 四.Android学习第四天——JAVA基础回顾 这才学习Android的 ...

  3. 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...

  4. Java进阶(四十)Java类、变量、方法修饰符讲解

    Java进阶(四十)Java类.变量.方法修饰符讲解 Java类修饰符 abstract: 将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现. final: 将一个类生命为最终(即非继承类) ...

  5. Java多线程(四)java中的Sleep方法

    点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更新中~ 正式篇: Java多线程(一) 什么是线程 Java多线程(二)关于多线程的CPU密集型和IO密集型这件事 Java多线程(三)如何 ...

  6. [Java并发编程(四)] Java volatile 的理论实践

    [Java并发编程(四)] Java volatile 的理论实践 摘要 Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 ...

  7. 二十四、JAVA的NIO和IO的区别

    一.JAVA的NIO和IO 1.NIO:面向缓冲区(buffer)(分为非阻塞模式IO和阻塞模式IO)组成部分:Channels管道,Buffers缓冲区,Selectors选择器 2.IO:面向流( ...

  8. 【图数据结构的遍历】java实现广度优先和深度优先遍历

    [图数据结构的遍历]java实现广度优先和深度优先遍历 宽度优先搜索(BFS)遍历图需要使用队列queue数据结构: 深度优先搜索(DFS, Depth First Search)的实现 需要使用到栈 ...

  9. 2017-2018-2 20165301 实验四《Java面向对象程序设计》实验报告

    2017-2018-2 20165301 实验四<Java面向对象程序设计>实验报告 一.Android Stuidio的安装测试 实验要求: 参考<Java和Android开发学习 ...

  10. 20155201 实验四《Java面向对象程序设计》实验报告

    20155201 实验四<Java面向对象程序设计>实验报告 一.实验内容 1.基于Android Studio开发简单的Android应用并部署测试; 2.了解Android.组件.布局 ...

随机推荐

  1. SQL优化的思路及基本原则(mysql)

    SQL优化的思路:  1.优化更需要优化的sql:  2.定位优化对象的性能瓶颈:优化前需了解查询的瓶颈是IO还是CPU,可通过PROFILING很容易定位查询的瓶颈.  3.明确优化目标:  4.从 ...

  2. cogs 306. [SGOI] 糊涂的记者

    306. [SGOI] 糊涂的记者 ★★★   输入文件:sign.in   输出文件:sign.out   评测插件时间限制:1 s   内存限制:128 MB [问题描述] 在如今的信息社会中,时 ...

  3. etymology-R

    1)vor = to eat devour vt. 狼吞虎咽地吃光: 吞没,毁灭: 目不转睛地看[de-向下+vour-吃] voracity  n.贪食,贪婪.拉丁词根vor-,vorac-表示吞食 ...

  4. lpad&amp;rpad

    lpad( string, padded_length, [ pad_string ] ) string: 准备被填充的字符串 padded_length: 填充之后的字符串长度 pad_string ...

  5. Bootstrap警告

    警告(Alerts)向用户提供了一种定义消息样式的方式.它们为典型的用户操作提供了上下文信息反馈. 警告(Alerts) 步骤: 1.创建一个 <div> 2.并向其加入一个 .alert ...

  6. JTCalendar

    JTCalendar是一款简易使用而且能够自己定义事件的日历.包含圈点标识的颜色等都能够自己定义.demo中还提供了转换日历模式的样例. 效果图: " style="margin: ...

  7. Android Handler 具体解释

    Android开发中常常使用Handler来实现"跨越线程(Activity)更新UI".本文将从源代码角度回答:为什么使用Handler可以跨线程更新UI?为什么跨线程更新UI一 ...

  8. 在外星人电脑上安装windows10和ubuntu16.04双系统小记

    最近刚刚入手了一台Alienware Aurora R6,买这货的主要目的是为了研究Deep Learning.之所以没有买组装机的原因,主要是担心组装机的不稳定,而实验经费中的设备费也还相对充足,于 ...

  9. notepad++ 查找引用(Find Reference)(适用于c c++及各类脚本比如lua、python等)

    在程序开发过程中,程序员经常用到的一个功能就是查找引用(Find Reference),Visual Studio里面的对应功能是“查找所有引用”(Find All References). 我在使用 ...

  10. Git不需重复输入账号和密码的方法

    1. 打开 TortoiseGit 附带工具 Puttygen(PuTTY Key Generator)  C:\Program Files\TortoiseGit\bin\puttygen.exe. ...