关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。
键值对:
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值, value表示与key对应的信息。
二叉搜索树:
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
- 最小的元素在最左侧
- 最大的元素在最右侧
二叉搜索树的查找:
二叉搜索树的插入数据:
二叉搜索树的删除结点:
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
- a. 要删除的结点无孩子结点
- b. 要删除的结点只有左孩子结点
- c. 要删除的结点只有右孩子结点
- d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
- 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
- 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
- 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中, 再来处理该结点的删除问题
二叉搜索树的实现:
#include<stdlib.h>
#include<stdio.h>
#include<iostream>
using namespace std;
template<class T>
struct BSTreeNode {
BSTreeNode(const T& data=T())
:_pLeft(nullptr)
,_pRight(nullptr)
,_data(data){}
BSTreeNode<T>* _pLeft;
BSTreeNode<T>* _pRight;
T _data;
};
template<class T>
class BSTree {
typedef BSTreeNode<T> Node;
public:
BSTree()
:_pRoot(nullptr)
{}
~BSTree() {
_Destory(_pRoot);
}
bool Insert(const T& data) {
//空树
if(_pRoot ==nullptr) {
_pRoot =new Node(data);
return true;
}
//非空树
//1.找到插入位置
Node* pCur=_pRoot;
Node* pParent = nullptr;
while(pCur) {
pParent = pCur;
else if(data < pCur->_data)
pCur=pCur->_pLeft;
else
pCur=pCur->_pRight;
else
return false;
}
//2.插入数据
pCur = new Node(data);
if(data < pParent->_data) {
pParent->_pLeft = pCur;
}
else
pParent->_pRight = pCur;
return true;
}
//查找
Node* Find(const T& data) {
Node* pCur = _pRoot;
while(pCur) {
if(data ==pCur->_data)
return pCur;
else if(data < pCur->_data)
pCur=pCur->_pLeft;
else
pCur=pCur->_pRight;
}
return NuLL;
}
//获取最左侧节点
Node* LeftMost() {
return _LeftMost(_pRoot);
}
//获取最右侧节点
Node* RightMost() {
return _RightMost(_pRoot);
}
//中序遍历
void InOrder() {
_InOrder(_pRoot);
}
//销毁
void _Destory(Node* pRoot) {
if(_pRoot) {
_Destory(_pRoot->_pLeft);
_Destory(pRoot->_pRight);
delete pRoot;
pRoot = nullptr;
}
}
//删除结点
void Delete(const T& data) {
if(_pRoot == nullptr) {
return;
}
//1.找到删除的位置
Node* pCur = _pRoot;
Node* pParent = nullptr;
while(pCur) {
if(data < pCur->data) {
//注意这个位置
pParent = pCur;
pCur=pCur->_pLeft;
}
else if(data > pCur->_pLeft) {
//注意这个位置
pParent = pCur;
pCur=pCur->_pRight;
}
//找到删除的位置了,改删除了
else
break;
}
//2.删除结点
if(pCur == nullptr) {
//节点不存在
return;
}
//分情况删除——四种情况
//1.左右孩子都不存在
//2.左右孩子都存在
//3.只有左孩子
//4.只有右孩子
Node* pDelNode = pCur;
if(pCur->_pRight == nullptr) {
//叶子结点 || 只有左孩子
if(pParent == nullptr) {
//就是根了
_pRoot->pCur->_pLeft;
}
else {
if(pCur == pParent->pLeft) { //是双亲的左
pParent->_pLeft = pCur->_pLeft;
}
else {
pParent->_pRight = pCur->_pLeft;
}
}
}
else if(pCur->_pLeft == nullptr) {
//只有右孩子
if(pParent == nullptr) {
//就是根了
_pRoot->pCur->_pRight;
}
else {
if(pCur == pParent->pLeft) { //是双亲的左
pParent->_pLeft = pCur->_pRight;
}
else {
pParent->_pRight = pCur->_pRight;
}
}
}
else {
//左右孩子都存在,不能直接删除,要找替代节点
//方式一:左子树最大的结点
//方式二:右子树中最小的结点
//在右子树中进行查找
Node* pMostLeft = pCur->_pRight;
pParent = pCur;
while(pMostLeft->_pLeft) {
pParent = pMostLeft;
pMostLeft = pMostLeft -> _pLeft;
}
pCur -> _data = pMostLeftp ->_data;
//删除替代结点
if(pMostLeft == pParent->_pLeft) {
pParent->_pLeft = pMostLeft->_pRight;
}
else {
pParent->_pRight = pMostLeft->_pRight;
}
pDelNode = pMostLeft;
}
delete pDelNode;
return true;
}
private:
Node* _LeftMost(Node* pRoot) {
if(pRoot == nullptr) {
return nullptr;
}
Node* pCur = pRoot;
while(pCur->_pLeft) {
pCur = pCur->_pLeft;
}
return pCur;
}
Node* _RightMost(Node* pRoot) {
if(pRoot == nullptr) {
return nullptr;
}
Node* pCur = pRoot;
while(pCur->_pRight) {
pCur = pCur->_pRight;
}
return pCur;
}
void _InOrder(Node* pRoot) {
if(pRoot){
_InOrder(pRoot->_pLeft);
cout<<pRoot->data<<" ";
_InOrder(pRoot->_pRight);
}
}
private:
Node* _pRoot;
}
二叉数的性能分析:
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:完全二叉树和单支。
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log2 N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码, 都可以是二叉搜索树的性能佳?(这也就是二叉搜索树的缺陷)