目录
一、查找的基本概念和顺序查找
二、分块查找
分块查找思想:
分块查找分析:
三、折半查找
折半查找判定树:
算法思路:
四、平衡二叉树(AVL树)
平衡因子:
AVL树插入:
AVL树删除:
平衡调整:
五、二叉排序树
搜索
插入与删除
二叉搜索树性能分析:
算法分析:
查找关键字代码
插入关键字代码
构造代码
删除节点
六、散列表
构造散列函数的tips:
1.常用Hash函数的构造方法:
2.常用Hash函数的冲突处理办法:
1.开放定址法:
2.拉链法:
3.散列表的查找过程:
4.散列表的查找性能:
查找定义:在数据集合中寻找满足某种条件的数据元素的过程称为查找
关键字:数据元素中某个可以以唯一标识该元素的数据项
平均查找长度(ASL:Average Search Length):在查找的过程中,一次查找的长度是指需要比较的关键字次数,而平均查找长度则是所有查找过程中进行关键字的比较次数的平均值
顺序查找(线性查找),主要用于在线性表中进行查找。从查找表的一端开始,顺序扫描查找表,依次将扫描到的关键字和待查找的值key进行比较。如果相等,则查找成功。如果扫描结束仍然没有发现相等的数据元素,则查找失败。
时间复杂度为O(n)
分块查找又称为索引顺序查找
①确定待查找值在哪个块(折半查找)
②在确定的块中查找待查找值(顺序查找)
由于分块查找实际是进行两次查找,所以整个算法的平均查找长度是两次查找的平均查找长度之和。 即ASL分块=ASL折半+ASL顺序
对于折半查找,查找的比较次数就是从根结点到该结点经历的结点数,时间复杂度为O(logn),具有N个(N>0)结点的完全二叉树的高度为 [log2(N+1)] 或 [log2N] +1。
首先将给定值key与表中中间位置元素的关键字比较,若相等,则查找成功,返回该元素的存储位置;若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分中。然后在缩小的范围内继续进行同样的查找,如此重复直到找到为止,或者确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。
含有n个结点平衡二叉树的最大深度为O(log2n),因此,平衡二叉树的平均查找长度为O(log2n)。平衡二叉树(AVL树)是特殊二叉排序树,特殊的地方在左右子树的高度之差绝对值不超过1,而且左右子树又是一棵平衡二叉树。
定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是−1、0或1。
如果是空树,插入后即为根结点,插入后直接返回true。
如果树不空,寻找插入位置,若在查找过程中找到key,则插入失败直接返回false。
插入节点。
更新平衡因子,对树进行调整。
被删除结点只有左孩子,parent的平衡因子为1或者-1,parent高度不变,则从parent到根所有结点高度均不变,不用调整。
被删除结点只有右孩子,parent的平衡因子变成0,虽然以parent为根的子树平衡,其高度减1,但需要检查parent的双亲结点的平衡性。
被删除结点左右孩子都有,变为删除中序遍历下的第一个结点q,结点parent的平衡因子为2或者-2,则较矮子数缩短,parent发生不平衡,需要进行平衡化旋转。
parent较高子树的根为q,如果q的平衡因子为0,执行单循环恢复pa;如果q的平衡因子与parent平衡因子(正负)号相同,则执行一个单循环恢复parent;如果q的平衡因子与parent平衡因子(正负)号相反,则执行一个双旋转恢复parent。
平衡二叉树的建立过程和二叉排序树的建立过程是相似的,都是从一棵空树开始陆续插入结点。不同的地方在于对于平衡二叉树的建立过程中,由于插入结点可能会破坏结点的平衡性,所以需要进行平衡调整。
二叉排序树(Binary Search Tree 也叫二叉搜索树)或者是一棵空树,或者是具有以下性质的二叉树 ①若左子树不空,则左子树上所有结点的值均小于它的根结点的值。 ②若右子树不空,则右子树上所有结点的值均大于它的根结点的值。 ③它的左右子树也是一棵二叉排序树。查找时间复杂度是O(n)
若根结点不为空, 根结点key==要查找的key,返回true;
根结点key>查找key,在其左子树查找;
根结点key<查找key,在其右子树查找;
否则返回false。
插入:首先检测这个节点是不是已经存在,要是存在不插入 ;否则将元素插入到找到的位置。
删除:首先检测这个节点是不是已经存在,有的情况;
要删除的节点没有孩子节点,直接删除该结点;
要删除的节点只有左孩子,删除该结点并使被删除结点的双亲结点指向被删除结点的左孩子结点;
要删除的节点只有右孩子,删除该结点并使被删除结点的双亲结点指向被删除结点的右孩子结点;
要删除的节点有左、右孩子结点,在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题
最坏情况下,平均查找长度为O(n);一般情况下平均长度为O(logn)
由于二叉排序树的特点(左子树<根结点<右子树),所以每次查找一个关键字,需要先和根结点进行比较: 如果这个关键字小于根结点的值,则再到这个根结点的左子树进行同样的比较操作一直进行下去直到找到该关键字,表示查找成功,或者是空指针,表示查找失败。 如果这个关键字大于根结点的值,则再到这个根结点的右子树进行同样的比较操作一直进行下去直到找到该关键字,表示查找成功,或者是空指针,表示查找失败。
1)空树:直接插入新结点返回成功
2)树不空:检查是否存在关键字重复的结点:
①存在:返回插入失败
②不存在:检查根结点的值和待插入关键字值的大小关系递归插入左右子树
①删除的是叶子结点,方法:直接删去该结点即可
②删除的是仅有左子树或者右子树的结点,方法:“子承父业”
③删除的是左右子树都有的结点,仿照②类型,先将一个孩子“继承父业”,另一个孩子“归顺”于这个孩子,方法:找到待删除结点的直接前驱或者直接后继结点,用该结点来替换待删除结点,再删除该结点。
散列表:根据给定的关键字来计算出关键字在表中的地址的数据结构。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。
散列函数:Hash(key)=Addr.即存储位置=f(关键字)
散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为“冲突”,这些发生碰撞的不同关键字称为同义词。
1)散列函数的定义域必须包含全部需要存储的关键字,而值域的范围则依赖于散列表的大小或地址范围。
2)散列函数计算出来的地址应该能等概率、均匀地分布在整个地址空间,从而减少冲突的发生。
3)散列函数应尽量简单,能够在较短的时间内就计算出任一关键字对应的散列地址。
1.开放定址法:直接取关键字的某个线性函数值为散列地址,散列函数为H(key)=a×key+b。式中,a和b是常数。这种方法计算最简单,并且不会产生冲突
2.除留余数法:假定散列表表长为m,取一个不大于m但最接近或等于m的质数p,利用以下公式把关键字转换成散列地址。散列函数为H(key)=key % p 除留余数法的关键是选好p,使得每一个关键字通过该函数转换后等概率地映射到散列空间上的任一地址,从而尽可能减少冲突的可能性
将产生冲突的Hash地址作为自变量,通过某种冲突解决函数得到一个新的空闲的Hash地址。
1)线性探测法:(di+k)%p k=1,2.。。。
2)再散列法:又称为双散列法。需要使用两个散列函数,当通过第一个散列函数H(Key)得到的地址发生冲突时,则利用第二个散列函数Hash2(Key)计算该关键字的地址增量。
对于不同的关键字可能会通过散列函数映射到同一地址,为了避免非同义词发生冲突,可以把所有的同义词存储在一个线性链表中,这个线性链表由其散列地址唯一标识。拉链法适用于经常进行插入和删除的情况。
类似于构造散列表,给定一个关键字Key。 先根据散列函数计算出其散列地址。然后检查散列地址位置有没有关键字。
1)如果没有,表明该关键字不存在,返回查找失败。
2)如果有,则检查该记录是否等于关键字。
①如果等于关键字,返回查找成功。
②如果不等于,则按照给定的冲突处理办法来计算下一个散列地址,再用该地址去执行上述过程。
和装填因子有关。α越大,表示装填的记录越“满”,发生冲突的可能性就越大,反之发生冲突的可能性越小