精华内容
下载资源
问答
  • 在分布式系统路由分配上,一致性哈希算法有很大的优势。在之前的文章中也讲过原理。算法容易理解,但是实现上要注意很多细节,虚节点加入也要消耗更多的存储来维护映射关系。但是今天介绍的jump consistent hash是一...

    在分布式系统路由分配上,一致性哈希算法有很大的优势。在之前的文章中也讲过原理。算法容易理解,但是实现上要注意很多细节,虚节点加入也要消耗更多的存储来维护映射关系。但是今天介绍的jump consistent hash是一种比较新颖的方法,代码简短,内存消耗少。下面我们来详细看看这个算法。

    首先我们先了解下这个算法,有个初步的概念。然后看下这个算法适用于哪些场景,有什么作用。最后,详细分析下算法原理。

    算法内容

    int32_t JumpConsistentHash(uint64_t key, int32_t num_buckets) {
        int64_t b = -1, j = 0;
        while (j < num_buckets) {
            b = j;
            key = key * 2862933555777941757ULL + 1;
            j = (b + 1) * (double(1LL << 31) / double((key >> 33) + 1));
        }
        return b;
    }
    

    以上就是算法的全部代码,输入参数分别是64位的key,桶的数量(一般对应服务器的数量),输出是一个桶的编号(从0开始)。

    满足算法的要求:

    平衡性,把对象均匀地分布在所有桶中。

    单调性,当桶的数量变化时,只需要把一些对象从旧桶移动到新桶,不需要做其它移动。

    适用场景

    用于分布式存储产品中,而不太适合用在缓存类型的产品。因为有节点不可用时,jumphash用存活节点分担不可用节点的能力不强,会导致分布不均。但是在存储类中,节点都会有主备,主节点不可用路由到备节点,key的分布不会有变化。

    适合在分布式系统中,根据key来选择被分配到的服务场景。每次新增新的服务节点,只有1/n的key会变动,不会因为扩容或缩容瞬间
    造成大部分缓存失效。

    但是也有局限,和其他的一致性hash相比。如果有中间的桶失效,是不能够像割环hash一样,均匀分配到其他节点的,只能找个新替换
    节点来取代。但是优点是不用存储,计算量也不大。代码短,易于实现。

    本质

    利用线性同余计算的固定性,每次输入参数固定,输出就固定的特性,来替代用存储。利用运算,减少存储空间。

    由于运算量的优化,比查找存储空间速度更快,所以从时间、空间上算法更优。

    引申:有时用运算生成的数字串,映射要存储的空间,会使算法有意想不到的效果。

    分析

    为什么上面的代码能够实现一致性hash的功能,我们一步一步来看。要实现的功能就是多加一个节点,节点数变为n,只有1/n的key会变动。

    我们先构造一个函数,

    ch(key, num_buckets) 
    
    表示有num_buckets个桶,一个key的值会分配到的bucket编号[0, num_buckets)。
    

    所以对于任意key,k,ch(k,1)=0,因为只有一个桶。为了让算法平衡,ch(k,2)讲有一半的key留在0号桶中,一半的移到1号桶中。

    总结的规律是,ch(k,n+1)和ch(k,n)相比,n/(n+1)的key是不动的,1/(n+1)的key移动到第n号桶。

    对于每次新增桶的个数时,计算每个key的新位置,确定是否要移动到新的桶中。

    通过随机数生成器,来判定key是否要移动到新的桶中,概率是1/(n+1)要移动。

    int ch(int key, int num_buckets) {
        random.seed(key) ;
        int b = 0; // This will track ch(key, j +1) .
        for (int j = 1; j < num_buckets; j ++) {
            if (random.next() < 1.0/(j+1) ) b = j ;
        }
        return b;
    }
    
    //代码中的random.next()产生[0,1)的随机数,随机数序列只和key有关,key为随机种子。
    
    

    这段代码是满足算法的平衡性和单调性的,算法复杂度是O(N)。满足了正确性,接下来优化性能。

    从算法代码可以看出,大多数情况下random.next() < 1.0/(j+1)是不被执行的。

    对于一个key来说,ch(key,j+1)的值,很少会随着j增长而变化的。当ch(key,j+1)!=ch(key,j)时,
    ch(key,j+1)=j。

    //我们假设ch(key,j)是一个随机变量,通过伪随机数,来确定一个数值b,当j增长到b时,ch(key,b)!=ch(key,b-1),
    并且ch(key,j)=ch(key,b-1)。

    假设一个key的值为k,b为一个跳变的桶数量。则ch(k,b)!=ch(k,b+1),并且ch(k,b+1)=b.

    下面寻找下一个比b大的跳变的桶数量j。则ch(k,j+1)!=ch(k,j),ch(k,j)=b,ch(k,j+1)=j。

    ch(k,b+1)=b

    ch(k,j)=b,

    ch(k,j)=ch(k,b+1)

    ch(k,j+1)=j

    ch(k,b)!=ch(k,b+1)

    ch(k,j+1)!=ch(k,j)

    所以,我们已知k,b时,要找到j,对于(b,j]区间的变量i,如果不发生跳变,必须满足
    ch(k,i)=ch(k,b+1)。

    所以有概率

    P(j>=i) = P(ch(k,i)=ch(k,b+1))

    先举几个例子P(ch(k,10)=ch(k,11))的概率是10/11,
    
    P(ch(k,11)=ch(k,12))的概率是11/12,
    
    所以P(ch(k,10)=ch(k,12))的概率是P(ch(k,10)=ch(k,11))*P(ch(k,11)=ch(k,12))=(10/11)*(11/12)=10/12
    
    对于任意的n>=m,P(ch(k,n)=ch(k,m))=m/n。
    

    所以对于上面的等式,
    P(j>=i) = P(ch(k,i)=ch(k,b+1))=(b+1)/i。

    假设一个随机数r在(0,1)区间,由k和j确定。

    如果r<=(b+1)/i,那么P(j>=i)=(b+1)/i为不跳变。
    那么产生随机数r后,就能确定i的最小值为(b+1)/r。
    因为r<=(b+1)/i => i<=(b+1)/r.

    又因为i是整数,所以有
    r!=0

    i=floor((b+1)/r)

    代码可改写为

    int ch(int key, int num_buckets) {
        random.seed(key);
        int b = -1; // bucket number before the previous jump
        int j = 0; // bucket number before the current jump
        while (j < num_buckets) {
            b = j;
            r = random.next();
            j = floor((b + 1) / r);
        }
        return = b;
    }
    

    假设r的期望为0.5,时间复杂度为Olg(N)。
    这个算法有点绕,通过随机数的产生来判定下一跳的j,优化算法,保证在整体key的跳变满足增加桶数为n+1时,只有1/(n+1)的数据移动。

    我们再看

    key = key * 2862933555777941757ULL + 1;
    j = (b + 1) * (double(1LL << 31) / double((key >> 33) + 1));


    r = random.next();
    j = floor((b + 1) / r);

    有什么关系。

    利用线性同余算法产生一个64位的整数,然后通过映射到(0,1]区间的小数。

    (key>>33)+1是取key值的高31位的值再加1,范围为(1,2^31+1)
    1LL<<31的值为2^31。

    所以
    [(key>>33)+1]/1LL<<31 的取值范围是(0,1],如果(key>>33)=2^31那么会大于1,由于是c的整数运算,大于1也会取证忽略掉小数部分。

    总结

    该算法的精髓:通过随机种子产生随机数,减少存储;利用概率和随机数,确定key在bucket_num范围内落在的桶序号。

    既减少了运算量,也易于实现,对于存储类路由非常适合,而且key的分散性不依赖key本身,只依赖随即生成器,对key的要求不高,不用做转换。

    参考:
    https://arxiv.org/ftp/arxiv/papers/1406/1406.2294.pdf
    https://blog.helong.info/blog/2015/03/13/jump_consistent_hash/
    

    欢迎收听公众号
    展开全文
  • GeoGeometry最初是一个简单附带项目,可以帮助我提出涵盖特定地理形状一系列地理哈希。 如果您要构建搜索引擎功能,这是一件很好事情。 另外,我添加了解决各种几何问题的算法。 当然,大多数情况下,这些都是...
  • 掌握二叉排序树构造和查找方法平衡二叉树平衡调整方法 熟练掌握哈希构造方法 难点 掌握各种不同查找方法之间区别和各自的适用情况 能够按定义计算各种查找方法在等概率情况下查找成功平均查找长度 模块...
  • 搜索几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找 # 一、二分法查找 >**二分查找**又称折半查找,优点是比较次数少,查找速度快,平均性能好;缺点是要求待查表①必须采用顺序存储结构 ②必须按...

    搜索:是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存在。 搜索的几种常见方法:顺序/线性查找、二分法查找、二叉树查找、哈希查找

    二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;缺点是要求待查表:

    • 必须采用顺序存储结构;
    • 必须按关键字大小有序排列;
    • 插入删除困难;

    二分查找/折半查找方法适用于不经常变动而查找频繁的有序列表

    • 首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;
    • 否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
    • 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

    二分查找/折半查找的时间复杂度:O(logn)【不考虑排序的时间】

    • 最差时间复杂度: O(logn);
    • 最优时间复杂度: O(1)
      在这里插入图片描述

    很多时候,和二分查找法相关的算法问题,不是在数组中查找特定值,而是使用二分查找法搜索问题的解。

    一、二分法查找指定数值

    1、递归方法

    java版本:

    public class BinarySearch {
        /**
         * 二分查找的时间复杂度是O(log(n))
         * 1.二分查找依赖顺序表结构即数组
         * 2.二分查找针对的是有序数据
         * 3.递归方法
         */
        public static int search(int[] arr, int target) {
            return search(arr, 0, arr.length - 1, target);
        }
    
        private static int search(int[] arr, int left, int right, int target) {
            if (left > right)
                return -1;
            int mid = left + (right - left) / 2;
    
            System.out.println("target = " + target + "; arr[mid] = " + arr[mid] + "; mid = " + mid);
    
            if (arr[mid] == target) {
                return mid;
            }
            if (target < arr[mid]) {
                return search(arr, left, mid - 1, target);
            } else {
                return search(arr, mid + 1, right, target);
            }
        }
    
        public static void main(String[] args) {
            int arr[] = {3, 5, 11, 17, 21, 23, 101};
            int result = search(arr, 23);
            System.out.println("result = " + result);
        }
    }
    
    

    输出结果:

    target = 23; arr[mid] = 17; mid = 3
    target = 23; arr[mid] = 23; mid = 5
    result = 5
    

    python版本:

    def binary_search(alist, item):
        """二分查找,递归"""
        n = len(alist)
        if n > 0:
            mid = n // 2
            if alist[mid] == item:
                return True
            elif item < alist[mid]:
                return binary_search(alist[:mid], item)
            else:
                return binary_search(alist[mid + 1:], item)
        return False
    
    if __name__ == "__main__":
        li = [17, 20, 26, 31, 44, 54, 55, 77, 93]
        print(binary_search(li, 55))
        print(binary_search(li, 100))
    

    2、非递归方法

    java版本:

    public class BinarySearch02 {
        /**
         * 二分查找的时间复杂度是O(log(n))
         * 1.二分查找依赖顺序表结构即数组
         * 2.二分查找针对的是有序数据
         * 3.非递归方法
         */
        public static int search(int[] arr, int target) {
            int left = 0;
            int right = arr.length - 1;
            // 循环不变量:在arr[left, right]的范围中查找 target
            while (left < right) {
                int mid = left + (right - left) / 2;
                System.out.println("target = " + target + "; arr[mid] = " + arr[mid] + "; mid = " + mid);
                if (arr[mid] == target) {
                    return mid;
                }
                // 如果没找到目标值,则修改左右边界
                if (arr[mid] < target) {
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
            return -1;
        }
    
    
        public static void main(String[] args) {
            int arr[] = {3, 5, 11, 17, 21, 23, 101};
            int result = search(arr, 23);
            System.out.println("result = " + result);
        }
    }
    

    输出结果:

    target = 23; arr[mid] = 17; mid = 3
    target = 23; arr[mid] = 23; mid = 5
    result = 5
    
    Process finished with exit code 0
    

    python代码:

    def binary_search_2(alist, item):
        """二分查找, 非递归"""
        n = len(alist)
        first = 0
        last = n - 1
        while first <= last:
            mid = (first + last) // 2
            if alist[mid] == item:
                return True
            elif item < alist[mid]:
                last = mid - 1
            else:
                first = mid + 1
        return False
    
    if __name__ == "__main__":
        li = [17, 20, 26, 31, 44, 54, 55, 77, 93]
        print(binary_search_2(li, 55))
        print(binary_search_2(li, 100))
    

    二、二分法查找法变种

    1、upper:查找比指定数值大的最小数值

    public class BinarySearch_upper {
    
        /**
         * 查找 > target 的最小值的索引
         */
        public static int upper(int[] arr, int target) {
            int left = 0;
            int right = arr.length;
            // 循环不变量:在arr[left, right]的范围中查找解
            while (left < right) {
                int mid = left + (right - left) / 2;
                System.out.println("target = " + target + "; arr[mid] = " + arr[mid] + "; mid = " + mid);
                if (arr[mid] <= target) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            return left;
        }
    
        public static void main(String[] args) {
            int arr[] = {3, 5, 11, 17, 17, 21, 23, 101};
            int result = upper(arr, 11);
            System.out.println("result = " + result);
        }
    }
    

    输出结果:

    target = 11; arr[mid] = 17; mid = 4
    target = 11; arr[mid] = 11; mid = 2
    target = 11; arr[mid] = 17; mid = 3
    result = 3
    
    Process finished with exit code 0
    

    2、lower:查找比指定数值小的最大数值

    public class BinarySearch_lower {
    
        /**
         * 查找 < target 的最大值的索引
         */
        public static int lower(int[] arr, int target) {
            int left = -1;
            int right = arr.length - 1;
            // 循环不变量:在arr[left, right]的范围中查找解
            while (left < right) {
                int mid = left + (right - left + 1) / 2;
                System.out.println("target = " + target + "; arr[mid] = " + arr[mid] + "; mid = " + mid);
                if (arr[mid] < target) {
                    left = mid;
                } else {
                    right = mid - 1;
                }
            }
            return left;
        }
    
        public static void main(String[] args) {
            int arr[] = {3, 5, 11, 17, 17, 21, 23, 101};
            int result = lower(arr, 11);
            System.out.println("result = " + result);
        }
    }
    

    输出结果:

    target = 11; arr[mid] = 17; mid = 3
    target = 11; arr[mid] = 5; mid = 1
    target = 11; arr[mid] = 11; mid = 2
    result = 1
    
    Process finished with exit code 0
    

    3、upper_ceil:查找指定元素最大索引

    在这里插入图片描述

    public class BinarySearch_ceil {
    
        /**
         * 查找 > target 的最小值的索引
         */
        public static int upper(int[] arr, int target) {
            int left = 0;
            int right = arr.length;
            // 循环不变量:在arr[left, right]的范围中查找解
            while (left < right) {
                int mid = left + (right - left) / 2;
                System.out.println("target = " + target + "; arr[mid] = " + arr[mid] + "; mid = " + mid);
                if (arr[mid] <= target) {
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            return left;
        }
    
        public static int ceil(int[] arr, int target) {
            int u = upper(arr, target);
            if (u - 1 >= 0 && arr[u - 1] == target) {
                return u - 1;
            }
            return u;
        }
    
        public static void main(String[] args) {
            int arr[] = {3, 5, 11, 17, 17, 21, 23, 101};
            int result01 = ceil(arr, 11);
            System.out.println("result01 = " + result01);
            System.out.println("===========================================");
            int result02 = ceil(arr, 13);
            System.out.println("result02 = " + result02);
        }
    }
    

    输出结果:

    target = 11; arr[mid] = 17; mid = 4
    target = 11; arr[mid] = 11; mid = 2
    target = 11; arr[mid] = 17; mid = 3
    result01 = 2
    ===========================================
    target = 13; arr[mid] = 17; mid = 4
    target = 13; arr[mid] = 11; mid = 2
    target = 13; arr[mid] = 17; mid = 3
    result02 = 3
    
    Process finished with exit code 0
    

    4、lower_ceil

    在这里插入图片描述

    5、upper_floor

    在这里插入图片描述

    6、lower_floor

    在这里插入图片描述

    三、二分法查找法模板

    在这里插入图片描述

    展开全文
  • 以下顺序是学习数据结构和算法最有效的方法: 基本 所有搜索算法 所有排序算法 数据结构 串 数组/向量 矩阵 堆栈,队列和优先级队列 链表 二叉树 堆 设置和哈希图 图形 特里 高级数据结构(段树,B +树等) 标准...
  • 七大查找算法

    2020-12-29 17:00:49
    文章目录1、顺序查找2、二分查找3、插值查找4、斐波那契查找5、树表查找6、分块查找7、哈希查找 查找定义:根据给定某个值,在查找表中确定一个其关键字等于给定值...适用于线性表顺序存储结构和链式存储结构。

    查找定义:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。

    查找算法分类

    1)静态查找和动态查找;

    注:静态或者动态都是针对查找表而言的。动态表指查找表中有删除和插入操作的表。

    2)无序查找和有序查找。

    无序查找:被查找数列有序无序均可;

    有序查找:被查找数列必须为有序数列。

    1、顺序查找

    顺序查找又称为线性查找,是一种最简单的查找方法。适用于线性表的顺序存储结构和链式存储结构。

    基本思路

    从第一个元素m开始逐个与需要查找的元素x进行比较,当比较到元素值相同(即m=x)时返回元素m的下标,如果比较到最后都没有找到,则返回-1。

    复杂度分析

    查找成功时的平均查找长度为: ASL = 每个元素被查找的概率 * 总的元素的个数=1/n*(1+2+3+…+n) = (n+1)/2 ;
      当查找不成功时,需要n+1次比较,时间复杂度为O(n),所以,顺序查找的时间复杂度为O(n)。
      
    优缺点

    缺点:是当n 很大时,平均查找长度较大,效率低;
    优点:是对表中数据元素的存储没有要求。另外,对于线性链表,只能进行顺序查找。

    int SeqSearch(int *a, int value, int len)
    {
        for(int i=0; i<len; i++)
        {
            if(a[i] == value)
            {
                return i;
            }
        }
    
        return -1;
    }
    

    2、二分查找

    二分查找,是一种在有序数组中查找某一特定元素的查找算法。

    基本思路

    用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。

    复杂度分析

    时间复杂度:折半搜索每次把搜索区域减少一半,时间复杂度为 O(logn) 。
    空间复杂度:O(1)。

    优缺点分析

    当查找表不会频繁有更新、删除操作时,使用折半查找是比较理想的。如果查找表有较频繁的更新、删除操作,维护表的有序会花费比较大的精力,不建议使用该查找方式。

    
    int BinarySearch(int * a, int value, int len)
    {
        int low, high, mid;
        low = 0;
        high = len-1;
        mid = 0;
    
        while(low<=high)
        {
            mid = (high-low)/2;
            
            if(a[mid] == value)
            {
                return mid;
            }
    
            if(a[mid] > value)
            {
                high = mid - 1;
            }
    
            if(a[mid] < value)
            {
                low = mid + 1;
            }
        }
    }
    

    递归实现

    
    int BinarySearch1(int * a, int value, int low, int high)
    {
        int mid = low + (high - low)/2;
    
        if(a[mid] == value)
        {
            return mid;
        }
    
        if(a[mid] < value)
        {
            BinarySearch1(a, value, mid+1, high);
        }
    
        if(a[mid] > value)
        {
            BinarySearch1(a, value, low, mid-1);
        }
    }
    
    

    3、插值查找

    在二分查找中,每次都是从待查找序列的中间点开始查找,这样的做法在正确性上固然没什么问题,但假如要查找的值距离某个边界比较近,还从中间点开始查找,就有点浪费时间了。举个例子来说说明,假如在在一个{1,2…,100}的数组中,要查找88这个值,还一直采用和中间点比较的策略,就显得不太明智,因为明显可以明显从较为靠后的位置去检索。为了克服这种弊端, 引入了插值查找。

    基本思路

    插值查找是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的 查找方法,其核心就在于插值的计算公式 (key-array[low])/(array[high]-array[low])*(high-low)。简而言之,基于二分查找算法,将查找点的选择改进为自适应选择。

    折半查找这种查找方式,不是自适应的(也就是说是傻瓜式的)。二分查找中查找点计算如下:
      mid=(low+high)/2, 即mid=low+1/2*(high-low);
      通过类比,我们可以将查找的点改进为如下:
      mid=low+(key-a[low])/(a[high]-a[low])*(high-low),
      也就是将上述的比例参数1/2改进为自适应的,根据关键字在整个有序表中所处的位置,让mid值的变化更靠近关键字key,这样也就间接地减少了比较次数。

    复杂度分析

    时间复杂性:如果元素均匀分布,则O(log(logn)),在最坏的情况下可能需要 O(n)。
    空间复杂度:O(1)。
      
    优缺点分析

    对于长度比较长、关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。

    
    int InsertionSearch(int * a, int value, int low, int high)
    {
        int mid = low+(value-a[low])/(a[high]-a[low])*(high-low);
    
        if(a[mid]==value)
        {
            return mid;
        }
        if(a[mid]>value)
        {
            return InsertionSearch(a, value, low, mid-1);
        }
        if(a[mid]<value)
        {
            return InsertionSearch(a, value, mid+1, high);
        }  
    }
    
    

    4、斐波那契查找

    和前面的二分查找、插值查找相比,斐波那契查找是类似的,不过换了一种寻找mid点的方法。顾名思义,该种查找方法中,使用到了斐波那契数列,斐波那契数列的形式是:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89…….(从第三个数开始,后边每一个数都是前两个数的和)。

    基本思路
      在斐波那契数列中的元素满足这样的关系:F[k]=F[k-1]+F[k-2],此处将这个数组稍微改一下,改成:(F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1,图示如下:
        在这里插入图片描述

    通过上面的图,应该就可以看出为什么要这样分割数组了,因为要找出一个中间mid值,以便将数组按斐波那契数列的规律,分割成两部分。
    复杂度分析
      最坏情况下,时间复杂度为O(logn),且其期望复杂度也为O(logn)。

    
        F[1]=1;
        for(int i=2;i<max_size;++i)
            F[i]=F[i-1]+F[i-2];
    }
    
    /*定义斐波那契查找法*/  
    int FibonacciSearch(int *a, int n, int key)  //a为要查找的数组,n为要查找的数组长度,key为要查找的关键字
    {
      int low=0;
      int high=n-1;
      
      int F[max_size];
      Fibonacci(F);//构造一个斐波那契数组F 
    
      int k=0;
      while(n>F[k]-1)//计算n位于斐波那契数列的位置
          ++k;
    
      int  * temp;//将数组a扩展到F[k]-1的长度
      temp=new int [F[k]-1];
      memcpy(temp,a,n*sizeof(int));
    
      for(int i=n;i<F[k]-1;++i)
         temp[i]=a[n-1];
      
      while(low<=high)
      {
        int mid=low+F[k-1]-1;
        if(key<temp[mid])
        {
          high=mid-1;
          k-=1;
        }
        else if(key>temp[mid])
        {
         low=mid+1;
         k-=2;
        }
        else
        {
           if(mid<n)
               return mid; //若相等则说明mid即为查找到的位置
           else
               return n-1; //若mid>=n则说明是扩展的数值,返回n-1
        }
      }  
      delete [] temp;
      return -1;
    }
    
    int main()
    {
        int a[] = {0,16,24,35,47,59,62,73,88,99};
        int key=100;
        int index=FibonacciSearch(a,sizeof(a)/sizeof(int),key);
        cout<<key<<" is located at:"<<index;
        return 0;
    }
    

    5、树表查找

    二叉排序树是最简单的树表查找算法,该算法需要利用待查找的数据,进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,然后再进行查找。

    二叉排序树性质
      二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
       1>若左子树不空,则左子树上所有结点的键值均小于或等于它的根结点的键值。
       2>若右子树不空,则右子树上所有结点的键值均大于或等于它的根结点的键值。
       3>左、右子树也分别为二叉排序树。

    二叉排序树中序遍历
      二叉排序树有不同的遍历方式,中序遍历的结果比较直观,是一个有序的序列。二叉树示例如下:
      
      二叉树上中序遍历的方式是:左节点、当前节点、右节点。该二叉树的遍历结果为:1、3、4、6、7、8、10、13、14。

    二叉树查找步骤
      先创建二叉排序树,再进行查找。

    二叉树查找实现

    struct BTreeNode
    {
        int data;
        BTreeNode * left;
        BTreeNode * right;
    };
    //二叉树创建
    void create(BTreeNode* & Node)
    {
        int  data = 0;
        
        cin>>data;
    
        if(data > 0)
        {
            Node = new BTreeNode;
            Node->data = data;
            create(Node->left);
            create(Node->right);
        }
        else
        {
            Node = NULL;
            return;
        }
    }
    //二叉搜索树查找
    BTreeNode * SearchTree(BTreeNode * Node, int data)
    {
        if(Node == NULL)
        {
            return NULL;
        }
    
        if(Node->data == data)
        {
            return Node;
        }
    
        if(Node->data > data)
        {
            return SearchTree(Node->left, data);
        }
        else
        {
            return SearchTree(Node->right, data);
        }
    }
    

    6、分块查找

    分块查找又称索引顺序查找,它是顺序查找的一种改进方法。

    算法思想:将n个数据元素"按块有序"划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,……

    算法流程
      step1 先选取各块中的最大关键字构成一个索引表;
      step2 查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。

    7、哈希查找

    什么是哈希表(Hash)

    我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素"分类",然后将这个元素存储在相应"类"所对应的地方。但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了"冲突",换句话说,就是把不同的元素分在了相同的"类"之中。后面我们将看到一种解决"冲突"的简便做法。
    总的来说,"直接定址"与"解决冲突"是哈希表的两大特点。
      
    什么是哈希函数?

    哈希函数的规则是:通过某种转换关系,使关键字适度的分散到指定大小的的顺序结构中,越分散,则以后查找的时间复杂度越小,空间复杂度越高。

    算法思想:哈希的思路很简单,如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现:将键作为索引,值即为其对应的值,这样就可以快速访问任意键的值。这是对于简单的键的情况,我们将其扩展到可以处理更加复杂的类型的键。

    算法流程

    1)用给定的哈希函数构造哈希表;
    2)根据选择的冲突处理方法解决地址冲突;

    常见的解决冲突的方法:拉链法和线性探测法。详细的介绍可以参见:浅谈算法和数据结构: 十一 哈希表。

    3)在哈希表的基础上执行哈希查找。
      哈希表是一个在时间和空间上做出权衡的经典例子。如果没有内存限制,那么可以直接将键作为数组的索引。那么所有的查找时间复杂度为O(1);如果没有时间限制,那么我们可以使用无序数组并进行顺序查找,这样只需要很少的内存。哈希表使用了适度的时间和空间来在这两个极端之间找到了平衡。只需要调整哈希函数算法即可在时间和空间上做出取舍。
      
    复杂度分析

    单纯论查找复杂度:对于无冲突的Hash表而言,查找复杂度为O(1)(注意,在查找之前我们需要构建相应的Hash表)。

    使用Hash,我们付出了什么

    我们在实际编程中存储一个大规模的数据,最先想到的存储结构可能就是map,也就是我们常说的KV pair,经常使用Python的博友可能更有这种体会。使用map的好处就是,我们在后续处理数据处理时,可以根据数据的key快速的查找到对应的value值。map的本质就是Hash表,那我们在获取了超高查找效率的基础上,我们付出了什么?

    Hash是一种典型以空间换时间的算法,比如原来一个长度为100的数组,对其查找,只需要遍历且匹配相应记录即可,从空间复杂度上来看,假如数组存储的是byte类型数据,那么该数组占用100byte空间。现在我们采用Hash算法,我们前面说的Hash必须有一个规则,约束键与存储位置的关系,那么就需要一个固定长度的hash表,此时,仍然是100byte的数组,假设我们需要的100byte用来记录键与位置的关系,那么总的空间为200byte,而且用于记录规则的表大小会根据规则,大小可能是不定的。

    展开全文
  • 3.5.4 字符数据在内存中的存储形式及使用方法 41 3.5.5 字符串常量 41 3.5.6 符号常量 42 3.6 变量赋初值 42 3.7 各类数值型数据之间的混合运算 43 3.8 算术运算符和算术表达式 44 3.8.1 C运算符简介 44 3.8.2 算术...
  • 3.5.4 字符数据在内存中的存储形式及使用方法 41 3.5.5 字符串常量 41 3.5.6 符号常量 42 3.6 变量赋初值 42 3.7 各类数值型数据之间的混合运算 43 3.8 算术运算符和算术表达式 44 3.8.1 C运算符简介 44 3.8.2 算术...
  • 说明:寿命老长一个Hash算法适用范围广,网站存储密码也经常使用。不同文件产生MD5哈希值是唯一,但这点已经有办法通过对文件进行少量修改,让文件MD5后的哈希值保持一致。使用:在CentOS下,要对文件...

    MD5校验

    原理:对文件进行MD5 Hash,求出文件的MD5哈希值,通过下载后文件MD5哈希值和发布者提供的MD5哈希值是否一致来判断文件是否在发布者发布之后被篡改过。

    说明:寿命老长的一个Hash算法,适用范围广,网站存储密码也经常使用。不同的文件产生的MD5哈希值是唯一的,但这点已经有办法通过对文件进行少量的修改,让文件的MD5后的哈希值保持一致。

    使用:在CentOS下,要对文件进行MD5 Hash是很简单的,一个 md5sum 命令即可:

    复制代码代码如下:

    # $是终端提示符,非输入.

    # #号是注释

    # 没有提示符的是输出

    #直接输出MD5 Hash

    $ md5sum your-downloaded-file-name

    fd4a1b802373c57c10c926eb7ac823d8 your-downloaded-file-name

    #将MD5 Hash值保存到md5-hash.txt文件中.

    $ md5sum your-downloaded-file-name > md5-hash.txt

    # 显示输出的md5-hast.txt内容

    $ cat md5-hash.txt

    fd4a1b802373c57c10c926eb7ac823d8 your-downloaded-file-name

    # 通过md5-hash.txt来校验你下载的文件是否正确

    $ md5sum -c md5-hash.txt

    your-downloaded-file-name: OK

    你是文件的发布者话,你可以通过md5sum把文件的哈希值发送给验证者,这样下载你文件的人就可以通过MD5哈希值来验证你的文件正确性。反过来,我们在网站上下载文件之后,同时可以获取发布者的MD5哈希值和本地生成的Hash值对比,如果一致,认为文件是正确的。

    SHA1校验

    原理: 原理同MD5一样,都是通过对文件进行HASH求值,比对文件发布者发布的HASH值,通过是否相等判断文件是否被篡改

    说明: SHA1 HASH求值方法可以说是MD5的一个升级版本(SHA1 20位,MD5 16位),在HASH求值方面,MD5退出的舞台将有SHA1占据。SHA家族有五个算法:SHA-1、SHA-224、SHA-256、SHA-384,和SHA-512,后四种有时候称为SHA2

    使用: CentOS有SHA1的命令: sha1sum

    复制代码代码如下:

    # 说明同上

    # 直接输出SHA1 Hash

    $ sha1sum your-downloaded-file-name

    12dc96cbd822598c1230c87622f3591461a77227 your-downloaded-file-name

    # 将SHA1 Hash值保存到文件中

    $ sha1sum your-downloaded-file-name > sha1-hash.txt

    # 显示文件内容

    $ cat sha1-hash.txt

    12dc96cbd822598c1230c87622f3591461a77227 your-downloaded-file-name

    #通过sha1-hash.txt来校验我们下载的文件your-downloaded-file-name

    # 注意,文件必须要要通过txt文件中的路径知道哦

    $ sha1sum -c sha1-hash.txt

    your-downloaded-file-name: OK

    这个SHA1和MD5基本一致,需要补充说明下的是,在使用 md5sum 也好,还是 sha1sum 也罢,校验文件的时候,务必要让系统能够根据文件中提供的路径找到文件,如果文件找不到,是没有办法进行校验的。

    如果是做多个文件的Hash校验,可以通过一个文件保存多个文件的Hash值即可。

    PGP校验

    原理:使用非对称加密,程序生成唯一的密钥对(公钥和私钥:Public Key和Private Key/Secret Key)。操作方法如下:

    1.发布者通过用生成的密钥对中的私钥对要发布的文件进行签名,得到签名文件(sign);

    2.发布者将密钥对中的公钥发布到公钥服务器;

    3.发布者将文件和用私钥生成的签名一起发布;

    4.验证者下载发布者发布的文件和签名;

    5.使用PGP的程序获取的发布者第二步发布的公钥;

    6.使用公钥校验文件签名

    说明:签名算法中,密钥的用处分别是:公钥用于加密信息和验证,私钥用于解密和签名。私钥掌握在信息发布方,公钥可以任意分发。信息发布方用密钥进行对信息进行签名,接收方在获取公钥后,可以用公钥对发布方发布的信息+签名进行验证。如果验证失败则认为信息被篡改。在网络中,我们经常碰到的HTTPS协议,使用了同样的机制。

    使用:由于PGP是商业应用程序,在CentOS/Linux中,具有同类功能的是GPG(也就是:GnuPG),同样遵守OpenPGP数据加密标准( RFC 4880 ),没有安装可以用 yum install gnupg 安装,命令是: gpg

    复制代码代码如下:

    # 说明同上

    # 由于过程相对复杂,并且在实际使用中,校验用的比较多,因此这里只介绍文件的校验过程。

    # 在获得文件和签名时,我们先用gpg校验签名,此时文件必须存在

    $ gpg --verify downloaded-file-sign.asc

    这里有多种情况,如果你只有签名,但生成签名的文件不存在时(系统没找到,一般应该放在同目录下面),返回的是:

    复制代码代码如下:

    gpg: 不含签名的数据

    gpg: can’t hash datafile: No data

    当你有文件的时候,但还没有与签名对应的公钥时,gpg返回的信息类似下面:

    复制代码代码如下:

    gpg: 于 2013年05月06日 星期一 18时27分27秒 CST 创建的签名,使用 RSA,钥匙号 47ACDAFB

    gpg: 无法检查签名:No public key

    注意:上面的信息在不同的文件和操作系统上生成的信息是不同的。但在没有公钥的时候,你可以发现gpg提供了一个该签名对应的钥匙号:47ACDAFB,这个是我们需要找的公钥。

    上面已经说过,发布者已经将公钥发布到公钥服务器中,供验证者下载,因此我们需要到公钥服务器中下载公钥,要下载公钥,钥匙号就很重要了。

    可用的公钥服务器可以通过wikipedia 上的Key Server条目来查看常用的一些key服务器列表。这里使用hkp://pgp.mit.edu:

    复制代码代码如下:

    # 获取服务器上的public key

    $ gpg --keyserver hkp://pgp.mit.edu --recv-keys 47ACDAFB

    gpg: 下载密钥‘47ACDAFB’,从 hkp 服务器 pgp.mit.edu

    gpg: 密钥 47ACDAFB:公钥“Stephan Mueller Stephan.Mueller@atsec.com”已导入

    gpg: 没有找到任何绝对信任的密钥

    gpg: 合计被处理的数量:1

    gpg: 已导入:1

    –recv-keys要与–keyserver配合使用,导入密钥对的公钥之后,我们就能够使用这个公钥来验证我们的签名了。

    再次运行我们之前的验证命令(gpg –verify sign-file),就可以看到验证的结果了。

    复制代码代码如下:

    #这时候我们再次验证我们的签名,就能得到验证结果了

    $ gpg --verify downloaded-file-sign.asc

    gpg: 于 2013年05月06日 星期一 18时27分27秒 CST 创建的签名,使用 RSA,钥匙号 47ACDAFB

    gpg: 完好的签名,来自于“Stephan Mueller Stephan.Mueller@atsec.com”

    gpg: 警告:这把密钥未经受信任的签名认证!

    gpg: 没有证据表明这个签名属于它所声称的持有者。

    主钥指纹: B0F4 2D33 73F8 F6F5 10D4 2178 520A 9993 A1C0 52F8

    看到这个结果,至少确认一个结果:这个文件是没有被篡改过的。

    一般我们到这步也就差不多了。

    但注意消息里面有个警告,说明这个是未受信任的签名认证。因为这个公钥谁都可以发布上去的,如果你确实需要进一步认证,可以在签名认证之前,你能还要联系下真正的发布者,确认这个密钥的信息——指纹!这个是这个算法的一个弱点。

    如果签名认证已经通过,你也就可以安心的在自己的系统内编译,安装它了。

    关于PGP的更多信息,可以参考以下网站:

    wikipedia PGP

    ubuntu GPG/PGP

    GnuPG ,HOWTOs中MiniHOWTO中有个zh的文档,是中文的

    gentoo GnuPG

    展开全文
  • 说明:寿命老长一个Hash算法适用范围广,网站存储密码也经常使用。不同文件产生MD5哈希值是唯一,但这点已经有办法通过对文件进行少量修改,让文件MD5后的哈希值保持一致。 使用:在CentOS下,要对...
  • 3-Set集合  Set集合与Collection集合基本相同,没有提供额外的方法。  Set集合中不许包含相同元素,如果试图把两个相同元素添加到同一个Set...(1)HashSet按照哈希算法存储集合中元素,具有很好存取和...
  • 每日一题:4_19

    2021-04-19 15:40:49
    再散列法(二次哈希法):再次使用一个不同的哈希算法再计算一次 (第一次%16换另一个数进行%运算) 链地址法(拉链法):将所有冲突元素按照链表存储,冲突后时间复杂度变为O(1+n)n为冲突元素个数)[hashMap就是用这种方法] ...
  • 4.6.3 DGIM算法的存储需求 4.6.4 DGIM算法中的查询应答 4.6.5 DGIM条件的保持 4.6.6 降低错误率 4.6.7 窗口内计数问题的扩展 4.6.8 习题 4.7 衰减窗口 4.7.1 最常见元素问题 4.7.2 衰减窗口的定义 4.7.3 ...
  • 4.6.3 DGIM算法的存储需求 4.6.4 DGIM算法中的查询应答 4.6.5 DGIM条件的保持 4.6.6 降低错误率 4.6.7 窗口内计数问题的扩展 4.6.8 习题 4.7 衰减窗口 4.7.1 最常见元素问题 4.7.2 衰减窗口的定义 4.7.3 ...
  • c++ 11 map 和 unordered_map

    2018-05-27 01:28:22
    1,底层数据结构:map采用红黑树,unordered_map采用哈希算法2,使用场景:map适用于数据需要自动排序,unordered_map适用于数据需要随机访问3,当存储的元素类型是自定义类型时,map需要重载 &lt; (小于号)...
  • 博客作业05--查找

    2018-05-26 21:23:00
    二分查找是一种效率较高的方法,但是要将表按关键字排序,只适用顺序存储结构。哈希平均查找长度是α函数,而不是n函数,用哈希表构造查找表时,可以选择适当装填因子α,使得平均查找长度在某个范围内。 ...
  • 7. SHA-1 是在 MD5 之后推出的哈希算法,它也一次对固定长度消息进行处理,而且 产 生固定长度哈希值,在哈希过程当中,最后得到【160】位消息摘要值 8. 公司对电脑密码强度要求是:字母加数字组合【8】位...
  • 数据结构基本类型实现代码数组/顺序表数组中二分查找算法实现链表单链表代码实现单向循环链表代码实现双向循环链表代码实现栈代码实现队列代码实现哈希表/散列表代码实现(取余法为例)散列冲突解决方法图代码...
  • Hopscotch map:使用hopscotch哈希算法来实现冲突解决快速哈希映射,只有头文件。 LSHBOX:局部敏感算法(LSH)C++工具箱,提供了好几种普遍LSH算法,也可以支持Python和MATLAB。 PGM-index:能够快速查找、...
  • beauty of architecture

    2015-09-14 22:57:23
    一致哈希—分布式存储的基础算法 索引原理:布尔代数和搜索引擎索引 MySQL索引背后数据结构和算法原理 4.1.2 NoSQL 集群环境下关系型数据库扩展性问题 数据模型与存储模型矛盾 NoSQL来源、主要特征和...
  • 7.4 方法引用:方法引用提供了一个很有用语义来直接访问类或者实例已经存在的方法或者构造方法, 结合Lambda表达式,方法引用使语法结构紧凑简明。不需要复杂引用。 详情移步:...
  • textdistance:支持 30 多种算法来计算序列之间距离。 Slug 化 awesome-slugify:一个 Python slug 化库,可以保持 Unicode。 python-slugify:Python slug 化库,可以把 unicode 转化为 ASCII。 unicode-...

空空如也

空空如也

1 2 3
收藏数 51
精华内容 20
关键字:

哈希算法适用的存储方法