文章目录

  • 一.集合
  • 1. 思维导图
  • 2. List
  • 2.1 ArrayList
  • 2.2 Vector(数组实现,线程同步)
  • 2.3 Linklist
  • 3. Set
  • 3.1 HashSet
  • 3.2 TreeSet
  • 3.3 LinkHashSet
  • 4. Map
  • 4.1 HashMap
  • 4.2 ConcurrentHashMap
  • 4.3 Hashtable
  • 4.4 TreeMap
  • LinkHashMap(记录插入顺序)
  • 二.栈和队列
  • 1 栈
  • 1.1 栈的入栈操作
  • 1.2 栈的出栈操作
  • 1.3 如何用两个队列实现栈
  • 2 队列
  • 2.1 队列的入队操作
  • 2.2 队列的出队操作
  • 2.3 如何利用两个栈实现队列
  • 三. 链表
  • 1 链表的结构
  • 2 链表插入
  • 3 查找节点
  • 4 移除节点
  • 5 反转链表
  • 5.1 三指针法
  • 5.2 递归法
  • 6 判断链表中是否有环
  • 6.1 利用hashtable
  • 6.2 快慢指针法
  • 四.树
  • 1 树的遍历
  • 1.1 深度优先
  • 1.1.1 先序
  • 1.1.2 中序
  • 1.1.3 后序
  • 1.2 广度优先
  • 2 手写一个二叉搜索树
  • 2.1 树的结构
  • 2.2 数的添加方法
  • 2.3 树的删除
  • 2.4 数的三种遍历(前序,中序和后序)
  • 2.5 反转二叉树


一.集合

1. 思维导图

iOS数据结构面试 数据结构面试宝典_java

2. List

在java中List是一种非常常见的数据类型,List是一种有序的Collection,实现类有三种,分别是ArrayList,Vector,LinkedList。

2.1 ArrayList

ArrayList是最常用的list实现类,内部使用数组实现的,因为内部是用数组实现的,所以可以对元素快速的访问,但由于插入和移除需要对数组进行复制,移动,代价比较高。当数组大小不够的时候,就要将目前的数组复制到新的存储空间中,因此比较适合随机的查找和遍历,不适合插入和删除,并且是线程不安全的。

2.2 Vector(数组实现,线程同步)

Vector与Arraylist一样,底层都是由数组实现的,不同的是Vector是线程安全的,即某一时刻只有一个线程可以操作,但实现同步需要很高的花费,因此访问上来说比Arraylist慢。

2.3 Linklist

Linklist底层是使用链表实现的,很适合数据的删除与插入,随机访问和便利比较慢,。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

3. Set

Set注重独一无二的特性,该体系集合适用于存储无序,值不能重复的元素,对象是否相等是由hashcode值判断的,所以如果想让两个不同的对象视为相等的,就得重写Object的equlas和hashcode方法。

3.1 HashSet

哈希表里面存放的是哈希值,HashSet存储元素的顺序不是按照存储数据来排列的,而是按照哈希值来存的,所以取值也是按照哈希值来取的,元素的哈希值是通过元素hashcode方法来获取,HashSet 首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals 方法 如果 equls 结果为 true ,HashSet 就视为同一个元素。如果 equals 为 false 就不是同一个元素。哈希值相同 equals 为 false 的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。如图 1 表示 hashCode 值不相同的情况;图 2 表示 hashCode 值相同,但 equals 不相同的情况。

iOS数据结构面试 数据结构面试宝典_数据结构_02

HashSet 通过 hashCode 值来确定元素在内存中的位置。一个 hashCode 位置上可以存放多个元素。

3.2 TreeSet

TreeSet是使用二叉树的原理对新 add()的对象按照指定的顺序排序(升序、降序),每增加一个对象都会进行排序,将对象插入的二叉树指定的位置。
Integer 和 String 对象都可以进行默认的 TreeSet 排序,而自定义类的对象是不可以的,自己定义的类必须实现 Comparable 接口,并且覆写相应的 compareTo()函数,才可以正常使用。
在覆写 compare()函数时,要返回相应的值才能使 TreeSet 按照一定的规则来排序
比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。

3.3 LinkHashSet

对于LinkedHashSet而言,它继承于HashSet、又基于 LinkedHashMap 来实现的。LinkedHashSet 底层使用 LinkedHashMap 来保存所有元素,它继承与 HashSet,其所有的方法操作上又与 HashSet 相同因此 LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个LinkedHashMap来实现,在相关操作上与父类HashSet的操作相同,直接调用父类 HashSet的方法即可。

4. Map
4.1 HashMap

HashMap根据键的hashcode值存储数据,可以通过键直接定位到它的值,所以具有很快的查询速度。但便利顺序是不确定的,HashMap最多只允许一条记录的值为null,允许多条记录的值为null,HashMap非线程安全,如果需要满足线程安全,可以用 Collections 的synchronizedMap 方法使HashMap 具有线程安全的能力,或者使用ConcurrentHashMap。我们用下面这张图来介绍HashMap 的结构。

4.1.1 java7实现

iOS数据结构面试 数据结构面试宝典_java_03

大方向上,HashMap 里面是一个数组,然后数组中每个元素是一个单向链表。上图中,每个带颜色的实体是嵌套类 Entry 的实例,Entry 包含四个属性:key, value, hash 值和用于单向链表的 next。

  1. capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
  2. loadFactor:负载因子,默认为 0.75。
  3. threshold:扩容的阈值,等于 capacity * loadFactor

4.1.2 java8实现

Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。根据 Java7 HashMap 的介绍,我们知道,查找的时候,根据 hash 值我们能够快速定位到数组的具体下标,但是之后的话,需要顺着链表一个个比较下去才能找到我们需要的,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在 Java8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

iOS数据结构面试 数据结构面试宝典_面试_04

4.2 ConcurrentHashMap

ConcurrentHashMap与hashmap差不多,但是ConcurrentHashMap支持多线程。整个ConcurrentHashMap是由多个Segment组成的,简单理解整个ConcurrentHashMap就是一个Segment数组,Segment通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

4.2.1 java中ConcurrentHashMap的结构

iOS数据结构面试 数据结构面试宝典_数据结构_05

Java8 对 ConcurrentHashMap 进行了比较大的改动,Java8 也引入了红黑树。并且从ReentrantLock+Segment+HashEntry改成了synchronized+CAS+HashEntry+红黑树。

4.3 Hashtable

hashtable很多常用功能和hashmap一致,不同的是它承Dictionary类,并且它是线程安全的,任意时间只有一个线程能写hashtable,并发行不如ConcurrentHashMap好,因为ConcurrentHashMap加入了分段锁,所以在新代码中不建议用hashtable,在线程不安全的情况下hashmap更好,在线程安全的情况下又不如ConcurrentHashMap。

4.4 TreeMap

TreeMap实现了SortedMap接口,能够按照键进行排序,默认是按照键的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。如果使用排序的映射,建议使用 TreeMap。在使用TreeMap时,key必须实现Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException 类型的异常。

LinkHashMap(记录插入顺序)

LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

二.栈和队列

1 栈

栈是一个先入后出的数据结构,栈可以用linklist或者数组来实现

1.1 栈的入栈操作

入栈就是往list的第一个地方插入元素

public void push(T e) {
        list.addFirst(e);
//        list.push(e);
    }
1.2 栈的出栈操作

出栈就是从list的最后一个值取出

public T pop() {
        //list.removeFirst();
        return list.pop();
    }
1.3 如何用两个队列实现栈

栈是先入后出结构,队列是先入先出结构,利用两个队列,可以入栈操作和出栈操作都在同一个队列中实现,然后另外一个队列只是一个中转站,每次入栈都是存入队列1中,而出栈则是把队列1中除最后一个元素外都放到队列2中,在出栈队列1中剩余的那个元素,在把队列2中的元素放回到队列1中。
入栈

public void push(int item) throws InterruptedException {
        queue1.put(item);
    }

出栈

public Object pop() throws InterruptedException {
        while (queue1.size()!=1){
            queue2.put(queue1.remove());
        }
        int res = queue1.remove();
        while (queue2.size()!=0){
            queue1.put(queue2.remove());
        }
        return res;
    }

2 队列

队列是一个先入先出的数据结构,队列可以用linklist或者数组来实现

2.1 队列的入队操作

入队就是往list中添加元素

public void enqueue(T e){
        list.add(e);
    }
2.2 队列的出队操作

出队就是从list中取出元素

public T dequeue(){
        return list.remove();
    }
2.3 如何利用两个栈实现队列

栈是先入后出结构,那么从第一个栈入,然后把数据导入到第二个栈中,然后从第二个栈中出,就实现了队列
入栈

public void enqueue(T e) {
        s1.push(e);
    }

出栈,先判断栈2中是否有元素,如果有那么就直接从栈2中取值,如果没有,需要先把栈1中的数据同步到栈2中

public T dequeue() {
        if(s2.size() > 0) {
            return s2.pop();
        }

        while(s1.size() > 0) {
            s2.push(s1.pop());
        }
        return s2.pop();
    }

三. 链表

1 链表的结构

一个链表节点对象需要有当前节点的值,和当前节点的下一个节点的指针两个信息,如果是双向链表还需要一个上一个节点的指针信息

static class Node<T> {
        Node<T> next = null;
        T data;
        public Node(T data){
            this.data = data;
        }
    }

2 链表插入

利用头插法,因为尾插法对于单向链表来说要遍历到最后一个节点,时间复杂度比较高,
插入节点的下一个节点 -> 头结点
头结点 -> 插入节点

public void insert(T data) {
        var node = new Node<>(data);
        node.next = head;
        head = node;

    }

3 查找节点

遍历整个链表,直到找到节点

public Node<T> find(Predicate<T> predicate) {
        var p = head;
        while(p != null) {
            if(predicate.test(p.data)) {
                return p;
            }
            p = p.next;
        }
        return null;

    }

4 移除节点

利用双指针,一个指向当前节点,一个指向当前节点的下一个节点,移除节点就是把移除节点的前一个节点 -> 移除节点的后一个节点

public void remove(Node<T> node){
        if(head == null) {
            return;
        }

        if(head == node) {
            head = head.next;
            return;
        }

        var slow = head;
        var fast = head.next;

        while(fast != node && fast != null) {
            slow = fast;
            fast = fast.next;
        }
        if(fast != null) {
            slow.next = fast.next;
//            fast.data = null;
        }

    }

5 反转链表

5.1 三指针法

用三个指针,一个为当前节点,一个是当前节点的上一个节点,一个是当前节点的下一个节点。每次换的时候就是
当前节点的下一个节点 -> 当前节点的上一个节点
把当前节点的上一个节点 -> 当前节点
当前节点 -> 当前节点的下一个节点

public void reverse(){
        // prev | current | next

        Node<T> prev = null;
        var current = head;
        Node<T> next;
        while(current != null) {
            next = current.next;
            current.next = prev;
            prev = current;
            current = next;
        }
        head = prev;

    }
5.2 递归法

可以把整个链表看成是头结点和其他节点的关系,每次递归的意味着其他节点一个都反转完了,只剩下头结点和其他节点了,那么每次就把头结点给处理好就行了。
那么就需要把
头结点的下一个节点的下一个节点止回自己就可以了。
然后把头结点指向空

private Node<T> _reverse2(Node<T> head) {
        if(head == null || head.next == null) {
            return head;
        }

        var rest = _reverse2(head.next);
        head.next.next = head;
        head.next = null;
        return rest;
    }

    public void reverse2() {
        head = _reverse2(head);
    }

6 判断链表中是否有环

6.1 利用hashtable

每次把链表节点存入到hashtable中,如果要是有环,那么就会一样的节点

public boolean hasLoop1(){
        var set = new HashSet<>();

        var p = head;
        while(p != null) {
            if(set.contains(p)) {
                return true;
            }
            set.add(p);
            p = p.next;
        }
        return false;
    }
6.2 快慢指针法

利用快慢指针,一个每次前进一个,一个每次前进两个,如果有环,那么总会有两个指针指向同一个节点的情况

public boolean hasLoop2(){

        if(head == null || head.next == null) {
            return false;
        }

        var slow = head;
        var fast = head.next.next;
        while(fast != null && fast.next != null) {
            if(fast == slow) {return true;}
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;


    }

全代码

package datastructure;

import org.junit.Test;

import java.util.HashMap;
import java.util.HashSet;
import java.util.function.Predicate;

import static org.junit.Assert.assertEquals;


public class List<T> {
    static class Node<T> {
        Node<T> next = null;
        T data;
        public Node(T data){
            this.data = data;
        }
    }
    Node<T> head = null;

    // O(1)
    public void insert(T data) {
        var node = new Node<>(data);
        node.next = head;
        head = node;

    }

    // O(n)
    public Node<T> find(Predicate<T> predicate) {
        var p = head;
        while(p != null) {
            if(predicate.test(p.data)) {
                return p;
            }
            p = p.next;
        }
        return null;

    }

    public Integer size(){
        var p = head;
        Integer c = 0;
        while(p != null) { p = p.next; c++; }
        return c;
    }

    // O(n)
    public void remove(Node<T> node){
        if(head == null) {
            return;
        }

        if(head == node) {
            head = head.next;
            return;
        }

        var slow = head;
        var fast = head.next;

        while(fast != node && fast != null) {
            slow = fast;
            fast = fast.next;
        }
        if(fast != null) {
            slow.next = fast.next;
//            fast.data = null;
        }

    }



    public void reverse(){
        // prev | current | next

        Node<T> prev = null;
        var current = head;
        Node<T> next;
        while(current != null) {
            next = current.next;
            current.next = prev;
            prev = current;
            current = next;
        }
        head = prev;

    }

    private Node<T> _reverse2(Node<T> head) {
        if(head == null || head.next == null) {
            return head;
        }

        var rest = _reverse2(head.next);
        head.next.next = head;
        head.next = null;
        return rest;
    }

    public void reverse2() {
        head = _reverse2(head);
    }

    public boolean hasLoop1(){
        var set = new HashSet<>();

        var p = head;
        while(p != null) {
            if(set.contains(p)) {
                return true;
            }
            set.add(p);
            p = p.next;
        }
        return false;
    }

    public boolean hasLoop2(){

        if(head == null || head.next == null) {
            return false;
        }

        var slow = head;
        var fast = head.next.next;
        while(fast != null && fast.next != null) {
            if(fast == slow) {return true;}
            slow = slow.next;
            fast = fast.next.next;
        }
        return false;


    }


    @Test
    public void test_insert(){
        var list = new List<Integer>();
        list.insert(1);
        list.insert(2);
        list.insert(3);

        var p = list.head;
        for(Integer i = 3; p != null ; i--) {
            assertEquals(i, p.data);
            p = p.next;
        }
    }

    @Test
    public void test_find_remove(){
        var list = new List<String>();

        list.insert("C++");
        list.insert("Java");
        list.insert("C");
        list.insert("C#");
        list.insert("python");

        var node = list.find(x -> x == "Java");
        assertEquals("Java", node.data);

        var node2 = list.find(x -> x == "ruby");
        assertEquals(null, node2);

        list.remove(node);
        assertEquals(Integer.valueOf(4), list.size());
        assertEquals(null, list.find(x -> x == "Java"));
    }

    @Test
    public void test_reverse() {
        var list = new List<Integer>();

        list.insert(1);
        list.insert(2);
        list.insert(3);
        list.reverse();

        var p = list.head;
        for (Integer i = 1; p != null; i++) {
            assertEquals(i, p.data);
            p = p.next;
        }
    }

    @Test
    public void test_reverse2() {
        var list = new List<Integer>();

        list.insert(1);
        list.insert(2);
        list.insert(3);
        list.reverse2();

        var p = list.head;
        for (Integer i = 1; p != null; i++) {
            assertEquals(i, p.data);
            p = p.next;
        }
    }

    @Test
    public void test_loop(){
        var list = new List<Integer>();
        list.insert(3);
        list.insert(2);
        list.insert(1);
        list.insert(0);
        var node = list.find(x -> x == 3);
        node.next = list.head;

        assertEquals(true, list.hasLoop1());
        assertEquals(true, list.hasLoop2());

    }

}

四.树

1 树的遍历

1.1 深度优先
1.1.1 先序

iOS数据结构面试 数据结构面试宝典_链表_06

1.1.2 中序

iOS数据结构面试 数据结构面试宝典_数据结构_07

1.1.3 后序

iOS数据结构面试 数据结构面试宝典_java_08

1.2 广度优先

iOS数据结构面试 数据结构面试宝典_java_09

2 手写一个二叉搜索树

二叉搜索树是一个中序遍历的数,它的特点就是左子树节点比当前节点小,右子树节点比当前节点大

2.1 树的结构

里面包含左子点的指针和右子点的指针,还有一个自己的数据

static class BSTNode<T> {
        BSTNode<T> left = null;
        BSTNode<T> right = null;
        T data;

        public BSTNode(T data) {
            this.data = data;
        }
    }
2.2 数的添加方法

利用递归去添加首先先判断添加的节点与当前节点的大小,如果大往右边添加,反之往左添加,然后看看右节点是否有值,如果有那么迭代比较,如果没有值那么就添加到这,左边同理

private void add(BSTNode<T> node, BSTNode<T> element) {

        if(element.data.compareTo(node.data) <= 0) {
            if(node.left == null) {
                node.left = element;
                return;
            }
            add(node.left, element);
        } else {
            if(node.right == null) {
                node.right = element;
                return;
            }
            add(node.right, element);
        }

    }

    public void add(T element) {
        var node = new BSTNode<>(element);
        if(root == null) {
            root = node;
            return;
        }

        add(root, node);

    }
2.3 树的删除

数的删除需要考虑的点有很多

  • 删除的节点没有左右节点,比较容易,直接删掉就可以
  • 删除的节点有一个子节点,那么就需要把子节点指向是当前节点的上一个节点
  • 删除的节点左右节点都有,需要挑选左子树中最大的或者右子树中最小的,替换当前节点,再将替换的节点置空
public  BSTNode<T> delete(BSTNode<T> root,BSTNode<T> val){
        if(root == null){
            return root;
        }

        if(root.data.compareTo(val.data)<0){
            root.right = delete(root.right,val);
            return root;
        }
        if(root.data.compareTo(val.data)>0){
            root.left = delete(root.left,val);
            return root;
        }

        if(root.left==null&&root.right==null){
            root = null;
            return root;
        }

        if(root.left==null&&root.right!=null){
            root = root.right;
            return root;
        }

        if(root.right==null&&root.left!=null){
            root = root.left;
            return root;
        }

        if(root.left!=null&&root.right!=null){
            T a = findMaxInLeftTree(root.left);
            root.data = a;
            root.left = delete(root.left,val);
            return root;
        }
        return root;
    }

    //找到左子树中最大的值
    private T findMaxInLeftTree(BSTNode<T> left) {
        if(left == null){
            return null;
        }
        if(left.right == null){
            return left.data;
        }
        if(left.right == null && left.left == null){
            return left.data;
        }
        return findMaxInLeftTree(left.right);
    }
2.4 数的三种遍历(前序,中序和后序)

其实这个很简单,如果是前序那么就先打印数据,然后再左右节点递归,如果是后序就先左右节点递归在打印,如果是中序,那么就先左节点然后打印然后在右节点

<T> void preOrder(BSTNode<T> node) {

        if(node == null) {
            return;
        }

        System.out.println(node.data);
        preOrder(node.left);
        preOrder(node.right);

    }

    <T> void postOrder(BSTNode<T> node){
        if(node == null) {
            return;
        }

        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.data);

    }

    <T> void inOrder(BSTNode<T> node){
        if(node == null) {
            return;
        }

        inOrder(node.left);
        System.out.println(node.data);
        inOrder(node.right);

    }
2.5 反转二叉树

递归的来看就是把当前层的左右节点互换,然后再去考虑下一层

public static <T> void reverse(BSTNode<T> node) {
        if(node == null) {
            return;
        }

        var t = node.left;
        node.left = node.right;
        node.right = t;

        reverse(node.left);
        reverse(node.right);

    }

全部源代码

package datastructure;

import org.junit.Test;

public class BSTree<T extends Comparable<T>> {

    BSTNode<T> root = null;

    static class BSTNode<T> {
        BSTNode<T> left = null;
        BSTNode<T> right = null;
        T data;

        public BSTNode(T data) {
            this.data = data;
        }
    }

    private void add(BSTNode<T> node, BSTNode<T> element) {

        if(element.data.compareTo(node.data) <= 0) {
            if(node.left == null) {
                node.left = element;
                return;
            }
            add(node.left, element);
        } else {
            if(node.right == null) {
                node.right = element;
                return;
            }
            add(node.right, element);
        }

    }

    public void add(T element) {
        var node = new BSTNode<>(element);
        if(root == null) {
            root = node;
            return;
        }

        add(root, node);

    }

    <T> void preOrder(BSTNode<T> node) {

        if(node == null) {
            return;
        }

        System.out.println(node.data);
        preOrder(node.left);
        preOrder(node.right);

    }

    <T> void postOrder(BSTNode<T> node){
        if(node == null) {
            return;
        }

        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.data);

    }

    <T> void inOrder(BSTNode<T> node){
        if(node == null) {
            return;
        }

        inOrder(node.left);
        System.out.println(node.data);
        inOrder(node.right);

    }

    // Breadth First Search
    public static <T> void bfs(BSTNode<T> node){


        var queue = new Queue<BSTNode<T>>();
        queue.enqueue(node);

        while(queue.size() > 0) {
            var item = queue.dequeue();
            System.out.println(item.data);

            if(item.left != null)
                queue.enqueue(item.left);
            if(item.right != null)
                queue.enqueue(item.right);
        }
    }

    public static <T> void reverse(BSTNode<T> node) {
        if(node == null) {
            return;
        }

        var t = node.left;
        node.left = node.right;
        node.right = t;

        reverse(node.left);
        reverse(node.right);

    }

     public  BSTNode<T> delete(BSTNode<T> root,BSTNode<T> val){
        if(root == null){
            return root;
        }

        if(root.data.compareTo(val.data)<0){
            root.right = delete(root.right,val);
            return root;
        }
        if(root.data.compareTo(val.data)>0){
            root.left = delete(root.left,val);
            return root;
        }

        if(root.left==null&&root.right==null){
            root = null;
            return root;
        }

        if(root.left==null&&root.right!=null){
            root = root.right;
            return root;
        }

        if(root.right==null&&root.left!=null){
            root = root.left;
            return root;
        }

        if(root.left!=null&&root.right!=null){
            T a = findMaxInLeftTree(root.left);
            root.data = a;
            root.left = delete(root.left,val);
            return root;
        }
        return root;
    }

    //找到左子树中最大的值
    private T findMaxInLeftTree(BSTNode<T> left) {
        if(left == null){
            return null;
        }
        if(left.right == null){
            return left.data;
        }
        if(left.right == null && left.left == null){
            return left.data;
        }
        return findMaxInLeftTree(left.right);
    }
    @Test
    public void test(){
        System.out.println("abcdefghijklmn".hashCode());
        var o = new Object();
        o.hashCode();
        BSTree tree = new BSTree<Integer>();

        tree.add(10);
        tree.add(7);
        tree.add(5);
        tree.add(8);
        tree.add(15);
        tree.add(30);
        tree.add(21);

        var printer = new TreePrinter();
        printer.print(tree.root);
        var node = new BSTNode<>(15);
        delete(tree.root, (BSTNode<T>) node);
        printer.print(tree.root);
        //bfs(tree.root);
        //preOrder(tree.root);
        //postOrder(tree.root);
//        inOrder(tree.root);
    }



    @Test
    public void test_reverse(){
        var tree = new BSTree<Integer>();

        tree.add(10);
        tree.add(7);
        tree.add(5);
        tree.add(8);
        tree.add(15);
        tree.add(30);
        tree.add(21);

        var printer = new TreePrinter();
        printer.print(tree.root);

        tree.reverse(tree.root);
        printer.print(tree.root);
    }

}

打印二叉树源代码

package datastructure;


import java.util.ArrayList;

public class TreePrinter {

    <T> int heightOf(BSTree.BSTNode<T> node) {
        if(node == null) {
            return 0;
        }
        return Math.max(
                heightOf(node.left),
                heightOf(node.right)
        ) + 1;
    }


    public <T> void print(BSTree.BSTNode<T> root) {
        int h = heightOf(root);
        int W = 2*(int)Math.pow(2, h);
        var lines = new StringBuilder[h*2];
        for(int i = 0; i < h*2; i++) {
            lines[i] = new StringBuilder(String.format("%" + W + "s", ""));
        }

        printNode(lines, W, root, 0, 0);
        for(var line : lines) {
            System.out.println(line.toString());
        }

    }

    private <T> void printNode(StringBuilder[] lines, int W,  BSTree.BSTNode<T> node, int h, int base) {
        var nums = Math.pow(2, h);
        var pos = base + (int)(W / (nums *  2));

        var str = node.data.toString();
        for(int i = 0; i < str.length(); i++) {
            lines[h*2].setCharAt(pos + i, str.charAt(i));
        }

        if(node.left != null) {
            lines[h*2+1].setCharAt(pos-1, '/');
            printNode(lines, W, node.left, h+1, base);
        }

        if(node.right != null) {
            lines[h*2 + 1].setCharAt(pos + str.length() + 1, '\\');
            printNode(lines, W, node.right, h+1, pos);
        }


    }

}