算法可以总结为针对指定问题的方法论,而问题按照类型又有递归、排序、分治、动态规划等分类。
什么是递归?
递归的特点:
- 可以归纳会若干个规模较小的,与原问题形式相同的子问题。
- 临界条件。
汉诺塔问题。
分治法
分治法:将大的问题分割成可以一些可以解决的小问题,如果依然无法解决则继续分割。
可以使用分治法的问题提特征:
- 使用分治法后,难度在降低。
- 问题可分。
- 解可合并。
- 互相独立。
典型即二分法。
- 二分法查找的时间复杂度是 O(logn),这也是分治法的普遍特性。
- 二分法查找的循环次数不确定。
- 二分法处理的原问题必须是有序的。
典型题:
- 找数组 arr = [-1,2,3,4,5,6,6,8,9,10,12,15] 中第一个大于 9 的数:二分法,取中middle + (middle - 1/middle + 1) 对比。
排序
比较排序算法优劣的分析角度:
- 时间复杂度:具体包括最好时间复杂度、最坏时间复杂度以及平均时间复杂度。
- 空间复杂度:如果空间复杂度为1 ,也叫做原地排序。
- 稳定性:稳定性是指相等的数据对象,在排序之后,顺序是否保持不变。
常见的排序算法及思想:
以已下数据为例:
let a = [1, 3, 4, 2, 7, 6, 9, 8];let b = [9, 8, 7, 6, 5, 4, 3, 2, 1];let c = [1, 2, 3, 4, 5, 6, 7, 8, 9];
冒泡排序

function maopao(arr) {let flag = false;for (let i = 1; i < arr.length; i++) {for (let j = 0; j < arr.length - i; j++) {count++;if (arr[j] > arr[j + 1]) {flag = true;let temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}if (flag === false) { //如果一次遍历后没有任何相邻的两个值是逆序的,则说明整个数组就是顺序的。无需再次执行break;}}return arr;}
冒泡排序的复杂度:
- 对于完全顺序的数据,当添加 flag 标志后最好时间复杂度是 O(n),即只需对比一次。
- 对于完全逆序的数据,时间复杂度为O(n+(n-1)+…+1) 即 O(n*n)。
- 平均复杂度也是 O(n*n)。
- 因为不需要额外的空间,所以空间复杂度度为 O(1)。
插入排序

function charu(arr) {for (let i = 1; i < arr.length; i++) {let temp = arr[i]let j = i - 1;for (; j >= 0; j--) {if (arr[j] > temp) {arr[j + 1] = arr[j];} else {break;}}arr[j+1] = temp;}return arr;}
插入排序算法复杂度:
- 对于完全顺序的数据,最好时间复杂度是 O(n),即只需对比一次。
- 对于完全逆序的数据,时间复杂度为O(n+(n-1)+…+1) 即 O(n*n)。
- 平均复杂度也是 O(n*n)。
- 因为不需要额外的空间,所以空间复杂度度为 O(1)。
归并排序

function guibing(arr) {let temp = new Array(arr.length);console.log(`原始数据:${arr.length}`);customMergeSort(arr, temp, 0, arr.length - 1);console.log(`归并排序数据:${arr}`);}function customMergeSort(arr, temp, start, end) {if (start < end) {let mid = Math.floor((start + end) / 2);//对左侧子序列进行递归排序customMergeSort(arr, temp, start, mid);//对右侧子序列进行递归排序customMergeSort(arr, temp, mid + 1, end);//合并customDoubleMerge(arr, temp, start, mid, end);}}function customDoubleMerge(arr, temp, left, mid, right) {let p1 = left;let p2 = mid + 1;let k = left;while (p1 <= mid && p2 <= right) {count++if (arr[p1] <= arr[p2]) {temp[k++] = arr[p1++];} else {temp[k++] = arr[p2++];}}while (p1 <= mid) {count++temp[k++] + arr[p1++];}while (p2 <= right) {count++temp[k++] = arr[p2++];}//复制回原数组for (let i = left; i <= right; i++) {arr[i] = temp[i];}}
归并排序算法复杂度:
- 因为采用了二分法,所以,最好、最坏、平均时间复杂度都是 O(nlogn)
- 空间复杂度上面,因为每次合并操作都要开辟基于数组的临时内存空间,所以空间复杂度为 O(n)。
- 因为归并排序设计交换操作,所以归并排序是不稳定的排序算法。
快速排序

function kuaisu(arr, start, end) {if (start > end) {return;}let i = start;let j = end;let temp = arr[start];let t;while (i < j) {//先从右侧开始,将小于 temp 的值都放到左侧while (temp <= arr[j] && i < j) {j--;}//从左侧开始将大于 temp 的值都放到右侧。while (temp >= arr[i] && i < j) {i++}t = arr[j];arr[j] = arr[i];arr[i] = t;}arr[start] = arr[i];arr[i] = temp;kuaisu(arr, start, j - 1);kuaisu(arr, j + 1, end);}
快速排序复杂度:
- 最好的情况下都是:O(n*logn)
- 最坏的情况下则是O(n*n)。
- 平均则是 O(n*logn)。
- 空间复杂度为 O(1)。
- 因为涉及交换操作,所以快排是不稳定的排序算法。
排序算法总结
最简答的暴力排解时间复杂度是 O(n*n), 例如冒泡和插入排序。
如果利用算法思维去解决问题,使用分治法是,最好则能降低到 O(n*logn)。同时不同的算法又对空间复杂度有不同的消耗。
动态规划
- 最短路径问题。
- 最大子串问题。
