前端自学数据结构第一弹——排序

149 阅读3分钟

时间复杂度 / 额外空间复杂度(O)

递归行为及其时间复杂度估计

  • 递归行为:

    递归行为实际是一个压栈过程,它将无法获取具体值的一个过程依次放入栈中,直到获取到具体值,再以出栈方式执行之前保存的过程,若再次遇到无法获取的,再重复上述,直到栈空,返回具体的值。

  • 其时间复杂度估计:

    master公式:T(N) = a*T(N/b) + O(N^d)

    a: 一个过程中包含多少个子过程

    对应三种结果:

    1. log(b,a) > d -> 复杂度为O(N^log(b,a))
    2. log(b,a) = d -> 复杂度为O(N^d * logN)
    3. log(b,a) < d -> 复杂度为O(N^d)

    例子:归并排序 / 快速排序

排序算法

时间复杂度(O(N^2))

冒泡排序

思路:临近的数字两两进行比较,按照从小到大或者从大到小的顺序进行交换。


const bubbleSort = (arr) => {
  for (let i = 0; i < arr.length; i += 1) {
    let temp
    for (let j = 0; j < arr.length - i - 1; j += 1) {
      if (arr[j] > arr[j + 1]) {
        temp = arr[j]
        arr[j] = arr[j + 1]
        arr[j + 1] = temp
      }
    }
  }
  return arr
}

// 给定范围排序
const bubbleRangeSort = (arr, L, R) => {
  if (L >= R) return arr 
  if (L < 0) L = 0
  if (R > arr.length - 1) R = arr.length - 1
    for (let i = L; i < R + 1; i += 1) {
      let temp
      for (let j = L; j < R - (i - L); j += 1) {
        if (arr[j] > arr[j + 1]) {
          temp = arr[j]
          arr[j] = arr[j + 1]
          arr[j + 1] = temp
        }
      }
    }
    return arr
  }

选择排序

思路:从所有序列中先找到最小的,然后放到第一个位置。之后再看剩余元素中最小的,放到第二个位置……以此类推


const chooseSort = (arr) => {
  for(let i = 0; i< arr.length - 1; i+=1) {
    let temp = arr[i] 
    for(let j = i + 1; j< arr.length; j+=1) {
      if (temp > arr[j]) {
        arr[i] = arr[j]
        arr[j] = temp
      }
    }
  }
  return arr
}

插入排序

思路:将一个记录插入到已经排好序的有序表中,从而一个形成一个有序表


const insertSort = (arr) => {
  for(let i = 0; i < arr.length; i+=1) {
    for(let j = 0; j < i; j+=1) {
      if (arr[i] < arr[j]) {
        let temp = arr[i]
        arr[i] = arr[j]
        arr[j] = temp
      }
    }
  }
  return arr
}

时间复杂度(O((N*logN))

加强理解问题

小和问题:

问题: 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。

思路: 中线分组,分成最小元素(形式二叉树),组之前的小和,与前后树杈的小和。

解答:


const smallSum = (arr) => {
  if (arr === undefined || arr === null || arr.length < 2) {
    return arr
  }
  return PartSmallSum(arr, 0 , arr.length - 1)
}

const PartSmallSum = (arr, L, R) => {
  if (L === R) return 0
  const middle = Math.floor(L + ((R - L) >> 1))
  return PartSmallSum(arr, L, middle) + PartSmallSum(arr, middle + 1, R) + margeSmallSum(arr, L, middle, R)
}

// 求出合并后的和*
const margeSmallSum = (arr, L, middle, R) => {
  let help = new Array(R - L + 1)
  let i = 0
  let pl = L
  let pr = middle + 1
  let res = 0

  while(pl <= middle && pr <= R) {
    res += arr[pl] < arr[pr] ? arr[pl]*(R-pr+1) : 0 
    help[i++] = arr[pl] < arr[pr] ? arr[pl++] : arr[pr++]
  }
  while(pl <= middle) {
    help[i++] = arr[pl++]
  }

  while(pr <= R) {
    help[i++] = arr[pr++]
  }

  for(let i = 0; i < help.length; i+=1) {
    arr[L + i] = help[i]
  }
  return res
}

荷兰问题:

问题: 给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的 右边。

思路: 前中后划范围,第一个数大于num,与最后一个数交换,再比较,小就与前一个数交换,等于就不交换。

解答:

const flagQuestion = (arr, num) => {
  if (arr === null || arr === undefined || arr.length < 2) {
    return arr
  }
  let cur = 0
  let pl = 0
  let pr = arr.length -1
  while (cur <= pr) {
    if (arr[cur] > num) {
      arr = changePosition(arr,cur,pr--)
    }
    if (arr[cur] < num) {
      changePosition(arr,cur++,pl++)
    }
    if (arr[cur] === num) {
      cur++
    }
  }
  return arr
}

const changePosition = (arr, l, r) => {
  if (l === r) return arr
  const temp = arr[l]
  arr[l] = arr[r]
  arr[r] = temp
  return arr
}

归并排序

思路:如小和所示

const mergeSort = (arr) => {
  if (arr === undefined || arr === null || arr.length < 2) {
    return arr
  }
  return partMergeSort(arr, 0 , arr.length - 1)
}

const partMergeSort = (arr, L, R) => {
  if (L === R) return arr
  const middle = Math.floor(L + ((R - L) >> 1))
  arr = partMergeSort(arr, L, middle)
  arr = partMergeSort(arr, middle + 1, R)
  arr = margePart(arr, L, middle, R)
  return arr
}

const margePart = (arr, L, middle, R) => {
  let help = new Array(R - L + 1)
  let i = 0
  let pl = L
  let pr = middle + 1

  while(pl <= middle && pr <= R) {
    help[i++] = arr[pl] < arr[pr] ? arr[pl++] : arr[pr++] // [3]
  }
  while(pl <= middle) {
    help[i++] = arr[pl++]
  }

  while(pr <= R) {
    help[i++] = arr[pr++]
  }

  for(let i = 0; i < help.length; i+=1) {
    arr[L + i] = help[i]
  }
  return arr
}

快速排序(随机长期为O((N*logN),不随机可能为O(N^2))

思路:如荷兰国旗

const quickSort = arr => {
  if (arr === null || arr === undefined || arr.length < 2) {
    return arr
  }
  return mergePartition(arr, 0, arr.length - 1)
}

const mergePartition = (arr, l, r) => {
  if (l < r) {
// 加上就是随机快速排序
    arr = changePosition(arr, l + Math.floor(Math.random() * (r - l + 1)), r)
    const p = partition(arr, l, r)
    arr = p.arr
    arr = mergePartition(arr, l, p.l - 1)
    arr = mergePartition(arr, p.more + 1, r)
  }
  return arr
}

const partition = (arr, l, r) => {
  // 以最后一个数作为划分
  let cur = l
  let more = r - 1
  while (cur <= more) {
    if (arr[cur] > arr[r]) {
      arr = changePosition(arr, cur, more--)
    }
    if (arr[cur] < arr[r]) {
      arr = changePosition(arr, cur++, l++)
    }
    if (arr[cur] === arr[r]) {
      cur++
    }
  }
  arr = changePosition(arr, ++more, r)
  return { arr, l, more }
}

const changePosition = (arr, l, r) => {
  if (l === r) return arr
  const temp = arr[l]
  arr[l] = arr[r]
  arr[r] = temp
  return arr
}