一直很害怕算法,老是感受特别伤脑子,所以至今为止,几种基本的排序算法一直都不是很清楚,更别说时间复杂度、空间复杂度什么的了。javascript
今天抽空理了一下,其实感受还好,并无那么可怕,虽然代码写出来仍是磕磕绊绊,可是思想和原理仍是大体上摸清楚了,记录、分享。html
另外一篇文章:三种非比较排序算法总结java
关于排序,前辈们已经讲解的够多了,我这里主要摘录一些概念。算法
排序算法稳定性的简单形式化定义为:若是Ai = Aj,排序前Ai在Aj以前,排序后Ai还在Aj以前,则称这种排序算法是稳定的。数组
选择排序每次比较的是数组中特定索引的值与全数组中每一个值的大小比较,每次都选出一个最小(最大)值,若是当前索引的值大于以后索引的值,则二者进行交换数据结构
// 分类 -------------- 内部比较排序 // 数据结构 ---------- 数组 // 最差时间复杂度 ---- O(n^2) // 最优时间复杂度 ---- O(n^2) // 平均时间复杂度 ---- O(n^2) // 所需辅助空间 ------ O(1) // 稳定性 ------------ 不稳定 var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; var temp; for (var i = 0; i < arr.length; i++) { for (var j = i + 1; j < arr.length; j++) { if (arr[i] > arr[j]) { temp = arr[j]; arr[j] = arr[i]; arr[i] = temp; } } } console.log(arr);
过程大体以下:ui
1 4 5 2 3 9 0 7 6 0 4 5 2 3 9 1 7 6 0 2 5 4 3 9 1 7 6 0 1 5 4 3 9 2 7 6 0 1 4 5 3 9 2 7 6 0 1 3 5 4 9 2 7 6 0 1 2 5 4 9 3 7 6 0 1 2 4 5 9 3 7 6 0 1 2 3 5 9 4 7 6 0 1 2 3 4 9 5 7 6 0 1 2 3 4 5 9 7 6 0 1 2 3 4 5 7 9 6 0 1 2 3 4 5 6 9 7 0 1 2 3 4 5 6 7 9
冒泡排序每次从数组的最开始索引处与后一个值进行比较,若是当前值比较大,则交换位置。这样一次循环下来,最大的值就会排入到最后的位置。.net
// 分类 -------------- 内部比较排序 // 数据结构 ---------- 数组 // 最差时间复杂度 ---- O(n^2) // 最优时间复杂度 ---- 若是能在内部循环第一次运行时,使用一个旗标来表示有无须要交换的可能,能够把最优时间复杂度下降到O(n) // 平均时间复杂度 ---- O(n^2) // 所需辅助空间 ------ O(1) // 稳定性 ------------ 稳定 var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; var t; for (var m = 0; m < arr.length; m++) { for (var n = 0; n < arr.length - m; n++) { if (arr[n] > arr[n + 1]) { t = arr[n + 1]; arr[n + 1] = arr[n]; arr[n] = t; } } } console.log(arr);
过程大体以下:设计
1 4 5 2 3 9 0 7 6 1 4 2 5 3 9 0 7 6 1 4 2 3 5 9 0 7 6 1 4 2 3 5 0 9 7 6 1 4 2 3 5 0 7 9 6 1 4 2 3 5 0 7 6 9 1 2 4 3 5 0 7 6 9 1 2 3 4 5 0 7 6 9 1 2 3 4 0 5 7 6 9 1 2 3 4 0 5 6 7 9 1 2 3 0 4 5 6 7 9 1 2 0 3 4 5 6 7 9 1 0 2 3 4 5 6 7 9 0 1 2 3 4 5 6 7 9
插入排序相似于扑克牌的插入方法,选取待排列数组中的任意一个数字做为已排序的基准,再依次从待排序数组中取出数字,根据依次比较,将这个数字插入到已排序的数组中code
// 分类 ------------- 内部比较排序 // 数据结构 ---------- 数组 // 最差时间复杂度 ---- 最坏状况为输入序列是降序排列的,此时时间复杂度O(n^2) // 最优时间复杂度 ---- 最好状况为输入序列是升序排列的,此时时间复杂度O(n) // 平均时间复杂度 ---- O(n^2) // 所需辅助空间 ------ O(1) // 稳定性 ------------ 稳定 var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; /** * 直接使用同一个数组方式 */ for (var i = 1; i < arr.length; i++) { var get = arr[i]; var j = i - 1; // 倒叙比较已经排序的值和取到的值进行比较 // 若是取到的值在已经排序中的值中存在合适的索引插入,则须要将这个索引以后的值进行后移 while (j >= 0 && arr[j] > get) { arr[j + 1] = arr[j]; j--; } arr[j + 1] = get; } console.log(arr); /** * 引入一个新的数组方式 * 引入一个数组后会更好理解 */ var sortList = [arr[0]]; for (var i = 1; i < arr.length; i++) { var sLen = sortList.length; // 若是取出的数字比已经排序的第一个值都小,则插入到最开始 if (arr[i] < sortList[0]) { sortList.unshift(arr[i]) continue; } // 若是取出的数字比已经排序的最后一个值都大,则插入到最末尾 if (arr[i] > sortList[sLen - 1]) { sortList[sLen] = arr[i]; continue; } for (var j = 0; j < sLen - 1; j++) { if (arr[i] >= sortList[j] && arr[i] <= sortList[j + 1]) { sortList.splice(j + 1, 0, arr[i]); break; } } } console.log(sortList);
过程大体以下:
1 1 4 1 4 5 1 2 4 5 1 2 3 4 5 1 2 3 4 5 9 0 1 2 3 4 5 9 0 1 2 3 4 5 7 9 0 1 2 3 4 5 6 7 9
二分插入排序是直接插入排序的一个变种,利用二分查找法找出下一个插入数字对应的索引,而后进行插入。
当n较大时,二分插入排序的比较次数比直接插入排序的最差状况好得多,但比直接插入排序的最好状况要差,所当以元素初始序列已经接近升序时,直接插入排序比二分插入排序比较次数少。二分插入排序元素移动次数与直接插入排序相同,依赖于元素初始序列。
// 分类 -------------- 内部比较排序 // 数据结构 ---------- 数组 // 最差时间复杂度 ---- O(n^2) // 最优时间复杂度 ---- O(nlogn) // 平均时间复杂度 ---- O(n^2) // 所需辅助空间 ------ O(1) // 稳定性 ------------ 稳定 var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; /** * 直接使用同一个数组方式 */ for (var i = 1; i < arr.length; i++) { var get = arr[i]; var left = 0; var right = i - 1; // 每次找出中间位置而后进行比较,最终肯定索引位置 while (left <= right) { var mid = parseInt((left + right) / 2); if (arr[mid] > get) { right = mid - 1; } else { left = mid + 1; } } for (var k = i - 1; k >= left; k--) { arr[k + 1] = arr[k]; } arr[left] = get; } /** * 引入一个新的数组方式 * 引入一个数组后会更好理解变化的方式 */ var sortList = [arr[0]]; for (var i = 1; i < arr.length; i++) { var sLen = sortList.length; var get = arr[i]; var left = 0; var right = sLen - 1; // 每次找出中间位置而后进行比较,最终肯定索引位置 while (left <= right) { var mid = parseInt((left + right) / 2); if (sortList[mid] > get) { right = mid - 1; } else { left = mid + 1; } } // splice是数组插入值的一个快捷方式,将值移位的方式以下 // sortList.splice(left, 0, get); for (var k = sLen - 1; k >= left; k--) { sortList[k + 1] = sortList[k]; } sortList[left] = get; } console.log(sortList);
过程大体以下:
1 1 4 1 4 5 1 2 4 5 1 2 3 4 5 1 2 3 4 5 9 0 1 2 3 4 5 9 0 1 2 3 4 5 7 9 0 1 2 3 4 5 6 7 9
希尔排序是一种更高效的插入排序,经过设计步长(gap)将数组分组,而后每组中单独采用排序算法将每组排序,而后在缩小步长,进行重复的分组排序工做,直到gap变为1的时候,整个数组分为一组,算法结束。
例如:数组 [1, 4, 5, 2, 3, 9, 0, 7, 6]
,若是每次以数组长度的一半来做为步长,能够分解为如下步骤
1. gap: Math.floor(9 / 2) = 4; 分为四组,分组为: { 1, 3 }, { 4, 9 }, { 5, 0 }, { 2, 7 } 最后一个数字 6 须要等到第5个数字排序完成,也就是3,能够得出3依旧还处在第4索引的位置,所以最后一个分组为 { 3, 6 } 完成一轮分组以及排序后的数组为:[ 1, 4, 0, 2, 3, 9, 5, 7, 6 ] 2. gap: Math.floor(4 / 2) = 2; 分为两组,分组为: { 1, 0, 3, 5, 6 }, { 4, 2, 9, 7 } 完成第二轮分组以及排序后的数组为:[ 0, 2, 1, 4, 3, 7, 5, 9, 6 ] 3. gap: Math.floor(2 / 2) = 1; 分为一组,即为:{ 0, 2, 1, 4, 3, 7, 5, 9, 6 } 完成第三轮分组以及排序后的数组为:[ 0, 1, 2, 3, 4, 5, 6, 7, 9 ]
// 分类 -------------- 内部比较排序 // 数据结构 ---------- 数组 // 最差时间复杂度 ---- 根据步长序列的不一样而不一样。已知最好的为O(n(logn)^2) // 最优时间复杂度 ---- O(n) // 平均时间复杂度 ---- 根据步长序列的不一样而不一样。 // 所需辅助空间 ------ O(1) // 稳定性 ------------ 不稳定 var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; var gap = Math.floor(arr.length / 2); function swap(arr, i, j) { var t; t = arr[j]; arr[j] = arr[i]; arr[i] = t; } for (; gap > 0; gap = Math.floor(gap / 2)) { //从第gap个元素,逐个对其所在组进行直接插入排序操做 for(var i = gap; i < arr.length; i++) { var j = i; // 这里采用的实际上是冒泡排序 while(j - gap >= 0 && arr[j] < arr[j-gap]) { //插入排序采用交换法 swap(arr, j, j-gap); j -= gap; } // 或者插入排序 var temp = arr[j]; if (arr[j] < arr[j-gap]) { while (j-gap >= 0 && temp < arr[j-gap]) { arr[j] = arr[j-gap]; j -= gap; } arr[j] = temp; } } } console.log(arr);
过程大体以下:
1 4 5 2 3 9 0 7 6 1 4 0 2 3 9 5 7 6 0 4 1 2 3 9 5 7 6 0 2 1 4 3 9 5 7 6 0 2 1 4 3 7 5 9 6 0 1 2 4 3 7 5 9 6 0 1 2 3 4 7 5 9 6 0 1 2 3 4 5 7 9 6 0 1 2 3 4 5 7 6 9 0 1 2 3 4 5 6 7 9
归并排序采用的是一种分治思想,将整个数组递归
分红若干小组,直到最后组中的个数为1时中止,那么此时再与同一级别的分组数字进行比较,这就是并
的操做。而后向上一层层地进行合并,最终合成一个排序好的数组。
这么讲可能有点糊涂,用一个例子分析。好比如今有这两个排序好的数组
var a = [1, 4, 6, 7, 9]; var b = [2, 3, 5, 8]; var temp = []; // 比较过程以下: // 比较两个数组中的第一个数字,将数字小的压进temp数组,同时将这个数字从原数组中删除 // 第一步 a[0] < b[0] // 获得 a: [4, 6, 7, 9] b: [2, 3, 5, 8] temp: [1] // 第二步 a[0] > b[0] // 获得 a: [4, 6, 7, 9] b: [3, 5, 8] temp: [1, 2] // 第三步 a[0] > b[0] // 获得 a: [4, 6, 7, 9] b: [5, 8] temp: [1, 2, 3] // 中间省略N步 // 第N+1步 a: [9] b: [] temp: [1, 2, 3, 4, 5, 6, 7, 8] // 此时b数组已经为空,则直接归并 // 获得 a: [] b: [] temp: [1, 2, 3, 4, 5, 6, 7, 8, 9]
注:以上的步骤只是归并排序递归中的最上层的一步,其中下面还会分红不少小的合并步骤。
// 分类 -------------- 内部比较排序 // 数据结构 ---------- 数组 // 最差时间复杂度 ---- O(nlogn) // 最优时间复杂度 ---- O(nlogn) // 平均时间复杂度 ---- O(nlogn) // 所需辅助空间 ------ O(n) // 稳定性 ------------ 稳定 var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; var len = arr.length; function mergeArray(arr, first, mid, last, t) { var i = mid, j = last, m = first, n = mid + 1, k = 0; while (m <= mid && n <= last) { if (arr[m] > arr[n]) { t[k++] = arr[n++]; } else { t[k++] = arr[m++]; } } while (m <= i) { t[k++] = arr[m++] } while(n <= j) { t[k++] = arr[n++]; } for (var p = 0; p < k; p++) { arr[first + p] = t[p]; } } function mergeSort(arr, first, last, t) { if (first < last) { var mid = Math.floor((first + last) / 2); mergeSort(arr, first, mid, t); mergeSort(arr, mid + 1, last, t) mergeArray(arr, first, mid, last, t); } } mergeSort(arr, 0, len - 1, []); console.log(arr);
过程大体以下:
1 4 5 2 3 9 0 7 6 1 4 5 2 3 9 0 7 6 1 4 5 2 3 9 0 7 6 1 4 5 2 3 9 0 7 6 1 2 3 4 5 9 0 7 6 1 2 3 4 5 0 9 7 6 1 2 3 4 5 0 9 6 7 1 2 3 4 5 0 6 7 9 0 1 2 3 4 5 6 7 9
快速排序的原理是:首先随机选择一个值,遍历整个数组,比这个值小的放在左边的数组中,比这个值大的放在右边的数组中,而后再根据上一步得出的左右数组重复上述的操做,直到分出的左右数组长度为1或者0的时候中止。
仍是举个栗子吧:
var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; // 1. 选取一个数,我这里取中间的数,即为arr[4] = 3 left: [1, 2, 0] right: [4, 5, 9, 7, 6] // 2. 在左右数组中重复上述操做 left: [1, 2, 0] 取数:left[1] = 2 left-left: [0, 1] // 继续递归 left-right: [] // 递归结束,直接返回 right: [4, 5, 9, 7, 6] 取数: right[3] = 9 right-left: [4, 5, 7, 6] // 继续递归 right-right: [] // 递归结束,直接返回
在递归中排序,而后链接选出的那个数,就完成了整个数组的排序
var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; function quickSort(arr) { if (arr.length === 1 || arr.length === 0) { return arr; } var left = []; var right = []; var len = arr.length; var f = 0; var l = len - 1; var mid = Math.floor((f + l) / 2); var midVal = arr[mid]; for (var i = 0; i < len; i++) { if (arr[i] < arr[mid]) { left.push(arr[i]); } else if (arr[i] > arr[mid]) { right.push(arr[i]) } } var leftArr = quickSort(left); var rightArr = quickSort(right); return leftArr.concat(midVal).concat(rightArr); } var result = quickSort(arr); console.log(result);
大体过程以下:
left: 1 2 0 middle: 3 right: 4 5 9 7 6 left: 1 0 middle: 2 right: left: 0 middle: 1 right: left: 4 5 7 6 middle: 9 right: left: 4 middle: 5 right: 7 6 left: 6 middle: 7 right:
堆排序是指利用堆这种数据结构所设计的一种选择排序算法。堆是一种近似彻底二叉树的结构(一般堆是经过一维数组来实现的),并知足性质:以最大堆(也叫大根堆、大顶堆)为例,其中父结点的值老是大于它的孩子节点。
咱们能够很容易的定义堆排序的过程:
更多请参看http://www.javashuo.com/article/p-hjboauhu-dg.html,这篇文章中进行了很详细地讲解。
var arr = [1, 4, 5, 2, 3, 9, 0, 7, 6]; var len = arr.length; function swap(arr, i, j) { var t = arr[j]; arr[j] = arr[i]; arr[i] = t; } function heapAdjust(arr, i, end) { var left = 2 * i + 1; // 左边子节点 var right = 2 * i + 2; // 右侧子节点 var max = i; if (left < end && arr[left] > arr[max]) { max = left; } if (right < end && arr[right] > arr[max]) { max = right; } if (max !== i) { swap(arr, max, i); heapAdjust(arr, max, end); } } function buildMaxHeap(arr, len) { var sNode = Math.floor(len / 2) - 1; // 第一个须要调整的非叶子节点 for (var i = sNode; i >= 0; i--) { heapAdjust(arr, i, len); } return len; } function heapSort(arr) { var heapSize = buildMaxHeap(arr, len); // 堆(无序区)元素个数大于1,未完成排序 while (heapSize > 1) { // 将堆顶元素与堆的最后一个元素互换,并从堆中去掉最后一个元素 // 此处交换操做颇有可能把后面元素的稳定性打乱,因此堆排序是不稳定的排序算法 swap(arr, 0, --heapSize); // 重新的堆顶元素开始向下进行堆调整,时间复杂度O(logn) heapAdjust(arr, 0, heapSize); } } heapSort(arr); console.log(arr);
大体实现以下:
1 4 5 2 3 9 0 7 6 7 6 5 4 3 1 0 2 9 6 4 5 2 3 1 0 7 9 5 4 1 2 3 0 6 7 9 4 3 1 2 0 5 6 7 9 3 2 1 0 4 5 6 7 9 2 0 1 3 4 5 6 7 9 1 0 2 3 4 5 6 7 9 0 1 2 3 4 5 6 7 9