不是科班生的我,第一次看见时间复杂度之类的名词表示很懵逼,因此就找了网上的资料补习了下:html
时间复杂度:是指执行算法所须要的计算工做量git
空间复杂度:是指算法在计算机内执行时所需存储空间的度量github
排序算法稳定性: 假定在待排序的记录序列中,存在多个具备相同的关键字的记录,若通过排序,这些记录的相对次序保持不变,即在原序列中,ri=rj,且ri在rj以前,而在排序后的序列中,ri仍在rj以前,则称这种排序算法是稳定的;不然称为不稳定的。算法
这里不详细说 shell
参考:算法的时间复杂度和空间复杂度-总结、理解排序算法的稳定性、算法和算法的复杂度segmentfault
名词解释: 数组
n:数据规模函数
k:“桶”的个数ui
In-place:占用常数内存,不占用额外内存spa
Out-place:占用额外内存
下面的算法实现升序
顾名思义,从第一个开始比较相邻的两个,小的数网上冒。
function bubleSort (arr) { var len = arr.length; var temp; for (var i=0; i<len-1; i++) { for(var j=0; j<len-1-i; j++) { //前一个大于后一个则交换位置 if (arr[j] > arr[j+1]) { temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } return arr }
假设第一个最小, 日后寻找比它小的,记下其index,一轮结束后将index上的数与开始假定的数交换位置。
function selectionSort (arr) { var len = arr.length; var minIndex, temp; for (var i=0; i<len-1; i++) { minIndex = i; for (var j=i+1; j<len; j++) { if (arr[minIndex] > arr[j]) { minIndex = j } } temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } return arr; }
打扑克的同志应该比较好理解。假设第一个元素是已经排序好的,将后一个元素提取出来,往前依次比较,比本身大的数日后挪,插入到第一次碰见比本身小的元素后面,组成一个新的序列。
function insertionSort (arr) { var len = arr.length; var current, preIndex; for (var i=1; i<len; i++) { preIndex = i - 1; current = arr[i]; while (preIndex>=0 && current < arr[preIndex]) { arr[preIndex+1] = arr[preIndex]; preIndex--; } arr[preIndex+1] = current; } return arr }
实质为分组插入排序。为了方便理解,借用网上某哥的图,参考连接在下文。
由于是在已经分组排序过的基础上进行插入排序,因此效率高。
//由于核心是插入排序,因此咱们改造直接插入排序 function directInsertionSort(arr, gap) { var len = arr.length; var current, preIndex; for (var i=gap; i<len; i++) { current = arr[gap]; preIndex = i - gap; while (preIndex>=0 && arr[preIndex] > current) { arr[preIndex+gap] = arr[preIndex]; preIndex -= gap; } arr[preIndex+gap] = current; } return arr; } //编写希尔排序函数 function shellSort (arr) { var len = arr.length; var gap = 1; //设置gap(希尔增量),这里咱们给出比较经常使用的h值为h = 3 * h + 1 while (gap < len/3) { gap = gap * 3 + 1; } for (gap; gap>0; gap = Math.floor(gap/3)) { directInsertSort(arr, gap); } return arr; }
碰见的问题,关于参数的传递:函数参数的传递能够分为按值传递和引用传递。
步长序列能够看一下wiki
相似直接插入,后一个元素(拿来比较的元素)与已排序的中间值m = (i-1) >> 1
(位移运算,至关于Math.floor((i-1)/2)
)进行比较,若是i上的值大于m上的值,则与高半区折半比较,最终将比i上值高的区域日后移,将i上的值插入。如
arr = [2, 6, 7, 6, 8] //前三个是已经排好的。 //range = [low, high] = [0, 2], i = 3, current = arr[i] // m = 1, arr[i] >= arr[m], rang = [2, 2] // m = 2, arr[i] < arr[m] // 变换位置 ==> arr = [2, 6, 6, 7, 8] ... ...
function binaryInsertionSort (arr) { var len = arr.length; var low, height, current, m; for (var i=1; i<len; i++) { current = arr[i]; low = 0; height = i-1; while (low <= height) { m = (low + height) >> 1; if (current >= arr[m]) {// = 是为了保证稳定性 low = m + 1; }else { height = m - 1; } } for (var j=i; j>low; j--) { arr[j] = arr[j-1]; } arr[low] = current; } return arr; }
采起分而治之的思想。递归分组、比较产生两个已排序序列,再依次比较两组开头元素,较小的元素放入申请的新数组中。
归并函数能够经过递归、迭代实现。
主要作的两件事就是分解、合并(下面并非按照执行顺序,只是思路):
[3, 5, 6, 2, 9] -------------------------------------- 分: [3, 5] [6, 2, 9] [3] [5] [6] [2, 9] [2] [9] -------------------------------------- 合: [2, 9] [3, 5] [2, 6, 9] [2, 3, 5, 6, 9]
function merge (left, right) { var result = []; while (left.length && right.length) { var item = left[0] <= right[0] ? left.shift() : right.shift(); result.push(item); } return result.concat(left.length ? left : right); } function mergeSort (arr) { var len = arr.length; if (len === 1) { return arr; } var m = len >> 1; var left = arr.slice(0, m); var right = arr.slice(m); return merge(mergeSort(left), mergeSort(right)) }
递归可能会形成堆栈溢出的问题。
主要作的两件事就是分解、合并(下面并非按照执行顺序,只是思路):
[3, 5, 6, 2, 9] -------------------------------------- 分: [3] [5] [6] [2] [9] -------------------------------------- 合: [3, 5] [2, 6] [9] [2, 3, 5, 6] [9] [2, 3, 5, 6, 9]
function merge (left, right) { var result = []; while (left.length && right.length) { var item = left[0] <= right[0] ? left.shift() : right.shift(); result.push(item); } return result.concat(left.length ? left : right); } function mergeSort (arr) { var len = arr.length; var result = []; //分组,每组有i个元素 for (var i=1; i<=len; i*=2) { //比较相邻两组,有一组剩余就退出 for (var j=0; j+i<len; j+=2*i) { left = arr.slice(j, j+i); right = arr.slice(j+i, j+2*i); result = merge(left, right); arr.splice(j, 2*i, ...result) } } return arr }
快速排序是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
步骤:选择一个元素做为基准,下面选择第一个,依次比较后面的元素,将小于基准的元素放在基准的左边,剩余放右边。造成左右两个分区,再递归按以前的步骤排序。
function swap (arr, i, j) { var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } function partition (arr, left, right) { var pivot = left; var index = left + 1; for (var i=index; i<=right; i++) { if (arr[i] < arr[pivot]) { swap(arr, index, i); index++; } } swap(arr, index-1, pivot); return index-1 } function quickSort (arr, left, right) { var len = arr.length; var partitionIndex; left = typeof left === 'number' ? left : 0; right = typeof right === 'number' ? right : len-1; if (left < right) { partitionIndex = partition (arr, left, right); quickSort(arr, left, partitionIndex-1); quickSort(arr, partitionIndex+1, right); } return arr; }
快速排序排序效率很是高. 虽然它运行最糟糕时将达到O(n²)的时间复杂度, 但一般, 平均来看, 它的时间复杂为O(nlogn), 比一样为O(nlogn)时间复杂度的归并排序还要快. 快速排序彷佛更偏心乱序的数列, 越是乱序的数列, 它相比其余排序而言, 相对效率更高.
Chrome的v8引擎为了高效排序, 在排序数据超过了10条时, 便会采用快速排序. 对于10条及如下的数据采用的即是插入排序.