**
1.二叉排序树的百度百科定义:
**
(1)二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树:
①二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
1)若左子树不空,则左子树上所有节点的值均小于它的根节点的值。
2)若右子树不空,则右子树上所有节点的值均大于它的根节点的值
3)左、右子树也分别为二叉排序树
4)没有键值相等的节点。
**
2.先补充说明以下对数器:
**
(1) 我们再写完一个程序后,为验证其正确性,往往选择一些测试用例去测试,如写了一个冒泡排序,我们需要验证其正确性,就可以拿我们写的冒泡排序和官方的sort对一组相同元素的数组进行排序,之后对排序后的两个数组进行逐一比较,若有不同的顺序,则打印这个数组,这样子,我们写出来的程序就得到了正确性的验证,如以下就是一个排序的对数器:
InsertSort insertSort = new InsertSort(20);
for (int i = 0; i < 1000; i++) {
insertSort.randomInit();
//copy一份新的数组,和insertSort.arrays一致
int[] sortArray = Util.copyArray(insertSort.arrays);
int[] mySortArray = insertSort.sort();
Arrays.sort(sortArray);
//检查两个数组的元素顺序是否一致
Util.check(mySortArray,sortArray);
}
//Check函数:
public static void check(int[] array1,int[] array2){
boolean flag = true;
for (int i = 0; i < array1.length; i++) {
if(array1[i]!=array2[i]){
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));
flag = false;
break;
}
}
if(flag){
System.out.println("true");
}else {
System.out.println("false");
}
}
今天我们写的bst,也可以使用treeMap进行对照,对数…
**
3.今天就来写一棵 BST。
**
(1)首先,树的组成是节点,故先创建一个节点类:
①思考节点类有什么属性:
1)因为是二叉树,所以有左右节点:left、right,可以再来一个父节点parent。不过此版本不带父节点,读者可以自行改进。
2)有一个value存放节点的内容
3)同时可以有一个key,作为树的权重的参考,
4)为了规范一点,我们实现Map.Entry接口
5)给上属性的get、set的方法,构造函数。
6)一个完整的节点类就出来了:
public class Entry<K,V> implements Map.Entry<K,V> {
private Entry<K,V> left;
private Entry<K,V> right;
private K key;
private V value;
public Entry(V value) {
this.value = value;
}
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
public Entry(Entry<K, V> left, Entry<K, V> right, K key, V value) {
this.left = left;
this.right = right;
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
return value;
}
@Override
public String toString() {
return "Entry{" +
" key=" + key +
", value=" + value +
", }";
}
public Entry<K, V> getLeft() {
return left;
}
public void setLeft(Entry<K, V> left) {
this.left = left;
}
public Entry<K, V> getRight() {
return right;
}
public void setRight(Entry<K, V> right) {
this.right = right;
}
public void setKey(K key) {
this.key = key;
}
}
(2)接下来就是主类BSTree:
①思考属性:
1)首先:节点类型 root,
2)Int 类型的 size,
3)貌似没了,不过可以 再来一个 节点 key 类型的比较器,comparetor;
②实现:
1)初始化
Root = null;
Size = 0;
Comparetor = null;
构造函数可分为 无参构造,也可以传一个 比较器进来。
2)方法:
a.比较节点key的权重:入参:返回值 int 大于0表示 key1>key2
a)当 比较器Comparetor 不为空时,则用比较器比较key的权重,当comparator为空时,则必须要key的类实现comparable接口,直接调用compareto方法。若没实现,则抛出异常…
b.返回树的节点数:
c.插入节点:
a)当 root 节点为 null时,则将此key+value新建节点赋值给root。
b)当节点不为null时,比较插入key与节点key的权重大小,若大于,则插入到节点的左侧子树,若左侧子树为空,则将此key+value新建节点赋值给左测子树。
c)若小于,与大于同理。
d)迭代进行插入,同时不要忘记 size++;
d.遍历树:
a)BST的中序序遍历是升序的排列,此例我们就使用中序遍历:
b)总所周知,set中的元素是无顺序的,为什么TreeSet的输出却是有序的呢,是因为在foreach时,使用的时迭代器,这里,我们也使用迭代器去实现我们自己的BST的迭代。
c)实现迭代有两步:
i.能获取iterator的类实现iterable接口,重写iterator方法,返回一个itrator的实例。
ii.新建一个类实现iterator接口,重写hasNext和next方法,hasNext表示是否有下一个元素,next表示取下一个元素。
d)由于此处是中序遍历且无父节点,所以必须借助一个栈来实现:
i.先将所有左侧节点入栈
public void addToStack(Entry<K, V> entry){
Entry<K,V> temp = entry;
while (temp!=null){
stack.push(temp);
temp = temp.getLeft();
}
}
ii.在取出时:将此节点的右节点压入栈
i)Addtostack(entry.right);
iii.故:迭代器的属性:树的根节点,一个帮助回溯的辅助栈(若节点右parent的域则不需要)。HasNext的条件为:栈不为空。
iv.Next则为栈顶元素。这样,我们的树就有一个迭代器了。
v.以下为迭代器代码:
public class MyTreeIterator<K,V> implements Iterator<Entry<K,V>> {
//private ArrayList<Entry<K,V>> arrayList;
private Stack<Entry<K,V>> stack;
private Entry<K,V> root;
public MyTreeIterator(Entry<K, V> root) {
this.root = root;
Entry<K,V> temp = root;
stack = new Stack<>();
addToStack(temp);
}
public void addToStack(Entry<K, V> entry){
Entry<K,V> temp = entry;
while (temp!=null){
stack.push(temp);
temp = temp.getLeft();
}
}
@Override
public boolean hasNext() {
return stack.size()>0;
}
@Override
public Entry<K,V> next() {
if(stack.size() == 0){
throw new NoSuchElementException("迭代器中无参数了!!!");
}
Entry<K,V> entry = stack.pop();
if(entry.getRight() != null){
addToStack(entry.getRight());
}
return entry;
}
@Override
public void remove() {
}
}
③接下来就是BST本身的实现了,根据前面给出的代码:
1)比较key的权重,写一个compare方法,实现如下:
/**
* compare two key how bigger if key1 more than key2 return num > 0,= 0 is someone.< 0 is letter key2.
* if comparator is null,use comparable to equally
* @param key1
* @param key2
* @return >0 or 0 or <0
*/
private final int compare(K key1, K key2){
if(comparator!=null){
return comparator.compare(key1,key2);
}else {
if(key1 instanceof Comparable){
Comparable<K> k = (Comparable<K>)key1;
return k.compareTo(key2);
}
throw new ClassCastException(" this class do not implement Comparable interface ");
}
}
2)返回树的节点数:
Return size即可
3)插入节点:
a.入参:key,value。
b.判断根节点是否为空,为空直接令其等于新节点。
c.每到一个节点,比较key的权重,比节点大,且节点右子树为空,则直接插入,否则节点迭代成左节点。权重比节点权重下,道理同上。若权重一致,则看业务需求,看是替换value还是保留原来的value,这里我偷懒,选择的是保留原value。
d.同时不忘添加时要size++;
e.代码如下:
/**
* put one key and value to this tree,in this tree create a entry to contains this key and value
* if this tree has some key equally whit this key , this value will no replace old value , will put fail
* @param key
* @param value
*/
public void put(K key,V value){
if(root == null){
root = new Entry<>(key,value);
size++;
}else {
Entry<K,V> temp = root;
while (temp != null){
int sign = compare(key,temp.getKey());
if( sign == 0 ){
break;
}else if( sign > 0){
if(temp.getRight() != null){
temp = temp.getRight();
}else {
temp.setRight(new Entry<>(key,value));
size++;
break;
}
}else{
if(temp.getLeft() != null){
temp = temp.getLeft();
}else {
temp.setLeft(new Entry<>(key,value));
size++;
break;
}
}
}
}
}
4)遍历树由于已经写好迭代器,故很简单:
@Override
public Iterator<Entry<K,V>> iterator() {
return new MyTreeIterator<>(root);
}
④这样,一颗具有插入,中序遍历的树就形成了,为了更加漂亮,我们再进行一系列的补充:
1)添加获取指定节点的方法getEntryUsingComparator:入参:key,返回值 entry。
a.逻辑基本与插入一致,当key等于null时,我们直接返回null,或者查找失败(查找到叶子节点都没有找到)返回null,找到的依据时key的权重相等,即compare方法的返回值为0,我们就返回 entry节点类对象。
b.因为有时候我们需要key找到指定节点去操作节点,故独立出来一个根据key找节点的方法。
2)添加get函数:入参:key,返回值 value。
a.由于有了获取指定节点的函数,故我们只要调用就行:
Entry<K,V> entry = getEntryUsingComparator(key);
if(entry != null){
return entry.getValue();
}
return null;
3)添加contains函数:入参:key,返回值 boolean。
a.containsKey:因为key可以进行比较,故查找起来十分简单,这里我们为了可维护性和简洁性,可以直接借助get函数:return get(key)!=null;
b.containsObject:因为object是无顺序的,所以我们查找它时是无任何优化的,故时间复杂度为o(n),并且时遍历整颗树去查找的,这里由于我们之前写了一个迭代器,固使用迭代器进行逐一比较.
c.代码实现:
public boolean containsValue(V value){
Iterator<Entry<K,V>> it = new MyTreeIterator<>(root);
while (it.hasNext()){
if(valEquals(it.next().getValue(),value)){
return true;
}
}
return false;
}
4)增加获取指定节点后的最小节点:入参 节点entry
a.getFirstEntry(entry p)
b.由于时有序的,故只要找到最左节点即是最小节点:
Entry<K,V> temp = entry;
if (temp != null){
while (temp.getLeft() != null){
temp = temp.getLeft();
}
}
return temp;
5)增加获取最大节点:
a.同获取最小节点一致
6)删除节点函数deleteEntry:入参:entry初始节点,要删除的key。
a.删除节点首先找到指定节点。找到的依据即是:比较函数compare返回值为0
b.删除节点有四种情况:
a)情况1:要删除的节点为叶子节点,直接删除即可
b)情况2:要删除的节点仅有一个左子树,让左子树代替此节点即可
c)情况3:要删除的节点仅有一个右子树,让右子树代替此节点即可
d)情况4:要删除的节点既有左子树也有右子树,此时,需要在左子树找到一个最大节点代替此节点,或者,在右子树找到一个最小节点代替此节点。再进行删除此最小/最大节点,将转变为情况1.
c.这里讲讲情况4:
a)首先,假设寻找到节点p,就是要删除的节点,找到p的左侧的最大权重节点:leftMax = getFirstEntry§;再令p节点的k、v、都等于leftMax节点的k、v。再递归调用本方法删除deleteEntry(p.left).
d.注意size–;
e.未找到删除节点:
a)根据key权重的比较,进行递归调用,如:p.right = deleteEntry(p.right).
private final Entry<K,V> deleteEntry(Entry<K,V> p,K key){
//two child
if( p == null){
return null;
}
int compareResult = compare(p.getKey(),key);
//如果 结果为0, 表示找到 要删除节点
if(compareResult == 0){
//case 1 :
if( p.getRight() == null && p.getLeft() == null){
size--;
p = null;
}else if( p.getLeft() != null && p.getRight() == null ){
size--;
p = p.getLeft();
}else if( p.getLeft() == null && p.getRight() != null ){
size--;
p = p.getRight();
}else {
if( size%2 == 0 ) {
//left max
Entry<K, V> leftMax = getLastEntry(p.getLeft());
//将p转为 leftMax
p.setKey(leftMax.getKey());
p.setValue(leftMax.getValue());
//
Entry<K, V> newLeft = deleteEntry(p.getLeft(),p.getKey());
p.setLeft(newLeft);
}else {
//right min
Entry<K, V> rightMin = getFirstEntry(p.getRight());
//将p转为 rightMin
p.setKey(rightMin.getKey());
p.setValue(rightMin.getValue());
Entry<K, V> newRight = deleteEntry(p.getRight(),p.getKey());
p.setRight(newRight);
}
}
}else if(compareResult < 0){
//未找到节点,递归调用删除p的右子树
//将返回值作为新右子树
Entry<K, V> newRight = deleteEntry(p.getRight(),key);
p.setRight(newRight);
}else {
//未找到节点,递归调用删除p的左子树
//将返回值作为新左子树
Entry<K, V> newLeft = deleteEntry(p.getLeft(),key);
p.setLeft(newLeft);
}
//返回新树
return p;
}
好的,以上就是删除节点的操作了,注意,返回值是新的entry节点。
7)Remove:返回值为:V
a.因为删除的操作时不会有被删除元素的返回的,所以我们先获取,再进行删除
b.获取,直接使用get函数
c.删除,直接调用deleteEntry方法:root = deleteEntry(root,key);这里因为可能会删除根节点,故需要让root等于新的根节点。
(3)这里我们可以看一看官方的treeMap如何实现的:
①寻找后继节点:
static <K,V> TreeMap.Entry<K,V> successor(TreeMap.Entry<K,V> t) {
//if t is null ,return null
if (t == null)
return null;
//left --> root --> right print
//so if this entry has right child,the next entry is right tree leftes child.
else if (t.right != null) {
TreeMap.Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p;
} else {
//if this entry has no child, to father,and if this node is father right child,should find up level,
//until root ... if allways not found , this entry has no next.
TreeMap.Entry<K,V> p = t.parent;
TreeMap.Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p;
}
}
②删除节点(此处因为treeMap entry 带有父节点,故可以使用递归),可以看到此处也是分为四种情况:
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p
// point to successor.
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p);
p.key = s.key;
p.value = s.value;
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
p.left = p.right = p.parent = null;
// Fix replacement
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node.
root = null;
} else { // No children. Use self as phantom replacement and unlink.
if (p.color == BLACK)
fixAfterDeletion(p);
if (p.parent != null) {
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
官方的代码还是非常的优秀,若是能够阅读的话真的受益匪浅。
(4) 最后给出所有的代码:
①MyTreeIterator<K,V>,迭代器的实现类:
public class MyTreeIterator<K,V> implements Iterator<Entry<K,V>> {
//private ArrayList<Entry<K,V>> arrayList;
private Stack<Entry<K,V>> stack;
private Entry<K,V> root;
public MyTreeIterator(Entry<K, V> root) {
this.root = root;
Entry<K,V> temp = root;
stack = new Stack<>();
addToStack(temp);
}
public void addToStack(Entry<K, V> entry){
Entry<K,V> temp = entry;
while (temp!=null){
stack.push(temp);
temp = temp.getLeft();
}
}
@Override
public boolean hasNext() {
return stack.size()>0;
}
@Override
public Entry<K,V> next() {
if(stack.size() == 0){
throw new NoSuchElementException("迭代器中无参数了!!!");
}
Entry<K,V> entry = stack.pop();
if(entry.getRight() != null){
addToStack(entry.getRight());
}
return entry;
}
@Override
public void remove() {
}
}
②Entry<K,V>,节点类:
public class Entry<K,V> implements Map.Entry<K,V> {
private Entry<K,V> left;
private Entry<K,V> right;
private K key;
private V value;
public Entry(V value) {
this.value = value;
}
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
public Entry(Entry<K, V> left, Entry<K, V> right, K key, V value) {
this.left = left;
this.right = right;
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
return value;
}
@Override
public String toString() {
return "Entry{" +
" key=" + key +
", value=" + value +
", }";
}
public Entry<K, V> getLeft() {
return left;
}
public void setLeft(Entry<K, V> left) {
this.left = left;
}
public Entry<K, V> getRight() {
return right;
}
public void setRight(Entry<K, V> right) {
this.right = right;
}
public void setKey(K key) {
this.key = key;
}
}
③BSTree<K,V>,BSTree类:
public class BSTree<K,V> implements Iterable<Entry<K,V>>{
private static final int MAX = 16;
private Entry<K,V> root;
private Comparator<K> comparator;
private int size;
// TreeMap
public BSTree() {
size = 0;
}
public BSTree(Comparator<K> comparator) {
this.comparator = comparator;
size = 0;
}
/**
* compare two key how bigger if key1 more than key2 return num > 0,= 0 is someone.< 0 is letter key2.
* if comparator is null,use comparable to equally
* @param key1
* @param key2
* @return >0 or 0 or <0
*/
private final int compare(K key1, K key2){
if(comparator!=null){
return comparator.compare(key1,key2);
}else {
if(key1 instanceof Comparable){
Comparable<K> k = (Comparable<K>)key1;
return k.compareTo(key2);
}
throw new ClassCastException(" this class do not implement Comparable interface ");
}
}
public int size(){
return size;
}
/**
* return tree entry count
* @return
*/
public boolean isEmpty(){
return size == 0;
}
/**
* put one key and value to this tree,in this tree create a entry to contains this key and value
* if this tree has some key equally whit this key , this value will no replace old value , will put fail
* @param key
* @param value
*/
public void put(K key,V value){
if(root == null){
root = new Entry<>(key,value);
size++;
}else {
Entry<K,V> temp = root;
while (temp != null){
int sign = compare(key,temp.getKey());
if( sign == 0 ){
break;
}else if( sign > 0){
if(temp.getRight() != null){
temp = temp.getRight();
}else {
temp.setRight(new Entry<>(key,value));
size++;
break;
}
}else{
if(temp.getLeft() != null){
temp = temp.getLeft();
}else {
temp.setLeft(new Entry<>(key,value));
size++;
break;
}
}
}
}
}
/**
* find this key --> entry
* if has no this key will return null
*
* @param key
* @return
*/
private final Entry<K,V> getEntryUsingComparator(Object key) {
if(root == null ) return null;
if(key == null) throw new NullPointerException();
K key1 = (K)key;
Entry<K,V> temp = root;
while (temp != null){
int sign = compare(key1,temp.getKey());
if( sign == 0 ){
return temp;
}else if( sign > 0){
temp = temp.getRight();
}else{
temp = temp.getLeft();
}
}
return null;
}
/**
* return this key --> entry's value
* @param key
* @return
*/
public V get(K key){
Entry<K,V> entry = getEntryUsingComparator(key);
if(entry != null){
return entry.getValue();
}
return null;
}
public boolean constainKey(K key){
return get(key) != null;
}
public boolean containsValue(V value){
Iterator<Entry<K,V>> it = new MyTreeIterator<>(root);
while (it.hasNext()){
if(valEquals(it.next().getValue(),value)){
return true;
}
}
return false;
}
public final boolean valEquals(V v1,V v2){
return v2 == null ? v1 == null : v2.equals(v1);
}
public Entry<K,V> getFirstEntry(Entry<K,V> entry){
Entry<K,V> temp = entry;
if (temp != null){
while (temp.getLeft() != null){
temp = temp.getLeft();
}
}
return temp;
}
public Entry<K,V> getLastEntry(Entry<K,V> entry){
Entry<K,V> temp = entry;
if (temp != null){
while (temp.getRight() != null){
temp = temp.getRight();
}
}
return temp;
}
public V remove(K key){
Entry<K,V> p = getEntryUsingComparator(key);
if(p == null)
return null;
V oldValue = p.getValue();
root = deleteEntry(root,key);
return oldValue;
}
private final Entry<K,V> deleteEntry(Entry<K,V> p,K key) {
//two child
if (p == null) {
return null;
}
int compareResult = compare(p.getKey(), key);
//如果 结果为0, 表示找到 要删除节点
if (compareResult == 0) {
//case 1 :
if (p.getRight() == null && p.getLeft() == null) {
size--;
p = null;
} else if (p.getLeft() != null && p.getRight() == null) {
size--;
p = p.getLeft();
} else if (p.getLeft() == null && p.getRight() != null) {
size--;
p = p.getRight();
} else {
if (size % 2 == 0) {
//left max
Entry<K, V> leftMax = getLastEntry(p.getLeft());
//将p转为 leftMax
p.setKey(leftMax.getKey());
p.setValue(leftMax.getValue());
//
Entry<K, V> newLeft = deleteEntry(p.getLeft(), p.getKey());
p.setLeft(newLeft);
} else {
//right min
Entry<K, V> rightMin = getFirstEntry(p.getRight());
//将p转为 rightMin
p.setKey(rightMin.getKey());
p.setValue(rightMin.getValue());
Entry<K, V> newRight = deleteEntry(p.getRight(), p.getKey());
p.setRight(newRight);
}
}
} else if (compareResult < 0) {
//未找到节点,递归调用删除p的右子树
//将返回值作为新右子树
Entry<K, V> newRight = deleteEntry(p.getRight(), key);
p.setRight(newRight);
} else {
//未找到节点,递归调用删除p的左子树
//将返回值作为新左子树
Entry<K, V> newLeft = deleteEntry(p.getLeft(), key);
p.setLeft(newLeft);
}
//返回新树
return p;
}
public void levelOrder(){
Queue<Entry<K,V>> queue = new LinkedList<>();
queue.offer(root);
int preCount = 1;
int pCount = 0;
while(!queue.isEmpty()){
preCount--;
Entry<K,V> p = queue.poll();
System.out.print(p+"\t");
if(p.getLeft() != null){
queue.offer(p.getLeft());
pCount++;
}
if(p.getRight() != null){
queue.offer(p.getRight());
pCount++;
}
if(preCount == 0){
preCount = pCount;
pCount = 0;
System.out.println();
}
}
}
// static <K,V> TreeMap.Entry<K,V> successor(TreeMap.Entry<K,V> t) {
// //if t is null ,return null
// if (t == null)
// return null;
// //left --> root --> right print
// //so if this entry has right child,the next entry is right tree leftes child.
// else if (t.right != null) {
// TreeMap.Entry<K,V> p = t.right;
// while (p.left != null)
// p = p.left;
// return p;
// } else {
// //if this entry has no child, to father,and if this node is father right child,should find up level,
// //until root ... if allways not found , this entry has no next.
//
// TreeMap.Entry<K,V> p = t.parent;
// TreeMap.Entry<K,V> ch = t;
// while (p != null && ch == p.right) {
// ch = p;
// p = p.parent;
// }
// return p;
// }
// }
@Override
public Iterator<Entry<K,V>> iterator() {
return new MyTreeIterator<>(root);
}
}
好的,这就完成了一个简易的二叉搜索树了。