本篇笔记还没写完,不想写了,后面再写
目录
01 需求分析
- 在n个动态的整数中搜索某个整数(查看其是否存在),如果要是使用动态数组存放元素,从第0个位置开始遍历搜索,平均时间复杂度:O(n);
- 在一个有序的动态数组,使用二分搜索,最坏的时间复杂度是:O(logn),但是添加、删除的平均时间复杂度是O(n).针对上述的这个需求,更好的设计方案是使用相应的二叉搜索树,添加 删除 搜索的最坏时间复杂度可优化到:O(logn).
02 接口设计
- 二叉搜索树是二叉树的一种,是一种应用非常广泛的二叉树,英文简称是BST.(别称是二叉查找树 二叉排序树)
- 特点:(1)任意一个节点的值都大于左子树所有节点的值; (2)任意一个节点的值都大于右子树所有节点的值; (3)左右子树也是二叉搜索树; (4)可以大大提高搜索效率.
- 接口如下所示:对比于动态数组的元素是没有索引的概念.
03 add-根节点
- 代码段如下所示:
import java.util.Comparator; public class BinarySearchTree<E> { private int size; private Node<E> root; public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { } public void add(E element) { elementNOtNullCheck(element); //添加第一个节点 if(root == null) { root = new Node<E>(element,null); size++; return ; } //添加的不是第一个节点 } public void remove(E element) { } public boolean contains(E element) { return true; } //检查节点是否为空 private void elementNOtNullCheck(E element) { if(element == null) { throw new IllegalArgumentException("element must not be null"); } } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element,Node<E> parent) { this.element = element; this.parent = parent; } } }
04 add-思路
- 比如说我们添加1和12,如下图所示:
- 添加的步骤是如下所示:(1)找到父节点 parent; (2)创建新节点 node; (3)parent.left = node 或者 parent.right = node
-
如果要是遇到相同的节点,建议进行相应的一个覆盖节点的使用过程.
05 add实现
- 完善了compare函数和add()函数,完整的代码如下所示:
import java.util.Comparator; public class BinarySearchTree<E> { private int size; private Node<E> root; public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { } public void add(E element) { elementNOtNullCheck(element); //添加第一个节点 if(root == null) { root = new Node<E>(element,null); size++; return ; } //添加的不是第一个节点 //找到父节点 Node<E> parent = null;//看下面的一句代码 Node<E> node = root; int cmp = 0; while(node != null) { int cmp = compare(element, node.element); parent = node;//这句话的含义是在进行比较之前进行一个节点的赋值,因为要是跳出循环之后,node=null,需要进行一个提前保存的过程. if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { return;//什么也不干 } } //插入到父节点的位置 Node<E> newNode = new Node<E>(element,parent); if(cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } } public void remove(E element) { } public boolean contains(E element) { return true; } //检查节点是否为空 private void elementNOtNullCheck(E element) { if(element == null) { throw new IllegalArgumentException("element must not be null"); } } /** * @return 返回值=0,e1=e2;返回值=1,e1>e2;返回值<0,e1<e2; * @param */ private int compare(E e1,E e2) { return 0; } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element,Node<E> parent) { this.element = element; this.parent = parent; } } }
06 comparable
- 第一种设计方式是通过相应的comparable的方式实现的.
- 设定一个相应的comparable的接口,接口的代码是如下所示:
public interface Comparable<E> {
int compareTo(E e);
}
- 声明一个Person类去继承相应的Comparable接口,代码是如下所示:
public class Person implements Comparable<Person>{//这里是需要进行继承一个官方的Comparable的
private int age;
public Person(int age)
{
this.age = age;
}
public int compareTo(Person e) {//对比年龄的大小
return (this.age - e.age);
}
}
- 二叉树之中的代码是如下所示:
import java.util.Comparator;
public class BinarySearchTree<E extends Comparable> {
private int size;
private Node<E> root;
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
}
public void add(E element) {
elementNOtNullCheck(element);
//添加第一个节点
if(root == null)
{
root = new Node<E>(element,null);
size++;
return ;
}
//添加的不是第一个节点
//找到父节点
Node<E> parent = null;//看下面的一句代码
Node<E> node = root;
int cmp = 0;
while(node != null)
{
cmp = compare(element, node.element);
parent = node;//这句话的含义是在进行比较之前进行一个节点的赋值,因为要是跳出循环之后,node=null,需要进行一个提前保存的过程.
if (cmp > 0) {
node = node.right;
} else if (cmp < 0) {
node = node.left;
} else {
return;//什么也不干
}
}
//插入到父节点的位置
Node<E> newNode = new Node<E>(element,parent);
if(cmp > 0)
{
parent.right = newNode;
}
else {
parent.left = newNode;
}
}
public void remove(E element) {
}
public boolean contains(E element) {
return true;
}
//检查节点是否为空
private void elementNOtNullCheck(E element)
{
if(element == null)
{
throw new IllegalArgumentException("element must not be null");
}
}
/**
* @return 返回值=0,e1=e2;返回值=1,e1>e2;返回值<0,e1<e2;
* @param
*/
private int compare(E e1,E e2)
{
return e1.compareTo(e2);
}
private static class Node<E>
{
E element;
Node<E> left;
Node<E> right;
Node<E> parent;
public Node(E element,Node<E> parent)
{
this.element = element;
this.parent = parent;
}
}
}
-
我们的主函数是这样进行编写测试.(应当注意函数之中对于Comparable接口的继承)
import java.util.Comparator;
public class BinarySearchTree<E extends Comparable> {
private int size;
private Node<E> root;
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
}
public void add(E element) {
elementNOtNullCheck(element);
//添加第一个节点
if(root == null)
{
root = new Node<E>(element,null);
size++;
return ;
}
//添加的不是第一个节点
//找到父节点
Node<E> parent = null;//看下面的一句代码
Node<E> node = root;
int cmp = 0;
while(node != null)
{
cmp = compare(element, node.element);
parent = node;//这句话的含义是在进行比较之前进行一个节点的赋值,因为要是跳出循环之后,node=null,需要进行一个提前保存的过程.
if (cmp > 0) {
node = node.right;
} else if (cmp < 0) {
node = node.left;
} else {
return;//什么也不干
}
}
//插入到父节点的位置
Node<E> newNode = new Node<E>(element,parent);
if(cmp > 0)
{
parent.right = newNode;
}
else {
parent.left = newNode;
}
}
public void remove(E element) {
}
public boolean contains(E element) {
return true;
}
//检查节点是否为空
private void elementNOtNullCheck(E element)
{
if(element == null)
{
throw new IllegalArgumentException("element must not be null");
}
}
/**
* @return 返回值=0,e1=e2;返回值=1,e1>e2;返回值<0,e1<e2;
* @param
*/
private int compare(E e1,E e2)
{
return e1.compareTo(e2);
}
private static class Node<E>
{
E element;
Node<E> left;
Node<E> right;
Node<E> parent;
public Node(E element,Node<E> parent)
{
this.element = element;
this.parent = parent;
}
}
}
07 comparator
- 上面的设计模式并不是很好的,为什么呢?因为如果要是使用两个相应的二叉搜索树,二叉搜索树的分配方式都是一样的,也就是所谓的比较规则是一样的,不可以进行改变,为了解决这个问题,我们采用一个新的接口comparator.
- 创建一个新的比较器接口,代码是如下所示:
public interface Comparator<E> { int compare(E e1,E e2); }
在二叉搜索树之中进行相应的一个构造函数的引用,并且不再继承原来的接口.
public class BinarySearchTree<E>{ private int size; private Node<E> root; private Comparator<E> comparator; public BinarySearchTree(Comparator<E> comparator) { this.comparator = comparator; } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { } public void add(E element) { elementNOtNullCheck(element); //添加第一个节点 if(root == null) { root = new Node<E>(element,null); size++; return ; } //添加的不是第一个节点 //找到父节点 Node<E> parent = null;//看下面的一句代码 Node<E> node = root; int cmp = 0; while(node != null) { cmp = compare(element, node.element); parent = node;//这句话的含义是在进行比较之前进行一个节点的赋值,因为要是跳出循环之后,node=null,需要进行一个提前保存的过程. if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { return;//什么也不干 } } //插入到父节点的位置 Node<E> newNode = new Node<E>(element,parent); if(cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } } public void remove(E element) { } public boolean contains(E element) { return true; } //检查节点是否为空 private void elementNOtNullCheck(E element) { if(element == null) { throw new IllegalArgumentException("element must not be null"); } } /** * @return 返回值=0,e1=e2;返回值=1,e1>e2;返回值<0,e1<e2; * @param */ private int compare(E e1,E e2) { return comparator.compare(e1,e2); } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element,Node<E> parent) { this.element = element; this.parent = parent; } } }
Person类直接调用相应的构造器,为主函数测试使用做准备.
public class Person implements Comparable<Person>{//这里是需要进行继承一个官方的Comparable的 private int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Person(int age) { this.age = age; } public int compareTo(Person e) {//对比年龄的大小 return (this.age - e.age); } }
主函数是如下所示:
public class Main { private static class personComparator implements Comparator<Person> { public int compare(Person e1, Person e2) { return e1.getAge() - e2.getAge(); } } private static class personComparator2 implements Comparator<Person> { public int compare(Person e1, Person e2) { return e2.getAge() - e1.getAge(); } } public static void main(String[] args) { Integer data[] = new Integer[] { 7,4,9,2,5,8,11,3 } ; /* BinarySearchTree<Integer> bst = new BinarySearchTree<Integer>(); for(int i = 0;i < data.length;i++) { bst.add(data[i]); } */ BinarySearchTree<Person> bst2 = new BinarySearchTree<Person>(new personComparator()); bst2.add(new Person(12)); bst2.add(new Person(15)); BinarySearchTree<Person> bst3 = new BinarySearchTree<Person>(new personComparator2()); bst2.add(new Person(12)); bst2.add(new Person(15)); } }
08 完美结合
- 通过对于二叉搜索树的改写,可以进行相应的一个二者的结合,在compare函数之中进行定义相应的比较规则,代码是如下所示:
public class BinarySearchTree<E>{ private int size; private Node<E> root; private Comparator<E> comparator; public BinarySearchTree() { this(null); } public BinarySearchTree(Comparator<E> comparator) { this.comparator = comparator; } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { } public void add(E element) { elementNOtNullCheck(element); //添加第一个节点 if(root == null) { root = new Node<E>(element,null); size++; return ; } //添加的不是第一个节点 //找到父节点 Node<E> parent = null;//看下面的一句代码 Node<E> node = root; int cmp = 0; while(node != null) { cmp = compare(element, node.element); parent = node;//这句话的含义是在进行比较之前进行一个节点的赋值,因为要是跳出循环之后,node=null,需要进行一个提前保存的过程. if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { return;//什么也不干 } } //插入到父节点的位置 Node<E> newNode = new Node<E>(element,parent); if(cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } } public void remove(E element) { } public boolean contains(E element) { return true; } //检查节点是否为空 private void elementNOtNullCheck(E element) { if(element == null) { throw new IllegalArgumentException("element must not be null"); } } /** * @return 返回值=0,e1=e2;返回值=1,e1>e2;返回值<0,e1<e2; * @param */ private int compare(E e1,E e2) { if(comparator != null) { return comparator.compare(e1,e2); } return ((Comparable<E>)e1).compareTo(e2); } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element,Node<E> parent) { this.element = element; this.parent = parent; } } }
09 匿名类
- 在main函数实现代码:
/* Java的匿名类,类似于IOS之中的Block,JS之中的闭包(function) */ BinarySearchTree<Person> bst4 = new BinarySearchTree<Person>(new Comparator<Person>() { public int compare(Person o1, Person o2) { return 0; } });
10 打印器使用
- 一个小小的网上,直接输入就是可以生成二叉树的网站.BinaryTreeGraph
- 查看工具是小马哥已经编辑好的,引入BinarySearchTree,直接实现相应的接口.
- BinarySearchTree之中的代码是如下所示:
import printer.BinaryTreeInfo; import java.util.Comparator; public class BinarySearchTree<E extends Comparable> implements BinaryTreeInfo { private int size; private Node<E> root; private Comparator<E> comparator; public BinarySearchTree() { this(null); } public BinarySearchTree(Comparator<E> comparator) { this.comparator = comparator; } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void clear() { } public void add(E element) { elementNOtNullCheck(element); //添加第一个节点 if(root == null) { root = new Node<E>(element,null); size++; return ; } //添加的不是第一个节点 //找到父节点 Node<E> parent = null;//看下面的一句代码 Node<E> node = root; int cmp = 0; while(node != null) { cmp = compare(element, node.element); parent = node;//这句话的含义是在进行比较之前进行一个节点的赋值,因为要是跳出循环之后,node=null,需要进行一个提前保存的过程. if (cmp > 0) { node = node.right; } else if (cmp < 0) { node = node.left; } else { return;//什么也不干 } } //插入到父节点的位置 Node<E> newNode = new Node<E>(element,parent); if(cmp > 0) { parent.right = newNode; } else { parent.left = newNode; } } public void remove(E element) { } public boolean contains(E element) { return true; } //检查节点是否为空 private void elementNOtNullCheck(E element) { if(element == null) { throw new IllegalArgumentException("element must not be null"); } } /** * @return 返回值=0,e1=e2;返回值=1,e1>e2;返回值<0,e1<e2; * @param */ private int compare(E e1,E e2) { if(comparator != null) { return comparator.compare(e1,e2); } return ((Comparable<E>)e1).compareTo(e2); } public Object root() { return root; } public Object left(Object node) { return ((Node<E>)node).left; } public Object right(Object node) { return ((Node<E>)node).right; } public Object string(Object node) { return ((Node<E>)node).element; } private static class Node<E> { E element; Node<E> left; Node<E> right; Node<E> parent; public Node(E element,Node<E> parent) { this.element = element; this.parent = parent; } } }
主函数之中的打印流程是如下所示:
public static void main(String[] args) { Integer data[] = new Integer[] { 7,4,9,2,5,8,11,3 } ; BinarySearchTree<Integer> bst = new BinarySearchTree<Integer>(); for(int i = 0;i < data.length;i++) { bst.add(data[i]); } BinaryTrees.println(bst);
测试结果是如下所示:
11 打印器Person
- 加入Person打印的过程.
static void test2()
{
BinarySearchTree<Person> bst2 = new BinarySearchTree<Person>(new personComparator());
Integer data[] = new Integer[] {
7,4,9,2,5,8,11,3
};
for(int i = 0;i < data.length;i++)
{ bst2.add(new Person(data[i]));
}
BinaryTrees.println(bst2);
}
- 打印结果是如下所示:
- 如果要是想要打印年龄,就是需要进行一个重写toString的过程即可.在相应的Person类后面进行添加如下代码,需要进行注意的toString返回的必须是一个String类型的数值,因此,+""转换.
@Override
public String toString() {
return age + "";
}
- 打印结果如下所示:
12 打印器更多用法
- 通过Math.random()函数进行一个随机数,进行测试即可.
13 打印器文件建议
static void test3()
{
BinarySearchTree<Integer> bst = new BinarySearchTree<Integer>();
for(int i = 0;i < 40;i++)
{
bst.add((int)(Math.random() * 100));
}
String str = BinaryTrees.printString(bst);
Files.writeToFile("F:/1.txt",str);
}
14 网站推荐
- 二叉搜索树随机生成:BinaryTreeGraph (520it.com)
- BinaryTreeVisualiser - Binary Search Tree (melezinek.cz)
- Binary and Linear Search Visualization (usfca.edu)
15 值相等处理
- 直接进行一个覆盖,原因是因为比如说,在比较的过程之中只是进行比较相应的年龄,要是名字不一样就是需要进行一个覆盖操作.
16 前序遍历(Preorder Traversal)
- 访问顺序
- 前序遍历代码:
/** * 前序遍历 */ public void preorderTraversal() { preorderTraversal(root); } private void preorderTraversal(Node<E> node) { if (node == null) return; System.out.println(node.element); preorderTraversal(node.left); preorderTraversal(node.right); }
17 中序遍历
- 遍历顺序:
- 遍历代码:
/** * 中序遍历 */ public void inorderTraversal() { inorderTraversal(root); } private void inorderTraversal(Node<E> node) { if (node == null) return; inorderTraversal(node.left); System.out.println(node.element); inorderTraversal(node.right); }
上述的代码进行一个位置的调换,将node.left与node.right的位置进行调换之后,打印输出是逆序的。
18 后序遍历
- 遍历顺序
- 遍历代码
/** * 后序遍历 */ public void postorderTraversal() { postorderTraversal(root); } private void postorderTraversal(Node<E> node) { if (node == null) return; postorderTraversal(node.left); postorderTraversal(node.right); System.out.println(node.element); }
19 层序遍历
- 遍历顺序
- 遍历代码
/** * 层序遍历 */ public void levelOrderTraversal() { if (root == null) return; Queue<Node<E>> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node<E> node = queue.poll(); System.out.println(node.element); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } }
20 设计遍历接口
- 内部代码
public static interface Vistor<E>
{
void visit(E element);
}
public void levelOrder(Vistor<E> vistor)
{
if (root == null) return;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
vistor.visit(node.element);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
- 外部实现
bst.levelOrder(new Vistor<Integer>)
{
//方法重写即可,自定义进行一个输出的方式
}
-
代码升级
public void preorder(Visitor<E> visitor) {
if (visitor == null) return;
preorder(root, visitor);
}
private void preorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
visitor.stop = visitor.visit(node.element);
preorder(node.left, visitor);
preorder(node.right, visitor);
}
public void inorder(Visitor<E> visitor) {
if (visitor == null) return;
inorder(root, visitor);
}
private void inorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
inorder(node.left, visitor);
if (visitor.stop) return;
visitor.stop = visitor.visit(node.element);
inorder(node.right, visitor);
}
public void postorder(Visitor<E> visitor) {
if (visitor == null) return;
postorder(root, visitor);
}
private void postorder(Node<E> node, Visitor<E> visitor) {
if (node == null || visitor.stop) return;
postorder(node.left, visitor);
postorder(node.right, visitor);
if (visitor.stop) return;
visitor.stop = visitor.visit(node.element);
}
public void levelOrder(Visitor<E> visitor) {
if (root == null || visitor == null) return;
Queue<Node<E>> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node<E> node = queue.poll();
if (visitor.visit(node.element)) return;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}