冒泡排序
数组中有 n 个数,比较每相邻两个数,如果前者大于后者,就把两个数交换位置;这样一来,第一轮就可以选出一个最大的数放在最后面;那么经过 n-1 轮,就完成了所有数的排序。
/**
* 冒泡排序 O(n²)
*/
function bubbleSort(arr) {
// 循环n-1轮
for (let i = 0; i < arr.length - 1; i++) {
// 每轮结束后,最大的数都放在后面,所以长度减去i
for (let j = 0; j < arr.length - i - 1; j++) {
// 交换
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
}
选择排序
数组中有 n 个数,持续遍历无序部分找到最小的元素的索引,与当前无序的部分开头元素进行交换,这样有序部分+1,遍历n-1轮后,就完成了所有数的排序。
/**
* 选择排序 O(n²)
*/
function selectionSort(arr) {
// 循环n-1轮
for (let i = 0; i < arr.length - 1; i++) {
// 每轮找到最小的元素
let minIdx = i;
for (let j = i + 1; j < arr.length; j++) {
arr[minIdx] > arr[j] && (minIdx = j);
}
// 与当前i位置进行交换
[arr[minIdx], arr[i]] = [arr[i], arr[minIdx]];
}
}
插入排序
数组中有 n 个数,持续遍历无序部分,将循环到的元素插入到前面的有序数组中。优势是在于:如果当前元素比他前一个元素要大,说明已经排序好,可提前终止条件。
/**
* 插入排序 O(n²)
*/
function insertionSort(arr) {
// 循环n-1轮
for (let i = 1; i < arr.length; i++) {
// 插入的位置
let j = i, current = arr[i];
for (;j > 0 && arr[j - 1] > current; j--) {
// 元素后移
arr[j] = arr[j - 1];
}
arr[j] = current;
}
}
归并排序
数组中有 n 个数,递归的将数组拆分俩部分,直到拆解成最小级别,将俩个有序的数组进行合并。合并到顶就是一个排序完成的数组。
/**
* 归并排序 O(nlogn)
* 将数组拆分俩部分分别进行归并排序,再将俩个有序的数组进行合并
*/
function mergeSort(arr) {
_mergeSort(0, arr.length - 1);
// 对数组[l, r]区间进行归并排序
function _mergeSort(l, r) {
if (l >= r) return;
const mid = (l + r) >> 1;
_mergeSort(l, mid);
_mergeSort(mid + 1, r);
// 左半部分最后一个元素小于右侧第一个元素,不用进行合并
if (arr[mid] > arr[mid + 1]) {
_merge(l, r, mid);
}
}
// 将[l, mid]和[mid + 1, r]俩个有序数组进行合并
function _merge(l, r, mid) {
let k = l, i = l, j = mid + 1;
const copyArr = arr.slice(l, r + 1);
while (k <= r) {
if (i > mid) { // 左半部分遍历完成,直接取j
arr[k] = copyArr[j - l];
j++;
} else if (j > r) { // 右半部分遍历完成,直接取i
arr[k] = copyArr[i - l];
i++
} else if (copyArr[i - l] < copyArr[j - l]) {
arr[k] = copyArr[i - l];
i++;
} else {
arr[k] = copyArr[j - l];
j++;
}
k++;
}
}
}
快速排序
从数组中取到一个参考值,通过partition操作将使参考值左侧列表元素都小于参考值,右侧列表都大于参考值。随后对俩部分重复上述的过程。
普通快速排序
function quickSort(array) {
_quickSort(array, 0, array.length - 1)
}
function _quickSort(array, l, r) {
if (l >= r) return
const p = _partition(array, l, r)
_quickSort(array, l, p - 1)
_quickSort(array, p + 1, r)
}
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p + 1...r] > arr[p]
function _partition(arr, l, r) {
// 参考标准,随机取,防止有序数组变成O(n²)
const radomIndex = Math.round(Math.random() * (r - l) + l);
[arr[l], arr[radomIndex]] = [arr[radomIndex], arr[l]]
const v = arr[l]
// arr[l+1...j] < v ; arr[j+1...i) > v
let j = l
for (let i = l + 1; i <= r; i++) {
if (arr[i] < v) {
// 交换: 将小于v的放置到小于列表中的后一位,j进行++
[arr[j + 1], arr[i]] = [arr[i], arr[j + 1]]
j++
}
}
// 将参考值与j进行交换,使得满足左侧小于参考值,右侧大于参考值
[arr[l], arr[j]] = [arr[j], arr[l]]
return j
}
双路快速排序
为了解决数组中存在大量相同元素,导致都在一侧。 使用双路排序,使得相同元素均匀的被分到俩侧。
function quickSort(array) {
_quickSort(array, 0, array.length - 1)
}
function _quickSort(array, l, r) {
if (l >= r) return
const p = _partition(array, l, r)
_quickSort(array, l, p - 1)
_quickSort(array, p + 1, r)
}
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p + 1...r] > arr[p]
function _partition(arr, l, r) {
// 参考标准,随机取
const radomIndex = Math.round(Math.random() * (r - l) + l);
[arr[l], arr[radomIndex]] = [arr[radomIndex], arr[l]]
const v = arr[l]
let i = l + 1, j = r
while(true) {
// 左右一起向中间靠,直到找到不符合的,进行交换
while(i <= r && arr[i] < v) {
i++
}
while (j >= l + 1 && arr[j] > v) {
j--
}
// i > j代表遍历结束
if (i > j) break
[arr[i], arr[j]] = [arr[j], arr[i]]
i++
j--
}
// 将参考值与j进行交换,使得满足左侧小于参考值,右侧大于参考值
[arr[l], arr[j]] = [arr[j], arr[l]]
return j
}
三路快速排序
对数组中存在大量相同元素来说,比双路快速排序性能更好。因为三路快速排序是对相同元素不作处理,而双路快速排序还需要均匀的交换
function quickSort(arr) {
_quickSort(arr, 0, arr.length - 1)
}
function _quickSort(arr, l, r) {
if (l >= r) return
// 随机参考标准
const radomIndex = Math.round(Math.random() * (r - l) + l);
[arr[l], arr[radomIndex]] = [arr[radomIndex], arr[l]]
const v = arr[l]
let lt = l // [l+1...lt] < v
let gt = r + 1 // [gt...r] > v
let i = l + 1 // [lt+1...i) === v
while(i < gt) {
if (arr[i] < v) { // 小于
[arr[i], arr[lt + 1]] = [arr[lt + 1], arr[i]]
lt++
i++
} else if (arr[i] > v) { // 大于
[arr[i], arr[gt - 1]] = [arr[gt - 1], arr[i]]
gt--
} else { // 相等
i++
}
}
// 参考标准与 小于v的最后一个元素交换
[arr[l], arr[lt]] = [arr[lt], arr[l]]
// 对小于、大于俩部分进行排序,中间相等的不做操作
_quickSort(arr, l, lt - 1)
_quickSort(arr, gt, r)
}
堆排序
利用二叉堆的特性(堆中某个节点值都小于父节点的值,完全二叉树),使用数组生成一个最大堆,依次从堆中取出最大的元素,完成排序。
class MaxHeap {
constructor(arr = []) {
this.data = [undefined, ...arr]
this.count = arr.length
// 非叶子节点,进行_shiftDown操作,可以将数组变成堆
for (let i = Math.floor(this.count / 2); i >= 1; i--) {
this._shiftDown(i)
}
}
get size() {
return this.count
}
get isEmpty() {
return this.count === 0
}
insert(element) {
this.count++
this.data[this.count] = element
this._shiftUp(this.count)
}
extractMax() {
if (this.count === 0) {
throw new Error('堆为空')
}
const ret = this.data[1]
this.data[1] = this.data[this.count]
this.data.length = this.count
this.count--
this._shiftDown(1)
return ret
}
// 将k对应的元素与父亲节点进行比较,如果大于父亲节点就交换,一直到合适的位置
_shiftUp(k) {
while (k > 1 && this.data[Math.floor(k / 2)] < this.data[k]) {
const parentIndex = Math.floor(k / 2);
[this.data[parentIndex], this.data[k]]
= [this.data[k], this.data[parentIndex]]
k = parentIndex
}
}
// 将k对应的元素与孩子进行比较,找到孩子中最大的值,与元素进行比较,大于就进行交换。
_shiftDown(k) {
while (2 * k <= this.count) {
let j = 2 * k
// 右孩子大于左孩子,把j切换成右孩子索引
if (j + 1 <= this.count && this.data[j + 1] > this.data[j]) {
j += 1
}
if (this.data[k] >= this.data[j]) {
break
}
[this.data[k], this.data[j]] = [this.data[j], this.data[k]]
k = j
}
}
}
function heapSort(arr) {
const maxHeap = new Heap(arr);
let i = arr.length - 1;
while (!maxHeap.isEmpty()) {
arr[i--] = maxHeap.extractMax();
}
}