一、 树的概述
树是计算机中应用广泛的一种数据结构,日常生活中常见的图谱,公司组织结构等,都是树结构的数据。
树结构在计算机中是根朝上,叶子结点向下的。如图,它是由N个有限结点组成的具有层次关系的集合。
树有如下特点:
- 没有父结点的称为根结点
- 每个结点有0或多个子结点
- 每一个非根结点只有一个父结点
- 每个结点及其后代结点可以看成一颗子树,称为当前结点父结点的一颗子树
二、 树的术语
- 结点的度:结点所含子树的个数称为结点的度,如下图:2,3,4为1的子树,1的度为3。5,8和6为2的子树,2的度为2。
- 叶结点:度为0的结点称为叶结点,如4即为叶结点
- 分支结点:度不为0的结点称为分支结点。
- 结点的层次:根结点层次为1,往下的后继结点层次为2,依次类推
- 结点的层序编号:将结点的元素,从上到下,从左到右依次排序,如下图为12345678
- 树的度:树中所有结点的度的最大值,不一定是根结点,如下截图的6若衍生很多的子结点,则度的最大值计算应该算6的子结点个数
- 树的深度:层次最多的结点
- 森林:去掉根结点后的N个不相交的集合,称为森林
- 孩子结点:一个结点的后继结点称为孩子结点
- 父结点:一个结点的前驱结点称为父结点
- 兄弟结点:同一父结点的孩子结点互称为兄弟结点
三、 二叉树的基本定义
二叉树:度不超过2的数称为二叉树
满二叉树:每一个结点度都达到最大值的树称为满二叉树
完全二叉树:往二叉树放元素时,按从上到下,从左到右的次序去放,即度为0的叶子结点,只能出现在最下或次下层的称为完全二叉树
四、 二叉查找树的构建
构建如上图的二叉树,需要怎样声明对象和组成二叉树呢,见如下实现:
public class Node<Key,Value> {
//结点的键
public Key key;
//结点的值
public Value value;
//结点的左子结点
public Node left;
//结点的右子结点
public Node right;
public Node(Key key, Value value, Node left, Node right) {
super();
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
public static void main(String[] args) {
Node node1 = new Node(1, "A", null, null);
Node node2 = new Node(2, "B", null, null);
Node node3 = new Node(3, "C", null, null);
Node node4 = new Node(4, "D", null, null);
Node node5 = new Node(5, "E", null, null);
Node node6 = new Node(6, "F", null, null);
node1.left = node2;
node1.right = node3;
node2.left = node4;
node2.right = node5;
node3.left = node6;
}
}
|
五、 二叉查找树的实现
二叉树插入键值对:
(1) 当前二叉树的结点为空时,把新结点作为根结点
(2) 当前二叉树的结点不为空时,若新结点的key小于当前结点,则继续查找当前结点的左子结点
(3) 若新结点的key大于当前结点,则继续查找当前结点的右子结点
(4) 若新结点的key等于当前结点,则将当前结点的value替换
二叉树获取指定结点:
(1) 从根结点开始,若查询的key小于当前结点,则继续查找当前结点的左子结点
(2) 若查询的key大于当前结点,则继续查找当前结点的右子结点
(3) 若查询的key等于当前结点,则返回当前结点
二叉树删除指定结点:
(1) 首先使用获取结点的方法,找到要删除的结点
(2) 找到被删除结点右子树中最小的元素min,并删除它
(3) 让被删除结点的左子树成为min的左子树,让被删除结点的右子树成为min的右子树
(4) 让被删除结点的父节点指向min
public class BinaryTree<Key extends Comparable<Key>,Value> {
// 根结点声明
public Node root;
//树的元素个数
public int N;
public BinaryTree() {
root = null;
N=0;
}
/**
* 二叉树元素个数
* @return
*/
public int size() {
return N;
}
/**
* 二叉树是否为空
* @return
*/
public boolean isEmpty() {
return N==0;
}
/**
* 往二叉树中添加元素
* @param key
* @param value
*/
public void put(Key key,Value value) {
root = put(root, key, value);
}
/**
* 往二叉树指定结点添加元素
* @param node
* @param key
* @param value
*/
public Node put(Node node,Key key,Value value) {
if(node==null) {
//总数+1
N++;
return new Node(key, value, null, null);
}
int compare = key.compareTo((Key) node.key);
if(compare>0) {
//存放的key大于当前结点,则继续遍历当前结点的右子树
node.right = put(node.right,key,value);
}else if(compare<0) {
//存放的key小于当前结点,则继续遍历当前结点的左子树
node.left = put(node.left,key,value);
}else {
//存放的key等于当前结点,则替换
node.value = value;
}
return node;
}
/**
* 从二叉树中获取key对应的value
* @param key
* @return
*/
public Value get(Key key) {
return get(root,key);
}
/**
* 从二叉树指定结点中获取key对应的value
* @param node
* @param key
* @return
*/
public Value get(Node node,Key key) {
if(node==null) {
return null;
}
int compare = key.compareTo((Key) node.key);
if(compare>0) {
//查找的key大于当前结点,则继续遍历当前结点的右子树
return get(node.right,key);
}else if(compare<0) {
//查找的key小于当前结点,则继续遍历当前结点的左子树
return get(node.left,key);
}else{
return (Value) node.value;
}
}
/**
* 删除二叉树中指定结点
* @param key
*/
public Node delete(Key key) {
return delete(root,key);
}
/**
* 删除二叉树中指定结点
* @param node
* @param key
*/
public Node delete(Node node,Key key) {
if(node==null) {
return null;
}
int compare = key.compareTo((Key) node.key);
if(compare>0) {
//删除的key大于当前结点,则继续遍历当前结点的右子树,
node.right = delete(node.right,key);
}else if(compare<0) {
//删除的key大于当前结点,则继续遍历当前结点的右子树,
node.left = delete(node.left,key);
}else {
//被删除结点只有左结点,没有右结点的情况
if(node.left!=null && node.right == null) {
//总数减1
N--;
return node.left;
}
//被删除结点只有右结点,没有左节点的情况
if(node.right!=null && node.left == null) {
//总数减1
N--;
return node.right;
}
//找到被删除结点的右子树中的最小结点,用来取代被删除的结点
Node min = node.right;
while(min.left!=null) {
min = min.left;
}
//删除被删除结点的右子树中的最小结点
Node delNode = node.right;
while(delNode.left !=null) {
if(delNode.left.left==null) {
delNode.left = null;
}else {
delNode = delNode.left;
}
}
//让被删除结点右子树中的最小结点,取代被删除的结点
min.right = node.right;
min.left = node.left;
node = min;
//总数减1
N--;
}
return node;
}
}
|
六、 二叉查找树最小键
查找二叉树中最小键的思路:从根结点找起,一直找当前结点的左子树,直到当前结点的左子树为空时,则当前结点为二叉查找树的的最小键
/**
* 查找二叉查找树的最小键
* @return
*/
public Key min() {
return (Key) min(root).key;
}
public Node min(Node node) {
if(node.left!=null) {
return min(node.left);
}else {
return node;
}
}
|
七、 二叉查找树最大键
查找二叉树中最大键的思路:从根结点找起,一直找当前结点的右子树,直到当前结点的右子树为空时,则当前结点为二叉查找树的的最大键
/**
* 查找二叉查找树的最大键
* @return
*/
public Key max() {
return (Key) max(root).key;
}
public Node max(Node node) {
if(node.right!=null) {
return max(node.right);
}else {
return node;
}
}
|
八、 二叉树的最大深度
计算二叉树最大深度的思路
- 当只有一个根结点时为1,当根结点为null时是0
- 计算左子树的最大深度
- 计算右子树的最大深度
- 比较左右子树最大深度,取较大值+1(根结点)
/**
* 查找二叉树的最大深度
* @return
*/
public int deepLength() {
return deepLength(root);
}
public int deepLength(Node node) {
//根结点为空
if(node==null) {
return 0;
}
//根结点不为空,定义左结点深度maxLeft,右结点深度maxRight,最大深度等于maxLeft和maxRight的较大值+1
int maxLeft = 0;
int maxRight = 0;
int max = 0;
System.out.println(node.key);
//右子树不为空,则持续遍历当前结点的右子树
if(node.right!=null) {
maxRight = deepLength(node.right);
}
//左子树不为空,则持续遍历当前结点的左子树
if(node.left!=null) {
maxLeft = deepLength(node.left);
}
//最大深度计算
max = maxLeft > maxRight ? maxLeft+1 : maxRight+1;
return max;
}
|
九、 二叉树的遍历
如线性表中的链表,数组,队列,栈一样,二叉树也有遍历查找的需求,由于树的结构和线性表有很大的差异,没有办法从前往后去查询,此时我们可以根据搜索路径来查找,即通过控制根结点
左子树或右子树的访问顺序,来实现二叉树的遍历查询。
- 前序遍历:先访问根结点,再访问左子树,后访问右子树
- 中序遍历:先访问左子树,再访问根结点,后访问右子树
- 后序遍历:先遍历左子树,再访问右子树,后访问根结点
前序遍历结果:EBADCGFH
中序遍历结果:ABCDEFGH
后序遍历结果:ACDBFHGE
十、 二叉树的遍历-前序遍历
前序遍历的实现思路
- 将当前结点的key放入队列中
- 遍历当前结点的左子树,左子树不为空时,递归遍历左子树
- 遍历当前结点的右子树,右子树不为空时,递归遍历右子树
/**
* 前序遍历实现
*/
public Queue<Key> preSearch() {
Queue<Key> keys = new Queue<Key>();
preSearch(root, keys);
return keys;
}
public void preSearch(Node node, Queue<Key> keys) {
if (node == null) {
return;
}
// 将当前结点放入队列中
keys.enqueue((Key) node.key);
// 遍历左子树,不为空时递归遍历
if (node.left != null) {
preSearch(node.left, keys);
}
// 遍历右子树,不为空时递归遍历
if (node.right != null) {
preSearch(node.right, keys);
}
}
|
十一、 二叉树的遍历-后序遍历
后序遍历的实现思路
- 遍历当前结点的左子树,左子树不为空时,递归遍历左子树
- 遍历当前结点的右子树,右子树不为空时,递归遍历右子树
- 将当前结点的key放入队列中
/**
* 后序遍历实现
*/
public Queue<Key> afterSearch() {
Queue<Key> keys = new Queue<Key>();
afterSearch(root, keys);
return keys;
}
public void afterSearch(Node node, Queue<Key> keys) {
if (node == null) {
return;
}
// 遍历左子树,不为空时递归遍历
if (node.left != null) {
afterSearch(node.left, keys);
}
// 遍历右子树,不为空时递归遍历
if (node.right != null) {
afterSearch(node.right, keys);
}
// 将当前结点放入队列中
keys.enqueue((Key) node.key);
}
|
十二、 二叉树的遍历-中序遍历
中序遍历的实现思路
- 遍历当前结点的左子树,左子树不为空时,递归遍历左子树
- 将当前结点的key放入队列中
- 遍历当前结点的右子树,右子树不为空时,递归遍历右子树
/**
* 中序遍历实现
*/
public Queue<Key> midSearch() {
Queue<Key> keys = new Queue<Key>();
midSearch(root, keys);
return keys;
}
public void midSearch(Node node, Queue<Key> keys) {
if (node == null) {
return;
}
// 遍历左子树,不为空时递归遍历
if (node.left != null) {
midSearch(node.left, keys);
}
// 将当前结点放入队列中
keys.enqueue((Key) node.key);
// 遍历右子树,不为空时递归遍历
if (node.right != null) {
midSearch(node.right, keys);
}
}
|
十三、 二叉树的遍历-层序遍历
层序遍历的实现思路
- 创建队列A,存储当前结点
- 循环队列A,首先弹出队列中的结点key,并存储到队列B中,如果当前结点的左子树不为空,则将左节点存储到队列A中,如果当前结点的右子树不为空,则将右结点存储到队列A中
/**
* 层序遍历实现
*/
public Queue<Key> layerSearch() {
Queue<Node> A = new Queue<Node>();
Queue<Key> B = new Queue<Key>();
A.enqueue(root);
while (!A.isEmpty()) {
Node n = A.dequeue();
B.enqueue((Key) n.key);
if (n.left != null) {
A.enqueue(n.left);
}
if (n.right != null) {
A.enqueue(n.right);
}
}
return B;
}
|