树
作业:
1. 二分搜索树一些方法的非递归实现
2**. 层序遍历,打印出一个二叉树
将数据使用数结构储存后,出奇的高效
二分搜索树
平衡二叉树 AVL 红黑树
堆 并查集
线段树 Trie(字典树,前缀树)
二叉树
二叉树具有唯一根节点
class Node {
E e;
Node left;
Node right
}
二叉树每个节点最多有两个孩子
二叉树节点最多只有一个父亲
二叉树具有天然递归结构
二叉树不一定是‘满’的
二分搜索树
二分搜索树是一个二叉树
二分搜索树的每个节点的值:每个节点
* 大于其左子树的所有节点的值
* 限于其右子数所有节点的值
二分搜索树的每个子数也是一个二分搜索树
存储的元素必须有可比较性
这里的二分搜索树不包含重复的元素
如果想包含重复的元素,只需要修改定义就可以了
* 左子树小于等于节点;或者右子数大于等于节点
前序遍历:递归先访问节点,然后访问左子树,然后访问右子树
中序遍历:递归先访问左子树,然后访问节点,然后访问右子树
访问的结果是从小到大的排序结果!!!顺序的
后续遍历:递归先访问左子树,然后访问右子树,然后访问节点
应用场景:为二分搜索树释放内存空间
反序遍历:我TM自己创的!,右子,节点,左子,倒叙访问
二分搜索树基本功能实现
package bannerySearchTree;
import datastructure.array.ArrayStack;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class BST> {
/*Comparable接口代表这个类型必须具有可比较性*/
private class Node{
public E e;
public Node left, right;
public Node(E e){
this.e = e;
this.left = null;
this.right = null;
}
}
private Node root;
private int size;
public BST(){
root = null;
size = 0;
}
public int size(){
return this.size;
}
public boolean isEmpty(){
return this.size == 0;
}
/*
// 添加元素,根节点特殊处理
public void add(E e){
if(size == 0) {
this.root = new Node(e);
size++;
}
else
this.add(root, e);
}
// 向以node为根的二分搜索树中插入元素e,递归算法
private void add(Node node, E e){
if(e.equals(node.e))
return;
else if(e.compareTo(node.e) < 0 && node.left == null){
node.left = new Node(e);
size ++;
return;
}
else if(e.compareTo(node.e) > 0 && node.right == null){
node.right = new Node(e);
size ++;
return;
}
if(e.compareTo(node.e) < 0){
add(node.left, e);
} else {
add(node.right, e);
}
}
*/
// 更加简洁的写法,因为null也可以看做是一个二分搜索树的节点
public void add(E e){
root = add(root, e);
}
/*将待插入的节点与当前节点比较,如果当前节点为终止条件,则直接赋值
* 如果当前节点不为空,与当前节点比较,选择对应的子树进行递归,最终将当前节点返回并挂载
* 到父节点对应的分支上。由于要满足将最终的插入节点挂载到父节点上,不得不对所有深度的递归
* 都重新进行挂载*/
private Node add(Node node, E e){
if(node == null){
size ++;
return new Node(e);
}
if(e.compareTo(node.e) < 0){
node.left = add(node.left, e);
}
else if(e.compareTo(node.e) > 0){
node.right = add(node.right, e);
}
return node;
}
// 查看二分搜索树中是否包含元素e
public boolean contains(E e){
return contains(root, e);
}
// 以node为根的二分搜索树中是否包含元素e,递归算法
private boolean contains(Node node, E e){
if(node.e == null)
return false;
if(e.compareTo(node.e) == 0)
return true;
else if(e.compareTo(node.e) < 0)
return contains(node.left, e);
else
return contains(node.right, e);
}
// 二分搜索树的前序遍历
public void preOrder(){
this.preOrder(root);
}
// 前序遍历,以node为根的二分搜索树,递归算法
private void preOrder(Node node){
if(node != null){
System.out.print(node.e);
System.out.print(' ');
preOrder(node.left);
preOrder(node.right);
}
}
// 打印带深度的前序遍历
@Override
public String toString(){
StringBuilder res = new StringBuilder();
this.generateBTSString(root, 0, res);
return res.toString();
}
private void generateBTSString(Node node, int depth, StringBuilder res){
if(node != null){
res.append(this.generateDepthGangGang(depth) + node.e + '\n');
generateBTSString(node.left, depth + 1, res);
generateBTSString(node.right, depth + 1, res);
} else
res.append(this.generateDepthGangGang(depth) + "NULL\n");
}
private String generateDepthGangGang(int depth){
StringBuilder res = new StringBuilder();
for(int i = 0; i < depth; i ++){
res.append("- ");
}
return res.toString();
}
// 中序遍历,先遍历左子树,然后节点,然后右子树。遍历的顺序就是排序的顺序
public void inOrder(){
this.inOrder(root);
}
private void inOrder(Node node){
if(node == null)
return;
inOrder(node.left);
System.out.print(node.e);
System.out.print(' ');
inOrder(node.right);
}
// 反中序遍历,我TM自己创的!,右子,节点,左子
public void reverseInOrder(){
this.reverseInOrder(root);
}
private void reverseInOrder(Node node){
if(node == null)
return;
reverseInOrder(node.right);
System.out.print(node.e);
System.out.print(' ');
reverseInOrder(node.left);
}
// 后续遍历
public void postOrder(){
this.postOrder(root);
}
private void postOrder(Node node){
if(node == null)
return;
postOrder(node.left);
postOrder(node.right);
System.out.print(node.e);
System.out.print(' ');
}
/*二分搜索树的层序遍历
* 利用队列,广度优先遍历!
* 现将节点入队,
* 出队,操作之后,将左右子节点入队
* (每深入一层,入队的规模平均呈2^n增加)
* 如果不明白画个图就一目了然了
* 应用:跟快的找到问题的解答
* 常用于算法设计中-最短路径
* 图中的深度优先于广度优先*/
public void levelOrder(){
// java自带的Queue是一个接口
// 必须指定使用队列底层使用的类型(Array or LinkedList)
Queue q = new LinkedList<>();
q.add(root);
while(!q.isEmpty()){
Node cur = q.remove();
System.out.print(cur.e);
System.out.print(' ');
if(cur.left != null)
q.add(cur.left);
if(cur.right != null)
q.add(cur.right);
}
}
// 非递归实现前序遍历
/*先将节点入栈,再出栈打印,在将右子树节点入栈(如果存在),再将左子树节点入栈(如果存在),再出栈*/
public void preOrderNC(){
ArrayStack stack = new ArrayStack<>();
Node cur = root;
stack.push(cur);
while(!stack.isEmpty()){
// 先出栈
cur = stack.pop();
System.out.print(cur.e);
System.out.print(' ');
if(cur.right != null){
stack.push(cur.right);
}
if(cur.left != null){
stack.push(cur.left);
}
}
}
// 非递归实现中序遍历
/**
* 使用栈
* 1. 不断地将当前的左子树压如栈中,直到遇见null就不压入
* 2. 从栈中取出一个节点,打印,令当前节点等于右子树节点,重复步骤1.
* 3. 直到栈为空,当前的节点为null
* (注意:每次操作了的节点都出栈了,不会存在重复遍历左子树的情况)*/
public void inOrderNC(){
Stack stack = new Stack<>();
Node node = root;
while(!stack.isEmpty() || node != null){
if(node != null){
stack.push(node);
node = node.left;
} else {
node = stack.pop();
System.out.println(node.e);
node = node.right;
}
}
}
// 非递归实现后续遍历
/** 递归的思想,类似于汉诺塔问题,
* 1. 申请一个栈1,将节点压入栈1中
* 2. 从栈1中取出一个节点放入栈2,将左孩子,右孩子依次压入栈中
* 3. 重复2.步骤,直到栈1中为空,让后从栈2中依次取出节点并打印,就是后续遍历的顺序
* 注意:每颗子树的头结点都是从栈1中出来,放入栈2中,然后对应的右子树节点,左子树节点从栈
* 1出来,以中右左的方式存入的栈2,最后打印的顺序又是反着的,左右中,这种思路与解决汉诺塔递归的思想
* 如出一辙*/
public void postOrderNC(){
Stack tep1 = new Stack<>();
Stack tep2 = new Stack<>();
Node node = root;
tep1.push(node);
while(!tep1.isEmpty()){
node = tep1.pop();
tep2.push(node);
if(node.left != null)
tep1.push(node.left);
if(node.right != null)
tep1.push(node.right);
}
while(!tep2.isEmpty()){
System.out.print(tep2.pop().e);
System.out.print(' ');
}
}
/*找到数中的最小值*/
public E minimum(){
if(this.isEmpty())
throw new IllegalArgumentException("Find failed, the tree is empty");
return minimum(root).e;
}
private Node minimum(Node node){
if(node.left == null)
return node;
else
return minimum(node.left);
}
/*找到数中的最大值*/
public E maxmum(){
if(this.isEmpty())
throw new IllegalArgumentException("Find failed, the tree is empty");
return maxmum(root).e;
}
private Node maxmum(Node node){
if(node.right == null)
return node;
else
return maxmum(node.right);
}
/**删除二叉树中的最大值与最小值,
最小值在树的最左边,有两种情况,
没有子树,有右子树(最大值以此类推)
* 作业:使用非递归的方式实现逻辑*/
/* 删除最小值 */
public E removeMin(){
E ret = this.minimum();
root = removeMin(root);
return ret;
}
/** 每深入一次递归,就将当前节点返回给父亲。从而不用保留父父节点进行赋值这种!
* 为了删除一个节点,代价就是每次递归,都将子树挂载给父亲*/
private Node removeMin(Node node){
if(node.left == null){
size--;
// if(node.right != null)
// return node.right;
// return null;
// 另一种方式,手动回收被删除的节点
// 临时保存右子树
Node rightNode = node.right;
// 将被删除的节点与子树脱离关系,这里只考虑右子树就可以了
node.right = null;
// 返回原来的右子树,如果没有右子树,返回为null也是符合逻辑的
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
/*删除最大值*/
public E removeMax(){
E res = maxmum();
root = removeMax(root);
return res;
}
private Node removeMax(Node node){
if(node.right == null) {
size--;
// if (node.left != null)
// return node.left;
// return null;
Node leftNode = node.left;
node.left = null;
return leftNode;
}
node.right = removeMax(node.right);
return node;
}
/**删除任意值
* 一:没有子节点,将当前节点删除,返回nul
* 二:有一个左子树或者右子树,删除该节点后将子树节点返回
* 三:同时存在两个子树,找到右子树找离当前值最近的那个大于节点的值,
* 在右子树的最左边,将此节点替换为当前的节点待返回,删除找到的节点*/
public void remove(E e){
root = remove(root, e);
}
private Node remove(Node node, E e){
if(e.compareTo(node.e) < 0){
node.left = remove(node.left, e);
return node;
} else if(e.compareTo(node.e) > 0) {
node.right = remove(node.right, e);
return node;
} else {
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
} else if(node.right == null){
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
} else {
/**算法 后继法:
* 找到右子树最小值赋给临时节点
* 将待删除节点的左子树赋给临时节点对应的子树
* 将待删除节点的右子树删除最小值并赋值给临时节点
* 返回临时节点
* @@ 直接调用successor = removeMin(node.right)这种算法要考虑很多情况
* @@ 而先找到右子树最小的,再删除该节点会自动处理那些情况*/
Node successor = this.minimum(node.right);
/*调用了removeMin size自行--*/
successor.right = removeMin(node.right);
successor.left = node.left;
node.left = node.right = null;
/**前驱法
Node successor = this.maxmum(node.left);
successor.left = this.removeMax(node.left);
successor.right = node.right;
node.left = node.right = null;*/
return successor;
}
}
}
/**关于二分搜索树的更多话题+ 作业(没有@的为作业)
* OK打印一棵树
* OK非递归实现中序遍历
* OK非递归实现反序遍历
* OK某个值(可以不包含在节点中)的floor 比此值小的最大的元素
* OK某个值(可以不包含在节点中)的ceil 比此值大的最小的那个元素
*
* OK维护size的二分搜索树,在节点中保存该节点包含的总结点数,包含自己,
* rank: 这个元素在数中排名第几?(推荐方式:)
* select: 排名是第几的元素是谁?(推荐方式:在节点中保存该节点包含的总结点数,包含自己)
*
* @OK维护depth的二分搜索树,每个节点保存自己的深度,根节点的深度为0
* @支持重复元素的二分搜索树(推荐方式,在内部增加一个成员变量count记录个数)
* @leetCode中关于数的习题(将所有课程学完了,没事儿的时候可以研究leetCode上面的问题)
* */
/**某个值(可以不包含在节点中)的floor 比此值小的最大的元素*/
public E floor(E e){
return floor(root, e, e);
}
// temp 保存遍历过的比次数小的值,初试值为自己,到终止条件可以根据此判断是否遍历过小于自己的值
private E floor(Node node, E e, E temp){
/**注意:
* 当遍历当前节点的时候,如果当前节点的值大于等于自己,需要在左子树中寻找合适的节点,
* 如果左子树存在,就继续遍历,如果不存在左子树,判断之前是否遍历过比自己小的节点,有则返回,
* 没有没有,说明此树中不存在这样的floor,抛出异常!
*
* 当遍历当前节点,当前节点的值小于自己,需要在当前节点的右子树去寻找答案,同时将temp置为当前节点
* (当前节点可能就是那个floor),如果存在右子树,继续遍历,如果不存在,则返回当前temp;
* */
if(e.compareTo(node.e) <= 0){
if(node.left != null){
return floor(node.left, e, temp);
} else {
if(temp.compareTo(e) < 0)
return temp;
else
throw new IllegalArgumentException("Seek failed, no such floor!");
}
} else {
temp = node.e;
if(node.right != null){
return floor(node.right, e, temp);
} else {
return temp;
}
}
}
/**某个值(可以不包含在节点中)的ceil 比此值大的最小的那个元素
* 原理与floor相反*/
public E ceil(E e){
return ceil(root, e, e);
}
private E ceil(Node node, E e, E temp){
if(e.compareTo(node.e) < 0){
temp = node.e;
if(node.left != null){
return ceil(node.left, e, temp);
} else {
return temp;
}
} else {
if(node.right != null){
return ceil(node.right , e , temp);
} else {
if(temp.compareTo(e) > 0)
return temp;
else
throw new IllegalArgumentException("Seek failed, no such ceil");
}
}
}
}
打印一颗二分搜索树
package bannerySearchTree;
import java.util.LinkedList;
import java.util.Queue;
/**打印一棵树,为了满足打印需求而设计的树,训练而已!提供的功能只与打印相关。
* 假设所有节点的数都小于10,大于0,整型;
* 如果数中的位数不确定,打印十分困难,得找到最大数的位数,
* 然后以此宽度为基数进行设计....总之,先做最简单的!!!
* 以所有节点的数为一个一位数,最深度n(深度从0开始)的一层之间的空隙为基准,
* 按照每一深度最左边的空格数为2^(n-1)-1, 每层元素与元素之间的空格数为(2^(n-1)-1)*2+1,
* 每层元素的个数为2^n(遇到特殊节点的值打印一个空白)进行循环打印*/
public class BSTPrint>{
private class Node {
E e;
Node left, right;
int deep; // 记录深度,从0开始
boolean printAble; // 记录当前节点是否允许打印,默认允许打印
public Node(E e){
this.e = e;
this.left = this.right = null;
this.printAble = true;
}
}
//
public Node root; // 根节点
public int deepest; // 记录最深的深度
public int size; // 记录数据个数
public BSTPrint(){
this.root = null;
this.deepest = 0;
this.size = 0;
}
public void add(E e){
root = this.add(root, -1, e);
}
private Node add(Node node, int deep, E e){
if(node == null){
Node newNode = new Node(e);
newNode.deep = deep + 1;
this.deepest = newNode.deep > this.deepest? newNode.deep: this.deepest;
size ++;
return newNode;
}
if(e.compareTo(node.e) < 0){
node.left = add(node.left, node.deep, e);
return node;
} else {
node.right = add(node.right, node.deep, e);
return node;
}
}
/**打印节点*/
public void printTree(){
/**将树补全, 在父节点操作子节点*/
if(root != null && this.deepest != 0)
addFullTree(root);
/** 对树进行层序遍历,是完整二叉树*/
Queue q = new LinkedList<>();
q.add(root);
int currentLayer = root.deep - 1; // 保存当前所打印的深度,初始化为一个不存在的层
char line = '|';
String nextLine = ""; // 保存待打印的值
while (!q.isEmpty()){
Node node = q.remove();
if(node.deep != currentLayer){
// 转换到下一行
currentLayer = node.deep;
// 打印上次保存的一行信息
System.out.println(nextLine);
nextLine = "\n";
// 打印每一行前面的空白并将空白赋值给缓存
String leftSpace = "";
for(int i = 0; i < Math.pow(2, this.deepest - node.deep) - 1; i++)
leftSpace += ' ';
System.out.print(leftSpace);
nextLine += leftSpace;
// 判断是否能够打印,打印连接线并将该值加入缓存
if(node.printAble){
System.out.print(line);
nextLine += node.e;
} else {
System.out.print(' ');
nextLine += ' ';
}
} else {
// 打印每个元素之间的间隔空格
String betweenSpace = "";
for(int i = 0; i < (Math.pow(2, this.deepest - node.deep) - 1) * 2 + 1; i++)
betweenSpace += ' ';
System.out.print(betweenSpace);
nextLine += betweenSpace;
// 判断是否能够打印,打印连接线并将该值加入缓存
if(node.printAble){
System.out.print(line);
nextLine += node.e;
} else {
System.out.print(' ');
nextLine += ' ';
}
}
if(node.left != null)
q.add(node.left);
if(node.right != null)
q.add(node.right);
}
System.out.println(nextLine);
}
private void addFullTree(Node pre){
if(pre.deep + 1 <= this.deepest && pre.left == null){
pre.left = new Node(pre.e);
pre.left.deep = pre.deep + 1;
pre.left.printAble = false;
size ++;
}
if(pre.deep + 1 <= this.deepest && pre.right == null){
pre.right = new Node(pre.e);
pre.right.deep = pre.deep + 1;
pre.right.printAble = false;
size ++;
}
if(pre.deep < this.deepest) {
addFullTree(pre.left);
addFullTree(pre.right);
}
}
public static void main(String[] args) {
BSTPrint bst = new BSTPrint<>();
int[] numbers = {8, 10, 5, 4, 12, 7, 9, 13, 6, 3, 11};
for (int i = 0; i < numbers.length; i++) {
bst.add(numbers[i]);
}
bst.printTree();
}
}
带有size的二分搜索树,并且可以查询排行的元素与元素的排行
package bannerySearchTree;
public class BSTSize> {
/**
维护size的二分搜索树,在节点中保存该节点包含的总结点数,包含自己,
rank: 这个元素在数中排名第几?(推荐方式:)
select: 排名是第几的元素是谁?(推荐方式:在节点中保存该节点包含的总结点数,包含自己)
只包含添加功能,目前不包含删除元素的功能(如果做删除功能,需要先做出删除最大值,最小值,然后再做
删除任意值复用删除最小值的方法<后继>>)*/
private class Node{
E e;
Node left, right;
int size;
public Node(E e){
this.e = e;
this.left = this.right = null;
this.size = 1;
}
}
private Node root;
private int totalSize;
public BSTSize(){
this.root = null;
this.totalSize = 0;
}
/**添加一个元素,添加成功之后,将所有的父节点自增1
* 注意:目前不支持添加已经存在的元素*/
public void add(E e){
root = this.add(root, e);
}
private Node add(Node node, E e){
if(node == null){
this.totalSize ++;
return new Node(e);
}
if(e.compareTo(node.e) < 0){
node.left = this.add(node.left, e);
node.size ++;
return node;
} else if(e.compareTo(node.e) > 0){
node.right = this.add(node.right, e);
node.size ++;
return node;
} else {
throw new IllegalArgumentException("Add failed, can not add same element");
}
}
/**rank: 这个元素在数中排名第几
* 算法简介:
* 从根节点开始,与当前节点比较,如果当前节点与自己相等,返回减去右子树个数的当前size
* 如果当前节点大于自己,遍历左子节点,返回遍历的左节点的返回值
* 如果当前节点小于自己,遍历右子节点,返回当 前节点减去右节点的size在加上遍历的右子树的返回值*/
public int rank(E e){
return this.rank(root, e);
}
private int rank(Node node, E e){
if(node == null){
throw new IllegalArgumentException("Seek failed, no such element");
} else
if(e.compareTo(node.e) == 0){
if(node.right == null){
return node.size;
} else {
return node.size - node.right.size;
}
} else if(e.compareTo(node.e) < 0){
return this.rank(node.left, e);
} else {
if(node.right != null)
return node.size - node.right.size + this.rank(node.right, e);
else
throw new IllegalArgumentException("Seek failed, no such element");
}
}
/**select: 排名是第几的元素是谁?*/
public E select(int n){
if(n < 1)
throw new IllegalArgumentException("Fained failed, order is too small!");
if(n > this.totalSize)
throw new IllegalArgumentException("Fained failed, order is too large!");
return select(root, 0, n);
}
/**node为当前节点, position遍历过的节点的位置,初始化为0,n为需要寻找的位置
* 调用递归之前已经处理了异常值,在这里面就可以不用处理了!*/
private E select(Node node, int position, int n){
/**计算当前节点所处的位置*/
int tempPosition = position;
if(node.right != null)
tempPosition = position + node.size - node.right.size;
else
tempPosition = position + node.size;
/**判断当前的位置并分配操作!*/
if(tempPosition == n)
return node.e;
else if(tempPosition < n)
return select(node.right, tempPosition, n);
else
return select(node.left, position, n);
}
public static void main(String[] args){
BSTSize bst = new BSTSize<>();
int[] numbers = {8, 10, 5, 4, 12, 7, 9, 13, 6, 3, 11};
for(int i = 0; i < numbers.length; i ++){
bst.add(numbers[i]);
}
System.out.println(bst.rank(7));
System.out.println(bst.select(4));
}
}