排序算法

282 阅读3分钟

辅助函数

const Compare = {
  LESS_THAN: -1,
  BIGGER_THAN: 1,
  EQUALS: 0,
}

function defaultCompare(a, b) {
  if (a === b) {
    return Compare.EQUALS
  }
  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN
}

function swap(array, a, b) {
  ;[array[a], array[b]] = [array[b], array[a]]
}

1. 冒泡排序

冒泡排序是比较相邻的两个项,如果第一个比第二个大,则交换他们。元素项向上移动至正确的顺序,就像气泡升至表面一样,冒泡排序因此得名。

function bubbleSort(array, compareFn = defaultCompare) {
  let len = array.length
  for (let i = 0; i < len; i++) {
    for (let j = 0; j < len - 1 - i; j++) {
      if (compareFn(array[j], array[j + 1]) === Compare.BIGGER_THAN) {
        swap(array, j, j + 1)
      }
    }
  }
  return array
}

2. 选择排序

选择排序算法是一种原址比较排序算法,思路是首先找到数据结构中最小的数排在第一位,接着找到第二小的位置排在的二位,以此类推。

function selectionSort(array, compareFn = defaultCompare) {
  const { length } = array
  let indexMin
  for (let i = 0; i < length - 1; i++) {
    indexMin = i
    //找出最小的项
    for (let j = i; j < length; j++) {
      if (compareFn(array[indexMin], array[j]) === Compare.BIGGER_THAN) {
        indexMin = j
      }
    }
    //把最小的项跟第i项交换
    if (i !== indexMin) {
      swap(array, i, indexMin)
    }
  }
  return array
}

3. 插入排序

插入排序每次排一个数据项,以此方式构建最后排序的数组。假定第一项已经排序了。接着,他和第二项进行比较,判断是否需要交换,接着第三项和前面的两项进行比较,判断是否需要交换位置,以此类推

function insertionSort(array, compareFn = defaultCompare) {
  const { length } = array
  let temp
  for (let i = 1; i < length; i++) {
    let j = i
    //temp:保存要插入的项
    temp = array[i]
    //在左边找到刚好左边比temp小,右边比temp大的位置
    while (j > 0 && compareFn(array[j - 1], temp) === Compare.BIGGER_THAN) {
      array[j] = array[j - 1]
      j--
    }
    //找到正确的位置插入
    array[j] = temp
  }
  return array
}

4. 希尔排序

希尔排序是插入排序的优化版。将原来的大数组不断分成更小的数组进行插入排序。

function shellSort(array, compareFn = defaultCompare) {
  const { length } = array
  let gap = Math.floor(length / 2)
  while (gap >= 1) {
    for (let i = gap; i < length; i++) {
      const temp = array[i]
      let j = i
      while (j > gap - 1 && compareFn(array[j - gap], temp) === Compare.BIGGER_THAN) {
        array[j] = array[j - gap]
        j -= gap
      }
      array[j] = temp
    }
    gap = Math.floor(gap / 2)
  }
  return array
}

5. 归并排序

归并排序是一种分而治之算法。其思想是将原始数组切分成较小的数组,直到每一个小数组只有一个位置,接着将小数组归并成较大的数组。直到最后只有一个排序完成的大数组。

//负责合并和排序产生大的数组
function merge(left, right, compareFn) {
  let i = 0
  let j = 0
  const result = []
  while (i < left.length && j < right.length) {
    result.push(compareFn(left[i], right[j]) === Compare.LESS_THAN ? left[i++] : right[j++])
  }
  return result.concat(i < left.length ? left.slice(i) : right.slice(j))
}

function mergeSort(array, compareFn = defaultCompare) {
  if (array.length > 1) {
    const { length } = array
    const middle = Math.floor(length / 2)
    const left = mergeSort(array.slice(0, middle), compareFn)
    const right = mergeSort(array.slice(middle, length), compareFn)
    array = merge(left, right, compareFn)
  }
  return array
}

6. 快速排序

  • 首先,从数组中选择一个值作为主元(pivot),也就是数组中间的那个值。
  • 创建两个指针(引用),左边一个指向数组第一个值,右边一个指向数组最后一个值。移动左指针直到我们找到一个比主元大的值,接着,移动右指针直到找到一个比主元小的值,然后交换它们,重复这个过程,直到左指针超过了右指针。这个过程将使得比主元小的值都排在主元之前,而比主元大的值都排在主元之后。这一步叫作划分(partition)操作。
  • 接着,算法对划分后的小数组(较主元小的值组成的子数组,以及较主元大的值组成的子数组)重复之前的两个步骤,直至数组已完全排序。
//划分操作
function partitiion(array, left, right, compareFn) {
  const pivot = array[Math.floor((right + left) / 2)]
  let i = left
  let j = right

  while (i <= j) {
    //移动left,直到找到一个比pivot大的元素
     while (compareFn(array[i], pivot) === Compare.LESS_THAN) {
       i++
     }
    //rigth,直到找到一个比pivot小的元素
     while (compareFn(array[j], pivot) === Compare.BIGGER_THAN) {
      j--
     }
    if (i <= j) {
      swap(array, i, j)
      i++
      j--
    }
  }
  console.log(i, '888')
  return i
}

function quick(array, left, right, compareFn) {
  let index
  if (array.length > 1) {
    index = partitiion(array, left, right, compareFn)
    if (left < index - 1) {
      quick(array, left, index - 1, compareFn)
    }
    if (index < right) {
      quick(array, index, right, compareFn)
    }
  }
  return array
}

function quickSort(array, compareFn = defaultCompare) {
  return quick(array, 0, array.length - 1, compareFn)
}

7. 计数排序

计数排序是一个分布式排序。分布式排序是使用已组织好的辅助数据结构(桶),然后进行合并,得到排序好的数组。计数排序使用一个用来存储每一个元素在原始数组中出现次数的临时数组。在所有元素都计算完成后,临时数组已排好序并可迭代以构建排序的结果数组。

计数排序是用来排序整数的优秀算法(它是一个整数排序算法),时间复杂度为O(n+k),其中k是临时计数数组的大小,但是,他确实需要更多的内存来存放临时数组。

function findMaxValue(array) {
  let max = array[0]
  for (let i = 1; i < array.length; i++) {
    if (array[i] > max) {
      max = array[i]
    }
  }
  return max
}

function countingSort(array) {
  if (array.length < 2) return array
  const maxValue = findMaxValue(array)

  const counts = new Array(maxValue + 1)
  array.forEach((element) => {
    if (!counts[element]) {
      counts[element] = 0
    }
    counts[element]++
  })
  console.log(counts)

  let sortedIndex = 0
  counts.forEach((count, i) => {
    while (count > 0) {
      array[sortedIndex++] = i
      count--
    }
  })
  return array
}

8. 桶排序

桶排序也是分布式排序算法,他将元素分为不同的桶(较小的数组),在使用一个简单的排序算法来对每一个桶进行排序,然后再将所有的桶合并成结果数组。

//创建桶并将元素分布到不同的桶中
function createBuckets(array, bucketSize) {
  let minValue = array[0]
  let maxValue = array[0]
  //找出最大值和最小值
  for (let i = 1; i < array.length; i++) {
    if (array[i] < minValue) {
      minValue = array[i]
    } else if (array[i] > maxValue) {
      maxValue = array[i]
    }
  }
  //桶的个数
  const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1
  //buckets是一个矩阵(多维数组),buckets中每一个位置包含另外一个数组
  const buckets = []
  for (let i = 0; i < bucketCount; i++) {
    buckets[i] = []
  }
  //将元素分布到桶中
  for (let i = 0; i < array.length; i++) {
    const bucketIndex = Math.floor((array[i] - minValue) / bucketSize)
    buckets[bucketIndex].push(array[i])
  }
  return buckets
}

//对每一个桶进行插入算法和将所有的桶合并为排序后结果的数组
function sortBuckets(buckets) {
  const sortedArray = []
  for (let i = 0; i < buckets.length; i++) {
    if (buckets[i] != null) {
      insertionSort(buckets[i])
      sortedArray.push(...buckets[i])
    }
  }
  return sortedArray
}

function bucketSort(array, bucketSize = 5) {
  if (array.length < 2) return array
  const buckets = createBuckets(array, bucketSize)
  return sortBuckets(buckets)
}

9. 基数排序

基数排序也是一个分布式排序,它是根据数字有效位或基数将整数分布到桶中。基数是基于数组中值的记数制的。

const getBucketIndex = (value, minValue, significantDigit, radixBase) =>
  Math.floor(((value - minValue) / significantDigit) % radixBase);

const countingSortForRadix = (array, radixBase, significantDigit, minValue) => {
  let bucketsIndex;
  const buckets = [];
  const aux = [];
  for (let i = 0; i < radixBase; i++) {
    buckets[i] = 0;
  }
  for (let i = 0; i < array.length; i++) {
    bucketsIndex = getBucketIndex(array[i], minValue, significantDigit, radixBase);
    buckets[bucketsIndex]++;
  }
  for (let i = 1; i < radixBase; i++) {
    buckets[i] += buckets[i - 1];
  }
  for (let i = array.length - 1; i >= 0; i--) {
    bucketsIndex = getBucketIndex(array[i], minValue, significantDigit, radixBase);
    aux[--buckets[bucketsIndex]] = array[i];
  }
  for (let i = 0; i < array.length; i++) {
    array[i] = aux[i];
  }
  return array;
};
function radixSort(array, radixBase = 10) {
  if (array.length < 2) {
    return array;
  }
  let minValue = array[0]
  let maxValue = array[0]
  for (let i = 1; i < array.length; i++) {
    if (array[i] < minValue) {
      minValue = array[i]
    } else if (array[i] > maxValue) {
      maxValue = array[i]
    }
  }
  let significantDigit = 1;
  while ((maxValue - minValue) / significantDigit >= 1) {
    array = countingSortForRadix(array, radixBase, significantDigit, minValue);
    significantDigit *= radixBase;
  }
  return array;
}