经典排序算法
在文章开始之前,先介绍一下排序算法里面的一些术语
1、稳定排序:如果 a 原本排在 b 的前面,且a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
2、非稳定排序:如果 a 原本排在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
3、原地排序:就是指在排序过程中不申请多余的存储空间,只利用原来待排数据的存储空间来进行比较和交换的数据排序。
4、非原地排序:需要利用额外的存储空间来辅助排序。
5、事件复杂度:一个算法执行所消耗的时间。
6、空间复杂度:运行完一个算法所需的内存大小。
在了解了这些基本术语后,会对你后面的算法理解有一定的帮助
目录
1.选择排序
2.冒泡排序
3.快速排序
4.插入排序
5.希尔排序
6.归并排序
7.堆排序
8.计数排序
9.桶排序
10.基数排序
十大经典排序
1、选择排序:首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。其次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。这种方法我们称之为选择排序。
var arr = [3,5,1,7,2,4,9,6,10,8]; function selectSort(arr){ var len = arr.length; var index; for(var i=0;i<len-1;i++){ index=i; for(var j=i+1;j<len;j++){ if(arr[index]>arr[j]){//寻找最小值 index=j;//保存最小值的索引 } } if(index!=i){ var temp =arr[i]; arr[i]=arr[index]; arr[index]=temp; } } return arr; } console.log(selectSort(arr));
2.冒泡排序:
1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2.第一轮的时候最后一个元素应该是最大的一个。
3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。
var arr = [3,5,1,7,2,4,9,6,10,8]; function sort(arr){ for(var i = 0;i<arr.length;i++){ for(var j = 0;j<arr.length - i - 1;j++){ if(arr[j] > arr[j+1]){ var swap = arr[j]; arr[j] = arr[j+1]; arr[j+1] = swap; } } } }; sort(arr); console.log(arr);
3.快速排序:
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。
var arr = [3,5,1,7,2,4,9,6,10,8]; function quickSort(arr){ if(arr.length <= 1){ return arr; } var pivotIndex = Math.floor(arr.length / 2); var pivot = arr.splice(pivotIndex,1)[0]; var left = []; var right = []; for(var i = 0; i < arr.length;i++){ if(arr[i] < pivot){ left.push(arr[i]); }else{ right.push(arr[i]); } } return quickSort(left).concat([pivot],quickSort(right)); } console.log(quickSort(arr));
4.插入排序:
(1) 从第一个元素开始,该元素可以认为已经被排序
(2) 取出下一个元素,在已经排序的元素序列中从后向前扫描
(3) 如果该元素(已排序)大于新元素,将该元素移到下一位置
(4) 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
(5)将新元素插入到下一位置中
(6) 重复步骤2
var arr = [3,5,1,7,2,4,9,6,10,8]; function insertSort(arr) { var len =arr.length; for (var i=1;i<len; i++) { var temp=arr[i]; var j=i-1;//默认已排序的元素 while (j>=0 && arr[j]>temp) { //在已排序好的队列中从后向前扫描 arr[j+1]=arr[j]; //已排序的元素大于新元素,将该元素移到一下个位置 j--; } arr[j+1]=temp; } return arr; } console.log(insertSort(arr));
5.希尔排序:按一定的间隔对数列进行分组,然后在每一个分组中做插入排序;随后逐次缩小间隔,在每一个分组中做插入排序...直到间隔等于1,做一次插入排序后结束。
function shellSort(arr) { var len = arr.length, temp, gap = 1; console.time('希尔排序耗时:'); while(gap < len/5) { //动态定义间隔序列 gap =gap*5+1; } for (gap; gap > 0; gap = Math.floor(gap/5)) { for (var i = gap; i < len; i++) { temp = arr[i]; for (var j = i-gap; j >= 0 && arr[j] > temp; j-=gap) { arr[j+gap] = arr[j]; } arr[j+gap] = temp; } } console.timeEnd('希尔排序耗时:'); return arr; } var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]; console.log(shellSort(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
6.归并排序:
归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
- <1>.把长度为n的输入序列分成两个长度为n/2的子序列;
- <2>.对这两个子序列分别采用归并排序;
- <3>.将两个排序好的子序列合并成一个最终的排序序列。
function mergeSort(arr) { //采用自上而下的递归方法 var len = arr.length; if(len < 2) { return arr; } var middle = Math.floor(len / 2), left = arr.slice(0, middle), right = arr.slice(middle); return merge(mergeSort(left), mergeSort(right)); } function merge(left, right) { var result = []; console.time('归并排序耗时'); while (left.length && right.length) { 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()); console.timeEnd('归并排序耗时'); return result; } var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]; console.log(mergeSort(arr));
7.堆排序:
- <1>.将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
- <2>.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
- <3>.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
/*方法说明:堆排序 @param array 待排序数组*/ function heapSort(array) { console.time('堆排序耗时'); if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') { //建堆 var heapSize = array.length, temp; for (var i = Math.floor(heapSize / 2) - 1; i >= 0; i--) { heapify(array, i, heapSize); } //堆排序 for (var j = heapSize - 1; j >= 1; j--) { temp = array[0]; array[0] = array[j]; array[j] = temp; heapify(array, 0, --heapSize); } console.timeEnd('堆排序耗时'); return array; } else { return 'array is not an Array!'; } } /*方法说明:维护堆的性质 @param arr 数组 @param x 数组下标 @param len 堆大小*/ function heapify(arr, x, len) { if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array' && typeof x === 'number') { var l = 2 * x + 1, r = 2 * x + 2, largest = x, temp; if (l < len && arr[l] > arr[largest]) { largest = l; } if (r < len && arr[r] > arr[largest]) { largest = r; } if (largest != x) { temp = arr[x]; arr[x] = arr[largest]; arr[largest] = temp; heapify(arr, largest, len); } } else { return 'arr is not an Array or x is not a number!'; } } var arr=[91,60,96,13,35,65,46,65,10,30,20,31,77,81,22]; console.log(heapSort(arr));//[10, 13, 20, 22, 30, 31, 35, 46, 60, 65, 65, 77, 81, 91, 96]
8.计数排序:
计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。
- <1>. 找出待排序的数组中最大和最小的元素;
- <2>. 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- <3>. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- <4>. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
function countingSort(array) { var len = array.length, B = [], C = [], min = max = array[0]; console.time('计数排序耗时'); for (var i = 0; i < len; i++) { min = min <= array[i] ? min : array[i]; max = max >= array[i] ? max : array[i]; C[array[i]] = C[array[i]] ? C[array[i]] + 1 : 1; } for (var j = min; j < max; j++) { C[j + 1] = (C[j + 1] || 0) + (C[j] || 0); } for (var k = len - 1; k >= 0; k--) { B[C[array[k]] - 1] = array[k]; C[array[k]]--; } console.timeEnd('计数排序耗时'); return B; } var arr = [2, 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2]; console.log(countingSort(arr)); //[1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]
9.桶排序:
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序。
- <1>.设置一个定量的数组当作空桶;
- <2>.遍历输入数据,并且把数据一个一个放到对应的桶里去;
- <3>.对每个不是空的桶进行排序;
- <4>.从不是空的桶里把排好序的数据拼接起来。
/*方法说明:桶排序 @param array 数组 @param num 桶的数量*/ function bucketSort(array, num) { if (array.length <= 1) { return array; } var len = array.length, buckets = [], result = [], min = max = array[0], regex = '/^[1-9]+[0-9]*$/', space, n = 0; num = num || ((num > 1 && regex.test(num)) ? num : 10); console.time('桶排序耗时'); for (var i = 1; i < len; i++) { min = min <= array[i] ? min : array[i]; max = max >= array[i] ? max : array[i]; } space = (max - min + 1) / num; for (var j = 0; j < len; j++) { var index = Math.floor((array[j] - min) / space); if (buckets[index]) { // 非空桶,插入排序 var k = buckets[index].length - 1; while (k >= 0 && buckets[index][k] > array[j]) { buckets[index][k + 1] = buckets[index][k]; k--; } buckets[index][k + 1] = array[j]; } else { //空桶,初始化 buckets[index] = []; buckets[index].push(array[j]); } } while (n < num) { result = result.concat(buckets[n]); n++; } console.timeEnd('桶排序耗时'); return result; } var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48]; console.log(bucketSort(arr,4));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
10.基数排序:
- <1>.取得数组中的最大数,并取得位数;
- <2>.arr为原始数组,从最低位开始取每个位组成radix数组;
- <3>.对radix进行计数排序(利用计数排序适用于小范围数的特点);
/** * 基数排序适用于: * (1)数据范围较小,建议在小于1000 * (2)每个数值都要大于等于0 * @author xiazdong * @param arr 待排序数组 * @param maxDigit 最大位数 */ //LSD Radix Sort function radixSort(arr, maxDigit) { var mod = 10; var dev = 1; var counter = []; console.time('基数排序耗时'); for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) { for(var j = 0; j < arr.length; j++) { var bucket = parseInt((arr[j] % mod) / dev); if(counter[bucket]== null) { counter[bucket] = []; } counter[bucket].push(arr[j]); } var pos = 0; for(var j = 0; j < counter.length; j++) { var value = null; if(counter[j]!=null) { while ((value = counter[j].shift()) != null) { arr[pos++] = value; } } } } console.timeEnd('基数排序耗时'); return arr; } var arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48]; console.log(radixSort(arr,2)); //[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]