高级排序算法
希尔排序
希尔排序是以它的创造者命名的,这个算法在插入排序的基础上做了很大的改善。希尔排序的核心理念与插入排序不同,它会首先比较距离较远的元素,而非相邻的元素。和简单地比较相邻元素相比,使用这种方案可以使离正确位置很远的元素更快的回到合适的位置。当开始用这个算法遍历数据集时,所有元素之间的距离会不断的减小,直到处理到数据集的末尾,这是算法比较的是相邻的元素了
function CArray(numElements) { var dataStore = []; for ( var i = 0; i < numElements; ++i ) { dataStore[i] = i; } return dataStore; } function setData(numElements,arr) { for ( var i = 0; i < numElements; ++i ) { arr[i] = Math.floor(Math.random() * (numElements + 1)); } }
/** * 希尔(Shell)排序 又称为 缩小增量排序,它是一种插入排序。它是直接插入排序算法的加强版 * 对比:很多数据时希尔排序比插入排序高效,数据量少的时候,插入排序更高效 */ function swap(arr, index1, index2) { var temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; } function shellSort(arr){ var N = arr.length; var h = 1; console.time('希尔排序耗时'); while(h < N / 4){ h = 4 * h + 1; } while (h >= 1) { for(var i = h; i < N; i++){ for(var j = i; j >= h && arr[j] < arr[j - h]; j -= h){ swap(arr, j, j - h); } } h = (h - 1)/4; } console.timeEnd('希尔排序耗时'); return arr; } var numElements = 10000; var arr = new CArray(numElements); setData(numElements,arr); shellSort(arr);
与上面的插入排序相比,10000个数字排序耗时5~10ms,500000个数据平均耗时120ms,插入排序50000个数据平均耗时800ms,显然,希尔排序比插入排序大数据时性能要高很多
归并排序
归并排序的命名来自它的实现原理:把一系列排好序的子序列合并成一个大的完整有序序 列。从理论上讲,这个算法很容易实现。我们需要两个排好序的子数组,然后通过比较数 据大小,先从最小的数据开始插入,最后合并得到第三个数组。然而,在实际情况中,归 并排序还有一些问题,当我们用这个算法对一个很大的数据集进行排序时,我们需要相当 大的空间来合并存储两个子数组。就现在来讲,内存不那么昂贵,空间不是问题,因此值 得我们去实现一下归并排序,比较它和其他排序算法的执行效率
/** * 归并排序 * 解析: * 把一系列排好序的子序列合并成一个大的完整有序序列 * 自底向上递归并排序 */ function mergeSort(arr) { debugger if (arr.length <= 1) { return arr; } var step = 1; var left, right; while (step < arr.length) {//循环不是if left = 0; right = step; while (right + step <= arr.length) {//注意加等于号 mergeArrays(arr, left, left + step, right, right + step); left = right + step;// 不要错写成left =left +step; right = left + step; } if (right < arr.length) { mergeArrays(arr, left, left + step, right, arr.length); } step *= 2; } return arr; } function mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) { debugger var rightArr = new Array(stopRight - startRight + 1); var leftArr = new Array(stopLeft - startLeft + 1); var k = startRight;//k为右起始值 for (var i = 0; i < rightArr.length - 1; i++) { rightArr[i] = arr[k]; k++; } k = startLeft;//k为左起始值 for (var i = 0; i < leftArr.length - 1; i++) { leftArr[i] = arr[k]; k++; } rightArr[rightArr.length - 1] = Infinity;//哨兵值 leftArr[leftArr.length - 1] = Infinity;//哨兵值 var m = 0, n = 0; for (var i = startLeft; i < stopRight; i++) {//注意m和n的累加位置 if (leftArr[m] <= rightArr[n]) { arr[i] = leftArr[m]; m++; } else { arr[i] = rightArr[n]; n++; } } } var arr = [5,9,7,2,0,5,3,6,1,10]; console.log(arr.join(',')); console.log(mergeSort(arr).join(','));
与希尔排序相比,500000个数据排序平均耗时138ms,比希尔排序平均耗时要长,在js中性能要低
快速排序
快速排序是处理大数据集最快的排序算法之一。它是一种分而治之的算法,通过递归的方式将数据依次分解为包含较小元素和较大元素的不同子序列。该算法不断重复这个步骤直到所有数据都是有序的
这个算法首先要在列表中选择一个元素作为基准值(pivot)。数据排序围绕基准值进行,将列表中小于基准值的元素移到数组的底部,将大于基准值的元素移到数组的顶部
/** * 快速排序 * 解析: * 1、在数据集之中,选择一个元素作为"基准"(pivot)。 * 2、所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。 * 3、对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。 * 理解重点: * 直到所有子集只剩下一个y元素为止 * 对比: * 快速排序适合用于大型数据集合,在处理小数据集合反而性能会下降。 * 快速排序是处理大数据集最快的排序算法之一,它是一种分而治之的算法,通过递归的方式将数据依次分解为包含较小元素和较大元素的不同子序列 */ function qSort(list){ if(list.length === 0){ return []; } var lesser = []; var greater = []; var pivot = list[0]; for(var i = 1; i < list.length; i++){ if(list[i] < pivot){ lesser.push(list[i]); }else{ greater.push(list[i]); } } return qSort(lesser).concat(pivot, qSort(greater)); } var numElements = 10000; var arr = new CArray(numElements); setData(numElements,arr); console.time('快速排序耗时') ; qSort(arr); console.timeEnd('快速排序耗时')
与上面的高级排序相比,500000个数字排序平均耗时480ms,大于归并排序,更大于希尔排序在js可计算范围内,实践出来的,希尔排序的效率比归并排序和快速排序高,在 JavaScript 中快速排序这种方式不太可行,因为这个算法的递归深度对它来讲太深了,一些语言提供了尾递归优化。这意味着如果一个函数返回自身递归调用的结果,那么调用的过程会被替换为一个循环,它可以显著提高速度。遗憾的是,JavaScript当前并没有提供尾递归优化。深度递归的函数可能会因为堆栈溢出而运行失败