排序算法
冒泡排序
基本原理:从第一个元素开始,把当前元素和下一个索引元素进行比较。如果当前元素大,那么就交换位置,重复操作直到比较最后一个元素,此时最后一个元素就是该数组中最大的树。下一轮重复以上操作,但最后一个元素已经是最大数了,不需要进行比较,只需要比较到 length - 1 的位置。
function swap(array, left, right) {
let rightValue = array[right]
array[right] = array[left]
array[left] = rightValue
}
function bubble(array) {
if (!array || array.length <= 2) return
for(let i = array.length - 1; i > 0; i--) {
// 从 0 到 length - 1 遍历
for(let j = 0; j < i; j++) {
if (array[j] > array[j + 1]) {
swap(array, j, j + 1)
}
}
}
return array
}
时间复杂度为 O(n * n)
选择排序
基本原理:遍历数组,设置最小值的索引为 0,如果取出的值比当前最小值小,就替换最小值索引,遍历完成后,将第一个元素和最小值索引上的元素交换。第一次遍历完成后,第一个元素就是数组中的最小值,下次遍历就可以从索引 1 开始重复上面的操作。
function swap(array, left, right) {
let rightValue = array[right]
array[right] = array[left]
array[left] = rightValue
}
function selection(array) {
if (!array || array.length <= 2) return
for(let i = 0; i < array.length; i++) {
let minIndex = i
for(let j = i + 1; j < array.length; j++) {
minIndex = array[i] < array[minIndex] ? j : minIndex
}
swap(array, i, minIndex)
}
return array
}
时间复杂度为 O(n * n)
插入排序
基本原理:第一个元素默认是已排序元素,取出下一个元素和当前元素比较,如果当前元素大就交换位置。此时第一个元素就是当前的最小数,所以下次取出操作从第三个元素开始,向前对比,重复之前的操作。
function swap(array, left, right) {
let rightValue = array[right]
array[right] = array[left]
array[left] = rightValue
}
function insertion(array) {
if (!array || array.length <= 2) return
for(let i = 1; i < array.length; i++) {
for(let j = i - 1; j >= 0 && array[j] > array[j + 1]; j--) {
swap(array, j, j + 1)
}
}
return array
}
时间复杂度为 O(n * n)
归并排序
基本原理:递归的将数组两两分开直到最多包含两个元素,然后将数组排序合并,最终合并为排序好的数组
function sort(array) {
if (!array || array.length <= 2) return
mergeSort(array, 0, array.length - 1)
return array
}
function mergeSort(array, left, right) {
// 左右索引相同说明已经只有一个数
if (left === right) return
// 等同于 left + (rigth - left) / 2,使用位运算是因为位运算比四则运算快
let mid = parseInt(left + ((right - left) >> 1))
mergeSort(array, left, mid)
mergeSort(array, mid + 1, right)
let help = []
let i = 0, p1 = left, p2 = mid + 1
while(p1 <= mid && p2 <= right) {
help[i++] = array[p1] < array[p2] ? array[p1++] : array[p2++]
}
while(p1 <= mid) {
help[i++] = array[p1++]
}
while(p2 <= right) {
help[i++] = array[p2++]
}
for(let i = 0; i < help.length; i++) {
array[left + i] = help[i]
}
return array
}
时间复杂度为 O(N * logN)
快速排序
基本原理:随机选取一个数组中的值作为基准值,从左至右取值与基准值对比大小。比基准值小的放数组左边,大的放右边,对比完成后将基准值和第一个比基准值大的值交换位置,然后将数组以基准值的位置分为两部分,继续递归以上操作。
function sort(array) {
if (!array || array.length <= 2) return
quickSort(array, 0, array.length - 1)
return array
}
function quickSort(array, left, right) {
if (left < right) {
swap(array, left, right)
// 随机取值,然后和末尾交换,这样做比固定取一个位置的复杂度略低
let indexs = part(array, parseInt(Math.random() * (right - left + 1) + left, right))
quickSort(array, left, indexs[0])
quickSort(array, indexs[1] + 1, right)
}
}
function part(array, left, right) {
let less = left - 1
let more = right
while(left < more) {
if (array[left] < array[right]) {
// 当前值比基准值小,less 和 left 都加一
++less
++left
} else if (array[left] > array[right]) {
// 当前值比基准值大,将当前值和右边的值交换,并且不改变 left,因为当前换过来的值还没有判断过大小
swap(array, --more, left)
} else {
// 和基准值相同,只移动下标
left++
}
}
// 将基准值和比基准值大的第一个值交换位置,这样数组就变成 [比基准值小, 基准值, 比基准值大]
swap(array, right, more)
return [less, more]
}
function swap(array, left, right) {
let rightValue = array[right]
array[right] = array[left]
array[left] = rightValue
}
二叉树
二叉树的先序、中序、后序遍历
根据访问根节点的时机进行记忆
- 先序遍历表示先访问根节点,然后访问左节点,最后访问右节点
- 中序遍历表示先访问左节点,然后访问根节点,最后访问右节点
- 后续遍历表示先访问左节点,然后访问右节点,最后访问根节点
递归方法实现
function TreeNode(val) {
this.val = val
this.left = this.right = null
}
var traversal = function(root) {
if (root) {
// 先序
traversal(root.left)
// 中序
traversal(root.right)
// 后序
}
}
非递归实现
非递归使用栈的结构,通过栈的先进后出模拟递归实现
- 先序遍历:
function pre(root) { if (root) { let stack = [] stack.push(root) // 判断栈是否为空 while(stack.length > 0) { // 弹出栈顶元素 root = stack.pop() console.log(root) // 先序遍历是先左后右,栈是先进后出,所以先 push 右边,再 push 左边 if (root.right) { stack.push(root.right) } if (root.left) { stack.push(root.left) } } } }
- 中序遍历:
function mid(root) { if (root) { let stack = [] // 中序遍历是先左再根后右,首先应该把最左边节点遍历到底依次 push 进栈 // 当左边没有节点时,就打印栈顶元素,然后寻找右节点 // 对于最左边的叶节点来说,可以把它看成是 null 节点的父节点 // 左边打印不出东西就把父节点拿出来打印,然后再看右节点 while(stack.length > 0 || root) { if (root) { stack.push(root) root = root.left } else { root = stack.pop() console.log(root) root = root.right } } } }
- 后续遍历:
function pos(root) { if (root) { let stack1 = [] let stack2 = [] // 后续遍历是先左再由后跟,所以对于一个栈来说 // 应该先 push 根节点,然后 push 右节点,最后 push 左节点 stack1.push(root) while(stack1.length > 0) { root = stack1.pop() stack2.push(root) if (root.left) { stack1.push(root.left) } if (root.right) { stack1.push(root.right) } } while(stack2.length > 0) { console.log(stack2.pop()) } } }
深度优先遍历
深度优先遍历包括:
- 先序遍历,先访问根节点,然后访问左节点,最后访问右节点
- 中序遍历,先访问左节点,然后访问根节点,最后访问右节点
- 后序遍历,先访问左节点,然后访问右节点,最后访问根节点
先序遍历可以用来显示目录结构等;中序遍历可以实现表达式树,在编译底层很有用;后序遍历可以用来实现计算机目录内的文件及其信息等。
先序遍历
- 递归
function dfs(root) { if (root) { console.log(root) dfs(root.left) dfs(root.right) } }
- 非递归
function dfs(root) { if (root) { const stack = [root] while(stack.length) { root = stack.pop() // 取出栈顶元素 console.log(root) // 因为先序遍历是先左后右,栈是先进后出结构 // 所以先 push 右边再 push 左边 if (root.right) stack.push(root.right) if (root.left) stack.push(root.left) } } }
中序遍历
- 递归
function dfs(root) { if (root) { dfs(root.left) console.log(root) dfs(root.right) } }
- 非递归
function dfs(root) { if (root) { const stack = [] // 中序遍历是先左再根最后右 // 所以⾸先应该先把最左边节点遍历到底依次 push 进栈 // 当左边没有节点时,就打印栈顶元素,然后寻找右节点 // 对于最左边的叶节点来说,可以把它看成是两个 null 节点的⽗节点 // 左边打印不出东⻄就把⽗节点拿出来打印,然后再看右节点 while(stack.length || root) { if (root) { stack.push(root) root = root.left } else { root = stack.pop() console.log(root) root = root.right } } } }
后序遍历
- 递归
function dfs(root) { if (root) { dfs(root.left) dfs(root.right) console.log(root) } }
- 非递归
function dfs(root) { if (root) { const stack1 = [] const stack2 = [] stack1.push(root) while(stack1.length) { root = stack1.pop() stack2.push(root) if (root.left) stack1.push(root.left) if (root.right) stack1.push(root.right) } while(stack2.length) { console.log(stack2.pop()) } } }
广度优先遍历
广度遍历是从二叉树的根结点开始,自上而下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问
- 递归
- 非递归
function bfs(root) { const queue = [node] while(queue.length) { root = queue.shift() console.log(root.value) if (root.left) queue.push(root.left) if (root.right) queue.push(root.right) } }