常见排序算法
插入排序
直接插入
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;
}
Shell排序
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)