数据结构(21)-二叉排序树

二叉排序树(Binary Sort Tree),又称二叉查找树,它或者是一棵空树,或者是一棵具有一下性质的树:

  1. 若它的左子树不空,则左?树上所有结点的值均?于它的根结点的值
  2. 若它的右子树不空,则右子树上所有结点的值均?于它的根结点的值
  3. 它的左右子树也分别是二叉排序树

二叉排序树采用了递归的定义方法。左子树结点一定比双亲结点小,右子树结点一定比双亲结点大。因为需要做删除插入操作,所有我们选择链式存储结构。

#define T_ERROR -1
#define T_OK 1
typedef int TStatus;
typedef int ElementType;

typedef struct BinaryNode {
    ElementType data;
    struct BinaryNode *leftChild, *rightChild;
} BinaryNode, *BinaryTree;

下面我们通过一个示例,来看看二叉排序树的操作。首先,我们把集合{62,88,58,47,35,73,51,99,37,93}按照二叉排序的规则生成一棵树。结果如下:

二叉排序树.png

二叉排序树的查找

在二叉排序树中查找数据可以使用递归来实现,首先我们将{key}与根结点比较,如果小于根结点的值就去左子树中查找,如果大于根结点的值就去右子树中查找。以此类推,即可得到结果。

代码如下:

/// 二叉排序树查找
/// @param bTree 需要查找的二叉树
/// @param key 查找的key
/// @param pTree 递归使用 指向bTree的双亲结点 初始值为NULL
/// @param sTree 查找成功即返回查找到的结点TStatus返回T_OK;查找不到则返回访问的最后一个结点TStatus返回T_ERROR
TStatus searchBinarySortTree(BinaryTree bTree, int key, BinaryTree pTree, BinaryTree *sTree) {
    if (!bTree) {
        // 递归到树的最后一个结点 还是没有找到
        *sTree = pTree;
        return T_ERROR;
    } else if (key == bTree->data) {
        // 查找命中
        *sTree = sTree;
        return T_OK;
    } else if (key < bTree->data) {
        // 如果key小于当前结点,则在左子树中找
        return searchBinarySortTree(bTree->leftChild, key, bTree, sTree);
    }
    // 如果key大于当前结点,则在右子树中找
    return searchBinarySortTree(bTree->rightChild, key, bTree, sTree);
}

二叉排序树的插入

在二叉排序树查找的基础上做插入就比较简单了,先判断需要插入的key是否存在二叉排序树中,如果存在则不能插入。如果不存在,判断是否是空树,若是空树,则把插入的结点作为根结点;如果不为空树,则只需要比较返回的结点值和key的大小,key若小于返回的结点值,则插入返回结点的左子树;key若大于返回的结点值,则插入返回结点的右子树。

代码如下:

/// 二叉排序树插入
/// @param bTree 二叉排序树
/// @param key 二叉排序树插入的key
TStatus insertBinarySortTree(BinaryTree *bTree, int key) {
    BinaryTree sTree;
    if (searchBinarySortTree(*bTree, key, NULL, &sTree) == T_ERROR) {
        // 没有从二叉排序树中查找到当前key
        BinaryTree iTree = (BinaryTree)malloc(sizeof(BinaryNode));
        iTree->data = key;
        iTree->leftChild = iTree->rightChild = NULL;
        
        if (sTree == NULL) {
            // 二叉树是空树
            *bTree = iTree;
        } else if (key < sTree->data) {
            // 不是空树 key比最后一个结点的值小 往最后一个结点的左子树插入
            sTree->leftChild = iTree;
        } else {
            // 不是空树 key比最后一个结点的值大 往最后一个结点的右子树插入
            sTree->rightChild = iTree;
        }
        
        return T_OK;
    }
    
    return T_ERROR;
}

二叉排序树的删除

相对于查找和插入,二叉排序树的删除就比较麻烦了,因为删除了结点之后,我们还得必须让剩下的树还得是二叉排序树。所以我们需要对删除的结点进行分类:

  • 删除的结点为叶子结点。可以直接删除不会对树造成影响。
  • 删除的结点只有左子树或者只有右子树。那我们就需要将子树替换原来的结点。
  • 删除的结点左右子树都有。我们先找到要删除的结点的前驱结点,然后用前驱结点来替换需要删除的结点,最后再删除前驱结点即可?;蛘哒业奖簧境岬愕暮蠹探岬?,然后使用后继结点替换被删除的结点,然后删除后继结点。

下面我们实现以下使用前驱结点删除的代码:

/// 删除结点bTree
void delete(BinaryTree *bTree) {
    BinaryTree tmpTree;
    
    if ((*bTree)->rightChild == NULL) {
        // 右子树为空 对接左子树
        tmpTree = *bTree;
        *bTree = (*bTree)->leftChild;
        free(tmpTree);
    } else if ((*bTree)->leftChild == NULL) {
        // 左子树为空 对接右子树
        tmpTree = *bTree;
        *bTree = (*bTree)->rightChild;
        free(tmpTree);
    } else {
        // 左右子树均不为空 都需要处理
        tmpTree = *bTree;
        // 找待删除结点的前驱
        // 找到被删除结点的左子树
        BinaryTree preTree = (*bTree)->leftChild;
        // 循环一直取左子树的右子树 最后一个右子树即为被删除结点的前驱结点
        while (preTree->rightChild) {
            tmpTree = preTree;
            preTree = preTree->rightChild;
        }
        // 循环之后preTree没有右子树了
        // tmpTree此时成了preTree的双亲结点 preTree是其右子树
        
        // 将被删除结点替换成前驱结点
        // 将被删结点的数据换成前驱的数据
        (*bTree)->data = preTree->data;
        if (tmpTree != *bTree) {
            // preTree的值赋给了要删除的结点 所以我们需要把preTree删掉 连接tmpTree右子树
            tmpTree->rightChild = preTree->leftChild;
        } else {
            // 连接tmpTree左子树
            tmpTree->leftChild = preTree->leftChild;
        }
        free(preTree);
    }
}

TStatus deleteBinarySortTree(BinaryTree *bTree, int key) {
    if (bTree == NULL) {
        return T_ERROR;
    }
    
    // 查找到要删除的key就删除对应的结点
    if (key == (*bTree)->data) {
        delete(bTree);
        return T_OK;
    } else if (key < (*bTree)->data) {
        return deleteBinarySortTree(&(*bTree)->leftChild, key);
    } else {
        return deleteBinarySortTree(&(*bTree)->rightChild, key);
    }
}

二叉排序树的操作依赖于树的形状,如果二叉排序树是平衡的,则其操作的时间复杂度为O(logn),如果是斜树的话,时间复杂度就是O(n)

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352