二分查找:
function bsearch (A, x) {
let l = 0, // 查找的左边界
r = A.length - 1, // 查找的右边界
guess // 猜测位置
while (l <= r) {
guess = Math.floor( (l + r) / 2 )
// 循环不变式
// guess等于l,r的中间位置
if (A[guess] === x) return guess
else if (A[guess] > x) r = guess - 1
else l = guess + 1
}
}
function bsearch (A, x, l = 0, r = A.length) {
const guess = Math.floor( (l + r) / 2 )
if (l > r) return -1
if (A[guess] === x) return guess
return A[guess] < x ?
bsearch(A, x, guess + 1, r) :
bsearch(A, x, l, guess - 1)
}
插入排序:
1、向已排序数组中插入值:
const A = [2,4,7,9,13]
const x = 8
const b = A.find(a => a > x)
if (b === undefined) {
A.push(x)
} else {
const idx = A.indexOf(b)
A.splice(idx, 0, x)
}
console.log(A)
const A = [2,4,7,9,13]
const x = 8
const b = A.find(a => a > x)
A.splice(idx === -1 ? A.length : idx, 0, x)
console.log(A)
function insert (A, x) {
let p = A.length - 1
while (p >= 0 && A[p] > x) {
A[p + 1] = A[p]
p--
}
A[p + 1] = x
}
const A = [2,4,7,9,13]
const x = 8
insert(A, x)
console.log(A)
2、未排序数组排序
function insert (A, i, x) {
let p = i - 1
while (p >= 0 && A[p] > x) {
A[p + 1] = A[p]
p--
}
A[p + 1] = x
}
function insertion_sort (A) {
for (let i = 1; i < A.length; i++) {
insert(A, i, A[i])
}
}
const A = [5,8,1,2,3,4,9]
insertion_sort(A)
console.log(A)
冒泡排序:
function swap (A, i, j) {
const t = A[i]
A[i] = A[j]
A[j] = t
}
function bubble_sort (A) {
for (let i = A.length - 1; i >= 1; i--) {
for (let j = 1; j <= i; j++) {
if (A[j - 1] > A[j]) {
swap(A, j - 1, j)
}
}
}
}
3、合并排序
先用前闭后开区间来描述一个有序数组
再定义以下变量:
p: 左半边开始,闭区间的值
q:左半边结束,右半边开始
r:右半边结束
function merge (A, p, q, r) {
// 采用前闭后开的方式,符合slice方法
let A1 = A.slice(p, q)
let A2 = A.slice(q, r)
A1.push(Number.MAX_SAFE_INTEGER)
A2.push(Number.MAX_SAFE_INTEGER)
// 往A中回写
for (let k = p, i = 0, j = 0; k < r; k++) {
A[k] = A1[i] < A2[j] ? A1[i++] : A2[j++]
}
}
如果是冒泡排序,时间复杂度是O(n2)
假如有100个数,要排序10000次
但如果把这100个数分成两组,每组分别排序,要排序5050 + 5050 = 5000次 再加上合并走的for循环的次数
// p和r是前闭后开
function merge_sort (A, p, r) {
if (r - p < 2) return
// 数组长度是奇数时,算出的q也是符合左闭右开区间的
const q = Math.ceil( (p + r) / 2 )
merge_sort(A, p, q)
merge_sort(A, q, r)
merge(A, p, q, r)
}
快速排序
下面的代码中各种范围还是采用左闭右开的区间,这么做的好处是:
对于[i, j),j - i就是要处理的长度
partition方法的执行思路:
- 小于中心点范围:[lo, i)
- 未确认范围:[i, j)
- 大于中心点范围:[j, hi - 1)
- 随着每次循环的执行,未确认范围越来越小,终止条件为i > j或i === j
function swap (A, i, j) {
[A[i], A[j]] = [A[j], A[i]]
}
function partition (A, lo, hi) {
// 随便指定一个中心点
const pivot = A[hi - 1]
let i = lo, j = hi - 1
while (i !== j) {
if (A[i] <= pivot) {
i++
} else {
swap(A, i, --j)
}
}
swap(A, j, hi - 1)
return j
}
function qsort (A, lo = 0, hi = A.length) {
if (hi - lo <= 1) return
const p = partition(A, lo, hi)
qsort(A, lo, p)
qsort(A, p + 1, hi)
}
汉诺塔:
思路:
moveTower n A->C use B {
// 先借助C将n-1个盘子从A移动到B
moveTower n-1 A->B use C
// 再将第n个盘子从A移动到C
moveDisk n A->C
// 再借助A将n-1个盘子从B移动到C
moveTower n-1 B->C use A
}
function moveTower (n, from, to, use) {
if (n === 1) {
moveDisk(from, to)
return
} else {
moveTower(n - 1, from, use, to)
moveDisk(from, to)
moveTower(n - 1, use, to, from)
}
}
枚举子集:
// decisions是布尔型数组
function find_subsets (S, decisions) {
if (S.length === decisions.length) {
return [decisions.map((v, i) => v ? S[i] : '').join('')]
}
let r = []
r = r.concat(find_subsets(S, decisions.concat(true)))
r = r.concat(find_subsets(S, decisions.concat(false)))
return r
}
const r = find_subsets('abc', [])
console.log(r)
全排列:
function permutation(str, list) {
if(list.length === str.length) {
return [ list.map(i => str[i]).join('') ]
}
let r = []
for(let i = 0; i < str.length ; i++) {
if(list.indexOf(i) === -1) {
r = r.concat( permutation(str, list.concat(i)) )
}
}
return r
}
console.log(permutation1('abc', []))
数组扁平化:
function fn(arr){
let arr1 = []
arr.forEach((val)=>{
if(val instanceof Array){
arr1 = arr1.concat(fn(val))
}else{
arr1.push(val)
}
})
return arr1
}