今天正好周末,趁着阳光好,赶紧写代码,正好复习一下一些常见的排序算法,网上排序算法有很多很经典的文章,大家平时都可以互相参考,下面进入正题
冒泡排序
相信很多程序员写的第一个排序都是冒泡排序吧,冒泡排序被分为稳定排序之一,关于稳定排序和不稳定排序,后面我会讲一下,冒泡排序的时间复杂度为O(n²),冒泡排序的每一趟都筛选出来一个当前集合的最大值,我们来看图

代码如下
//冒泡
bubble () {
let length = this.arr.length, i, j, arr = [...this.arr]
for (i = 0; i < length; i++) {
for (j = 0; j < length - i; j++) {
if (arr[j + 1] && arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr
}快速排序
作为前端童鞋对Chrome浏览器应该不陌生,旧版本的Chrome所实现的Array.prototype.sort就是基于快速排序实现的,快速排序的时间复杂度为O(n * logn),快速排序采用了分治的思想,利用递归将数组分解到最小单元,每个单元的排序都实现自治,最后合并来达到排序。快速排序为非稳定性排序,我们来看图

代码如下
//快速排序
quick (arr) {
let left = [], right = [], length, middleIndex, middleValue
arr = arr === void 0 ? [...this.arr] : arr
length = arr.length
if (length <= 1) return arr
middleIndex = ~~(length / 2)
middleValue = arr.splice(middleIndex, 1)
arr.forEach(item => {
if (item < middleValue) {
left.push(item)
} else {
right.push(item)
}
});
return this.quick(left).concat(middleValue, this.quick(right))
}插入排序
插入排序是一种简单直观且稳定的排序算法,算法适用于少量数据的排序,少量数据时,性能优于冒泡,时间复杂度为O(n^2),插如排序内层是反向查找的过程,看图

代码如下
//插入排序,逆向查找
insert (arr) {
let length = arr.length, i, j, temp
for (i = 0; i < length; i++) {
temp = this.arr[i]
j = i
while (j > 0 && arr[j - 1] > temp) {
arr[j] = arr[j - 1]
j--
}
arr[j] = temp
}
return arr
}选择排序
选择排序一种原值比较的排序算法,思路就是找到数组中的最小值,并将其放到第一位,每一趟都可以找到当前元素的最小值,以此类推,时间复杂度也是O(n²),看图

代码如下
//选择排序,查找最小值
select (arr) {
let length = arr.length, i, j, minIndex
for (i = 0; i < length; i++) {
minIndex = i
for (j = i; j< length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j
}
}
//拿到最小值
minIndex !== i && ([arr[i], arr[minIndex]] = [arr[minIndex], arr[i]])
}
return arr
}堆排序
堆排序也是一种高效的算法,把数组当成二叉树来排序而闻名,堆排序首先要构建大顶堆,大顶堆是一个非线性的数据结构,但是整体性能要优于完全二叉树和自平衡二叉树,大顶堆的元素更新也比常见的数据结构要快,一般为O(logn),大顶推的内存存储要优于树形结构或者链表结构,在一些经典面试题中,大顶堆的数据结构优势极为明显,例如在固定长度的数组中,找出第五大元素,以及插入元素如何更新,这时候用到大顶堆效率会很高,接下来看图

代码如下
//堆排序
heap (arr) {
//构建大顶堆
function buildHeap (arr = [], i, length) {
let left = i * 2 + 1, right = i * 2 + 2, largest = i
if (left < length && arr[left] > arr[largest]){
largest = left
}
if (right < length && arr[right] > arr[largest]){
largest = right
}
largest !== i
&& ([arr[i], arr[largest]] = [arr[largest], arr[i]])
// 继续查找最大值,更新子节点
&& buildHeap(arr, largest, length)
}
let i, length = arr.length, middleIndex = ~~(length / 2)
//自下而上,查找最大值
for (i = middleIndex; i >= 0 ; i--) {
buildHeap(arr, i, length)
}
//开始排序
while (length > 1) {
// 交换头尾
length--
[arr[0], arr[length]] = [arr[length], arr[0]]
// 重新排序
buildHeap(arr, 0, length)
}
return arr
}归并排序
归并排序的思想和快排一样,都是分治思路,时间复杂度为O(n * logn),FireFox所实现的Array.prototype.sort就是基于归并排序的变体,将原数组按照中间分割成若干个小单元,进行分治排序,看图

代码如下
//归并排序
reduce (arr) {
function splitArr (arr) {
if (arr.length <= 1) return arr
let middleIndex = ~~(arr.length / 2),
left = arr.slice(0, middleIndex),
right = arr.slice(middleIndex)
return merge(splitArr(left), splitArr(right))
}
function merge (left, right) {
let li = 0, ri = 0, res = []
while (li < left.length && ri < right.length) {
if (left[li] < right[ri]) {
res.push(left[li++])
} else {
res.push(right[ri++])
}
}
//剩余的填充进去
if (li < left.length) res.push(...left.slice(li))
if (ri < right.length) res.push(...right.slice(ri))
return res
}
return splitArr(arr)
}Duff装置
这个应该和排序是没关系了,属于循环的结构优化体 Duff装置的实现撒通过将数组的长度除以8来获取循环体需要进行多少次迭代,然后将每个循环体内部展开调用8次,来达到减少循环体的调用,优化代码效率,看代码
duff (arr = [], visitor = function () {}) {
let length = this.arr.length,
leftover = length % 8
iterations = ~~(length / 8)
idx = 0
if (leftover > 0) {
do {
visitor(arr[idx++])
} while (--leftover > 0)
}
do {
visitor(arr[idx++])
visitor(arr[idx++])
visitor(arr[idx++])
visitor(arr[idx++])
visitor(arr[idx++])
visitor(arr[idx++])
visitor(arr[idx++])
visitor(arr[idx++])
} while (--iterations > 0)
}总结
以上就是我总结的常见算法,当然还有一些遗漏的,例如希尔排序、基数排序、桶排序等,后续可能会更新进来,这里总结一下稳定排序和非稳定排序的区别,稳定排序的优势在于,原数组中如果两个值一样的元素,在排序完成后元素位置不变,但是不稳定排序完成后,数组元素的位置将可能发生偏差,应用场景在于,能够保证元素的第二排序规则,比如班级学号和考试成绩排序,第一排序规则肯定是成绩,第二这是学号,OK,今天先到这了,如果gif图有侵权的,可以留言相告。