基本概念
一. 数据表:指的是数据元素的有限集合,两个关键词 1.数据元素 2.有限
二. 静态查找表和动态查找表:静态查找静态顾名思义不改变,即查找时元素固定不变,查找失败时只返 回失败标记,动态查找表指的是查找的过程是动态的,表中数据不是一成不变的,查找失败时,会把 给定元素插入数据表中。
三. 查找效率:关心查找的时间复杂度,一般用平均查找长度来衡量(ASL)平均查找长度指的是在数据表中查找各个元素所需进行关键字的比较次数的期望:
Pi表示第i个数据被查找的概率,Ci表示查找第i个数据所需关键字的比较次数。
四.装载因子:代表数据表中元素的装载情况,设数据表长度为m,表中元素个数为n,则数据表的装载因子为a=n/m
顺序表
最简单的查找,从表的一段查找到另外一段,用给定的值依次与表中的元素进行比较
顺序查找成功的ASL=(n-1)/2
顺序查找失败的ASL为n+1
有序表的折半查找
设n个数据按关键字从小到大的顺序存放在一个顺序表中(low=0,high=n-1)
求出mid=(low+high)/2
然后进行比较,详细看算法笔记
二叉查找树分析折半查找的性能
设二叉查找树上有n个节点,构造二叉查找树方法:
1.当n=0时,二叉查找树为空树
2.n不为0时,根节点时mid=(n-1)/2的数据元素,左子树是elem[0]~elem[mid-1]相应的二叉查找树,右子树是elem[mid+1]~elem[n-1]对应的二叉查找树,有一种递归的思想在里面。
当查找一个元素时,在图上正好走了一条从根节点到该关键字的相应节点的路径,比较次数等于层号,所以折半查找成功时比较次数最多为二叉查找树的高度
如图,二叉查找树进行扩充,让所有的叶子结点的空指针(2个)都指向一个外部节点,代表那些查找不成功的节点,这个二叉树称为扩充二叉树,在扩充二叉树上查找不成功的过程就是从根节点到某个扩充的新节点的过程,比较的次数是该路径上内部节点的个数,一般n个数据的有序表,折半查找的比较次数最多为log2(n+1)向下取整。
图片:课本p268ASL
索引顺序表和倒排表
如果数据元素n很大时顺序查找效率很低,如果要用有序表的折半查找,那么排序所花费的时间开销也很大,这时可以用索引方法实现存储和查找。
索引顺序表
其实和字典的方法差不多,由两部分构成,主表和索引表,都是采用顺序结构进行存储,主表中存放所有的信息,索引表中存放数据元素的主关键字和索引信息,类似字典中的拼音
二叉排序树
是一种动态的查找表,适用于动态查找
定义
二叉排序/搜索树或者是一棵空树,或者是具有下列性质的二叉树:
1)左子树(如果存在)上所有结点的关键字都小于根结点的关键字
2)右子树(如果存在)上所有结点的关键字都大于根结点的关键字。
3)左子树和右子树也是二叉排序树。
所以中序遍历是有序的,是从小到大
查找
从根节点开始,类似于二分查找的方式去逐步比较缩小范围,如果大于这个节点,那么向这个节点的右子树找,如果小,那么就去做子树上找,是一种树形查找。
比较次数不大于树的高度。代码实现:
template<class ElemType>
BinTreeNode<ElemType> *BinarySortTree<ElemType>::Find(const ElemType&key,BinTreeNode<ElemType> *&f)const
{
BinTreeNode<ElemType> *p=GetRoot(); //指向根节点
f=NULL;
while(p!=NUUL &&p->data!=key)
{
if(key<p->data)
{
f=p;
p=p->leftChild;
}
else
{
f=p;
p=p->reightChild;
}
}
return p;
}
设置f指针作用:
查找关键字为key的元素,返回指向改元素的指针,并用f返回其双亲的节点指针。
如果没找到,那么f返回的是查找失败时双亲节点的指针,也就是插入位置。
插入
通过插入,可以建立BST(二叉排序树)
在二叉排序树上插入元素,首先要检查这个元素是否在二叉排序树上已经出现,如果找不到说明要插入到查找失败的地方。
递归法代码:
template <class Type>void BSTree<Type>::
Insert (const Type &x,BSTreeNode<Type>*&p)
{
if (p==NULL)//空二叉树
p=new BSTreeNode<Type>(x);//创建数据元素x的结点
else if(x<p→data)// 在左子树插入
Insert (x,p-leftChild )
else if(x>p→data)//在右子树插入
Insert (x,p-rightChild )
else//结点x已存在
cout <<"There has node x"<<endl;
}
非递归算法:
template<class ElemType>
bool BinarySortTree<ElemType>::Insert(const ElemType &e)
{
BinTreeNode<ElemType> *f;
if(Find(e,f) == NULL)
{
BinTreeNode<ElemType> *p;
p=new BInTreeNode<ElemType>(e);
if(IsEmpty())
root=p;
else if(e<f->data)
f->leftChild=p;
else
f->rightChild=p;
return true;
}
else
return false;
}
注意:同一组数据,由于输入顺序不同,所建立的二叉排序树不一定相同,查找性能也不一样
所以引出来了平衡二叉树。
删除
思想:如果删除为叶子,只需要将其双亲指向他的指针置为空,再释放改元素的存储空间即可。
如果删除的只有左(右)子树,那么让它的左(右)孩子代替他,再将它释放即可。
如果左右孩子都存在,那么可以用它左子树最右边的元素(最大的,中序遍历最后的一个)替代,再删除这个元素。或者用它右子树中最左边(最小的,中序遍历第一个)替代,再删除这个元素。(比较好的算法)
还有两种方法,但是可能会增加二叉排序树的高度:该元素右子树作为其左子树最大元素的右子树,再删除这个节点,或者让该元素左子树作为其右子树最小元素的左子树,再删除这个节点
代码(前两种):
template<class ElemType>
void BinarySortTree<ElemeType>::Delete(BinTreeeNode *&p)
{
BinTreeNode<ElemType> *tmpPtr,*tmpF;
if(p->leftChild==NULL&&p->rightChild==NULL)
{
delete p;
p=NULL;
}
else if(p->leftChild==NULL)//只有右子树
{
tmpPtr=p;
p=p->rightChild;
delete tmpPtr;
}
else if(p->rightChild==NULL)//只有左子树
{
tmpPtr=p;
p=p->leftChild;
delete tmpPtr;
}
else
{
tmpF=p;
tmpPtr=p->leftChild;
while(tmpPtr->rightChild!=NULL)
{
tmpF=tmpPtr;
tmpPtr=tmpPtr->rightChild;
}
p->data=tmpPtr->data;
if(tmpF->rightChild==tmpPtr)
Delete(tmpF->rightChild);
else
Delete(tmpF->leftChild);
}
}
if (tmpF->rightChild==tmpPtr)
Delete(tmpF->rightChild);
else
Delete(tmpF->leftChild);
这其实就是这个算法最精髓的部分,开始没想到else有什么作用,后来发现,他是在处理只有一个左孩子,而右孩子可以无限这种情况,因为这种情况就不会进while循环。
再者就是这个递归算法非常秒,你那这个节点去替换,那么下面的也要替换,递归完美解决这个问题。
二叉排序树的查找性能分析
比较次数为该结点的所在的层数
最佳性能:平均查找次数为log2n
最差性能:n*(1+n)/2/n=(n+1)/2
平衡二叉树
节点的左子树和右子树的高度之差绝对值不超过1。
平衡因子只能是-1,0,1
平衡旋转
假如插入一个新的节点之后平衡性被破坏,首先找到插入新节点后失去平衡的最小子树的根节点,然后对其进行旋转。
旋转的命名(LL、RR、LR、RL)也都是根据被破坏节点Mar(平衡因子大于一)与麻烦节点(造成这种情况的结点)的相对位置命名。
LL旋转
如果在A的左孩子B的左子树上插入新的节点,使A的平衡因子由-1变到-2,那么就需要进行LL旋转
注意一定是失去平衡的最小子树的根节点进行旋转:
RR旋转
同理
LR旋转
在A的左孩子的右子树上插入节点导致平衡因子由-1变成-2。
根为-2,左节点为1
先逆时针转,再顺时针旋转,这两个旋转时候注意节点位置的变化,因为要一直保持平衡二叉树性质。
RL旋转
在A的右孩子的左子树上插入节点导致平衡因子由1变成2。
A节点为2,右孩子为-1
平衡二叉树插入节点
插入节点之后,出现了不平衡
算法思想:
1)按二叉排序树的性质插入节点。
2)如果插入之后出现不平衡,转3,否则结束
3)找失去平衡的最小子树
4)判断平衡旋转的类型进行平衡化处理
如何发现出现不平衡现象?
平衡因子绝对值大于1,这时失去平衡的最小子树必为离插入节点最近,而且插入之前平衡因子绝对值为1的节点。
思路:
1)在查找节点x的插入位置过程中,用指针a指向从根节点到插入位置路径上距离插入位置最近的 且平衡因子的绝对值为1的节点,如果没有,那么a指向根节点。
2)对从a节点到x节点的路径上的每一个节点(不包括x),根据节点与x关键字的 比较,如果如 果节点关键字大于x,则节点平衡因子-1,否则加1。
3)如果平衡因子绝对值为2,则表示失去平衡,再根据左右孩子平衡因子值来确定旋转类型