三大基础排序
1.冒泡排序
1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2.第一轮的时候最后一个元素应该是最大的一个。
3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。
function bubbleSort(arr) {
for(let i=0;i<arr.length-1;i++){
for(let j=0;j<arr.length-1-i;j++){
if(arr[j] > arr[j+1]){
swap(arr, j, j+1);
}
}
}
}
复杂度分析
- 时间复杂度 O(n^2)
第一趟a1&a2、a2&a3、…an-2&an-1、an-1&an:共n-1次比较
第二趟a1&a2、a2&a3、…an-2&an-1:共n-2次比较
…
第n-1趟a1与a2比较:共一次比较
1+2+3+......+n-1 = 1/2(n-1)^2
- 空间复杂度 O(1)
动态图
2.选择排序
首先在未排序序列中找到最小元素,存放到排序序列的起始位置,
然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
以此类推,直到所有元素均排序完毕。
function selectSort(arr) {
let minIndex;
for(let i=0;i<arr.length-1;i++){
minIndex = i;
for(let j=i+1;j<arr.length;j++){
if(arr[j] < arr[minIndex]){
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
复杂度分析
- 时间复杂度
第一趟a2&a1、a3&a1、…an-1&a1、an&a1:共n-1次比较
第二趟a3&a2、a4&a2、…an-1&a2、an&a2:共n-2次比较
…
第n-1趟an与an-1比较:共一次比较
1+2+3+......+n-1 = 1/2(n-1)^2
- 空间复杂度 O(1)
动态图
3.插入排序
从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到下一位置中
6.重复步骤2
function insertSort(arr) {
let index;
for(let i=0;i<arr.length-1;i++){
index = i;
while(index >= 0 && arr[index] > arr[index+1]){
swap(arr, index, index+1);
index--;
}
}
}
复杂度分析
- 时间复杂度
最好情况(arr正有序): a1&a2、a2&a3、an-1&an:共n次比较 O(n)
最坏情况(arr逆有序): O(n^2)
第一趟a2&a1:共1次比较
第二趟a3&a2、a3&a1:共2次比较
…
第n-1趟an&an-1… an&a1:共n-1次比较
1+2+3+......+n-1 = 1/2(n-1)^2
- 空间复杂度 O(1)
动态图
三大基础排序中,冒泡和选择只具有教学意义,实际开发中并不使用。
三大进阶排序
1.归并排序
归并排序是建立在归并操作的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。归并排序是一种稳定的排序算法,将已有序的子序列合并,等到一个完全有序的序列,即先使每个子序列有序,再使子序列段有序,若将两个有序表合并成一个有序表,称作2路合并
function mergeSort(arr, left, right) {
if(arguments.length === 1){
mergeSort(arr, 0, arr.length-1);
return;
}
if(left === right) return;
let mid = parseInt( (left + right) / 2);
mergeSort(arr, left, mid);
mergeSort(arr, mid+1, right);
merge(arr, left, mid, right);
}
function merge(arr, left, mid, right) {
let tmp = new Array(right-left+1),
index = 0,
p1 = left,
p2 = mid+1;
while(p1 <= mid && p2 <= right) {
tmp[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while(p1 <= mid) {
tmp[index++] = arr[p1++];
}
while(p2 <= right) {
tmp[index++] = arr[p2++];
}
for(let i=0; i<tmp.length; i++) {
arr[left+i] = tmp[i];
}
}
- 时间复杂度
T(n) = T(n/2) * 2 + O(n)
=> O(nlogn)
- 空间复杂度:O(n)
动态图
2.快速排序
快速排序的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序
function quickSort(arr, left, right) {
if(arguments.length === 1){
quickSort(arr, 0, arr.length-1);
return;
}
if(left >= right) return;
let target = arr[parseInt((left+right)/2)],
less = left-1,
more = right+1,
index = left;
while(index < more) {
if( arr[index] < target) {
swap(arr, ++less, index++);
} else if( arr[index] > target ) {
swap(arr, --more, index);
} else {
index++;
}
}
quickSort(arr, left, less);
quickSort(arr, more, right);
}
- 时间复杂度
T(n) = T(n/2) * 2 + O(n)
=> O(nlogn)
- 空间复杂度:O(logn)
动态图
3.堆排序
将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
function heapSort(array) {
let length = array.length;
buildMaxHeap(array);
for (let j = length - 1; j >= 1; j--) {
swap(array, 0, j);
heap(array, 0, --length);
}
}
function buildMaxHeap(array){
let length = array.length;
for (let i = parseInt(length / 2) - 1; i >= 0; i--) {
heap(array, i, length);
}
}
function heap(array, x, length) {
let l = 2 * x + 1, r = 2 * x + 2, largest = x;
if (l < length && array[l] > array[largest]) {
largest = l;
}
if (r < length && array[r] > array[largest]) {
largest = r;
}
if (largest != x) {
swap(array, x, largest);
heap(array, largest, length);
}
}
动态图
希尔排序,三大计数排序(计数排序、桶排序、基数排序)并不常用,这里不做讨论