1. 线性搜索
输入一个数组和一个元素,查找该元素是否在这个数组中,如果找到了,返回第一个匹配的元素在数组中的索引,否则返回"not-found"。
(1)第一种常见的写法:
function linearSearch(array, n, x){
var answer = 'not-found';
for( var i = 0; i < array.length; i ++){
if( array[i] == x){
answer = i;
break;
}
}
return answer;
}
```
(2)第一种写法先对数组中的每一个元素先判断是否越界,再判断是否相等。还可以更高效,即不用判断数组是否越界,给数组最后一项设置一个**信号量**,即我们要查找的数 `x`,
* 第一步,将数组最后一项的值 `array[n]`保存再一个变量 `temp`里,将目标值`x`赋给 `array[n]`;
* 第二步,设置一个变量 `i`, 值为 `0`;
* 第三步,只要 `array[n] != x`,执行以下操作:
A. 将 `i` 自增 `1`.
因为第一步的时候将目标值`x`已经赋给了 `array[n]`,所以如果`x`在`array`的范围内一直没有找到,`i` 一直自增,直到 `i == n `,`array [i] == x ` 必定成立,循环结束。
* 第四步,`array[n]` 的值重新赋为 `temp`,目的是检查 `array[n]` 是否真的等于 `x`;
* 第五步,如果 `i < n` 或者 `array [n] = x` ,返回 i 的值作为输出。
function sentinelLinearSearch(array, x){
var n = array.length - 1;
var last = array[n];
array[n] = x;
var i = 0;
var answer = "not-found";
while( array[i] != x ){
i ++ ;
}
array [n] = last;
if ( i < n || array[n] == x){
answer = i;
}
return answer;
}
### 2. 递归
function factorial(n){
if( n==0 ) {
return 1;
} else {
return n * factorial(n-1)
}
}
function recursiveLinearSearch( array, i, x){
var n = array.length - 1;
if ( i > n ) {
return 'not-found';
} else if ( i <= n && array[i] == x) {
return i;
} else {
return recursiveLinearSearch(array, i+1, x);
}
}
#### 3. 二分查找
优点:从包含 n 个元素的数组中执行查找操作仅需 `O(lgn)` 时间。
(1)找到有序数组`array` 中的 `x` ,在任意情况下,我们仅考虑某个子数组,也就是说,介于索引之间的部分数组,将这两个索引记为` p` 和 `r`,
初始时,`p = 0`,`r = n`,即子数组为整个完整数组。
function binarySearch(array, x){
var p = 0, r = array.length - 1;
while ( p <= r ) {
var q = Math.ceil((p + r) / 2);
if( array[q] == x) {
return q;
} else if( array[q] > x) {
r = q - 1;
} else {
p = q + 1;
}
}
return 'not-found';
}
function recursiveBinarySearch(array, p, r, x){
if( p > r ) {
return 'not found';
} else {
var q = Math.ceil(( p + r ) / 2);
if( array[q] == x) {
return q;
} else if ( array[q] > x) {
return recursiveBinarySearch(array, p, q - 1, x);
} else {
return recursiveBinarySearch(array, q + 1, r, x);
}
}
}
### 4. 排序
####(1)选择排序
通过对整体的选择,找到当前数和最小数交换。在一次排序中把最小的元素放在前面。时间复杂度 `O(n^2)`。
交换函数:
function swap (array, a, b){
var temp = array[a];
array[a] = array[b];
array[b] = temp;
}
选择排序:
function selectionSort( array ){
var smallest = 0;
for (var i = 0; i < array.length - 1; i ++) {
smallest = i;
for (var j = i + 1; j < array.length; j ++) {
if (array[j] < array[smallest]) {
smallest = j;
}
}
if (smallest != i){
swap (array, i, smallest);
}
}
return array;
}
对于外层循环的每次迭代,内层循环会执行它的循环体内的所有循环,当 i 等于 0 时,内层循环中 j 从 1 变化到 n - 1,共执行 n - 1 次, 当 i 等于 1 时,内层循环中 j 从 2 变化到 n - 1,共执行 n - 2 次,……,总结可得,内层循环每次执行 n - i 次。内层循环迭代的总数是:
> (n - 1) + (n - 2) + (n -3) + ... + 2 + 1 = n * (n - 1) / 2 = (n^2 - n) / 2
> (n 为数组长度)
省略低阶项 ( -n ) 和常数因子 1/2,我们可以称内层循环的总次数是 `O( n^2 )`。
####(2)插入排序:
不是通过交换位置,而是通过比较找到合适的位置插入元素来达到排序的目的。
function insertionSort( array ){
for (var i = 1; i < array.length; i++){
var target = array[i];
var j = i;
while (j > 0 && target < array[j - 1]){
array[j] = array[j - 1];
j-- ;
}
array[j] = target;
}
return array;
}
因为内部循环的第一次迭代会覆盖 `array[i]`,所以内部迭代开始前先将 `array[i]` 的值保存在 `target` 中。
* 在最好的情况下,即数组一开始就是有序的,内层循环每次迭代 0 次,外层循环迭代 n - 1 次,花费常量时间 `O(n)` 。
* 在最坏的情况下,即数组是逆序的,内层循环每次执行最大次数。外层每迭代 1 次,内层迭代 i 次,外层 i 值从 1 变化到 n - 1,内层的迭代次数构成一个等差数列:
> 1 + 2 + 3 + ... + (n - 2) + (n - 1) = n * (n -1) / 2
> (n 为数组长度)
所以,最坏情况的时间复杂度是 `O(n^2)`。
插入排序和选择排序相比,当数组基本有序时,使用插入排序更好一些。
####(3)归并排序
对于所有情况,它仅有一个 `O(nlgn)` 的时间复杂度。
写法一:
function sort(array, first, last) {
first = (first === undefined) ? 0 : first
last = (last === undefined) ? array.length - 1 : last
if (first >= last) {
return;
}
var middle = Math.floor((first + last) / 2);
sort(array, first, middle);
sort(array, middle + 1, last);
var f = first,
m = middle,
i,
temp;
while (f <= m && m + 1 <= last) {
if (array[f] >= array[m + 1]) { // 这里使用了插入排序的思想
temp = array[m + 1];
for (i = m; i >= f; i--) {
array[i + 1] = array[i];
}
array[f] = temp;
m++;
} else {
f++;
}
}
return array
}
写法二:
function mergeSort( arr ){
var len = arr.length;
if( len < 2) {
return arr;
} else {
var middle = Math.floor(len / 2);
var left = arr.slice(0, middle);
var 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());
}
}
if(left.length){
result.push(left.shift());
} else if (right.length) {
result.push(right.shift());
}
return result;
}
/* function merge(array){
var p = 0, r = array.length - 1 ;
var q = Math.ceil((p + r) / 2);
var n1 = q - p, n2 = r - q;
var array1 = array.slice(p, q),
array2 = array.slice(q, r + 1);
array1[n1] = Math.max(...array1) + 100;
array2[n2 + 1] = Math.max(...array2) + 100;
var i = j = 0;
for(var k = p; k <= r; k++){
if(array1[i] <= array2[j]){
array[k] = array1[i];
i++;
} else {
array[k] = array2[j];
j++;
}
}
return array;
} */
####(4)快速排序
快速排序也采用分治模式,与归并排序的区别在于:
* 快速排序按照原址工作;
* 快排的最坏运行时间是 `O(n^2)`,平均运行时间是 `O(nlgn) `。
function quickSort(array) {
if (arr.length <= 1) {
return arr;
}
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
####(5)堆排序
堆排序是借助堆来实现的选择排序,思想同简单的选择排序,以下以大顶堆为例。如果想升序排序就使用大顶堆,反之使用小顶堆。原因是堆顶元素需要交换到序列尾部。
function heapAdjust(array, start, end){
var temp = array[start];
for(var i = 2 * start + 1; i <= end; i = 2){
//左右孩子的节点分别为2i+1,2*i+2
//选择出左右孩子较小的下标
if(i < end && array[i] < array[i + 1]){
i++;
}
if(temp >= array[i]){
break;
}
array[start] = array[i]; //将子节点上移
start = i; //下一轮筛选
}
array[start] = temp; //插入正确的位置
}
function heapSort(array){
for(var i=array.length/2; i>=0; i--) {
heapAdjust(array, i, array.length-1);
}
for(var i=array.length-1; i>=0; i--) {
swap(array, 0, i);
heapAdjust(array, 0, i-1);
}
return array;
}
function swap (array, a, b){
var temp = array[a];
array[a] = array[b];
array[b] = temp;
}
####(6)希尔排序
希尔排序是插入排序的一种高效率的实现,也叫缩小增量排序。基本思想是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。
/**
* 希尔排序的一趟插入
* @param array 待排数组
* @param d 增量
*/
function shellInsert(array, d){
for(var i = d; i < array.length; i++) {
var temp = array[i];
for(var j = i; j >=d; j -= d){
if (array[j] < array[j - d]) {
array[j] = array[j - d];
} else {
break;
}
}
array[j] = temp;
}
}
function sellSort(array){
var d = Math.floor(array.length / 2);
if (array.length <= 1) {
return array;
}
while(d >= 1) {
shellInsert(array, d);
d =Math.floor(d / 2);
}
return array;
}
---
参考资料:
[bubkoo 的博客](http://bubkoo.com/2014/01/15/sort-algorithm/merge-sort/)
[MERGE SORT 动画演示](http://www.ee.ryerson.ca/~courses/coe428/sorting/mergesort.html)
[快速排序](http://www.ruanyifeng.com/blog/2011/04/quicksort_in_javascript.html)
[面试中的 10 大排序算法总结](http://www.codeceo.com/article/10-sort-algorithm-interview.html)