历史小剧场
当徐达策马跨进元大都的那一刻,他就完成了历史上第一次成功的北伐,打破了不能由南自北统一的魔咒,自此已离去四百年的燕云十六州又回到了汉民族怀抱。
山河奄有中华地,日月重开大宋天。
插入排序
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
const insertionSort = (arr) => {
const len = arr.length;
for (let i = 1; i < len; i++) {
let preIndex = i - 1;
let current = arr[i];
// 位置i之前,是已排好序的数字,while的作用是找到一个坑位,给当前数字current插入
while (preIndex >= 0 && arr[preIndex] > current) {
// 对大于current的值,往后移一位,给current的插入腾出位置
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
console.log("插入排序 => ", insertionSort(arr))
堆排序
堆排序,是选择排序的优化版本,利用数据结构-树,对数据进行管理。
以大顶堆为例:
- 构建大顶堆;
- 将堆顶的最大数拿出,与堆底的叶子节点进行交换;
- 接着,树剪掉最大数的叶子;
- 再对堆进行调整,重新变成大顶堆;
- 返回步骤2,以此循环,直至取出所有树;
// 7、堆排序
const heapSort = (arr) => {
const len = arr.length;
// 构建大顶堆
const heapify = (arr, i, len) => {
let temp = arr[i];
for (let k = i * 2 + 1; k < len; k = k * 2 + 1) {
// 先判断左右叶子节点,哪个比较大
if (k + 1 < len && arr[k + 1] > arr[k]) {
k++;
}
// 将最大的叶子节点,与当前的值进行比较
if (arr[k] > temp) {
// k节点大于i节点的值,需要交换
arr[i] = arr[k];
i = k;
} else {
break;
}
}
// i是交换后的下标,将temp放到该下标处
arr[i] = temp;
}
// 构建大顶堆
for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
// 开始的第一个节点是 树的最后一个非叶子节点
// 从构建子树开始,逐步调整
heapify(arr, i, len);
}
// 堆排序
for (let j = len - 1; j > 0; j--) {
// 交换堆顶元素和最后一个元素
[arr[0], arr[j]] = [arr[j], arr[0]];
// 重新调整堆
heapify(arr, 0, j);
}
return arr;
}
console.log("堆排序 => ", heapSort(arr))
计数排序
- 计数排序的要点:开辟一块连续格子组成的空间,给数据进行存储;
- 将数组中的数字,依次读取,存入其值对应的下标中;
- 存储完成后,再按照空间的顺序,依次读取每个格子的数据,输出即可。
计数排序要求排序的数据,必须是由范围的整数
// 8、计数排序
const countingSort = (arr) => {
let maxValue = Number.MIN_VALUE;
let minValue = Number.MAX_VALUE;
// 位移,用于处理负数
let offset = 0;
const result = [];
// 取出数组的最大值,最小值
arr.forEach(num => {
maxValue = Math.max(minValue, num);
minValue = Math.min(minValue, num);
})
if (minValue < 0) {
offset = -minValue;
}
console.log("maxValue => ", maxValue)
console.log("minValue => ", minValue)
console.log("offset => ", offset)
// 计数数组
const bucket = new Array(maxValue+offset+1).fill(0); // 初始化连续的格子
// 将数组中的每个数字,根据值放入对应的下标中
arr.forEach(num => bucket[num + offset]++)
// 读取格子中的数
bucket.forEach((store, index) => {
while(store--) {
result.push(index - offset);
}
});
return result;
}
console.log("计数排序 => ", countingSort(arr))
桶排序(分治法+空间换时间)
将数组进行分组,减少排序的数量,再对子数组进行排序,最后合并即可得到结果
// 9、桶排序(分治法+空间换时间)
const bucketSort = (arr, bucketSize = 10) => {
// bucketSize 每个桶可以存放的数字区间 [0, 9]
if (arr.length <= 1) {
return arr;
}
let [maxValue, minValue] = Array.of(arr[0], arr[0]);
let result = [];
// 取出数组的最大值,最小值
arr.forEach(num => {
maxValue = Math.max(maxValue, num);
minValue = Math.min(minValue, num);
})
// 初始化桶的数量
const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
// 初始化桶的容器
// 这里的js语法,不能直接 fill([]),因为生成的二维下标数组,是同一个地址
const buckets = new Array(bucketCount).fill(0).map(() => []); // [[], [], [], [], ...]
// 将数字按照映射的规则,放入桶中
arr.forEach(num => {
const bucketIndex = Math.floor((num - minValue) / bucketSize)
buckets[bucketIndex].push(num);
})
// 遍历每个桶内存储的数字
buckets.forEach(store => {
// 桶内只有1个数字或者空桶,或者都是重复数字,则直接合并到结果中
if (store.length <= 1 || bucketSize === 1) {
result = result.concat(store);
return;
}
// 递归,将桶内的数字,再进行一次划分到不同的桶中
const subSize = Math.floor(bucketSize / 2); // 减少桶内的数字区间,但必须是最少为1
const temp = bucketSort(store, subSize <= 1 ? 1 : subSize);
result = result.concat(temp);
})
return result;
}
console.log("桶排序 => ", bucketSort(arr))
基数排序
- 基数排序,一般是从右到左,对进制位上的数字进行比较,存入[0, 9]的10个桶中,进行排序。
- 从低位开始比较,逐位进行比较,让每个进制位(个、十、百、千、万)上的数字,都能放入对应的桶中,形成局部有序。
// 10、基数排序
const radixSort = (arr) => {
let maxNum = arr[0];
// 求出最大的数字,用于确定最大进制位
arr.forEach(num => {
if (num > maxNum) {
maxNum = num;
}
})
// 获取最大数字有几位
let maxDigitNum = 0;
while (maxNum) {
maxNum = Math.floor(maxNum / 10);
maxDigitNum++;
}
/**
* 求出数字每个进制位上的数字,只支持正整数
* @param {*} num 整数
* @param {*} digit 位数,从0开始
* @returns
*/
const getDigitNum = (num, digit) => {
return Math.floor((num / Math.pow(10, digit)) % 10);
}
// 对每个进位上的数进行排序
for (let i = 0; i < maxDigitNum; i++) {
// 初始化10个桶
let buckets = new Array(10).fill(0).map(() => [])
for (let k = 0; k < arr.length; k++) {
// 获取当前进位上的数字
const bucketIndex = getDigitNum(arr[k], i);
// 排序的数字放入对应桶中
buckets[bucketIndex].push(arr[k]);
}
// 所有数字放入桶中后,现从0-9的顺序将桶中的数字取出
const res = [];
buckets.forEach(bucket => {
bucket.forEach(num => {
res.push(num);
})
})
arr = res;
}
return arr;
}
console.log("基数排序 => ", radixSort(arr))