二叉树和二叉排序树的定义
学到数据结构,不说一说树那就是在扯淡,所以为了不扯淡,今天就来说说树里面比较特殊的两种树,二叉树和二叉排序树(B树和B+树以及红黑树之后也会分析)。个人觉得说到任何算法,或者要想透彻的理解一种算法首先要理解和它相关的数据结构,因为每一种算法都是依据数据结构特有的属性来设计的,从而达到“巧夺天工”的效果。因此在说二叉排序树的构成、插入、查找(获取)、删除(好好理解)之前先来说说它的定义。
在说二叉树排序树的定义前,因为二叉树和二叉排序树名字相似,总感觉有必要随便提下二叉树的概念:“二叉树是一个左右子树有序且每个节点度最多为2的树”
二叉排序树:首先是一颗二叉树其次它有如下两条规则:
1)若它的左子树不为空,那么它左子树上的任何节点的数据都小于左子树根节点的父节点的值
2)若它的右子树不为空,那么它右子树上的任何节点的数据都大于右子树根节点的父节点的值
对于二叉排序树的定义总结起来就是一句话:“左小右大”(不完全对,缺少了一些隐含约束,所以还是好好理解上面标黑的那两条规则,在结合一个图仔细体会)
二叉树排序树的算法
对于二叉树排序树的算法,因为二叉排序树首先是一棵二叉树所以这里也顺便把二叉树的遍历讲解了。
提醒:以下的代码演示均按JAVA语言,学其他编程语言的同学重点理解其思路
1)二叉树的构成
//定义二叉树节点结构
class TreeNode{
int data;//数据域
TreeNode left;//左节点
TreeNode right;//右节点
TreeNode(int data){
this.data = data;
}
}
//定义整个二叉树结构
class BinaryTree{
TreeNode root;//根节点
//插入节点
void insertNode(int data){
TreeNode node = new TreeNode(data);
//根节点为null
if(root == null) {
root = node;
return;
}
TreeNode cur = root;
//循环遍历寻找合适的插入点
while(true) {
//插入左子树
if(cur.data > data){
cur = cur.left;
if(cur == null){//cur是父节点的左叶子节点
cur.left = node;
return;
}
}else{//插入右子树
cur = cur.rigth;
if(cur == null){//cur是父节点的右叶子节点
cur.rigth = node;
return;
}
}
}
}
//search二叉树查找
TreeNode search(int data){
TreeNode cur = root;
if(root.data == data)//查找的是根节点就直接返回
return root;
while(cur.data != data){//循环遍历寻找节点
if(cur == null)//没有就返回null
return null;
else if(cur.data > data){//左子树寻找
cur = cur.left;
}else{//右子树寻找
cur = cur.rigth;
}
}
return cur;
}
/*
二叉排序树的删除
1、删除的是叶子节点
直接删除
2、删除的是只有一个子节点
叶子节点直接继承父节点位置
3、删除的节点有左右节点
以中序遍历的前驱和后继节点替换,下面的代码演示是以后继节点替换
*/
boolean deleteNode(int data){
if(root != null){
TreeNode cur = root;//当前节点
TreeNode parent = null;//父节点
boolean isLeftChild = false;//左节点标志
//从根节点遍历待删节点
while(cur != null && cur.data != data){
parent = cur;
if(cur.data > data){//左子树寻找
cur = cur.left;
isLeftChild = true;//待删节点为左节点
}else{//右子树寻找
cur = cur.rigth;
isLeftChild = false;//待删节点为右节点
}
}
if(cur == null)//没有节点
return false;
//找到代删节点后
//如果是叶子节点
if(cur.left == null && cur.rigth == null){
if(cur == root)//树只有根节点
root = null;
else if(isLeftChild){//叶子节点是左节点
parent.left = null;
}else{
parent.rigth = null;
}
}else if(cur.left == null){//只有右节点
if(cur == root)//删除节点是根节点
root = cur.rigth;
else if(isLeftChild){
parent.left = cur.rigth;//此处cur.right.data < parent.data(个人踩过的坑,
//就是这里只有知道了这个性质才能不判断cur.right.data是否大于parent.data,
//因为一旦这样就违反了排序树的定义了,所以想要知道为什么就仔细去品味上面的两条规则)
}else{
parent.rigth = cur.rigth;
}
}else if(cur.rigth == null){//只有左节点
if(cur == root)//删除节点是根节点
root = cur.left;
else if(isLeftChild)
parent.left = cur.left;
else
parent.rigth = cur.left;
}else{//即有左节点也有右节点
//以中序遍历序列的待删节点的后序节点来替换待删节点
TreeNode successor = getHouXuNode(cur);
if(cur == root)
root =successor;
if(isLeftChild)
parent.left = successor;
else
parent.rigth = successor;
successor.left = cur.left;//引用以前的左节点
successor.rigth = cur.rigth;//引用以前的右节点
}
}
return true;
}
//获取待删节点中序节点的后序节点
TreeNode getHouXuNode(TreeNode node){
TreeNode cur = node.rigth;//从待删节点右子树开始
if(cur.left == null)//待删节点的右节点只有一个节点就是它的中序后继节点
return cur;
TreeNode successor = null;//后序节点
TreeNode successorParent = null;//中序后继节点的父节点
while(cur != null){
successorParent = successor;
successor = cur;
cur = cur.left;
}
if(successor.rigth != null){//中序后继节点有右节点
successorParent.left = successor.rigth;//这里因为知道中序后继节点一定是successorParent的左节点(不懂的话自己遍历下二叉排序树就知道了)
}
return successor;
}
}
//最后顺便说下二叉树的遍历的三种递归方式
//前序遍历
void preTraveTreeNode(TreeNode root){
if(root != null){
System.out.println(root.data);
preTraveTreeNode(root.left);
preTraveTreeNode(root.rigth);
}
}
//中序遍历
void midTraveTreeNode(TreeNode root){
if(root != null){//一旦为null返回的节点再继续执行时一定是叶子节点
midTraveTreeNode(root.left);
System.out.println(root.data);
midTraveTreeNode(root.rigth);
}
}
//后序遍历
void afterTraveTreeNode(TreeNode root){
if(root != null){
afterTraveTreeNode(root.left);
afterTraveTreeNode(root.rigth);
System.out.println(root.data);
}
}