目录
(6)二叉树的基础遍历:前序、中序、后序遍历。 (深度优先思想)
一、二叉树。
(1)树的基本定义。
(2)树的相关术语。
(3)二叉树的基本定义。
(4)二叉树实现思想:
(1)插入思想:
(2)查询与删除思想:
(3)代码实现:
package 树.二叉树;
import 线性表.线性表_队列.Queue;
//注意:类定义的泛型只能继承类或接口,不能实现接口。(可以认为是继承实现接口的类或继承实现接口类的类)
//理解:实参输入的类应当实现了接口或是继承了实现了接口的类。(实现了接口的类本身就是相当于继承了该接口,其他类继承了此类也算是继承了该接口)
public class BinaryTree <Key extends Comparable<Key>,Value>{
//记录根节点
private Node root;
//记录树中元素的个数
private int N;
private class Node{
//存储键
public Key key;
//存储值
public Value value;
//记录左子结点
public Node left;
//记录右子结点
public Node right;
public Node (Key key,Value value,Node left,Node right){
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"key=" + key +
", value=" + value +
", left=" + left +
", right=" + right +
'}';
}
}
//获取树中元素的个数
public int size(){
return N;
}
//向树中添加元素key-value
public void put(Key key,Value value){
//从root根结点开始比较
root = put(root, key, value);
}
//向指定的树x中添加key-value,并返回添加元素后新的树
private Node put(Node x,Key key,Value value){
//如果x树为空
if (x == null){
N++;
return new Node(key,value,null,null);//返回一个新结点
}
//如果x树不为空,比较x结点的键和key的大小
int permission = key.compareTo(x.key);
if (permission > 0){
//如果大于,则继续找x结点的右子树
x.right = put(x.right,key,value);
}else if (permission < 0){
//如果key小于x结点的键,则继续找x结点的左子树
x.left = put(x.left,key,value);
}else {
//如果key等于x的结点的键,则替换x结点的值为value即可
x.value = value;
}
return x;
}
//查询树中指定key对应的value
public Value get(Key key){
return get(root,key);
}
//从指定的树x中,查询key对应的值
public Value get(Node x,Key key){
//x树为null
if (x == null) {
return null;
}
//x树不为null
int permission = key.compareTo(x.key);
//比较key和x结点的键的大小
if (permission > 0){
//如果大于,则继续找x结点的右子树
return get(x.right,key);
}else if (permission < 0){
//如果key小于x结点的键,则继续找x结点的左子树
return get(x.left,key);
}else {
//如果key等于x的结点的键,则找到了键为key的结点,只需要返回x结点的值即可
return x.value;
}
}
//删除树中key对应的value
public void delete(Key key){
root = delect(root,key);
}
//删除指定树x中的key对应的value,并返回删除后的新树
public Node delect(Node x,Key key){
//x树为null
if (x == null){
return null;
}
//x树不为null
int permission = key.compareTo(x.key);
if (permission > 0){
//如果大于,则继续找x结点的右子树
x.right = delect(x.right,key);
}else if (permission < 0){
//如果key小于x结点的键,则继续找x结点的左子树
x.left = delect(x.left,key);
}else {
//元素个数-1
N--;
//如果key等于x的结点的键,完成真正的删除结点动作,要删除的结点就是x
if (x.right == null){
return x.left;//如果右结点为空,那么不管左结点是否为空,都返回左结点(左结点为空,则返回null,相当于删除了,反之也代替了删除的那个)
}
if (x.left == null){
return x.right;
}
//找到右子树中最小的结点
Node minNode = x.right;
while (minNode.left != null){
minNode = minNode.left;
}
//删除右子树中最小的结点
Node n = x.right;
if (n.left == null){
//注意:这里如果用n(n只是引用对象,改变它只会是把n换一个引用对象,而之前的n存储的引用对象是不会有影响的)
//如果用n.left则是调用对象内部的变量,可以彻底改变值
//一个引用类型对象,想要改变其,只能调用内部变量或方法。
x.right = n.right;
}else {
while (n.left != null){
if (n.left.left != null){
n = n.left;
}else {
n.left = n.left.right;
//这里要跳出循环,否则可能还会执行(如果赋值不为null)
break;
}
}
}
//3.3让被删除结点的左子树称为最小结点minNode的左子树,让被删除结点的右子树称为最小结点minNode的右子树
minNode.left = x.left;
minNode.right = x.right;
//3.4让被删除结点的父节点指向最小结点minNode
x = minNode;
//个数-1
N--;
}
return x;
}
//查找整个树中的最小的键
public Key min(){
return N > 0 ? min(root).key : null;
}
//在指定树x中找出最小键所在的结点
private Node min(Node x){
//需要判断x还有没有左子结点,如果有,则继续向左找,如果没有,则x就是最小键所在的结点
if (x.left != null){
return min(x.left);
}else {
return x;
}
}
//在整个树中找到最大的键
public Key max(){
return N > 0 ? max(root).key : null;
}
//在指定的树x中,找到最大的键所在的结点
private Node max(Node x){
//判断x还有没有右子结点,如果有,则继续向右查找,如果没有,则x就是最大键所在的结点
if (x.right != null){
return max(root);
}else {
return x;
}
}
@Override
public String toString() {
return "BinaryTree{" +
"root=" + root +
", N=" + N +
'}';
}
//使用前序遍历获取整个树中所有的键
public Queue<Key> preErgodic(){
Queue<Key> keys = new Queue<>();
preErgodic(root,keys);
return keys;
}
//获取整个树的最大深度
public int maxDepth(){
return maxDepth(root);
}
//获取整个树的最大深度
private int maxDepth(Node x){
if (x == null){
return 0;
}
//x的最大深度
int max = 0;
//左子树的最大深度
int maxL = 0;
//右子树的最大深度
int maxR = 0;
//计算x结点左子树的最大深度
maxL = maxDepth(x.left);
//计算x结点右子树的最大深度
maxR = maxDepth(x.right);
//比较左子树最大深度和右子树最大深度,取最大值+1即可
max = maxL > maxR ? maxL+1 : maxR+1;
return max;
}
}
测试代码:
package 树.二叉树;
public class BinaryTreeTest {
public static void main(String[] args) {
//创建二叉树查找对象
BinaryTree<Integer,String> bt = new BinaryTree<>();
bt.put(28,"二十八");
System.out.println("1****************************************************************************************************");
bt.put(8,"八");
bt.put(7,"七");
bt.put(20,"二十");
// bt.put(15,"十五");
// bt.put(16,"十六");
System.out.println(bt);
bt.delete(28);
System.out.println(bt);
System.out.println("2*******************************************************************************************************");
}
}
(5)二叉树查询最小键与最大键。
(6)二叉树的基础遍历:前序、中序、后序遍历。 (深度优先思想)
(1)前序遍历。
//使用前序遍历获取整个树中所有的键
public Queue<Key> preErgodic(){
Queue<Key> keys = new Queue<>();
preErgodic(root,keys);
return keys;
}
//使用前序遍历获取指定树x的所有键,并放到keys队列中
private void preErgodic(Node x,Queue<Key> keys){
if (x == null){
return;
}
//1.把x结点的key放入到keys中
keys.enqueue(x.key);
//2.递归便利x结点的左子树
if (x.left != null){
preErgodic(x.left,keys);
}
//3.递归遍历x结点的右子树
if (x.right != null){
preErgodic(x.right,keys);
}
}
(2)中序遍历。
//使用中序遍历获取整个树中所有的键
public Queue<Key> midErgodic(){
Queue<Key> keys = new Queue<>();
midErgodic(root,keys);
return keys;
}
//使用中序遍历获取指定树x的所有键,并放到keys队列中
private void midErgodic(Node x,Queue<Key> keys){
if (x == null){
return;
}
//1.先递归,把左子树中的键放到keys中
midErgodic(x.left,keys);
//2.把当前结点x的键放到keys中
keys.enqueue(x.key);
//3.再递归,把右子树中的键放到keys中
midErgodic(x.right,keys);
}
(3)后序遍历。
//使用后续遍历,把整个树中所有的键返回
public Queue<Key> afterErgodic(){
Queue<Key> keys = new Queue<>();
afterErgodic(root,keys);
return keys;
}
//使用后续遍历,把指定树x中的所有键放入到keys中
private void afterErgodic(Node x,Queue<Key> keys){
if (x == null){
return;
}
//1.先递归,把左子树中的键放到keys中
afterErgodic(x.left,keys);
//2.再递归,把右子树中的键放到keys中
afterErgodic(x.right,keys);
//3.然后把当前结点x的键放到keys中
keys.enqueue(x.key);
}
前序、中序、后序遍历测试:
package 树.二叉树;
import 线性表.线性表_队列.Queue;
public class BinaryTreeErgodicTest {
public static void main(String[] args) {
//创建树对象
BinaryTree<String,String> bt = new BinaryTree<>();
//往树中添加数据
bt.put("E", "5");
bt.put("B", "2");
bt.put("G", "7");
bt.put("A", "1");
bt.put("D", "4");
bt.put("F", "6");
bt.put("H", "8");
bt.put("C", "3");
//前序遍历,不是按键或值大小遍历
Queue<String> keys1 = bt.preErgodic();
for (String key : keys1) {
String value = bt.get(key);
System.out.println(key+"----"+value);
}
//中序遍历,不是按键或值大小遍历
Queue<String> keys2 = bt.midErgodic();
for (String key : keys2) {
String value = bt.get(key);
System.out.println(key+"----"+value);
}
//中序遍历,不是按键或值大小遍历
Queue<String> keys3 = bt.afterErgodic();
for (String key : keys3) {
String value = bt.get(key);
System.out.println(key+"----"+value);
}
}
}
(7)二叉树的高级遍历:层序遍历。 (广度优先思想)
注意:这是个方法,在二叉树类中的一个层序方法。
//使用层序遍历,获取整个树中所有的键
public Queue<Key> LayerErgodic(){
//定义两个队列,分别存储树中的键和树中的结点
Queue<Key> keys = new Queue<>();
Queue<Node> nodes = new Queue<>();
//默认,往队列中放入根结点
if (root != null){
nodes.enqueue(root);
while (!nodes.isEmpty()){
//1.从对列中弹出一个结点,如果有,则放入到keys中
Node choose = nodes.dequeue();//因为出队列其实是在删除元素,先进先出
keys.enqueue(choose.key);
//2.判断当前结点还有没有左子结点,如果有,则放入到nodes中
if (choose.left != null){
nodes.enqueue(choose.left);
}
//3.判断当前结点还有没有右子结点,如果有,则放入到nodes中
if (choose.right != null){
nodes.enqueue(choose.right);
}
}
}
return keys;
}
层序遍历测试:
package 树.二叉树;
import 线性表.线性表_队列.Queue;
public class BinaryTreeErgodicTest {
public static void main(String[] args) {
//创建树对象
BinaryTree<String,String> bt = new BinaryTree<>();
//往树中添加数据
bt.put("E", "5");
bt.put("B", "2");
bt.put("G", "7");
bt.put("A", "1");
bt.put("D", "4");
bt.put("F", "6");
bt.put("H", "8");
bt.put("C", "3");
//层序遍历
Queue<String> keys4 = bt.LayerErgodic();
for (String key : keys4) {
String value = bt.get(key);
System.out.println(key+"----"+value);
}
}
}
(8)二叉树的最大深度。
//获取整个树的最大深度
private int maxDepth(Node x){
if (x == null){
return 0;
}
//x的最大深度
int max = 0;
//左子树的最大深度
int maxL = 0;
//右子树的最大深度
int maxR = 0;
//计算x结点左子树的最大深度
maxL = maxDepth(x.left);
//计算x结点右子树的最大深度
maxR = maxDepth(x.right);
//比较左子树最大深度和右子树最大深度,取最大值+1即可
max = maxL > maxR ? maxL+1 : maxR+1;
return max;
}
代码测试:
package 树.二叉树;
public class BinaryTreeMaxDepthTest {
public static void main(String[] args) {
//创建树对象
BinaryTree<String,String> bt = new BinaryTree<>();
//往树中添加数据
bt.put("E", "5");
bt.put("B", "2");
bt.put("G", "7");
bt.put("A", "1");
bt.put("D", "4");
bt.put("F", "6");
bt.put("H", "8");
bt.put("C", "3");
//获取整个树的最大深度
int maxDepth = bt.maxDepth();
System.out.println(maxDepth);
}
}
(9)折纸问题
package 树.二叉树;
import 线性表.线性表_队列.Queue;
public class PagerFoldingTest {
public static void main(String[] args) {
//模拟折纸过程,产生树
Node<String> tree = createTree(3);
//遍历树,打印每个结点
printTree(tree);
}
//通过摸底对折N次纸,产生树(个人理解:通过深度,制造一棵树)
public static Node<String> createTree(int N){
//定义根结点
Node<String> root = null;
for (int i = 0; i < N; i++) {
//1.当前是第一次对折
if (i == 0){
root = new Node<>("down",null,null);
continue;
}
//2.当前不是第一次对折
//定义一个辅助队列,通过层序遍历的思想,找到叶子结点,叶子结点添加子结点
Queue<Node<String>> queue = new Queue<>();
queue.enqueue(root);
//循环遍历队列
while (!queue.isEmpty()){
//从队列中弹出一个结点
Node<String> tmp = queue.dequeue();
//如果有左子结点,则把左子结点放入到队列中
if (tmp.left != null){
queue.enqueue(tmp.left);
}
//如果有右子结点,则把右子结点放入队列中
if (tmp.right != null){
queue.enqueue(tmp.right);
}
//如果同时没有左子结点和右子结点,那么证明该结点是叶子结点,只需要给该结点添加左子结点和右子结点即可
if (tmp.left == null && tmp.right == null){
tmp.left = new Node("down",null,null);
tmp.right = new Node("up",null,null);
}
}
}
return root;
}
//打印树中每一个结点到控制台
public static void printTree(Node<String> root){
//需要使用中序遍历完成
if (root == null){
return;
}
//1.打印左子树的每个结点
if (root.left != null){
printTree(root.left);
}
//2.打印当前结点
System.out.print(root.item+" ");
//3.打印右子树的每个结点
if (root.right != null){
printTree(root.right);
}
}
//结点类
private static class Node<T>{
public T item;//存储元素
public Node left;
public Node right;
public Node(T item,Node left,Node right){
this.item = item;
this.left = left;
this.right = right;
}
}
}