数据结构与算法之归并、快速排序
这是排序问题中的两个重要且基础的排序算法。之所以拿来一起介绍使用为他俩有着异曲同工之妙,只要理解了归并排序,就知道了快排是咋回事的了。
归并排序就是从局部无序变有序,最后全局无序变有序。基本思想就是先俩俩一组,通过合并排序,保证组内有序后,我们在将相邻的两组合并排序(将两组链接并排序)成为新的一组。然后再合并排序,再依次循环,直到一组的长度是整个待排序数组的长度。
传统的排序算法就是写两个嵌套的循环,然后一个个的对比,那么此时的算法的时间复杂度就是O(nn),当要求排序的数组是非常大的时候,这是不可接受的。而归并排序的时间复杂度是O(nlogn),显然是优于传统的排序算法。
代码实现:
1,我们先设计出合并排序代码,即将两组数组合并的同时进行排序。
const int maxn=100;
void merge(int a[],int l1,int r1,int l2,int r2){
int temp[maxn];
int i=l1;
int j=l2;
int index=i;
while(i<=r1&&j<=r2){
if(a[i]<a[j]){
j++;
temp[i]=a[i];
index+=1;
}
else{
i++;
temp[index]=a[j];
index+=1;
}
}
if(i<=r1){
for(int k=r1;k<=r1;k++){
temp[index]=a[k];
index+=1;
}
}
if(j<=r2){
for(int k=r2;k<=r2;k++){
temp[index]=a[k];
index+=1;
}
}
for(int i=l1;i<=r2;i++){
a[i]=temp[i];
}
}
归并排序的非递归实现:
void mergesort(int a[]){
for(int step=1;step<=n/2;step=step*2){
for(int i=0;i<n;i=i+step;){
merge(a,i,i+step-1,i+step,min(n,i+2*step-1))
}
}
}
递归实现:
void mergesort(int a[],int left ,int right){
if(left<right){
int mid=(right+left)/2;
mergesort(a,left,mid);
mergesort(a,mid+1,right);
merge(a,left,mid,mid+1,right);//相当于二叉树的后序遍历
}
}
注意递归代码如果看不懂的话,可以结合二叉树的后序遍历,来画一下二叉搜索树模拟一下。
下面我们在介绍一下快速排序。第一步是选择主元,那啥是主元呢?说白了就是待排序的那个元素。第二步是将主元放在正确的位置。下面结合一个例子:
待排序的数组:5 7 2 3 6 ,要求按增序顺序
排序过程:将5作为主元,然后核心思想就是将大于5的放在5的右边,小于5的放在5的左边。操作后就变为:3 2 5 7 6。
我们发现现在离成功进了一步,因为元素5的位置已经正确。继续观察,元素5两侧的序列仍是无序状态,所以我们以5为界限,拆成两个子序列,然后对两个子序列分别再进行一次,仍然选择子序列的第一个元素为主元。。。。。依次进行,直至子序列的长度为1。代表排序完成。所以不难算出该算法的时间复杂度为O(n*logn)。
快速排序代码如下:
为啥说有点相似呢?因为他俩都有二分的一影子,前者是数量二分,后者是位置二分。他俩又是不同的,归并排序“目光短浅”,他一开始就让小段小段的有序,直至后来全局有序。而快速排序“目光长远”,先让混沌无序的数组变得有一点有序,即有一个元素处于正确的位置,然后整体有序。
另外当数组中的元素越随机时,快速排序算法的效率越高。特别的,当待排序的数组已经是增序或者减序时,该算法的时间复杂度是O(n*n),该算法完全退化为一般的排序算法(即相当于两层for循环)。因为每次的子序列就是它母序列本身。
以下是快速排序的递归实现的代码:
void qucksort(int a[],int left,int right){
int temp;
a[left]=temp;
int i=left;
int j=right;
int mid;
while(i<j){
while(a[j]>=temp&&i<j){
j--;
}
a[i]=a[j];
while(a[i]<temp&&i<j){
i++;
}
a[j]=a[i];
}
mid=j;
a[j]=temp;
qucksort(a,left,mid-1);
qucksort(a,mid+1,left);
}