冒泡排序
冒泡排序会一直比较相邻的两个数,如果顺序错误,就会进行交换。之所以叫冒泡,是因为不停交换,最后结果就会冒出来。
算法步骤
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
function bubbleSort(arr) {
let exchange;
for (let i = 0; i < arr.length; i++) {
exchange = false; // 标识是否有数据交换
for (let j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
exchange = true;
let temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (!exchange) return arr;
}
return arr;
}
平均时间复杂度 O(N2)
当数组已经有序时,一个循环下来没有位置交换,排序结束,时间复杂度为 O(n)。
当数组是逆序时,内循环一共要执行 (n-1)+(n-2)+...+1=n*(n-1)/2,即时间复杂度为 O(n2)。
快速排序
快速排序是从冒泡排序改进的,本质也是通过替换来的,比冒泡快,所以叫快速排序。 下面介绍左右指针法,其他方法还有挖坑法、前后指针法,可以看这里
1、选取基准,例如选取第一个元素作为基准,申明左右指针分别指向数组的头尾
2、将右指针向左移动,当当前元素小于基准时停下
3、将左指针向右移动,当当前元素大于基准时停下
4、将两个指针的值进行交换
5、循环 2-4 步骤,直到左右指针相遇
6、将基准值跟指针指向位置的值进行交换
至此,基准的左边元素都小于等于基准,基准的右边元素都大于等于基准,再递归将左右子数组也按照刚才的步骤处理即可。
function quickSort(arr) {
quick_sort(arr, 0, arr.length - 1);
return arr;
}
function quick_sort(arr, start, end) {
if (start >= end) {
return;
}
let reference = arr[start];
let p1 = start;
let p2 = end;
while (p1 < p2) {
if (p1 < p2 && arr[p2] >= reference) {
p2--;
}
if (p1 < p2 && arr[p1] <= reference) {
p1++;
}
if (p1 < p2) {
let temp = arr[p1];
arr[p1] = arr[p2];
arr[p2] = temp;
}
}
arr[start] = arr[p1];
arr[p1] = reference;
quick_sort(arr, start, p1 - 1);
quick_sort(arr, p1 + 1, end);
}
平均时间复杂度 O(NlogN)
快速排序是递归的,每一次递归,都需要将递归调用时的上线文环境保存到栈中,最大递归调用次数是 log2n , 因此存储开销为 O(nlog2n)
最坏的情况下,待排序序列已经是有序的,这样,进行一次分区后,只得到一个分区,这个分区里的元素个数比上一次少一个元素,如此,需要 n-1 次分区,第一次分区需要遍历 n-1 个元素才能完成分区,第二次需要 n-2 次比较,第三次需要 n-3 次比较,最后一次需要 1 次比较,总的比较次数就是 n*(n-1)/2,约等于 n2 /2,时间复杂度为 O( n2 )。 最好的情况 O(NlogN)
二分查找
在一个有序的序列中,要查找一个元素,我们不需要遍历一遍,而是通过二分法,先找个中间值,如果等于中间值,就找到了,如果比中间值小,就在左边序列去找,如果比中间值大,就去右边找,一旦找到,就返回。
虽然二分查找跟快排都是分治法,把大问题拆成小问题,但是一旦在小问题解决,整体就解决。
function binarySearch(arr, target, left, right) {
if (left > right) {
return false;
}
const middle = Math.floor((right + left) / 2);
if (arr[middle] === target) {
return true;
} else if (arr[middle] > target) {
return binarySearch(arr, target, left, middle - 1);
} else {
return binarySearch(arr, target, middle + 1, right);
}
}
function isInArray(arr, target) {
return binarySearch(arr, target, 0, arr.length - 1);
}
时间复杂度 O(logN) 最快 O(1) 最慢 O(N) 空间复杂度 O(1)
动态规划
和递归相反的技术
通常都会用一个数组来建立一张表,解决每个子问题,最终大问题就会得到解决
核心思想:拆分子问题,记住过往,减少重复计算
解题
- 斐波那契数列 [1,1,2,3,5...]
function feibonacici(n) {
if (n === 1 || n === 2) {
return 1;
}
const arr = [1, 1];
for (let i = 2; i < n; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[n - 1];
}
- 青蛙跳台阶 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
function frogJump(n) {
if (n < 1) {
return 0;
}
if (n === 1) {
return 1;
} else if (n === 2) {
return 2;
} else {
const arr = [1, 2];
for (let i = 2; i < n; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[n - 1];
}
}