JavaScript之常见排序算法

235 阅读5分钟

今天正好周末,趁着阳光好,赶紧写代码,正好复习一下一些常见的排序算法,网上排序算法有很多很经典的文章,大家平时都可以互相参考,下面进入正题

冒泡排序

相信很多程序员写的第一个排序都是冒泡排序吧,冒泡排序被分为稳定排序之一,关于稳定排序和不稳定排序,后面我会讲一下,冒泡排序的时间复杂度为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图有侵权的,可以留言相告。