种类:插入排序、选择排序、归并排序、堆排序、快速排序、计数排序
、桶排序、基数排序、冒泡排序、希尔排序、梳排序
一、插入排序
对数进行从小到大排序时,需要将一个数放到前面,而将那些比他大的数挤到后面,来实现排序。
过程分析(6,5,3,1,8,7,2,4)
将数列划分为两个部分,有序和无序,即将数列中的第一个元素(6)看作有序的部分,后面的其他数(5,3,1,8,7,2,4)看做无序的部分.
从前到后扫描有序部分,将无序部分中的第一个元素插入到有序的部分中合适的位置
然后将有序序列该数后面的数依次后移一位,形成新的有序部分
重复以上步骤,直到遍历完整个数列
js代码
<script>
var arr = [3,2,5,8,4,7,6,9];
function sort(arr) {
//从小到大排列:假设第0个元素是有序数列,第一个以后的元素是无序数列,
// 从第一个元素起将无序数列的元素插入到有序数列中
for (var i = 1; i < arr.length; i++) {
var key = arr[i];
var j = i - 1;//j
//若后一个元素小于前一个元素;
while (j >= 0 && key < arr[j]){
//把大的值赋给后面的元素
arr[j + 1] = arr[j];
//j从后向前循环
j--;
}
//将小的值赋给跳出上个循环的后一个值
arr[j + 1] = key;
}
return arr;
}
sort(arr);
console.log(arr);
</script>
空间复杂度
O(n)用于存储整个数列,O(1)辅助,暂存操作数,用于插入。
时间复杂度
最好:已经有序的情况下,只需要遍历数列一次,为O(n)。
最坏:反序情况下比较次数依次是1 + 2 + 3 + … + (N - 1),即(1/2)n(n-1)次。O(n^2)。
平均:O(n^2)
二、选择排序
在未排序序列每次选择出最小的数,和排在第一个的数进行交换。从而达到将较小的数排在前面的目的。
过程分析(从小到大)
在未排序的序列中找到最小的元素,存放到序列起始的位置
再从未排序的序列中寻找最小的元素,然后放到序列末尾
重复第二步,直到所有的元素都排列完毕
js代码
<script>
var arr=[3,2,5,8,4,7,6,9];
function selectionSort(arr){
//定义未排序数组的最小值
var minIndex ,temp;
for(var i = 0;i < arr.length;i++){
minIndex = i;
//寻找未排序序列的最小值
for(var j = i+1;j < arr.length;j++){
if(arr[j] < arr[minIndex]){
minIndex = j;
}
}
//将找到的最小值插入到已排序序列的后面
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
selectionSort(arr);
console.log(arr);
</script>
空间复杂度
O(n)用于存储整个数列,O(1)辅助,暂存操作数,用于交换。
时间复杂度
由于一共有n个位置要进行选择,每次选择时又要对剩下为放入合适位置的数进行便利,所以时间复杂度不分最好和最坏,依次比较(N-1)+(N-2)+ … +1次,即(N-1+1)*N/2 = (N^2)/2, 为O(n^2)。
三、归并排序
归并算法主要通过拆分和合并两个步骤来完成对数列的排序,将需要排序的数列拆分成尽量小的序列,然后重新合并,合并时按顺序排列,最终形成有序序列。
过程分析
①迭代法
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并的空间,并移动指针到下一位置
重复步骤3直到某一指针到达序列尾
将另一序列剩下的所有元素直接复制到合并列尾
②递归法
假设序列共有n个元素
将序列每相邻两个数字进行归并操作,形成n/2个序列,排序后每个序列包含四个元素
将上述序列再次归并形成n/4个序列,每个序列包含四个元素
重复步骤2,直到所有元素排序完毕
js代码
四、冒泡排序
冒泡排序中较大的数与较小数交换了位置。冒泡排序是一种用时间换空间的算法。
过程分析
- 比较相邻的元素,如果第一个比第二个大,就交换他们两个。
- 对每一对相邻的元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的元素。
- 针对所有的元素重复以上步骤,除了最后一个
- 持续每次对越来越少的元素重复上面的步骤,知道没有任何一对数字需要比较。
js代码
<body>
<script>
var arr = [3,2,5,8,4,7,6,9];
function bubbleSort(arr){
//控制交换的趟数
for(var i = 0;i<arr.length;i++)
//targ是做为一个开关来使用;如果这一趟没有做交换,则targ=0;跳出循环
var targ = 0;
for(var j = 0;j<arr.length;j++){
//将相邻的两个数进行比较,若满足则进行交换
if(arr[j]>arr[j+1]){
var temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
targ = 1;
}
}
if(!targ){
break;
}
}
return arr;
}
bubbleSort(arr);
console.log(arr);
</script>
</body>
时间复杂度
外层循环n-1次,内层循环n-i次。内层循环总的次数用等差数列求和公式是(1+(n-1))(n-1)/2=n(n-1)/2≈n^2/2,外层循环赋值次数为常数设为a,内层循环赋值次数也是常数设为b,所以f(n)≈a * n + b * n^2/2,时间复杂度是O(n^2).
最好:O(n)
最坏:O(n^2)
平均:O(n^2)。
稳定性:稳定
五、快速排序
在快速排序中也有一个基准–枢轴(pivot),其他的数根据大小排在它的两边。快速排序在最好情况和一般情况下的时间复杂度都是最低的。
过程分析
从数列中挑出一个元素,称为“基准”(pivot)
重新排序数列,所有的元素比基准值小的放在基准值的前面,比基准值大的放在基准的后面,这个过程结束后,基准就处于数列的中间位置
用递归把小于基准值元素的子数列和大于基准值元素的子序列排序
复杂度低的方式
//交换两个数,原地交换,而不是用两个空数组
function swap(array, a, b) {
[array[a], array[b]] = [array[b], array[a]];
}
function quick(array, left, right) {
let index;
if (array.length > 1) {
index = partition(array, left, right);
if (left < index - 1) {
quick(array, left, index - 1);
}
if(index < right) {
quick(array, index, right);
}
}
return array;
}
function quickSort(array) {
return quick(array, 0, array.length - 1);
};
function partition(array, left, right) {
const pivot = array[Math.floor((right + left) / 2)];
let i = left;
let j = right;
while (i <= j) {
while (compare(array[i], pivot) === -1) {
i++;
}
while (compare(array[j], pivot) === 1) {
j--;
}
if (i <= j) {
swap(array, i, j);
i++;
j--;
}
}
return i;
}
function compare(a, b) {
if (a === b) {
return 0;
}
return a < b ? -1 : 1;
}
阮一峰版本:因为有了splice和concat,每次递归都会多触发两个数组,导致空间复杂度变大
var quickSort = function(arr) {
if (arr.length <= 1) {
return arr;
}
var pivotIndex = Math.floor(arr.length / 2);
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
sum = right.length + left.length + sum;
return quickSort(left).concat([pivot], quickSort(right));
};