目录
概览
一、冒泡排序
1、算法描述
2、图示
3、代码
二、选择排序
1、算法描述
2、图示
3、代码
三、插入排序
1、算法描述
2、图示
编辑 3、代码
四、希尔排序
1、算法描述
2、图示
3、代码
五、并归排序
1、算法描述
2、图示
编辑编辑3、代码
六、快速排序
1、算法描述
2、图示
编辑编辑 3、代码
七、堆排序
1、算法描述
2、图示
3、代码
八、计数排序
1、算法描述
2、图示
3、代码
九、桶排序
1、算法描述
2、图示
3、代码
十、基数排序
1、算法描述
2、图示
3、代码
概览
十大排序算法可以分成两类:
- 比较类别排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破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、图示
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、图示
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;
}