【算法】(冒泡 | 快速 | 归并 ) 排序

262 阅读5分钟

目录

  1. 排序分类
  2. 交换排序 - 冒泡排序
  3. 交换排序 - 快速排序
  4. 归并排序
  5. 快排和归并区别
  6. 十大基本排序

零、排序分类

image.png

一、 交换排序 - 冒泡排序

把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或者等于右侧相邻元素时,位置不变

function bubbleSort(list:Array<number>) {
    // 注意 这里是 <  length - 1 也就是说遍历次数为 n-1 
    for (let i = 0; i < list.length - 1; i++) {
        // 这里 < length -1的原因同上 - i的原因是 有序区
        for (let j = 0; j < list.length - i -1; j++) {
            if( list[j] > list[j+1]) {
                let temp = list[j];
                list[j] = list[j+1];
                list[j+1] = temp


            }
        }
    }
    return list
}

1.1) 冒泡排序优化

**改进冒泡排序: **设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

// 是否有序标识优化
function bubbleSortTwo(list: Array<number>) {
    // 注意 这里是 <  length - 1 也就是说遍历次数为 n-1
    for (let i = 0; i < list.length - 1; i++) {
        let isSorted = true; // 有序标识 每一轮都为true
        for (let j = 0; j < list.length - i - 1; j++) {
            if (list[j] > list[j + 1]) {
                let temp = list[j];
                list[j] = list[j + 1];
                list[j + 1] = temp;
                // 如果有交换发生,则不是有序的
                isSorted = false;
            }
        }
        if (isSorted) {
            break;
        }
    }
    return list;
}


function bubbleSort2(arr) {
    console.time('改进后冒泡排序耗时');
    var i = arr.length-1;  //初始时,最后位置保持不变
    while ( i> 0) {
        var pos= 0; //每趟开始时,无记录交换
        for (var j= 0; j< i; j++)
            if (arr[j]> arr[j+1]) {
                pos= j; //记录交换的位置
                var tmp = arr[j]; arr[j]=arr[j+1];arr[j+1]=tmp;
            }
        i= pos; //为下一趟排序作准备
     }
     console.timeEnd('改进后冒泡排序耗时');
     return arr;
}
var arr=[3,44,38,5,47,15,36,26,27,2,46,4,19,50,48];
console.log(bubbleSort2(arr));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]

1.2) 冒泡排序性能分析

大小相同的元素没有交换位置,所以冒泡排序是稳定的。

算法名称最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度是否稳定
冒泡排序O(n)O(n²)O(n²)O(1)稳定

二、交换排序 - 快速排序

它的思路:首先在未排序的序列中找到最小或者最大的元素,放到排序序列的起始位置,然后再从未排序的序列中继继续寻找最小或者最大元素,然后放到已经排序序列的末尾。以此类推,直到所有元素排序完毕。

来看一个例子,用选择排序的方式排序数组[2,5,4,1,3]

  • 第一趟,找到数组中最小的元素1,将它和数组的第一个元素交换位置

image.png

  • 第二趟,在未排序的元素中找到最小的元素2,和数组的第二个元素交换位置。

图片

选择排序2

  • 第三趟,在未排序的元素中找到最小的元素3,和数组的第三个元素交换位置。

图片

选择排序-3

  • 第四趟,在未排序的元素中找到最小的元素4,和数组的第四个元素交换位置。

图片

选择排序4

那么到这,我们的数组就是有序的了。

图片

选择排序5

2.1) 选择排序代码实现

选择排序的思路很简单,实现起来也不难。

const quickSort1 = arr => {
	if (arr.length <= 1) {
		return arr;
	}
	//取基准点
	const midIndex = Math.floor(arr.length / 2);
	//取基准点的值,splice(index,1) 则返回的是含有被删除的元素的数组。
	const valArr = arr.splice(midIndex, 1);
	const midIndexVal = valArr[0];
	const left = []; //存放比基准点小的数组
	const right = []; //存放比基准点大的数组
	//遍历数组,进行判断分配
	for (let i = 0; i < arr.length; i++) {
		if (arr[i] < midIndexVal) {
			left.push(arr[i]); //比基准点小的放在左边数组
		} else {
			right.push(arr[i]); //比基准点大的放在右边数组
		}
	}
	//递归执行以上操作,对左右两个数组进行操作,直到数组长度为 <= 1
	return quickSort1(left).concat(midIndexVal, quickSort1(right));
};
const array2 = [5, 4, 3, 2, 1];
console.log('quickSort1 ', quickSort1(array2));
// quickSort1: [1, 2, 3, 4, 5]


2.2) 选择排序性能分析

选择排序稳定吗?

答案是不稳定的,因为在未排序序列中找到最小值之后,和排序序列的末尾元素交换。

算法名称最好时间复杂度最坏时间复杂度平均时间复杂度空间复杂度是否稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定

快速排序的特点就是快,而且效率高!它是处理大数据最快的排序算法之一。

思想

  • 先找到一个基准点(一般指数组的中部),然后数组被该基准点分为两部分,依次与该基准点数据比较,如果比它小,放左边;反之,放右边。
  • 左右分别用一个空数组去存储比较后的数据。
  • 最后递归执行上述操作,直到数组长度 <= 1;

特点:快速,常用。

缺点:需要另外声明两个数组,浪费了内存空间资源。

三、归并排序

思想

排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

归并排序采用的是分治思想

分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。

image.png

四、快排和归并区别

快排和归并用的都是分治思想,递推公式和递归代码也非常相似,那它们的区别在哪里呢 ?

image.png

可以发现:

  • 归并排序的处理过程是由下而上的,先处理子问题,然后再合并。
  • 而快排正好相反,它的处理过程是由上而下的,先分区,然后再处理子问题。
  • 归并排序虽然是稳定的、时间复杂度为 O(nlogn) 的排序算法,但是它是非原地排序算法。
  • 归并之所以是非原地排序算法,主要原因是合并函数无法在原地执行。
  • 快速排序通过设计巧妙的原地分区函数,可以实现原地排序,解决了归并排序占用太多内存的问题。

参考

总结

  1. 排序分为: 比较类排序非比较类排序
  2. 把相邻的元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置;当一个元素小于或者等于右侧相邻元素时,位置不变
  3. 归并排序采用的是分治思想分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。

待办