冒泡排序
冒泡排序是一种简单的排序算法,它的基本思想是重复地比较相邻两个元素的大小,并交换它们,直到整个序列都有序为止。冒泡排序的时间复杂度为 O(n^2)。
下面是 JavaScript 中实现冒泡排序的代码:
function bubbleSort(arr) {
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; // 交换相邻两个元素的值
}
}
}
return arr;
}
在这个代码中,我们使用了两个循环来遍历整个数组。外层循环控制比较轮数,内层循环控制每轮比较的次数。每次内层循环比较相邻两个元素的大小,并交换它们的位置,这样可以保证每轮循环之后,最大的元素都被移到了数组的最后面。最终,整个数组都会被排序好,我们将它返回即可。
使用示例:
let arr = [3, 1, 6, 2, 9, 4, 0, 5];
bubbleSort(arr); // [0, 1, 2, 3, 4, 5, 6, 9]
选择排序
选择排序(Selection Sort)是一种简单直观的排序算法,其基本思想是在待排序的元素中选出最小(或最大)的一个元素,与序列最左侧的元素交换位置,然后再从剩余未排序的元素中继续寻找最小(或最大)的元素,重复以上操作直到整个序列有序。
以下是选择排序的详细实现过程:
1.首先,我们找到数组中最小的元素。
2.其次,将最小元素和数组的第一个元素交换位置。
3.接着,在剩余的元素中找到最小元素,将其与数组的第二个元素交换位置。
4.重复以上步骤,一直到数组排序完成。
JavaScript实现代码如下:
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 找到更小的数,更新最小值的下标
minIndex = j;
}
}
temp = arr[i]; // 将最小值交换到当前位置
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
下面是一个使用选择排序的例子:
var arr = [64, 25, 12, 22, 11];
console.log(selectionSort(arr)); // [11,12,22,25,64]
选择排序是一种简单有效的排序算法,虽然它的时间复杂度为O(n^2),但是在小规模数据的排序中相对于其他高级排序算法具有一定的优势。
插入排序
插入排序是一种基本的排序算法,其基本思想是将待排序的元素插入到已经有序的数组中,以达到排序的目的。下面是 js 插入排序的详解。
- 算法步骤
插入排序的基本思路是,将待排序的元素插入到已经排好序的数组中,因此,插入排序的算法步骤如下:
-
将待排序的数组分为两个区间:已排序区间和待排序区间。
-
初始时,已排序区间只有一个元素,即数组的第一个元素。
-
将待排序区间的第一个元素插入到已排序区间的适当位置(如有必要,已排序区间需要将插入位置之后的元素后移)。
-
重复步骤 3,直到待排序区间中的所有元素都插入到已排序区间中。
-
js 代码实现
以下是使用 js 实现插入排序的代码:
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
// 将当前元素插入到已排序区间的适当位置
let j = i;
while (j > 0 && arr[j] < arr[j - 1]) {
[arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
j--;
}
}
return arr;
}
// 示例:
let arr = [3, 1, 5, 7, 2, 4, 9, 6];
console.log(insertionSort(arr)); // 输出 [1, 2, 3, 4, 5, 6, 7, 9]
- 算法分析
插入排序算法的时间复杂度为 O(n^2)。虽然插入排序算法的时间复杂度不是最优的,但是它具有以下几个优点:
- 算法思路简单,易于理解和实现。
- 当待排序的元素数量比较少时(如少于 10 个),插入排序算法的效率比较高。
- 插入排序算法是稳定的,即排序前相同的元素,在排序后仍然保持相同的顺序。
希尔排序
希尔排序是一种改进的插入排序算法,也称缩小增量排序,它首先将数组分为若干子数组,然后对每个子数组进行插入排序,最后对整个数组进行一次插入排序。具体来说,希尔排序是通过将间隔 h 不断缩小进行插入排序的一种算法。
假设待排序的数组为 arr,希尔排序的实现过程如下:
-
确定初始步长 h,一般取数组长度的一半,然后将数组分为 h 个子数组。
-
分别对每个子数组进行插入排序,即将子数组中的元素按照从小到大的顺序进行排序。
-
将步长 h 缩小为原来的一半,重新分为 h/2 个子数组。
-
重复步骤 2 和 3,直到步长为 1,即整个数组变为一个子数组。
-
对整个数组进行一次插入排序,排序完成。
希尔排序的时间复杂度取决于步长序列的选择,一般情况下可以选择以下步长序列:
-
Knuth 序列:h = 1, 4, 13, 40, …,其时间复杂度为 O(n^(3/2))。
-
Hibbard 序列:h = 1, 3, 7, 15, …,其时间复杂度为 O(n^(3/2))。
-
Sedgewick 序列:h = 1, 5, 19, 41, 109, …,其时间复杂度为 O(n^(4/3))。
希尔排序的优点是相较于冒泡排序、选择排序等简单排序算法更加高效,适用于大规模数据的排序。但是其实现过程比较复杂,需要对步长序列的选择进行优化,否则可能导致性能的下降。
归并排序
归并排序是一种基于分治思想的排序算法。它将问题分解成小问题,通过递归求解小问题,然后将小问题的解合并成大问题的解。具体实现过程如下:
- 将待排序数组分为两个子数组,分别对这两个子数组进行递归排序,直到子数组长度为 1 或 0。
- 合并两个已排好序的子数组。合并的过程中,将每个子数组的首元素进行比较,将较小的元素放入新的数组中,并将对应子数组的指针向后移动一位。当其中一个子数组的指针移动到末尾时,将另一个子数组的剩余元素直接拷贝到新的数组中。
- 返回合并后的数组作为结果。
JavaScript 代码实现:
function merge(left, right) {
let result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] < right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
while (i < left.length) {
result.push(left[i++]);
}
while (j < right.length) {
result.push(right[j++]);
}
return result;
}
function mergeSort(arr) {
if (arr.length <= 1) {
return arr;
}
let mid = Math.floor(arr.length / 2);
let left = arr.slice(0, mid);
let right = arr.slice(mid);
left = mergeSort(left);
right = mergeSort(right);
return merge(left, right);
}
这里定义了两个函数,
merge
函数用来合并两个已排好序的子数组,
mergeSort
函数是递归函数,用来排序整个数组。函数的实现过程与上面提到的算法思想一致。
快速排序
快速排序是一种常见的排序算法,它采用了分治的思想,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再利用递归的方式将这两部分数据分别进行快速排序,最终达到整个序列有序的效果。在实现中,我们需要选取一个基准元素,一般选择第一个或最后一个元素作为基准,将序列中比基准元素小的放在左边,比基准元素大的放在右边,再递归地对左右两部分数据进行排序。
以下是 JavaScript 实现快速排序的代码:
function quickSort(arr) {
if (arr.length <= 1) {
return arr;
}
const pivotIndex = Math.floor(arr.length / 2);
const pivot = arr.splice(pivotIndex, 1)[0];
const left = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
代码解释:
- 对于长度不超过 1 的数组,直接返回。
- 选择数组的中间元素作为基准元素 pivot。
- 将除了 pivot 以外的元素分为左右两个数组,左边的元素小于 pivot,右边的元素大于等于 pivot。
- 将左右两部分数据分别递归进行快速排序,并将左右数组和 pivot 拼接起来返回。
使用示例:
const arr = [6, 5, 3, 1, 8, 7, 2, 4];
const sortedArr = quickSort(arr);
console.log(sortedArr); // [1, 2, 3, 4, 5, 6, 7, 8]
堆排序
堆排序(Heap Sort)是一种树形选择排序算法,它是把无序数组构建成二叉堆,利用堆的特性进行排序的一种算法。堆的结构分为大根堆和小根堆。在堆中,最大的元素总是位于根节点。
堆排序的基本思想是:利用堆结构的特点(即大/小根堆)依次将堆顶元素与堆底元素交换位置并调整堆结构,这样,最后得到的就是一个有序序列了。
具体步骤如下:
-
构建堆:将无序数组构建成二叉堆,具体实现过程是从最后一个非叶子节点开始,依次将子树调整为堆,直到根节点。
-
堆排序:依次将堆顶元素与堆底元素交换位置,并调整堆结构,使得剩余元素仍然构成堆。重复执行该步骤,直到所有元素都有序。
JavaScript 实现代码如下:
function heapSort(arr) {
function swap(arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
function heapify(arr, n, i) {
let largest = i;
let l = 2 * i + 1;
let r = 2 * i + 2;
if (l < n && arr[l] > arr[largest]) {
largest = l;
}
if (r < n && arr[r] > arr[largest]) {
largest = r;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, n, largest);
}
}
function buildHeap(arr) {
let n = arr.length;
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
heapify(arr, n, i);
}
}
buildHeap(arr);
for (let i = arr.length - 1; i >= 0; i--) {
swap(arr, 0, i);
heapify(arr, i, 0);
}
return arr;
}
// Example
let arr = [5, 3, 8, 4, 2];
let sortedArr = heapSort(arr);
console.log(sortedArr); // [2, 3, 4, 5, 8]
计数排序
计数排序是一种非比较排序算法,它适用于待排序序列中元素范围较小的情况。计数排序的主要思想是,统计待排序序列中每个元素出现的次数,然后根据元素出现的次数,输出排序后的序列。
具体的实现步骤如下:
-
找出待排序序列中最大值和最小值,确定元素的取值范围。
-
根据取值范围创建一个计数数组count,count[i]表示待排序序列中i出现的次数。
-
遍历待排序序列,统计每个元素出现的次数,记录在count数组中。
-
根据count数组中的计数信息,对待排序序列进行排序。
-
输出排序后的序列。
下面是JavaScript实现代码:
function countSort(arr) {
// 找出待排序序列中最大值和最小值,确定元素的取值范围
const maxValue = Math.max(...arr);
const minValue = Math.min(...arr);
const len = maxValue - minValue + 1;
// 根据取值范围创建一个计数数组count
const count = new Array(len).fill(0);
// 统计每个元素出现的次数,记录在count数组中
for (let i = 0; i < arr.length; i++) {
count[arr[i] - minValue]++;
}
// 根据统计信息,对待排序序列进行排序
let sortedIndex = 0;
for (let i = 0; i < len; i++) {
while (count[i] > 0) {
arr[sortedIndex++] = i + minValue;
count[i]--;
}
}
return arr;
}
// 测试
const arr = [3, 1, 6, 2, 7, 2, 8, 4];
console.log(countSort(arr)); // [1, 2, 2, 3, 4, 6, 7, 8]
上述代码中,我们先找出待排序序列中的最大值和最小值,然后创建一个计数数组count,其中数组的长度为最大值和最小值之差加一。接着,我们遍历待排序数组,统计每个元素出现的次数,并记录在count数组中。最后,我们根据count数组中的统计信息,对待排序序列进行排序。排序完毕后,返回排序后的序列。
桶排序
桶排序是一种排序算法,它的基本思想是将待排序的元素分配到有限数量的桶中,然后对桶中元素进行排序,最后按照顺序将各个桶中元素依次排列得到排序结果。
以下是 JavaScript 实现桶排序的代码:
function bucketSort(arr, bucketSize) {
if (arr.length === 0) {
return arr;
}
let i;
let minValue = arr[0];
let maxValue = arr[0];
for (i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i];
} else if (arr[i] > maxValue) {
maxValue = arr[i];
}
}
// 桶数量
let bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
let buckets = new Array(bucketCount);
for (i = 0; i < buckets.length; i++) {
buckets[i] = [];
}
// 将元素分配到桶中
for (i = 0; i < arr.length; i++) {
buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i]);
}
// 对每个桶中的元素进行排序
arr.length = 0;
for (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;
}
function insertionSort(arr) {
let len = arr.length;
let preIndex, current;
for (let i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
该实现中使用了插入排序算法对每个桶中的元素进行排序。这是因为桶排序的时间复杂度取决于对每个桶中元素的排序算法,插入排序的时间复杂度为
O
(
n
2
)
O(n^2)
O
(
n
2
)
,但由于每个桶中的元素数量不超过
b
u
c
k
e
t
S
i
z
e
bucketSize
b
u
c
k
e
tS
i
ze
,所以插入排序是比较理想的选择。不过也可以使用其他的排序算法,比如快速排序或归并排序,以提高桶排序的效率。
基数排序
基数排序是一种非比较排序算法,适用于对整数进行排序。它的基本思想是将整数按位数切割成不同的数字,然后按每个位数分别进行排序。这个过程从低位到高位进行,也就是说,先排最低位,再排次低位,直到排完最高位。
以下是基数排序的 JavaScript 实现:
function radixSort(arr) {
const maxNum = Math.max(...arr);
let divisor = 1; // 除数,用于求每位的数字
while (divisor <= maxNum) {
const buckets = Array.from({ length: 10 }, () => []); // 10 个桶,用于存放每位数字相同的数字
for (let i = 0; i < arr.length; i++) {
const digit = Math.floor((arr[i] / divisor) % 10); // 求出当前数字的对应位数上的数字
buckets[digit].push(arr[i]); // 将数字放入对应的桶中
}
arr = [].concat(...buckets); // 合并桶中的数字,组成新的 arr 数组
divisor *= 10; // 将除数乘以 10,求下一位数字
}
return arr;
}
该实现中,我们先求出数组中的最大值,然后定义一个变量
divisor
,初值为 1,每轮循环
divisor
都会乘以 10,这样可以依次求出数字的个位、十位、百位等等。在每轮循环中,我们创建 10 个桶,用于存放数字。我们将每个数字放入对应的桶中,最后将所有桶中的数字按顺序依次合并,组成新的数组。经过多轮的循环和桶的合并,我们就得到了有序的数组。