1、冒泡排序的优化
列表每相邻的数,若前面的大于后面的,则交换两个数。
长度为【length】的列表,最后剩下的一个数是默认有序的,第一次for循环 i 的边界为length - 1。
经过第 i 次循环,有序部分的长度为 i,无序部分长度为length - i。
第二层for循环j的边界为 length - i - 1。
// 冒泡排序的优化
function demo(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let flag = false;
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]];
flag = true; //如果元素换位,则flag变为true
// console.log('观察代码运行的过程', arr);
}
}
if (!flag) {
console.log(`flag为false,余下列表没有换位,余下列表为有序列表`);
break; //跳出i的循环
}
}
return arr;
}
// const arr = [4, 5, 3, 1, 2];
const arr = [1, 2, 3, 4, 5];
console.log(demo(arr));
2、选择排序
记录无序区最小值的索引,默认无序区索引最小值为无序区第一个。
将得到的无序区最小值和无序区的第一个值交换,意为将无序区的第一个位置变为有序区,-->左侧有序区间扩增。
function demo(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let minIndex = i; //无序区的第一个索引先记作为本次循环的最小值
for (let j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j; //发现更小的j
}
}
// 最小值就是无序区的第一个数字,minIndex没有发生改变,是不需要换位的(优化)
if (i !== minIndex) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
// console.log('观察代码运行的过程', arr);
}
return arr;
}
const arr = [4, 5, 3, 1, 2];
// const arr = [1, 2, 3, 4, 5];
console.log(demo(arr));
3、插入排序
想象自己在打扑克,有手就行。
function demo(arr) {
//摸牌的范围是1~length
for (let i = 1; i < arr.length; i++) {
tmp = arr[i]; //记录摸到的牌
j = i; //手中牌的下标
// 左侧的牌比摸到的牌大,则将左侧的排依次向右移
while (j > 0 && arr[j - 1] > tmp) {
arr[j] = arr[j - 1];
j--;
}
// 左侧的牌小于等于摸到的牌,将摸到的牌插入左侧的牌之后
arr[j] = tmp;
// console.log('观察代码运行的过程', arr);
}
return arr;
}
const arr = [4, 5, 3, 1, 2];
// const arr = [1, 2, 3, 4, 5];
console.log(demo(arr));
4、快速排序
function demo(arr, left = 0, right = arr.length - 1) {
if (left < right) {
let mid = partition(arr, left, right);
demo(arr, left, mid - 1);//递归mid的左侧部分
demo(arr, mid + 1, right);//递归mid的右侧部分
}
return arr;
// 归位函数
function partition(arr, left, right) {
tmp = arr[left]; //选定一个数字作为基数
while (left < right) {
while (arr[right] >= tmp && left < right) {
right--;//1.right大于基数,则right指针左移(2.right忽略比基数大的,只去找比基数小的,找到后将其与left指针交换)
}
arr[left] = arr[right];//2.right指针找到小于基数的数字,将此数字与left交换
while (arr[left] <= tmp && left < right) {
left++;
}
arr[right] = arr[left];
}
arr[left] = tmp;
return left;
}
}
const arr = Array.from(Array(5000), (v, k) => k).sort((a, b) => Math.random() - Math.random());
// console.log('未排序的', arr);
console.log(demo(arr));
5、堆排序
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆。
思路:将大顶堆的根节点与最后一个叶子节点交换的,砍掉最后一个节点,此时根节点下的两个子树都是大顶堆,但是加上根节点不符合大顶堆的性质,为了维护大顶堆的性质,从根节点开始heapify。
// 交换值
const swap = (A, i, j) => {
let temp = A[i];
A[i] = A[j];
A[j] = temp;
};
// 维护最大堆性质
const maxHeapify = (A, heapSize, i) => {
let l = i * 2 + 1;
let r = i * 2 + 2;
let largest = i; //根、左子、右子比较,择最大与根交换
if (l < heapSize && A[l] > A[i]) {
largest = l;
}
if (r < heapSize && A[r] > A[largest]) {
largest = r;
}
if (largest !== i) {
swap(A, i, largest);
maxHeapify(A, heapSize, largest);//largest标记了比根大的子节点,继续向下。。。maxHeapify
}
return A;
};
//从最小子树开始向上build大顶堆
const buildMaxHeap = A => {
//边界:由完全二叉树性质得知,子节点i找父节点,父节点为(i-1)/2,数组索引0~n-1.故(n-2)/2==>Math.floor(n/2)-1为最后一个叶子节点的根。
for (let i = Math.floor(A.length / 2) - 1; i >= 0; i--) {
maxHeapify(A, A.length, i);
}
};
const heapSort = A => {
buildMaxHeap(A);
A.heapSize = A.length;
for (let i = A.length - 1; i >= 0; i--) {
swap(A, 0, i); //交换最大的和最后一个叶子节点
A.heapSize--;
maxHeapify(A, A.heapSize, 0); //根节点不再是大根堆,从根开始heapify
}
return A;
};
console.log(heapSort([3, 4, 5, 1, 5, 7, 2]));
5.1打印前k大个数字(需要用到小顶堆)
// 前k大问题,要用到小根堆
// 交换值
const swap = (A, i, j) => {
let temp = A[i];
A[i] = A[j];
A[j] = temp;
};
// 维护最小堆性质
const minHeapify = (A, heapSize, i) => {
let l = i * 2 + 1;
let r = i * 2 + 2;
let largest = i; //根、左子、右子比较,择最小与根交换
if (l < heapSize && A[l] < A[i]) {
largest = l;
}
if (r < heapSize && A[r] < A[largest]) {
largest = r;
}
if (largest !== i) {
swap(A, i, largest);
minHeapify(A, heapSize, largest); //largest标记了比根小的子节点,继续向下。。。minHeapify
}
return A;
};
const topk = (A, k) => {
// 前k个元素构建成最小堆
let minHeap = A.splice(0, k);
for (let i = Math.floor(minHeap.length / 2) - 1; i >= 0; i--) {//从第一个非叶节点向上执行
minHeapify(minHeap, minHeap.length, i);
}
minHeap.heapSize = minHeap.length;
// 剩余部分依次和小顶堆的顶部比较,大于小顶部堆顶部的会代替小顶堆顶部,然后heapify
for (let i = 0; i < A.length; i++) {
if (A[i] > minHeap[0]) {
minHeap[0] = A[i];
minHeapify(minHeap, minHeap.heapSize, 0);
}
}
return minHeap; //因为小顶堆的缘故,这里打出来时倒序的
};
console.log(topk([3, 4, 5, 1, 8, 7, 2], 5));
6、归并排序(先拆分再合并,顾名思义)
// 一次归并
const merge = (li, low, mid, high) => {
let i = low;
let j = mid + 1;
arr = []; //临时变量,非原地排序
while (i <= mid && j <= high) {
if (li[i] < li[j]) {
arr.push(li[i]);
i++;
} else {
arr.push(li[j]);
j++;
}
}
while (i <= mid) {
arr.push(li[i]);
i++;
}
while (j <= high) {
arr.push(li[j]);
j++;
}
// li.splice(low, arr.length, ...arr);
li.splice(low, high - low + 1, ...arr); //将数组中某一部分替换成排序好的顺序
};
const mergeSort = (li, low, high) => {
//至少有两个元素,
if (low < high) {
let mid = Math.floor((low + high) / 2);
mergeSort(li, low, mid); //递归左侧
mergeSort(li, mid + 1, high); //递归右侧
merge(li, low, mid, high); //将左侧和右侧合并起来
}
return li;
};
const li = Array.from(Array(5000), (v, k) => k).sort((a, b) => Math.random() - Math.random());
console.time();
console.log(mergeSort(li, 0, li.length - 1));
console.timeEnd();
7、希尔排序(插入的基础上做切片操作,时间复杂度基于切片的大小)
function insertSort(li, gap) {
for (let i = gap; i < li.length; i++) {
let tmp = li[i];
let j = i - gap;
while (j >= 0 && tmp < li[j]) {
li[j + gap] = li[j];
j -= gap;
}
li[j + gap] = tmp;
}
}
function shellSort(li) {
let gap = Math.floor(li.length / 2);
while (gap > 0) {
insertSort(li, gap);
gap = Math.floor(gap / 2);
}
return li;
}
const li = Array.from(Array(5000), (v, k) => k).sort((a, b) => Math.random() - Math.random());
console.time();
console.log(shellSort(li));
console.timeEnd();
8、计数排序(利用数组索引,记录出现次数)
function demo(arr, maxCount) {
let count = new Array(maxCount + 1); //桶的容量
let arrIndex = 0;
for (let i = 0; i < arr.length; i++) {
if (!count[arr[i]]) {
count[arr[i]] = 1;
} else {
count[arr[i]] += 1;
}
}
for (let j = 0; j < count.length; j++) {
while (count[j] > 0) {
arr[arrIndex++] = j;
count[j]--;
}
}
return arr;
}
const li = Array.from(Array(101), (v, k) => k).sort((a, b) => Math.random() - Math.random());
// const li = [1, 3, 2, 4, 1, 2, 3, 1, 3, 5];
console.time();
console.log(demo(li, 100));
console.timeEnd();
9、桶排序1.0
将元素分在不同的桶中,在对每个桶中的元素排序
因为桶本身是有序的(例如:0 ~ 99为0号桶,100 ~ 199为1号桶),桶内的元素也是有序的,将所有桶的集合扁平化即为结果
function bucketSort(li, n = 100, maxNum = 10000) {
// 创建二维数组,对应着n个桶,0~99
const buckets = Array.from(new Array(n), () => {
return new Array();
});
for (const x of li) {
// 判断x在几号桶里,把1w放到99号桶里
let i = Math.min(parseInt(x / parseInt(maxNum / n)), parseInt(n - 1));
buckets[i].push(x); //将x放入到应该去的桶之中
// 在桶中调整位置,冒泡
for (let j = buckets[i].length - 1; j >= 1; j--) {
if (buckets[i][j] < buckets[i][j - 1]) {
[buckets[i][j], buckets[i][j - 1]] = [buckets[i][j - 1], buckets[i][j]];
} else {
break;
}
}
}
return buckets.flat(Infinity); //扁平
}
const li = Array.from(Array(5000), (v, k) => k).sort((a, b) => Math.random() - Math.random());
console.time();
console.log(bucketSort(li));
console.timeEnd();
基数排序
function radixSort(arr) {
let buckets = new Array(10); // 定义二维数组buckets,每个桶就是一个一维数组
for (let i = 0; i < buckets.length; i++) {
buckets[i] = new Array(arr.length); //防止数据溢出,将每个桶的容量设置为极限的arr.length(空间换时间)
}
let buckeElementCounts = new Array(10).fill(0); //一维数组记录每个桶中存放数据的个数
let max = Math.max(...arr); //得到数组中最大值
let maxLength = (max + '').length; //得到最大值的位数
for (let i = 0, n = 1; i < maxLength; i++, n = n * 10) {
// 每一轮,对每个元素的各个位数进行排序处理
// 第一次是个位数,第二次是十位数,第三次是百位数
for (let j = 0; j < arr.length; j++) {
//取出每个元素的各位的值
let digitOfElement = Math.floor(arr[j] / n) % 10;
buckets[digitOfElement][buckeElementCounts[digitOfElement]] = arr[j];
buckeElementCounts[digitOfElement]++;
}
let index = 0;
for (let k = 0; k < buckeElementCounts.length; k++) {
// 如果桶中有数据,才放入原数组
if (buckeElementCounts[k] !== 0) {
// 循环该桶即第k个桶,即第k个一维数组,放入
for (let l = 0; l < buckeElementCounts[k]; l++) {
//取出元素放入arr
arr[index] = buckets[k][l];
//arr下标后移
index++;
}
// 每轮处理后,下标要清0
buckeElementCounts[k] = 0;
}
}
}
//按照这个桶的顺序,以数组的下标依次取出数据,放入原来的数组中
return arr;
}
// const arr = [123456, 10, 11, 1, 1, 1, 1, 2, 4, 3, 5, 6, 8, 7, 9];
const arr = Array.from(Array(5000), (v, k) => k).sort((a, b) => Math.random() - Math.random());
console.time();
console.log(radixSort(arr));
console.timeEnd();