使用javascript实现常见的排序算法

前言
算法,一门老生常谈的学科,最近在复习算法的知识,随作为切图的我,在平常工作中很难用的上这些知识,但是加强自身功底还是很有必要的。写下笔记记录成长的点滴,往后回头也还可以复习复习。在学习之余也乐于分享,好在在进击的路上,与诸君共勉。
正文
评述算法优劣的术语
稳定: 如果在序列里面本来a位于b前面,当a === b时,不交换a和b的位置; 不稳定: 就是在前者的基础上,a和b的位置交换了
内排序: 所有的操作都在内存中完成; 外排序: 由于数据量太大,把数据存放在磁盘上,而排序需要通过磁盘和内存的数据传输才能实现
时间复杂度: 一个算法运行完所需要时间的大小;空间复杂度: 一个算法运行需要的内存大小。
排序算法
排序算法的对比
图中名词解析: n: 数据规模; k:“桶”的个数;In-place: 占用常数内存,不占用额外内存; Out-place: 占用额外内存
对了算法术语有了初步的了解后,我们来看看排序算法的实现
1.冒泡算法
思路:
(1)从前向后遍历元素,对比相邻的两个元素,根据条件交换两个元素
(2)重复的对比相邻的两个数,把小的值移到前面,此过程就像冒泡一样
javascript代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// 构造排序算法类骨架
function ArraySort() {
    this.bubbleSort = function() {}
    this.selectSort = function() {}
    this.insertSort = function() {}
    this.mergeSort = function() {}
    this.quickSort = function() {}
    this.heapSort = function() {}
    
    // 交换
    function swap(array, i, j) {
        [array[i], array[j]] = [array[j], array[i]]
    }
}
// 实现
this.bubbleSort = function(array) {
    let length = array.length
    for(let i = 0; i < length; i++) {
        for(let j = 0; j < length-1-i; j++) {
            if(array[j] > array[j+1]) {
                swap(array, j, j+1)
            }
        }
    }
}
2.选择排序
思路:
(1)在一层遍历的时候,选择首元素认为是最小值
(2)在二层遍历中,将每个元素跟这个伪最小值对比,选择出比其更小的元素
(3)二层遍历结束前,将这两个元素交换
(4)重复123过程
javascript代码实现1
2
3
4
5
6
7
8
9
10
11
12this.selectSort = function(array) {
    let length = array.length,minIndex
    for(let i = 0; i < length-1; i++) {
        minIndex = i
        for(let j = i+1; j < length; j++) {
            if(array[minIndex] > array[j]) {
                minIndex = j
            }
    }
    if(minIndex != i) swap(array, i, minIndex)
}
3.插入排序
思路:
(1)类似拿到扑克牌后,将小牌插到前面那样。从下标1开始(递增),从后往前遍历
(2)抽出元素,前一个元素与其对比,大的就往后插
(3)元素往前移动,重复2,当元素不能往前移动了,就把抽出的元素插到空的的位置上
(4)重复23
javascript代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15this.insertSort = function(array) {
    let length = array.length, temp, j
    for(let i = 1; i < length; i++) {
        j = i
        temp = array[i]
        while(j>0 && array[j-1] > temp) {
            array[j] = array[j-1]
            j--
        }
        array[j] = temp
    }
}
4.归并排序
前面三个排序的时间复杂度都是O(n^2),归并排序采用了分治的思想,将复杂的问题分割成小单元,小单元在问题解决后再合并成解,利用了递归,算法复杂度为O(nlogn).
思路:
(1)将数组对折分半
(2)递归对折到最小单元,比较两个数的大小,交换
javascript代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35this.mergeSort = function(array) {
    mergeRec(array)
    
    function mergeRec(array) {
        if (array.length === 1) return array
        let left, right, imid
        imid = Math.floor(array.length/2)
        left = array.slice(0, imid)
        right = array.slice(imid)
    
        return merge(mergeRec(left), mergeRec(right))
    }
    
    function merge(left, right) {
        let result = [], il = 0, ir = 0
        while (il < left.length && ir < right.length) {
            if (left[il] < right[ir]) {
                result.push(left[il++])
            } else {
                result.push(right[ir++])
            }
        }
    
        while (il < left.length) {
            result.push(left[il++])
        }
    
        while (ir < right.length) {
            result.push(right[ir++])
        }
        
        return result
    }
}
5.快速排序
快排是使用最广泛的排序算法,跟归并一样也使用了分治思想,时间复杂度也为O(nlogn)
思路:
(1)取数组中间的值作为参考值,从首尾向中间遍历,从中间值左边找到比其大的数,再从右边找到比其小的数,交换两者,直到左边游标大于右边的游标
(2)把下标0到左边游标-1作为一个区间,把左边游标到length-1作为一个区间
(3)递归12过程
javascript代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34this.quickSort = function() {
    quick(array, 0, array.length-1)
    
    function quick(array, left, right) {
        if (array.length < 2) return
        let index = partition(array, left, right)
    
        if (left < index - 1) {
            quick(array, left, index - 1)
        } 
        if (index < right){
            quick(array, index, right)
        }
    }
    
    function partition(array, left, right) {
        let pivot = array[Math.floor((left + right) / 2)]
    
        while (array[left] < pivot) {
            left++
        }
    
        while (array[right] > pivot) {
            right--
        }
    
        if (left <= right) {
            swap(array, left, right)
        }
    
        return ++left
    }
}
6.堆排序
堆排序把数组当作二叉树来管理,也属于属于高效的排序算法之一。堆结构符合一下原则:根节点为堆中最大的值,父节点总是大于子节点值。
思路:
(1)构造一个满足array[parent(i)] >= array[i]的二叉树
(2)交换堆中的第一个元素和最后一个元素,此时最大值已经排好位置,缩小堆大小。
(3)此时堆可能不符合原则了,需要转化堆,也就是将新堆中的最大值放置根节点。
(4)重复23
javascript代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34this.heapSort = function() {
    let heapsize = array.length
    if (heapsize < 2) return
    // 构造堆,父节点总是大于子节点
    for(let i = Math.floor(heapsize / 2); i >= 0; i--) {
        heapify(array, heapsize, i)
    }
    while (heapsize > 0) {
        heapsize--
        swap(array, 0, heapsize)
        heapify(array, heapsize, 0)
    }
    
    function heapify(array, heapsize, i) {
        let left, right, parenti
        left = i*2+1
        right = i*2+2
        largest = i
    
        if (left < heapsize && array[left] > array[largest]) {
            largest = left
        }
        if (right < heapsize && array[right] > array[largest]) {
            largest = right
        }
        
        if (largest !== i) {
            swap(array, largest, i)
            heapify(array, heapsize, largest)
        } 
    }
}
看到这你可能会想,“兄dei Array原型上不是有sort()吗,何必自己写排序算法呀”。的确,在开发过程中我们很少情况下写排序算法,大部分情况前端拿到的数据是后端已经排后序的了,有时连sort也不用掉用,因为前端排序数据很花性能,我们还是花多点时间在用户体验上会比较划算。
写这个常见的排序算法完全是自己加固技术功底的玩意,但是有趣的问题来了,sort的实现原理是啥呀?要不我们来实现一个。
Array.prototype.sort的实现
在MDN上sort的定义
语法1
arr.sort([compareFunction])
参数
compareFunction 可选
用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。
返回值
排序后的数组。请注意,数组已原地排序,并且不进行复制。
javascript代码实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47this.arraySortFn(array, cFn) {
    let _cFn
    if (cFn === undefined) {
        _cFn = _compareStringUnicode
    }else if (typeof cFn === 'function') {
        _cFn = cFn
    }else {
        throw new Error('compareFn must be a function')
    }
    _selectSort(array, _cFn)
    
    // 改写选择排序,传入判断条件
    function _selectSort(array, cFn) {
        let l = array.length, selectIndex
        for (let i = 0; i < l - 1; i++) {
            // 假设的最值下标
            selectIndex = i
            for (let j = i + 1; j < l; j++) {
                if (cFn(array[selectIndex], array[j]) > 0) selectIndex = j
            }
            if (selectIndex !== i) swap(array, i, selectIndex)
        }
    }
    
    // 默认比较字符串的Unicode编码
    function _compareStringUnicode(a, b) {
        if(
            (typeof a !== 'string' && typeof a !== 'number' )||
            (typeof b !== 'string' && typeof b !== 'number')
        ) {
            throw new Error(`${a} / ${b} must be number or string`)
        }
        a = String(a)
        b = String(b)
        let l = a.length > b.length ? b.length : a.length
        for(let i=0; i<l; i++) {
            if(a.charCodeAt(i) === b.charCodeAt(i)) continue
            return a.charCodeAt(i) > b.charCodeAt(i)
        }
        return a.length > b.length ? true : false
    }
}
参考资料:
1.十大经典排序算法总结(JavaScript描述)
2.《学习javascript数据结构与算法》