Last active
June 29, 2021 08:29
-
-
Save ideawu/a114679bb8f0a94452d462ae14b7c977 to your computer and use it in GitHub Desktop.
快速排序QuickSort算法JavaScript实现, 包括 Hoare 和 Lomuto 版本的实现,以及网友实现版本的对比
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<body> | |
<script> | |
// @author: ideawu | |
// @link: http://www.ideawu.net/blog/archives/1021.html | |
var swap_count = 0; | |
var cmp_count = 0; | |
// https://gist.github.com/wintercn/c30464ed3732ee839c3eeed316d73253 | |
function wintercn_qsort(arr, start, end){ | |
var midValue = arr[start]; | |
var p1 = start, p2 = end; | |
while(p1 < p2) { | |
swap(arr, p1, p1 + 1); | |
while(compare(arr[p1], midValue) >= 0 && p1 < p2) { | |
swap(arr, p1, p2--); | |
} | |
p1 ++; | |
} | |
if(start < p1 - 1) | |
wintercn_qsort(arr, start, p1 - 1); | |
if(p1 < end) | |
wintercn_qsort(arr, p1, end); | |
} | |
// http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html | |
var ruanyifeng_qsort = function(arr) { | |
if (arr.length <= 1) { return arr; } | |
var pivotIndex = Math.floor(arr.length / 2); | |
var pivot = arr.splice(pivotIndex, 1)[0]; | |
swap_count++; | |
var left = []; | |
var right = []; | |
for (var i = 0; i < arr.length; i++){ | |
if (compare(arr[i], pivot) < 0) { | |
swap_count++; | |
left.push(arr[i]); | |
} else { | |
swap_count++; | |
right.push(arr[i]); | |
} | |
} | |
swap_count++; | |
return ruanyifeng_qsort(left).concat([pivot], ruanyifeng_qsort(right)); | |
} | |
function compare(a, b){ | |
cmp_count ++; | |
return a-b; | |
} | |
function swap(arr, s, e){ | |
swap_count ++; | |
var tmp = arr[s]; | |
arr[s] = arr[e]; | |
arr[e] = tmp; | |
} | |
function partition_hoare(arr, start, end){ | |
var pivot = arr[start]; | |
var s = start; | |
var e = end; | |
while(1){ | |
while(compare(arr[s], pivot) < 0){ | |
s ++; | |
} | |
while(compare(arr[e], pivot) > 0){ | |
e --; | |
} | |
if(s == e){ | |
return s; | |
}else if(s > e){ | |
return s-1; | |
} | |
swap(arr, s, e); | |
s++; | |
e--; | |
} | |
} | |
function qsort_hoare(arr, start, end){ | |
if(start >= end){ | |
return; | |
} | |
var p = partition_hoare(arr, start, end); | |
qsort_hoare(arr, start, p); | |
qsort_hoare(arr, p+1, end); | |
} | |
function partition_lomuto(arr, start, end){ | |
var pivot = arr[end]; | |
var s = start; | |
for(var g = start; g < end; g++){ | |
if(compare(arr[g], pivot) < 0){ | |
if(s != g){ | |
swap(arr, s, g); | |
} | |
s ++; | |
} | |
} | |
if(s == end){ | |
return s-1; | |
}else{ | |
swap(arr, s, end); | |
return s; | |
} | |
} | |
function qsort_lomuto(arr, start, end){ | |
if(start >= end){ | |
return; | |
} | |
// console.log('in', arr, start, end); | |
var p = partition_lomuto(arr, start, end); | |
// console.log(JSON.stringify(arr.slice(start, p+1)), JSON.stringify(arr.slice(p+1, end+1))); | |
qsort_lomuto(arr, start, p); | |
qsort_lomuto(arr, p+1, end); | |
} | |
function bubble_sort(arr, start, end){ | |
for(var i=start; i<=end; i++){ | |
for(var j=start; j<=end-i-1; j++){ | |
if(compare(arr[j], arr[j+1]) > 0){ | |
swap(arr, j, j+1); | |
} | |
} | |
} | |
} | |
function check_result(a){ | |
var pass = true; | |
var v = a[0]; | |
for(var j=0; j<a.length; j++){ | |
var n = a[j]; | |
if(n < v){ | |
pass = false; | |
break; | |
} | |
v = a[j]; | |
} | |
return pass; | |
} | |
function run_test(func, name){ | |
document.write('<b>run test: ', name, '</b><br/>'); | |
swap_count = 0; | |
cmp_count = 0; | |
var nlogn = 0; | |
var stime = (new Date()).getTime(); | |
for(var i=1; i<=arr.length; i++){ | |
var a = arr.slice(0, i); | |
var b = func(a, 0, a.length - 1); | |
if(func == ruanyifeng_qsort){ | |
a = b; | |
} | |
if(!check_result(a)){ | |
console.log(i, 'fail'); | |
console.log(a); | |
} | |
nlogn += Math.floor(i*Math.log(i)); | |
// console.log('n:', i, 'swaps:', swap_count, 'compares:', cmp_count, 'nlogn:', Math.floor(i*Math.log(i)), JSON.stringify(a)); | |
} | |
var etime = (new Date()).getTime(); | |
document.write('time: ', etime-stime, ' ms', '<br/>'); | |
document.write('compare: ', cmp_count, ' swap: ', swap_count, ' nlogn: ', nlogn, '<br/>'); | |
document.write('<br/>'); | |
} | |
var count = 2000; | |
var arr = []; | |
for(var i=0; i<count; i++){ | |
arr.push(Math.floor(Math.random() * count)); | |
} | |
console.log('datasource:', JSON.stringify(arr)); | |
document.write('Running...'); | |
setTimeout(function(){ | |
document.write('<pre>'); | |
run_test(wintercn_qsort, 'wintercn_qsort'); | |
run_test(ruanyifeng_qsort, 'ruanyifeng_qsort'); | |
run_test(qsort_hoare, 'qsort_hoare'); | |
run_test(qsort_lomuto, 'qsort_lomuto'); | |
document.write('</pre>'); | |
// document.write('Running bubble_sort...'); | |
// setTimeout(function(){ | |
// document.write('<pre>'); | |
// run_test(bubble_sort, 'bubble_sort'); | |
// document.write('</pre>'); | |
// }, 0); | |
}, 0); | |
</script> | |
</body> | |
</html> |
hoare改进版。compare和swap数量更低。
function qsort_recr(arr, start, end){
var midValue = arr[start];
var h = start + 1;
var t = end;
while (h <= t) {
while (h <= t && compare(arr[h], midValue) <= 0) {
h++;
}
if (h > t) {
break;
}
while (t >= h && compare(midValue, arr[t]) <= 0) {
t--;
}
if (t < h) {
break;
}
swap(arr, h, t);
h++;
t--;
}
swap(arr, start, t);
if (t - 1 > start) {
qsort_recr(arr, start, t - 1);
}
if (h < end) {
qsort_recr(arr, h, end);
}
}
讲真,首先我认为在时间复杂度不变的前提下,这种优化非常无聊
其次,你非要比这个憋说我欺负你:
function wintercn_qsort(arr, start, end){
//start = start | 0;
//end = end | 0 ;
var midValue = arr[start];
var p1 = start + 1, p2 = end;
while(p1 < p2) {
while(compare(arr[p1], midValue) >= 0 && p1 < p2) {
swap(arr, p1, p2--);
}
p1 ++;
}
var mid = arr[p2] <= midValue ? p2 : p2 -1;
swap(arr, start, mid);
if(start < mid - 1)
wintercn_qsort(arr, start, mid - 1);
if(mid < end)
wintercn_qsort(arr, mid + 1, end);
}
结果:
time: 223 ms
compare: 22042901 swap: 13008246 nlogn: 14208385
run test: ruanyifeng_qsort
time: 962 ms
compare: 22773479 swap: 25452695 nlogn: 14208385
run test: qsort_hoare
time: 243 ms
compare: 32123353 swap: 4989996 nlogn: 14208385
run test: qsort_lomuto
time: 251 ms
compare: 33021494 swap: 10394010 nlogn: 14208385
看到我注释掉那两行没? 这种语言特定的优化我都懒得用。
@ZTGeng 赞!目前是你的版本最快。compare明显减少,swap减少不明显。
贴个基础版:
function qsort_base(arr, start, end) {
if (start >= end) {
return
}
var m = start;
for (var i = start + 1; i <= end; i++) {
if (compare(arr[i], arr[start]) < 0) {
m++;
if (m != i) {
swap(arr, m, i);
}
}
}
swap(arr, start, m);
qsort_base(arr, start, m - 1);
qsort_base(arr, m + 1, end);
}
再来个非递归版,和递归版其实是一样的,compare和swap数量应该与递归版完全相同(因为计算过程完全一致),耗时也许能少一丢丢?
function qsort_loop(arr, start, end) {
var _start = start;
var _end = end;
var stack = [];
while (1) {
var midValue = arr[_start];
var h = _start + 1;
var t = _end;
while (h <= t) {
while (h <= t && compare(arr[h], midValue) <= 0) {
h++;
}
if (h > t) {
break;
}
while (t >= h && compare(midValue, arr[t]) <= 0) {
t--;
}
if (t < h) {
break;
}
swap(arr, h, t);
h++;
t--;
}
swap(arr, _start, t);
if (h < _end) {
stack.push(h);
stack.push(_end);
}
if (t - 1 > _start) {
_end = t - 1;
} else if (stack.length > 0) {
_end = stack.pop();
_start = stack.pop();
} else {
break;
}
}
}
耗时排名稳定在第一或者第二的位置,weibo:光挣钱不说话
function shiye515Sort(arr, start, end) {
let divider = end;
let dividerValue = arr[divider]
let blank = divider
let pickStart = start
let pickEnd = end - 1
let isAsc = true
while (pickStart <= pickEnd) {
if (isAsc) {
if (compare(arr[pickStart], dividerValue) > 0) {
swap(arr, blank, pickStart);
blank = pickStart
isAsc = false
}
pickStart++
} else {
if (compare(arr[pickEnd], dividerValue) < 0) {
swap(arr, blank, pickEnd);
blank = pickEnd
isAsc = true
}
pickEnd--
}
}
let mid = isAsc ? (pickEnd + 1) : (pickStart - 1)
if (start < mid - 1) {
shiye515Sort(arr, start, mid - 1)
}
if (mid + 1 < end) {
shiye515Sort(arr, mid + 1, end)
}
}
为啥没人提到要先shuffle下?quicksort最坏复杂度不是O^2吗?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
一次典型的结果对比: