算法学习记录-查找——二叉排序树(Binary Sort Tree)
二叉排序树 也称为 二叉查找数。
它具有以下性质:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值。
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
它的左、右子树也分别为二叉排序树。
之前的查找折半查找、斐波那契查找、插值查找的前提条件就是序列为有序,为顺序存储结构。我们在查找一章提过,查找还有动态查找,比如插入和删除操作,
进行这些操作对顺序存储结构效率不那么高。能不能有一种既静态查找效率高,动态查找效率也高呢?
联想之前的堆排序,人们创造出一个堆这样的结构来提高排序效率。(佩服他们的思维)堆结构建立在二叉树上,之前一直都不知道二叉树是怎么用的,
看到这里二叉树的作用就体现出来了,我们定义二叉排序树来实现动、静态查找效率都高的结构。
先举一个例子:
先看看一个无序序列排列好的二叉排序树:
上图中,上面数组是原始数组,下面的二叉排序树是按照上面的原始数组来构建的。
1.我们如果按中序遍历得到的结果就是这个序列的 顺序排序结果。{0,1,2,3,4,5,6,7,8,9}
2.我们实现静态查找,查找关键字8:
从根结点开始,根结点(6),8比6大,往右(到结点(7))。
当前结点(7),8比该结点大,往右(到结点(8))。
找到8。
这里有点类似折半、斐波那契、插值查找的思想。所以它的静态查找效率比较高。
3.动态查找(插入和删除)
如果要插入记录10到这个序列中,我们只需要给它找一个合适的位置,在对插入后的二叉树中序遍历的时候,依旧是一个顺序排列表。
插入10,比根结点(6)大,向右(到结点7)
比当前结点(7)大,向右(到结点8)
比当前结点(8)大,向右(到结点9)
比当前结点(9)大,向右,无孩子,则建立孩子并赋值10.
完成后,该树的中序遍历依然是一个顺序表,对原先树几乎是没有影响,所以其插入操作也非常的高效。
对于其删除:
由于二叉排序树在删除一个记录时候要保证其中序遍历的顺序不受影响,操作要分几种情况,会稍微麻烦点,这个后面再讲。
所以综合看来,这样的二叉排序树对 静动态操作 基本都高效,避免了有序表的插入删除时候的大量记录的移动。
到这里,看到了二叉排序数的好处,
问题就来了!
怎么从一个无序的数列构建这样的二叉排序树?
通过上面过程,基本能够看出无序表到二叉排序树的构建过程。
其实质就是查找与插入操作。先查看结点是否存在,然后比较,再插入。
知道了思想,写程序就不难了。
插入算法程序如下:
bool SearchBST(pBinNode T,myDataType key,pBinNode *pNode)//pNode是指针,需要修改,所以用二级指针,该指针用来记录要查找结点,若不存在则返回将插入结点的父结点。
{ //根结点指针,寻找的值,记录父节点值
if (T == NULL)//如果是空树,则父结点指向NULL
{
*pNode = NULL;
return false;
}
pBinNode crntNode = T; while(crntNode)//找到要插入结点的父结点
{
if (crntNode->data == key)
{
return true;
}
else if(crntNode->data > key)
{
*pNode = crntNode;
crntNode = crntNode->lchd;
}
else if (crntNode->data < key)
{
*pNode = crntNode;
crntNode = crntNode->rchd;
}
}
return false;
} bool insertBSTNode(pBinNode *T,int key,int index)//第一个参数之所以为二级指针,因为在该函数中有malloc生成的是指针,而要将该指针的值在函数中赋给另一个指针,所以要用指针的指针才能接收
{
pBinNode n=NULL;
pBinNode p=NULL;
if (false == SearchBST(*T,key,&p))//找到要插入的结点的父结点p
{
n = (pBinNode)malloc(sizeof(BinNode));
if (n == NULL)
{
printf("error:no more memery!\n");
return false;
}
(*n).data = key;
(*n).index = index;
(*n).lchd = NULL;
(*n).rchd = NULL; if (p == NULL)
{
*T = n;
}
else if(key < p->data)
{
p->lchd = n;
}
else if (key > p->data)
{
p->rchd = n;
}
return true;
}
else
{
return false;
}
}
仔细想想这样的Search是不是可以用递归的调用方法。
删除操作,对于删除有三种情况:
- 欲删除的结点是叶子结点,由于删除不破坏树结构,只需要修改其父结点指向为NULL,然后free该欲删除的结点。
- 欲删除的结点只有左子树或者只有右子树,只需要将欲删除的结点的父结点孩子指针指向其(左/右)子树,然后free欲删除的结点。
- 欲删除的结点既有左子树又有右子树,此时不能如上面两种方法处理。
最简单的方法就是将他的左右子树重新排序,按二叉排序树条件组成新的子树,再连接到树上。这样做会非常的复杂,假如删除的是根结点,那么意味着整个树将要重新构成。
我们想想二叉排序树的中序遍历,其中序遍历就是无序序列的有序集合。
我们删除其中一个结点,通过调整方法,尽可能减少破坏树的结构,我们只要在调整后,如果能保证调整后的树的中序遍历依然是有序集合,那么这样的调整就能成功。
举几个例子:
代码:
void DeleteNode(pBinNode *fNode,pBinNode *sNode)//参数分别为 父亲 和 孩子
{
pBinNode q = NULL;
pBinNode s = NULL;
if ((*sNode)->rchd == NULL)//右子树为空,只要把它的左子树直接连上就行
{
q = *sNode;
(*fNode)->rchd = (*sNode)->lchd;
free(q);
}
else if((*sNode)->lchd == NULL)//左子树为空,只要把它的右子树直接连上就可以了。
{
q=*sNode;
(*fNode)->rchd = (*sNode)->rchd;
free(q);
}
else //左右都不为空!
{
q = *sNode;
s = (*sNode)->lchd;//其前驱一定是在左子树上的最后一个右分支
while(s->rchd)
{
q = s;
s=s->rchd;
}
(*sNode)->data = s->data;
if (q != *sNode)
{
q->rchd = s->lchd;
}
else
{
q->lchd= s->lchd;
}
free(s); }
} bool DeleteBST(pBinNode *T,int key)//找是否存在关键字
{
if (!(*T))
{
return false;
} pBinNode s = *T;//son
pBinNode f = NULL;//father while(s)
{
if(key == s->data)
{
//删除
DeleteNode(&f,&s);
return true;
}
else if (s->data > key)
{
f = s;
s = s->lchd;
}
else if (s->data < key)
{
f = s;
s = s->rchd;
}
}
return false;//no value in array
}
完整程序:
#include <stdlib.h> typedef int myDataType;
//myDataType src_ary[10] = {9,1,5,8,3,7,6,0,2,4};
//myDataType src_ary[10] = {1,2,3,4,5,6,7,8,9,10};
//myDataType src_ary[10] = {10,9,8,7,6,5,4,3,2,1}; myDataType src_ary[] = {,,,,,,,,,}; //****************树结构***********************//
typedef struct BTS{
myDataType data;
int index;
struct BTS *lchd,*rchd;
}BinNode,*pBinNode; void prt_ary(myDataType *ary,int len)
{
int i=;
while(i < len)
{
printf(" %d ",ary[i++]);
}
printf("\n");
} bool SearchBST(pBinNode T,myDataType key,pBinNode *pNode)//pNode是指针,需要修改,所以用二级指针,该指针用来记录要查找结点,若不存在则返回将插入结点的父结点。
{ //根结点指针,寻找的值,记录父节点值
if (T == NULL)//如果是空树,则父结点指向NULL
{
*pNode = NULL;
return false;
}
pBinNode crntNode = T; while(crntNode)//找到要插入结点的父结点
{
if (crntNode->data == key)
{
return true;
}
else if(crntNode->data > key)
{
*pNode = crntNode;
crntNode = crntNode->lchd;
}
else if (crntNode->data < key)
{
*pNode = crntNode;
crntNode = crntNode->rchd;
}
}
return false;
} bool insertBSTNode(pBinNode *T,int key,int index)//第一个参数之所以为二级指针,因为在该函数中有malloc生成的是指针,而要将该指针的值在函数中赋给另一个指针,所以要用指针的指针才能接收
{
pBinNode n=NULL;
pBinNode p=NULL;
if (false == SearchBST(*T,key,&p))//找到要插入的结点的父结点p
{
n = (pBinNode)malloc(sizeof(BinNode));
if (n == NULL)
{
printf("error:no more memery!\n");
return false;
}
(*n).data = key;
(*n).index = index;
(*n).lchd = NULL;
(*n).rchd = NULL; if (p == NULL)
{
*T = n;
}
else if(key < p->data)
{
p->lchd = n;
}
else if (key > p->data)
{
p->rchd = n;
}
return true;
}
else
{
return false;
}
} void DeleteNode(pBinNode *fNode,pBinNode *sNode)//参数分别为 父亲 和 孩子
{
pBinNode q = NULL;
pBinNode s = NULL;
if ((*sNode)->rchd == NULL)//右子树为空,只要把它的左子树直接连上就行
{
q = *sNode;
(*fNode)->rchd = (*sNode)->lchd;
free(q);
}
else if((*sNode)->lchd == NULL)//左子树为空,只要把它的右子树直接连上就可以了。
{
q=*sNode;
(*fNode)->rchd = (*sNode)->rchd;
free(q);
}
else //左右都不为空!
{
q = *sNode;
s = (*sNode)->lchd;//其前驱一定是在左子树上的最后一个右分支
while(s->rchd)
{
q = s;
s=s->rchd;
}
(*sNode)->data = s->data;
if (q != *sNode)
{
q->rchd = s->lchd;
}
else
{
q->lchd= s->lchd;
}
free(s); }
} bool DeleteBST(pBinNode *T,int key)//找是否存在关键字
{
if (!(*T))
{
return false;
} pBinNode s = *T;//son
pBinNode f = NULL;//father while(s)
{
if(key == s->data)
{
//删除
DeleteNode(&f,&s);
return true;
}
else if (s->data > key)
{
f = s;
s = s->lchd;
}
else if (s->data < key)
{
f = s;
s = s->rchd;
}
}
return false;//no value in array
} int _tmain(int argc, _TCHAR* argv[])
{
int i;
pBinNode T=NULL;//排序树的根结点 for (i=;i<;i++)//创建树
{
if (false == insertBSTNode(&T,src_ary[i],i))
{
printf("error:src_ary[%d]=%d\n",i,src_ary[i]);
}
}
DeleteBST(&T,);//删除6
//DeleteBST(&T,8);
getchar();
return ;
}
测试结果:
在178出设置断点。
程序运行到178行之前,变量结果:
运行到179,执行了178行后
另外两种可以自己测试下。
补充,另外还有一种方法写程序,递归
//********************************
int DeleteNode_R(pBinNode *p)
{
// printf("data=%d\n",(*p)->data);
// return 0;
pBinNode q = NULL;
pBinNode s = NULL;
if ((*p)->rchd == NULL)//右子树为空,只要把它的左子树直接连上就行
{
q = *p;
(*p) = (*p)->lchd;
free(q);
}
else if((*p)->lchd == NULL)//左子树为空,只要把它的右子树直接连上就可以了。
{
q=*p;
(*p) = (*p)->rchd;
free(q);
}
else //左右都不为空!
{
q = *p;
s = (*p)->lchd;//其前驱一定是在左子树上的最后一个右分支
while(s->rchd)
{
q = s;
s=s->rchd;
}
(*p)->data = s->data;
if (q != *p)
{
q->rchd = s->lchd;
}
else
{
q->lchd= s->lchd;
}
free(s);
}
return ;
} int DeleteBST_R(pBinNode *T,int key)
{
if (!(*T))
{
return false;
}
else
{
if ((*T)->data == key)
{
return DeleteNode_R(T);
}
else if(key > (*T)->data)
{
return DeleteBST_R(&(*T)->rchd,key);
}
else if (key < (*T)->data)
{
return DeleteBST_R(&(*T)->lchd,key);
}
}
}
算法学习记录-查找——二叉排序树(Binary Sort Tree)的更多相关文章
- 二叉排序树(Binary Sort Tree)
1.定义 二叉排序树(Binary Sort Tree)又称二叉查找(搜索)树(Binary Search Tree).其定义为:二叉排序树或者是空树,或者是满足如下性质的二叉树: ① 若它的左子树 ...
- 算法学习记录-查找——平衡二叉树(AVL)
排序二叉树对于我们寻找无序序列中的元素的效率有了大大的提高.查找的最差情况是树的高度.这里就有问题了,将无序数列转化为 二叉排序树的时候,树的结构是非常依赖无序序列的顺序,这样会出现极端的情况. [如 ...
- 算法学习记录-查找——折半查找(Binary Search)
以前有个游戏,一方写一个数字,另一方猜这个数字.比如0-100内一个数字,看谁猜中用的次数少. 这个里面用折半思想猜会大大减少次数. 步骤:(加入数字为9) 1.因为数字的范围是0-100,所以第一次 ...
- 二叉查找树(Binary Sort Tree)(转)
二叉查找树(Binary Sort Tree) 我们之前所学到的列表,栈等都是一种线性的数据结构,今天我们将学习计算机中经常用到的一种非线性的数据结构--树(Tree),由于其存储的所有元素之间具有明 ...
- 算法与数据结构基础 - 二叉查找树(Binary Search Tree)
二叉查找树基础 二叉查找树(BST)满足这样的性质,或是一颗空树:或左子树节点值小于根节点值.右子树节点值大于根节点值,左右子树也分别满足这个性质. 利用这个性质,可以迭代(iterative)或递归 ...
- 算法学习记录-排序——插入排序(Insertion Sort)
插入排序: 在<算法导论>中是这样描述的 这是一个对少量元素进行排序的有效算法.插入排序的工作机理与打牌时候,整理手中的牌做法差不多. 在开始摸牌时,我们的左手是空的,牌面朝下放在桌子上. ...
- 算法学习记录-排序——冒泡排序(Bubble Sort)
冒泡排序应该是最常用的排序方法,我接触的第一个排序算法就是冒泡,老师也经常那这个做例子. 冒泡排序是一种交换排序, 基本思想: 通过两两比较相邻的记录,若反序则交换,知道没有反序的记录为止. 例子: ...
- 二叉排序树(Binary Sort Tree)
参考文章:http://blog.csdn.net/ns_code/article/details/19823463 不过博主的使用第一种方法操作后的树已经不是二叉排序树了,值得深思!! #inclu ...
- 算法学习记录-排序——选择排序(Simple Selection Sort)
之前在冒泡排序的附录中提到可以在每次循环时候,不用交换操作,而只需要记录最小值下标,每次循环后交换哨兵与最小值下标的书, 这样可以减少交换操作的时间. 这种方法针对冒泡排序中需要频繁交换数组数字而改进 ...
随机推荐
- ubuntu中执行定时任务crontab
今天研究了下ubuntu里的crontab内置指令.这是设置定时执行脚本任务的指令,我先测试了下最基础的执行. 第一次使用crontab 时,会出现 no crontab for root - usi ...
- JSON(未完待续,等讲到对象时再加)
1 定义 JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation) JSON 是轻量级的文本数据交换格式 JSON 独立于语言:JSON 使用 Jav ...
- Linux下常用的数据恢复工具
一.数据删除 命令:rm -rf,将任何数据直接从硬盘删除,且没有任何提示 建议做法: 把命令参数放到后面:rm -rfi 将删除的东西通过mv命令移动到系统下的/temp目录下,然后写个脚 本定期执 ...
- 织梦修改“dedecms提示信息”
1.根目录下include文件夹,找到common.func.php: 2.根目录下dede文件夹(管理目录默认dede),找到sys_data_done.php: 3.打开以上2个.php文件,把“ ...
- Wireshark漫谈(一)
可能有人会说,一个软件的安装有什么好谈的,无非就是"同意,同意,同意......是,是,是"诸如此类的选项.的确,Wireshark软件的安装步骤是挺简单的,不过本文不是想谈安装步 ...
- 掌握 Azure 的注册、帐户和订阅管理 Azure 上云须知
计划使用由世纪互联运营的 Microsoft Azure 的用户,可通过下列流程注册开通并购买所需 Azure 服务:信息获取 > 试用 > 购买 > 账户/订阅管理 > 支付 ...
- Python3+Selenium3+webdriver学习笔记10(元素属性、页面源码)
#!/usr/bin/env python# -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记10(元素属性.页面源码)'''from selenium i ...
- web端 repeat和简单控件
<%@ %> - 这里面写一些声明和引用的<% %> - 编写C#代码的<%= %><%# %> Repeater - 重复器 相当于winfo ...
- [nmon]使用nmon工具监控系统资源
1.下载nmon 下载正确的nmon版本, 查看linux服务器版本,命令:lsb_release -a,查看到当前系统为RedHat 6.4 然后我们根据我们的linux版本,下载相应nmon版本, ...
- Processing分形之一——Wallpaper
之前用C语言实现过一些分形,但是代码比较复杂.而对于天生对绘图友好的Processing,及其方便. 在大自然中分形普遍存在,我们用图形模拟,主要是找到一个贴近的函数. 代码 /** * Wallpa ...