经典排序算法 0. 放在前面
==1. 冒泡排序(重点)== 比较相邻的元素,前面>后面,则调换位置
所有元素都进行这一操作
需要用到两个for循环
时间复杂度:
最好情况:O(n)
平均情况:O(n^2)
最坏情况:O(n^2)
稳定性:稳定
额外的空间:O(1)
原地排序,不需要额外空间
1. 一般版 1 2 3 4 5 6 7 8 9 10 11 12 13 function bubbleSort1 (arr ) { var len = arr.length; for (var i=0 ;i<arr.length;i++){ for (var j=0 ;j<len-1 -i;j++){ if (arr[j]>arr[j+1 ]){ var temp = arr[j+1 ]; arr[j+1 ] = arr[j]; arr[j] = temp; } } } return arr }
2. 改进版 设置pos,记录每轮排序中最后一次进行交换的位置。下一轮排序只扫描到pos位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function bubbleSort2 (arr ) { var i = arr.length - 1 ; while (i>0 ) { var pos = 0 ; for (var j=0 ;j<i;j++){ if (arr[j]>arr[j+1 ]){ pos = j; var temp = arr[j+1 ]; arr[j+1 ] = arr[j]; arr[j] = temp; } } i = pos; } return arr }
3. 双向冒泡 正向和反向两遍冒泡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function bubbleSort3 (arr ) { var low = 0 ; var high = arr.length - 1 ; while (low<high) { for (var j=low;j<high;++j){ if (arr[j]>arr[j+1 ]){ var temp = arr[j+1 ]; arr[j+1 ] = arr[j]; arr[j] = temp; } } --high; for (var j=high;j<low;--j){ if (arr[j]<arr[j+1 ]){ var temp = arr[j+1 ]; arr[j+1 ] = arr[j]; arr[j] = temp; } } } return arr }
2. 选择排序 在序列中找到最小/最大值,放到序列起始位置
对所有未排序的元素进行相同的操作,每轮只找最小/最大值
选择排序 vs 冒泡排序:
选择排序是记录这一轮遍历数组里的最小/最大值,遍历完成后将这个最值放在起始位置,只需要一次交换
冒泡排序是两两之间进行交换,需要交换很多次
时间复杂度:
最好情况:O(n^2)
平均情况:O(n^2)
最坏情况:O(n^2)
稳定性:不稳定
大小相同的两个元素,相对前后位置对换了
比如序列:{ 5, 8, 5, 2, 9 },一次选择的最小元素是2,然后把2和第一个5进行交换,从而改变了两个元素5的相对次序。
额外的空间:O(1)
1. 简单选择排序(基本) 1-1 取下标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function selectionSort (arr ) { let len = arr.length; for (let i=0 ;i<len-1 ;i++) { for (let j=i;j<len;j++) { let min = i; if (arr[min] > arr[j]) { min = j; } if (min !== i) { let temp = arr[i]; arr[i] = arr[min]; arr[min] = temp; } } } return arr; }
1-2 取值 和上面的方法类似,只不过这里是直接拿数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function selectionSort (arr ) { for (let i = 0 ; i < arr.length - 1 ; ++i) { let min = arr[i]; for (let j = i + 1 ; j < arr.length; ++j) { if (min > arr[j]) { let temp = min; min = arr[j]; arr[j] = temp; } } arr[i] = min; } return arr; }
==2. 堆排序(重点)==
堆概念
大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
小顶堆:每个节点的值都小于或者等于它的左右子节点的值。
具体操作
最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
创建最大堆:将堆中的所有数据重新排序
堆排序:移除位在第一个数据的根节点,并做最大堆调整的递归运算
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 function buildMaxHeap (arr ) { var len = arr.length; for (var i = Math .floor(len/2 )-1 ; i>=0 ; i--) { maxHeapify(arr, i); } }function maxHeapify (arr, i ) { var left = 2 *i+1 , right = 2 *i+2 , largest = i; if (left < len && arr[left] > arr[largest]) { largest = left; } if (right < len && arr[right] > arr[largest]) { largest = right; } if (largest !== i) { swap(arr, i, largest); maxHeapify(arr, largest); } } function swap (arr, i, j ) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } function heapSort (arr ) { buildMaxHeap(arr); for (var i = arr.length-1 ; i > 0 ; i--) { swap(arr, 0 , i); len--; maxHeapify(arr, 0 ); } return arr; }
3. 插入排序 从第二位开始抽
抽出一个数,往前一位从后往前以此进行比对,找到比这个数小的数时则放其后(其实不就是打扑克排牌时的做法嘛)
1)从第一个元素开始,该元素可以被认为已经被排序 2)取出下一个元素,在已经排好序的序列中从后往前扫描 3)直到找到小于或者等于该元素的位置 4)将该位置后面的所有已排序的元素从后往前依次移一位 5)将该元素插入到该位置 6)重复步骤2~5
时间复杂度:
最好情况:O(n) 输入的数组是升序排序
平均情况:O(n^2)
最坏情况:O(n^2) 输入的数组是降序排序
稳定性:稳定
相等元素的相对次序没有改变
额外的空间:O(1)
1. 直接插入排序(基本) 1 2 3 4 5 6 7 8 9 10 11 12 function InsertionSort (arr ) { for (let i=1 ;i<arr.length;i++){ let temp = arr[i]; let j = i - 1 ; while (j>=0 && arr[j]>arr[i]){ arr[j+1 ] = arr[j]; j--; } a[j+1 ] = b; } return arr }
2. 二分插入排序 使用二分查找法,实际上就是在上面的方法中更改了查找位置
在已排序序列中二分查找到第一个比它大的数的位置
时间复杂度:
最好情况:O(nlogn)
平均情况:O(n^2)
最坏情况:O(n^2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function binaryInsertSort (arr ) { for (var i=1 ;i<arr.length; i++) { var temp=arr[i],left=0 ,right=i-1 ; while (left<=right){ var mid= parseInt ((left+right)/2 ); if (temp<arr[mid]){ right = mid-1 ; }else { left=mid+1 ; } } for (var j=i-1 ;j>=left;j--){ arr[j+1 ]=arr[j]; } arr[left]=temp; } return arr; }
缩小增量排序
尽可能让小的值往前靠,让大的值靠后(避免大幅度的后移)
设置一个增量n,这个n为数组长度的1/2
相隔n个的元素视为同一组,在组内进行比较排序
再将n*1/2,重复2步骤,直到增量n为1(此时所有元素都是同一个组了)
(↓ 下面的代码是复制博客里的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function shellSort (arr ) { let interval = Math .floor(arr.length / 2 ); while (interval >= 1 ) { for (let i = interval; i < length; i++) { let temp = arr[i]; let j = i; while (arr[j - interval] > temp && j - interval >= 0 ) { arr[j] = arr[j - interval]; j -= interval ; } arr[j] = temp; } interval = Math .floor(interval / 2 ); } return arr; }
==4. 快速排序(重点)== 将数据分成两部分
一边的数据全都小于基准数,一边的数据全都大于基准数
按照这种方法再分别对这两部分的数据进行快速排序
第一轮之后的效果:
1. 有递归版本
递归是什么? ==> 套娃
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function quick (arr, left = 0 , right = arr.length - 1 ) { if (left >= right) return arr; var i = left, j = right, basic =i; while (i < j) { while (arr[j] >= arr[basic] && j > basic) j--; while (arr[i] <= arr[basic] && i < j) i++; var temp = arr[basic]; arr[basic] = arr[j]; arr[j] = temp; basic = i } quick(arr, left, basic - 1 ); quick(arr, basic + 1 , right); return arr; }
由于使用了递归,所以要有条件,不然会无限套娃,然后就崩了 =。=
2. 无递归版本(栈)
先来了解一下栈:大概可以比喻成 ==> 把东西往容器里放,拿出来的时候只能是最后放的那个
栈声明一些方法。
push(element(s)): 添加一个(或几个)新元素到栈顶
pop():移除栈顶的元素,同时返回被移除的元素
peek():返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它)
isEmpty():如果栈里没有任何元素就返回true,否则返回false
clear():移除栈里的所有元素
size():返回栈里的元素个数。该方法和数组的length 属性很类似
模拟栈,将待排序数组的[left,right]保存到数组中,循环取出进行快排
(↓贴个代码,看懂了,不会写)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function quickSort (arr ) { let stack = []; stack.push([0 , arr.length - 1 ]); while (stack.length) { let list = stack.pop(); let i = left = list[0 ]; let j = right = list[1 ]; let mid = arr[(i + j) >> 1 ]; do { while (arr[i] < mid) ++i; while (arr[j] > mid) --j; if (i <= j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; ++i; --j; } } while (i <= j); if (i < right) stack.push([i, right]); if (left < j) stack.push([left, j]); } return arr; }
==5. 归并排序(重点) == 分而治之
将数组分组至两两比较,最后直到所有元素都被分到一组里
时间复杂度:
最好情况:O(nlogn)
平均情况:O(nlogn)
最坏情况:O(nlogn)
稳定性:稳定
额外的空间:O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 mergeSort = (arr ) => { if (arr.length<=1 ){ return arr; }else { var mid=Math .floor(arr.length/2 ); var left=arr.slice(0 ,mid); var right=arr.slice(mid); left = mergeSort(left); right = mergeSort(right); var temp=[]; while (left.length && right.length){ if (left[0 ] < right[0 ]){ temp.push(left.shift()); }else { temp.push(right.shift()); } } return temp.concat(left,right); } }
6. 基数排序(了解) 从个位数开始,依次放入0~9的桶中,再依次拿出来排序;十位、千位……重复上述操作,一直排到最高位为止
时间复杂度:
最好情况:O(n+k)
平均情况:O(n+k)
最坏情况:O(n+k)
稳定性:稳定
额外的空间:O(n+k)
(贴一个大佬的代码,写法有点帅)
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 radixSort = (arr ) => { if (arr.length < 2 ) { return arr; } let maxDigit = String (parseInt (Math .max(...arr))).length let mod = 10 ; let dev = 1 ; let counter = []; for (let i = 0 ; i < maxDigit; i++, dev *= 10 , mod *= 10 ) { for (let j = 0 ; j < arr.length; j++) { let bucket = parseInt ((arr[j] % mod) / dev); counter[bucket] = counter[bucket] == null ? [] : counter[bucket]; counter[bucket].push(arr[j]); } let pos = 0 ; for (let j = 0 ; j < counter.length; j++) { let value = null ; if (counter[j]!=null ) { while ((value = counter[j].shift()) != null ) { arr[pos++] = value; } } } } return arr; }