常见排序算法03
1 快速排序
2 归并排序
1 快速排序
快排的本质就是 分而治之 ,也就是说以 key 为准,对两边的数据进行一个整理,比如:大于 key 的值往 key 的右边扔,小于 key 的往左边挪,至于等于 key 的值往左往右都行。
快排注意一下:有些数组第一次是不需要挖坑填坑的。例如,2 5 3 6 4 7。2是基数,从右往左却没有比它小的数,2仍填进原来的位置,此轮填坑就结束,然后递归排序2左右两边的数。
看代码:
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<sys/timeb.h>
#define MAX_SIZE 10
//快速排序 --挖坑填坑
int QuickSort(int arr[],int start,int end){
int i=start;
int j=end;
int tmp=arr[start]; // 保存基数
if(i<j){ // 此if为递归的返回条件!!!
// 最外层的while相当于做一个简单的分类,例如升序时,小的往左扔,大的往右扔
while(i<j){ // 在一轮基数里确保不断的挖坑填坑!!!
while(i<j && arr[j]>tmp){ //先从右往左 找小于基数的数 且i也不能大于j
j--;
}
if(i<j){ // 如果i<j说明找到比基数小的数 而不是因为i=j退出的 因此下面填坑 也相当于挖坑
arr[i]=arr[j];
i++; // 填完记得将i加1
}
while(i<j && arr[i]<tmp ){ // 再从左往右 找大于基数的数
i++;
}
if(i<j){ // 填坑挖坑
arr[j]=arr[i];
j--;
}
}
// 最后将tmp填进i或j的下标中-----这样就进行了一次基数填进
arr[j]=tmp;
// 最终实现每个数的排序是通过一次次递归实现的
//然后循环填基数
//把左半部分进行快速排序 确定的基数就不必再排序
QuickSort(arr,start,i-1);
//把右半部分进行快速排序
QuickSort(arr,i+1,end);
}
return 0;
}
//总结快写小技巧:3个if3个while 条件都有i<j
//输出数组
int PrintArr(int arr[],int len){
for(int i=0;i<len;i++){
printf("%d ",arr[i]);
}
printf("\n");
return 0;
}
int main(){
//创建数组并用随机数赋值
int arr[MAX_SIZE];
srand((unsigned int)time(NULL));
for(int i=0;i<MAX_SIZE;i++){
arr[i]=rand()%10;
}
PrintArr(arr,MAX_SIZE);
//调用快速排序
QuickSort(arr,0,MAX_SIZE-1);
//打印数组
PrintArr(arr,MAX_SIZE);
return 0;
}
总结快排:挖坑填坑。快写小技巧:3个if3个while 条件都有i<j。
2 归并排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<sys/timeb.h>
#define MAX_SIZE 10
//两个有序数组合并函数
//mid的作用是将一个数组分成两部分
int Merge(int arr[],int start,int mid,int end,int *tmp){
//用局部变量代替原下标 记录两部分的始末下标
int i_start=start;
int i_end=mid;
int j_start=mid+1;
int j_end=end;
//用于记录tmp临时数组的元素个数
int len=0;
//循环将两个有序数组放进临时数组
while(i_start<=i_end && j_start<=j_end){
if(arr[i_start]<arr[j_start]){ //归并要稳定需要加等号
tmp[len]=arr[i_start];
i_start++;
len++;
}else{
tmp[len]=arr[j_start];
j_start++;
len++;
}
}
//最后将剩余的元素循环放进临时数组
//第一个有序数组的剩余部分
while(i_start<=i_end){
tmp[len]=arr[i_start];
i_start++;
len++;
}
//第二个有序数组的剩余部分
while(j_start<=j_end){
tmp[len]=arr[j_start];
j_start++;
len++;
}
// 1因为只是tmp排好序 原数组并没有排序 需要为下一次有序排序做准备
// 所以最后 再将临时数组排好序的元素放回原数组 也是最容易忘掉的步骤!!!
for(int i=0;i<len;i++){
arr[i+start]=tmp[i]; // 2arr加start是因为当左右两部分数组合并时,tmp将arr原本左边排好序的覆盖
} // 例如4和2排序后arr[0]=2 arr[1]=4;8返回不会调用Merge 所以恰巧2、4与8合并时8并不会覆盖左边的2和4
// 但是2 4 8排好序后 0和3各自返回能调用Merge 再将0 3写入arr[i]=tmp[i];那么就变成0 3 8了
return 0; // 所以一定要加start 才能变成248 03两个有序数组
}
//归并排序--思想:递归左右部分后合并排序
//利用递归依次将数组从中间分成两个部分,指到为1个元素就返回,然后每次将分开的两部分有序数组合并
int MergeSort(int arr[],int start,int end,int *tmp){
//如果大于等于(或者等于)就说明每部分只有一个元素,就返回,不再分组
if(start == end){
return 0;
}
//分成两部分的分隔下标
int mid=(start+end)/2;
//再分隔前半部分
MergeSort(arr,start,mid,tmp);
//再分隔后半部分
MergeSort(arr,mid+1,end,tmp);
//最后将这两部分有序数组合并(有序是因为单个元素调用时,每次都调用该合并函数排好序)
Merge(arr,start,mid,end,tmp);
return 0;
}
//输出数组
int PrintArr(int arr[],int len){
for(int i=0;i<len;i++){
printf("%d ",arr[i]);
}
printf("\n");
return 0;
}
int main(){
//创建数组并用随机数赋值
int arr[MAX_SIZE];
//创建临时数组
int *tmp=(int *)malloc(sizeof(int)*MAX_SIZE);
srand((unsigned int)time(NULL));
for(int i=0;i<MAX_SIZE;i++){
arr[i]=rand()%10;
}
//排序前数组
PrintArr(arr,MAX_SIZE);
//调用归并排序
MergeSort(arr,0,MAX_SIZE-1,tmp);
//排序后数组
PrintArr(arr,MAX_SIZE);
//释放临时数组
free(tmp);
return 0;
}
总结归并排序:递归将数组分成只有一个元素的有序数组,然后调用有序排序。但要注意:
1)最后需要在Merge用有序的临时数组给原数组赋值,确保它有序,为下一次有序排序作准备;
2)赋值时需加上start,防止左右合并时右边的数覆盖左边的排好序的数。
最后的总结:
1)总结快排:
挖坑填坑。快写小技巧:3个if3个while 条件都有i<j。
快排的本质就是 分而治之 ,也就是说以 key 为准,对两边的数据进行一个整理,比如:大于 key 的值往 key 的右边扔,小于 key 的往左边挪,至于等于 key 的值往左往右都行。
2)总结归并排序:递归将数组分成只有一个元素的有序数组,然后调用有序排序。但要注意:
1)最后需要在Merge用有序的临时数组给原数组赋值,确保它有序,为下一次有序排序作准备;
2)赋值时需加上start,防止左右合并时右边的数覆盖左边的排好序的数。