热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

二叉查找树的原理与实现BinarySearchTree

二叉查找树是一种特殊的二叉树。其树结点的结构和普通二叉树一样。所不同的是,我们对二叉查找树的建立和修改操作都需要使其始终满足一个条件:对于树中的每个结点X,它的左子树中所有关键字值

    二叉查找树是一种特殊的二叉树。其树结点的结构和普通二叉树一样。所不同的是,我们对二叉查找树的建立和修改操作都需要使其始终满足一个条件:对于树中的每个结点X,它的左子树中所有关键字值小于X的关键值,而它的右子树中所有关键字大于X的关键值。

    正是这个条件二叉查找树的建立和修改操作和普通二叉树不一样,需要按照一定的规则。这一规则赋予二叉查找树一些性质。

二叉查找树具有的性质:

1、二叉查找树具有较好的查找性质(如果构建得当的话)。

2、二叉查找树的中序遍历序列是正序排序。

3、最小元素在最左端。最大元素在最右端。


以二叉查找树为基础的树:

1、平衡二叉树(AVL树)

2、红黑树

3、伸展树(Splay)

4、B树


二叉查找树具有的操作:

1、查找特定元素;

2、查找最大、最小元素;

3、中序遍历序列索引:查找某个结点的前趋结点、查找某个结点的后继结点;

3、插入结点:仅在叶子结点插入。

4、删除结点:可以删除任意结点,但不可以改变该树的性质。因此删除操作存在三种情况。(假设被删结点为z)

第一种情况:z结点是叶子结点。那么它可以直接被删除。而不会影响其他结点。

第二种情况:z结点具有一个孩子(左或者右)。那么把z的孩子接在z的双亲上,就不会破坏树的性质。

第三种情况:z结点具有两个孩子。对于这种情况,可能存在不同的处理方法。《算法导论》中给出的办法是用z结点的后继结点的值来的代替z的位置,然后把后继结点删除。

为什么可以用后继结点来替代z的位置?

理由一:z结点的后继结点必定是z的右子树的最左结点(即z的右子树中的最小元素),当删除掉z元素之后,z结点的后继结点自然可以拿出来作为z的左右两个子树的分叉结点。

理由二:同时,z的后继结点必定没有左孩子(即叶子结点或者单右孩子结点),删除这样的结点属于第二种情况。

#include 
#include

typedef struct TreeNode{
int value;
TreeNode* left;
TreeNode* right;
}TreeNode, *SearchTree;

void clear(SearchTree &root)
{
if(root!=NULL)
{
clear(root->left);
clear(root->right);
free(root);
root = NULL;
}
}

SearchTree find(int x, SearchTree root)
{
if(root == NULL)
return NULL;
if(x value)
return find(x, root->left);
else if(x > root->value)
return find(x, root->right);
else
return root;
}

SearchTree find_nonrecursion(int x, SearchTree root)
{
if(root == NULL)
return NULL;
while(root != NULL)
{
if(x == root->value)
return root;
else if(x value)
root = root->left;
else if(x > root->value)
root = root->right;
}
return NULL;
}

SearchTree find_min(SearchTree root)
{
if(root == NULL)
return NULL;
if(root->left == NULL)
return root;
else
return find_min(root->left);
}

SearchTree find_min_nonrecursion(SearchTree root)
{
if(root == NULL)
return NULL;
while(root->left !=NULL)
{
root = root->left;
}
return root;
}

SearchTree find_max(SearchTree root)
{
if(root == NULL)
return NULL;
if(root->right == NULL)
return root;
else
return find_max(root->right);
}

SearchTree find_max_nonrecursion(SearchTree root)
{
if(root == NULL)
return NULL;
while(root->right != NULL)
{
root = root->right;
}
return root;
}

void insert(int x, SearchTree &T)
{
//参数T必须是引用类型,因为当树最初为空时,并不存在根结点的地址
if(T == NULL)
{
T = (TreeNode*)malloc(sizeof(TreeNode));
T->value = x;
T->left = NULL;
T->right = NULL;
printf("%d has been inserted.\n",x);
return;
}
int flag = 0;
TreeNode *root = T;//不能把根节点的地址弄丢了
TreeNode *p = NULL;
while(root != NULL)
{
p = root;
if(x value)
{
flag = 1;
root = root->left;
}
else if(x > root->value)
{
flag = -1;
root = root->right;
}
else //该元素已经存在
{
printf("%d Exists!\n",x);
return ;
}
}

if(flag == 1)
{
p->left = (TreeNode*)malloc(sizeof(TreeNode));
p = p->left;
}
else
{
p->right = (TreeNode*)malloc(sizeof(TreeNode));
p = p->right;
}
p->value = x;
p->left = NULL;
p->right = NULL;
printf("%d has been inserted.\n",x);
}

void remove(int x, SearchTree &T)
{
TreeNode *root = T;
TreeNode *p = root;
int flag = 0;
while(root != NULL && root->value != x)
{
if(x value)
{
p = root;
flag = 1;
root = root->left;
}
else
{
p = root;
flag = -1;
root = root->right;
}
}
if(root == NULL)
{
printf("%d Not Found!\n",x);
return;
}

if(p == root) //被删结点是根节点
{
TreeNode *tmp = root;
if(root->left == NULL)
{
root = root->right;
free(tmp);
if(root == NULL)
{
T = NULL;
printf("%d has been inserted.\n",x);
return;
}
}
else if(root->right == NULL)
{
root = root->left;
free(tmp);
if(root == NULL)
{
T = NULL;
printf("%d has been inserted.\n",x);
return;
}
}
else
{
TreeNode *post = root->right;
TreeNode *ppost;
while(post->left!=NULL)
{
ppost = post;
post = post->left;
}
if(post == root->right)
{
root->value = post->value;
root->right = post->right;
free(post);
}
else
{
root->value = post->value;
ppost->left = NULL;
free(post);
}
}
}
else //被删结点不是根节点
{
if(root->left == NULL)
{
TreeNode *tmp = root;
if(flag == 1)
p->left = root->right;
else
p->right = root->right;
free(tmp);
}
else if(root->right == NULL)
{
TreeNode *tmp = root;
if(flag == 1)
p->left = root->left;
else
p->right = root->left;
free(tmp);
}
else
{
TreeNode *post = root->right;
TreeNode *ppost;
while(post->left != NULL)
{
ppost = post;
post = post->left;
}
if(post == root->right)
{
root->value = post->value;
root->right = post->right;
free(post);
}
else
{
root->value = post->value;
ppost->left = NULL;
free(post);
}
}
}
printf("%d has been removed.\n",x);
}

void inorder_traverse(SearchTree root)
{
if(root != NULL)
{
inorder_traverse(root->left);
printf("%d ", root->value);
inorder_traverse(root->right);
}
}

void show(SearchTree T)
{
printf("Tree: ");
inorder_traverse(T);
printf("\n");
}

int main()
{
SearchTree T = NULL;
printf("%s\n",T);

insert(3, T);
insert(7, T);
insert(2, T);
printf("%s\n",T);
show(T);

remove(7,T);
remove(2,T);
remove(3,T);
printf("%s\n",T);

insert(4, T);
insert(6, T);
insert(6, T);
insert(6, T);
insert(9, T);
show(T);

TreeNode *p;

p = find(4, T);
printf("Find :%d\n",p->value);

p = find_max(T);
printf("MAX :%d\n",p->value);
p = find_max_nonrecursion(T);
printf("MAX :%d\n",p->value);

p = find_min(T);
printf("MIN :%d\n",p->value);
p = find_min_nonrecursion(T);
printf("MIN :%d\n",p->value);
return 1;
}

编码细节:

1、我在定义结构体类型的时候经常在typedef之后忘记加struct,这一点再次提醒自己。

2、以上对树的许多操作都有递归形式和非递归形式两种,都要掌握。

3、对于不会修改树的操作函数,可以只传入指向树根结点的指针作为形参。这时候只传入了根结点的地址的副本,不会改变根结点的地址。但对于要修改树的操作函数(清空树、插入结点、删除结点),则必须传入指向树根结点指针的引用,因为这些函数可能会改变树根结点的地址,当树为空时,需要将根指针指向NULL。使用引用类型的话就要注意一开始就要把根结点指针备份,以免丢失。

PS:此类比较复杂一些的代码,我如何训练自己顶住压力在人前当面写出来?


推荐阅读
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 判断编码是否可立即解码的程序及电话号码一致性判断程序
    本文介绍了两个编程题目,一个是判断编码是否可立即解码的程序,另一个是判断电话号码一致性的程序。对于第一个题目,给出一组二进制编码,判断是否存在一个编码是另一个编码的前缀,如果不存在则称为可立即解码的编码。对于第二个题目,给出一些电话号码,判断是否存在一个号码是另一个号码的前缀,如果不存在则说明这些号码是一致的。两个题目的解法类似,都使用了树的数据结构来实现。 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • Java编程实现邻接矩阵表示稠密图的方法及实现类介绍
    本文介绍了Java编程如何实现邻接矩阵表示稠密图的方法,通过一个名为AMWGraph.java的类来构造邻接矩阵表示的图,并提供了插入结点、插入边、获取邻接结点等功能。通过使用二维数组来表示结点之间的关系,并通过元素的值来表示权值的大小,实现了稠密图的表示和操作。对于对稠密图的表示和操作感兴趣的读者可以参考本文。 ... [详细]
  • HashMap的扩容知识详解
    本文详细介绍了HashMap的扩容知识,包括扩容的概述、扩容条件以及1.7版本中的扩容方法。通过学习本文,读者可以全面了解HashMap的扩容机制,提升对HashMap的理解和应用能力。 ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 求解连通树的最小长度及优化
    本文介绍了求解连通树的最小长度的方法,并通过四边形不等式进行了优化。具体方法为使用状态转移方程求解树的最小长度,并通过四边形不等式进行优化。 ... [详细]
author-avatar
heqiuhao
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有