let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); insort(arr); console.log(arr); function insort(arr){ for(let i=1;i<arr.length;i++){//从第二个元素开始依次处理 let temp=arr[i];//待插入值 let j=i-1;//从其前一个位置开始依次比较 while(j>=0&&arr[j]>temp){//若比待插入值大,依次向后移 arr[j+1]=arr[j]; j--; } arr[j+1]=temp;//将待插入值插入正确位置 } return arr; }
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); halfinsort(arr); console.log(arr); function halfinsort(arr){ for(let i=1;i<arr.length;i++){//从第二个元素开始依次处理 let temp=arr[i];//待插入值 // 折半查找0,i-1范围内temp应处于的位置low let low=0,high=i-1; while(low<=high){ let mid=Math.floor((low+high)/2); if(arr[mid]>temp){ high=mid-1; }else{ low=mid+1; } } // 将low,i-1的值依次后移一位 for(let j=i;j>low;j--){ arr[j]=arr[j-1]; } arr[low]=temp;//将待插入值插入正确位置 } return arr; }
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); shellsort(arr); console.log(arr); function shellsort(arr){ let d=Math.floor(arr.length/2);// 第一次增量 while(d>=1){// 增量最小至1 console.log(d); // 此增量下shell排序,从d+1开始 for(let i=d;i<arr.length;i++){ // <=i并且间隔为d的一组进行插入排序 let j=i-d; let temp=arr[i];//待插入值 while(j>=0&&temp<arr[j]){//若比待插入值大,依次向后移 arr[j+d]=arr[j]; j=j-d; } arr[j+d]=temp;//将待插入值插入正确位置 console.log(arr); } d=Math.floor(d/2);// 更新增量 } return arr; }
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); bubsort(arr); console.log(arr); function bubsort(arr){ let flag=false; for(let i=0;i<arr.length-1;i++){//最多len-1趟排序,i表示趟数 flag=false;//标志位 for(let j=0;j<arr.length-i-1;j++){//一趟冒泡排序,两两交换,最大沉底 let temp=arr[j]; if(temp>arr[j+1]){ arr[j]=arr[j+1]; arr[j+1]=temp; flag=true; } } // 如果flag不为true,表示已是正确排序,不需要继续下一趟排序 if(!flag) break; } return arr; }
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); quicksort(arr,0,arr.length-1); console.log(arr); // 分治法 function quicksort(arr,left,right){ if(left>=right) return; let index=quick(arr,left,right); quicksort(arr,left,index-1);// 处理左边 quicksort(arr,index+1,right);// 处理右边 } function quick(arr,left,right){ // 子表的一趟快速排序 let x=arr[left];//基准值 let i=left; let j=right; // 当i=j时,已按当前基准分好,获得基准正确位置 while(i!=j){ // 从右向左扫描,直至到比基准小,i<j保证最终i=j while(arr[j]>=x&&i<j){ j--; } // 从左向右扫描,直至到比基准大,i<j保证最终i=j while(arr[i]<=x&&i<j){ i++; } console.log(i+" "+j); // 交换两者 if(i<j){ let temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } } // 将基准放到正确位置 arr[left]=arr[i]; arr[i]=x; return i; }
与冒泡法区别:每一趟记录最小值位置,一次交换到位
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); selectsort(arr); console.log(arr); function selectsort(arr){ for(let i=0;i<arr.length-1;i++){//最多len-1趟排序,i表示趟数 var index=i;// 此趟最小值下标 for(let j=i+1;j<arr.length;j++){// 遍历 if(arr[j]<arr[index]) index=j; // 更新最小值下标 } // 交换将此趟最小值放在正确位置 if(i!=index){ let temp=arr[i]; arr[i]=arr[index]; arr[index]=temp; } } return arr; }
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); heapsort(arr); console.log(arr); function heapsort(arr){ // 1.建立大根堆 // 从最后一个非叶节点(arr.len/2-1)开始依次调整,直至所有非叶节点都调整完 for(let i=arr.length/2-1;i>=0;i--){ adjustHeap(arr,i,arr.length); } // 2.调整堆结构,即交换顶元素0与末尾元素j for(let j=arr.length-1;j>0;j--){ swap(arr,0,j);// 交换顶元素0与末尾元素j adjustHeap(arr,0,j);// 重新调整顶元素位置 } return arr; } // 调整大根堆 function adjustHeap(arr,index,len){ let temp=arr[index];// 当前元素 for(let i=2*index+1;i<len;i=i*2+1){// 从左子节点开始,直至当前节点所有子节点调整完成 // 若左子节点小于右子节点,i为index节点的子节点值最大的序号 if(i+1<len&&arr[i]<arr[i+1]){ i++; } // 若子节点最大值大于父节点,父节点更新值,重置新的调整点index,否则调整到位跳出循环 if(arr[i]>temp){ arr[index]=arr[i]; index=i; }else { break; } } arr[index]=temp;// 将当前元素值放在正确位置 } // 交换元素 function swap(arr,a,b){ let temp=arr[a]; arr[a]=arr[b]; arr[b]=temp; }
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); mergesort(arr,0,arr.length-1); console.log(arr); // 分治法 function mergesort(arr,left,right){ if(left<right){ let mid=Math.floor((left+right)/2); mergesort(arr,left,mid);// 左边归并排序,使得左子序列有序 mergesort(arr,mid+1,right);// 右边归并排序,使得右子序列有序 merge(arr,left,mid,right);// 将两个有序子数组合并操作 }else{ return; } } function merge(arr,left,mid,right){ console.log(left+" "+mid+" "+right); let temparr=[];//临时数组,合并结果 let i=left;//左序列指针 let j=mid+1;//右序列指针 let t=0;//临时数组指针 // 依次比较将最小的按顺序放入临时数组temparr while(i<=mid&&j<=right){ if(arr[i]<=arr[j]){ temparr[t++]=arr[i++]; }else{ temparr[t++]=arr[j++]; } } //若左边剩余,将左边剩余元素填充进temp中 while(i<=mid){ temparr[t++]=arr[i++]; } //若右边剩余,将右序列剩余元素填充进temp中 while(j<=right){ temparr[t++]=arr[j++]; } t=0; //将temparr中的元素全部拷贝到原数组中 while(left<=right){ arr[left++]=temparr[t++]; } }
let arr=[15,9,8,1,4,11,7,12,13,6,5,3,16,2,10,14]; console.log(arr); radixsort(arr,0,arr.length-1,2); console.log(arr); function radixsort(arr,left,right,digit){ let radix=10;// 基数0-9 let i=0,j=0; let rows=[];// 存放0-9各自的数据 let cols=[];// 一次排序结果 // d为arr中元素最大位数,从低位到高位进行d次排序 for(let d=1;d<=digit;d++){ // 初始化rows[0-9]数据为0 for(i=0;i<radix;i++){ rows[i]=0; } // 遍历所有元素d位,记录[0-9]各自个数 for(i=left;i<=right;i++){ j=getDigit(arr[i],d); console.log(arr[i]+" "+j); rows[j]++; } console.log(rows); // 将[0-9]更新为右边界索引 for(i=1;i<radix;i++){ rows[i]=rows[i-1]+rows[i]; } console.log(rows); // 从右向左将数据依次装入cols[] for(i=right;i>=left;i--){ j=getDigit(arr[i],d);// 求出d位数数值 cols[rows[j]-1]=arr[i];// 放入结果对应位置,rows[j]-1为其索引值 rows[j]--;// 此位数据索引减一 } console.log(cols); // 将此次排序结果更新到arr for(i=left,j=0;i<=right;i++,j++){ arr[i]=cols[j]; } // console.log(arr); } return arr; } // 获取x第d位数,比如x=123,d=1,则返回3 function getDigit(x,d){ let y=Math.floor(x/Math.pow(10,(d-1)))%10; return y; }
1.稳定性比较
- 除shell排序之外的所有插入排序、冒泡排序、归并排序、基数排序都是稳定排序
shell排序、快速排序、选择排序(直接选择、堆)为非稳定排序
2.时间性能比较
O(n2):除shell外的所有插入排序、冒泡排序、直接选择排序
- O(nlog2n):快速排序、堆排序、归并排序、基数排序
- 一般结论:快速排序最好,但最差情况下,不如堆排序和归并排序;n很小时,快速排序很差
- 当序列中的记录基本有序或n较小时,直接插入排序最佳,最好情况O(n)
3.说明
- 不存在绝对最好的排序方法。实际应结合具体情况选择甚至多种方式结合
- 基于比较的排序算法(基数排序不是)最坏执行时间一定不小于O(nlog2n)
转载请标明出处:【数据结构】常见排序算法
文章来源: 【数据结构】常见排序算法