1、二叉搜索树的来历:array查询方便,但添加元素比较麻烦(元素个数超过容量需要扩容)。linked list方便添加元素,但是查询比较困难(只能从头一个个往下找)。因此发明了二叉搜索树这种数据结构,它是一种linked list结构,但集合了array和linked list的优点:方便查询;方便添加元素。
2、二叉搜索树的结构:就是一种linked list结构(这种class的filed里面至少包含一个reference能够指向未来的其他object地址),里面含2个reference(可以分别叫做left和right),一个可以比较大小的Key,一个值Value,以及整数N(表示该节点下面所连接的所有节点个数,包括该节点本身)。
3、二叉搜索树的特点:对于任意一个节点,它的Key值一定大于left指向节点的Key值,小于right指向节点的Key值。
具体如下:
public class BST<Value, Key extends Comparable<Key>>
{
//最头上的reference需要记一下。
Node root;
//Node结构,包含两个reference,整数N,以及Key和Value。
private Node
{
Node left, right;
int N;
Key key;
Value val;
Node(Key key, Value val)
{
this.key = key;
this.val = val;
N = 1;
}
}
//显示整个二叉树所有元素的个数
public int size() { return size(root); }
//以x为首的二叉树所有元素的个数
private int size(Node x)
{
if(x == null) return 0;
else return x.N;
}
public Value min() { return min(root).val; }
private Node min(Node x)
{
if(x.left == null) return x;
else return x.left;
}
public void deleteMin() { root = deleteMin(root); }
//循环找到一个节点,该节点的左侧为空,即最左侧节点。直接返回它的右侧。
private Node deelteMin(Node x)
{
if(x.left == null) return x.right;
else return deleteMin(x.left);
}
//暴露给客户端的方法。由于此操作要改变原来的结构,需要返回reference,与上一级产生关联。
public void addElement(Key key, Value val) { root = addElement(key, val, root); }
/**
1、如果用void的话,相当于可以找到需要插入元素的位置并添加一个新的元素,但是无法与上一级
元素产生关联,等于做无用功。
2、插入的新元素要么覆写已经存在的元素,要么插入到二叉树结构的最底层。
3、两个级别的分支判断:第一级两个分支,判断是否为null;第二级别在非null的基础上进行key
值的比较,又开辟3个小分支。
注意return需要写在第一级别层面。
4、最底层返回的是新创建元素(可能是已存在元素)的reference,然后可能将此reference关联给
上一级元素的left或right,然后而可能再将这个上一级元素的reference返回上上级元素的left
或right(它们早已相关联,看上去这次操作似乎没意义,但必须再执行一次)。
5、addElement方法返回的是某一级别的reference(第4条)。
*/
private Node addElement(Key key, Value val, Node x)
{
//如果本级节点为空,相当于找到了应该插入的位置,故应创建一个新元素,并返回该元素的reference
//给上一级,供其产生关联。若root本身是个空元素,那么就直接将新元素的reference传给root。但一
//般情况下,该reference供给上一级的right或left。
//第一级别第一分支,要有return。
if(x == null) return new Node(key, val);
//如果本级节点不为空,那么需要对key值进行比较,判断该元素的位置是不是本级节点,还是在本级节点
//的左边或右边。
//第一级别第二分支,下面开始第二级别的3个小分支。
int cmp = key.compareTo(x.key);
//如果要插入的元素就是本级节点,执行覆写操作。
if(cmp == 0) x.val = val;
//如果要插入元素的key值比较大,说明它的位置在本级节点的右侧。x的right向右侧关联。本级
//addElement(key, val, x.right)所返回的要么是个已经关联的元素的reference,要么就是
//新元素的reference。
else if(cmp > 0) x.right = addElement(key, val, x.right);
//向左侧关联。
else x.left = addElement(key, val, x.left);
//第一级别第二分支需要对N进行更新。不能直接用x.left.N,排除null错误。
//如果本层节点不为空,那么它的N值必会增加(或不变),必须进行更新。
x.N = size(x.left) + size(x.right) + 1;
//第一级别第二分支进展至此,本级left或right关联结束,N也更新完成,return本级reference本身,
//返回上一级。
return x;
}
//删除任意元素必然从root入手。
//同添加元素一样,都改变了原有数据结构,要return回reference供给上一级进行关联。否则做无用功(void)。
public void deleteElement(Key key) { root = deleteElement(key, root); }
/**
删除元素,就是将所要删除元素的reference重新赋值给合适的“新”元素(该元素已存在于二叉树中,只不过要
换位置),更新一下N值,再返回这个reference给上一级关联。
一共就3种情况:
1、要删除的元素下面没有其他子元素。
2、要删除的元素下面只有一个子元素。
3、要删除的元素下面有两个子元素。
更新原则是:
1、换成null。
2、换成这个子元素。
3、换成以右侧子元素为首二叉树的最元素,也就是所有子元素中刚刚比本级元素大的数。
*/
private Node deleteElement(Key key, Node x)
{
//若本级为空:要么没找到要删除的元素,返回null给上一级;要么root本身就是空元素,自然返回null。
//一级分支第一种情况。
if(x == null) return null;
//不为空是一级分支第二种情况。此时比较key值。
int cmp = key.compareTo(x.key);
//若比较小,说明要删除的元素在右侧。右侧返回可能为一个已经关联的元素的reference,也可能是null或换了位置的
//“新”元素。
if(cmp > 0) x.right = deleteElement(key, x.right);
else if(cmp < 0) x.left = deleteElement(key, x,left);
else
{
//右侧为空,左侧可能为空或不为空,对应1.2条,返回x.left就对了。
if(x.right == null) return x.left;
if(x.left== null) return x.right;
//x一会儿要赋给别的元素,先用个t暂时保存一下此时的x。
Node t = x;
//现在x是右侧最小值。
x = min(x.right);
//右侧二叉树先把最小元素抠掉,再把头上的元素作为x的right。
x.right = deleteMin(x.right);
//x的left还是原来的left,在t中保存着。
x.left = t.left;
}
x.N = size(x.left) + size(x.right) + 1;
return x;
}
}