1. 归并排序
归并排序排序思路:
- 拆:先把数组一分为二拆分,最终拆分成单个数字
- 合:再把前半部分有序数组和后半部分有序数组排序
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stdbool.h>
void Random(int* arr,int n){
srand(time(NULL));
for(int i=0;i<n;++i){
arr[i] = rand()%n;
}
}
void PrintArr(int* arr,int n){
for(int i=0;i<n;++i){
printf("%d ",arr[i]);
}
printf("\n");
}
bool Ordered(int* arr,int n,bool asc){ // asc = true 表示为升序
if(NULL == arr || n < 1) return false;
if(1 == n) return true;
for(int i=0;i<n-1;++i){
if(asc){
if(arr[i] > arr[i+1]) return false;
}else{
if(arr[i] < arr[i+1]) return false;
}
}
return true;
}
/*********************************归并排序************************************/
//将两个有序数组合并成一个新的有序数组
void Merge(int* arr1,int n1,int* arr2,int n2){
int res[n1+n2]; //用于存放新数组
int i=0,j=0,k=0; //分别是arr1、arr2、res的下标
while(i<n1 && j<n2){
if(arr1[i] < arr2[j]){
res[k] = arr1[i];
++k;
++i;
}else{
res[k] = arr2[j];
++k;
++j;
}
// res[k++] = (arr1[i]<arr2[j]) ? arr1[i++] : arr2[j++];
}
while(i<n1){
res[k] = arr1[i];
++k;
++i;
}
// if(i<n1) memcpy(res+k,arr1+i,(n1-i)*sizeof(int));
while(j<n2){
res[k] = arr2[j];
++k;
++j;
}
// if(j<n2) memcpy(res+k,arr2+j,(n2-j)*sizeof(int));
//把排好序的数组再分别放回arr1和arr2中去
k = 0;
for(int i=0;i<n1;++i,++k){
arr1[i] = res[k];
}
for(int i=0;i<n2;++i,++k){
arr2[i] = res[k];
}
}
//先拆分,再合并排序(递归)
void MergeSort(int* arr,int n){
if(n<=1) return; //拆分到剩一个的时候退出
int mid = n/2; //先一分为二
MergeSort(arr,mid); //拆成前半部分
MergeSort(arr+mid,n-mid); //和后半部分
Merge(arr,mid,arr+mid,n-mid); //再合并排序
}
int main(){
int n;
scanf("%d",&n);
int arr[n];
Random(arr,n);
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
MergeSort(arr,n);
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
}
结果为:
30
22 28 6 5 28 22 25 14 18 22 5 19 6 18 25 11 13 6 2 21 6 22 8 8 11 5 16 29 20 7
0
2 5 5 5 6 6 6 6 7 8 8 11 11 13 14 16 18 18 19 20 21 22 22 22 22 25 25 28 28 29
1
程序的执行顺序如图:
以 “ 7 6 5 4 3 2 1 ” 为例
在两个有序数组合并时,我们可以直接使用三元运算符,还可以用数组复制的方式整块复制
//将两个有序数组合并成一个新的有序数组
void Merge(int* arr1,int n1,int* arr2,int n2){
int res[n1+n2]; //用于存放新数组
int i=0,j=0,k=0; //分别是arr1、arr2、res的下标
while(i<n1 && j<n2){
res[k++] = (arr1[i]<arr2[j]) ? arr1[i++] : arr2[j++];
}
if(i<n1) memcpy(res+k,arr1+i,(n1-i)*sizeof(int)); //memcpy(复制到哪儿,复制谁,复制多少个)
if(j<n2) memcpy(res+k,arr2+j,(n2-j)*sizeof(int));
//把排好序的数组再放回arr1中去,因为arr1和arr2是连续的
memcpy(arr1,res,(n1+n2)*sizeof(int));
}
在合并时,我们还可以理解为,将mid前后的两段有序数组排序成一个新的有序数组
//将mid前后的两段有序数组排序成一个新的有序数组
void Merge(int* arr,int n,int mid){
int res[n]; //用于存放新数组
int i=0,j=mid,k=0; //分别是arr1、arr2、res的下标
while(i<mid && j<n){
res[k++] = (arr[i]<arr[j]) ? arr[i++] : arr[j++];
}
if(i<mid) memcpy(res+k,arr+i,(mid-i)*sizeof(int)); //memcpy(复制到哪儿,复制谁,复制多少个)
if(j<n) memcpy(res+k,arr+j,(n-j)*sizeof(int));
//把排好序的数组再放回arr1中去,因为arr1和arr2是连续的
memcpy(arr,res,n*sizeof(int));
}
//先拆分,再合并排序(递归)
void MergeSort(int* arr,int n){
if(n<=1) return; //拆分到剩一个的时候退出
int mid = n/2; //先一分为二
MergeSort(arr,mid); //拆成前半部分
MergeSort(arr+mid,n-mid); //和后半部分
Merge(arr,n,mid); //再合并排序
}
迭代方法:
不管怎么分,直接合
先两个两个合(Merge),过后再两个两个合(Merge),过后再两个两个合(Merge)
//将mid前后的两段有序数组排序成一个新的有序数组
void Merge(int* arr,int n,int mid){
int res[n]; //用于存放新数组
int i=0,j=mid,k=0; //分别是arr1、arr2、res的下标
while(i<mid && j<n){
res[k++] = (arr[i]<arr[j]) ? arr[i++] : arr[j++];
}
if(i<mid) memcpy(res+k,arr+i,(mid-i)*sizeof(int)); //memcpy(复制到哪儿,复制谁,复制多少个)
if(j<n) memcpy(res+k,arr+j,(n-j)*sizeof(int));
//把排好序的数组再放回arr1中去,因为arr1和arr2是连续的
memcpy(arr,res,n*sizeof(int));
}
//安排合适的两个有序数组进行排序
int min(int a,int b) {return a<b?a:b;}
void SubMerge(int* arr,int n,int step){
int len = n;
for(int i=0;i+step<n;i+=step*2){
Merge(arr+i,min(2*step,len),min(step,len)); //merge(首地址,数组长度,中段下标)
len -= 2*step; //更新剩余长度
}
}
//给SubMerge函数提供步长
void MergeSort(int* arr,int n){
for(int i=1;i<n;i*=2){ //生成步长 1/2/4/6/8/···
SubMerge(arr,n,i);
}
}
2. 快速排序
快速排序排序思路:
- 以第一个数为基准,先从后往前找,找到一个比它小的数,停下来
- 再从前往后找,找到一个比它大的数,停下来,把这两个数交换位置
- 再做以上两步操作,直到右边碰到左边,这时所在位置的数一定比基准数小
- 把碰头数和基准数交换,这时能保证基准数左边都比它小,右边都比它大
- 记住这个位置,定下这个位置,把数组一分为二,前后数组继续用上述方法排序
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
void Random(int* arr,int n){
srand(time(NULL));
for(int i=0;i<n;++i){
arr[i] = rand()%n;
}
}
void PrintArr(int* arr,int n){
for(int i=0;i<n;++i){
printf("%d ",arr[i]);
}
printf("\n");
}
bool Ordered(int* arr,int n,bool asc){ // asc = true 表示为升序
if(NULL == arr || n < 1) return false;
if(1 == n) return true;
for(int i=0;i<n-1;++i){
if(asc){
if(arr[i] > arr[i+1]) return false;
}else{
if(arr[i] < arr[i+1]) return false;
}
}
return true;
}
/***********************快速排序*************************/
void swap(int* p,int* q){
int t = *p;
*p = *q;
*q = t;
}
//以一个数为基准,确定这个数所在位置
int partition(int* arr,int n){
int key = arr[0]; // 数组第一个值当作基准
int i=0,j=n-1;
while(i<j){
while(i<j && arr[j]>=key) --j; // j向先前跑找小的数,找到停下
if(i>=j)break;
while(i<j && arr[i]<=key) ++i; // i向再后跑找大的数,找到停下
if(i>=j)break;
swap(arr+i,arr+j); // 前后都停下来后,两个数交换
}
swap(arr+i,arr); // 碰头位置的数和基准数交换
return i; //返回碰头位置
}
//排好一个数后,分前后两个部分继续确定基准数所在位置(递归)
void QuickSort(int* arr,int n){
if(n<=1) return;
int index = partition(arr,n);
QuickSort(arr,index);
QuickSort(arr+index+1,n-(index+1));
}
int main(){
int n;
scanf("%d",&n);
int arr[n];
Random(arr,n);
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
QuickSort(arr,n);
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
}
3. 希尔排序
希尔排序排序思路:
- 数组个数/2为步长,对每块儿步长的对应元素进行插入排序
- 对上述操作的步长再/2作为新步长,再对每块儿步长的对应元素进行插入排序
- 直到步长为1,对所有元素进行插入排序
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
void Random(int* arr,int n){
srand(time(NULL));
for(int i=0;i<n;++i){
arr[i] = rand()%n;
}
}
void PrintArr(int* arr,int n){
for(int i=0;i<n;++i){
printf("%d ",arr[i]);
}
printf("\n");
}
bool Ordered(int* arr,int n,bool asc){ // asc = true 表示为升序
if(NULL == arr || n < 1) return false;
if(1 == n) return true;
for(int i=0;i<n-1;++i){
if(asc){
if(arr[i] > arr[i+1]) return false;
}else{
if(arr[i] < arr[i+1]) return false;
}
}
return true;
}
/***************************希尔排序****************************/
//对相隔步长的数进行插入排序
void insert(int* arr,int n,int step){
int last = arr[n-1]; //把抽到的这个数先存进来
int i = 0; //为了增长作用域,定义在for循环外
for(i=n-1-step;i>=0;i-=step){ //从后向前找
if(arr[i] > last){ //如果这个数比抽到的数大
arr[i+step] = arr[i]; //则向后移
}else{ //如果这个数比抽到的数小
arr[i+step] = last; //则把抽到的数紧接着填入
return;
}
}
arr[i+step] = last; //如果没有找到比抽到的数小的数,需要头插
}
//一个一个抽牌进入数组
void insertsort(int* arr,int n,int step){
for(int i=1;i<=n;++i){
insert(arr,i,step);
}
}
//生成对应的步进
void ShellSort(int* arr,int n){
for(int i=n/2;i>0;i/=2){
insertsort(arr,n,i);
}
}
int main(){
int n;
scanf("%d",&n);
int arr[n];
Random(arr,n);
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
ShellSort(arr,n);
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
}
三种高级排序算法总结:
4. 其他类型数据排序
必须告诉指针数据类型是多大(size),指针才能跨越多大的字节找到下一个元素
#include<stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
/**********************整数类型操作*******************/
void Random(int* arr,int n){
srand(time(NULL));
for(int i=0;i<n;++i){
arr[i] = rand()%n;
}
}
bool Ordered(int* arr,int n,bool asc){ // asc = true 表示为升序
if(NULL == arr || n < 1) return false;
if(1 == n) return true;
for(int i=0;i<n-1;++i){
if(asc){
if(arr[i] > arr[i+1]) return false;
}else{
if(arr[i] < arr[i+1]) return false;
}
}
return true;
}
//整数类型输出
void PrintArr(int* arr,int n){
for(int i=0;i<n;++i){
printf("%d ",arr[i]);
}
printf("\n");
}
//整数类型比大小
int cmp(const void* a,const void* b){
return *(int*)a - *(int*)b;
}
/************************浮点类型操作************************/
//浮点数类型比大小
int cmp_double(const void* a,const void* b){
return *(double*)a - *(double*)b > 0 ? 1:-1;
}
//浮点数类型输出
void PrintArrDouble(double* arr,int n){
for(int i=0;i<n;++i){
printf("%f ",arr[i]);
}
printf("\n");
}
/**********************各种类型快速排序**************************/
int Swap(void* a,void* b,int size){
unsigned char buffer[size]; //定义一个无符号的字符,用来放数据
memcpy(buffer,a,size); //a复制给buffer,大小为size
memcpy(a,b,size);
memcpy(b,buffer,size);
}
typedef int (*cmp_t)(const void*,const void*);
//以一个元素为基准,确定这个元素的位置
int MyPartition(void* arr,int n,int size,cmp_t cmp){
unsigned char key[size];
memcpy(key,arr,size); //取数组中第一个size长度的内容
int i=0,j=n-1;
while(i<j){
while(i<j && cmp(arr+j*size,key)>=0) --j; //j向前跑找小的数,找到停下来
if(i>=j) break;
while(i<j && cmp(arr+i*size,key)<=0) ++i; //i向后跑找大的数,找到停下来
if(i>=j) break;
Swap(arr+i*size,arr+j*size,size); //前后都停下来后,两部分内容交换
}
Swap(arr+i*size,arr,size); //碰头位置的元素和基准元素交换
return i;
}
//排好一个元素后,分前后两部分继续确定基准元素位置
void MyQuickSort(void* arr,int n,int size,cmp_t cmp){ // int (*cmp)(const void*,const void*)
if(n<=1) return;
int index = MyPartition(arr,n,size,cmp);
MyQuickSort(arr,index,size,cmp);
MyQuickSort(arr+(index+1)*size,n-(index+1),size,cmp);
}
int main(){
int n;
scanf("%d",&n);
int arr[n];
Random(arr,n);
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
MyQuickSort(arr,n,sizeof(int),cmp); //其他类型快速排序
PrintArr(arr,n);
printf("%d\n",Ordered(arr,n,true));
double test[] = {2.2,5.5,1.1,6.6,8.8};
MyQuickSort(test,5,sizeof(double),cmp_double); //其他类型快速排序
PrintArrDouble(test,5);
}
结果为:
30
0 3 13 8 15 17 12 23 0 21 20 5 16 4 24 29 25 8 20 16 23 28 5 2 5 10 3 10 12 0
0
0 0 0 2 3 3 4 5 5 5 8 8 10 10 12 12 13 15 16 16 17 20 20 21 23 23 24 25 28 29
1
1.100000 2.200000 5.500000 6.600000 8.800000
数组用做指针时,一定记得要加size
size和cmp都要用到,一定要记得返回