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的大佬可以告知,如果有任何不妥,我会立马修改。如果代码有任何问题我也会及时更改。
大家加油~