经典排序算法

74 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

常见的排序方法大致可分为两类
比较类排序:通过比较来决定元素间的相对次序,由于时间复杂度不能突破,因此也称非线性时间比较类排序
非比较类排序:不能通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间
算法复杂度
在这里插入图片描述
一,冒泡排序
核心思想:
相邻两个元素比较,将较小的数字放到左侧,以此类推,这样最后那个元素就是最大的数
算法描述:
比较相邻的元素,如果第一个比第二个大,就交换它们两个,对每一对相邻元素作同样的工作,直到最后一对,这样最后面那个数就是最大的数,针对以上所有的元素重复作这个操作,除了最后一个,直到排序完成
代码实现:

function bubbleSort(arr) 
{    
varlen = arr.length;    
for(vari = 0; i < len - 1; i++) 
{        
for(varj = 0; j < len - 1 - i; j++) 
{            
if(arr[j] > arr[j+1]) 
{       
// 相邻元素两两对比                
vartemp = arr[j+1];       
// 元素交换                
arr[j+1] = arr[j];                
arr[j] = temp;            
}        
}    
}   
returnarr;
}

二,选择排序
核心思想:
首先在未排序的序列中找到最小的那个元素,将它放到起始位置,再从剩余未排序的序列中找到最小的那个元素,放到已排序序列的末尾,以此类推,直到所有的元素都排序完成。
算法描述:
初始状态:无序区为R[1…n],有序区为空;
第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。
该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
n-1趟结束,数组有序化了。
代码实现:

function selectionSort(arr) 
{    
varlen = arr.length;    
varminIndex, temp;    
for(vari = 0; i < len - 1; i++) 
{        
minIndex = i;        
for(varj = i + 1; 
j < len; j++) 
{            
if(arr[j] < arr[minIndex]) 
{    
// 寻找最小的数                
minIndex = j;                
// 将最小数的索引保存            
}        
}        
temp = arr[i];       
arr[i] = arr[minIndex];        
arr[minIndex] = temp;    
}    
returnarr;
} 

三:插入排序
核心思想:
通过构建有序序列,在已排序的序列中从后向前扫描,找到相应位置并插入
算法描述:
从第一个元素开始,该元素可以认为已经被排序;
取出下一个元素,在已经排序的元素序列中从后向前扫描;
如果该元素(已排序)大于新元素,将该元素移到下一位置;
重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
将新元素插入到该位置后;
重复步骤2~5。
代码实现:

function insertionSort(arr) 
{    
varlen = arr.length;    
varpreIndex, current;    
for(vari = 1; i < len; i++) 
{        
preIndex = i - 1;        
current = arr[i];        
while(preIndex >= 0 && arr[preIndex] > current) 
{            
arr[preIndex + 1] = arr[preIndex];            
preIndex--;        
}        
arr[preIndex + 1] = current;    
}    
returnarr;
}

四,希尔排序
核心思想:
是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
算法描述:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
代码实现:

function shellSort(arr) 
{    
varlen = arr.length;    
for(vargap = Math.floor(len / 2); 
gap > 0; 
gap = Math.floor(gap / 2)) 
{        
// 注意:这里和动图演示的不一样,动图是分组执行,实际操作是多个分组交替执行        
for(vari = gap; i < len; i++) 
{            
varj = i;            
varcurrent = arr[i];            
while(j - gap >= 0 && current < arr[j - gap]) 
{                 
arr[j] = arr[j - gap];                 
j = j - gap;            
}            
arr[j] = current;        
}    
}    
returnarr;
}

五,归并排序
核心思想:
该算法采用的是分冶法,将已有序的子序列合并,得到完全有序的序列,即先让每个子序列有序,再使子序列间有序,若将两个有序表合并成一个有序表,称2路归并
算法描述:
把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。
代码实现:

function mergeSort(arr) 
{    
varlen = arr.length;    
if(len < 2) 
{        
returnarr;    
}    
varmiddle = Math.floor(len / 2),        
left = arr.slice(0, middle),        
right = arr.slice(middle);    
returnmerge(mergeSort(left), mergeSort(right));
} 
function merge(left, right) 
{    
varresult = [];     
while(left.length>0 && right.length>0) 
{        
if(left[0] <= right[0]) 
{            
result.push(left.shift());        
}
else
{            
result.push(right.shift());        
}    
}     
while(left.length)        
result.push(left.shift());     
while(right.length)        
result.push(right.shift());     
returnresult;
}

六,快速排序
核心思想:
先选择一个元素作为基准,通过一趟排序将待排元素分成两部分,一部分是大于基准的数,一部分是大于基准的数,然后分别对这两部分进行排序,以达到整个序列有序
算法描述:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
代码实现:

function quickSort(arr, left, right) 
{    
varlen = arr.length,        
partitionIndex,        
left =typeofleft !='number'? 0 :
left,        
right =typeofright !='number'? len - 1 : 
right;     
if(left < right) {        
partitionIndex = partition(arr, left, right);        
quickSort(arr, left, partitionIndex-1);        
quickSort(arr, partitionIndex+1, right);    
}    
returnarr;
} 
function partition(arr, left ,right) {    
// 分区操作    
varpivot = left,                     
// 设定基准值(pivot)        
index = pivot + 1;    
for(vari = index; i <= right; i++) {        
if(arr[i] < arr[pivot]) {            
swap(arr, i, index);            
index++;        
}           
}    
swap(arr, pivot, index - 1);    
returnindex-1;
} 
function swap(arr, i, j) {    
vartemp = arr[i];    
arr[i] = arr[j];    
arr[j] = temp;
}

七,堆排序
核心思想:
堆积是一个近似完全二叉树的结构,并同时满足堆积性质(子节点的键值或者索引总是小于(大于)它的父节点)
算法描述:
将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
代码实现:

varlen;   
// 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量 
function buildMaxHeap(arr) {  
// 建立大顶堆    
len = arr.length;    
for(vari = Math.floor(len/2); i >= 0; i--) {        
heapify(arr, i);    
}
} 
function heapify(arr, i) {    
// 堆调整    varleft = 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);        
heapify(arr, largest);    
}
}
function swap(arr, i, j) {    
vartemp = arr[i];    
arr[i] = arr[j];    
arr[j] = temp;
} 
function heapSort(arr) {    
buildMaxHeap(arr);     
for(vari = arr.length - 1; i > 0; i--) 
{        
swap(arr, 0, i);        
len--;        
heapify(arr, 0);    
}    
returnarr;
}