ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

前端的几种基本算法(二分查找,选择排序,插入排序,希尔排序,归并排序,快速排序,堆排序)

2021-04-12 19:01:36  阅读:209  来源: 互联网

标签:arr 插入排序 元素 堆排序 len base let 排序


现在前端对于算法的要求是越来越高了,以下简单归纳下前端的几种基本的排序算法与二分查找相关的内容

二分查找

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

在有序的数组中查询一个元素用二分查找法是非常高效的,在应用中可以简单的分为三种情况,即:查找目标值,查找比目标值大的第一个元素,查找比目标值小的第一个元素。

 

 

 

 

 

查找目标值

function binarySearch(arr, target) {
  let l = 0
  let r = arr.length - 1
  let mid = 0

  while(l <= r) {
    mid = (l + r) >> 1
    if (arr[mid] > target) {
      r = mid - 1
    } else if (arr[mid] < target) {
      l = mid + 1
    } else {
      return mid
    }
  }

  return -1
}

查找比目标值大的第一个元素

function binarySearchFirstGreate(arr, target) {
  let l = 0
  let r = arr.length - 1
  let mid = 0

  while(l <= r) {
    mid = (l + r) >> 1
    if (arr[mid] > target) {
      r = mid - 1
    } else {
      l = mid + 1
    }
  }

  return l
}

查找比目标值小的第一个元素

function binarySearchFirstLess(arr, target) {
  let l = 0
  let r = arr.length - 1
  let mid = 0

  while(l <= r) {
    mid = (l + r) >> 1
    if (arr[mid] < target) {
      l = mid + 1
    } else {
      r = mid - 1
    }
  }

  return r
}

 

选择排序

选择排序的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。

选择排序是不稳定的排序方法。

 

function selectionSort(arr) {
  let l = arr.length
  for(let i = 0; i < l; i++) {
    for(let j = i + 1; j < l; j++) {
      if (arr[i] > arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
}

 

插入排序

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。

插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动

它与选择排序的区别是:

  1. 选择排序是在未排列的数据中选取最大(小)的值。
  2. 插入排序是在已排列的数据中寻找正确的位置,所以插入排序比选择排序性能会好很多。

function insertSort(arr) {
  let l = arr.length
  for(let i = 1; i < l; i++) {
    for(let j = i; j > 0; j--) {
      if (arr[j - 1] > arr[j]) {
        [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
      }
    }
  }
}

 

希尔排序(增强版的插入排序)

希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

 

function shellSort(arr) {
  let t = new Date()
  let len = arr.length
  let h = 1
  while(h < len / 3) h = 3 * h + 1 // 1, 4, 13, 40, 121, 364, 1093
  while(h >= 1) {
    // 将数组变为h有序
    for(let i = h; i < len; i++) {
      for(let j = i; j >= h; j -= h) {
        if (arr[j] < arr[j - h]) {
          [arr[j - h], arr[j]] = [arr[j], arr[j - h]]
        }
      }
    }
    h = Math.floor(h / 3)
  }
}

 

归并排序

归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

 

首先归并排序需要一个将两个有序数组合并的方法:

function merge(a, l, m, r) {
  let i = l, j = m + 1, aux = []

  for (let k = l;k <= r; k++) {
    aux[k] = a[k]
  }

  for (let k = l; k <= r; k++) {
    if (i > m) {
      a[k] = aux[j++]
    } else if (j > r) {
      a[k] = aux[i++]
    } else if (aux[j] < aux[i]) {
      a[k] = aux[j++]
    } else {
      a[k] = aux[i++]
    }
  }

  return a
}

归并排序的算法可以分为两种方式:

  1. 自顶向下:采用递归的方式,不断的将分割的子数组,直到将子数组的个数分割成1,然后再用merge合并成一个有序的大数组
  2. 自底向上:采用双层循环的方式,先将数组内的元素与相邻元素归并,然后递增到最后的一个大数组

自顶向下

function sort_down(a, l, r) {
  if (l >= r) return
  let m = (l + r) >> 1
  sort_down(a, l, m) // 左边排序
  sort_down(a, m + 1, r) // 右边排序
  if (a[m] > a[m + 1]) {
    merge(a, l, m, r) // 合并
  }
}

自底向上

function sort_up(a) {
  let n = a.length
  for (let i = 1; i < n; i += i) {
    for (let j = 0; j < n - i; j += i + i) {
      merge(a, j, i + j - 1, Math.min(j + i + i - 1, n - 1))
    }
  }
}

 

快速排序

快速排序(Quicksort)是对冒泡排序算法的一种改进。

快速排序是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速的原地排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

快速排序分为两种方式:

  1. 二向切分快速排序:先进行左指针的值与base的比较,如果比base大,则从右指针递减与base的比较,如果遇到比base小的则进行左指针与右指针互换,以此规则循环,直到比base小的都在左,大的都在右。然后进行递归直到整个数组有序。
  2. 三向切分快速排序:设立三个指针:左指针,中指针,右指针。三向切分只比较中指针指向的值与base的大小,如果中指针小于base,则左指针与中指针互换且都递增1,如果比base大,则中指针与右指针互换,继续与base比较,如果相等,则中指针加1,直到整个数组的左部分都比base小,右部分都比base大,然后递归直到整个数组有序。(左指针的索引是与base最近的,直到右指针的索引靠近base,则该循环结束。)

三向切分比二向切分的优化点在于:如果数组能有重复值的话,三向切分不需要重复比较,而二向切分是要重复比较的,对于大批量的用户数据排序,该特性非常有用。

 

 

 三向切分图

二向切分快速排序

function quickSort(arr, l, r) {
  if (l >= r) return

  let base = arr[l]
  let i = l
  let j = r

  while(l <= r) {
    while(l < r && arr[++l] < base) {}
    while(l < r && arr[--r] > base) {}
    
    if (l < r) {
      [arr[l], arr[r]] = [arr[r], arr[l]]
    } else {
      [arr[l - 1], arr[i]] = [base, arr[l - 1]]
      break
    }
  }

  quicksort(arr, i, l - 2)
  quicksort(arr, l, j)
}

三向切分快速排序

function sQuickSort(arr, l, r) {
  if (l >= r) return
  let lf = l
  let ri = r
  let v = arr[lf]
  let i = l + 1

  while(i <= ri) {
    if (v > arr[i]) {
      [arr[lf++], arr[i++]] = [arr[i], arr[lf]]
    } else if (v < arr[i]) {
      [arr[i], arr[ri--]] = [arr[ri], arr[i]]
    } else {
      i++
    }
  }
  squicksort(arr, l, lf - 1)
  squicksort(arr, ri + 1, r)
}

堆排序

堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

 

 堆有两个重要的基本操作,即在堆有序时对单个元素的下沉和上浮操作。

以大顶堆为例(大顶堆即堆顶元素为最大,小顶堆的堆顶元素为最小):

大顶堆的下沉:

function sink(arr, k, len) {
  while(2 * k + 1 < len) {
    let j = 2 * k + 1
    if (j < len - 1 && arr[j] < arr[j + 1]) j++
    
    if (arr[k] >= arr[j]) break
    
    [arr[k], arr[j]] = [arr[j], arr[k]]
    k = j
  }
}

大顶堆的上浮:

function swim(arr, len) {
  let p = 0 // 父级节点
  while(len > 0) {
    p = (len - 1) >> 1

    // (len & 1) 为0的情况下是有兄弟节点的,选出最大的与父级节点比较
    if ((len & 1) === 0 && arr[len] < arr[len - 1]) len--

    if (arr[len] <= arr[p]) break

    [arr[len], arr[p]] = [arr[p], arr[len]]
    len = p
  }
}

在有序堆中每次添加和删除元素后执行的下沉和上浮操作,都会得到目前有序堆中的最大(小)元素,以此特性就可以进行对元素排序。

function heapSort(arr) {
  let len = arr.length
  let copy = []
  // 建立一个有序的堆
  for (let i = (len - 1) >> 1; i >= 0; i--) {
    sink(arr, i, len)
  }
  // 每次将堆顶元素与堆尾元素进行替换,再进行堆顶元素的下沉且堆长度减一,以此可以得到一个有序的数据
  while(len--) {
    [arr[0], arr[len]] = [arr[len], arr[0]]
    sink(arr, 0, len)
  }
}

大顶堆的的排序得到的是一个降序排序,小顶堆的则得到的是升序数据。

 

简单描述各排序算法的性能特点:

算法 是否稳定 是否原地排序 时间复杂度 空间复杂度 备注
选择排序 N2 1 取决于输入元素的排列情况
插入排序 介于N和N2之间 1
希尔排序 NlogN?
N6/5?
1
快速排序 NlogN lgN 运行效率由概率提供保证
三向快速排序 介于N和NlogN之间 lgN 运行效率由概率保证,同时也
取决于输入元素的分布情况
归并排序

NlogN

N  
堆排序 NlogN 1  

标签:arr,插入排序,元素,堆排序,len,base,let,排序
来源: https://www.cnblogs.com/kdcg/p/14648745.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有