数据结构与算法第十节:排序算法之归并、快速排序

1. 常用经典排序算法

冒泡排序、插入排序、选择排序; O(n^2)

快速排序、归并排序; O(nlogn)

计数排序、基数排序、桶排序; O(n)


归并排序和快速排序。这两种排序算法适合大规模的数据排序,比上一节讲的那三种排序算法要更常用(上一节排序算法更适合小规模的数据排序)。

分治思想: 分而治之, 将一个大问题分解成小的子问题。

1. 归并排序

(1). 将数组从中间分成前后两个部分;
(2). 然后对前后两部分分别排序,再将排好序的两部分合并在一起;

分治算法一般用递归来实现,分治是一种解决问题的处理思想,递归是一种编程技巧。

递归的书写技巧,分析得出递推公式,然后找到终止条件

  • 源代码案例
	void mergesort(int* a, int s, int n)
	{
		if(s>=n) return;	
		if(a == NULL || n <2) return;	
		int mid = (n+s)/2;	
		
		mergesort(a, s, mid);
		mergesort(a, mid+1, n);
		merge(a, s, mid, n);
	}
	
	void merge(int* a, int s, int mid, int n)
	{
		int help[n-s+1];
		int j=0;
		int lindex = s;
		int rindex = mid+1;
		
		// 判断语句"=="等号边界.
		while(lindex <= mid && rindex <= n) 
		{
			/************
			 * 涉及递归的时间复杂度分析稍微比较复杂(且空间复杂度不能像时间复杂度一样分析): 
			 * 对n个元素排序消耗的时间T(n) = 2*T(n/2)+K, K-为合并两个子问题的时间消耗; T(n) = 2*(2*T(n/4)+n/2)+K = ... = 2^k * T(n/2^k) +K*n
			 * 		令T(n/2^k) = T(1)(一个元素的排序的时间消耗为常数C) --> k =logn; 
			 * 		所以: T(n) = nlogn+Cn;
			 * 归并排序:, 时间复杂度为O(nlogn), 空间复杂度为O(n)(所以归并排序不是原地排序),是稳定排序。
			 * 
			 * 1. 将数组从中间开始分为两个部分,依次递归;
			 * 2. 分别对分割的数组的分别排序, 最后分割的数组一般为一个或者两个;
			 * 3. 将分割好的有序数组,互相比较大小,从小的开始插入到新的数组,进行合并;
			 * ********************/
			help[j++] = (a[lindex] < a[rindex]) ? a[lindex++] : a[rindex++];
		}
		while(lindex <= mid)
		{
			help[j++] = a[lindex++];
		}
		while(rindex <= n)
		{
			help[j++] = a[rindex++];
		}
		// 将辅助数组的数据copy更新到原数组数据中.
		
		for(int i=0; i< n-s+1; i++)
		{
			// 注意这里的左右边界不要认为"s=0, n=数组的长度,就省略为以下形式"
			/***
			 * for(int i=0; i< n; i++) {a[i] = help[i];}
			 * 这样是错误的, 因为函数是递归调用, 所以数据分段式的组合应满足函数的实际长度.
			 */
			a[s+i] = help[i];
		}
	}

2. 快速排序

分治的思想

核心思路:

(1). 假设要排序数组[p,r]的数据, 首先选择p-r之间的任意一个数据作为分区点(pivot);

(2). 然后遍历数组的数据, 将小于pivot放于左边,将大于pivot放于右边;

(3). 然后根据分治、递归的思想,不断排序ppivot-1和pivot+1r两个区间;

  • 源代码案例
	void quicksort(int* a, int s, int n)
	{
		if(a == NULL || n<2) return;
		if(s >= n ) return;
		
		int povit = partition(a, s, n);
		
		quicksort(a, s, povit-1);
		quicksort(a, povit+1, n);
	}
	
	int partition(int* a, int s, int n )
	{
		int q = a[n];
		int i = s;
		/****
		 * 快速排序: 时间复杂度分析(递归树分析(暂时不谈),归并是递推公式分析)O(nlogn), 空间复杂度为O(1)(属于原地排序); 不稳定排序(相同的元素的先后顺序会发生改变)
		 * 1. 分区时, 给出两个索引都指向初始位置, 并设置初始分区点;
		 * 2. 将一个索引遍历所有数组数据(除掉分区点), 然后将其与分区点数据进行对比;
		 * 3. 将小于pivot的数据移动到左边, 大于的数不动(实际上当最后pivot与最后一个小的数交换位置时, 大数会自动排在pviot的后面);
		 * 4. 最后将pivot对应的数移到中间;
		 * **********************************/
		for(int j=s; j<=n-1; j++)
		{	
			if(a[j] < q)
			{
				swap(a[i], a[j]);
				i++;
			}
		}
		swap(a[i], a[n]); // 将分区点的数转换到小数与大数的中间,将前后分隔开
		return i;
	}

3.最后类似上一节,给出主函数测试代码,供初学者参考

int main()
{
	int a[] = {4,5,6,1,3,2};
	std::cout<<"Before Sort Data: "<<std::endl;
	for(int i =0; i<6; i++)
	{
		cout<<a[i]<<",";
	}
	cout<<endl;
	solutions solve;
	
	//solve.mergesort(a, 0, 5);	
	solve.quicksort(a, 0, 5);
	std::cout<<"After Sort Data: "<<std::endl;
	for(int i =0; i<6; i++)
	{
		cout<<a[i]<<",";
	}
	cout<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱发呆de白菜头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值