亲爱的各位骚年,之前我介绍了三种基本排序方法的JavaScript实现,今天我们来讲一下三种更加高级的排序算法——希尔排序、归并排序和快速排序。除此之外还有堆排序,但堆排序需要用到树,而树这种数据结构用javascript实现起来比较麻烦,不能把堆排序的优势凸显出来,因此这篇博客就先不讲堆排序。首先来看一下测试类
function CArray(elements){
this.dataStore=[];
this.elements=elements;
this.insert=insert;
this.toString=toString;
this.clear=clear;
this.setData=setData;
this.hillSort=hillSort;//希尔
this.mergeSort=mergeSort;//归并
this.fastSort=fastSort;//快排
for(var i=0;i<elements;i++){
this.dataStore[i]=i;
}
}
function setData(){
for(var i=0;i<this.elements;i++){
this.dataStore[i]=Math.floor(Math.random()*(this.elements+1));
}
}
function clear(){
for(var i=0;i<this.elements;i++){
this.dataStore[i]=0;
}
}
function insert(element){
this.dataStore.push(element);
}
function toString(){
var restr="";
for(var i=0;i<this.elements;i++){
restr+=this.dataStore[i]+" ";
if((i+1)%10==0){
restr+="\n";
}
}
return restr;
}
//交换数组中两个元素的位置
function swap(arr,index1,index2){
var temp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
一、希尔排序
希尔排序的工作原理是——通过定义一个间隔序列来表示在排序过程中进行比较的元素之间有多远的间隔。比如现在有10个元素的无序数组
[10,5,2,13,50,5,19,20,33.11]
如果我们给定的间隔序列是(5,3,1),则把元素分为5组
(10,5)、(5,19)、(2,20)、(13,33)、(50,11)
分别进行排序,完成排序后再按照间隔3个元素重新分组,每组又进行排序,以此类推,直到间隔为1。最后一次排序的间隔必须是1,这是最相邻元素进行标准的插排。
那么为什么希尔排序会插排效率更高呢?这是因为从第一次排序开始,每次排序都会有越来越多的元素本身就在正确的位置,这样就不必对很多元素进行交换了。
现在先来看一个栗子,在这个栗子中,间隔序列是预先设置好的(5,3,1)
function hillSort(){
var gaps=[5,3,1];
//该层循环控制间隔的取值
for(var subscript=0;subscript<gaps.length;subscript++){
for(var interval=gaps[subscript];interval<this.elements;interval++){
for(var temporary=interval;temporary>=gaps[subscript]&&this.dataStore[temporary-gaps[subscript]]>this.dataStore[temporary];temporary-=gaps[subscript]){
swap(this.dataStore,temporary,temporary-gaps[subscript]);
}
}
}
}
在这段代码中,外层循环的作用是调整间隔的取值、中间层循环的作用是选取当前元素、内层循环的作用是找到在当前元素一个间隔之前的元素并排序,如果发生交换的话还需要检查交换顺序后较小元素与之前一个间隔元素的大小。在内循环中temporary-=gaps[subscript]不可以写成temporary-=interval,因为interval的初试值等于当前间隔,但interval的含义是当前元素下标,它不一定会与当前间隔相等。
现在可以给大家说明的是希尔排序所用到的间隔序列可以是预先设置好的静态序列,也可以是动态生成的序列,而且两种序列效率是差不多的,其实对于静态序列,只要不只有1,其他无论怎么设置序列里的元素,实际的效率都差不多。不过对于那些有选择困难症的同学,动态生成间隔序列而不需要自己去选择序列里的元素绝对是一个不二的选择。下面就来看一下使用动态间隔的希尔排序
function hillSort(){
var N=this.elements;
var gaps=1;
for(;gaps<N/3;){
gaps=3*gaps+1;
};
for(;gaps>=1;){
console.log("间隔:"+gaps);
for(var interval=gaps;interval<this.elements;interval++){
for(var temporary=interval;temporary>=0&&this.dataStore[temporary-gaps]>this.dataStore[temporary];temporary-=gaps){
swap(this.dataStore,temporary,temporary-gaps);
}
}
gaps=(gaps-1)/3;
}
}
二、归并排序
归并排序是另外一种高级排序算法,其核心思想是“ 分而治之”。比如这里有一个无序数组,我们先把相邻的两个元素进行排序,此时原数组可以看成由若干个拥有两个元素的子数组组成(或许会有一个元素剩下),然后再把相邻的两个子数组按照内部元素大小的顺序进行融合,以此类推。很明显,我们可以看到归并排序中有递归的思想,我们就用递归实现它
//这个函数的目的是为了改变原数组
function mergeSort(){
this.dataStore=mergeGo(this.dataStore);
}
//将数组拆分为两部分
function mergeGo(arr){
var len=arr.length;
if(len>1){
var index=Math.floor(len/2);
var left=arr.slice(0,index);
var right=arr.slice(index);
return fuseArray(mergeGo(left),mergeGo(right));
}else{
return arr;
}
}
//将两个数组按照大小顺序融合
function fuseArray(arrA,arrB){
var arr=[];
for(;arrA.length&&arrB.length;){
if(arrA[0]<arrB[0]){
arr.push(arrA.shift());
}else{
arr.push(arrB.shift());
}
};
if(arrA.length){
return arr.concat(arrA);
}else{
return arr.concat(arrB);
};
}
三、快速排序
下面就要讲到目前我们所接触到的最快的排序算法——快速排序。快速排序可以看出改良的插入排序,它也有递归的思想。具体来说就是先找一个最开始的“基准”,通常选取中位数,也可以选其他位置上的数,下面的栗子中我选第一个元素作为基准,然后遍历数组,小于基准的放在lesser数组,大于基准的放在greater数组,对lesser与greater进行上述操作,直到排序结束
function fastSort(){
this.dataStore=fastGo(this.dataStore);
}
function fastGo(arr){
if(arr.length==0){
return arr;
}else{
var lesser=[];
var greater=[];
var pivot=arr[0];
for(var n=1;n<arr.length;n++){
if(arr[n]<=pivot){
lesser.push(arr[n]);
}else{
greater.push(arr[n]);
}
}
//将lesser、基准与greater连接成为新数组
return fastGo(lesser).concat(pivot,fastGo(greater));
}
}