1.快速排序
快速排序思想
根本思想肯定是分治,每一次选出一个数,将原数组分成两部分,比该数小的放在左边,比该数大的放在右边(仅仅指从小到大的顺序排列),那么此时我们对该数的排序就算完成,然后通过递归,将数组一分再两半,将两部分又分别重新选出一个数,对该数进行一次"排序“。不断向下递归,直到每一个数都被我们排序过一次(最后剩下的一个数不用,因为他本身就是最小或者最大(也可能是局部最大或者最小)),这样操作以后排序就算完成。
快速排序实现
其实快排中最难的就是将数组按照大小分成两半的部分
法一: 一般是采用双指针方法,将数组从两头开始扫,比如说 i指针最开始指向左边第一个数,j指针最开始指向右边第一个数,mid指我们选定的那个数,while a[i]<a[mid]则i++,同理while a[j]>a[mid]则j–,当进行完这一步之后如果i仍然小于j则说明,i,j卡住了,即a[i]>=a[x],a[j]<=a[x],此时我们就可以swap(a[i],a[j]),然后重新进行上一步操作。
法二: 同时也可以采用从头到尾遍历的方法,具体操作:只需要一个指针(标记位),一般可以以第一个数作为需要进行排序的数(暂记为A),从第二个数(标记位)开始扫,如果扫到的数小于A,则将该数与标记位swap,同时标记位++(标记位表示小于A的数列尾端位置,也就是最后将数列一分为二的中间点),遍历完之后,从第二位到标记位都是小于A的数,标记位之后的数都是大于或等于A的数,此时我们仅仅只需要将A与标记位对应的数swap则可以实现将数组一分为二(一边为小于A的数,一边为大于等于A的数)的目标
AC代码
#include <bits/stdc++.h>
const int MAXN = 1e7;
using namespace std;
int n,a[MAXN];
//三数取中
//排序稳定:并不是指时间复杂度稳定,而是说相同值的数排序后按照原来的位置进行排序
void quick_sort(int l,int r)
{
if(l>=r) return ;
int pivot_mid = l;
int x = l+1;//标记位
for(int i=l+1;i<=r;i++)
{
if(a[i]<a[pivot_mid])
{
swap(a[x],a[i]) ;
x++ ;
}
}
for(int i=0;i<n;i++) printf("%d ",a[i]);cout<<endl;
swap(a[pivot_mid],a[x-1]);
quick_sort(l,x-2);
quick_sort(x,r);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
quick_sort(0,n-1);
for(int i=0;i<n;i++) printf("%d ",a[i]);
return 0;
}
排序是否稳定
排序稳定是指相同的数之间的相对位置不会改变,而在快速排序中与选中的数的相同大小的数可能会被swap,同时法二中被选中的数由于需要换到数组中间去也会影响稳定性,故:快速排序并不稳定。
快速排序时间复杂度
快速排序时间复杂度有不同情况
最坏情况: 当原数组处于正序或者倒序时,每次我们选择第一个数进行划分,那么该数就是最大数或最小数,也就意味着每次我们只能分割出一个数,那么就意味着需要向下递归n-1次,那么总的计算次数就是1+2+…+n-1,即n(n-1)/2,则时间复杂度为O(n^2);
最好情况: 就是每次我们都能选到中值进行排序,那么也就是只需要向下进行log2(n)次递归,计算次数也就是n*log2(n),时间复杂度也就是O(nlogn)
平均时间复杂度: 其实就是O(nlogn),只是不同情况下底数不同罢了
快排如何优化呢
1.为了避免原数组正序/倒序,我们可以直接将原数组打乱
2.为了避免选中最大数或者最小数,我们可以随机选取一个数来作为我们需要排序的数
3.可以采用三数取中的方法,大义为随机选取三个数,取这三个数中间大小的数进行排序
如何保证稳定性
可以将数组进行初始化,重新声明一个二维数组,第一个用来存储value,第二个用来存储等值数量,不过本人还没有想到一个时间复杂度比较小的方法进行初始化。
2.归并排序
算法思想
其实这个也是一种分治思想的应用,就是先数组一分为二,再一分为二,最后分的只剩一个,然后向上回溯,回溯的时候按照大小顺序进行数组的合并,因为最后一次递归只有一个数不需要排序,然后向上回溯过程中每一次回溯都按照大小顺序排列,则获得的每一个数组片段都是排好序的,只需要按照正确的顺序插入、合并即可。
合并代码片段
//这里的COPY数组用来存储排好序的数组片段
//A_1表示回溯上来的左边的数组的头指针,A_2表示回溯上来的右边的数组的头指针
//A_3表示COPY数组的头指针
//mid表示左边数组最后一位的下标
int A_1 = l,A_2 = mid+1,A_3 = 0;//分别为左边,右边,copy数组的指针;
while(A_1<=mid&&A_2<=r) //当两个数组都没有扫完时
{
if(ARRAY[A_1]<=ARRAY[A_2])//如果左边的数组更小一点则放前面
//为什么要加上等号呢,为了保证排序的稳定性
COPY[A_3++]=ARRAY[A_1++];
else
COPY[A_3++]=ARRAY[A_2++];
}
while(A_1<=mid) COPY[A_3++]=ARRAY[A_1++];
//如果左边数组还有剩余则添加到COPY数组后
while(A_2<=r) COPY[A_3++]=ARRAY[A_2++];
//同理
for(int i=l,j=0;i<=r;i++,j++)//将排好序的片段放回原数组里面
ARRAY[i]=COPY[j];
AC代码
#include <iostream>
#include <cstdio>
#include <ctime>
#include <algorithm>
#define MAXN 0x99ffff//10092543
using namespace std;
int N,ARRAY[MAXN],COPY[MAXN];
void merge_sort(int l,int r)
{
if(l>=r) return ;
int mid = l+r >>1;//二进制数整体右移一位
merge_sort(l,mid);merge_sort(mid+1,r);//向下递归
int A_1 = l,A_2 = mid+1,A_3 = 0;//分别为左边,右边,copy数组的指针;
while(A_1<=mid&&A_2<=r)
{
if(ARRAY[A_1]<=ARRAY[A_2])
COPY[A_3++]=ARRAY[A_1++];
else
COPY[A_3++]=ARRAY[A_2++];
}
while(A_1<=mid) COPY[A_3++]=ARRAY[A_1++];
while(A_2<=r) COPY[A_3++]=ARRAY[A_2++];
for(int i=l,j=0;i<=r;i++,j++)
ARRAY[i]=COPY[j];
return ;
}
int main()
{
scanf("%d",&N);
for(int i=0;i<N;i++)
scanf("%d",&ARRAY[i]);
merge_sort(0,N-1);
for(int i=0;i<N;i++)
printf("%d ",ARRAY[i]);
return 0;
}
归并排序时间复杂度
无论怎么样都需要向下分log2(n)次,每次进行n次比较、赋值运算,故时间复杂度为O(nlogn);
归并排序稳定性
因为我们在合并的时候保证了等值数相对位置的不改变,故归并排序是稳定的排序
完结撒花
uestc公管01