排序算法

排序算法时间复杂度是否基于比较
冒泡 、插入、选择O(n^2)
快排 、归并O(nlogn)

一、适合小规模数据的排序:

排序算法是否原地排序是否稳定实际应用
冒泡排序(Bubble Sort)
插入排序(Insertion Sort)
选择排序(Selection Sort)

原地排序:特指空间复杂度是 O(1) 的排序算法。
稳定性:若待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

冒泡排序(Bubble Sort)
1.对相邻的两个元素进行比较,若不满足大小关系则互换。
2.一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次就完成了 n 个数据的排序。
3.当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。

// 冒泡排序,a表示数组,n表示数组大小
void bubbleSort(int[] a, int n) {
	if (n <= 1) return;
 
	for (int i = 0; i < n; ++i) 
	{
	    // 提前退出冒泡循环的标志位
	    bool flag = false;
	    for(int j = 0; j < n-i-1; ++j)
	    {
	        if (a[j] > a[j+1]) 
	        { 
	        	// 交换
	        	int tmp = a[j];
	        	a[j] = a[j+1];
	        	a[j+1] = tmp;
	        	// 表示有数据交换   
	        	flag = true;    
	        }
	    }
	    // 没有数据交换,提前退出
	    if (!flag) break; 
    }
}

插入排序(Insertion Sort)
1.将数组中的数据分为已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的 第一个元素。
2.取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并将插入点之后的元素顺序往后移动一位,保证已排序区间数据一直有序。
3.重复该过程,直到未排序区间元素为空。

// 插入排序,a表示数组,n表示数组大小
void insertionSort(int[] a, int n) {
	if (n <= 1) return;
	
	for (int i = 1; i < n; ++i) 
	{
		int value = a[i];
		int j = i - 1;
		// 查找插入的位置
		for (; j >= 0; --j) 
		{
			if (a[j] > value) 
			{
				// 数据移动
				a[j+1] = a[j];
			} 
			else 
			{
				break;
			}
		}
		// 插入数据
		a[j+1] = value; 
	}
}

选择排序(Selection Sort)
1.选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。
2.但选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。

冒泡排序 VS 插入排序
从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。

冒泡排序中数据的交换操作:
if (a[j] > a[j+1]) { 
   // 交换
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}

插入排序中数据的移动操作:
if (a[j] > value) {
  // 数据移动
  a[j+1] = a[j];  
} else {
  break;
}

二、适合大规模的数据排序:

排序算法是否原地排序是否稳定实际应用
归并排序(Merge Sort)
快速排序(Quick Sort)

归并排序(Merge Sort)
1.归并排序使用分治的思想,递归的方法,将一个大问题分解成一个个小的子问题来解决。
2.先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

在这里插入图片描述

template<typename T>
void merge_sort_recursive(T arr[], T tmp[], int start, int end) {
    //分解终止条件
    if (start >= end)
        return;
    //分解
    int mid = start + ((end - start)>> 1);
    int start1 = start, end1 = mid;
    int start2 = mid + 1, end2 = end;
    merge_sort_recursive(arr, tmp, start1, end1);
    merge_sort_recursive(arr, tmp, start2, end2);
    //按顺序合并
    int n = start;
    while (start1 <= end1 && start2 <= end2)
        tmp[n++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    while (start1 <= end1)
        tmp[n++] = arr[start1++];
    while (start2 <= end2)
        tmp[n++] = arr[start2++];
    //转移
    for (n = start; n <= end; n++)
        arr[n] = tmp[n];
}

template<typename T>
void merge_sort(T arr[], const int len) {
    T tmp[len];
    merge_sort_recursive(arr, tmp, 0, len - 1);
}

快速排序(Quick Sort)
1.快排利用的也是分治思想,有点像归并排序,但思路其实完全不一样
2.归并排序中有一个 merge() 合并函数,快排有一个 partition() 分区函数
3.通过游标 i 把 arr[left…end-1]分成两部分。arr[left…i-1]的元素都是小于 pivot基数的“已处理区”,arr[i…end-1]是“未处理区”。每次从未处理区 arr[i…end-1]中取一个元素 arr[ j ]与 pivot 对比,若小于 pivot则将其与“已处理区间的元素对调,也就是 arr[ i ]的位置

//分区函数,左边为小于pivot的区,中间为pivot,右边为大于pivot的区
template<typename T>
int partition(T arr[], int start, int end) 
{ 
	int pivot = arr[end]; 
	int i = j = start;
	for(j; j < end-1; j++)
	{ 
		if( arr[j] < pivot )
		{ 
			std::swap(arr[i++], arr[j]);
		} 
	} 
	std::swap(arr[i], arr[end]);
	//返回 pivot 的下标
	return i
}
//递归函数
template<typename T>
void quick_sort_recursive(T arr[], int start, int end) {
    if (start >= end) 
    	return;
    //根据pivot分为左右两区
    int pivot = partition(arr,start,end);
    //再将pivot左右两边进行递归排序
    quick_sort_recursive(arr, start, pivot-1);
    quick_sort_recursive(arr, pivot+1, end);
}
//起始入口
template<typename T>
void quick_sort(T arr[], int len) {
    quick_sort_recursive(arr, 0, len-1);
}

时间复杂度:
平均:
O(nlogn)
最好:
如果每次分区操作,都能正好把数组分成大小接近相等的两个小区间,那快排的时间复杂度递推求解公式跟归并是相同的, O(nlogn)
最差:
如果数组中的数据原来已经是有序的了,比如 1,3,5,6,8。如果我们每次选择最后一个元素作为 pivot,那每次分区得到的两个区间都是不均等的。我们需要进行大约 n 次分区操作,才能完成快排的整个过程。每次分区我们平均要扫描大约 n/2 个元素,这种情况下,快排的时间复杂度就从 O(nlogn) 退化成了 O(n^2)。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值