最近学习算法,先来总结一下复杂度为O(nlog(n))的快速排序,用js封装了一个quickSort对象,提供六种快速排序方法:
(1)单路快排:递归版,非递归版;
(2)双路快排:递归版,非递归版;
(3)三路快排:递归版,非递归版。
概念简介:
1. 单路快排从一个方向开始遍历,将标量值置于正确的位置,即右边的所有值都比标量值大,左边的所有值都比标量值小,等值可置于任意一端。
2. 双路快排是我最早接触的快速排序算法,之前也实现了一版,但是由于当时理解的不是很到位,存在多次不必要的数据交换。并没有完全展现双路快排的思想:从两个方向同时遍历,一次交换会将两个数置于正确的端。
3. 三路快排对于含有大量等值的数据列能够在每趟排序过程中将所有等于标量值的数据置于正确的位置,进而明显缩小排序区间,提高排序效率。
算法实现:
(1)单路快排递归版
var qsort1 = function(arr,lt,gt){ if(lt >= gt){ return; } var v = arr[lt], orilt = lt, origt = gt; lt++; gt++; while(lt < gt){ if(arr[lt] <= v){ lt++; }else{ this.swap(arr,lt,gt-1); gt--; } } this.swap(arr,orilt,lt-1); lt--; qsort1(arr,orilt,lt-1); qsort1(arr,gt,origt);};
(2)单路快排非递归版
var qsort1nr = function(arr){ if(arr.length <= 0){ return; } var tmplt = []; var tmpgt = []; tmplt.push(0); tmpgt.push(arr.length -1); while(tmplt.length > 0){ var lt = tmplt.shift(); var gt = tmpgt.shift(); if(lt >= gt){ return; } var v = arr[lt], orilt = lt, origt = gt; lt++; gt++; while(lt < gt){ if(arr[lt] <= v){ lt++; }else{ this.swap(arr,lt,gt-1); gt--; } } this.swap(arr,orilt,lt-1); lt--; if(orilt < lt-1){ tmplt.push(orilt); tmpgt.push(lt-1); } if(origt > gt){ tmplt.push(gt); tmpgt.push(origt); } }}
(3)双路快排递归版
var qsort2 = function(arr,lt,gt){ if(lt < gt){ var k = _qsort2(arr,lt,gt); qsort2(arr,lt,k); qsort2(arr,k+1,gt); }};var _qsort2 = function(arr,lt,gt){ var v = arr[lt]; var k = lt; while(lt < gt){ while(arr[lt+1] <= v && lt < gt){ lt++; } while(arr[gt] > v && gt > lt+1){ gt--; } if(lt+1 < gt){ this.swap(arr,lt+1,gt); lt++; gt--; }else{ break; } } this.swap(arr,k,lt); return lt;}
(4)双路快排非递归版
var qsort2nr = function(arr){ if(arr.length <= 0){ return; } var tmplt = [],tmpgt = []; tmplt.push(0); tmpgt.push(arr.length -1); while(tmplt.length > 0){ var lt,gt; lt = tmplt.shift(); gt = tmpgt.shift() console.log(lt+'=>'+gt); if(lt >= gt){ continue; } var v = arr[lt]; var k = lt,orilt=lt,origt=gt; while(lt < gt){ while(arr[lt+1] <= v && lt < gt){ lt++; } while(arr[gt] > v && gt > lt+1){ gt--; } if(lt+1 < gt){ this.swap(arr,lt+1,gt); lt++; gt--; }else{ break; } } this.swap(arr,k,lt); if(orilt < lt-1){ console.log(orilt+'=>'+(lt-1)); tmplt.push(orilt); tmpgt.push(lt-1); } if(gt < origt){ console.log(gt+'=>'+origt); tmplt.push(gt); tmpgt.push(origt); } }}
(5)三路快排递归版
var qsort3 = function(arr,lt,gt){ if(lt >= gt){ return; } var orilt = lt, origt = gt, e = lt; var v = arr[e]; e++; gt++; while(e < gt){ if(arr[e] == v){ e++; }else if(arr[e] < v){ if(e != lt+1){ this.swap(arr,e,lt+1); } lt++; e++; }else if(arr[e] > v){ this.swap(arr,gt-1,e); gt--; } } this.swap(arr,orilt,lt); lt--; qsort3(arr,orilt,lt); qsort3(arr,gt,origt);}
(6)三路快排非递归版
var qsort3nr = function(arr){ if(arr.length <= 0){ return; } var tmplt = []; var tmpgt = []; tmplt.push(0); tmpgt.push(arr.length-1); while(tmplt.length > 0){ var lt = tmplt.shift(); var gt = tmpgt.shift(); if(lt >= gt){ return; } var orilt = lt, origt = gt, e = lt; var v = arr[e]; e++; gt++; while(e < gt){ if(arr[e] == v){ e++; }else if(arr[e] < v){ if(e != lt+1){ this.swap(arr,e,lt+1); } lt++; e++; }else if(arr[e] > v){ this.swap(arr,gt-1,e); gt--; } } this.swap(arr,orilt,lt); lt--; if(orilt < lt){ tmplt.push(orilt); tmpgt.push(lt); } if(gt < origt){ tmplt.push(gt); tmpgt.push(origt); } }}
交换数组两个位置的数值(int型):
//交换两个整数值var swap = function(arr,x,y){ if(arr[x] == arr[y]){ return; } arr[x] = (arr[x]) ^ (arr[y]); arr[y] = (arr[x]) ^ (arr[y]); arr[x] = (arr[x]) ^ (arr[y]);};
总结:
(1)递归到非递归的转换主要是记录每次排序的区间起始位置。在递归版算法中编译器替我们记录了每次递归调用应该返回的位置,所以在非递归版算法中我们要自己承担这一份责任;
(2)在非递归版算法中记录每次递归区间需要用到栈这一数据结构,可以用js的数组模拟栈的功能,这里我用了两个栈分别存储排序区间的起始位置与结束位置,其实完全可以用一个栈来做这件事情,但是要注意入栈和出栈的顺序。
(3)实现上述算法的过程中最容易出现问题的是区间指针的开闭状态,当前lt是指向比标量值小的所有数值的最后一个,还是指向下一个,其实这里开闭状态可以随意设定,但是交换数据以及越界判断等地方一定要与设定的区间状态相协调。