二叉搜索树(BST)相关知识总结
2015-07-02 10:31
411 查看
前言:二叉搜索树是满足如下性质的二叉树:即任何一个节点的左子树节点值都小于根节点值,进而又小于其右子树节点的值。所以,二叉搜索树的中序遍历是有序的。接下来主要介绍二叉搜索树的相关操作,包括创建、插入、删除、查找等;其时间复杂度与树的高度成正比,即O(logn)。
1.二叉搜索树中查找某个元素
算法思想:因为BST满足_left.key<=p.key<=_right.key,因此,我们可以从根节点开始与给定的查找关键字val进行比较,如果p.key=val,说明查找到了,返回p即可;如果p.key>val,则在左子树中进行寻找,令p=p->left;如果p.key < val,则需要在右子树中进行查找,令p=p->right即可;总结如下:
因为二叉树是递归定义的,所以其很多操作都可以用递归的方式进行解决,所以首先给出BST查找操作的递归版本,如下:
将递归版本改造成循环的方式也是很容易的,代码如下:
时间复杂度分析:
查找算法从根节点开始不断地向左子树和右子树查找,这样就形成一条从根节点向下的路径,所以其时间复杂度与树的高度成正比,即O(longn);
2.二叉搜索树返回最小值元素
算法思想:
源码如下:
3.二叉搜索树中返回最大值元素
算法思想:
由于二叉搜索树对于任意节点都满足_left.key<=p.key<=_right.key,故最大值元素一定是最右下方元素;
源码如下:
4.二叉搜索树中返回一个节点的前驱节点
前驱节点:
BST节点的前驱节点是指中序遍历时当前节点的前一个节点,同理可以定义后继节点;
算法思想:
前驱节点主要有以下几种情况:
第一种:左子树不为空时,一定是左子树最右下方节点;
第二种:左子树为空且当前节点是父亲节点的左子树时,循环向上找,直到当前节点所在的子树是父亲节点的右子树为止;返回父亲节点,即为前驱节点。如下图所示:
源码如下:
5.二叉搜索树返回一个节点的后继节点
算法思想:
后继节点有以下两种情况:
情况一、右子树不为空时,返回右子树的最左下方节点;
情况二、右子树为空且当前节点是父亲节点的右子树时,循环向上找,直到当前节点所在的子树是父亲节点的左子树时为止;返回父亲节点;如下图所示:
源码如下:
6.二叉搜索树中插入一个节点
算法思想:
由于二叉搜索树中各节点的值是互异的,因此插入的新节点的值要么比某个节点的值大,即向其右子树方向进行寻找,要么比某个节点的值小,即向其左子树的方向寻找;形成一条从根节点向下的路径。待插入的位置一定是叶子节点;
源码如下(递归版本):
说明:由于需要递归地在左子树或者右子树进行插入,叶子节点作为其左子结点或者右子结点,所以要采用引用的方式,或者指针的指针;
源码如下(非递归版本):
7.二叉搜索树中删除给定值的节点
算法思想:
主要分为以下三种情况:
1.如果待删除节点的左子树为空,则将其右孩子放到待删节点的位置;
2.如果待删除节点的右子树为空,则将其左孩子放到待删节点的位置;
3.如果其左右子树都不为空,则将其后继节点也就是右子树的最左下节点放到待删节点的位置;其后继节点一定是没有左子树的,但可能有右子树;需要将这个右子树作为其父亲节点的左子树;所以需要判断父亲节点是不是待删节点。示意如下图所示:
由于需要用到节点的父亲节点,故需要重新定义数据结构;源码如下:
随机构建二叉搜索树:
我们定义n个关键字的一颗随机构建二叉搜索树为按照随机次序插入这些关键字到一颗初始空树中生成的树。即循环插入操作。有如下定理:
到此,对于二叉查找树的相关操作都已经介绍完了。下面分析一下二叉搜索树相关操作的时间复杂度,由于相关操作都是与树的高度成正比,因此,其时间复杂度为O(logn);中序遍历由于要访问所有的节点,因此其时间复杂度为O(n);
1.二叉搜索树中查找某个元素
算法思想:因为BST满足_left.key<=p.key<=_right.key,因此,我们可以从根节点开始与给定的查找关键字val进行比较,如果p.key=val,说明查找到了,返回p即可;如果p.key>val,则在左子树中进行寻找,令p=p->left;如果p.key < val,则需要在右子树中进行查找,令p=p->right即可;总结如下:
令p=pRoot; p.key = val,retern p; p.key > val,令p=p->left; p.key < val,令p=p->right; 直到p==Null;
因为二叉树是递归定义的,所以其很多操作都可以用递归的方式进行解决,所以首先给出BST查找操作的递归版本,如下:
BiNode *BST_Search_Recursion(BiNode *pRoot,int key) //递归的方式 { if (pRoot==NULL) return pRoot; if (pRoot->m_data==key) return pRoot; BiNode *pResult; if (pRoot->m_data > key) pResult=BST_Search_Recursion(pRoot->plChild,key);//向左 else pResult=BST_Search_Recursion(pRoot->prChild,key);//向右 return pResult; }
将递归版本改造成循环的方式也是很容易的,代码如下:
BiNode *BST_Search(BiNode *pRoot,int key) { BiNode *pCurNode=pRoot; while (pCurNode) { if (pCurNode->m_data==key) { return pCurNode; } pCurNode= pCurNode->m_data > key ? pCurNode->plChild : pCurNode->prChild; } return pCurNode; }
时间复杂度分析:
查找算法从根节点开始不断地向左子树和右子树查找,这样就形成一条从根节点向下的路径,所以其时间复杂度与树的高度成正比,即O(longn);
2.二叉搜索树返回最小值元素
算法思想:
由于二叉搜索树对于任意节点都满足_left.key<=p.key<=_right.key,故最小值元素一定是最左下方元素;
源码如下:
BiNode *BST_ExtractMin(BiNode *pRoot) //返回最小值元素 { BiNode *pCurNode=pRoot; BiNode *pResult=pRoot; while (pCurNode) { pResult=pCurNode;//记录最左下节点 pCurNode=pCurNode->plChild; } return pResult; }
3.二叉搜索树中返回最大值元素
算法思想:
由于二叉搜索树对于任意节点都满足_left.key<=p.key<=_right.key,故最大值元素一定是最右下方元素;
源码如下:
BiNode *BST_ExtractMax(BiNode *pRoot) //返回最大值元素 { BiNode *pCurNode=pRoot; BiNode *pResult=pRoot; while (pCurNode) { pResult=pCurNode;//记录最右下节点 pCurNode=pCurNode->prChild; } return pResult; }
4.二叉搜索树中返回一个节点的前驱节点
前驱节点:
BST节点的前驱节点是指中序遍历时当前节点的前一个节点,同理可以定义后继节点;
算法思想:
前驱节点主要有以下几种情况:
第一种:左子树不为空时,一定是左子树最右下方节点;
第二种:左子树为空且当前节点是父亲节点的左子树时,循环向上找,直到当前节点所在的子树是父亲节点的右子树为止;返回父亲节点,即为前驱节点。如下图所示:
源码如下:
BiNode *BST_PreSuccessor(BiNode *pRoot,BiNode *pCurNode) //返回一个节点的前驱节点 { if (pCurNode==NULL) { return NULL; } if (pCurNode->plChild!=NULL) { return BST_ExtractMax(pCurNode->plChild); } //否则返回当前节点的父亲节点 BiNode *pParent=BST_FindParent(pRoot,pCurNode); while (pParent!=NULL&&pCurNode==pParent->plChild)//循环向上查找 { pCurNode=pParent; pParent=BST_FindParent(pRoot,pCurNode); } return pParent; }
5.二叉搜索树返回一个节点的后继节点
算法思想:
后继节点有以下两种情况:
情况一、右子树不为空时,返回右子树的最左下方节点;
情况二、右子树为空且当前节点是父亲节点的右子树时,循环向上找,直到当前节点所在的子树是父亲节点的左子树时为止;返回父亲节点;如下图所示:
源码如下:
BiNode *BST_FindSuccessor(BiNode *pRoot,BiNode *pCurNode) { if (pRoot==NULL || pCurNode==NULL) { return NULL; } if (pCurNode->prChild!=NULL) { return BST_ExtractMin(pCurNode->prChild); } BiNode *pParent=BST_FindParent(pRoot,pCurNode); while (pParent!=NULL && pCurNode==pParent->prChild) //循环向上查找 { pCurNode=pParent; pParent=BST_FindParent(pRoot,pCurNode); } return pParent; }
6.二叉搜索树中插入一个节点
算法思想:
由于二叉搜索树中各节点的值是互异的,因此插入的新节点的值要么比某个节点的值大,即向其右子树方向进行寻找,要么比某个节点的值小,即向其左子树的方向寻找;形成一条从根节点向下的路径。待插入的位置一定是叶子节点;
源码如下(递归版本):
bool BST_InsertNode(BiNode* &pRoot,int key) //注意要采用引用方式 { if (pRoot==NULL) { pRoot=new BiNode(key); return true; } bool bResult; if (pRoot->m_data > key) bResult=BST_InsertNode(pRoot->plChild,key);//在左子树递归插入 if (pRoot->m_data<key) bResult=BST_InsertNode(pRoot->prChild,key);//在右子树递归插入 return bResult; }
说明:由于需要递归地在左子树或者右子树进行插入,叶子节点作为其左子结点或者右子结点,所以要采用引用的方式,或者指针的指针;
源码如下(非递归版本):
void BST_Insert(BiNode * &pRoot,int Key) { //新建一个节点值为key的节点 BiNode *pInsert=new BiNode(Key); BiNode *pCurNode=pRoot; //判断根节点是否为空,如果为空,新节点作为根 if (pRoot==NULL) { pRoot=pInsert;//新节点作为根节点 return; } BiNode *pParentNode=NULL; while (pCurNode) { pParentNode=pCurNode;//记录父亲节点 if (pCurNode->m_data > Key) pCurNode=pCurNode->plChild; else pCurNode=pCurNode->prChild; }//形成一条自上而下的路径,O(logn) if (pParentNode->m_data > Key) pParentNode->plChild=pInsert; //作为父亲节点的左子节点 else pParentNode->prChild=pInsert; //作为父亲节点的右子节点 }
7.二叉搜索树中删除给定值的节点
算法思想:
主要分为以下三种情况:
1.如果待删除节点的左子树为空,则将其右孩子放到待删节点的位置;
2.如果待删除节点的右子树为空,则将其左孩子放到待删节点的位置;
3.如果其左右子树都不为空,则将其后继节点也就是右子树的最左下节点放到待删节点的位置;其后继节点一定是没有左子树的,但可能有右子树;需要将这个右子树作为其父亲节点的左子树;所以需要判断父亲节点是不是待删节点。示意如下图所示:
由于需要用到节点的父亲节点,故需要重新定义数据结构;源码如下:
void BST_DeleteNode(BiNode * pRoot,int key) { BiNode *pCurNode=pRoot;//遍历节点找到key BiNode *pParentNode=NULL; while (pCurNode) //O(logn) { if (pCurNode->m_data==key) break; pParentNode=pCurNode;//记录父亲节点 if (pCurNode->m_data > key) pCurNode=pCurNode->plChild; else pCurNode=pCurNode->prChild; } if (pCurNode==NULL) //没有找到 { cout<<"Can't find the node!"<<endl; return; } //找到了当前节点和父亲节点以后开始对其进行处理,分为以下几种情况: if (pCurNode->plChild==NULL && pCurNode->prChild==NULL) //第一种情况:叶子节点 { if (pCurNode==pRoot) //是否是根节点 { pRoot=NULL;//只有一个节点,删掉 return; } if (pCurNode==pParentNode->plChild)//是父亲的左孩子 pParentNode->plChild=NULL; else pParentNode->prChild=NULL; delete pCurNode; return; } else if (pCurNode->plChild==NULL || pCurNode->prChild==NULL) //左子树或右子树不为空 { if (pCurNode==pRoot) //是否是根节点 { if (pCurNode->plChild==NULL) pRoot=pRoot->prChild; else pRoot=pRoot->plChild; } else //不是根节点 { if (pCurNode->plChild==NULL) //第二种情况:左子树为空 { if (pCurNode==pParentNode->plChild) pParentNode->plChild=pCurNode->prChild; else pParentNode->prChild=pCurNode->prChild;//将右子树作为父亲节点的孩子 } if(pCurNode->prChild==NULL) //第三种情况:右子树为空 { if (pCurNode==pParentNode->plChild) pParentNode->plChild=pCurNode->plChild; else pParentNode->prChild=pCurNode->plChild;//将左子树作为父亲节点的孩子 } } delete pCurNode; } else //第四种情况:左右子树均不为空的情况 { BiNode *PTempParent=pCurNode; BiNode *pSuccesor=pCurNode->prChild;//不为空 while (pSuccesor->plChild) { PTempParent=pSuccesor; pSuccesor=pSuccesor->plChild; //找到后继节点 } pCurNode->m_data=pSuccesor->m_data;//修改其值 if (PTempParent==pCurNode) //右子树无左子树 pCurNode->prChild=pSuccesor->prChild; else PTempParent->plChild=pSuccesor->prChild; delete pSuccesor; } }
随机构建二叉搜索树:
我们定义n个关键字的一颗随机构建二叉搜索树为按照随机次序插入这些关键字到一颗初始空树中生成的树。即循环插入操作。有如下定理:
一颗有n个不同关键字的随机构建二叉搜索树的期望高度为O(logn);
void BST_Create(BiNode *pRoot,vector< int > Data) { pRoot=NULL; int n=Data.size(); for(int i=0;i < n;i++) BST_InsertNode(pRoot,Data[i]);//循环插入节点 }
到此,对于二叉查找树的相关操作都已经介绍完了。下面分析一下二叉搜索树相关操作的时间复杂度,由于相关操作都是与树的高度成正比,因此,其时间复杂度为O(logn);中序遍历由于要访问所有的节点,因此其时间复杂度为O(n);
相关文章推荐
- JavaScript2谁刚开始学习应该知道4最佳实践文章(翻译)
- 如何卸载eclipse中的ADT
- PS打开文件问题
- wordpress安装
- 多字节和宽字符之间的转换方法
- JNI字段描述符“([Ljava/lang/String;)V”
- 公司参加互联网金融研讨会
- lldb调试IPHONE步骤
- sql游标的使用
- 1017. A除以B (20):做完挺开心的一道=.=
- DEV-aspxgridview_RowValidating行验证
- ASP.NET MVC 中将数据从View传递到控制器中的三种方法(表单数据绑定)
- 构建自己的embedded linux系统
- 《Java并发编程实践》笔记7——非阻塞同步算法
- PHP中foreach()用法汇总
- Build Action 设置为 Embedded Resource
- 阅读zepto.js的core中的Core methods
- 浅谈批处理中的%cd%与%~dp0
- Java编程规范
- Android文件重命名File.renameTo()以及定义副本名方法(自定义规则)