duyifei0 2019-06-26
本文代码也上传到了 排序算法回顾(javascript)。
思路:从未排序的序列中选出最小(大)的元素,放进已排好序的序列末尾。
时间复杂度:O(n^2)
算法稳定性:不稳定
// 定义一个函数用于交换 function swap (array, i, j) { let temp = array[i]; array[i] = array[j]; array[j] = temp; } function selectionSort (arr) { let minIndex; for (let i = 0; i < arr.length; i++) { minIndex = i; for (let j = i + 1; j < arr.length; j++) { // 对未排序的序列进行循环,找出最小元素。 if (arr[j] < arr[minIndex]) { minIndex = j; } } swap(arr, i, minIndex); // 最小元素与放如排好序的序列末尾。 } return arr; } let arr = [1,2,8,4,3,6,10]; selectionSort(arr) // 1,2,3,4,6,8,10
选择排序所需要的元素比较次数为 (n-1) + (n-2) + ... + 1 = n*(n-1)/2 ,元素赋值次数界于 0 ~ 3(n-1) 之间,也就是原序列已排好序于原序列为反序两种极端情况。
思路:从第二个元素往后遍历,从前面的序列中找到一个合适的位置进行插入。
时间复杂度:O(n^2)
算法稳定性:稳定
let arr = [5,3,2,6,7,10,1]; // 进行小到达排序 function InsertionSort(arr) { let len = arr.length; for (let i = 1; i < len; i++) { let curr = arr[i]; // 要执行插入操作的元素 let j = i; // 从i开始往回遍历 while (j > 0 && arr[j-1] > curr) { // 不断跟curr元素进行比较,大于curr的往后退一位,最终给curr腾出一个插入的位置 arr[j] = arr[j-1]; j--; } arr[j] = curr // curr插入到合适的位置中 } return arr; } console.log(InsertionSort(arr)); // 1,2,3,5,6,7,10
容易看出,当序列已排好序的时候,元素比较的次数最少,比较次数为 n - 1 次,每一个元素只需要和前一个元素比较即可,当序列是按反序排列,那么比较次数最多,比较次数为 n*(n-1)/2 。
元素赋值次数为等于比较次数加上 n - 1。
思路:多次遍历序列,比较相邻元素,将最大(最小)元素像泡泡一样冒到后面已排好序的序列中。
时间复杂度:O(n^2)
算法稳定性:稳定
function advanceBubbleSort1(arr){ let len = arr.length; let flag; // 设置一个标记,如果某一轮没有交换,表示已经排好序了。不必再循环遍历。 for(let i = 1, i <= len - 1; i++){ flag = false; for(let j = 0; j < len - i; j++){ if(arr[j] > arr[j + 1]){ temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; flag = true; } } if(flag === false){ break; } } return arr; }
快速排序是一个非常流行并且高效的排序算法。
它之所以高效是因为它在原位上进行排序,不需要辅助的存储空间。
思路:以最左元素作为主元进行划分,最后再将主元放回正确位置,递归。
平均时间复杂度 Θ(nlogn), 最坏的情况 θ(n^2)
算法稳定性:不稳定
在了解快速排序之前需要了解一个关键算法:划分算法
function partition(arr, left ,right) { // 分区操作 var pivot = left, // 设定基准值(pivot),即以最左元素为主元 index = pivot + 1; for (var i = left + 1; i <= right; i++) { if (arr[i] < arr[pivot]) { swap(arr, i, index); index++; } } swap(arr, pivot, index - 1); // 最后把主元放回正确位置 return index-1; } function swap(arr, i, j) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
我们可以看到,整个划分都在原数组上进行,不需要引进额外的辅助数组。
快速排序算法需要以划分算法为核心:
function quickSort(arr, left, right) { var len = arr.length, partitionIndex; if (left < right) { partitionIndex = partition(arr, left, right); quickSort(arr, left, partitionIndex-1); quickSort(arr, partitionIndex+1, right); } return arr; } let arr = [1,6,3,8,5,0,7] console.log(quickSort(arr, 0, 6)) // 0,1,3,5,6,7,8
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
平均时间复杂度:O(nlogn)
算法稳定性:不稳定
思路:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
function shellSort(arr) { var len = arr.length, temp, gap = 1; while (gap < len/3) { gap = gap*3 + 1; } for (gap; gap > 0; gap = Math.floor(gap/3)) { for (var i = gap; i < len; i++) { temp = arr[i]; for (var j = i-gap; i >= 0 && arr[j] > temp; j-=gap) { arr[j + gap] = arr[j]; } arr[j + gap] = temp; } } return arr; } let arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] console.log(shellSort(arr)); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
归并排序采用分治法的思想。这里给出一种自上而下递归的方法。
思路:分半->分半->再分半->分到每组只剩下一个元素的时候就回溯
平均时间复杂度:O(nlogn)
算法稳定性: 稳定
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 = []; 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()); return result; } let arr = [5, 3, 6, 8, 2, 0, 1]; console.log(mergeSort(arr)); // [ 0, 1, 2, 3, 5, 6, 8 ]
堆排序是指利用堆这种数据结构所设计的一种排序算法。
大项堆:每个节点的值都大于或等于其子节点的值,用于升序排序
小项堆:每个节点的值都小于或等于其子节点,用于降序排序
平均时间复杂度:O(nlogn)
算法稳定性: 不稳定
var len; function buildMaxHeap(arr) { // 建立大顶堆 len = arr.length; for (var i = Math.floor(len/2) - 1; i >= 0; i--) { heapify(arr, i); } } function heapify(arr, i) { // 堆调整 var left = 2 * i + 1, right = 2 * i + 2, largest = i; if (left < len && arr[left] > arr[largest]) { largest = left; } if (right < len && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(arr, i, largest); heapify(arr, largest); } } function swap(arr, i, j) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } function heapSort(arr) { buildMaxHeap(arr); for (var i = arr.length-1; i > 0; i--) { swap(arr, 0, i); len--; heapify(arr, 0); } return arr; } let arr = [4,3,8,10,11,13,7,30,17,26]; console.log(heapSort(arr)) // [ 3, 4, 7, 8, 10, 11, 13, 17, 26, 30 ]
了解几个概念:
可以这样帮助理解,O 类似于 <= ,Ω 类似于 >=, θ 类似于 = ,但只能说是类似于。
如
let i = 0; for (let i = 0; i < n; i++) { i ++; }
可以看到迭代次数为n,所以时间复杂度为 θ(n)
什么是基本运算呢?
如上一份代码
let i = 0; for (let i = 0; i < n; i++) { i ++; }
选择自加运算,同理得时间复杂度 θ(n)
要知道时间复杂度只是描述一个增长趋势,复杂度为O的排序算法执行时间不一定比复杂度为O长,因为在计算O时省略了系数、常数、低阶。实际上,在对小规模数据进行排序时,n2的值实际比 knlogn+c还要小。