一、冒泡排序 (o(n^2))
-
基本思想:冒泡排序比较任何两个相邻的项,如果第一个比第二个大,则交换它们。元素项向上移动至正确的顺序,就好像气泡升至表面一样,冒泡排序因此得名。
-
代码实现如下:
function bubbleSort(arr) {
var len = arr.length;
var temp = [];
for (var i = 0; i < len; i++) {
for (var j = 0; j < len - i - 1; j++) { //j < len - i - 1 指内循环减去外循环中已跑过的轮数
if (arr[j] > arr[j + 1]) {
temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
console.log(bubbleSort([13, 435, 12, 1, 4, 5765, 4])); //1,4,4,12,13,435,5765
- 解释:外循环会从数组的第一位迭代至最后一位,它控制了在数组中经过多少轮排序(应该是数组中每项经过一轮,轮数和数组长度一致)。然后,内循环将从第一位迭代至倒数第二位,内循环实际上进行当前项和下一项的比较。如果这两项顺序不对(当前项比下一项大),则交换它们,意思是位置为 j+1 的值将会被换到位置 j 上,反之亦然。
二、选择排序 (o(n^2))
-
基本思想:选择排序算法是一种原址比较排序算法。选择排序的大致思路是找到数组结构中的最小值并将其位置放置在第一位,接着找到第二小的值并将其放在第二位,以此类推。
-
代码实现如下:
function selectSort(arr) {
var len = arr.length;
var temp = [];
for (var i = 0; i < len - 1; i++) {
var index = i; //index里存的是数组中的最小值的索引,刚开始我们假设原数组的第一位为最小值,因此我们把它的索引存给index
for (var j = i + 1; j < len; j++) { //从存入值的下一位开始遍历
if (arr[index] > arr[j]) { //寻找最小值
index = j; //保存最小值的索引
}
}
if (index != i) { //如果该最小值与原最小值不同,则交换这两个索引所对应的数组值
temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
}
return arr;
}
console.log(selectSort([23, 1, 35, 56, 6])); //[1, 6, 23, 35, 56]
- 外循环迭代数组,并控制迭代轮数(数组的第n个值——下一个最小值)。我们假设本迭代轮次的第一个值为数组最小值。然后,从当前 i 的值开始至数组结束,我们比较是否位置 j 的值比当前最小值小;如果是,则改变最小值至新最小值。当内循环结束,将得出数组第 n 小的值。最后,如果该最小值与原最小值不同,则交换其值。
三、插入排序 (o(n^2))
- 基本思想:插入排序每次排一个数组项,以此方式构建最后的排序数组。假定第一项已经排序了,接着,它和第二项开始比较,判断第二项是应该待在原位还是插到第一项之前。这样头两项就已正确排序,接着和第三项比较,以此类推。
- 代码实现如下:
function insertSort(arr) {
var len = arr.length;
for (var i = 1; i < len; i++) {
//用 i 的值来初始化一个辅助变量,并将其值存储在一个临时变量中
var j = i;
var temp = arr[i]; //这个临时变量temp,就是待插入的值
while (j > 0 && arr[j - 1] > temp) { //如果待比较值在原数组中的前一位的值比待比较值大,则在原数组中交换这两个值的位置,并且从后往前去比较,即 j--
arr[j] = arr[j - 1];
j--;
}
arr[j] = temp; //将待比较的值插入到正确的位置中
}
return arr;
}
console.log(insertSort([24, 11, 5, 21, 56, 3])); //[3, 5, 11, 21, 24, 56]
-
解释:使用for循环是用迭代数组来给第 i 项找到正确的位置。注意,算法是从第二个位置(索引1)而不是0位置开始的(我们认为第一项已排序了)。然后,用 i 的值来初始化一个辅助变量,并将其值存储在一个临时变量中(temp ),便于之后将其插入正确的位置上。下一步是要找到正确的位置来插入项目。只要变量 j 比0大(因为数组的第一个索引是0——没有负值的索引)并且数组中前面的值比待比较的值大,我们就把这个值移到当前位置上并减小 j。最终,该项目到正确的位置上。
-
注:排序小型数组时,插入排序比冒泡排序和选择排序性能要好
四、归并排序 (o(nlog^(n)))
-
基本思想:将原始数组切分为较小的数组,直到每个小数组只有一个位置,接着将小数组归并为较大的数组,直到最后只有一个排序完成的大数组。
-
代码实现如下:
//将大数组划分为小数组
function mergeSort(arr){
var len=arr.length;
if(len===1){
return arr;
}
var middle=Math.floor(len/2),
//将数组分成左右两个子序列
left=arr.slice(0,middle),
right=arr.slice(middle,len);
return merge(mergeSort(left),mergeSort(right)); //将排序好的两个大的子数组进行最后一轮比较并合并
}
//将小数组归并为大数组
function merge(left,right){ //将左右两个子序列作为merge函数(辅助函数)的两个参数
var result=[];
while(left.length>0&&right.length>0){
if(left[0]<right[0]){ //左边序列的第一个元素和右边序列的第一个元素比较,小的元素则被放入到result中
//shift是删除数组第一个元素并返回
//如果数组中有多个元素,删除第一个,那么接下来第二个就变成第一个,然后继续选出来被比较,直到数组的长度为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;
}
console.log(mergeSort([34, 22, 46, 14, 3])); //[3, 14, 22, 34, 46]
- 归并排序将一个大数组转化为多个小数组直到只有一个项。由于算法是递归的,则需要一个停止条件,在此的条件是判断数组的长度是否为1。若是,则返回这个数组,因为它已经排序过了。
- 如果数组长度比1大,首先找到数组的中间位,找到后将数组分为两个小数组,分别为 left(由索引0至中间索引的元素组成)和 right(由中间索引至原始数组最后一个位置的元素构成)。
- 调用 merge 函数,它负责合并和排序小数组来产生大数组,直到回到原始数组并已排序完成。为了不断将原始数组分成小数组,我们得再次对 left 数组和 right 数组递归调用 mergeSort 函数,并同时作为参数传递给 merge 函数。
- merge 函数接受两个数组作为参数。首先需要声明归并过程要创建的新数组以及用来迭代两个数组所需的两个变量。迭代两个数组的过程中,需要对左边序列的第一个元素和右边序列的第一个元素进行比较,小的元素则被放入到result中。
- shift是删除数组第一个元素并返回。如果数组中有多个元素,删除第一个,那么接下来第二个就变成第一个,然后继续选出来被比较,直到数组的长度为0。
五、快速排序 (o(nlog^(n)))
- 基本思想:
- 首先,从数组中选择中间一项作为主元。
- 划分操作:创建两个指针,左边一个指向数组的第一个项,右边一个指向数组的最后一个项。移动左指针直到我们找到一个比主元大的元素,接着,移动右指针直到找到一个比主元小的元素,然后交换它们,重复这个过程,直到左指针超过了右指针。这个过程将使得比主元小的值都排在主元之前而比主元大的值都排在主元之后。
- 算法对划分后的小数组重复之前的两个步骤,直至数组已完全排序。
- 代码实现如下:
function quickSort(arr) {
return quick(arr, 0, arr.length - 1);
}
function quick(arr, left, right) {
let index;
if (arr.length > 1) {
index = partition(arr, left, right);
if (left < index - 1) {
quick(arr, left, index - 1);
}
if (index < right) {
quick(arr, index, right);
}
}
return arr;
}
// 划分操作函数
function partition(arr, left, right) {
// 利用index取中间值可以大大降低算法的时间复杂度
const pivot = arr[Math.floor((right + left) / 2)];
let i = left;
let j = right;
while (i <= j) {
while (arr[i] < pivot) {
i++;
}
while (arr[j] > pivot) {
j--;
}
if (i <= j) {
swap(arr, i, j);
i++;
j--;
}
}
return i;
}
var swap = function (array, index1, index2) {
var temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
};
console.log(quickSort([3, 5, 1, 6, 4, 7, 2])); //[1, 2, 3, 4, 5, 6, 7]
- 解释:
- 声明 index ,该变量帮助我们将子数组分离为较小值数组和较大值数组,这样我们就能递归调用 quick 函数,partition 函数返回值将赋值给 index。
- 若数组的长度大于1(因为只有一个元素的数组必然是已排序了的),我们将对给定子数组执行 partition 操作(第一次调用是针对整个数组)以得到 index,即index = partition(arr, left, right);如果子数组存在较小值的元素,则对该数组重复这个过程,即 quick(arr, left, index - 1);同理,对存在较大值的子数组也是如此,如果子数组存在较大值,我们也将重复快速排序过程,即 quick(arr, index, right)。
- 划分过程:将数组的中间项作为主元,初始化两个指针:left——数组第一个元素;right——数组最后一个元素。只要 left 和 right 指针没有交错,就执行划分操作。首先,移动 left 指针直到找到一个元素比主元大;移动 right 指针直到找到一个元素比主元小。当左指针指向的元素比主元大且右指针指向的元素比主元小,并且此时左指针索引没有右指针索引大;此时交换它们,然后移动两个指针,并重复此过程。划分结束后,返回左指针的索引,用来创建子数组。
- 图解:
1.给定数组 [3, 5, 1, 6, 4, 7, 2] ,展现划分操作的第一次执行
-
对有较小值的子数组执行的划分操作(7和6不包含在子数组之内)
-
继续创建子数组,但此次操作是针对上图中有较大值的子数组(有1的较小子数组不用再划分,因为它仅含有一个项)
-
子数组([2, 3, 5, 4])中的较小数组([2, 3])继续划分,即 quick(arr, left, index - 1)
- 子数组([2, 3, 5, 4])中的较大数组([5, 4])继续划分,即 quick(arr, index, right)
- 最终,较大数组([6, 7])也会进行划分操作,快排执行完成。
六、堆排序