一、排序算法:
1. 冒泡排序
- 空间复杂度:
O(1)
- 时间复杂度:
O(n^2)
思路:外层循环管趟数,内层循环管每一趟交换次数。
function bubbleSort(arr) {
for (i = 0; i < arr.length - 1; i++) {
for (j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr
}
优化的冒泡排序(记忆化:下一次比较的终点为上一次发生变化的位置)
function BubbleSortBetter(arr) {
let i = array.length - 1
while(i > 0) {
// 定义发生变化位置 即下一次遍历起始位置
let position = 0;
for(let j = 0; j < i; j++) {
if (array[j] > array[j + 1]) {
// 定义发生变化的位置 确定下一次变化的重点位置
//后面未发生变化的位置上的元素一定比前面还未排序的元素大
position = j;
[array[j], array[j+1]] = [array[j+1], array[j]];
}
}
// 定义下一次遍历的终点,避免重复遍历
i = position;
}
return array;
}
2. 插入排序
- 空间复杂度:
O(1)
- 时间复杂度:
O(n^2)
思路:将左侧序列看为一个有序的序列,每次都将要插入的数字插入到合适的位置。插入时,从有序序列的最右侧开始比较,若比较的数较大,比较的数后移一位,要插入的数继续向前比较知道找到要插入的位置。(与冒泡的思路类似,冒泡是向后找位置,插入是向前找位置)
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
let target = i
for (let j = i - 1; j >= 0; j--) {
if (arr[target] < arr[j]) {
[arr[target], arr[j]] = [arr[j], arr[target]]
target = j
} else {
break
}
}
}
return arr
}
3. 选择排序
- 空间复杂度:
O(1)
- 时间复杂度:
O(n^2)
思路:每次循环都找到一个最小的值放在左侧的有序序列中,每找到一个下次循环的序列长度就向后减一即可
function selectSort(arr) {
for (i = 0; i < arr.length; i++) {
let minIndex = i;
for (j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
[arr[minIndex], arr[i]] = [arr[i], arr[minIndex]]
}
return arr
}
4. 快速排序
- 空间复杂度:
O(logn)
- 时间复杂度:
O(n^2)
思路:每一趟排序将要排序的数据分割成独立的两部分,同时找到middle元素的最终位置,然后通过递归排序分割出的部分,完成排序.
function quickSort(arr) {
//临界值判断
if (arr.length < 2) {
return arr
}
let middle = arr[0];
let left = [];
let right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < middle) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([middle], quickSort(right))
}
//这种快排时间复杂度较高,但比较容易理解和记忆
优化的快速排序(首尾双指针)
- 空间复杂度:
O(1)
- 时间复杂度:
O(nlogn)
使用索引记录的方法,不需要额外空间
(1)记录一个索引l从数组最左侧开始,记录一个索引r从数组最右侧开始
(2)先找到右侧第一个小于target的值array[r],并将其赋值到array[l],l+1
(3)再找到左侧第一个大于target的值array[l],并将其赋值到array[r], r-1
(4)循环2和3步骤,直到l=r时,左侧的值全部小于target,右侧的值全部小于target,l即为target最终位置
(5)递归左边序列和右边序列,完成排序。
function QuickSort2(array, start, end) {
// 当要排序段为0
if (end - start < 1) {
return null
}
// 选择中介值 即第一个值
const target = array[start]
// 左、右索引
let l = start
let r = end
while (l < r) {
// 先从后向前找小于中介值的结点
while (l < r && array[r] >= target) {
r--
}
// 交换位置
if (l < r) {
array[l++] = array[r]
}
// 从前向后找大于中介值的结点
while (l < r && array[l] < target) {
l++
}
// 交换位置
if (l < r) {
array[r--] = array[l]
}
}
// 当结束时 l = r 此时此处即为中介值位置
array[l] = target
// 之后递归排序左边和右边数组
QuickSort2(array, start, l - 1)
QuickSort2(array, l + 1, end)
return array
}
5. 归并排序
- 时间复杂度:
O(nlogn)
- 空间复杂度:
O(n)
思路:归并排序一般分为两步:分离和合并(分而治之的思想)
分离:将数组从中点进行分割,分为左、右两个数组,之后递归分割直到长度小于2
合并:合并时创建一个临时数组,比较两数组(此时通过前面的递归数组均已有序)第一个元素的大小,将较小的插入临时数组,之后直到一方为空,将另一个不为空的数组全部插入临时数组。
function MergeSort(array) {
if (array.length < 2) {
return array
}
// 获取中点索引
const mid = Math.floor(array.length / 2)
// 分为前半段和后半段
let front = array.slice(0, mid)
let end = array.slice(mid)
return merge(MergeSort(front), MergeSort(end))
}
//归并函数
function merge(front, end){
// 创建一个临时存储数组
const temp = []
while(front.length && end.length) {
// 比较两数组第一个元素,将较小的元素取出并加入到临时数组
if (front[0] < end[0]) {
temp.push(front.shift())
} else {
temp.push(end.shift())
}
}
// 若左右数组有一个为空,那么此时另一个数组一定大于temp中的所有元素,直接将所有元素加入temp
while(front.length) {
temp.push(front.shift())
}
while(end.length) {
temp.push(end.shift())
}
return temp
}
二、斐波那契数列
1. 斐波那契数列 (递归)
function Fibonacci(n) {
if (n < 2) {
return n;
} else {
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
2. 斐波那契数列(记忆化)
function Fibonacci(n, memory) {
if (!memory) {
memory = [];
}
if (n <= 2) {
return n;
}
if (!memory[n]) {
memory[n] = Fibonacci(n - 1, memory) + Fibonacci(n - 2, memory);
}
return memory[n];
}
3. 斐波那契数列(动态规划)
function Fibonacci(n) {
let arr = [1, 1];
for (let i = 2; i <= n; i++) {
arr[n] = arr[n - 1] + arr[n - 2];
}
return arr[n];
}
(推荐,简单好记)
参考个人博客文档:极力推荐 数据结构和算法