使用JavaScript完成排序算法:冒泡排序、选择排序 、快速排序 、插入排序、希尔排序、归并排序
1.冒泡排序
每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。
而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。
function bubbleSort(arr) { for (var i = 1; i < arr.length; i++) { for (var j = 0; j < arr.length - i; j++) { if (arr[j] > arr[j + 1]) { var num = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = num; } } } return arr; }
时间复杂度:O(n^2)
2.选择排序
工作原理是:第一次从待排序的中数据元素选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。首先在未排序的序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
function selectionSort(arr) { for (var i = 0; i < arr.length - 1; i++) { var min = i; for (var j = i + 1; j < arr.length; j++) { if (arr[j] < arr[min]) min = j; } if (min != i) { var num = arr[i]; arr[i] = arr[min]; arr[min] = num; } } return arr; }
时间复杂度:O(n^2)
3.快速排序
(1)选出一个mid,一般是最左边或是最右边的。
(2)定义一个left[]和一个right[],遍历数组,在走的过程中,若遇到大于mid的数,则存入right中,否则存入right中
(4)此时mid的左边都是小于mid的数,mid的右边都是大于mid的数
(5)将mid的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序
function quickSort(arr) { if (arr.length <= 1) { return arr; } else { var left = [], right = [], mid = arr[0]; for (var i = 1; i < arr.length; i++) { arr[i] > mid ? right.push(arr[i]) : left.push(arr[i]); } return quickSort(left).concat([mid], quickSort(right)); } } console.log(quickSort(arr));
时间复杂度:O(nLogn)
4.插入排序
(1)从第一个元素开始,该元素可以认为已经被排序。
(2)取出下一个元素作为新元素,在已经排序的元素序列中从后向前扫描,进行大小比较。
(3)如果已排序的元素大于新元素,则继续向前扫描,比较大小。
(4)重复上一个步骤,直到找到已排序的元素小于或者等于新元素的位置,将新元素插入到该位置,重复上面的步骤。
function insertionSort(arr) { for (let i = 1; i < arr.length; i++) { let preIndex = i - 1; let current = arr[i]; // 位置i之前,是已排好序的数字,while的作用是找到一个坑位,给当前数字current插入 while (preIndex >= 0 && arr[preIndex] > current) { arr[preIndex + 1] = arr[preIndex]; // 对大于current的值,往后移一位,给current的插入腾出位置 preIndex--; } arr[preIndex + 1] = current; } return arr; }
时间复杂度:O(n^2)
5.希尔排序 (插入排序的进阶)
希尔排序是把序列按下标的一定增量(一般为数组长度的一半)分组,对每组使用直接插入排序算法排序;随着增量的逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个序列恰好被分为一组,算法便终止。
function shellSort(arr) { // 参数为需要排序的数组 // 获取初始的增量 let gap = Math.floor(arr.length / 2); while (gap >= 1) { // 增量小于1时结束循环 // 从gap开始遍历,因为插入排序假设第一个是有序的 for (let i = gap; i < arr.length; i++) { let j = i; // 为插入排序从后向前排序提供条件 // 如果排序的后面的数字小于前面的,交换两个数的位置 while (arr[j] < arr[j - gap] && j - gap >= 0) { let temp = arr[j]; arr[j] = arr[j - gap]; arr[j - gap] = temp; // j减小gap从后向前遍历 j -= gap; } } // 增量每次都减小一半 gap = Math.floor(gap / 2); } return arr; }
时间复杂度:
步长的选择是希尔排序的重要部分,只要最终步长为1任何步长序列都可以工作。
算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。
步长序列的不同,会导致最坏的时间复杂度情况的不同。
本文中,以N/2为步长的最坏时间复杂度为N^2。
用这样步长序列的希尔排序比插入排序要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。
6.归并排序 (大数据进行排序的常用排序算法)
将大的数组,分解为小数组,直至单个元素。然后,使用选择排序的方式,对分拆的小数组,进行回溯,并有序合并,直至合并为一个大的数组。
1.将大数组分为若干小数组,直至单个元素
⒉将若干个小数组两两合并,保证合并后的组是有序的
3.重复第二步操作直到只剩下一组,排序完成
function mergeSort(arr) { return sort(arr, 0, arr.length - 1); // 注意右区间是arr.length - 1 // sort方法,进行递归 function sort(arr, left, right) { // 当left !== right时,证明还没分拆到最小元素 if (left < right) { // 取中间值,分拆为两个小的数组 const mid = Math.floor((left + right) / 2); const leftArr = sort(arr, left, mid); const rightArr = sort(arr, mid + 1, right); // 递归合并 return merge(leftArr, rightArr); } // left == right, 已经是最小元素,直接返回即可 return left >= 0 ? [arr[left]] : []; } // 合并两个有序数组 function merge(leftArr, rightArr) { let left = 0; let right = 0; const tmp = []; // 使用双指针,对两个数组进行扫描 while (left < leftArr.length && right < rightArr.length) { if (leftArr[left] <= rightArr[right]) { tmp.push(leftArr[left++]); } else { tmp.push(rightArr[right++]); } } // 合并剩下的内容 if (left < leftArr.length) { while (left < leftArr.length) { tmp.push(leftArr[left++]); } } if (right < rightArr.length) { while (right < rightArr.length) { tmp.push(rightArr[right++]); } } return tmp; } }
时间复杂度:n⋅log2n