注:不适合对完全没学过的,仅供自己发现问题后重新找找是不是以前的问题。
突然发现堆排竟然被放在了useless里面hhhhh,那就先不学吧,掌握好快排和归并
快排
分治与递归结合
快排基于分治
简要说下之前对快排的误解:
1. 为什么要分两边
+ 首先这样子做可以把比这个数小的重新排序,比这个数大的也重新排序
+ 如果按照自己以为的,快排就会出现最小值或最大值恰在一端时的死循环。(左key右先左最小卡死,右key左先右最大卡死,不信可以试试)
+ 所以我们将其分为两块重新排序。
+ 相对来说比分块要占用的少,不容易出现段错误(RE)
2. 左边key为什么一定要从右边开始
+ 因为如果左指针先走,找到了位置,然而右指针却没找到比key小的值,这时候就会发生key值与右指针所指交换,这是错误的,所以我们取一边为key,让另一边先走。
3. 最后的分组到底怎么分,为什么不是左+右的和除2分开
+ 因为此时begin已经到达了此处了,我们不需要再计算一遍,此乃其一
+ 其二是真的正好是在中间吗?不能在边上吗?为何一定在中间呢?快排可是存在一圈没找到key恰为最小这种情况的,怎么能直接平分中间呢?
+ 所以必须如此,也最方便,毕竟此时begin==end,以其平分,这个数正好就在这个位置,所以一侧-1,一侧+1,不要把这个数字带进去。
自己敲的代码,算是半个独立吧:
如果你是用c写的:
1.头文件iostream换stdio
2.cin换scanf()
3.cout换printf()
4.swap用指针写
#include<iostream>
#include<time.h>
#include<stdlib.h>
using namespace std;
int a[1000];
void swap(int &a,int &b){
int temp=a;
a=b;
b=temp;
return ;
}
void quicksort(int left,int right){
if(left>=right){
return ;
}
int begin=left,end=right;
int key=a[begin];
while(begin<end){
while(a[end]>=key&&begin<end){
--end;
}
while(a[begin]<=key&&begin<end){
++begin;
}
swap(a[begin],a[end]);
}
swap(a[begin],a[left]);
quicksort(left,begin-1);
quicksort(begin+1,right);
return ;
}
int main(){
int n;
cin>>n;
srand((int)time(0));
for(int i=0;i<n;i++){
a[i]=rand()%100+1;
}
quicksort(0,n-1);
for(int i=0;i<n;i++){
cout<<a[i]<<' ';
}
return 0;
}
归并
归并排序,采用的也是分治和递归的思想
当我把一堆数据无限细分成单个,此时我就可以两两比较放局部有序,然后递归回去排就行
所以整个算法实际上分拆到最后不可再拆和归并
还是先提注意事项:
1. + 不要理解错误,不是让把原来的数组真正物理意义上的分开,我们完全可以限定区间操作做到逻辑意义上的分开。
+ 真正需要开辟空间的是我们每次取元素后要放的位置,而不是开数组放原来数组内容。原来的数组此段是我们即将放入局部有序序列的容器。
2. + 归并的范围,这里好好说说,毕竟是写过一次后开始自己推敲了半个多小时的。
+ 首先,我最后一定要把这个东西给分为单个元素,一定一定是单个元素,否则两个元素的话,比如91,那么我插入顺序就是9和1,不一定会是有序的。
+ 所以我们一定一定要把范围确定好。什么时候结束?当这个东西,范围恰好只有一个,即【0,0】这样子的,此时作为结束条件,即left>=right
+ 为什么在这个时候作为结束条件呢?因为首先,这个区间不能再分了,否则可以无限循环的,是个死循环。其次,我只要让其一个元素为1了,另一个元素也分为1或者不为1但再分为1,就可以作为局部有序了。一个元素必定有序啊。
+ 所以我们返回后,取中间,这样两边都是1个元素,这样的话,那么就可以进行分治了
+ 我们取中间,一段为中间-左端+1,一段为右边-中间。为什么呢,因为本身都是+1,但是右端需要往右一下,所以少了那个1。拿实例说话,比如【0,2】这个区间,这时候一共可是3个元素,所以取中间后是0-1,因为c么,从0计数,所以是两个元素了,那么就是1-0+1,即取n1=mid-left+1,n2=right-mid。实际上为right-(mid+1)+1,为mid右移一位后right-mid加1,所以就直接right-mid了。
3. + 划分好区间后就开始局部排序了。这时候首先我要开辟空间,大小上文提到了,不多说。
+ 两个数组要分别获取对应区间中的元素,如上文所说以mid为中心分开,至于取的a的元素,自然也就与此时的区间有关。前面一个区间从left开始取,每次+i,后面一个区间,如上文所说,从mid+1开始取,然后+i。当然你这里都可以换成j,没有影响。
+ 然后是排序,统一方法是比较俩个数组,找到较小的添上去,较大的不变。由于我们在排序之前的两个区间都是局部有序,那么我们可以只比较首元素就行。
+ 注意比较首元素的时候需要一个i一个j,不能合在一块儿。
+ 而被赋值的a的起始左边为什么呢?为left,令一个数等于left后就让他往后动就行,否则你只打算让数组在某一个地方来回动么?
+ 一方率先空后,另一方把剩下的全部按顺序加入数组中。
+ 记得解放空间。
嫑废话,上代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<malloc.h>
#include<ctime>
using namespace std;
int a[1000];
void merge(int left,int right){
int i,j,k=0;
int mid=(left+right)/2;
int n1=mid-left+1;
int n2=right-mid;
int *front=(int *)malloc(n1*sizeof(int));
int *base=(int *)malloc(n2*sizeof(int));
for(i=0;i<n1;i++){
front[i]=a[i+left];
}
for(i=0;i<n2;i++){
base[i]=a[i+mid+1];
}
i=0;
j=0;
k=left;
while(i<n1&&j<n2){
if(front[i]<base[j]){
a[k++]=front[i++];
}else{
a[k++]=base[j++];
}
}
while(i<n1){
a[k++]=front[i++];
}
while(j<n2){
a[k++]=base[j++];
}
free(front);
free(base);
return ;
}
void mergesort(int left,int right){
if(left>=right){
return ;
}
mergesort(left,(left+right)/2);
mergesort((left+right)/2+1,right);
merge(left,right);
}
int main(){
int n;
cin>>n;
srand((int)time(0));
for(int i=0;i<n;i++){
a[i]=rand()%250+1;
}
mergesort(0,n-1);
for(int i=0;i<n;i++){
cout<<a[i]<<' ';
}
return 0;
}