基础数据结构及算法:高级排序算法,包括归并排序、快速排序、希尔排序

1. 归并排序

归并排序排序思路:

  1. 拆:先把数组一分为二拆分,最终拆分成单个数字
  2. 合:再把前半部分有序数组和后半部分有序数组排序

在这里插入图片描述

#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. 快速排序

快速排序排序思路:

  1. 以第一个数为基准,先从后往前找,找到一个比它小的数,停下来
  2. 再从前往后找,找到一个比它大的数,停下来,把这两个数交换位置
  3. 再做以上两步操作,直到右边碰到左边,这时所在位置的数一定比基准数小
  4. 把碰头数和基准数交换,这时能保证基准数左边都比它小,右边都比它大
  5. 记住这个位置,定下这个位置,把数组一分为二,前后数组继续用上述方法排序
    在这里插入图片描述
#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. 希尔排序

希尔排序排序思路:

  1. 数组个数/2为步长,对每块儿步长的对应元素进行插入排序
  2. 对上述操作的步长再/2作为新步长,再对每块儿步长的对应元素进行插入排序
  3. 直到步长为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都要用到,一定要记得返回

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值