介绍
此篇属于前端算法入门系列的第三篇,主要介绍数据结构与算法中的的5大排序算法
、2大搜索算法
以及我们刷算法面试题常见的4大算法思想
,总结常见的解题思路,让你的刷题事半功倍。
- 前端算法入门一:刷算法题常用的JS基础扫盲
- 前端算法入门二:时间空间复杂度&8大数据结构的JS实现
- 前端算法入门三:5大排序算法&2大搜索&4大算法思想
- 前端面试算法高频100题(附答案,分析思路,一题多解)
文章主要包含以下内容:
- 冒泡排序
- 快速排序
- 插入排序
- 归并排序
- 选择排序
- 顺序搜索
- 二分搜素
- 分而治之
- 动态规划
- 贪心算法
- 回溯算法
一、5大基础排序算法
1.冒泡排序(常考)
原理如下:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个,如果不是相等的就跳过比下面的元素 ,这样依次的循环下去 直到所有的元素都比较完成才结束。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
function bubbleSort(arr) {
const len = arr.length
if (len <= 1) return
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
const temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
}
// 功能测试
const arr = [4, 3, 6, 2, 5, 7, 9, 8, 1]
bubbleSort(arr)
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
2.快速排序(常考)
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
/**
* @description 快速排序
* @author hovinghuang
*/
/**
* 快速排序 (splice)
* @param arr
* @returns
*/
function quickSort1(arr: number[]): number[] {
const len = arr.length
if (len === 0) return arr
const midIndex = Math.floor(len / 2)
const midValue = arr.splice(midIndex, 1)[0]
const left: number[] = []
const right: number[] = []
// 注意: splice 会修改原数组,所以用 arr.length
for (let i = 0; i < arr.length; i++) {
const n = arr[i]
if (n < midValue) {
left.push(n)
} else {
right.push(n)
}
}
return quickSort1(left).concat([midValue], quickSort1(right))
}
/**
* 快速排序 (slice)
* @param arr
* @returns
*/
function quickSort2(arr: number[]): number[] {
const len = arr.length
if (len === 0) return arr
const midIndex = Math.floor(len / 2)
const midValue = arr.slice(midIndex, midIndex + 1)[0]
const left: number[] = []
const right: number[] = []
for (let i = 0; i < len; i++) {
if (i === midIndex) continue
const n = arr[i]
if (n < midValue) {
left.push(n)
} else {
right.push(n)
}
}
return quickSort2(left).concat([midValue], quickSort2(right))
}
// 功能测试
const testArr3 = [3, 2, 5, 1, 8, 7]
console.info('quickSort2:', quickSort2(testArr3))
3.插入排序
插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
const temp = arr[i];
let j = i;
while (j > 0) {
if (arr[j - 1] > temp) {
arr[j] = arr[j - 1];
} else {
break;
}
j--;
}
arr[j] = temp;
}
}
// 功能测试
const arr = [4, 3, 6, 2, 5, 7, 9, 8, 1]
insertionSort(arr)
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
4.归并排序
分为两步:
- 分割:将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。
- 归并:将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。
function mergeSort(arr) {
if(arr.length === 1) return arr
let mid = Math.floor(arr.length / 2)
let left = arr.slice(0, mid)
let right = arr.slice(mid)
return merge(mergeSort(left), mergeSort(right))
}
function merge(a, b) {
let res = []
while (a.length && b.length) {
if (a[0] < b[0]) {
res.push(a[0])
a.shift()
} else {
res.push(b[0])
b.shift()
}
}
if(a.length){
res = res.concat(a)
} else {
res = res.concat(b)
}
return res
}
// 功能测试
const arr = [4, 3, 6, 2, 5, 7, 9, 8, 1]
console.log(mergeSort(arr)) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
5.选择排序
其基本思想是:
- 首先在未排序的数列中找到最小(or最大)元素,然后将其存放到数列的起始位置。
- 接着,再从剩余未排序的元素中继续寻找最小(or最大)元素,然后放到已排序序列的末尾。
- 以此类推,直到所有元素均排序完毕。
function selectionSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let indexMin = i;
for (let j = i; j < arr.length; j++) {
if (arr[j] < arr[indexMin]) {
indexMin = j;
}
}
if (indexMin !== i) {
const temp = arr[i];
arr[i] = arr[indexMin];
arr[indexMin] = temp;
}
}
}
// 功能测试
const arr = [4, 3, 6, 2, 5, 7, 9, 8, 1]
selectionSort(arr)
console.log(arr) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
6.顺序搜索
function sequentialSearch(arr, target) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === target) {
return i;
}
}
return -1;
};
const arr = [4, 3, 6, 2, 5, 7, 9, 8, 1]
console.log(sequentialSearch(arr, 8)) // 7
7.二分搜索
二分搜索
,也叫折半搜索
,是一种在有序数组
中查找特定元素的搜索算法。所以是用二分查找的前提是数组必须是有序
的.
/**
* 凡是有序,必二分
* 凡是二分,时间复杂度必包含 O(logn)
* 递归代码思路清晰,非递归性能更好
* @description 二分查找 (循环)
* @author hovinghuang
*/
/**
* 二分查找(循环)
* @param arr
* @param target
* @returns
*/
function binarySearch01(arr: number[], target: number): number {
const len = arr.length;
if (len === 0) return -1;
let startIndex = 0;
let endIndex = len - 1;
while (startIndex <= endIndex) {
const midIndex = Math.floor((startIndex + endIndex) / 2); // 将数字向下舍入到最接近的整数
const midValue = arr[midIndex];
if (target < midValue) {
// 目标值较少,则继续在左侧查找
endIndex = midIndex - 1;
} else if (target > midValue) {
// 目标值较大,则继续在右侧查找
startIndex = midIndex + 1;
} else {
return midIndex;
}
}
return -1;
}
/**
* 二分查找(递归)
* @param arr
* @param target
*/
function binarySearch02(arr: number[], target: number, startIndex?: number, endIndex?: number): number {
const length = arr.length
if (length === 0) return -1
// 开始和结束的范围
if (startIndex == null) startIndex = 0
if (endIndex == null) endIndex = length - 1
// 如果 start 和 end 相遇则结束
if (startIndex > endIndex) return -1
// 中间位置
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midValue = arr[midIndex]
if (target < midValue) {
// 目标值较小,则继续在左侧查找
return binarySearch02(arr, target, startIndex, midIndex - 1)
} else if (target > midValue) {
// 目标值较大,则继续在右侧查找
return binarySearch02(arr, target, midIndex + 1, endIndex)
} else {
// 相等,返回
return midIndex
}
}
// 功能测试
// const testArr = [-20, -10, 30];
// const testTarget = 30;
// console.info(binarySearch02(testArr, testTarget));
// 性能测试
// const testArr = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
// const testTarget = 30;
// console.time('binarySearch01')
// for (let i = 0; i < 100 * 10000; i++) {
// binarySearch01(testArr, testTarget)
// }
// console.timeEnd('binarySearch01')
// console.time('binarySearch02')
// for (let i = 0; i < 100 * 10000; i++) {
// binarySearch02(testArr, testTarget)
// }
// console.timeEnd('binarySearch02')
二、4大算法思想
1.分而治之
分而治之
是算法设计中的一种方法。它将一个问题分
成多个和原问题相似的小问题,递归解决
小问题,再将结果合并
以解决原来的问题。
场景一:归并排序
- 分:把数组从中间一分为二
- 解:递归的对两个子数组进行归并排序
- 合:合并有序子数组
场景二:快速排序
- 分:选基准,按基准把数组分成两个子数组
- 解:递归的对两个子数组进行快速排序
- 合:合并两个子数组
leetcode
2.动态规划
动态规划
是算法设计中的一种方法。它将一个问题分解成相互重叠
的子问题,通过反复求解子问题,来解决原来的问题。
场景一:斐波那契数列
- 定义子问题:F(n) = F(n - 1) + F(n - 2)
- 反复执行:从2循环到n,执行上述公式
动态规划和分而治之区别?
- 区别在于子问题是否独立
动态规划
的子问题是重叠
的分而治之
的子问题是独立
的
leetcode
3.贪心算法
贪心算法
是算法设计中的一种方法。期盼通过每个阶段的局部最优
选择,从而达到全局最优,但是结果并不一定是最优的
。常见的反面例子如:零钱兑换问题。
leetcode
4.回溯算法
回溯算法
是算法设计中的一种方法。回溯算法
是一种渐进式寻找并构建问题解决方式的策略。回溯算法
会先从一个可能的动作开始解决问题,如果不行,就回溯并选择另一个动作,直到将问题解决。
什么问题适合用回溯算法
解决?
- 有很多路
- 这些路,有思路,也有出路
- 通常需要递归来模拟所有的路
leetcode
参考文章
您的点赞和评论是我持续更新的动力,感谢关注。