一般在面试笔试中,
最常考的排序算法是:快速排序、归并排序
常被提及的算法还有:插入排序、冒泡排序、堆排序、基数排序、桶排序
选择排序
选择排序是一种简单直观的排序算法,基本原理如下:
对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录与第一个记录交换;接着对不包括第一个记录以外的其他记录进行第二轮的比较,得到最小的记录并与第二个记录进行位置交换;重复该过程,直到比较的记录只有一个时为止。
以数组{38, 65, 97, 76, 13, 27, 49}为例(假设要求为升序排列):
第一次排序后:13 [65 97 76 38 27 49]
第二次排序后:13 27 [97 76 38 65 49]
第三次排序后:13 27 38 [76 97 65 49]
第四次排序后:13 27 38 49 [97 65 76]
第五次排序后:13 27 38 49 65 [97 76]
第六次排序后:13 27 38 49 65 76 [97]
最后一次排序:13 27 38 49 65 76 97
具体代码实现:
const selectSort = function(nums) {
if(nums.length === 0) return [];
let len = nums.length;
for(let i=0;i<len-1;i++){
let minIndex = i;
for(let j=i+1;j<len;j++){
if(nums[j] < nums[minIndex]) {
minIndex = j;
}
}
[nums[i], nums[minIndex]] = [nums[minIndex], nums[i]];
}
return nums;
}
选择排序是一种不稳定的排序,最好、最坏和平均情况下的时间复杂度都为O(n2),空间复杂度为O(1)
插入排序
基本原理如下:
对于给定一组记录,初始时假设第一个记录自成一个有序序列,其余的记录为无序序列;接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直到最后一个记录插入到有序序列中为止。
以数组{38, 65, 97, 76, 13, 27, 49}为例(假设要求为升序排列):
第一步插入38以后:[38] 65 97 76 13 27 49
第二步插入65以后:[38 65] 97 76 13 27 49
第三步插入97以后:[38 65 97] 76 13 27 49
第四步插入76以后:[38 65 76 97] 13 27 49
第五步插入13以后:[13 38 65 76 97] 27 49
第六步插入27以后:[13 27 38 65 76 97] 49
第七步插入49以后:[13 27 38 49 65 76 97]
具体代码实现:
const insertSort = function(nums) {
if(!nums || nums.length === 0) return [];
let len = nums.length;
for(let i=1;i<len;i++){
let value = nums[i];
let j = i-1;
while(j >= 0) {
if(nums[j] > value) {
nums[j+1] = nums[j];
nums[j] = value;
}
j-=1;
}
}
return nums;
}
插入排序是一种稳定的排序方法,最好情况下的时间负责度为O(n),最坏情况下的时间复杂度为O(n2), 平均情况下的时间复杂度为O(n2)。空间复杂度为O(1).
冒泡排序
冒泡排序顾名思义就是整个过程就像气泡一样往上升,单向冒泡排序的基本原理是:
对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换其位置,进行一轮比较和换位后,n个记录中的最大记录将位于第n位;然后对前(n-1)个记录进行第二轮比较;重复该过程直到进行比较的记录只剩下一个为止。
以数组{36, 25, 48, 12, 25, 65, 43, 57}为例(假设要求为升序排列):
第一次排序:[25 36 12 25 48 43 57] 65
第二次排序:[25 12 25 36 43 48] 57 65
第三次排序:[12 25 25 36 43] 48 57 65
第四次排序:[12 25 25 36] 43 48 57 65
第五次排序:[12 25 25] 36 43 48 57 65
第六次排序:[12 25] 25 36 43 48 57 65
第七次排序:[12] 25 25 36 43 48 57 65
具体代码实现:
const bubbleSort = function(nums) {
if(!nums || nums.length === 0) return [];
let len = nums.length;
for(let i=0;i<len-1;i++){
for(let j=i+1;j<len;j++){
if(nums[i] > nums[j]) {
[nums[i], nums[j]] = [nums[j], nums[i]];
}
}
}
return nums;
}
冒泡排序是一种稳定的排序方法,最好的情况下的时间复杂度为O(n),最坏的情况下时间复杂度为O(n2),平均情况下时间复杂度为O(n2)。空间复杂度为O(1).
归并排序
归并排序是利用递归与分治技术将数据划分为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并为越来越大的有序序列。其中“归”代表的是递归的意思,即递归地将数组折半地分离为单个数组。具体而言,归并排序算法的原理如下:
对于给定的一组记录(假设共有n个记录),首先将每两个相邻长度为1的子序列进行归并,得到n/2(向上取整)个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列为止。
所以归并排序的关键就是两步:第一步,划分子表;第二步,合并半子表。
以数组{49, 38, 65, 97, 76, 13, 27}为例(假设要求为升序排序),排序过程如下:
初始关键字:[49] [38] [65] [97] [76] [13] [27]
第一次归并:[38 49] [65 97] [13 76] [27]
第二次归并:[38 49 65 97] [13 27 76]
第三次归并:[13 27 38 49 65 76 97]
具体代码实现:
const mergeSort = function (nums) {
if(!nums || nums.length <= 1) return nums;
let half = Math.floor(nums.length/2);
let left = mergeSort(nums.slice(0, half));
let right = mergeSort(nums.slice(half));
return merge(left, right);
}
function merge(left, right) {
let i = j = 0;
let res = [];
while(i < left.length && j < right.length) {
if(left[i] <= right[j]) {
res.push(left[i]);
i++;
} else {
res.push(right[j]);
j++;
}
}
res = res.concat(left.slice(i));
res = res.concat(right.slice(j));
return res;
}
二路归并排序的过程需要进行logn次。每一趟归并排序的操作,就是讲两个有序子序列进行归并,而每一对有序子序列归并时,记录的比较次数均小于等于记录的移动次数,记录移动的次数均等于文件中记录的n个数,即每一趟归并的时间复杂度为O(n)。因此二路归并排序在最好、最坏和平均情况下的时间复杂度为O(nlogn),而且是一种稳定的排序方法,空间复杂度为O(1).
快速排序
快速排序是一种非常高效的排序算法,它采用“分而治之”的思想,把大的拆分成小的,小的再拆分成更小的。其原理是:
对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前部分的所有记录都比后部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。
具体的算法步骤如下:
- 分解: 将输入的序列array[m,…,n]划分为两个非空子序列array[m,…,k]和array[k+1,…,n],使array[m,…,k]任意元素不大于array[k+1,…,n]中任一元素的值。
- 递归求解: 通过递归调用快速排序算法分别对array[m,…,k]和array[k+1,…,n]进行排序;
- 合并: 由于对分解出的两个子序列的排序是就地进行的,所以在array[m,…,k]和array[k+1,…,n]都排好序后,不需要执行任何计算,array[m,…,n]就已排好序。
以数组{49, 38, 65, 97, 76, 13, 27, 49}为例(假设要求为升序排序),排序过程如下:
初始化关键字排序:[49 38 65 97 76 13 27 49]
一次排序后:[27 38 13] 49 [76 97 65 49]
二次排序后:[13] 27 [38] 49 [49 65] 76 [97]
三次排序后:13 27 38 49 49 [65] 76 97
最后的排序结果:13 27 38 49 49 65 76 97
具体代码实现:
const quickSort = function(nums, left, right) {
if(left >= right) return nums;
let key = nums[left];
let low = left;
let high = right;
while(left < right) {
while(left < right && nums[right] >= key) {
right -= 1;
}
nums[left] = nums[right];
while(left < right && nums[left] <= key) {
left += 1;
}
nums[right] = nums[left];
}
nums[right] = key;
quickSort(nums, low, left-1);
quickSort(nums, left+1, high);
return nums;
}
未完待续......