吼 背景是有个同学面试京东被问了说各种排序,一想想自己也是背了那么久的,然后又记不得,没有系统的整理过;
评价术语:
- 时空复杂度;执行耗时和运行所需的内存空间;
- 稳定:在排序中,如果a=b,稳定则是指这两的index相对位置不改变;记住(快排和堆,选择排序都不稳定!)
有一张图用了很久的图了 反正我看了无数遍也记不住
一般排序会分为插入(直接插入,希尔)、选择(简单选择,堆)、交换(冒泡,快排)、归并和基数
插入排序:
直接插入排序
很容易理解啊,打扑克排序哈哈;看代码 动画看参考文献1
function insertSort(arr){
for(let i=0;i<arr.length;i++){
var key = arr[i];
var j = i - 1;
while(j>=0 && arr[j]>key){
arr[j+1] = arr[j];
j--;
}
arr[j+1]=key
}
return arr
}
如果改进的话,可以把和前面已排序的插入过程变成二分;
时间复杂度:平均(n2) 最好(n) 最差(n2) 稳定
希尔:
可以突破n2时间复杂度的算法,改进了插入的过程,通过设置间隔序列实现;举个例子:也就是第一次1和5比,2和6比,3和7比;然后完了把增量减半;继续;一般不考
代码wip:
//希尔排序,自组采用直接插入排序 针对有序序列在插入时采用交换法
function shellSort(arr){
//逐步降低步长直至为1为止
for(let shellWidth = arr.length/2;shellWidth>0;shellWidth/2){
//根据步长,将数组进行分组,并使用插入排序法进行交换排序
//从增量大小的那组数据进行插入排序
for(let atom =shellWidth; atom<arr.length; atom++ ){
//atom-shellWidth 表示和该元素同组的隔壁相邻的元素,对于同一组的元素,进行插入排序
while(atom-shellWidth>0&&arr[atom-shellWidth]>arr[atom]){
swap(arr,atom-shellWidth,atom);
atom=atom-shellWidth;
}
}
}
}
选择排序:
简单选择排序
找到数组中最小的元素,放到素组最左边;需要n-1趟排序
function chooseSort(arr){
for(let i=0; i<arr.length; i++){
minIndex = i;
for(let j=i+1; j<arr.length; j++){
if(arr[j]<arr[minIndex]){
minIndex = j
}
}
[arr[i],arr[minIndex]] = [arr[minIndex],arr[i]]
}
console.log(arr)
}
时间复杂度,平均最好最坏都是n2,不稳定
堆排序
大顶堆就是根节点大于左右子节点,那么先把数组构建一个大顶堆,然后把根节点和最后一个进行交换,到此最大值就在最后了;然后n-1继续构造;以此类推;
这里边有几个数学知识 背一下吧: 第一个叶子节点下标:Math.floor(arr.length/2 - 1)
参考:www.jianshu.com/p/90bf2dcd6…
nlogn 不稳定
交换排序:(重点考点)
冒泡:
示意图:cuijiahua.com/blog/2017/1…
对比
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len; 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;
}
n2最坏和平均 最好的时候是inplace的时候n;是稳定的排序;
可以通过一个flag来进行改进;如果进行某一趟排序时并没有进行数据交换,则说明所有数据已经有序,可立即结束排序,避免不必要的比较过程。
改进之后的代码:
function bubbleSort(arr){
for(let i = 0; i < arr.length; i++) {
let flag = true
for(let j = 0; j < arr.length - i - 1; j++) {
if(arr[j] > arr[j+1]) {
flag = false
let temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
// 这个flag的含义是:如果`某次循环`中没有交换过元素,那么意味着排序已经完成
if(flag)break;
}
return arr
}
快排:
nlogn! 不稳定! 有nlogn的空间复杂度
- 选取基准元素
- 比基准元素小的元素放到左边,大的放右边
- 在左右子数组中重复步骤一二,直到数组只剩下一个元素
- 向上逐级合并数组
优化:开了俩内存,不太行哦;
function quickSort(arr, left, right) { //这个left和right代表分区后“新数组”的区间下标,因为这里没有新开数组,所以需要left/right来确认新数组的位置
if (left < right) {
let pos = left - 1 //pos即“被置换的位置”,第一趟为-1
for(let i = left; i <= right; i++) { //循环遍历数组,置换元素
let pivot = arr[right] //选取数组最后一位作为基准数,
if(arr[i] <= pivot) { //若小于等于基准数,pos++,并置换元素, 这里使用小于等于而不是小于, 其实是为了避免因为重复数据而进入死循环
pos++
let temp = arr[pos]
arr[pos] = arr[i]
arr[i] = temp
}
}
//一趟排序完成后,pos位置即基准数的位置,以pos的位置分割数组
quickSort(arr, left, pos - 1)
quickSort(arr, pos + 1, right)
}
return arr //数组只包含1或0个元素时(即left>=right),递归终止
}
归并
参考:
[1] juejin.cn/post/684490…
[2]juejin.cn/post/684490…