精华内容
下载资源
问答
  • 常见快速搜索算法图解

    千次阅读 2020-01-29 09:49:05
    搜索是在一个项目集合中找到一个特定项目的算法过程。搜索通常的答案是真的或假的,因为该项目是否存在。搜索的几种常见方法:顺序查找、二分法查找、二叉树查找、哈希查找

    搜索

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

    二分查找

    二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

    在这里插入图片描述

    二分查找的实现

    (非递归实现)

    def binary_search_2(alist,item):
        """二分查找:while版"""
        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
    

    (递归实现)

    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
    

    时间复杂度

    • 最优时间复杂度:O1O(1)
    • 最坏时间复杂度:Olog2nO(log_2n)

    哈希查找

    散列表也叫作哈希表(hash table),这种数据结构提供了键(Key)和值(Value)的映射关系。只要给出一个Key,就可以高效查找到它所匹配的Value,时间复杂度接近于O(1)O(1)
    在这里插入图片描述
    散列表是如何根据Key来快速找到它所匹配的Value呢?

    哈希函数

    散列表在本质上也是一个数组。可是数组只能根据下标,像a[0]、a[1]、a[2]、a[3]、a[4]这样来访问,而散列表的Key则是以字符串类型为主的。

    例如以学生的学号作为Key,输入002123,查询到李四;或者以单词为Key,输入by,查询到数字46……

    所以我们需要一个“中转站”,通过某种方式,把Key和数组下标进行转换。这个中转站就叫作哈希函数。
    在这里插入图片描述
    这个所谓的哈希函数是怎么实现的呢?在不同的语言中,哈希函数的实现方式是不一样的。这里以python的字典(dictionary)为例,来看一看哈希函数在python中的实现。

    在python及大多数面向对象的语言中,每一个对象都有属于自己的hashcode,这个hashcode是区分不同对象的重要标识。无论对象自身的类型是什么,它们的hashcode都是一个整型变量。既然都是整型变量,想要转化成数组的下标也就不难实现了。最简单的转化方式是什么呢?是按照数组长度进行取模运算。

    index = HashCode (Key) % Array.length

    通过哈希函数,我们可以把字符串或其他类型的Key,转化成数组的下标index。

    如给出一个长度为8的数组,则当key=001121时,

    index = HashCode ("001121") % Array.length = 1420036703 % 8 = 7
    而当key=this时,
    index = HashCode ("this") % Array.length = 3559070 % 8 = 6

    字典的读写操作

    有了哈希函数,就可以在字典中进行读写操作了。

    1.写操作

    写操作就是在散列表中插入新的键值对(在python中用dict)。如调用dict= {‘002931’: ‘王五’},意思是插入Key为002931、Value为王五的键值对。具体该怎么做呢?

    第1步,通过哈希函数,把Key转化成数组下标5。
    第2步,如果数组下标5对应的位置没有元素,就把这个dict填充到数组下标5的位置。
    在这里插入图片描述
    但是,由于数组的长度是有限的,当插入的dict越来越多时,不同的Key通过哈希函数获得的下标有可能是相同的。例如002936这个Key对应的数组下标是2;002947这个Key对应的数组下标也是2。
    在这里插入图片描述
    这种情况,就叫作哈希冲突。

    哈希冲突是无法避免的,既然不能避免,我们就要想办法来解决。解决哈希冲突的方法主要有两种,一种是开放寻址法,一种是链表法。

    开放寻址法的原理很简单,当一个Key通过哈希函数获得对应的数组下标已被占用时,我们可以“另谋高就”,寻找下一个空档位置。
    以上面的情况为例,dict6通过哈希函数得到下标2,该下标在数组中已经有了其他元素,那么就向后移动1位,看看数组下标3的位置是否有空。
    在这里插入图片描述
    很不巧,下标3也已经被占用,那么就再向后移动1位,看看数组下标4的位置是否有空。
    在这里插入图片描述
    幸运的是,数组下标4的位置还没有被占用,因此把Entry6存入数组下标4的位置。
    在这里插入图片描述
    这就是开放寻址法的基本思路。当然,在遇到哈希冲突时,寻址方式有很多种,并不一定只是简单地寻找当前元素的后一个元素,这里只是举一个简单的示例而已。

    在python中就是采用的开放定址法来解决冲突,开放定址法也称为闭散列法.当产生冲突时,python会通过一个二次探测函数f,计算下一个候选索引, 如果索引不可用,就再次用f探测.直到找到一个可用的位置.

    之所以叫做闭散列法,就是因为冲突的元素没有开辟额外的存储空间,还是在原先hash表的空间范围之内。

    2.读操作
    讲完了写操作,我们再来讲一讲读操作。读操作就是通过给定的Key,在散列表中查找对应的Value。

    例如调用 dict.get(“002936”),意思是查找Key为002936的dcit在散列表中所对应的值。

    3. 扩容
    在讲解数组时,曾经介绍过数组的扩容。既然散列表是基于数组实现的,那么散列表也要涉及扩容的问题。

    首先,什么时候需要进行扩容呢?

    当经过多次元素插入,散列表达到一定饱和度时,Key映射位置发生冲突的概率会逐渐提高。这样一来,大量元素拥挤在相同的数组下标位置,形成很长的链表,对后续插入操作和查询操作的性能都有很大影响。
    在这里插入图片描述
    这时,散列表就需要扩展它的长度,也就是进行扩容。

    对于python中的散列表实现dict来说,影响其扩容的因素有两个。

    • Capacity,即散列表的当前长度
    • LoadFactor,即散列表的填装因子,默认值为0.75f

    散列表的填装因子很容易计算。
    在这里插入图片描述
    散列表使用数组来存储数据,因此你需要计算数组中被占用的位置数。例如,下述散列表的填装因子为2/5,即0.4。
    在这里插入图片描述
    填装因子大于1意味着被占用元素的数量超过了数组的长度。一旦填装因子开始增大,你就需要在散列表中添加位置,这被称为调整长度。

    以上就是散列表各种基本操作的原理。由于dict的实现代码相对比较复杂,这里就不直接列出源码了,有兴趣的读者可以在python中直接阅读PyDictObject对象的源码。

    二叉树查找

    二叉查找树(binary search tree)在二叉树的基础上增加了以下几个条件。

    • 如果左子树不为空,则左子树上所有节点的值均小于根节点的值
    • 如果右子树不为空,则右子树上所有节点的值均大于根节点的值
    • 左、右子树也都是二叉查找树

    下图就是一个标准的二叉查找树。
    在这里插入图片描述
    二叉查找树的这些条件有什么用呢?当然是为了查找方便。

    例如查找值为4的节点,步骤如下。

    1. 访问根节点6,发现4<6。
      在这里插入图片描述
    2. 访问节点6的左孩子节点3,发现4>3。
      在这里插入图片描述
    3. 访问节点3的右孩子节点4,发现4=4,这正是要查找的节点。
      在这里插入图片描述
      对于一个节点分布相对均衡的二叉查找树来说,如果节点总数是n,那么搜索节点的时间复杂度就是O(logn),和树的深度是一样的。

    这种依靠比较大小来逐步查找的方式,和二分查找算法非常相似。

    展开全文
  • 几种常见搜索算法

    万次阅读 多人点赞 2019-06-05 22:22:03
    广度优先搜索(BFS) 深度优先搜索(DFS) 爬山法(Hill Climbing) 最佳优先算法(Best-first search strategy) 回溯法(Backtracking) 分支限界算法(Branch-and-bound Search Algorithm) A*算法 广度...

    目录

     

    广度优先搜索(BFS)

    深度优先搜索(DFS)

    爬山法(Hill Climbing)

    最佳优先算法(Best-first search strategy) 

    回溯法 (Backtracking)

    分支限界算法(Branch-and-bound Search Algorithm)

    A*算法


    广度优先搜索(BFS)

    这个不用我多说了吧……

    深度优先搜索(DFS)

    同上……

    爬山法(Hill Climbing)

    DFS的变形,不同的是每次选择的是最优的一个子结点,即局部最优解

    例如,对于8数码问题,设置一个函数表示放错位置的数目,每次选择子结点中放错最少的结点

    步骤:

    1.建立一个栈,将根结点放入栈

    2.判断栈顶元素是否是目标结点,如果是,算法结束,如果不是,进入第三步

    3.栈顶元素出栈,根据评估函数计算的顺序将此结点的子结点入栈

    4.如果栈空,则输出失败,否则,进入第二步

    最佳优先算法(Best-first search strategy) 

    是DFS和BFS的结合

    每次找到的是所有结点中最好估计值的那个结点

    找到的是全局最优解

    步骤:

    1.根据评估函数建立一个堆(或用优先队列),将根结点放入堆中

    2.判断栈顶元素是否是目标结点,如果是,算法结束,如果不是,进入第三步

    3.移出堆顶元素结点,将此结点的所有子结点加入堆

    4.如果堆空,输出失败,否则,进入第二步

    回溯法 (Backtracking)

    找到所有选择,走不通则回溯

    假定问题的解是一个向量(a1,a2,a3,...,an),其中的每个元素ai都是问题的一个元素

    步骤:

    建立一个问题的部分解v=(a1,a2,...,ak)

    若这个部分解是可行解,则继续,若不是可行解,删除ak,加入ak情况的另一种可能

    若ak的可能已经遍历完,回溯并寻找ak-1的下一个可能

    算法改进:搜索剪枝

     剪枝(pruning)可以帮助我们减少搜索空间,更快的找到解

    剪枝的思想就是就是通过某种判断,避免一些不必要的遍历过程,就是如果发现此分支不可能找到最优解,就立刻回溯

    剪枝的策略需要具体问题具体分析,这里不细讲

    回溯法框架:

    递归法

    Backtrack(k,X[1...K-1])
        if(k>n) output(X[1...N])
        else
            for each element x in S(k):
                   if(constraint(x,X[1...k-1]))
                        X[k]=x
                        backtrack(k+1,X[1...k])

    迭代法

    IterativeBacktrack()
        k=1
        while k>0
            while set S(k) is not empty
                get a new element x from set S(k)
                if(constraint(x,X[1,k-1]))
                    X[k]=x
                    if(solution(X)) output(X)
                    else k++
            k--

    分支限界算法(Branch-and-bound Search Algorithm)

    分支限界法与回溯法的区别
    1.求解目标不同 
         1.回溯法的求解目标是找出解空间树中满足约束条件的所有解
         2.分支限界法的求解目标则是尽快找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解
         3.分支限界法通常用于解决离散值的最优化问题
    2.搜索方式不同 
         1.回溯法以深度优先的方式(遍历结点)搜索解空间树
         2.分支限界法以广度优先或最小耗费优先的方式搜索解空间树
    3.对扩展结点的扩展方式不同 
         1.分支限界法中,每一个活结点只有一次机会成为扩展结点
          2.活结点一旦成为扩展结点,就一次性产生其所有儿子结点
    4.存储空间的要求不同 
         1.分支限界法的存储空间比回溯法大得多,因此当内存容量有限时,回溯法成功的可能性更大 

    转载自最佳优先搜索(Best-First Search) 

    分支限界算法可以用来寻找最优解,在平均情况下不必穷尽搜索

    分支限界算法的搜索类似于最佳优先算法并做了一些改进(比如剪枝)

    两个要点:

    如何产生分支

    如何产生界限

    基本思想:

    用一种方法分开解空间

    用一种方法预测一系列解的最小界(lower bound),用一种方法预测最优解的最大界(upper bound)

    如果一个解的最小界超出了整个解空间的最大界,那么这个解不可能是最优的,我们就可以提前终止此分支

    分支界限适合最小化问题

    平均情况下,许多分支能较早被终止,但许多NP难问题在最坏情况下仍是指数级的

    A*算法

    个人感觉类似最佳优先算法,都是维护一个优先队列或堆,将结点按照某个值优先的情况放进去,不同的是这次需要一个估计函数h(n)

    算法思想:对于优先队列,每取出一个结点n,将他的所有儿子结点n'放入优先队列,优先级由函数f(n)计算出

    g(n):起点到结点n的代价

    h(n):结点n到终点的估计代价

    f(n)=g(n)+h(n)

    A*算法是一种启发式算法

    设h*(n)为结点n到目标结点的实际最小代价

    只要h(n)<=h*(n),那么代价就不会被高估,这个算法就可以找出最优解

    A*算法使用最佳优先策略,用来解决优化问题

    步骤:

    1.把起点放入优先队列

    2.重复如下过程:

    • 取出优先级最高的结点n,即f(n)最小的结点,作为当前要处理的结点
    • 将这个结点放入一个close表中,这个表储存父结点子结点等信息
    • 对于此结点可达的结点n':
    • ①若这个结点不在队列中,计算g(n'),h(n'),f(n'),将其加入队列,并将n设为n'的父亲
    • ②若n'在队列中,计算由n到n'的g(n')值,更小的g(n')意味着这是更好的路径,如果g(n')更小,则将n设为n'的父亲,并重新计算g(n')和f(n')
    • 停止,当:
    • ①终点被找到
    • ②队列为空,此时查找失败

    3.保存路径,从终点开始,沿着父结点移动至起点,即为路径

    展开全文
  • 前言最近为了巩固一下自己的算法基础,又把算法书里的基本算法刷了一遍, 特地总结一下前端工程师需要了解的排序算法和搜索算法知识,虽然还有很多高深算法需要了解, 但是基础还是要好好巩固一下的...

    前言

    最近为了巩固一下自己的算法基础,又把算法书里的基本算法刷了一遍, 特地总结一下前端工程师需要了解的排序算法和搜索算法知识,虽然还有很多高深算法需要了解, 但是基础还是要好好巩固一下的.本文将以图文的形式为大家介绍如下算法知识,希望在读完之后大家能有所收获:

    • 冒泡排序及其优化

    • 选择排序

    • 插入排序

    • 归并排序

    • 快速排序

    • 顺序搜索

    • 二分搜索

    正文

    我想对于每个前端工程师来说, 最头疼的就是算法问题, 但是算法往往也是衡量一个人编程能力的一个很重要的指标.目前很多主流框架和库都应用了大量的算法设计模式,为了让自己的段位更高,我们只能不断的"打怪"(也就是刷算法)升级,才能成为"最强王者".

    其实前端发展这么多年, 越来越偏向于精细化开发, 很多超级应用(比如淘宝,微信)都在追求极致的用户体验, 时间就是金钱,这要求工程师们不能像以前那样,开发的程序只要能用就行, 我们往往还要进行更加细致的测试(包括单元测试, 性能测试等),就拿排序来说, 对于大规模数据量的排序, 我们采用冒泡排序肯定是要被疯狂吐槽的,因为冒泡排序的性能极差(复杂度为O(n^2).在真实项目中我们往往不会采用冒泡排序,更多的会用快速排序或者希尔排序.关于排序算法性能问题我在

    有详细介绍. 接下来就让我们来一起学习如何实现文章开头的几个常用排序和搜索算法吧.

    1. 冒泡排序及其优化

    我们在学排序算法时, 最容易掌握的就是冒泡排序, 因为其实现起来非常简单,但是从运行性能的角度来看, 它却是性能最差的一个.

    冒泡排序的实现思路是比较任何两个相邻的项, 如果前者比后者大, 则将它们互换位置.

    为了更方便的展示冒泡排序的过程和性能测试,笔者先写几个工具方法,分别为动态生成指定个数的随机数组生成元素位置序列的方法,代码如下:

    // 生成指定个数的随机数组
    const generateArr = (num = 10) => {
      let arr = []
      for(let i = 0; i< num; i++) {
        let item = Math.floor(Math.random() * (num + 1))
        arr.push(item)
      }
      return arr
    }
    
    // 生成指定个数的元素x轴坐标
    const generateArrPosX = (n= 10, w = 6, m = 6) => {
      let pos = []
      for(let i = 0; i< n; i++) {
        let item = (w + m) * i
        pos.push(item)
      }
      return pos
    }
    
    

    有了以上两个方法,我们就可以生成任意个数的数组以及数组项坐标了,这两个方法接下来我们会用到.

    我们来直接写个乞丐版的冒泡排序算法:

    bubbleSort(arr = []) {
        let len = arr.length
        for(let i = 0; i< len; i++) {
          for(let j = 0; j < len - 1; j++) {
            if(arr[j] > arr[j+1]) {
              // 置换
              [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
            }
          }
        }
        return arr
      }
    
    

    接下来我们来测试一下, 我们用generateArr方法生成60个数组项的数组, 并动态生成元素坐标:

    // 生成坐标
    const pos = generateArrPosX(60)
    // 生成60个项的数组
    const arr = generateArr(60)
    
    

    执行代码后会生成下图随机节点结构:

    有关css部分这里就不介绍了,大家可以自己实现.接下来我们就可以测试我们上面写的冒泡排序了,当我们点击排序时,结果如下:

    可以看到数组已按照顺序排好了,我们可以使用console.time来测量代码执行所用的时间,上面"乞丐版"冒泡排序耗时为0.2890625ms.

    我们深入分析代码就可以知道两层for循环排序导致了很多多余的排序,如果我们从内循环减去外循环中已跑过的轮数,就可以避免内循环中不必要的比较,所以我们代码优化如下:

    // 冒泡排序优化版
    bubbleSort(arr = []) {
      let len = arr.length
      // 优化
      for(let i = 0; i< len; i++) {
        for(let j = 0; j < len - 1 - i; j++) {
          if(arr[j] > arr[j+1]) {
            // 置换
            [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
          }
        }
      }
      return arr
    }
    
    

    经过优化的冒泡排序耗时:0.279052734375ms, 比之前稍微好了一丢丢, 但仍然不是推荐的排序算法.

    2. 选择排序

    选择排序的思路是找到数据结构中的最小值并将其放置在第一位,接着找到第二个最小值并将其放到第二位,依次类推.

    我们还是按照之前的模式,生成一个60项的数组, 如下:

    选择排序代码如下:

    selectionSort(arr) {
        let len = arr.length,
            indexMin
        for(let i = 0; i< len -1; i++) {
          indexMin = i
          for(let j = i; j < len; j++){
            if(arr[indexMin] > arr[j]) {
              indexMin = j
            }
          }
          if(i !== indexMin) {
            [arr[i], arr[indexMin]] = [arr[indexMin], arr[i]]
          }
        }
        return arr
    }
    
    

    点击排序时, 结果如下:

    说明代码运行正常, 可以实现排序, 控制台耗时为: 0.13720703125ms, 明显比冒泡排序性能要好.

    3. 插入排序

    插入排序 的思路是每次排一个数组项,假定第一项已经排序,接着它和第二项比较, 决定第二项的位置, 然后接着用同样的方式决定第三项的位置, 依次类推, 最终将整个数组从小到大依次排序.

    代码如下:

    insertionSort(arr) {
        let len = arr.length,
            j,
            temp;
        for(let i = 1; i< len; i++) {
          j = i
          temp = arr[i]
          while(j > 0 && arr[j-1] > temp) {
            arr[j] = arr[j-1]
            j--
          }
          arr[j] = temp;
        }
     }
    
    

    执行结果如下:

    控制台打印耗时为:0.09912109375ms.

    4. 归并排序

    归并排序算法性能比以上三者都好, 可以在实际项目中投入使用,但实现方式相对复杂.

    归并排序是一种分治算法,其思想是将原始数组切分成较小的数组,直到每个小数组只有一个元素,接着将小数组归并成较大的数组,最后变成一个排序完成的大数组。

    其实现过程如下图所示:

    为了实现该方法我们需要准备一个合并函数和一个递归函数,具体实现如下代码:

    // 归并排序
    mergeSortRec(arr) {
     let len = arr.length
     if(len === 1) {
       return arr
     }
     let mid = Math.floor(len / 2),
         left = arr.slice(0, mid),
         right = arr.slice(mid, len)
     return merge(mergeSortRec(left), mergeSortRec(right))
    }
    // 合并方法
    merge(left, right) {
        let result = []
            l = 0,
            r = 0;
        while(l < left.length && r < right) {
          if(left[l] < right(r)) {
            result.push(left[l++])
          }else {
            result.push(right[r++])
          }
        }
        while(l < left.length) {
          result.push(left[l++])
        }
        while(r < right.length) {
          result.push(right[r++])
        }
        return result
    }
    
    

    以上代码中的递归作用是将一个大数组划分为多个小数组直到只有一项,然后再逐层进行合并排序。如果有不理解的可以和笔者交流或者结合笔者画的草图进行理解。

    5. 快速排序

    快速排序是目前比较常用的排序算法,它的复杂度为O(nlog^n),并且它的性能比其他复杂度为O(nlog^n)的好,也是采用分治的思想,将原始数组进行划分,由于快速排序实现起来比较复杂,这里讲一下思路:

    1. 从数组中选择中间项作为主元

    2. 创建两个指针,左边一个指向数组第一项,右边一个指向数组最后一项,移动左指针直到我们找到一个比主元大的元素,移动右指针直到找到一个比主元小的元素,然后交换它们的位置,重复此过程直到左指针超过了右指针

    3. 算法对划分后的小数组重复1,2步骤,直到数组完全排序完成。

    代码如下:

    // 快速排序
    quickSort(arr, left, right) {
        let index
        if(arr.length > 1) {
          index = partition(arr, left, right)
          if(left < index - 1) {
            quickSort(arr, left, index -1)
          }
          if(index < right) {
            quickSort(arr, index, right)
          }
        }
      }
    // 划分流程
    partition(arr, left, right) {
        let part = arr[Math,floor((right + left) / 2)],
            i = left,
            j = right
        while(i <= j) {
          while(arr[i] < part) {
            i++
          }
          while(arr[j] > part) {
            j--
          }
          if(i <= j) {
            // 置换
            [arr[i], arr[j]] = [arr[j], arr[i]]
            i++
            j--
          }
        }
        return i
    }
    
    

    7. 顺序搜索

    搜索算法也是我们经常用到的算法之一,比如我们需要查找某个用户或者某条数据,不管是在前端还是在后端,都会使用搜索算法。我们先来介绍最简单也是效率最低的顺序搜索,其主要思想是将每一个数据结构中的元素和我们要查询的元素做比较,然后返回指定元素的索引。

    之所以说顺序搜索效率低是因为每次都要从数组的头部开始查询,直到查找到要搜索的值,整体查询不够灵活和动态性。顺序搜索代码实现如下:

    sequentialSearch(arr, item) {
        for(let i = 0; i< arr.length; i++) {
          if(item === arr[i]) {
            return i
          }
        }
        return -1
    }
    
    

    接下来我们看下面一种比较常用和灵活的搜索算法——二分搜索。

    8. 二分搜索

    二分搜索的思想有点“投机学”的意思,但是它是一种有理论依据的“投机学”。首先它要求被搜索的数据结构已排序,其次进行如下步骤:

    1. 找出数组的中间值

    2. 如果中间值是待搜索的值,那么直接返回中间值的索引

    3. 如果待搜索的值比中间值小,则返回步骤1,将区间范围缩小,在中间值左边的子数组中继续搜索

    4. 如果待搜索的值比选中的值大,则返回步骤1,将区间范围缩小,在中间值右边的子数组中继续搜索

    5. 如果没有搜到,则返回-1

    为了方便理解笔者画了如下草图:

    由上图大家可以很容易的理解二分搜索的实现过程,接下来我们看下代码实现:

    binarySearch(arr, item) {
        // 调用排序算法先对数据进行排序
        this.quickSort(arr)
    
        let min = 0,
            max = arr.length - 1,
            mid,
            el
        while(min <= max) {
          mid = Math.floor((min + max) / 2)
          el = arr[mid]
          if(el < item) {
            min = mid + 1
          }else if(el > item) {
            max = mid -1
          }else {
            return mid
          }
        }
        return -1
      }
    
    

    其实还有很多搜索算法,笔者在js基本搜索算法实现与170万条数据下的性能测试有具体介绍。

    参考文献:Learning JavaScript Data Structures and Algorithms

    最后

    如果想学习更多H5游戏webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在公号《趣谈前端》加入我们的技术群一起学习讨论,共同探索前端的边界.

    展开全文
  • 常见的几种搜索算法

    千次阅读 2020-06-10 17:54:13
    广度优先搜索算法(Breadth-First Search,BFS)是一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

    广度优先搜索(BFS)

    广度优先搜索算法(Breadth-First Search,BFS)是一种盲目搜寻法,目的是系统地展开并检查图中的所有节点,以找寻结果。换句话说,它并不考虑结果的可能位置,彻底地搜索整张图,直到找到结果为止。

    类似树的按层遍历,其过程为:首先访问初始点Vi,并将其标记为已访问过,接着访问Vi的所有未被访问过可到达的邻接点Vi1、Vi2……Vit,并均标记为已访问过,然后再按照Vi1、Vi2……Vit的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依此类推,直到图中所有和初始点Vi有路径相通的顶点都被访问过为止。

    深度优先搜索(DFS)

    如算法名称那样,深度优先搜索所遵循的搜索策略是尽可能“深”地搜索树。

    它的基本思想是:为了求得问题的解,先选择某一种可能情况向前(子结点)探索,在探索过程中,一旦发现原来的选择不符合要求,就回溯至父亲结点重新选择另一结点,继续向前探索,如此反复进行,直至求得最优解。深度优先搜索的实现方式可以采用递归或者栈来实现。

    由此可见,把通常问题转化为树的问题是至关重要的一步,完成了树的转换基本完成了问题求解。

    1. 减少节点数,思想:尽可能减少生成的节点数
    2. 定制回溯边界,思想:定制回溯边界条件,剪掉不可能得到最优解的子树

    在很多情况下,我们已经找到了一组比较好的解。但是计算机仍然会义无返顾地去搜索比它更“劣”的其他解,搜索到后也只能回溯。为了避免出现这种情况,我们需要灵活地去定制回溯搜索的边界。

    在这里插入图片描述
    通过上图来加深了解,比方说 8 是出口,1是出发点,2 , 5 , 6 , 9 是第一处分岔口, 假如我们选择 2 , 然后 3 , 4成为第二个分岔口,我们随便选择 3 ,发现是死胡同,然后返回上一个分岔口 2 ,再选择 4 , 结果又是死胡同,我们又返回 2 ,发现 2 没有别的路了, 然后返回 1 , 再走别的路, 直到走到 8 。

    爬山法(Hill Climbing)

    DFS的变形,不同的是每次选择的是最优的一个子结点,即局部最优解

    例如,对于8数码问题,设置一个函数表示放错位置的数目,每次选择子结点中放错最少的结点

    步骤:

    1.建立一个栈,将根结点放入栈

    2.判断栈顶元素是否是目标结点,如果是,算法结束,如果不是,进入第三步

    3.栈顶元素出栈,根据评估函数计算的顺序将此结点的子结点入栈

    4.如果栈空,则输出失败,否则,进入第二步

    最佳优先算法(Best-first search strategy)

    是DFS和BFS的结合

    每次找到的是所有结点中最好估计值的那个结点

    找到的是全局最优解

    步骤:

    1.根据评估函数建立一个堆(或用优先队列),将根结点放入堆中
    2.判断栈顶元素是否是目标结点,如果是,算法结束,如果不是,进入第三步
    3.移出堆顶元素结点,将此结点的所有子结点加入堆
    4.如果堆空,输出失败,否则,进入第二步

    回溯法 (Backtracking)

    找到所有选择,走不通则回溯

    假定问题的解是一个向量(a1,a2,a3,…,an),其中的每个元素ai都是问题的一个元素

    步骤:

    建立一个问题的部分解v=(a1,a2,…,ak)
    若这个部分解是可行解,则继续,若不是可行解,删除ak,加入ak情况的另一种可能
    若ak的可能已经遍历完,回溯并寻找ak-1的下一个可能

    分支限界算法(Branch-and-bound Search Algorithm)

    分支限界法与回溯法的区别:

    1. 求解目标不同 :
    • 回溯法的求解目标是找出解空间树中满足约束条件的所有解
    • 分支限界法的求解目标则是尽快找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解
    • 分支限界法通常用于解决离散值的最优化问题
    1. 搜索方式不同 :
    • 回溯法以深度优先的方式(遍历结点)搜索解空间树
    • 分支限界法以广度优先或最小耗费优先的方式搜索解空间树
    1. 对扩展结点的扩展方式不同 :
    • 分支限界法中,每一个活结点只有一次机会成为扩展结点
    • 活结点一旦成为扩展结点,就一次性产生其所有儿子结点
    1. 存储空间的要求不同 :
    • 分支限界法的存储空间比回溯法大得多,因此当内存容量有限时,回溯法成功的可能性更大

    A*算法

    个人感觉类似最佳优先算法,都是维护一个优先队列或堆,将结点按照某个值优先的情况放进去,不同的是这次需要一个估计函数h(n)

    算法思想:对于优先队列,每取出一个结点n,将他的所有儿子结点n’放入优先队列,优先级由函数f(n)计算出

    g(n):起点到结点n的代价

    h(n):结点n到终点的估计代价

    f(n)=g(n)+h(n)

    A*算法是一种启发式算法

    设h*(n)为结点n到目标结点的实际最小代价

    只要h(n)<=h*(n),那么代价就不会被高估,这个算法就可以找出最优解

    A*算法使用最佳优先策略,用来解决优化问题

    步骤:

    1.把起点放入优先队列
    2.重复如下过程:
    取出优先级最高的结点n,即f(n)最小的结点,作为当前要处理的结点
    将这个结点放入一个close表中,这个表储存父结点子结点等信息
    对于此结点可达的结点n’:
    ①若这个结点不在队列中,计算g(n’),h(n’),f(n’),将其加入队列,并将n设为n’的父亲
    ②若n’在队列中,计算由n到n’的g(n’)值,更小的g(n’)意味着这是更好的路径,如果g(n’)更小,则将n设为n’的父亲,并重新计算g(n’)和f(n’)
    停止,当:
    ①终点被找到
    ②队列为空,此时查找失败
    3.保存路径,从终点开始,沿着父结点移动至起点,即为路径

    展开全文
  • 常见智能算法实现

    千次阅读 2021-05-15 20:43:56
    常见智能算法实现 ...基于粒子群算法的多目标搜索算法 基于多层编码遗传算法的车间调度算法 免疫优化算法在物流配送中心选址中的应用 粒子群算法的寻优算法 基于粒子群算法的PID控制器优化设计 基于
  • 首先,我们必须要明白,搜索算法不同与一般的算法,可以说是相当重要。...常见搜索算法 1:枚举算法 所谓的枚举算法就是一一列举出所有的情况,如果符合条件就进行相应的操作,这种算法效率显然不高,适用
  • 常见搜索算法原理及其时间复杂度 1.顺序查找: 最基础的查找方法,对比每一个元素进行查找。在数据量很大的时候效率相当的慢。 数据结构:有序或者无需的队列 时间复杂度:O(n) 2.二分查找: 二分查找首先要求...
  • 特征选择-常见搜索算法

    千次阅读 2013-06-17 10:18:08
    2.2.1完全搜索  完全搜索分为穷举搜索(Exhaustive)与非穷举搜索(Non-Exhaustive)两类。... 算法评价:枚举了所有的特征组合,属于穷举搜索,时间复杂度是O(2n),实用性不高。  (2)分支限界搜索( Br
  • 常见聚类算法总结

    万次阅读 2018-09-07 17:12:55
    常见聚类算法总结 1.常见算法 1.原型聚类 “原型”是指样本空间中具有代表性的店。此类算法假设聚类结构能够通过一组原型刻画,通常情形下,算法先对原型进行初始化,然后对原型进行迭代更新求解。–西瓜书 ...
  • 常用搜索算法—盲目搜索和启发式搜索

    万次阅读 多人点赞 2019-05-25 00:51:39
    搜索算法 本文主要以一些概念对较为常见的搜索作简单介绍: 一、盲目搜索 对一个图进行搜索意味着按照某种特定的顺序依次访问其顶点。在所有搜索方式中,广度优先算法和深度优先搜索算法都十分重要,因为它们提供了...
  • 10种常见AI算法

    千次阅读 2019-10-08 12:55:39
    感知机 二分类 二分类的线性分类模型,也是判别模型...基于随机梯度下降法对损失函数的最优化算法,有原始形式和对偶形式。 K近邻法 K-nearest neighbor, K-NN 多分类和回归 是一种分类和回归方法,有监督学...
  • 参数搜索算法的重要性不言而喻...计算方法最常见的是迭代梯度递减算法和随机搜索算法。 一、有约束问题: 一维搜索: 1、试探法 a、黄金分割法(0.618法) 计算步骤: b、Fibonacci法 计算步骤:
  • 文章目录前言一、局部搜索算法(local search)二、 爬山法(HILL-CLIMBING)模拟退火(SIMULATED ANNEALING)集束搜索(Beam Search)遗传算法(Genetic algorithm)总结 前言 局部搜索算法简介 ​ 局部搜索算法是...
  • 常见GC算法

    千次阅读 2017-02-01 19:26:11
    下面介绍几种常见的GC算法。引用计数法 Reference Counting给对象添加一个引用计数器,每过一个引用计数器值就+1,少一个引用就-1。当它的引用变为0时,该对象就不能再被使用。它的实现简单,但是不能解决互相循环...
  • c语言常见排序算法

    千次阅读 多人点赞 2017-06-01 17:50:17
    排序算法 冒泡排序 快速排序 选择排序
  • 最优化算法——常见优化算法分类及总结

    万次阅读 多人点赞 2018-10-27 12:54:53
    之前做特征选择,实现过基于群智能算法进行最优化的搜索,看过一些群智能优化算法的论文,在此做一下总结。 最优化问题  在生活或者工作中存在各种各样的最优化问题,比如每个企业和个人都要考虑的一个问题“在...
  • 二分搜索(英语:binary search),是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在...
  • 常见分类算法优缺点

    千次阅读 2018-10-21 21:36:54
    本文主要回顾下几个常用算法的适应场景及其优缺点! 机器学习算法太多了,分类、回归、聚类、推荐、图像识别领域等等,要想找到一个合适算法真的不容易,所以在实际应用中,我们一般都是采用启发式学习方式来实验。...
  • 搜索算法 本文主要以一些概念对较为常见的搜索作简单介绍: 一、盲目搜索 对一个图进行搜索意味着按照某种特定的顺序依次访问其顶点。在所有搜索方式中,广度优先算法和深度优先搜索算法都十分重要,因为它们...
  • 算法导论》常见算法总结

    万次阅读 多人点赞 2018-11-08 15:51:55
    调整过程如下图所示: 4、堆排序算法 堆排序算法过程为:先调用创建堆函数将输入数组A[1…n]造成一个最大堆,使得最大的值存放在数组第一个位置A[1],然后用数组最后一个位置元素与第一个位置进行交换,并将堆的大小...
  • python实现常见算法

    千次阅读 多人点赞 2019-05-17 10:48:32
    常见算法: 一、排序引入 1.排序与搜索 排序算法(英语:Sorting algorithm)是一种能将一串数据依照特定顺序进行排列的一种算法。 2.排序算法的稳定性 稳定性:稳定排序算法会让原本有相等键值的纪录维持相对次序。...
  • 常见算法问题

    千次阅读 2019-06-25 04:39:42
    主要算法题目录如下,可能题目有重复,欢迎大家与我交流! 一、贪心算法 Problem1删数问题 【解题报告】 Problem2旅行家的预算 Problem3线段覆盖 Problem4背包问题 Problem5任务调度 Problem6果子合并 ...
  • 搜索算法入门

    千次阅读 多人点赞 2017-01-05 10:02:30
    搜索算法是利用计算机的高性能来有目的的穷举一个问题解空间的部分或所有的可能情况,从而求出问题的解的一种方法。现阶段一般有枚举算法、深度优先搜索、广度优先搜索、A*算法、回溯算法、蒙特卡洛树搜索、散列...
  • 搜索算法汇总

    千次阅读 2016-01-27 16:45:34
    搜索引擎算法一:TrustRank 算法 TrustRank 是近年来比较受关注的基于链接关系的排名算法。TrustRank中文可以翻译为信任指数。 TrustRank 算法最初来自于2004年斯坦福大学和雅虎的一项联合研究,用来检测...
  • 常见排序算法及其时间复杂度

    万次阅读 多人点赞 2019-06-20 18:00:18
    常见排序算法及其时间复杂度 一、内部排序:1.稳定的排序算法1.1 冒泡排序1.1.1 冒泡排序流程1.1.2 冒泡排序的实现1.2 插入排序1.2.1 插入排序流程1.2.2 插入排序的实现1.3 归并排序1.3.1 归并排序流程1.3.2 归并...
  • 常见的排序算法

    千次阅读 多人点赞 2016-06-07 01:55:34
    排序算法可谓数据结构模块中的重中之重,常见的哈希表,二叉树,搜索树/平衡树,位图等数据结构只是处理实际问题的抽象方法,实际在处理接受或生成的数据集时,排序算法显得尤其重要,排序算法家族很庞大,其中包括...
  • 数据结构算法常见面试考题

    万次阅读 多人点赞 2018-11-08 09:29:44
    (1) 红黑树的了解(平衡树,二叉搜索树),使用场景 把数据结构上几种树集中的讨论一下: 1.AVLtree 定义:最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡...
  • 几种常见GC算法介绍

    万次阅读 多人点赞 2019-02-21 23:49:16
    本文主要是对常用的GC算法(引用计数法、标记-清除法、复制算法、标记-清除算法)作出相关的说明,并对相关知识做简单的介绍。 一、什么是堆?  堆指用于动态(即执行程序时)存放对象的内存空间。而这个对象,在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 159,223
精华内容 63,689
关键字:

常见的搜索算法