小伙伴们大家好,今天给大家介绍两种排序方法。
(义父义母们给孩子一个赞吧呜呜)
归并排序
给定一个无序数组,使其变为有序,归并排序的思想是不断将数组一分为二使其变为有序直到最终分解的数组只有一个元素(本身即有序),再不断合并直到合并为原数组长度。
分解
归并排序分解简单,只需进行一步:mid=(start+end)/2,或者使用mid=satrt+(end-start)/2。(可以防止溢出)这样原来的数组就会被分解为两部分:(start,mid)、(mid+1,end)。
合并
归并排序重在合并。我们要想将两个数组合为一部分,就需要知道两个数组的首尾。剩下的就比较简单,循环遍历两个数组,谁小要谁,该数组下标加一。
代码实现
#include<iostream>
using namespace std;
void merge(int *a,int start,int mid,int end){
int i=start,j=mid+1,k=0;
int result[end-start+1];
while(i<=mid&&j<=end){
if(a[i]<a[j]){
result[k++]=a[i++];
}
else{
result[k++]=a[j++];
}
}
while(i<=mid){
result[k++]=a[i++];
}
while(j<=end){
result[k++]=a[j++];
}
for(int m=start;m<=end;m++){
a[m]=result[m-start];
}
}
void mergesort(int *a,int start,int end){
if(start<end){//数组仅有一个元素本身有序无需处理
int mid=start+(end-start)/2;
mergesort(a,start,mid);
mergesort(a,mid+1,end);
merge(a,start,mid,end);
}
}
int main(){
int a[6]={3,8,2,9,5,2};
mergesort(a,0,5);
for(int i=0;i<6;i++){
cout<<a[i]<<" ";
}
}
大家看代码时注意一下细节,比如合并时将result数组元素赋值给原数组,需要将result数组下标减去start。
快速排序
思想
快速排序思想是先从数组中找一个主元,(开始元素,末尾元素,随机选取......)每一趟快速排序实现:数组中比主元小的放置主元左侧,数组中比主元大的防止主元右侧。并将数组以主元所在位置为依据一分为二,重复操作。
方法一(交换法)
利用双指针i、j,i初始时为start,j初始时为end,i找比主元大的元素,j找比主元小的元素,都找到后将其交换。若i、j相遇,则代表遍历结束,需要将主元和该位置元素交换。
先给大家看一下代码,细节一会解释:
#include<iostream>
using namespace std;
int getmid(int *a,int start,int end){
int i=start,j=end;
int key=a[start];//选取第一个元素为主元
while(i<j){
while(i<j&&a[j]>=key){
j--;
}
while(i<j&&a[i]<=key){
i++;
}
if(i<j){
swap(a[i],a[j]);
}
else{
swap(a[start],a[i]);
}
}
return i;
}
void quicksort(int *a,int start,int end){
if(start<end){//数组仅有一个元素本身有序无需处理
int mid=getmid(a,start,end);
quicksort(a,start,mid-1);
quicksort(a,mid+1,end);
}
}
int main(){
int a[6]={3,7,2,1,4,1};
quicksort(a,0,5);
for(int i=0;i<6;i++){
cout<<a[i]<<" ";
}
}
该代码中,我们以开始元素作为主元。这时有很多小伙伴有疑问:为什么最后可以直接将主元和相遇位置元素交换呢 ?也就是说为什么相遇位置元素值一定比主元小呢?
我们来分析一下:技巧就是先j后i。如果j最后一次操作未找到比主元小的元素,那么它一直减小直到找到i,上一轮i的位置元素是交换得来的一定比主元小,该种情况相遇位置元素比主元小。如果j最后一次操作找到了比主元小的元素,此时轮到i,i一直增大和j相遇,(i找到比主元大的说明不是最后一轮)此时j的元素比主元小,即该情况相遇位置元素比主元小。综合两种情况,相遇位置元素一定比主元小,可以直接交换。
还需要注意一个小细节:对于i来说第一次操作肯定满足判定条件执行i++,因此a[start]元素值不会发生改变,一直是主元,因此可以直接写为swap(a[strat],a[i]))。
如果我们选取最后一个元素作为主元呢?第一种方法是先将主元和开始元素进行交换,转换为第一个元素当作主元,重复上述操作。第二种方法是:先i后j,这样最后相遇位置元素一定大于主元,将主元和相遇位置元素进行交换即可。
方法二(覆盖法)
方法二和方法一比较类似,只是细节上有所差异。我们以开始元素作为主元,将其看作一个坑,依然采用先j后i(i和j的含义同上),当j找到一个比主元小的元素后,将该元素覆盖掉坑,并且此时将该元素位置作为新坑。当i找到一个比主元大的元素后,将该元素覆盖掉坑,并且此时将该元素位置作为新坑。最后i、j相遇时,将主元防止坑上即可。
先给大家展示代码,细节一会讲:
#include<iostream>
using namespace std;
int getmid(int *a,int start,int end){
int i=start,j=end;
int key=start;//坑的位置(初始时为主元位置)
int x=a[start];//记录主元 因为a[start]元素会发生变化
while(i<j){
while(i<j&&a[j]>=x){
j--;
}
if(i<j){
a[key]=a[j];
key=j;
}
else{
a[key]=x;
}
while(i<j&&a[i]<=x){
i++;
}
if(i<j){
a[key]=a[i];
key=i;
}
else{
a[key]=x;
}
}
return key;
}
void quicksort(int *a,int start,int end){
if(start<end){//数组仅有一个元素本身有序无需处理
int mid=getmid(a,start,end);
quicksort(a,start,mid-1);
quicksort(a,mid+1,end);
}
}
int main(){
int a[6]={3,8,7,9,5,2};
quicksort(a,0,5);
for(int i=0;i<6;i++){
cout<<a[i]<<" ";
}
}
该思路使用覆盖的方法,可能小伙伴们会有疑问:会不会存在某个元素被覆盖而未被保存 ?我们来分析一下:我们每次将坑中的元素覆盖,而坑中的元素已经覆盖上一个坑,也就是说只有第一次被覆盖的元素才会有这种可能。第一个坑的初始位置元素为主元,因此我们提前保存主元就可以。
和方法一不同的是,a[strat]元素会发生变化,因此不能使用a[key]=a[start]来将主元放到数组中。我们提前保存主元为X,使用a[key]=x即可。
如果我们选取最后一个元素作为主元呢?第一种方法是先将主元和开始元素进行交换,转换为第一个元素当作主元,重复上述操作。第二种方法是:先i后j,坑的初始位置为end,继续上述操作即可。
方法三(双指针法)
方法三我们使用双指针i,j。采用首元素作为主元,i用来记录最后一个比主元小的位置,j用来遍历。如果j位置的元素值大于主元,j++,i不动。否则交换j位置元素和i+1位置元素,i++,j++。当j=end时循环结束。最后将a[i]和a[start]进行交换即可。(i+1位置为比主元大的元素)
还是一样,先给大家看一下代码,再做具体解释:
#include<iostream>
using namespace std;
int getmid(int *a,int start,int end){
int i=start,j=start+1;
int key=a[start];
while(j<=end){
if(a[j]>=key){
j++;
}
else{
swap(a[++i],a[j++]);
}
}
swap(a[i],a[start]);
return i;
}
void quicksort(int *a,int start,int end){
if(start<end){//数组仅有一个元素本身有序无需处理
int mid=getmid(a,start,end);
quicksort(a,start,mid-1);
quicksort(a,mid+1,end);
}
}
int main(){
int a[6]={3,2,7,9,5,2};
quicksort(a,0,5);
for(int i=0;i<6;i++){
cout<<a[i]<<" ";
}
}
当第二个元素比主元小时,交换自己和自己无影响(大家也可以多增加一个判断条件排除这种情况让其不交换)。
如果我们选取最后一个元素作为主元呢?第一种方法是先将主元和开始元素进行交换,转换为第一个元素当作主元,重复上述操作。第二种方法是:i为end,j为end-1,i用来记录最后一个比主元大的元素,j用来遍历。当j遇见比主元小的元素时,j--,i不变。否则交换j位置元素和i-1位置元素,j--,i--。循环结束条件为j走到0位置。最后交换a[end]和a[i]即可。(a[i-1]为比主元小的元素)
总结
归并排序注重合并,快速排序注重分解。并且快速排序可以有多种方法实现。除上述三种方法外,还有很多,大家可以自己查找相关资料。
希望大家多多点赞,感谢大家支持!!(给孩子一个赞把呜呜)!!