O(nlogn)的排序算法及基础优化(c++代码实现)

O(nlogn)的排序算法

1、归并排序

[算法思想]

首先将数组进行两两分组,然后想办法把左边的数组排序,再把右边的数组排序,最后再归并起来(递归)
在这里插入图片描述

[归并过程]

开辟同样大小的临时空间,设置三个索引i,j,k

i表示左边数组正在考虑元素的索引
j表示右边数组正在考虑元素的索引
k表示相比较后元素应该放到的位置
在这里插入图片描述

[代码实现]

//用户调用的函数
template<typename T>
void mergeSort(T arr[], int n){
	__mergeSort(arr,0,n-1);//私有函数,递归调用该函数对arr数组0到n-1进行排序
}

template<typename T>
//递归使用归并排序对arr[l,...,r]的范围进行归并排序
void __mergeSort(T arr[], int l, int r){
	if(l>=r)
		return;
	int mid = (l+r)/2;
	__mergeSort(arr,l,mid);//对左边进行归并排序
	__mergeSort(arr,mid+1,r);//对右边进行归并排序
	__merge(arr,l,mid,r);//对左右两部分进行归并操作
}

template<typename T>
void __merge(T arr[], int l, int mid, int r){
 	T aux[r-l+1];//申请的额外空间
 	for(int i=l;i<=r;i++)
 		aux[i-l] = arr[l];//arr下标从l开始,aux下标从0开始
 	int i = l, j = mid+1;
 	for(int k=l;k<=r;k++){
 		if(i>mid){//保证索引i不越界,当i>mid时,索引j及后面的元素加入arr
 			arr[k] = aux[j-l];
 			j++
 		}
 		else if(j>r){//保证索引j不越界,当i>mid时,索引j及后面的元素加入arr
 			arr[k] = aux[i-l];
 			i++;
 		}
 		else if(aux[i-l]<aux[j-l]){
 			arr[k] = aux[i-l];
 			i++;
 		}
 		else{
 			arr[k] = aux[j-l];
 			j++;
 		}
 	}
}

[算法优化]

(1)当数组近乎有序时,减少不必要的归并操作
template<typename T>
void __mergeSort(T arr[], int l, int r){
	if(l>=r)
		return;
	int mid = (l+r)/2;
	__mergeSort(arr,l,mid);
	__mergeSort(arr,mid+1,r);
	if(arr[mid]>arr[mid+1])//若arr[mid]<arr[mid+1]则整个数组有序
		__merge(arr,l,mid,r);
}
(2)针对递归到底进行优化,当递归接近底端时,由于数组元素越来越少,所以整个数组近乎有序,采用插入排序进行优化
template<typename T>
void __mergeSort(T arr[], int l, int r){
	if(r-l<=15){
		insertSortion(arr,l,r);
		return;
	}
	int mid = (l+r)/2;
	__mergeSort(arr,l,mid);
	__mergeSort(arr,mid+1,r);
	if(arr[mid]>arr[mid+1])//若arr[mid]<arr[mid+1]则整个数组有序
		__merge(arr,l,mid,r);
}

template<typename T>
void insertSort(T arr[], int l, int r){
	for(int i=l+1;i<=r;i++){
		int j;
		T temp = arr[i];
		for(j=i;j>0&&temp<arr[j-1];j--){
			arr[j] = arr[j-1];
		}
		arr[j] = temp;
	}
}

2、自底向上的归并排序

递归实现的是自顶向下的归并排序
在这里插入图片描述

[代码实现]

template<typename T>
void mergeSortBU(T arr[],int n){
	for(int sz=1;sz<=n;sz+=sz){//sz表示将一侧要进行merge操作的元素个数
		for(int i=0;i+sz<n;i+=sz+sz){//i表示merge操作起始位置
			//i+sz<n 保证每次对两部分进行merge
			//min(i+sz+sz-1,n-1) 数组末尾可能不足sz个元素 保证i+sz+sz-1不越界
			//对arr[i,..,i+sz-1]和arr[i+sz,..,i+sz+sz-1]进行归并
			__merge(arr,i,i+sz-1,min(i+sz+sz-1,n-1));
		}
	}

2、快速排序

20世纪对世界影响最大的算法之一

[算法思想]

首先,从当前数组中选定一个元素(通常为第一个元素)
然后,将选定元素按照某种方法移到排好序时应当处在的位置
对选定元素两边继续进行上述操作。
在这里插入图片描述

[Partition操作]

通过该方法找到选定元素相应位置。
索引left:选定的元素
索引j: <v和>v的分界点
索引i: 待判断元素
其中arr[left+1,…,j]<v ; arr[j+1,…i-1]<v
在这里插入图片描述
若arr[i]>v 则将元素e直接纳入>v部分,即i++
在这里插入图片描述
若arr[i]<v 则将元素e纳入<v部分,即j++,swap(arr[j],arr[e])
在这里插入图片描述
partition操作后数组变为
在这里插入图片描述
最后再将arr[left]与arr[j]进行交换,即完成partition操作

在这里插入图片描述

[代码实现]

//用户调用的函数
template<typename T>
void quickSort(T arr[], int n){
	__quickSort1(arr,0,n-1);//私有函数,递归调用该函数对arr数组0到n-1进行排序
}

template<typename T>
void __quickSort1(T arr[], int l, int r){
	//对arr[l,...,r]进行快速排序
	if(l>=r)
		return;
	int p = __partition(arr,l,r);
	__qucikSort1(arr,l,p-1);
	__quickSort1(arr,p+1,r);
}
template<typename T>
int __partition(T arr[], int l, int r){
	//对arr[l,...,r]进行partition操作
	//返回索引p,使得arr[l,...,p-1]<arr[p],arr[p+1,...,r]>arr[p]
	T v = arr[l];
	//定义arr[l+1,...,j]<v, arr[j,...,i)>v
	//注意i为开区间,因为i定义为待判定元素
	//要保证程序始终满足定义
	//初始状态要保证<v和>v部分为空,所以将索引j初始化为l
	int j = l;
	for(int i=l+1;i<=r;i++){
		if(arr[i]<v){
			swap(arr[j+1],arr[i])//建议写成swap(arr[++j],arr[i])
			j++;
		}
	}
	swap(arr[l],arr[j]);
	return j;
}		

[算法改进]

(1)针对递归到底的条件进行优化,当数据量较少时采用插入排序
[代码实现]
//用户调用的函数
template<typename T>
void quickSort(T arr[], int n){
	__quickSort2(arr,0,n-1);//私有函数,递归调用该函数对arr数组0到n-1进行排序
}

template<typename T>
void __quickSort2(T arr[], int l, int r){
	if(r-l<=15){
		insertionSort(arr,l,r);
		return;
	}
	int p = __partiton(arr,l,r);
	__quickSort2(arr,l,p-1);
	__quickSort2(arr,p+1,r);
}

template<typename T>
void insertionSort(T arr[], int l, int r){
	for(int i=l;i<r-l+1;i++){
		int j = i;
		T temp = arr[i];
		for(j=i;j>0&&temp<arr[j-1];j--){
			arr[j] = arr[j-1];
		}
		arr[j] = temp;
	}
}
//partition操作没有改变
...
(2)对于近乎有序的数组,快速排序的性能会退化,最差退化,若数组完全有序,快排性能退化为O(n*n)
[分析]

数组进行partiton操作后没有<v的部分,只有>v的部分,下一次继续选择最左边作为v,以此类推,排序树会退化为一个链表。
在这里插入图片描述

[解决办法]

随机选择v

//用户调用的函数
template<typename T>
void quickSort(T arr[], int n){
	srand (time(NULL));//初始化随机数种子
	__quickSort2(arr,0,n-1);//私有函数,递归调用该函数对arr数组0到n-1进行排序
}
//quickSort2操作没有改变
...

template<typename T>
int __partition(T arr[], int l, int r){
	//随机产生一个l<=i<=r的索引,rand()%(r-l+1)+l
	swap(arr[rand()%(r-l+1)+l],arr[l]);
	T v = arr[l];
	int j = l;
	for(int i=l+1;i<=r;i++){
		if(arr[i]<v){
			swap(arr[++j],arr[i]);
		}
	}
	swap(arr[l],arr[j]);
	return j;
}

想说的话

这一部分主要是介绍了两种O(nlogn)的算法,分别是归并排序和快速排序,其中针对快速排序的优化只介绍了两种比较基础的(因为今天太晚了,而且写的太长大家阅读起来也不方便)下期介绍两种快速排序的高级优化分别是双路快排以及三路快排。

对了,因为之前没有写过博文,所以在发表时,有些文章标签,分类专栏弄得不是很清楚,最重要得是,不知道这种整理笔记的博文算不算是原创,希望有csdn的大佬可以告知,如果有任何不妥,我会立马修改。如果代码有任何问题我也会及时更改。

大家加油~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值