二叉查找树(BST)

二叉查找树(Binary Search Tree)又叫二叉排序树(Binary Sort Tree),它是一种数据结构,支持多种动态集合操作,如 Search、Insert、Delete、Minimum 和 Maximum 等。

二叉查找树要么是一棵空树,要么是一棵具有如下性质的非空二叉树:

  1. 若左子树非空,则左子树上的所有结点的关键字值均小于根结点的关键字值。

  2. 若右子树非空,则右子树上的所有结点的关键字值均大于根结点的关键字值。

  3. 左、右子树本身也分别是一棵二叉查找树(二叉排序树)。

可以看出,二叉查找树是一个递归的数据结构,且对二叉查找树进行中序遍历,可以得到一个递增的有序序列。

首先,我们来定义一下 BST 的结点结构体,结点中除了 key 域,还包含域 left, right 和 parent,它们分别指向结点的左儿子、右儿子和父节点:

typedef struct Node
{
int key;
Node* left;
Node* right;
Node* parent;
} *BSTree;

一、BST的插入与构造

二叉查找树作为一种动态结构,其特点是树的结构通常不是一次生成的,而是在查找过程中,当树中不存在结点的关键字等于给定值时再进行插入。

由于二叉查找树是递归定义的,插入结点的过程是:若原二叉查找树为空,则直接插入;否则,若关键字 k 小于根结点关键字,则插入到左子树中,若关键字 k 大于根结点关键字,则插入到右子树中。

/**
* 插入:将关键字k插入到二叉查找树
*/
int BST_Insert(BSTree &T, int k, Node* parent=NULL)
{
if(T == NULL)
{
T = (BSTree)malloc(sizeof(Node));
T->key = k;
T->left = NULL;
T->right = NULL;
T->parent = parent;
return 1; // 返回1表示成功
}
else if(k == T->key)
return 0; // 树中存在相同关键字
else if(k < T->key)
return BST_Insert(T->left, k, T);
else
return BST_Insert(T->right, k, T);
}

构造一棵二叉查找树就是依次输入数据元素,并将它们插入到二叉排序树中的适当位置。具体过程是:每读入一个元素,就建立一个新结点;若二叉查找树为空,则新结点作为根结点;若二叉查找树非空,则将新结点的值与根结点的值比较,如果小于根结点的值,则插入到左子树中,否则插入到右子树中。

/**
* 构造:用数组arr[]创建二叉查找树
*/
void Create_BST(BSTree &T, int arr[], int n)
{
T = NULL; // 初始时为空树
for(int i=0; i<n; ++i)
BST_Insert(T, arr[i]);
}

注意,插入的新结点一定是某个叶结点。另外,插入操作既可以递归实现,也可以使用非递归(迭代)实现。通常来说非递归的效率会更高。

/**
* 非递归插入:将关键字k插入到二叉查找树
*/
int BST_Insert_NonRecur(BSTree &T, int k)
{
Node* pre = NULL; // 记录上一个结点
Node* t = T;
while(t != NULL)
{
pre = t;
if(k < t->key)
t = t->left;
else if(k > t->key)
t = t->right;
else
return 0;
} Node* node = (Node*)malloc(sizeof(Node));
node->key = k;
node->left = NULL;
node->right = NULL;
node->parent = pre; if(pre == NULL)
T = node;
else
{
if(k < pre->key)
pre->left = node;
else
pre->right = node;
}
return 1;
}

二、BST的查找

对于二叉查找树,最常见的操作就是查找树中的某个关键字。除了Search操作外,二叉查找树还能支持如 Minimum(最小值)、Maximum(最大值)、Predecessor(前驱)、Successor(后继)等查询。对于高度为 h 的树,这些操作都可以在 Θ(h) 时间内完成。

1. 查找

BST 的查找是从根结点开始,若二叉树非空,将给定值与根结点的关键字比较,若相等,则查找成功;若不等,则当给定值小于根结点关键字时,在根结点的左子树中查找,否则在根结点的右子树中查找。显然,这是一个递归的过程。

/**
* 递归查找:返回指向包含关键字k的结点的指针
*/
Node* BST_Search(BSTree T, int k)
{
if(T == NULL || k == T->key)
return T;
if(k < T->key)
return BST_Search(T->left, k);
else
return BST_Search(T->right, k);
}

也可以使用非递归的实现:

/**
* 非递归查找:返回指向包含关键字k的结点的指针
*/
Node* BST_Search_NonRecur(BSTree T, int k)
{
while(T != NULL && k != T->key)
{
if(k < T->key)
T = T->left;
else
T = T->right;
}
return T;
}

2. 最大值与最小值

由二叉查找树的性质可知,最左下结点即为关键字最小的结点,最右下结点即为关键字最大的结点。此过程无需比较,只需要沿着最左和最右的路径查找下去,直到遇到 NULL 为止。

/**
* 最小值:查找二叉查找树中关键字最小的结点
*/
Node* BST_Minimum(BSTree T)
{
while(T->left != NULL)
T = T->left;
return T;
} /**
* 最大值:查找二叉查找树中关键字最大的结点
*/
Node* BST_Maximum(BSTree T)
{
while(T->right != NULL)
T = T->right;
return T;
}

3. 前驱与后继

给定一个二叉查找树的结点,求出它在中序遍历中的前驱与后继。如果所有的关键字均不相同,则某结点 x 的后继是:

  • 若结点 x 的右子树不为空,则 x 的后继就是它的右子树中关键字值最小的结点;

  • 若结点 x 的右子树为空,为了找到其后继,从结点 x 开始向上查找,直到遇到一个祖先结点 y,它的左儿子也是结点 x 的祖先,则结点 y 就是结点 x 的后继。如下图

/**
* 后继:查找给定结点在中序遍历中的后继结点
*/
Node* BST_Successor(Node* node)
{
if(node->right != NULL)
return BST_Minimum(node->right);
Node* p = node->parent;
while(p!=NULL && p->right == node)
{
node = p;
p = p->parent;
}
return p;
}

求前驱(predecessor)的过程对称,对于某个结点 x ,它的前驱是:

  • 若结点 x 的左子树不为空,则 x 的前驱是它的左子树中关键字值最大的结点;

  • 若结点 x 的左子树为空,为了找到其前驱,从结点 x 开始向上查找,直到遇到一个祖先结点 y,它的右儿子也是结点 x 的祖先,则结点 y 就是结点 x 的前驱。

/**
* 前驱:查找给定结点在中序遍历中的前驱结点
*/
Node* BST_Predecessor(Node* node)
{
if(node->left != NULL)
return BST_Maximum(node->left);
Node* p = node->parent;
while(p!=NULL && p->left == node)
{
node = p;
p = p->parent;
}
return p;
}

之所以在这里讨论如何求中序序列的后继,主要是为了后面讲删除操作做铺垫。

三、BST的删除

二叉查找树的删除操作是相对复杂一点,它要按 3 种情况来处理:

  • 若被删除结点 z 是叶子结点,则直接删除,不会破坏二叉排序树的性质;

  • 若结点 z 只有左子树或只有右子树,则让 z 的子树成为 z 父结点的子树,替代 z 的位置;

  • 若结点 z 既有左子树,又有右子树,则用 z 的后继(Successor)代替 z,然后从二叉查找树中删除这个后继,这样就转换成了第一或第二种情况。

void BST_Delete(BSTree &T,Node* z)
{
if(z->left == NULL && z->right == NULL)
{
if(z->parent != NULL)
{
if(z->parent->left == z)
z->parent->left = NULL;
else
z->parent->right = NULL;
}
else
{
T = NULL; // 只剩一个结点的情况
}
free(z);
}
else if(z->left != NULL && z->right == NULL)
{
z->left->parent = z->parent;
if(z->parent != NULL)
{
if(z->parent->left == z)
z->parent->left = z->left;
else
z->parent->right = z->left;
}
else
{
T = z->left; // 删除左斜单支树的根结点
}
free(z);
}
else if(z->left == NULL && z->right != NULL)
{
z->right->parent = z->parent;
if(z->parent != NULL)
{
if(z->parent->left == z)
z->parent->left = z->right;
else
z->parent->right = z->right;
}
else
{
T = z->right; // 删除右斜单支树的根结点
}
free(z);
}
else
{
Node* s = BST_Successor(z);
z->key = s->key; // s的关键字替换z的关键字
BST_Delete(T, s); // 转换为第一或第二种情况
}
}

对于一个高度为 h 的二叉查找树来说,删除操作和插入操作一样,都可以在 Θ(h) 时间内完成。

四、随机构造的二叉查找树

二叉查找树可以实现任何一种基本的动态集合操作,且各基本操作的运行时间都是 Θ(h)。当树的高度较低时,这些操作执行的较快;但是,当树的高度较高时,性能会变差。比如,如果各元素是按严格增长的顺序插入的,那么构造出来的树就是一个高度为 n-1 的链。 为了尽量减少这种最坏情况的出现,我们可以随机地构造二叉查找树,即随机地将各关键字插入一棵初始为空的树来构造 BST。

//#include <cstdlib>
//#include <ctime>
/**
* 随机构造二叉查找树
*/
void Create_BST(BSTree &T, int arr[], int n)
{
T = NULL;
// 随机遍历数组,进行插入操作
srand(time(NULL));
for(int i=n-1; i>=0; --i)
{
int j = rand() % (i+1);
BST_Insert(T, arr[j]);
swap(arr[j], arr[i]);
}
}

附:随机遍历数组

在随机构造二叉查找树时,需要解决 随机遍历数组 的问题,即随机遍历一个数组中的所有元素,既不重复也不遗漏。这里能想到的一种思路是:先随机生成0...n-1之间的一个数,然后与数组最后一个数交换,然后再随机生成0...n-2之间的一个数,与数组倒数第二个数交换,直到整个数组遍历结束。显然这个算法的时间复杂度是
O(n):

#include <iostream>
#include <cstdlib> // srand rand
#include <ctime> // time
using namespace std; void swap(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
} /**
* 随机遍历数组
*/
void Traverse_Random(int arr[], int n)
{
srand(time(NULL));
for(int i=n-1; i>=0; --i)
{
int j = rand() % (i+1);
cout << arr[j] << " "; // 输出
swap(arr[j], arr[i]);
}
} int main()
{
int arr[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Traverse_Random(arr, 9);
getchar();
return 0;
}

(全文完)

个人站点:http://songlee24.github.com

二叉查找树(BST)的更多相关文章

  1. 二叉查找树(BST)

    二叉查找树(BST):使用中序遍历可以得到一个有序的序列

  2. 查找系列合集-二叉查找树BST

    一. 二叉树 1. 什么是二叉树? 在计算机科学中,二叉树是每个结点最多有两个子树的树结构. 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree). 二叉树常 ...

  3. 二叉查找树BST 模板

    二叉查找树BST 就是二叉搜索树 二叉排序树. 就是满足 左儿子<父节点<右儿子 的一颗树,插入和查询复杂度最好情况都是logN的,写起来很简单.   根据BST的性质可以很好的解决这些东 ...

  4. [学习笔记] 二叉查找树/BST

    平衡树前传之BST 二叉查找树(\(BST\)),是一个类似于堆的数据结构, 并且,它也是平衡树的基础. 因此,让我们来了解一下二叉查找树吧. (其实本篇是作为放在平衡树前的前置知识的,但为了避免重复 ...

  5. 【查找结构 2】二叉查找树 [BST]

    当所有的静态查找结构添加和删除一个数据的时候,整个结构都需要重建.这对于常常需要在查找过程中动态改变数据而言,是灾难性的.因此人们就必须去寻找高效的动态查找结构,我们在这讨论一个非常常用的动态查找树— ...

  6. 二叉查找树(BST)的实现

    一.二叉树介绍 二叉查找树(Binary Search Tree,BST),又称二叉排序树,也称二叉搜索树,它或者是一颗空树,或者具有如下性质的树:若它的左子树不为空,则左子树上所有节点的值都小于根节 ...

  7. 3.2 符号表之二叉查找树BST

    一.插入和查找 1.二叉查找树(Binary Search Tree)是一棵二叉树,并且每个结点都含有一个Comparable的键,保证每个结点的键都大于其左子树中任意结点的键而小于其右子树的任意结点 ...

  8. 从一段简单算法题来谈二叉查找树(BST)的基础算法

    先给出一道很简单,喜闻乐见的二叉树算法题: 给出一个二叉查找树和一个目标值,如果其中有两个元素的和等于目标值则返回真,否则返回假. 例如: Input: 5 / \ 3 6 / \ \ 2 4 7 T ...

  9. 二叉查找树BST

    每棵子树头节点的值都比各自左子树上所有节点值要大,也都比各自右子树上所有节点值要小. 二叉查找树的中序遍历序列一定是从小到大排列的. 一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点.相应 ...

  10. K:二叉查找树(BST)

    相关介绍:  二叉查找树(英语:Binary Search Tree),也称二叉搜索树.有序二叉树(英语:ordered binary tree),排序二叉树(英语:sorted binary tre ...

随机推荐

  1. day25-2 OSI协议和socket抽象层

    目录 OSI协议 物理层 数据链路层 以太网协议 Mac地址 广播地址 网络层 获取对方Mac地址(ARP协议) 传输层 TCP协议 UDP协议 应用层 socket抽象层 OSI协议 互联网的本质就 ...

  2. zuul 网关

    1.网关的作用 网关可以拦截客户端所有请求,对该请求进行权限控制,负载均衡.日志管理.接口调用监控等操作. 1)网关对所有服务会话进行拦截 2)网关安全控制,统一异常处理,XXS.SQL注入 3)权限 ...

  3. sql中递归查询

    with AA as ( select * from tb_ClientBranch_Category where BRANCH_MOM_NAME='北京易华录信息技术股份有限公司' union al ...

  4. 入门迅速、应用广泛、月薪两万,马哥Python前景为什么这么好?

    随着Python的技术的流行,Python在为人们带来工作与生活上带来了很多的便捷,因为Python简单,学起来快,也是不少新手程序员入门的首选语言.新手们比较关心的就是Python的发展前景与方向. ...

  5. count() 方法

    count() :方法用于统计字符串里某个字符出现的次数.可选参数为在字符串搜索的开始与结束位置. num1,num2 = input('请输入字符串:'),input('请输入要查询的子串:') p ...

  6. eclipse中同步代码PULL报错checkout conflict with files的解决方法

    1.Team--->Synchronize Workspace 2.在同步窗口找到冲突文件,把自己本地修改的复制出来 3.在文件上右键选择 Overwrite----->Yes , 4.再 ...

  7. linux下设置python3.x为默认版本

    rm /usr/bin/python ln -s /usr/local/bin/python3.x /usr/bin/python sybomlic 安装目录 系统目录

  8. torch.nn.Embedding理解

    Pytorch官网的解释是:一个保存了固定字典和大小的简单查找表.这个模块常用来保存词嵌入和用下标检索它们.模块的输入是一个下标的列表,输出是对应的词嵌入. torch.nn.Embedding(nu ...

  9. CSU 2018年12月月赛 F(2218): Finding prime numbers

    Description xrdog has a number set. There are 95 numbers in this set. They all have something in com ...

  10. 使用requests+BeaBeautiful Soup爬取妹子图图片

    1. Requests:让 HTTP 服务人类 Requests 继承了urllib2的所有特性.Requests支持HTTP连接保持和连接池,支持使用cookie保持会话,支持文件上传,支持自动确定 ...