【1. 查找】

这里是引用

1. 查找的基本概念

  1. 查找。在数据集合中寻找满足某种条件的数据元素的过程称为查找。查找的结果一般分为两种:一是查找成功,即在数据集合中找到了满足条件的数据元素;二是查找失败。
  2. 查找表。用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成。对查找表的常见操作有:①查询符合条件的数据元素;②插入、删除数据元素。
  3. 静态查找表。若一个查找表的操作只涉及查找操作,则无须动态地修改查找表,此类查找表称为静态查找表。与此对应,需要动态地插入或删除的查找表称为动态查找表。适合静态查找表的查找方法有顺序查找、折半查找、散列查找等;适合动态查找表的查找方法有二叉排序树的查找、散列查找等。
  4. 关键字。数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。例如,在由一个学生元素构成的数据集合中,学生元素中“学号”这一数据项的值唯一地标识一名学生。
  5. 平均查找长度:在这里插入图片描述
    > ● 静态查找方法:顺序查找、折半查找、散列查找
    ● 动态查找方法:二叉排序树查找、散列查找

2. 顺序查找和折半查找

2.1 顺序查找

顺序查找又称线性查找,它对顺序表和链表都是适用的。对于顺序表,可通过数组下标递增来顺序扫描每个元素;对于链表,可通过指针next 来依次扫描每个元素。顺序查找通常分为对一般的无序线性表的顺序查找和对按关键字有序的线性表的顺序查找。

2.1.1 无序表查找

其基本思想:①从线性表的一端开始,逐个检查关键字是否满足给定的条件;②若查找到某个元素的关键字满足给定条件,则查找成功,返回该元素在线性表中的位置;③若已经查找到表的另一端,但还没有查找到符合给定条件的元素,则返回查找失败的信息。

typedef struct {
    int *data;
    int size;
}List;
int searchData(List list,int key){
    list.data[0]=key;  //哨兵 下面的语句从后往前查找 找到了就返回 没找到返回0 代表没有找到
    //这样写主要是为了减少数组越界的条件
    for(int i=list.size;list.data[i]!=key;i--)
    return i;
}
  1. 查找成功:每一个是1/n 比较次数是n-i+1次 平均查找长度ASL=(n+1)/2
  2. 查找不成功:ASL=n+1
  3. 对链表只能顺序查找
  4. 缺点:平均查找长度大,效率低
  5. 优点:对数据元素的存储没有要求,顺序存储或链式存储皆可。对表中记录的有序性也没有要求,无论记录是否按关键字有序,均可应用。同时还需注意,对链表只能进行顺序查找

2.1.2 有序表查找

  1. 若在查找之前就已知表是关键字有序的,则查找失败时可以不用再比较到表的另一端就能返回查找失败的信息,从而降低查找失败的平均查找长度。假设表L是按关键字从小到大排列的,查找的顺序是从前往后,待查找元素的关键字为 key,当查找到第i个元素时,发现第i个元素的关键字小于 key,但第i+1个元素的关键字大于key,这时就可返回查找失败的信息,因为第i个元素之后的元素的关键字均大于key,所以表中不存在关键字为 key的元素。
  2. 如图:可知假设有6个结点查找识别失败的结点是有7个,也就是n+1,在有序线性表的顺序查找中,查找成功的平均查找长度和一般线性表的顺序查找一样。查找
    失败时,查找指针一定走到了某个失败结点。这些失败结点是我们虚构的空结点,实际上是不存在的,所以到达失败结点时所查找的长度等于它上面的一个圆形结点的所在层数。查找不成功的平均查找长度在相等查找概率的情形下为
    在这里插入图片描述
    ASL:在这里插入图片描述
    3.有序线性表的顺序查找和后面的折半查找的思想是不一样的,且
    有序线性表的顺序查找中的线性表可以是链式存储结构,而折半查找中的线性表只能是顺序存储结构

2.2 折半查找

  1. 折半查找,也称为二分查找,是一种在有序数组中查找特定元素的高效算法。该算法每次通过比较中间元素与目标值的大小,将查找范围缩小一半,直到找到目标元素或确定目标元素不存在。
  2. 原理:在有序数组中,首先确定查找范围的最低点(low)和最高点(high),然后计算中间位置(mid)。如果中间位置的元素等于目标值,则查找成功;如果目标值小于中间元素,则在低半区继续查找;反之,在高半区继续查找。重复这个过程,直到找到目标值或查找范围为空。
  3. 折半查找的基本思想:①首先将给定值 key与表中中间位置的元素比较,若相等,则查找成功,返回该元素的存储位置;②若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分(例如,在查找表升序排列时,若 key大于中间元素,则所查找的元素只可能在后半部分),然后在缩小的范围内继续进行同样的查找。重复上述步骤,直到找到为止,或确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。
    private static int halfSearch(int[] arr, int key) {
        int low = 0, high = arr.length - 1, mid;
        while (low <= high) {
            mid = low + (high - low) / 2;
            if (arr[mid] == key) {
                return mid;
            } else if (arr[mid] > key) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        return -1;
    }

2.2.1 折半查找判定树

这圆结点表示记录,查找成功最终一定落在圆结点上。查找失败为方形结点。
在这里插入图片描述
在这里插入图片描述

2.3 分块查找

  1. 分块查找又称索引顺序查找,它吸取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找。
  2. 分块查找的基本思想:将查找表分为若干子块。块内的元素可以无序,但块间的元素是有序的,即第一个块中的最大关键字小于第二个块中的所有记录的关键字,第二个块中的最大关键字小于第三个块中的所有记录的关键字,以此类推。再建立一个索引表,索引表中的每个元素含有各块的最大关键字和各块中的第一个元素的地址,索引表按关键字有序排列。
  3. 在这里插入图片描述

3. 树形查找

3.1 二叉排序树(BST)

3.1.1 二叉排序树的定义

  1. 构造一棵二叉排序树的目的并不是排序,而是提高查找、插入和删除关键字的速度二叉排序树这种非线性结构也有利于插入和删除的实现
  2. 二叉排序树的定义:二叉排序树(也称二叉查找树)或者是一棵空树,或者是具有下列特性的二叉树:①若左子树非空,则左子树上所有结点的值均小于根结点的值。②若右子树非空,则右子树上所有结点的值均大于根结点的值。③左、右子树也分别是一棵二叉排序树。
  3. 由于二叉排序树的特性,进行一次中序遍历得到的序列为递增的有序序列

3.1.2 二叉排序树的查找

  1. 从根结点开始向下比较,二叉排序树非空,先将给定值与根结点的关键字比较,若相等,则查找成功;若不等,若小于根结点的关键字,则在根结点的左子树上查找,否则在根结点的右子树上查找。这显然是一个递归的过程。
//递归调用
BSTNode *search(BSTree T, int key) {
    if (T == NULL) return NULL;
    if (key == T->data) return T;
    else if (key < T->data) return search(T->lChild, key);
    else return search(T->rChild, key);
}
//非递归调用
BSTNode *search2(BSTree T, int key) {
    if (T == NULL)return NULL;
    while (T != NULL && key != T->data) {
        if (key < T->data) T = T->lChild;
        else T = T->rChild;
    }
    return T;
}

3.1.3 二叉排序树的插入

  1. 二叉排序树作为一种动态树表,其特点是树的结构通常不是一次生成的,而是在查找过程中,当树中不存在关键字值等于给定值的结点时再进行插入的。插入结点的过程如下:若原二叉排序树为空,则直接插入;否则,若关键字k小于根结点值,则插入到左子树,若关键字k大于根结点值,则插入到右子树。插入的结点一定是一个新添加的叶结点,且是查找失败时的查找路径上访问的最后一个结点的左孩子或右孩子。
bool insert(BSTree &T, int key) {
    if (T == NULL) {
        T = new BSTNode;
        T->data = key;
        T->lChild = T->rChild = NULL;
        return true;
    } else if (key == T->data) {
        return false;
    } else if (key < T->data) {
        return insert(T->lChild, key);
    } else {
        return insert(T->rChild, key);
    }
}

3.1.4 二叉排序树的构造

  • 从一棵空树出发,依次输入元素,将它们插入二叉排序树中的合适位置。
    注意:二叉树是一种动态查找表。特点是,树的结构不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的结点时再进行插入。新插入的结点一定是一个新添加的叶子结点,并且是查找不成功时查找路径上访问的最后一个结点的左孩子或右孩子结点。
    在这里插入图片描述

3.1.5 二叉排序树的刪除

在二叉排序树中删除一个结点时,不能把以该结点为根的子树上的结点都删除,必须先把被删除结点从存储二叉排序树的链表上摘下,将因删除结点而断开的二叉链表重新链接起来,同时确保二叉排序树的性质不会丢失。删除操作的实现过程按3种情况来处理:
若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的性质。
若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置。
若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。
在这里插入图片描述

3.1.6 二叉排序树的查找效率

  1. 二叉排序树的查找效率,主要取决于树的高度。若二叉排序树的左、右子树的高度之差的绝对值不超过1(平衡二叉树,下一节),它的平均查找长度为 O(log2n)。若二叉排序树是一个只有右(左)孩子的单支树(类似于有序的单链表),则其平均查找长度为O(n)。【有序数插入的话实际上就变成了线性表,英雌查找长度为n,如下图的b情况】
    在这里插入图片描述
  2. 从查找过程看,二叉排序树与二分查找相似。就平均时间性能而言,二叉排序树上的查找和二分查找差不多。但二分查找的判定树唯一,而二叉排序树的查找不唯一,相同的关键字其插入顺序不同可能生成不同的二叉排序树。
    就维护表的有序性而言,二叉排序树无须移动结点,只需修改指针即可完成插入和删除操作,平均执行时间为O(log2n)。二分查找的对象是有序顺序表,若有插入和删除结点的操作,所花的代价是O(n)。【不同的场景用不同的查找方法当有序表是静态查找表时,宜用顺序表作为其存储结构,而采用二分查找实现其找操作若有序表是动态查找表,则应选择二叉排序树作为其逻辑结构。

3.2 平衡二叉树(AVL)

思考下平衡二叉树的出现是为了解决什么问题?

3.2.1 平衡二叉树的定义

  1. 为了避免树的高度增长过快,降低二叉排序树的性能,规定在插入和删除结点时,要保证任意结点的左、右子树高度差的绝对值不超过1,将这样的二叉树称为平衡二叉树(Balanced Binary Tree),也称 AVL树。定义结点左子树与右子树的高度差为该结点的平衡因子,则平衡二叉树结点的平衡因子的值只可能是-1、0或1。
  2. 平衡二叉树可定义为或是一棵空树,或是具有下列性质的二叉树:①它的左子树和右子树都是平衡二叉树,且左子树和右子树的高度差的绝对值不超过1。图7.9(a)所示是平衡二叉树,图 7.9(b)所示是不平衡的二叉树。结点中的数字为该结点的平衡因子。在这里插入图片描述

3.2.2 平衡二叉树的插入

  1. 二叉排序树保证平衡的基本思想如下:每当在二叉排序树中插入(或删除)一个结点时,首先检查其插入路径上的结点是否因为此次操作而导致了不平衡。若导致了不平衡,则先找到插入路径上离插入结点最近的平衡因子的绝对值大于1的结点A,再对以A为根的子树,在保持二叉排序树特性的前提下,调整各结点的位置关系,使之重新达到平衡。
    在这里插入图片描述
  2. 插入的四种旋转
3.2.2.1 LL型

LL平衡旋转(右单旋转)。由于在结点A的左孩子(L)的左子树(L)上插入了新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要一次向右的旋转操作。将A的左孩子B向右上旋转代替A成为根结点,将A向右下旋转成为B的右孩子,而B的原右子树则作为A的左子树。如,【这里的a图原本是平衡的,但是A的左孩子的左子树插入了新的结点,导致A的的左子树的高度-右子树高度=2,不满足平衡二叉树的定义,因此要进行右旋,而右旋其实就是找到不平衡的最小子树进行调整,这里的最小子树就是A、B、BL,因此调整后变成c图】在这里插入图片描述

3.2.2.2 RR型

RR平衡旋转(左单旋转)。由于在结点A的右孩子®的右子树®上插入了新结点,
A的平衡因子由-1 减至-2,导致以A为根的子树失去平衡,需要一次向左的旋转操作。
将A的右孩子B向左上旋转代替A成为根结点,将A向左下旋转成为B的左孩子,而 B
的原左子树则作为A的右子树,如
在这里插入图片描述

3.2.2.3 LR型

LR平衡旋转(先左后右双旋转)。由于在A的左孩子(L)的右子树®上插入新结点,A的平衡因子由1增至2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先左旋转后右旋转。先将A的左孩子B的右子树的根结点C向左上旋转提升到B的位置,然后把结点C向右上旋转提升到A的位置,如
在这里插入图片描述

3.2.2.4 RL型

RL平衡旋转(先右后左双旋转)。由于在A的右孩子®的左子树(L)上插入新结点,A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,需要进行两次旋转操作,先旋转后左旋转。先将A的右孩子B的左子树的根结点C向右上旋转提升到 B的位置,然后把结点C向左上旋转提升到A的位置
在这里插入图片描述
【总结的来说,实际上旋转的操作每次旋转只要选取最小不平衡子树就行,而不平衡子树以三个结点的相对中位数进行旋转即可】

3.2.3 平衡二叉树的删除【★★★★★】

  1. 与平衡二叉树的插入操作类似,以删除结点w为例来说明平衡二叉树删除操作的步骤:①用二叉排序树的方法对结点w执行删除操作。②若导致了不平衡,则从结点 w开始向上回溯,找到第一个不平衡的结点z(即最小不平衡子树);y为结点z的高度最高的孩子结点;x是结点y的高度最高的孩子结点。③然后对以z为根的子树进行平衡调整,其中x、y和z可能的位置有4种情况:
    ●y是z的左孩子,x是y的左孩子(LL,右单旋转);
    ●y是z的左孩子,x是y的右孩子(LR,先左后右双旋转);
    ●y是z的右孩子,x是y的右孩子(RR,左单旋转);
    ●y是z的右孩子,x是y的左孩子(RL,先右后左双旋转)。
  2. 这四种情况与插入操作的调整方式一样。不同之处在于,插入操作仅需要对以z为根的子树进行平衡调整而删除操作就不一样,先对以z为根的子树进行平衡调整,若调整后子树的高度减1,则可能需要对z的祖先结点进行平衡调整,甚至回溯到根结点(导致树高减1)
    在这里插入图片描述

3.2.4 平衡二叉树的查找

  1. 在平衡二叉树上进行查找的过程与二叉排序树的相同。因此,在查找过程中,进行关键字的比较次数不超过树的深度。假设以 nh表示深度为h的平衡二叉树中含有的最少结点数。显然,有n0=0,n1=1,n2=2,并且有nh=nh-2+nh-1+1
  2. 含有n个结点的平衡二叉树的最大深度为O(log2n),因此平均查找效率为O(log2n)。

3.3 红黑树(RBT)

后续补充

3.3.1 红黑树的定义

3.3.2 红黑树的插入

3.3.3 红黑树的删除

4. B树和B+树

4.1 B树定义及基本操作

4.1.1 B树的定义解释

  1. 定义:所谓m阶B树是所有结点的平衡因子均等于0的m路平衡查找树
  2. m阶B树的五个性质
    ①除根结点与终端结点外每一个结点至少有(m/2)向上取整个子树,也就是节点内关键字个数至少为(m/2)-1。
    ②每个结点最多有m个子树,最多有m-1个关键字。
    ③若根结点不是叶结点,至少有2个子树,即至少有一个关键字。
    ④结点内关键字内部有序
    所有的叶结点“都出现在同一层次上,并且不带信息(可以视为外部结点或类似于折半查找判定树的失败结点,实际上这些结点并不存在,指向这些结点的指针为空)。

4.1.2 B树的查找

  1. 在B树上进行查找与二叉排序树很相似,只是每个结点都是多个关键字的有序表,在每个结点上所做的不是两路分支决定,而是根据该结点的子树所做的多路分支决定
  2. B树的查找包含两个基本操作:①在B树中找结点;②在结点内找关键字。由于B树常存储在磁盘上,则前一查找操作是在磁盘上进行的,而后一查找操作是在内存中进行的,即在磁盘上找到目标结点后,先将结点信息读入内存,然后再采用顺序查找法或折半查找法。因此,在磁盘上进行查找的次数即目标结点在B树上的层次数,决定了B树的查找效率。【内存查找是很快的,查找效率主要取决于外存查找中的B树关键结点在B数中的高度。高度越深,需要的IO就越多,效率就越低】
  3. 在B树上查找到某个结点后,先在有序表中进行查找,若找到则查找成功,否则按照对应的指针信息到所指的子树中去查找。查找到叶结点时(对应指针为空),则说明树中没有对应的关键字,查找失败。

4.1.3 B树的高度

  1. B树中的大部分操作所需的磁盘存取次数与B树的高度成正比
  2. B树的高度不包括最后的不带任何信息的叶结点所处的那一层(有些书对B树的高度的定义中,包含最后的那一层)
  3. n个关键字的高度为h的m阶B树在这里插入图片描述
    logm(n+1)=<h=<logceil(m/2)(n+1)/2 +1

4.1.4 B树的插入

  1. 与二叉排序树的插入操作相比,B树的插入操作要复杂得多。在B树中查找到插入的位置后,并不能简单地将其添加到终端结点(最底层的非叶结点)中,因为此时可能会导致整棵树不再满足B树定义中的要求。
    ①利用前述的B树查找算法,找出插入该关键字的终端结点(在B树中查找 key时,会找到表示查找失败的叶结点,因此插入位置一定是最底层的非叶结点)。
    ②每个非根结点的关键字个数都在[ceil[m/2]-1,m-1]。若结点插入后的关键字个数小于m,可以直接插入;若结点插入后的关键字个数大于m-1,必须对结点进行分裂。
    ③分裂的方法是:取一个新结点,在插入 key 后的原结点,从中间位置([m/2])将其中的关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置([m/2])的结点插入原结点的父结点。若此时导致其父结点的关健字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根结点为止,进而导致B树高度增1。在这里插入图片描述

4.1.5 B树的删除【★★★★★】

  1. B树的删除操作与插入操作类似,但要稍微复杂一些,即要使得删除后的结点中的关键字个数≥[m/2]-1,因此将涉及结点的“合并”问题。
  2. 当被删关键字k不在终端结点中时,可以用k的前驱(或后继)k’,即k的左侧子树中“最右下”的元素(或右侧子树中“最左下”的元素),来替代k,然后在相应的结点中删除k’,关键字k’必定落在某个终端结点中,则转换成了被删关键字在终端结点中的情形。
  3. 需讨论被删关键字在终端结点中的情形,有下列三种情况:
    ①直接删除关键字。若被删关键字所在结点删除前的关键字个数≥[m/2],表明删除该关键字后仍满足B树的定义,则直接删去该关键字。
    ②弟够借。若被删关键字所在结点删除前的关键字个数=[m/2]-1,且与该结点相邻的右(或左)兄弟结点的关键字个数≥[m/2],则需要调整该结点、右(或左)兄弟结点及其双亲结点(父子换位法),以达到新的平衡。【兄弟够借,兄代父,父代子】在这里插入图片描述
    ③兄弟不够借。若被删关键字所在结点删除前的关键字个数=「m/2]-1,且此时与该结点相邻的左、右兄弟结点的关键字个数都=「m/2]-1,则将关键字删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并在这里插入图片描述
    注意:在合并过程中,双亲结点中的关键字个数会减1。若其双亲结点是根结点且关键字个数减少至0(根结点关键字个数为1时,有2棵子树),则直接将根结点删除,合并后的新结点成为根;若双亲结点不是根结点,且关键字个数减少到「m/2]-2,则又要与它自己的兄弟结点进行调整或合并操作,并重复上述步骤,直至符合B树的要求为止。

4.2 B+树的基本概念

  1. B+树是应数据库所需而出现的一种 B树的变形树。
  2. 五个性质:
    ①每个分支结点最多有m棵子树(孩子结点)。
    ②非叶根结点至少有两棵子树,其他每个分支结点至少有「m/2]棵子树。
    结点的子树个数与关键字个数相等。【与B树不同】
    ④有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来(支持顺序查找)。
    所有分支结点(可视为索引的索引)中仅包含它的各个子结点(即下一级的索引块)中关键字的最大值及指向其子结点的指针

4.2.1 B树和B+树的对比

  1. 在 B+树中,具有n个关键字的结点只含有n棵子树,即每个关键字对应一棵子树;而在B树中,具有n个关键字的结点含有n+1棵子树
  2. 在 B+树中,每个结点(非根内部结点)的关键字个数n的范围是「m/2]≤n≤m(非叶根结点:2≤n≤m);而在B树中,每个结点(非根内部结点)的关键字个数n的范围是「m/2]-1≤n≤m-1(根结点:l≤n≤m-1)
  3. 在 B+树中,叶结点包含了全部关键字,非叶结点中出现的关键字也会出现在叶结点中:而在B树中,最外层的终端结点包含的关键字和其他结点包含的关键字是不重复的
  4. 在 B+树中,叶结点包含信息,所有非叶结点仅起索引作用,非叶结点的每个索引项只含有对应子树的最大关键字和指向该子树的指针,不含有对应记录的存储地址。这样能使一个磁盘块存储更多的关键字,使得磁盘读/写次数更少,查找速度更快
  5. 在 B+树中,用一个指针指向关键字最小的叶结点,将所有叶结点串成一个线性链表。
    在 B+树中查找时,无论查找成功与否,每次查找都是一条从根结点到叶结点的路径。
    以下图片出自-原博主苏泽SuZe-博客地址在这里插入图片描述

4.2.2 BST|AVL|RBT|B|B+对比及总结

在这里插入图片描述

5. 散列表

5.1 散列表的基本概念

  1. 前面介绍的线性表和树表的查找中,查找记录需进行一系列的关键字比较,记录在表中的位置与记录的关键字之间不存在映射关系,因此在这些表中的查找效率取决于比较的次数
  2. 散列函数(也称哈希函数):一个把查找表中的关键字映射成该关键字对应的地址的函数,记为 Hash(key)=Addr(这里的地址可以是数组下标、索引或内存地址等)。
    散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生冲突的不同关键字称为同义词一方面,设计得好的散列函数应尽量减少这样的冲突另一方面,由于这样的冲突总是不可避免的,所以还要设计好处理冲突的方法
  3. 散列表(也称哈希表):根据关键字而直接进行访问的数据结构。也就是说,散列表建立了关键字和存储地址之间的一种直接映射关系。 理想情况下,对散列表进行查找的时间复杂度为O(1),即与表中元素的个数无关。

5.2 散列函数的构造方法

  1. 散列函数的定义域必须包含全部关键字,而值域的范围则依赖于散列表的大小。
  2. 散列函数计算出的地址应尽可能均匀地分布在整个地址空间,尽可能地减少冲突。
  3. 散列函数应尽量简单,能在较短的时间内计算出任意一个关键字对应的散列地址。

5.2.1 直接定址法

直接定址法实际上就是用某个线性函数来表示。优点:不会发生冲突;缺点:如果关键字分布不连续会造成大量的存储空间浪费。
这里是引用

5.2.2 除留余数法

最常用的一种方法,取不大于m的质数p用于key%p。
模p为最接近m的质数时造成冲突的可能性最小。
在这里插入图片描述

5.2.3 数字分析法

  1. 设关键字是r进制数(如十进制数),而r个数码在各位上出现的频率不一定相同,可能在某些位上分布均匀一些,每种数码出现的机会均等;而在某些位上分布不均匀,只有某几种数码经常出现,此时应选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。
  2. 比如学号一个班的同学前几位都是2022030XX,后两位每种数码(0,1,2…)出现的机会均等,此时应选取数码分布较为均匀的若干位作为散列地址。该方法适合于已知关键字集合,若更换了关键字(身份号),则需要重新构造新的散列函数。

5.2.4 平方取中法

  1. 顾名思义,这种方法取关键字的平方值的中间几位作为散列地址。具体取多少位要视实际情况而定。这种方法得到的散列地址与关键字的每位都有关系,因此使得散列地址分布比较均匀适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数
  2. 在不同的情况下,不同的散列函数具有不同的性能,因此不能笼统地说哪种散列函数最好。在实际选择中,采用何种构造散列函数的方法取决于关键字集合的情况。【根据实际情况来确定选择哪种构造方法,不谈场景不谈好坏】

5.3 处理冲突的方法

  1. 任何设计出来的散列函数都不可能绝对地避免冲突。为此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找下一个“空”的 Hash 地址。用 Hi;表示处理冲突中第i次探测得到的散列地址,假设得到的另一个散列地址 Hi仍然发生冲突,只得继续求下一个地址Hk,以此类推,直到 H不发生冲突为止,则H为关键字在表中的地址。

5.3.1 开放定址法

  • 开放地址法主要指的是【空闲地址-既向同义词开放,又向非同义词开放】
  • 注意采用开放定址法时,不能随便物理删除表中已有元素,否则会截断其他同义词元素的查找路径。因此,要删除一个元素时,可以做一个删除标记,进行逻辑删除。但这样做的副作用是:执行多次删除后,表面上看起来散列表很满,实际上有许多位置未利用。在这里插入图片描述
5.3.1.1 线性探测再散列法
  • 线性探测的逻辑是冲突发生时,顺序查看表中下一个单元(探测到表尾地址m-1时,下一个探测地址是表首地址 0)。直到找出一个空闲单元(当表未填满时一定能找到一个空闲单元)或查遍全表。
  • 线性探测法可能使第i个散列地址的同义词存入第i+1个散列地址,这样本应存入第i+1个散列地址的元素就争夺第1+2个败列地址的元素的地址……从而造成大量元素在相邻的散列地址上聚集(或堆积)起来,大大降低了查找效率
    在这里插入图片描述
5.3.1.2 平方探测法


当序列为02,12,22,32,42…(k-1)2,k2时,称为平方探测法,其中 k ≤m/2,散列表长度m必须是一个可以表示为4k+3的素数,又称二次探测法。
平方探测法是一种处理冲突的较好方法,可以有效避免出现“堆积”问题,它的缺点是不能探测到散列表上的所有单元,但至少能探测到一半单元。

5.3.1.3 双散列法

需要使用两个散列函数,当通过第一个散列函数 H(key)得到的地址发生冲突时,则利用第二个散列函数 Hash2(key)计算该关键字的地址增量。它的具体散列函数形式如下:
Hi=(H(key)+ixHash2(key))%m
初始探测位置H0=H(key)%m。i是冲突的次数,初始为0。。
在这里插入图片描述

5.3.1.4 伪随机序列法

d=伪随机数序列。
在这里插入图片描述

5.3.2 拉链法(链接法,chaining)

显然,对于不同的关键字可能会通过散列函数映射到同一地址,为了避免非同义词发生冲突,可以把所有的同义词存储在一个线性链表中,这个线性链表由其散列地址唯一标识。假设散列地址为i的同义词链表的头指针存放在散列表的第i个单元中,因而查找、插入和删除操作主要在同义词链中进行。拉链法适用于经常进行插入和删除的情况。
在这里插入图片描述

5.4 散列查找及性能分析

这里是引用
散列表的查找效率取决于三个因素:散列函数、处理冲突的方法和装填因子。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青北念

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值