【前端】js写十种排序算法(未完待续…)
最坏的时间复杂度:
O(n^2):直接插入、冒泡、选择、快速、桶
O(nlogn):堆、归并、希尔
O(n+k):计数
O(n*k):基数
最好的时间复杂度:
O(n):直接插入、冒泡
O(nlogn):堆、归并、希尔、快速
O(n+k):计数、桶
O(n*k):基数
O(n^2):选择
空间复杂度:
O(1):直接插入、冒泡、选择、希尔、堆
O(n):归并
O(logn):快速
O(n+k):基数、桶
O(k):计数
稳定性:(判断相同值在排序后是否会交换前后次序,稳定则相同值不变前后顺序)
不稳定:快速排序、希尔排序、选择排序、堆排序。(谐音:情绪不稳定, 快些选一堆朋友)
其余排序稳定。
非比较类排序:计数、基数、桶(除这仨以外,其他均比较类)
注:以下都按由小到大排序举例 ↓,eg:5 38 15 46 2 12 76 52
1、直接插入排序
默认第一个元素已排序,将未排序数在已排序数从后往前比较大小找到对应位置插入。
最好O(n),最坏 O(n^2)。
(eg:a>b, b插入到a前→ba,再把c逐个与ab比较,插入对应大小位置,每趟如此)
单趟eg:5 38 15 46 2 12 76 52(第一趟确定第一个数)
const insertSort= (arr) => {for(let j = 1; j< arr?.length; j++) {let i = j - 1;let temp = arr[j];while(i >= 0 && arr[i]>temp) { //将大于未排序第一个元素的都往后挪arr[i+1] = arr[i];i--;}arr[i+1] = temp; //在小于未排序元素的+1位置插入}return arr; }
2、冒泡排序
每次将相邻数两两比较,反序则交换。
最好O(n),最坏 O(n^2)
(eg:a>b, ab互换为ba,再把a与c进行比较,每趟循环能确定末尾数位置)
单趟eg:5 15 38 2 12 46 52 76
const bubbleSort = (arr) => {for(let i = 0; i<arr?.length; i++) {for(let j = 0; j < arr?.length-1-i; j++ ) {if(arr[j] > arr[j+1]) {[arr[j], arr[j+1]] = [arr[j+1],arr[j]]; //1、解构赋值交换// 2、var temp = arr[j+1];arr[j+1]=arr[j];arr[j]=temp; // 临时变量交换// 3、arr[j+1] = arr[j+1] ^ arr[j]; //XOR运算交换// arr[j] = arr[j+1] ^ arr[j];// arr[j+1] = arr[j+1] ^ arr[j];}}}return arr }
3、简单选择排序
每次在未排序中选择最小的元素,与未排序的第一个元素进行交换。
最好、最坏 O(n^2),性能略优于冒泡。
(eg:abc最小是c,先放c,再从ab选最小,如果a小,就ca,再继续比较剩下未排序……,每趟确定最开头数位置)
单趟eg:2 38 15 46 5 12 76 52(最小数和第一个数交换)
const selectSort= (arr) => {if(arr?.length <= 1) return arr;for(let i = 0; i< arr?.length; ) {let temp = i;for(let j = i; j< arr?.length-1; j++) {temp = arr[j] > arr[j+1] ? j+1 : j;}if(temp !== i) {[arr[i],arr[temp]] = [arr[temp],arr[i]];}}return arr; }
4、快速排序
选择一个基准元素(通常选第一个),将数组与基准比大小分成两部分,再递归排序两部分。
最好O(nlogn),最坏 O(n^2)。
(eg:abcde选a基准,ab比较,b大于a继续下一个,c小于a,bc位置互换acbde,d大于a,e小于啊,eb互换acebd,全部走完,将基准与最后一个小于互换ceabd,每趟确定基准元素位置)
单趟eg:(基准元素5)
5 2 15 46 38 12 76 52(遇到比5小,将比较的第一个数和当前小的数互换)
2 5 15 46 38 12 76 52(基准元素与最后一个比其小的数互换)
5、希尔排序
将待排序元素按增量分成多份分别进行直接插入排序,基本有序后进行全体直接插入排序。
最好、最坏O(nlogn)。
单趟eg:(按增量为每次n/2来)
5 38 15 46 2 12 76 52(增量8/2=4,按步长4进行分组)
2 12 15 46 5 38 76 52(分组内按直接插入排序,下一趟增量4/2=2分组情况:{2,15,5,76}和{12,46,38,52}组内插入排,再到增量2/1=1全部插入排序)
6、堆排序
按顺序堆成二叉树,从后往前挨个比较,堆大顶堆,取下堆顶放在排序末尾,再将最后的叶子挪到堆顶,每趟循环能确定末尾数位置。
最好、最坏O(nlogn)。
单趟eg:
5 5 5 76
38 15 ----→ 38 76 ----→ 52 76 ---→ 52 5
46 2 12 76 52 2 12 15 38 2 12 15 38 2 12 15
52 46 46 46
取下76放在排序最末尾,将46移到堆顶
7、归并排序
将长度为n的序列分成n/2两个子序列,分别对子序列进行归并,最后将两个排序好的子序列归并成最终序列。(递归拆分排序后合并→由上到下递归,由下到上迭代)
最好、最坏O(nlogn)。
单趟eg:5 38 15 46 2 12 52 76(先两个互相比较,第二趟再分{5,38,15,46}和{2,12,52,76}分别排序……)
8、计数排序
要求输入的数据必须在确定范围内的整数,将输入的数据值转化为键存储在额外开辟的数组空间中。
最好、最坏O(n+k)。
(eg:找出待排序数组的最大、最小元素,统计每个值(eg:i)出现的次数,存入开辟的新数组的第i项,对所有计数累加,从第一个元素开始,每一项和前一项相加,将每个元素)
9、基数排序
从数字的低位到高位先排序后收集,直到最高位。
最好、最坏O(n*k)。
(eg: ① 获取数组的最大数,获取到最大数的位数,② 从个位开始存入hash,hash从0-9,③ 存完以后从0-9开始挨个收集拼起来,④ 再从十位继续,以此类推)
单趟eg:(按hash存,先按个位)
2→ 2→12→52
5→ 5→15
6→46→76
8→38
最后挨个拼接 2 12 52 5 15 46 76 38(下一趟再按十位)
10、桶排序
设置一个数组当空桶,挨个遍历放进桶里,对不是空的桶进行排序,并进行拼接。
(eg:① 分配每个桶的区间,② 遍历数组挨个对应放入桶,③ 每个桶分别排序,④ 挨个遍历桶输出)
最好O(n+k),最坏 O(n^2)。
eg:(按hash存)
0→ 2 → 5
1→12→15
3→38
4→46
5→52
7→76
