文章目录
知识点:
1.英文名Binary Search Tree,叫 二叉排序树 ,也叫 二叉搜索树。
2. 二叉搜索树 或者是一棵空树,或者是具有下列性质的二叉树:
- 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码互不相同。
- 左子树(如果存在)上所有结点的关键码都小于根结点的关键码。
- 右子树(如果存在)上所有结点的关键码都大于根结点的关键码。(即左<根<右)
- 左子树和右子树也是二叉搜索树。
- 没有键值相等的结点。
3.如果对一棵二叉搜索树进行中序遍历,可以按从小到大的顺序,将各结点关键码排列起来,所以也称二叉搜索树为二叉排序树。
4.大根堆、小根堆要求其根节点为最大 或 最小。和二叉搜索树是不同的。
结构设计:
class BSTree {
class BstNode {
int key;
//即三叉链表
BstNode leftchild;
BstNode parent;
BstNode rightchild;
//构造函数
public BstNode() {
key = 0;
leftchild = parent = leftchild = null;
}
public BstNode(int x) {
key = x;
leftchild = parent = leftchild = null;
}
public BstNode(int x,BstNode left,BstNode pa,BstNode right) {
key = x;
leftchild = left;
parent = pa;
rightchild = right;
}
}
BstNode root;
BstNode cur;
public BSTree() {
root = null;
cur = null;
}
}
查询
- 若找到了值对应的结点就用cur指向,并返回真
- 二叉搜索树:左 < 根 < 右 (若值比根小,在左边找没有的话,右边更大肯定更没有)
递归实现
//递归 处理找
private BstNode Search(BstNode ptr,int kx) {
if(ptr == null || ptr.key == kx) {
return ptr;
}else if(kx < ptr.key) {
return Search(ptr.leftchild, kx);
}else {
return Search(ptr.rightchild, kx);
}
}
//处理结果
public boolean SearchValue(int kx) {
boolean res = false;
cur = Search(root,kx);
if(cur != null) {
res = true;
}
return res;
}
非递归实现
//非递归
public boolean FindValue(int kx) {
cur = root;
boolean res = false;
//层层往下找
while(cur != null && cur.key != kx) {
cur = kx < cur.key ? cur.leftchild : cur.rightchild;
}
//处理结果
if(cur != null && cur.key == kx) {
res = true;
}
return res;
}
插入
在插入之前,先使用搜索算法在树中检查要插入元素有还是没有。
- 搜索成功: 树中已有这个元素,不再插入。
- 搜索不成功: 树中原来没有关键码等于给定值的结点,把新元素加到搜索操作停止的地方。
// 插入
public boolean Insert(int kx)
{
boolean res = true;
//空树时
if(root == null)
{
root = new BstNode(kx);
return res;
}
cur = root;
BstNode pa = null;
//在合适的分支找到底为止
while(cur != null && cur.key != kx) {
pa = cur;//找cur的孩子,所以升级当爸了
cur = kx < cur.key ? cur.leftchild : cur.rightchild;
}
//有该值,就不插入(返回false,因为不允许重复)
if(cur != null && cur.key == kx) {
res = false;
}
else
{
cur = new BstNode(kx);
cur.parent = pa;//子指向父
//没有该值,就将新结点挂在上面查找的最后一个位置的左或右孩子
if(cur.key < pa.key)
{
pa.leftchild = cur;//父指向子
}
else
{
pa.rightchild = cur;
}
}
return res;
}
建树
给出二叉树的顺序存储,通过插入可以从头建立一棵树
//二叉树顺序存储
int []ar={53,17,78,9,45,65,87,23,81,94,88,92};
BSTree myt = new BSTree();
//用插入方式建立二叉排序树
for(int i = 0;i<ar.length;++i)
{
myt.Insert(ar[i]);
}
中序遍历
递归遍历代码和B树的一样,很简单。
非递归
//非递归的中序遍历
public void NiceInOrder()
{
//先合法性判断
if(root == null)
return ;
Stack<BstNode> st = new Stack<BstNode>();
cur = root;
while(cur != null || !st.isEmpty())
{
while(cur != null)
{
st.push(cur);//栈:先进后出
cur = cur.leftchild;
}
// 即把左到底的最后的孩子先输出
cur = st.pop();
System.out.print(cur.key+ " ");
//即左 根 右的遍历顺序
cur = cur.rightchild;
}
System.out.println();
}
不使用栈或队列
找中序序列直接后继
private BstNode First(BstNode ptr) {
//左到底 即以ptr为根的树的min
while (ptr != null && ptr.leftchild != null) {
ptr = ptr.leftchild;
}
return ptr;
}
//找中序序列的直接后继
private BstNode Next_InOrder(BstNode ptr)
{
if(ptr == null)
return null;
if(ptr.rightchild != null)
{
return First(ptr.rightchild);//直接后继为 右子树的最左下结点
}
else
{
BstNode pa = ptr.parent;
// 45的后继是53 94后继为空
while(pa != null && pa.leftchild != ptr)
{
ptr = pa;
pa = ptr.parent;
}
return pa;
}
}
使用First和Next函数实现中序遍历
private void NiceInOrder_For(){
for(BstNode p=First(root);p!=null;p=Next_InOrder(p)){
System.out.print(p.key+" ");
}
System.out.println();
}
非递归中序倒序遍历
找中序序列直接前驱
//找中序序列直接前驱
private BstNode Last(BstNode ptr) {
//右到底 相当于Max
while (ptr != null && ptr.rightchild != null) {
ptr = ptr.rightchild;
}
return ptr;
}
private BstNode Prev_InOrder(BstNode ptr) {
if (ptr == null)
return null;
if (ptr.leftchild != null) {
//左子树的最右下 53的前驱是45
return Last(ptr.leftchild);
} else {
BstNode pa = ptr.parent;
//9没有前驱 65的前驱是53
while (pa != null && pa.rightchild != ptr) {
ptr = pa;
pa = ptr.parent;
}
return pa;
}
}
利用Last和Prev实现逆序遍历
//非递归实现逆向遍历
private void Reverse_NiceInOrder(){
//如果有head结点,就可以不用!=null作为结束标记
for(BstNode p=Last(root);p!=null;p=Prev_InOrder(p)){
System.out.print(p.key+" ");
}
System.out.println();
}
将二叉排列树 按照中序序列 改成 二叉链表
- 不给parent属性,将二叉排列树 按照中序序列 改成 二叉链表,
- 即左孩子是直接前驱,右孩子是直接后继
// 非递归 中序遍历基础上将二叉树改成二叉链表
public void InOrderList() {
if (root == null)
return;
Stack<BstNode> st = new Stack<BstNode>();
cur = root;
BstNode pr = null;
while (cur != null || !st.isEmpty()) {
//左
while (cur != null) {
st.push(cur);
cur = cur.leftchild;
}
//根(即访问的结点)
cur = st.pop();//一个元素能且只能进栈一次
//在非递归实现中序遍历时是pop后要将cur打印,这里不需要,只要处理一个当前结点与上一个结点的关系即可
//即第一次进入while时,此时cur是第一个结点
if(pr == null)
{
root = cur;
pr = cur;//最近一次访问的结点
}
else
{
pr.rightchild = cur;//即pr的直接后继 是 这次访问的结点cur
cur.leftchild = pr;// 这次访问的结点cur的直接前驱 是 pr
pr = cur;
}
//右
cur = cur.rightchild;
}
System.out.println();
}
//将上面转为链表的二叉树打印查看
public void PrintList()
{
BstNode p = root;
//最后一个节点的next没有设置,所以为null
while(p != null)
{
System.out.print(p.key + " ");
p = p.rightchild; //next
}
System.out.println();
}
判断是不是二叉排序树
非递归
//非递归 判断是不是二叉排序树(即在中序遍历的基础上,将输出值加个判断)(wps图,带个例子就明白了)
public boolean Is_BSTree()
{
boolean res = true;
if(root == null)
return res;
Stack<BstNode> st = new Stack<BstNode>();
cur = root;
BstNode pre = null;//按中序遍历顺序 的 上一个访问的结点,
//直到找完所有的结点
while(cur != null || !st.isEmpty())
{
//左到底(若cur为空,直接执行下面的pop)
while(cur != null)
{
st.push(cur);//共用一个栈,都往里面压
cur = cur.leftchild;
}
//根 并 判断是否满足 中序遍历依次是由小到大的顺序
cur = st.pop();//先进后出(第一次弹出叶子节点,肯定 无在他之前弹出来的元素)
if(pre != null && pre.key >= cur.key)
{
res = false;
break;//一旦发现,立马退出while,接着退出唯一的一层Is_BSTree
}
pre = cur;
// 右
cur = cur.rightchild;
}
return res;
}
删除数据
- 在二叉搜索树中删除一个结点时,必须将因删除结点而断开的二叉链表重新链接起来,同时确保二叉搜索树的性质不会失去。
- 为保证在执行删除后,树的搜索性能不至于降低,还需要防止重新链接后树的高度增加。
所有情况:
1.Root=null
2.未找到数据
3.数据找到了
-
删除叶结点,只需将其双亲结点指向它的指针清零,再释放它即可。
-
被删结点只有左子树,可以拿它的左子结点顶替它的位置,再释放它(也可以用左子节点的值覆盖被删除结点,然后删子节点,再将子节点的的子树挂在被删除结点下)。
-
被删结点只有右子树,可以拿它的右子结点顶替它的位置,再释放它。
-
被删结点左、右子树都存在,可以在它的右子树中寻找中序遍历的下一个结点(即找直接后继,其实在 左最下),再来处理这个结点的删除问题(若使用直接后继的值覆盖需删除结点,然后删除直接后继即可;若直接后继为根,还需要将直接后继的孩子作为新root;还要将直接后继的子树重挂在直接后继的父节点下)。
(以上几种情况,记前驱 后继都不够直观,记位置左子 右子 或 右子树最左下)
代码
//左到底
private BstNode Next(BstNode ptr)
{
while(ptr != null && ptr.leftchild != null)
{
ptr = ptr.leftchild;
}
return ptr;
}
// 删除指定值(唯一)的结点
public boolean Remove(int kx)
{
boolean res = false;
//空树
if(root == null)
return res;
BstNode pa = null;
cur = root;
//按中序找该节点,有就用cur指向,pa指向其父
while(cur != null && cur.key != kx)
{
pa = cur;
cur = kx < cur.key ? cur.leftchild:cur.rightchild;
}
//没找着
if(cur == null)
return res;
//双分支(即左右孩子都有时)
if(cur.leftchild != null && cur.rightchild != null)
{
BstNode nt = Next(cur.rightchild);//nt为后继结点(即右子树的最左 且 最下的节点)
cur.key = nt.key;//用后继结点的值 覆盖 要删除的结点值(然后删除后继节点nt即可)
pa = nt.parent;//后继节点(nt)可能为叶子或有右子树,所以需再处理下 后继节点的父亲(pa)的child
cur = nt;//cur指向后继结点
}
/*
若进了上面的if, cur指向原本的后继节点(值被用了,现在他可以作为被删除结点处理了)只能为 叶 或 只有右子树
若没进if,cur是要删除的结点 且 没有双孩子,只能为 叶子 或 单分支(只有右子树 只有左子树)
*/
BstNode child = (cur.leftchild != null) ? cur.leftchild:cur.rightchild;
//待删除结点cur为叶时,上面处理完child为null cur有右子树时,需要将右子树提上 挂到 后继节点的父亲下
if(child != null)
child.parent = pa; //cur的孩子 指向 cur的父!!!!!!!!!!
//即待删除结点是根节点,其值提到被删除结点处后,然后结点便要被删除了,此时需要让root指向待删除结点的孩子
if(pa == null)
{
root = child;
}
else {
/*
cur为叶时,child为null,其父指向child即指向空
cur为单分支时,将cur的子树 重挂在其父的左或右边
父 指向 子的子(这下cur不指向谁,也没人指向,为空引用,会被自动垃圾回收)!!!!!!!!!!
别用child.key<pa.key,因为child有可能为null.
*/
//即待删除结点在左分支时,把待删除结点的子树 往上提,重新挂在 待删除结点父亲的左边
if (cur.key < pa.key ) {
pa.leftchild = child;
} else {
pa.rightchild = child;
}
}
return true;
}