常见的算法浅学一下,二分查找、插入冒泡归并排序
二分查找
二分查找(Binary search)也称折半查找,是一种效率较高的查找方法。但是要求数组必须是有序的。
- 最好时间复杂度是: O(1),最好情况下只需要进行1次比较就能找到目标元素
- 最坏**时间复杂度是: O(log2n),最坏情况下查找至最后一个元素,或查找不到目标元素
- 平均**时间复杂度是: O(log2n)
while循环写法:
function bSearch(arr, tar) {
let l = 0, // left 查找范围左边界
r = arr.length - 1, // right 查找范围右边界
g // guess 猜想位置
while (l <= r) {
g = Math.floor((l + r) / 2) // 循环不变式
if (arr[g] === tar) return g
else if (arr[g] > tar) r = g - 1
else l = g + 1
}
return -1
}
const A = [3, 5, 19, 22, 25, 33, 45, 47, 57, 66, 71, 78]
console.log(bSearch(A, 88))
console.log(bSearch(A, 68))
console.log(bSearch(A, 22))
二分查找递归写法的递归树写法:
function binarySearch(A, p, r, x) {
const guess = Math.floor((r + p) / 2)
if (p >= r) return -1
if (A[guess] === x) return guess
return A[guess] > x ? binarySearch(A, p, guess - 1, x) : binarySearch(A, guess + 1, r, x)
}
const A = [3, 5, 19, 22, 25, 33, 45, 47, 57, 66, 71, 78]
console.log(binarySearch(A, 0, A.length - 1, 8));
console.log(binarySearch(A, 0, A.length - 1, 19));
插入排序
插入排序是一种比较原始的排序方法,接近于人的理解。时间复杂度是: O(n^2)
1 - javascript的原始实现
function insertOrigin(arr, num) {
const tmp = arr.find(a => a > num) // tmp代表第一个大于 num 的数字
if (tmp === undefined) { // 代表所有元素都比num小
arr.push(num)
} else {
const idx = arr.indexOf(tmp)
arr.splice(idx, 0, num)
}
console.log(arr)
}
const A = [2, 4, 7, 9, 13] // 原数组
const x = 8 // 需要插入的元素
insertOrigin(A, x)
2 - javascript的原始实现 —— 优化
function insertOriginOpti(arr, num) {
const tmp = arr.find(a => a > num)
const idx = arr.indexOf(tmp)
arr.splice(idx === -1 ? arr.length : idx, 0, num)
console.log(arr)
}
const B = [2, 4, 7, 9, 13] // 原数组
const x = 8 // 需要插入的元素
insertOriginOpti(B, x)
3 - 抛开原始实现。更好的算法实现
function insertBetter(arr, num) {
let p = arr.length - 1 // p 指向下一个需要比较的元素,p+1指向空位
while (p >= 0 && arr[p] > num) {
arr[p + 1] = arr[p]
p--
}
arr[p + 1] = x
console.log(arr);
}
const A = [2, 4, 7, 9, 13] // 原数组
const x = 8 // 需要插入的元素
insertBetter(A, x)
4 - 完整的插入并排序的计算过程
常见问题:如何在一个有序数组中插入一个新值,新数组仍保持有序?
function insertion_sort(A) {
for (let i = 1; i < A.length; i++) {
insert(A, i, A[i])
}
}
function insert(A, i, x) {
let p = i - 1
while (p >= 0 && A[p] > x) {
console.log(p + 1, p, A[p + 1], A[p]);
A[p + 1] = A[p]
p--
}
A[p + 1] = x
}
const A = [5, 8, 1, 3, 2, 4, 9]
insertion_sort(A)
console.log(A)
冒泡排序
冒泡排序(bubble sort)也称作下沉排序(sinking sort),它重复比较相邻的两个元素,直到整个数组都没有数字可以在进行交换,时间复杂度是: O(n^2)
// 交换
function swap(A, i, j) {
const t = A[i]
A[i] = A[j]
A[j] = t
}
// 第一种写法
function bubble_sort(A) {
for (let i = 0; i < A.length - 1; i++) {
for (let j = 0; j < A.length - i - 1; j++) {
A[j + 1] < A[j] && swap(A, j + 1, j)
}
}
}
// 第二种写法
function bubbleSort(A) {
for (let i = A.length - 1; i >= 1; i--) {
for (let j = 1; j <= i; j++) {
A[j - 1] > A[j] && swap(A, j - 1, j)
}
}
}
const A = [5, 8, 1, 4, 6, 2, 3]
const B = [5, 8, 1, 4, 6, 2, 3]
bubble_sort(A)
bubbleSort(B)
console.log(A);
console.log(B);
归并排序
归并排序也叫合并排序,将原数组拆分成若干个子数组,然后合并,目前应用的越来越广泛了,常见问题就是如何合并两个有序数组?,它可以保证:
最坏情况的时间复杂度是:O(nlgn),最好情况的时间复杂度是: O(nlgn),空间复杂度:O(n)
O(nlgn)的解释是:将一个数据集分成两半,然后将分开的每一半再分成两半,依此类推,在此过程中同时遍历每一半数据
首先要能够归并两个有序数组,换句话说就是合并两个有序数组为一个有序数组。
// A:数组
// p:左半边开始位置
// q:左半边结束,右半边开始位置
// r:右半边结束
function merge_sort(A, p, q, r) {
let A1 = A.slice(p, q) // 存放左半边的临时空间
let A2 = A.slice(q, r) // 存放右半边的临时空间
// 追加哨兵
A1.push(Number.MAX_SAFE_INTEGER)
A2.push(Number.MAX_SAFE_INTEGER)
for (let k = p, i = 0, j = 0; k < r; k++) {
// 循环不变式
// k: 下一个写入位置
// i: A1中获回写位置
// j: A2中回写位置
A[k] = A1[i] < A2[j] ? A1[i++] : A2[j++]
}
}
const A = [1, 3, 5, 2, 4, 6]
const B = [2, 4, 6, 1, 3, 5]
const C = [1, 2]
merge_sort(A, 0, 3, 6)
merge_sort(B, 1, 3, 5)
merge_sort(C, 1, 2)
console.log(A, B, C);
而归并排序就是先使用递归将数组中元素进行拆分,直至拆分得到单个元素作为一个数组,此时就可以将其看作一个有序数组(只有一个元素自然是有序的)进行归并。最终将所有元素归并得到一个有序数组。
归并排序主要涉及两个部分:一个是递归问题,另一个就是有序数组进行归并(也就是第一部分的合并的代码内容)
function merge_sort(A, p, r) {
if (r - p < 2) { return }
const q = Math.ceil((p + r) / 2) // 拆分得到单个元素
merge_sort(A, p, q) // 实现左半边的排序
merge_sort(A, q, r) // 实现右半边的排序
merge(A, p, q, r)
}
function merge(A, p, q, r) {
let A1 = A.slice(p, q)
let A2 = A.slice(q, r)
A1.push(Number.MAX_SAFE_INTEGER)
A2.push(Number.MAX_SAFE_INTEGER)
for (let k = p, i = 0, j = 0; k < r; k++) {
A[k] = A1[i] < A2[j] ? A1[i++] : A2[j++]
}
}
const A = [39, 27, 43, 3, 9, 82, 10]
merge_sort(A, 0, A.length)
console.log(A);