常见的算法浅学一下,二分查找、插入冒泡归并排序

二分查找

二分查找(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)

image-20221028114123751

 // 交换
 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);
posted @ 2023-05-10 10:08  Echoyya、  阅读(65)  评论(1编辑  收藏  举报