关于二叉树的一些基本概念
二叉树:
二叉树就是度不超过2的树(每个结点最多有两个子结点)
满二叉树:
一个二叉树,如果每一层的结点数都达到最大值,则这个二叉树就是满二叉树
完全二叉树:
叶结点只能出现在最下层和此下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树,即如果树不满则要求左满右不满,是为完全二叉树
我们分析了二叉树结点的特性后,不难设计出二叉树的结点类 。
private class Node {
private K key;//键
private V value;//值
private Node left;//左子结点
private Node right;//右子结点
public Node(K key,V value,Node left,Node right){
this.key=key;
this.value=value;
this.left=left;
this.right=right;
}
}
下面,我们接着来分析二叉查找树的API设计
类名: BinaryTree
成员变量:
private Node root 记录根结点
private int N 记录树中元素个数
成员方法:
public void put(K key,V value) 向树中插入一个键值对
private Node put(Node,K key,V value) 向树中指定结点处插入键值对
public V get(K key) 查找树中key对应值
private V get(Node x,K key) 从树中指定节点x中查key对应值
public void delete(K key) 根据key删除键值对
private Node delete(Node x,K key) 根据key删除树中指定结点处的键值对
public int size() 获取树中元素个数
BinaryTree 完整的java代码实现
package com.tingcream.alg.tree;
import com.tingcream.alg.linear.Queue;
/**
* 二分查找树类,特性
* 1、每个结点最多有两个子节点(左子节点、右子节点)
* 2、左子节点存储的元素比当前节点小,右子节点存储的元素比当前节点大
*
* BinaryTree
*
*/
public class BinaryTree,V> {
private Node root;//表示树的根结点
private int N;//记录数中元素个数
//获取数量
public int size(){
return this.N;
}
public boolean isEmpty(){
return this.N<=0;
}
//向树中put一个元素key-value
public void put(K key, V value){
//调用重载的put方法
root=put(root,key,value);
}
//向指定的结点x中添加kv,返回添加后的x结点
public Node put(Node x,K key, V value){
//如果x为空,则new一个新的结点作为根结点
if(x==null){
N++;
return new Node(key,value,null,null);
}
//如果x不为空
// 比较x结点的键和key的大小
//如果key小于 x结点的键,则继续找x结点的左子树
//如果key大于 x结点的键,则继续找x结点的右子树
//如果key等于x 结点的键,则替换x结点的值为value
int cmp=key.compareTo(x.key);
if(cmp>0){
x.right= put(x.right,key,value);
}else if(cmp<0){
x.left=put(x.left,key,value);
}else{//key与当前结点x的键相等,则替换x结点的value值
x.value=value;
}
return x;
}
//查询树中指定key对应的value
public V get(K key){
return get(root,key);
}
//从指定的结点x中,查找key对应的值
public V get(Node x,K key){
//如果结点x为null
if(x==null){
return null ;
}
int cmp=key.compareTo(x.key);
if(cmp>0){
// 如果key大于 x结点的键,则继续找x结点的右子树
return get(x.right,key);
}else if(cmp<0){
//如果key小于 x结点的键,则继续找x结点的左子树
return get(x.left,key);
}else{
//如果key等于x 结点的键,则找到了键为key的结点,返回这个结点的value即可
return x.value;
}
}
//删除树中key对应的结点
public void delete(K key){
delete(root,key);
}
//删除指定结点x中的key对应的结点,并返回被删除的结点x
public Node delete(Node x,K key){
//x树为null
if(x==null){
return null;
}
//x树不为null
int cmp=key.compareTo(x.key);
if(cmp>0){
// 如果key大于 x结点的键,则继续找x结点的右子树
x.right=delete(x.right,key);
}else if(cmp<0){
//如果key小于 x结点的键,则继续找x结点的左子树
x.left=delete(x.left,key);
}else{
//如果key等于x结点的键,则找到了我们要删除的节点x
//元素个数减一
N--;
//如果x没有右子节点,那么直接返回左节点
if(x.right==null){
return x.left;
}
//x有右子节点,没有左边子节点,返回右子节点
if(x.left==null ){
return x.right;
}
//结点x既有左子结点又有右子结点
//找到右子树中的最小结点
if(x.right.left==null){
x.right.left=x.left;
x=x.right;
return x;
}else{
Node minNode= x.right;
while(minNode.left!=null){
minNode= minNode.left;
}
//删除右子树中最小的结点
Node n=x.right;
while(n.left!=null){
if(n.left.left==null){
n.left=null; //删除右子树中最小的结点 ,断开left引用
}else{
n=n.left;
}
}
//交换x和minNode节点
// //让x结点的左子树成为minNode的左子树
// //让x结点的右子树成为minNode的右子树
// minNode.left=x.left;
// minNode.right=x.right;
// //让x结点的父亲结点指向minNode
// x=minNode;
//x结点不动,用minNode结点中的key、value替换x节点中的key、value
x.key=minNode.key;
x.value=minNode.value;
}
}
return x;
}
//查找二叉查找树中的最小的键
public K min(){
if(root==null){
return null;
}
return min(root).key;
}
//找出最小键所在结点
private Node min(Node x){
//方式二 :while循环
Node n=x;
while(n.left!=null){
n=n.left;
}
return n;
}
//查找二叉查找树中的最大的键
public K max(){
if(size()>0){
return null;
}
return max(root).key;
}
//找出最大键所在结点
private Node max(Node x){
//方式二 :while循环
Node n=x;
while(n.right!=null){
n=n.right;
}
return n;
}
//使用前序遍历,获取整个树中的所有的键
public Queue preErgodic(){
Queue keys= new Queue();//new一个空队列keys
preErgodic(root,keys);
return keys;
}
//使用前序遍历,把指定树x中的所有键放入到keys队列中
private void preErgodic(Node x,Queue keys){
//如果x树为空,则直接return (安全性校验)
if(x==null){
return;
}
//把x结点的key放入到keys中
//递归遍历x结点的左子树
//递归遍历x结点的右子树
//将x树的根结点入队
keys.enqueue(x.key);
//如果x树有左子树,则继续遍历它的左子树
if(x.left!=null){
preErgodic(x.left,keys);
}
//如果x树有右子树,则继续遍历它的右子树
if(x.right!=null){
preErgodic(x.right,keys);
}
}
//使用中序遍历,获取整个树中的所有的键
public Queue midErgodic(){
Queue keys= new Queue();//new一个空队列keys
midErgodic(root,keys);
return keys;
}
//使用中序遍历,把指定树x中的所有键放入到keys队列中
private void midErgodic(Node x,Queue keys){
//如果x树为空,则直接return (安全性校验)
if(x==null){
return;
}
//把x结点的key放入到keys中
//递归遍历x结点的左子树
//递归遍历x结点的右子树
//如果x树有左子树,则继续遍历它的左子树
if(x.left!=null){
midErgodic(x.left,keys);
}
//将x树的根结点入队
keys.enqueue(x.key);
//如果x树有右子树,则继续遍历它的右子树
if(x.right!=null){
midErgodic(x.right,keys);
}
}
//使用后序遍历,获取整个树中的所有的键
public Queue afterErgodic(){
Queue keys= new Queue();//new一个空队列keys
afterErgodic(root,keys);
return keys;
}
//使用后序遍历,把指定树x中的所有键放入到keys队列中
private void afterErgodic(Node x,Queue keys){
//如果x树为空,则直接return (安全性校验)
if(x==null){
return;
}
//把x结点的key放入到keys中
//递归遍历x结点的左子树
//递归遍历x结点的右子树
//如果x树有左子树,则继续遍历它的左子树
if(x.left!=null){
afterErgodic(x.left,keys);
}
//如果x树有右子树,则继续遍历它的右子树
if(x.right!=null){
afterErgodic(x.right,keys);
}
//将x树的根结点入队
keys.enqueue(x.key);
}
//层序遍历,遍历整个树所有key加入队列 (广度优先 )
public Queue layerErgodic(){
if(isEmpty()){
return null;
}
//定义两个队列,分别存储树中的key和树中的节点
Queue keys=new Queue();//要返回的keys 队列
Queue nodes=new Queue();//节点队列
//向把root结点放入nodes队列
nodes.enqueue(root);
while(!nodes.isEmpty()){
//出队一个结点,得到其key放入keys队列
Node n= nodes.dequeue();
keys.enqueue(n.key);
//判断新出队的节点是否有左子节点,如果有则将其左子节点入队
if(n.left!=null){
nodes.enqueue(n.left);
}
//判断新出队的节点是否有右子节点,如果有则将其右子节点入队
if(n.right!=null){
nodes.enqueue(n.right);
}
}
return keys;
}
//获取整个树最大深度
public int maxDepth(){
if(isEmpty()){
return 0;
}
return maxDepth(root);
}
//获取指定树的最大深度
private int maxDepth(Node x){
if(x==null){
return 0;
}
//获取x结点的左子树的最大深度
//获取x结点的右子树的最大深度
//得到x节点的左右子树最大深度的较大值,将较大值+1 即可
int max=0;//x的最大深度
int maxL=0;//x的左子树的最大深度
int maxR=0;//x的右子树的最大深度
if(x.left!=null){
maxL= maxDepth(x.left);
}
if(x.right!=null){
maxR= maxDepth(x.right);
}
max=(maxL>maxR)?maxL+1:maxR+1;
return max;
}
//内部类Node 表示结点
private class Node {
private K key;//键
private V value;//值
private Node left;//左子结点
private Node right;//右子结点
public Node(K key,V value,Node left,Node right){
this.key=key;
this.value=value;
this.left=left;
this.right=right;
}
}
}
二叉树前序遍历: 先遍历根结点,再遍历左子树,最后遍历右子树
二叉树中序遍历: 先遍历左子树,再遍历根结点,最后遍历右子树
二叉树后序遍历: 先遍历左子树,再遍历右子树,最后遍历根结点
二叉树的层序遍历(广度优先): 从顶层(root)到最底层,一层一层地依次遍历,同一层中的元素从左到右遍历。