目录
概览
十大排序算法可以分成两类:
- 比较类别排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
算法的时间复杂度:
相关概念:
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
!!以下算法均按照升序排序!!
一、冒泡排序
1、算法描述
从头开始,一次比较相邻的两个元素,如果前一个元素比后一个元素小,则往后移动一个元素继续比较;如果前一个元素比后一个元素大,则两个元素交换位置,再往后移一个元素继续比较。
每一趟比较下来,最后一个元素肯定是当前趟最大的,进行下一趟比较时,该元素就不再参与比较了。
2、图示
3、代码
function bubbleSort(arr) {
let len = arr.length;
// 一共比较多少趟
for (let i = 0; i < len - 1; i++) {
// 每趟进行比较,j=要进行排序的元素个数,每一趟排下来最后1个元素都不进行下一趟的排序
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 12, 46, 4, 19, 50, 48];
console.log(bubbleSort(arr)); // [3, 4, 5, 12, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
二、选择排序
1、算法描述
每趟比较中找到最小的元素,将最小的元素与首元素进行位置交换,首元素就不再进行下一趟排序,重复操作直到完成排序
2、图示
3、代码
function selectSort(arr) {
let len = arr.length;
for (let i = 0; i < len - 1; i++) {
let minIndex = i; // 记录最小元素的索引
for (let j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j; // 如果有遇到比首元素小的,就记录该索引,这样就能找到当前趟最小元素的索引
}
}
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; // 交换首元素和最小元素的位置
}
return arr;
}
let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 12, 46, 4, 19, 50, 48];
console.log(selectSort(arr)); // [3, 4, 5, 12, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
三、插入排序
1、算法描述
- 第一个元素默认已排好序,从第二个元素开始。
- 如果第二个元素比第一个元素小,那就交换二者位置,否则不交换,前两个元素的顺序已经固定。
- 第三个元素与前两个元素进行比较,从后向前比,先与第二个元素比,如果比第二个元素小,再与第一个元素比,比第一个元素小,就放在第一个元素的位置,第一个元素和第二个元素向后移一个位置;如果第三个元素不比第二个元素小,不交换位置,这趟的排序以固定,也就是前三个元素的大小顺序已经排好。
- 之后的排序也是这么进行。
2、图示
3、代码
function insertSort(arr) {
let len = arr.length;
for (let i = 1; i < len; i++) {
let now = arr[i]; // 当前趟的元素
let lastIndex = i - 1; // 已经排好序的序列里的最后一个元素
// 从后向前遍历已经排好序的序列
// 如果当前趟的元素now比已经排好序的序列的元素小
while (lastIndex >= 0 && arr[lastIndex] > now) {
arr[lastIndex + 1] = arr[lastIndex]; // 将比now大的元素向后移一个位置
lastIndex--;
}
arr[lastIndex + 1] = now; // 将当前趟的元素now插入到已经排好序的序列里
}
return arr;
}
let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 12, 46, 4, 19, 50, 48];
console.log(insertSort(arr)); // [3, 4, 5, 12, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
四、希尔排序
插入排序的改进版。与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。
1、算法描述
- 选择一个增量 k=arr.length/2 进行排序,增量k为几就将该arr分成几组,每组有 arr.length/k=2 个元素。然后组内元素从前往后进行比较,按照升序排列好,再将小组并成一个大arr。
- 再次分组,分成k/2组,每个组有 arr.length/k/2 个元素,再次将组内元素按升序进行排序,排序好在并成大arr。
- 重复上述操作,直至arr排好序。
- 插入排序时,并不是一个分组内的数字一次性用插入排序完成,而是每个分组交叉进行。
2、图示
3、代码
function shellSort(arr) {
let len = arr.length;
// 第一次分组是k=arr.length/2,之后的每次分组都是k/2
for (let k = Math.floor(len / 2); k > 0; k = Math.floor(k / 2)) {
// 遍历每个分组的元素,i在分组里表示的是每个组内最后一个元素
// 在原数组arr里表示的是分组后的第一个元素,然后后面的元素依次与前面的元素进行比较
for (let i = k; i < len; i++) { // i控制当前进行排序的是哪一个小组
let j = i; // j控制的是当前小组的最后一个元素
// 各小组内进行比较时,按升序排列
while (j - k >= 0 && arr[j] < arr[j - k]) {
[arr[j], arr[j - k]] = [arr[j - k], arr[j]];
j = j - k; // 让j变成当前小组的上一个元素
}
}
}
return arr;
}
let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 12, 46, 4, 19, 50, 48];
console.log(shellSort(arr)); // [3, 4, 5, 12, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
五、并归排序
分治法
1、算法描述
把长度为n的arr分成两个长度为n/2的子序列,一直划分到最小个数之后,两两比较,合并成长度为2的小序列,然后再小序列间比较,一直重复合并成最终的arr
2、图示
![](https://img-blog.csdnimg.cn/img_convert/9eaaaa0bac482a590f85d0da73cef984.png)
3、代码
function mergeSort(arr) {
let len = arr.length;
if (len < 2) {
return arr;
}
let mid = Math.floor(len / 2);
let left = arr.slice(0, mid);
let right = arr.slice(mid);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let result = [];
while (left.length > 0 && right.length > 0) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length) {
result.push(left.shift());
}
while (right.length) {
result.push(right.shift());
}
return result;
}
let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 12, 46, 4, 19, 50, 48];
console.log(mergeSort(arr)); // [3, 4, 5, 12, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
六、快速排序
分治法
1、算法描述
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序
2、图示
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210607155951398.gif#pic_center)
3、代码
function quickSort(arr, left, right) {
let len = arr.length,
partitionIndex,
left = typeofleft != "number" ? 0 : left,
right = typeofright != "number" ? len - 1 : right;
if (left < right) {
partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}
function partition(arr, left, right) {
// 分区操作
let pivot = left, // 设定基准值(pivot)
index = pivot + 1;
for (let i = index; i <= right; i++) {
if (arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);
return index - 1;
}
function swap(arr, i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 12, 46, 4, 19, 50, 48];
console.log(quickSort(arr)); // [3, 4, 5, 12, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
七、堆排序
1、算法描述
- 通过构建大顶堆
- 将堆顶的最大数拿出,与堆底的叶子节点进行交换
- 接着,树剪掉最大数的叶子
- 再对堆进行调整,重新变成大顶堆
- 返回步骤2,以此循环,直至取出所有数
2、图示
3、代码
var len; // 因为声明的多个函数都需要数据长度,所以把len设置成为全局变量
function buildMaxHeap(arr) {
// 建立大顶堆
len = arr.length;
for (var i = Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i);
}
}
function heapify(arr, i) {
// 堆调整
var left = 2 * i + 1,
right = 2 * i + 2,
largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest);
}
}
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function heapSort(arr) {
buildMaxHeap(arr);
for (var i = arr.length - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0);
}
return arr;
}
let arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 12, 46, 4, 19, 50, 48];
console.log(heapSort(arr)); // [3, 4, 5, 12, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
八、计数排序
1、算法描述
- 找出待排序的数组中最大和最小的元素;
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
2、图示
3、代码
function countingSort(arr, maxValue) {
let bucket = newArray(maxValue + 1),
sortedIndex = 0;
(arrLen = arr.length), (bucketLen = maxValue + 1);
for (let i = 0; i < arrLen; i++) {
if (!bucket[arr[i]]) {
bucket[arr[i]] = 0;
}
bucket[arr[i]]++;
}
for (let j = 0; j < bucketLen; j++) {
while (bucket[j] > 0) {
arr[sortedIndex++] = j;
bucket[j]--;
}
}
return arr;
}
九、桶排序
1、算法描述
- 设置一个定量的数组当作空桶
- 遍历输入数据,并且把数据一个一个放到对应的桶里去;
- 对每个不是空的桶进行排序;
- 从不是空的桶里把排好序的数据拼接起来
2、图示
3、代码
function bucketSort(arr, bucketSize) {
if (arr.length === 0) {
return arr;
}
let minValue = arr[0];
let maxValue = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i]; // 输入数据的最小值
} else if (arr[i] > maxValue) {
maxValue = arr[i]; // 输入数据的最大值
}
}
// 桶的初始化
let DEFAULT_BUCKET_SIZE = 5; // 设置桶的默认数量为5
bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;
let bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
let buckets = newArray(bucketCount);
for (let i = 0; i < buckets.length; i++) {
buckets[i] = [];
}
// 利用映射函数将数据分配到各个桶中
for (let i = 0; i < arr.length; i++) {
buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
}
arr.length = 0;
for (let i = 0; i < buckets.length; i++) {
insertionSort(buckets[i]); // 对每个桶进行排序,这里使用了插入排序
for (let j = 0; j < buckets[i].length; j++) {
arr.push(buckets[i][j]);
}
}
return arr;
}
十、基数排序
1、算法描述
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点)
2、图示
3、代码
let counter = [];
function radixSort(arr, maxDigit) {
let mod = 10;
let dev = 1;
for (let i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
for (let j = 0; j < arr.length; j++) {
let bucket = parseInt((arr[j] % mod) / dev);
if (counter[bucket] == null) {
counter[bucket] = [];
}
counter[bucket].push(arr[j]);
}
let pos = 0;
for (let j = 0; j < counter.length; j++) {
let value = null;
if (counter[j] != null) {
while ((value = counter[j].shift()) != null) {
arr[pos++] = value;
}
}
}
}
return arr;
}