• 数据结构-查找算法以及查找结构


    • 二分查找(折半查找)

    二分查找适用的存储结构是顺序表,且必须是有序的

    实现代码:

    public class BinarySearch {
    
        public static void main(String[] args) {
    
            int[] nums = {3, 7, 8, 9, 13, 22, 31, 59, 442};
    
            int idx = binarySearch(nums, 7);
    
            System.out.println(idx);
    
        }
    
        private static int binarySearch(int[] nums, int target) {
            int start = 0;
            int last = nums.length - 1;
            int middle = -1;
            while (start <= last) {
                //取中间值
                middle = (start + last) / 2;
                int value = nums[middle];
                if (value == target){
                    return middle;
                }
                if (value > target){
                    last = middle - 1;
                }
                if (value < target){
                    start = middle + 1;
                }
            }
            return -1;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    排好序的顺序表如下

    3, 7, 8, 9, 13, 22, 31, 59, 442

    定义两个指针start,last,初始值分别指向顺序表的第一与最后一个元素,然后依次去中间值,中间值这里是向下取整,如果是向下取整,则从中间节点的左节点的数量比右节点的数量少0个或1个。比较中间值与目标值的大小,如果小于,则移动start指针,如果大于,则移动last指针,直到找到位置。

    折半查找的算法时间复杂度为log2n。查找判定树是一颗平衡二叉树(左右子树的高度差不超过1),且还是一颗有序二叉树(右节点比根节点大,左节点比根节点小),在构造查找判定树时,树的右节点会比左节点多1或0个节点。

    • 分块查找(索引顺序查找)

    分开查找把n个元素平均分为根号n个块,每个块中有根号n个元素,此时的平均查找速度最快
    ASL = 根号(n) + 1。例如总元素个数为10000个,则需要分为100个块,每个块有100个元素,此时最好的平均查找长度ASL=100+1。

    如果是将n个元素平均分为b块,每块分为s个元素,则ASL = (b+1/2) + (s+1/2) 。

    • 二叉排序树

    给定一组顺序列,求二叉排序树的构造过程,ASL(每个节点的平均查找长度)以及所有失败情况的平均查找长度(失败情况就是所有空链域节点,n个节点的二叉树共有n+1个空链域节点)。

    二叉排序树的删除:

    1.当被删除的节点是叶子节点时,只需将父节点对应的左节点或右节点置为null即可。
    2.当被删除的节点只有左节点没有右节点时,则让第一个左孩子节点来充当该节点。
    3.当被删除的节点只有右节点没有左节点时,则让第一个右孩子节点来充当该节点。
    4.当被删除的节点既有左节点又有右节点时,此时可以利用中序遍历(二叉树的中序遍历顺序时递增排好序的)找到左子树中最大的节点或者右子树中最小的节点来充当该节点。

    • 平衡二叉树(AVL树)

    平衡因子 = 左子树高度 - 右子树高度
    平衡因子的绝对值不能超过1,最多能取到 -1,1,0 这三种情况。当一个树的某个子树不满足这种情况时,需要进行树的平衡化操作。

    1. 平衡二叉树的旋转操作

    如果新插入的节点导致了树的不平衡,则要看新插入的节点和根节点的相对位置,可有以下四种情况:

    祖先节点 - 父节点 - 末节点

    LL:插入的节点位于最小不平衡树的左节点的左节点,此时只需右旋父节点一次。

    RR:插入的节点位于最小不平衡树的右节点的右节点,此时只需要左旋父节点一次。

    RL:插入的节点位于最小不平衡树的右节点的左节点,此时需要旋转的是末节点,最终的效果是把末节点上升到祖先节点的位置,由于是RL,所以需要先将末节点左旋,再右旋。

    LR:同RL,需将末节点先右旋,再左旋。

    1. 平衡二叉树的深度为h 最小节点个数

    nh = nh-1 + nh-2 + 1 (n >= 3)

    n0 = 0,n1 = 1,n2 = 2,

    n3 = n1 + n2 + 1 = 4

    n4 = n2 + n3 + 1 = 7

    1. 平衡二叉树的删除操作

    平衡二叉树也是一颗二叉排序树,所以满足二叉排序树的删除规则
    3.1 二叉排序树的删除规则

    3.1.1如果是叶子节点,则直接删除
    3.1.2如果被删除的节点只有左节点没有右节点,则让第一个左节点充当此节点位置即可
    3.1.3如果被删除的节点只有右节点没有左节点,则让第一个右节点充当此节点位置即可
    3.1.4如果被删除的节点既有左节点又有右节点,则需要递归的找出最节点中最大的或右节点中最小的子节点来充当此节点。

    但是如果是平衡二叉树,则直接删除某个节点后,可能会导致树的不平衡性,此时需要从调整的节点开始向上找到第一个不平衡的子树,找到这个子树的根节点以后,继续找到当前根节点高度最高的子节点(可能是左子树,也可能是右子树), 继续找从高度最高的子节点开始找,找到高度最高的后代节点,一共需要找2次,此时确定出当前后代节点相对于最开始的根节点的位置,是RR,LL,RL,还是LR,然后进行相应的旋转即可。调整完本次之后,继续递归查找,直到不存在不平衡的树为止。

    • 红黑树(RBT)

    在这里插入图片描述

    红黑树是平衡二叉树的一种优化,平衡二叉树在删除某个节点时可能需要进行大量的旋转操作后才能使得树重新保持平衡。而红黑树在删除某个节点后在常数级的时间内就可以使树保持平衡。这里要注意,红黑树首先是一颗二叉排序树,还得是一颗平衡二叉树,也就是说它本身也是一个平衡二叉排序树。

    红黑树的几个特性

    1.节点非黑即红
    2. 根节点和叶子节点为黑色,注意这里的叶子节点并不是度为0的节点,而是指的那些个空链域指针。
    3. 不能存在相邻的父子节点都是红色,也就是说,如果父节点是红色,子节点必然是黑色。红节点的父节点和子节点都是黑色。
    4.对于每个节点,从该节点到达任意一个叶子节点的路径上,所包含的黑节点数目相同。

    红黑树的几个性质

    1.从根节点到叶子节点的最长路径不会超过最短路径的2倍(这里的路径是和图中的路径一样的,每条边是一个路径),这个其实很好理解,因为红黑树本身也是一颗平衡二叉排序树。既然是一个平衡二叉树,那么每个节点平衡因子绝对值就不能超过1。

    1. 有n个内部节点得红黑树高度h <= 2log2(n+1)。
    • B树与B+树

    B树与B+树都是数据结构的在逻辑上是一对多的一种,都是由树这种结构演变而来。

    B树可以n阶叉树,例如5叉树,6叉树等。
    5阶B树就意味着 每个节点最多有5个分支,那么每个节点中内部最多有5-1 = 4个节点。5阶B树的结构体声明:

    typedef struct Node{
        
    	int data[4]; //4个索引值
    	struct Node * child[5]; //5个指针
    	int size;//有效指针 
        
    } * BTree,BNode;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    一、 B树性质:

    1. m阶B树每个节点可以有m个指针。
    2. 为了保证B树的查找效率,每个节点至少有 [m/2] 向上取整个指针。
    3. 每个子树都是绝对平衡的。
    4. 根节点的子节点个数为 [2,m],为什么不能是1,如果是1,就破坏了第三个性质
    5. 所有叶子节点都位于同一层,如果不位于同一层,也是破坏了第三个性质
    6. 除根节点以外的非叶子节点的子节点个数为[ Math.ceil(m/2),m]个,例如5阶B树的非叶子节点的子节点个数为[3,5]。
    7. 每个非叶子节点的值(索引)的个数为 子节点的个数-1,由性质6可知,子节点个数为[ Math.ceil(m/2),m]个,那么值的个数就是
      [ Math.ceil(m/2) - 1,m - 1]。

    根据以上几个性质,可判断一个B树是否是一个合法的B树

    二、B 树的插入

    B树的插入过程是一个自底向上的过程,不断的分裂,增加树的高度。插入的时候都是先到达底层插入的,每个节点到达了指定得阶数以后,需要从 m/2个节点开始分裂,产生上一层节点。这也就保证了,节点的每次分裂至少能分裂出2个子节点来,保证了树的绝对平衡。

    三、B树高度的计算

    计算m阶B树的高度,若一个m阶B树含有n个节点,则树的高度最低为?

    m阶B树每个节点最多含有(m-1)个值,第一层有1个节点,第二层最多有m个节点,第三层最多有m2,第h层有mh-1个节点

    n <= (m-1) * (1 + m + m2 + ……+mh-1)

    B+树的性质:

    1. B+树中,只有叶子节点才真正存储了每条记录的指针,非叶子节点只存储每个子节点中的最大值,可以是id也可以是其他字段。(联想关系型数据库中的索引)
    2. m阶B+树的每个节点有最多有m个指针,这里和B树是有区别的
    3. 每个节点有几个关键字就有几个子节点。

    B树与B+树对比:

    1. B树中每个关键字就相当于是一条记录,如果是B树,不一定一直找到叶子节点才能确定是否含有查询的节点。而在B+树中,必须找到最后一层节点才能够确定是否含有查询的节点。
    2. m阶B树中每个节点最多包含m+1个子节点(指针),而m阶B+树中每个节点最多包含m个子节点。
    3. 为了保证查找效率,B+树和B树的关键字数量要求一致,除根节点外,m阶B树每个节点包含的最少子节点数量为 [m/2],5阶B树和B+树,每个节点至少包含 5/2 = 3个子节点。而对于B树来说,包含3个子节点的节点有2个关键字,而对于B+树来说,包含3个子节点的节点有3个关键字。
    • 散列查找(哈希查找,哈希表,散列表)

    在这里插入图片描述

    散列表是一种数组+链表的数据结构,在初始化一个散列表后,会创建一个定长的数组,而数组中每一个元素存储的又是一个链表,当要插入一个元素时,先通过hash函数计算这个元素的hash值,再根据hash值对数组长度取模来决定存储到哪个下标位置的链表上去。在Java中,重新对象的equals方法同时也一定要重写hashCode方法,确保两个equals相等的对象hashCode也相等,如果是两个equals相等的对象hashcode不相等,那么很有可能存入了但是取不出来的情况。

    一、散列查找的平均查找长度

    以上述元素为例,平均查找长度为

    ( 16 + 24 + 31 + 41 ) / 12 = 1.75

    如果是查找0这个元素,发现这个元素并不存在,此时的查找长度为0而不是1,因为查找长度比较的是关键字的次数。

    二、散列查找的装填因子

    装填因子 = 每个下标出的链表存储的元素个数相加 / 数组长度

    0+4+0+2+0+0+2+1+0+0+2+1+0 / 13 = 0.92。

    三、常见的几种散列函数

    1. 除留余数法

    若数组的长度为m,则取一个不大于m的最大质数(素数)。

    1. 直接地址法

    适用与一些连续的数值存储,例如一个班级的学生学号从101-150,则可以使用一个散列函数

    h = (hkey - 101),结果就是 0-50。

    1. 数字分析法

    适用于一些没有连续性的数值,例如手机号后四位,可以直接把手机号后4位来当作地址存储到散列表中。

    四、常见的几种处理hash冲突的方法

    1. 拉链法

    数组中每个元素是一个指针,指针保存就是地址,也就是数组+链表的方式,典型应用如Java中的HashMap。当发生hash冲突时,可以依次追加到

    1. 再散列法

    当通过一次散列函数计算出的下标位置发生冲突时,继续通过散列函数重新计算得到下标,直到计算出的下标不存在元素为止。

    1. 开放地址法

    当通过散列函数计算得到的下标存在冲突时,使用开发地址法将元素存入。开发地址法具体又有2种算法,一种是线性探测法,一种是平方探测法。

    线性探测法: 当待插入的下标已经有元素时,依次向后查找,找到第一个不存在元素的位置插入。

    待插入元素 【8,17,5,10,29】

    8 % 7 = 1
    17 % 7 = 3
    5 % 7 = 5

    01234567
    8175

    当要存10这个元素时,10 % 7 = 3 ,此时3这个位置已经有元素了,为了避免hash冲突,依次从下标3开始向右查找,发现下标四不存在元素,则存入到下标4即可。

    01234567
    817105

    平方探测法:当待插入的下标已经有元素时,依次计算从当前下标开始相加或相减 k2 , -(k2)

    待插入元素 【8,17,5,10,29】

    8 % 7 = 1
    17 % 7 = 3
    5 % 7 = 5

    01234567
    8175

    当要插入10这个元素时,发现该位置已经存入元素,为了解决hash冲突,使用平方探测法,12 = 1,-(12 ) = -1, 22 = 4 , -(22 ) = -4, 3 + 1 = 4,3-1 = 2,3+4 = 7,3-4 = -1,从下标3向左移动了4个,到了-1这个位置,则说明此时需要从数组的末尾开始向左移动,也就是最终到达7这个位置。在平方探测法算法中,先计算第一次1的平方,3+1=4,下标4没有存储元素,则此时可以存入。

  • 相关阅读:
    公众号标签
    idea 超实用的插件
    Java 蓝桥杯校赛 谁最长?
    switch调试
    C++.I/O流
    Curve 文件存储随着存量数据增长
    数据挖掘——RFM客户价值模型及航空公司客户分析实例
    XCTF1-web disabled_button weak_auth view_source cookie backup
    基于jsp+mysql+ssm峰值预警停车场管理系统-计算机毕业设计
    SpringMvc(二、请求传参
  • 原文地址:https://blog.csdn.net/qq_43750656/article/details/126099680