[数据结构与算法]排序算法汇总(JavaScript版)

39 篇文章 0 订阅
36 篇文章 0 订阅

JavaScript所有排序算法汇总

在这里插入图片描述

一、冒泡排序

        Array.prototype.bubbleSort = function () {
            for (let i = 0; i < this.length - 1; i++) {
                let flag = 0 /* 用于记录一趟冒泡是否发生交换 */
                for (let j = 0; j < this.length - 1 - i; j++) { /* 一趟冒泡 */                     
                    if (this[j] > this[j + 1]) {
                        const temp = this[j]
                        this[j] = this[j + 1]
                        this[j + 1] = temp
                        flag = 1 /* 发生了交换 */
                    }
                }
                if (flag === 0) break /* 如果一趟冒泡全程无交换,则说明排序已完成,break跳出循环 */
            }
            return this
        }
        const arr = [2, 1, 5, 3, 4]
        console.log(arr.bubbleSort()) //  [1, 2, 3, 4, 5]

稳定性:稳定
时间复杂度:O(N^2) 两层嵌套循环

最好情况:数组本来就有序,第一次冒泡就没有发生交换,flag===0,break,进行了一次循环O(n)
最坏情况:数组完全逆序,O(N^2)

二、插入排序

        Array.prototype.insertionSort = function () {
            for (let i = 1; i < this.length; i++) { /* 从第二张开始 */
                const temp = this[i] /* 抽出一张牌,此时产生了一个空位 */
                let j = i
                while (j > 0) { /* 往前比较 */
                    if (this[j - 1] > temp) {
                        /* 被比较的牌如果大于抽出的牌 */
                        this[j] = this[j - 1] /* 则被比较的牌往后放到一位到空位中,空位也就前移了 */
                    } else {
                        break /* 否则 */
                    }
                    j--
                }
                this[j] = temp /* 将抽出的牌放到当前空出的位置 */
            }
            return this
        }
        arr = [2, 3, 1, 5, 4]
        console.log(arr.insertionSort())

稳定性:稳定
时间复杂度:O(N^2) 两层嵌套循环
最好情况:数组本来就有序,只需要一次循环将牌都抽一次O(N)
最差情况:数组完全逆序,每次循环都要错位O(N^2)

冒泡排序和插入排序的比较:

插入排序相比冒泡排序而言,交换的操作要更加简单,冒泡排序是每一次都进行交换操作,而插入排序则是错开位置最后插入

冒泡排序插入排序选择排序合称为简单排序
默认从小到大的排序顺序,如果对于下标i<j,而arr[i]>arr[j],则称(i,j)是一对逆序对(inversion)
对于序列{34,8,64,51,32,21}
(34,8)(34,32)(32,21)(64,51)(64,32)(64,21)(51,32)(51,21)(32,21)一共9个逆序对,
对于简单排序来说,每次交换的都是两个相邻的元素,所以交换一次就消除一个逆序对,做交换的次数也就正好等于逆序对的数量

插入排序:T(N,I)=O(N+I) /* I表示逆序对数量 */
如果序列中的逆序对比较少,换言之序列基本有序,则插入排序简单而高效

定理:任意N个不同元素组成的序列平均具有N(N-1)/4个逆序对
定理:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为Ω(N^2)(Ω为下界)
这意味着:想要提高算法的效率,我们必须
——>每次消去不止1个逆序对
——>每次交换相隔比较远的2个元素,这样就可能在一次交换中消去几个逆序对

三、希尔排序(by Donald Shell)

利用了插入排序的简单,同时克服插入排序每次只交换相邻两个元素的缺点
在这里插入图片描述

原始希尔排序:
        Array.prototype.shellSort = function () {
            for (let D = Math.floor(this.length / 2); D > 0; D = Math.floor(D / 2)) { /* 选取增量序列 */
                for (let i = D; i < this.length; i += D) { /* 插入排序(将原来的1改为了D) */
                    let temp = this[i]
                    let j = i
                    while (j >= D) {
                        if (this[j - D] > temp) {
                            this[j] = this[j - D]
                        } else {
                            break
                        }
                        j -= D
                    }
                    this[j] = temp
                }
            }
            return this
        }
        console.log([2, 4, 5, 3, 1, 2, 5, 6, 7, 9, 1, 5, -1, 2, 6, 4, 72, 6, 8, 4].shellSort())

最坏情况:T=Θ(N^2)(Θ是上界也是下界)
这种原始希尔排序有个缺点,因为他的增量序列是靠每次 /2来选取的,想下面这种情况则导致了一个问题:
在这里插入图片描述
那么,就有一些学者提出了更多的增量序列:

1.Hibbard增量序列
  ·Dk=2^k - 1 保证了相邻元素互质
  ·最坏情况:T=Θ(N^(3/2)
  ·猜想:Tavg=O(N^(5/4))
2.Sedgewick增量序列
  ·{1,5,19,41,109,...}
  计算方法:9*4^i - 9*2^i +1或4^i - 3*2^i + 1
  ·猜想:Tavg=O(N^(7/6)),Tworst=O(N^(4/3))
  ·当要排序的对象是几万数量级的,用希尔排序+Sedgewick增量序列效果是比较好的

四、选择排序

        Array.prototype.selectionSort = function () {
            for (let i = 0; i < this.length; i++) {
                let minPosition = i
                /* 从this[i]到this[length-1]中找最小元,并将其位置赋给minPosition */
                for (let j = i + 1; j < this.length; j++) {
                    if (this[j] < this[minPosition]) {
                        minPosition = j
                    }
                }
                /* 将最小元和this[i]交换 */
                let temp = this[i]
                this[i] = this[minPosition]
                this[minPosition] = temp
            }
            return this
        }
        console.log([5, 4, 1, 3, 2].selectionSort())

由于选择排序寻找最小元的操作每次都需要遍历完i到length-1的数组,所以他的时间复杂度: T=Θ(N^2),对此寻找最小元操作的改进由此我们有了堆排序

五、堆排序

在这里插入图片描述
改进BuildHeap为大顶堆:

        // 交换两个节点
        function swap(A, i, j) {
            let temp = A[i];
            A[i] = A[j];
            A[j] = temp;
        }
        // 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:
        // 假设 结点 i 以下的子堆已经是一个大顶堆,shiftDown函数实现的
        // 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面
        // 将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点
        // 都执行 shiftDown操作,所以就满足了结点 i 以下的子堆已经是一大
        //顶堆
        function shiftDown(A, i, length) {
            let temp = A[i]; // 当前父节点
            // j<length 的目的是对结点 i 以下的结点全部做顺序调整
            for (let j = 2 * i + 1; j < length; j = 2 * j + 1) {
                temp = A[i]; // 将 A[i] 取出,整个过程相当于找到 A[i] 应处于的位置
                if (j + 1 < length && A[j] < A[j + 1]) {
                    j++; // 找到两个孩子中较大的一个,再与父节点比较
                }
                if (temp < A[j]) {
                    swap(A, i, j) // 如果父节点小于子节点:交换;否则跳出
                    i = j; // 交换后,temp 的下标变为 j
                } else {
                    break;
                }
            }
        }
        // 堆排序
        Array.prototype.heapSort = function () {
            // 初始化大顶堆,从第一个非叶子结点开始
            for (let i = Math.floor(this.length / 2 - 1); i >= 0; i--) {
                shiftDown(this, i, this.length);
            }
            // 排序,每一次for循环找出一个当前最大值,数组长度减一
            for (let i = Math.floor(this.length - 1); i > 0; i--) {
                swap(this, 0, i); // 根节点与最后一个节点交换
                shiftDown(this, 0, i); // 从根节点开始调整,并且最后一个结点已经为当
                // 前最大值,不需要再参与比较,所以第三个参数
                // 为 i,即比较到最后一个结点前一个即可
            }
            return this
        }
        let arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2];
        console.log(arr.heapSort());

六、归并排序

        function mergeSort(arr) { /* 采用自上而下的递归方法,用来拆分数据成小的数组 */
    		var len = arr.length;
    	  if(len < 2) {
    	    return arr;
    	  }
    	  var middle = Math.floor(len / 2),
    	  left = arr.slice(0, middle),
    	  right = arr.slice(middle);
    	  return merge(mergeSort(left), mergeSort(right));
    	}
    	function merge(left, right){
    	  var result = [];
    	  while (left.length && right.length) {
    	    if (left[0] <= right[0]) { /* 对比左右的数据,小的数据先进栈 */
    	      result.push(left.shift());
    	    } else {
    	      result.push(right.shift());
    	    }
    	  }
    	  while (left.length){
    	    result.push(left.shift());
    	  }
    	  while (right.length){
    	    result.push(right.shift());
    	  }
    	  return result;
    	}
    	var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
    	console.log(mergeSort(arr));

稳定性:稳定
时间复杂度:O(NlogN)
空间复杂度: O(N)

七、快速排序

主元为头元素:

        Array.prototype.quickSort = function () {
            const rec = (arr) => {
                if (arr.length <= 1) {
                    return arr
                }
                const left = []
                const right = []
                const mid = arr[0]
                /* 这里基准主元我直接用了头部元素 */
                for (let i = 1; i < arr.length; i++) {
                    if (arr[i] < mid) {
                        left.push(arr[i])
                    } else {
                        right.push(arr[i])
                    }
                }
                return [...rec(left), mid, ...rec(right)]
            }
            const res = rec(this)
            res.forEach((n, i) => {
                this[i] = n
            });
            return this
        }

        const arr = [2, 4, 5, 3, 1]
        console.log(arr.quickSort())

优化方案:
1.判断当前处理的数组大小,如果小于Cutoff(自己设定的一个值),则用插入排序处理
2.主元的选取,上面这个算法主元选取的是头部元素,更好的方式是随机选取,但是Math.random()这个函数并不便宜,所以我们可以采用选取头中尾三个元素的中位数为主元

递归操作的时间复杂度是O(logN)
分区操作的时间复杂度是O(N)
所以快速排序的时间复杂度是:O(NlogN)

八、表排序

表排序属于简介排序
它针对排序对象的元素是一个很大的结构体的时候
这种元素不适合进行直接移动
所以我们用指针(元素的下标)代表元素进行排序
比如我们要按照key的顺序输出书籍:
在这里插入图片描述

物理排序

在这里插入图片描述

如上图,N个数字的排列由若干个独立的环组成。

即:

A[0]对应table[3],A[3]对应table[1],A[1]对应table[5],A[5]对应table[0],这是一个环。
有了独立的环,针对每个环进行移动操作则减少移动次数(避免无效的移动)。
首先将A[0]存在Temp,之后将A[3]移至[0],则此时[3]处空出,将A[1]移至[3],以此类推。
应该如何判断一个环结束呢?

if(table[i] == i) break;
每当放好一本书时,就把其所放位置table[i]设为i。
复杂度分析
最好情况:初始即有序;
最坏情况:

有⌊ N / 2 ⌋ \lfloor N/2 \rfloor⌊N/2⌋个环,每个环包含2个元素;
需要⌊ 3 N / 2 ⌋ \lfloor 3N / 2 \rfloor⌊3N/2⌋此元素移动。
则T = O ( m N ) T=O(mN)T=O(mN),m是每个A元素的复制时间。

桶排序

桶排序解释可参考:https://blog.csdn.net/an2766160/article/details/88532740

        function bucketSort(arr, bucketSize) {
            if (arr.length === 0) {
                return arr;
            }
            
            var i;
            var minValue = arr[0];
            var maxValue = arr[0];
            for (i = 1; i < arr.length; i++) {
                if (arr[i] < minValue) {
                    minValue = arr[i]; //输入数据的最小值
                } else if (arr[i] > maxValue) {
                    maxValue = arr[i]; //输入数据的最大值
                }
            }

            //桶的初始化
            var DEFAULT_BUCKET_SIZE = 5; //设置桶的默认大小为5
            bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
            var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
            var buckets = new Array(bucketCount);
            for (i = 0; i < buckets.length; i++) {
                buckets[i] = [];
            }

            //利用映射函数将数据分配到各个桶中
            for (i = 0; i < arr.length; i++) {
                buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
            }

            arr.length = 0;
            for (i = 0; i < buckets.length; i++) {
                insertionSort(buckets[i]); //对每个桶进行排序,这里使用了插入排序
                for (var j = 0; j < buckets[i].length; j++) {
                    arr.push(buckets[i][j]);
                }
            }

            return arr;
        }

基数排序

// Todo
此文部分参考:浙江大学慕课 数据结构与算法 陈越
向大家推荐浙江大学翁恺老师和陈越老师的慕课

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值