一、排序算法
排序算法有很多种,我们只讲最具代表性的几种算法:冒泡排序、希尔排序、归并排序、快速排序。
1.冒泡排序(Bubble Sort)
实现思路:
- 比较相邻的元素,如果第一个比第二个大,就交换它们两个。
- 对于每一个相邻元素作同样的比较,从开始的第一对到结尾的最后一对,比较完这一趟,最大的元素会放到(冒泡)到数组最后一位。
- 每比较完一趟,下一趟比较的数组长度会减一。因为比较完一趟数组之后,后面的最后一个元素是前面元素的最大值,下一轮进行比较没有意义。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
实现:
function bubbleSort(arr){
var len = arr.length;
for(let i=0; i<len; i++){
for(let j=0; j<len-1-i; j++){
if(arr[j]>arr[j+1]){
var temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
return arr;
}
改进一:设置一标志变量pos,用于记录每趟排序中最后一位进行交换的位置。由于pos位置之后的记录均是已交换到位,故在进行下一次趟排序时只要扫描到pos位置即可。
function bubbleSort(arr) {
console.time("改进后冒泡排序耗时");
while(i>0) {
var pos = 0;//每趟开始时,无记录交换
for(let j=0; j<i; j++){
if(arr[j]>arr[j+1]) {
pos=j;//记录交换的位置
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
i=pos;//为下一趟排序准备
}
console.timeEnd('改进后冒泡排序耗时');
return arr;
}
改进二:传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每一趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者),从而使排序趟数几乎减少了一般。
function bublleSort(arr) {
var low = 0;
var high = arr.length-1;//设置变量的初始值
var temp, j;
console.time('2.改进后冒泡排序耗时');
while(low<high) {
for(j=low; j<high; j++) {
if (arr[j]> arr[j+1]) {
tmp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=tmp;
}
--high; //修改high值, 前移⼀位
}
for (j=high; j>low; --j) {//反向冒泡,找到最⼩者
if (arr[j]<arr[j-1]) {
tmp = arr[j];
arr[j]=arr[j-1];
arr[j-1]=tmp;
}
++low; //修改low值,后移⼀位
}
}
console.timeEnd('2.改进后冒泡排序耗时');
return arr3;
}
2.希尔排序(Shell Sort)
1959年Shell提出;第一个突破O(n^2)的排序算法;是简单插入排序的改进版;它与插入排序的不同之处在于,他会优先比较距离较远的元素,希尔排序又叫缩小增量排序。
算法简介:希尔排序的核心在于间隔序列的设定。既可以提前设定好间隔序列,也可以动态的定义间隔序列。
实现思路:
先将整个待排序的记录序列分割成若个子序列分别进行直接插入排序,具体算法描述:
- 选择⼀个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进⾏k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若⼲⻓度为m 的⼦序列,分别对各⼦表进⾏直接插⼊排序。仅增 量因⼦为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]
3.归并排序(Merge Sort)
和选择排序⼀样,归并排序的性能不受输⼊数据的影响,但表现比选择排序好的多,因为始终都是O(n log n)的 时间复杂度。代价是需要额外的内存空间。
归并排序是建⽴在归并操作上的⼀种有效的排序算法。该算法是采⽤分治法(Divide and Conquer)的⼀个⾮常 典型的应用。归并排序是⼀种稳定的排序⽅法。将已有序的⼦序列合并,得到完全有序的序列;即先使每个⼦序 列有序,再使⼦序列段间有序。若将两个有序表合并成⼀个有序表,称为2-路归并。
实现思路:
- 把⻓度为n的输⼊序列分成两个⻓度为n/2的⼦序列。
- 对这两个子序列分别采⽤归并排序。
- 将两个排序好的⼦序列合并成⼀个最终的排序序列。
代码实现:
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));
4.快速排序(Quick Sort)
快速排序的基本思想:通过⼀趟排序将待排记录分隔成独⽴的两部分,其中⼀部分记录的关键字均⽐另⼀部分的关键字 ⼩,则可分别对这两部分记录继续进⾏排序,以达到整个序列有序。
实现思路:
- 从数组中选择中间⼀项作为主元;
- 创建两个指针,左边⼀个指向数组的第⼀项,右边指向数组最后⼀项。移动左指针直到我们找到⼀个⽐主元⼤的元 素,接着,移动右指针直到找到⼀个⽐主元⼩的元素。然后交换它们,重复这个过程,直到左指针超过了右指针。这个 过程是的⽐主元⼩的值都排在了主元之前,⽽⽐主元⼤的值都排在了主元之后,这⼀步叫划分操作。
- 接着,算法对划分的⼩数组(较主元⼩的值组成的⼦数组,以及较主元⼤的值组成的⼦数组)重复之前的两个步骤, 直⾄数组以完全排序。
// 快速排序
const quickSort = (function() { // 默认状态下的⽐较函数
function compare(a, b) { if (a === b) { return 0 }
return a < b ? -1 : 1
}
function swap(array, a, b) {
[array[a], array[b]] = [array[b], array[a]]
}
// 分治函数
function partition(array, left, right) { // ⽤index取中间值⽽⾮splice
const pivot = array[Math.floor((right + left) / 2)]
let i = left
let j = right
while (i <= j) {
while (compare(array[i], pivot) === -1) {
i++
}
while (compare(array[j], pivot) === 1) {
j--
}
if (i <= j) {
swap(array, i, j)
i++
j--
}
}
return i
}
// 快排函数
function quick(array, left, right) {
let index
if (array.length > 1) {
index = partition(array, left, right)
if (left < index - 1) {
quick(array, left, index - 1)
}
if (index < right) {
quick(array, index, right)
}
}
return array
}
return function quickSort(array) { return quick(array, 0, array.length - 1) }