数据结构之树篇2——二叉排序(查找,搜索)树

引入

基本性质:

二叉排序树(又叫二叉搜索、查找树)

  1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 左、右子树也分别为二叉排序树。
  4. 不允许有键值相同结点。

二分查找与二叉排序树

​ 二分查找也称为折半查找,要求原线性表有序,它是一种效率很高的查找方法。如果在需要进行频繁修改的表中采用二分查找,其效率也是非常低下的,因为顺序表的修改操作效率低。如果只考虑频繁的修改,我们可以采用链表。然而,链表的查找效率又非常低。综合这两种数据结构的优势,二叉查找树(Binary Sort/Search Tree)诞生了。

给定一串数\((65,32,87,46,71,98,39)\),使用插入构造法,步骤如下:

1470173-20191102233805064-1976109015.png

构建

根据上面的图示过程,可以看出二叉排序树适合采用二叉链表作为存储结构。中序遍历二叉排序树可得到一个依据关键字的有序序列, 一个无序序列可以通过构造一棵二叉排序树变成一个有序序列,构造树的过程即是对无序序列进行排序的过程。每次插入的新的结点都是二叉排序树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。

结构体定义

typedef struct BinarySortTreeNode{
    int data;
    struct BinarySortTreeNode *Left = NULL;
    struct BinarySortTreeNode *Right = NULL;
}*BinarySortTree;

非递归算法

BinarySortTree CreatBStree(int *a,int length) {
    BinarySortTree BST = new BinarySortTreeNode;
    BST->data = *a++;    
    for (int i = 1; i < length; i++) {
        BinarySortTreeNode *pre = NULL, *p = BST, *Node=new BinarySortTreeNode;
        while (p) {
            pre = p;
            p = *a < p->data ? p->Left : p->Right;
        }
        Node->data = *a;
        *a < pre->data ? pre->Left = Node : pre->Right = Node;
        a++;
    }
    return BST;
}

递归写法

BinarySortTree InsertBStree(BinarySortTree BST, int x){
    if (BST == NULL){
        BST = new BinarySortTreeNode;
        BST->data = x;
        return BST;
    }    
        if (x < BST->data)
            BST->Left = InsertBStree(BST->Left, x);
        else
            BST->Right = InsertBStree(BST->Right, x);
        return BST;
}
BinarySortTree CreatBStByRecursion(int *a,int length) {
    BinarySortTree BST = NULL;
    for (int i = 0; i < length; i++)
        BST = InsertBStree(BST, a[i]);
    return BST;
}

插入与删除

插入节点

​ 这个算法加个循环就可以作为 " 建立二叉排序树 " 的算法递归写法。

BinarySortTree InsertBStree(BinarySortTree BST, int x){
    if (BST == NULL){
        BST = new BinarySortTreeNode;
        BST->data = x;
        return BST;
    }    
        if (x < BST->data)
            BST->Left = InsertBStree(BST->Left, x);
        else
            BST->Right = InsertBStree(BST->Right, x);
        return BST;
}
BinarySortTree CreatBStByRecursion(int *a,int length) {
    BinarySortTree BST = NULL;
    for (int i = 0; i < length; i++)
        BST = InsertBStree(BST, a[i]);
    return BST;
}

查找节点

  找不到返回NULL,找到返回该节点。

BinarySortTreeNode* BSTreeFind(BinarySortTree BST,int x) {
    BinarySortTree p = BST;
    while (p) {
        if (x == p->data) break;
        p = x > p->data ? p->Right : p->Left;
    }
    return p;
}

删除节点

  1. 被删除的结点是叶子,将父节点的 left 或 right 指针域设置为NULL。

  2. 被删除的结点只有左子树或者只有右子树,用该节点的 left 或 right 重新接上来。和删除单链表节点类似。

  (第一种情况,写的时候可以和第二种合并起来)

  3. 被删除的结点既有左子树,也有右子树,需要按照二叉排序树的性质从其左子树或者有子树中选择节点补到待删除节点的位置。(选左、选右都可以)

如果从左子树中选,就应该选择左子树中最右边的那个叶子节点(这里肯定是叶子,如果不是叶子,那么就不是最右边的节点)

如果从右子树中选,就应该选择右子树中最左边的那个叶子节点。

写法1 :

​ 这个写法更适合JAVA之类,有垃圾回收的语言,可以把代码里的那些delete省去,但是必须要注意的是:必须这样调用 Tree = BSTDel ( Tree , delNum) ; 就像函数里面的递归一样形式,否则删除只有一个子树的根节点的时候,会出错。

//获得子树中的最大值
int GetMax(BSTree root) {
    if (root->Right == NULL) return root->data;
    return GetMax(root->Right);
}
//删除子树中的最大值
BSTree DelMax(BSTree root) {
    if (root->Right == NULL) {
        BSTNode *t= root->Left;
        delete root;
        return t;
    }
    root->Right = DelMax(root->Right);
    return root;
}
BSTree BSTDel(BSTree root,int x) {
    if (root == NULL) return NULL;
    if (root->data == x) {
        //被删除的结点只有左子树或右子树,或没有子树
        if (! root->Right || !root->Left) {
            BSTNode *t = !root->Right ? root->Left : root->Right;
            delete root;
            return t;
        }
        //删除既有左子树,也有右子树的结点
        root->data = GetMax(root->Left);
        root->Left = DelMax(root->Left);
    }
    else if (x > root->data) root->Right = BSTDel(root->Right, x);
    else  root->Left = BSTDel(root->Left, x);
    return root;
}

写法2:

指针很强大,传进 BSTree *root,可以直接修改 *root 的值。(也很麻烦,上面的写法,如果是JAVA,完全不用考虑delete的问题,代码量可以更短。)

void BSTDel(BSTree *root, int x){
    if (!*root) {
        cout << "找不着" << endl;
        return;
    }
    BSTree p = *root;
    if (p->data == x) {
        if (!p->Right || !p->Left)
            *root = !p->Right ? p->Left : p->Right;
        else{
                BSTNode *parent = p->Left, *q = p->Left;
                if (!q->Right)    q->Right = p->Right;
                else {
                    while (q->Right) {
                        parent = q;
                        q = q->Right;
                    }
                    parent->Right = q->Left;
                    q->Left = p->Left;
                    q->Right = p->Right;
                }
                *root = q;
        }
        delete p;
    }
    else if (x > p->data) //向右找
        BSTDel(&(p->Right), x);
    else if (x < p->data) //向左找
        BSTDel(&(p->Left), x);
}

丢人记录:

//下面这是最开始写的,那个时候还不能理解BSTree *root是个什么含义。
//在删除只含单个子树的根节点时会出错。它是错的,但我还是要往上放,666。
void BSTreeDelete(BSTree root, int x) {
    if (!BSTreeFind(root, x)) return;
    BSTNode *pre = NULL, *p = root;
    while (x != p->data) {
        pre = p;
        p = x < p->data ? p->Left : p->Right;
    }
    //1. 左右子树都为空
    if (p->Left == NULL && p->Right == NULL) {
        p->data < pre->data ? pre->Left = NULL : pre->Right = NULL;
        delete p;
        return;
    }
    //2. 左子树或右子树为空
    if (p->Left == NULL) {
        p->data < pre->data ? pre->Left = p->Right : pre->Right = p->Right;
        delete p;
    }
    else if (p->Right == NULL) {
        p->data < pre->data ? pre->Left = p->Left : pre->Right = p->Left;
        delete p;
    }
    //3.左右子树都不为空
    else {
        BSTNode *pre = p->Left, *q = p->Left;
        while (q->Right) {
            pre = q;
            q = q->Right;
        }
        p->data = q->data;
        if (q == p->Left)
            p->Left = p->Left->Left;
        else //最右边的这个节点只可能有左子树
            pre->Right = q->Left;
        delete q;
    }
}

  一般二叉排序树是不允许重复数据的,如果实际应用中遇到相同的值,那么向左向右插入都可以,只要保证树在中序遍历时是非严格单调递增即可。但是代码中需要注意一下,否则找爹的时候会找错。。。

BSTree BSTCreat(int *a,int length) {
    BSTree T = new BSTNode;
    T->data = *a++;    
    T->Left = NULL;
    T->Right = NULL;
    for (int i = 1; i < length; i++) {
        BSTNode *pre = NULL, *p = T, *Node=new BSTNode;
        while (p) {
            pre = p;
            p = *a < p->data ? p->Left : p->Right;
        }
        Node->data = *a;
        Node->Left = NULL; Node->Right = NULL;
        *a < pre->data ? pre->Left = Node : pre->Right = Node;
        a++;
    }
    return T;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
描述 用函数实现如下二叉排序算法: (1) 入新结点 (2) 前序、中序、后序遍历二叉 (3) 中序遍历的非递归算法 (4) 层次遍历二叉 (5) 在二叉查找给定关键字(函数返回值为成功1,失败0) (6) 交换各结点的左右子 (7) 求二叉的深度 (8) 叶子结点数 Input 第一行:准备建的结点个数n 第行:输入n个整数,用空格分隔 第三行:输入待查找的关键字 第四行:输入待查找的关键字 第五行:输入待入的关键字 Output 第一行:二叉的先序遍历序列 第行:二叉的中序遍历序列 第三行:二叉的后序遍历序列 第四行:查找结果 第五行:查找结果 第六行~第八行:入新结点后的二叉的先、中、序遍历序列 第九行:入新结点后的二叉的中序遍历序列(非递归算法) 第十行:入新结点后的二叉的层次遍历序列 第十一行~第十三行:第一次交换各结点的左右子后的先、中、后序遍历序列 第十四行~第十六行:第次交换各结点的左右子后的先、中、后序遍历序列 第十七行:二叉的深度 第十八行:叶子结点数 Sample Input 7 40 20 60 18 50 56 90 18 35 30 Sample Output 40 20 18 60 50 56 90 18 20 40 50 56 60 90 18 20 56 50 90 60 40 1 0 40 20 18 30 60 50 56 90 18 20 30 40 50 56 60 90 18 30 20 56 50 90 60 40 18 20 30 40 50 56 60 90 40 20 60 18 30 50 90 56 40 60 90 50 56 20 30 18 90 60 56 50 40 30 20 18 90 56 50 60 30 18 20 40 40 20 18 30 60 50 56 90 18 20 30 40 50 56 60 90 18 30 20 56 50 90 60 40 4 4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值