【上机训练】排序

按数的大小排

  1. 内定义数据类型

    题目描述:

    对输入的n个数进行排序并输出。

    输入

    输入的第一行包括一个整数n(1<=n<=100)。
    接下来的一行包括n个整数。

    输出

    可能有多组测试数据,对于每组数据,将排序后的n个整数输出,每个数后面都有一个空格。
    每组测试数据的结果占一行。

    样例输入

    4
    1 4 3 2

    样例输出

    1 2 3 4

    解法思路

    代码

#include <cstdio>
#include <iostream>

#include <algorithm>  	//使用sort函数时需要引入

using namespace std;

int arr[100];

int main(){
    
    
    int n;
    while(scanf("%d",&n)!=EOF){
    
    	//输入序列的元素个数 
        //遍历输入
        for(int i=0;i<n;++i){
    
    
            scanf("%d",&arr[i]);
        }

        //排序
        sort(arr,arr+n);    //数组的名字也代表它起始元素的地址; sort()有三个参数(first,last,comp)
                            //这里comp省略,默认升序

        //遍历输出
        for(int i=0;i<n;++i){
    
    
            printf("%d ",arr[i]);   //输出中间有空格:"%d "
        }
        printf("\n");
    }

    return 0;
}
  1. 自定义数据类型(成绩排序)

    题目描述:

    用一维数组存储学号和成绩,然后,按成绩排序输出。

    输入

    输入第一行包括一个整数N(1<=N<=100),代表学生的个数。
    接下来的N行每行包括两个整数p和q,分别代表每个学生的学号和成绩。

    输出

    按照学生的成绩从小到大进行排序,并将排序后的学生信息打印出来。
    如果学生的成绩相同,则按照学号的大小进行从小到大排序。

    样例输入

    3
    1 90
    2 87
    3 92

    样例输出

    2 87
    1 90
    3 92

    解法思路

    代码

#include <cstdio>
#include <iostream>

#include <algorithm>

using namespace std;

struct student{
    
    
    int number;
    int score;
};

student arr[100];

bool compare(student x,student y){
    
      //返回ture的话x在y的前面
    if(x.score==y.score){
    
    
        return x.number<y.number;   //同分时:按number升序排
    }else{
    
    
        return x.score<y.score;     //不同分时:按score升序排
    }
}

//默认的升序compare实现:
//bool compare(int a,int b){
    
    
//    return a>b;     //按默认的升序排序
//}

int main(){
    
    
    int n;
    scanf("%d",&n);              //输入元素个数
    
    //遍历输入一维数组         
    for(int i=0;i<n;++i){
    
    
        scanf("%d%d",&arr[i].number,&arr[i].score);
    }

    sort(arr,arr+n,compare);    //这里不能默认比较函数,需要按题自定义compare函数

    //遍历输出
    for(int i=0;i<n;++i){
    
    
        printf("%d %d\n",arr[i].number,arr[i].score);
    }
    return 0;
}

还可以使用重载操作符实现:

#include <cstdio>
#include <iostream>

#include <algorithm>

using namespace std;

//在结构体当中写明,每个student要怎比
struct student{
    
    
    int number;
    int score;
    
    //使用重载操作符的方式
    bool operator< (student stu) const {
    
        //“operator<” 相当于函数名,传入一个student类型的参数stu,并将函数设为常函数 
        
        if(score==stu.score){
    
                   //重载函数里的逻辑和之前的一样
            return number<stu.number;   
        }else{
    
    
            return score<stu.score;     
        }
    }
};

student arr[100];

int main(){
    
    
    int n;
    scanf("%d",&n);              //输入元素个数

    //遍历输入一维数组
    for(int i=0;i<n;++i){
    
    
        scanf("%d%d",&arr[i].number,&arr[i].score);
    }

    sort(arr,arr+n);    //在结构体里将“<”重新定义了,所以默认的<就是题目中的比较方法

    //遍历输出
    for(int i=0;i<n;++i){
    
    
        printf("%d %d\n",arr[i].number,arr[i].score);
    }
    return 0;
}

不是按数的大小排

  1. 整数奇偶排序

    题目描述:

    输入10个整数,彼此以空格分隔。重新排序以后输出(也按空格分隔),要求: 1.先输出其中的奇数,并按从大到小排列; 2.然后输出其中的偶数,并按从小到大排列。

    输入

    任意排序的10个整数(0~100),彼此以空格分隔。

    输出

    可能有多组测试数据,对于每组数据,按照要求排序后输出,由空格分隔。
    1. 测试数据可能有很多组,请使用while(cin>>a[0]>>a[1]>>…>>a[9])类似的做法来实现;
    2. 输入数据随机,有可能相等。

    样例输入

    4 7 3 13 11 12 0 47 34 98

    样例输出

    47 13 11 7 3 0 4 12 34 98

    解法思路

    代码

#include <cstdio>
#include <iostream>

#include <algorithm>

using namespace std;

int arr[10];

bool compare(int x,int y){
    
    
    if(x%2==1 && y%2==1){
    
               //x,y都是奇数时,按降序排
        return x>y;
    }else if(x%2==0 && y%2==0){
    
    
        return x<y;                 //x,y都是偶数时,按升序排
    }else if(x%2==1 && y%2==0){
    
    
        return true;            //x为奇数,y为偶数时,奇数x在前(需要返回ture)  还可以写成return x%2>y%2;
    }else{
    
    
        return false;           //x为偶数,y为奇数时,奇数y在前(需要返回false)  还可以写成return x%2>y%2;
    }
}

int main(){
    
    
    while(scanf("%d",&arr[0])!=EOF){
    
     //已经告诉了元素的个数时:先只输入1个
        for(int i=1;i<10;++i){
    
           //之后的9个元素,按循环输入
            scanf("%d",&arr[i]);
        }

        sort(arr,arr+10,compare); 

        for(int i=0;i<10;++i){
    
    
            printf("%d ",arr[i]);
        }
    }
    return 0;
}

利用排序算法的特性解题

  1. 线性排序

    题目描述:

    给你n个整数,请按从大到小的顺序输出其中前m大的数。

    输入

    每组测试数据有两行,第一行有两个数n,m (0<n,m<1000000) ,第二行包含n个各不相同,且都处于区间[-500000,500000]的整数(加了限制)

    输出

    对每组测试数据按从大到小的顺序输出前m大的数。

    样例输入

    5 3
    3 -35 92 213 -644

    样例输出

    213 92 3

    解法思路

    已知基于比较的排序算法的时间复杂度的下限为O(nlogn)
    那么如何控制排序算法在O(n)内完成排序?
    如何不通过比较大小的方法将排序突破O(n):必须加一个限制——给定范围
    如:5 3 2 4 3 3 2 7 7 1(所有元素都在0~9)→这样可以保证辅助数组的元素个数
    ⭐构造一个辅助数组:记录每个记录在序列中的出现次数
    之后再线性地扫描一遍辅助数组,即可得到排序

    代码

#include <cstdio>
#include <iostream>
#include <cstring>  //使用了memset
#include <algorithm>

using namespace std;

const int MAXN=1e6 +10;
const int RANGE=5e5;

int arr[MAXN];       //构造存放原序列的数组
int number[MAXN];    //根据每个元素取值范围,构造辅助数组

int main(){
    
    
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF){
    
    

        //清空辅助数组,不然上次排序的数据残余会对下次的辅助数组统计有影响
        memset(number,0,sizeof(number)); //memset(首个元素地址,全都置为0,数组大小)

        for(int i=0;i<n;++i){
    
    
            scanf("%d",&arr[i]);
            number[arr[i]+RANGE]++;     //每输入一个,辅助数组中对应的加1
                                        //且由于输入的arr[i]的范围是(-5e5,5e5),但辅助数组构造的范围(0,1e6)
                                        //所以将输进来的arr[i]+5e5(整体向右平移5e5)
        }

        //遍历辅助数组
        int index=0;
        for(int i=0;i<MAXN;++i){
    
    
            while(number[i]--){
    
             //当number[i]中的数字不为0时,
                arr[index++] = i-RANGE; //将排好的序列输出回原来的数组
            }
        }
        
        //序列是升序排的,所以从后向前输出m个
        for(int i=n-1;i>=n-m;--i){
    
    
            if(i==n-m){
    
    
                printf("%d\n",arr[i]);  //最后一个输出后加换行
            }else{
    
    
                printf("%d ",arr[i]);
            }
        }
    }
    return 0;
}
  1. 求逆序数对

    题目描述:

    输入

    输出

    4
    4 2 8 0 3
    10 0 1 2 3 4 5 6 7 8 9
    6 -42 23 6 28 -100 65537
    5 0 0 0 0 0

    样例输入

    4
    4 2 8 0 3
    10 0 1 2 3 4 5 6 7 8 9
    6 -42 23 6 28 -100 65537
    5 0 0 0 0 0

    样例输出

    Scenario #1:
    3
    Scenario #2:
    0
    Scenario #3:
    5
    Scenario #4:
    0

    解法思路

    原序列:2 8 0 4 3
    逆序对:(8,0)(8,4)(8,3)(4,3)
    逆序数:4
    使用归并排序O(nlogn)处理逆序数问题很高效
    如何统计逆序数:
    在combine函数中,右边序列中的元素在原序列中一定是在左边序列的后边
    当将 工作指针 j 指向的元素放到temp中时,此时在左序列工作指针 i 右边的所有数一定比工作指针 j 指向的元素大,这些元素就与工作指针 j 指向的元素构成逆序对
    故在combine函数中添加:
    ⭐number += middle-i+1; 就统计出了逆序对数

    代码

//归并排序算法
#include <cstdio>
#include <iostream>

#include <algorithm>

using namespace std;

int arr[100];
int temp[100];

void Combine(int left,int middle,int right){
    
        //归并函数
    int i = left;
    int j = middle +1;
    int k = left;

    while(i<=middle && j<=right){
    
       //i,j分别是左右两个的工作指针
        if(arr[i] <= arr[j]){
    
           //将两者小的放到temp辅助数组中
            temp[k++] = arr[i++];
        }else{
    
    
            temp[k++] = arr[j++];
        }
    }

    while(i<=middle){
    
                   //将两个序列中剩下的全部输入temp
        temp[k++] = arr[i++];
    }
    while(j<=right){
    
    
        temp[k++] = arr[j++];
    }

    for(k=left;k<=right;++k){
    
           //将排好的序列复制到原来的数组arr中
        arr[k]=temp[k];
    }
    return;
}

void MergeSort(int left,int right){
    
             //left,right待排序区间
    if(left<right){
    
                             //只要left<right就递归归并
        //int middle = (left+right)/2;
        int middle = left + (right-left)/2;    //上面的写法如果left+right可能会大于int的上界,从而溢出

        MergeSort(left,middle);
        MergeSort(middle+1,right);

        Combine(left,middle,right);
    }
    return;
}

int main(){
    
    
    int n;
    scanf("%d",&n);             //输入n
    for(int i=0;i<n;++i){
    
           //输入所有待排元素
        scanf("%d",&arr[i]);
    }

    MergeSort(0,n-1);           //调用归并函数

    for(int i=0;i<n;++i){
    
           //将排好的序列全部输出
        printf("%d ",arr[i]);
    }
    printf("\n");
    return 0;
}
//本题代码
#include <cstdio>
#include <iostream>

#include <algorithm>

using namespace std;

const int MAXN=1010;

int arr[MAXN];
int temp[MAXN];
int number;   //统计逆序数

void Combine(int left,int middle,int right){
    
        //归并函数
    int i = left;
    int j = middle +1;
    int k = left;

    while(i<=middle && j<=right){
    
       //i,j分别是左右两个的工作指针
        if(arr[i] <= arr[j]){
    
           //将两者小的放到temp辅助数组中
            temp[k++] = arr[i++];
        }else{
    
    
            number += middle-i+1;  //⭐
            temp[k++] = arr[j++];
        }
    }

    while(i<=middle){
    
                   //将两个序列中剩下的全部输入temp
        temp[k++] = arr[i++];
    }
    while(j<=right){
    
    
        temp[k++] = arr[j++];
    }

    for(k=left;k<=right;++k){
    
           //将排好的序列复制到原来的数组arr中
        arr[k]=temp[k];
    }
    return;
}

void MergeSort(int left,int right){
    
             //left,right待排序区间
    if(left<right){
    
                             //只要left<right就递归归并
        //int middle = (left+right)/2;
        int middle = left + (right-left)/2;    //上面的写法如果left+right可能会大于int的上界,从而溢出

        MergeSort(left,middle);
        MergeSort(middle+1,right);

        Combine(left,middle,right);
    }
    return;
}

int main(){
    
    
    int casenum;
    scanf("%d",&casenum);
    for(int current=1;current<=casenum;++current){
    
     //本题要求输出对应的哪一组,下面需要用到每一个current
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;++i){
    
           //输入所有待排元素
            scanf("%d",&arr[i]);
        }
        number=0;
        MergeSort(0,n-1);           //调用归并函数
        printf("Scenario #%d:\n",current);
        printf("%d\n\n",number);
    }
    return 0;
}
  1. 第K大的数

    题目描述:

    输入

    输出

    样例输入

    样例输出

    解法思路

    利用快排

    1. 当进行一次划分后,可以确定其中一个元素(枢纽) 的最终位置,并可以得到他的下标,如果此时要求第k大的数,刚好left=k-1,则这个枢纽就是要找的数
    2. 但是并不是每次都能刚好找到,但是划分好的数列,枢纽左边的数一定比枢纽小,右边的一定比枢纽大
    3. 所以我们要找的第k大的数,当k-1<left时,第k大一定不可能出现在left右边,就去left 的左边去找,直到找到刚好left=k-1。

    代码

//快速排序
#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

int arr[100];

int Partition(int left,int right){
    
             //一趟划分
    int random=left+rand()%(right-left);    //找一个随机值作为枢纽
    swap(arr[left],arr[random]);            //将枢纽放到第一个元素,此时left为枢纽

    while(left<right){
    
             //一直交换直到left=right
        while(left<right&&arr[left]<=arr[right]){
    
       //此时left指向枢纽,当枢纽比right的数小时
            --right;                                //让right左移
        }
        swap(arr[left],arr[right]);                 //直到找到一个right的值小于left(枢纽)的值,则交换

        while(left<right&&arr[left]<=arr[right]){
    
       //交换后,此时right指向枢纽,当枢纽比left的数大时
            left++;                                 //让left右移                       
        }
        swap(arr[left],arr[right]);                 //直到找到一个left的值大于right(枢纽)的值,则交换
    }
    return left;                                    //当left=right时停止划分,返回枢纽的下标
}
void Quicksort(int left,int right){
    
    
    if(left<right){
    
    
        int position=Partition(left,right);         //一次划分
        Quicksort(left,position-1);                 //对划分好的左右,分别快速排序
        Quicksort(position+1,right);
    }

}

int main(){
    
    
    int n;
    scanf("%d",&n);         //两天就把%记成&了?
    for(int i=0;i<n;++i){
    
    
        scanf("%d",&arr[i]);
    }

    Quicksort(0,n-1);

    for(int i=0;i<n;++i){
    
    
        printf("%d",arr[i]);
    }
    return 0;
}

求第k大的数(时间复杂度为O(n),快排)

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

int arr[100];

int Partition(int left,int right){
    
    
    int random=left+rand()%(right-left);
    swap(arr[left],arr[random]);

    while(left<right){
    
             //一直交换直到left=right
        while(left<right&&arr[left]>=arr[right]){
    
       //更改递增递减,改此处的<=,>=即可
            --right;
        }
        swap(arr[left],arr[right]);

        while(left<right&&arr[left]>=arr[right]){
    
    
            left++;
        }
        swap(arr[left],arr[right]);
    }
    return left;
}
int Quicksort(int left,int right,int k){
    
    
    if(left<right){
    
    
        int position=Partition(left,right); //position为划分返回的枢纽下标
        if(position==k-1){
    
                      //⭐返回的下标刚好等于所求的k-1
            return arr[position];           //则直接返回枢纽值
        }else if(position<k-1){
    
                 //当position<k-1时,第k大只能出现在枢纽的右边
            Quicksort(position+1,right,k);
        }else {
    
                                 //当position>k-1时,第k大只能出现在枢纽的左边
            Quicksort(left,position-1,k);
        }
    }

}

int main(){
    
    
    int n;
    scanf("%d",&n);         //两天就把%记成&了?
    for(int i=0;i<n;++i){
    
    
        scanf("%d",&arr[i]);
    }

    int k;                  //输入k
    scanf("%d",&k);

    printf("%d",Quicksort(0,n-1,k));

//    for(int i=0;i<n;++i){
    
    
//        printf("%d",arr[i]);
//    }
    return 0;
}

时间复杂度较大的方法

#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

int arr[100];

int main(){
    
    
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i){
    
    
        scanf("%d",&arr[i]);
    }
    int k;
    scanf("%d",&k);

    sort(arr,arr+n);

    printf("%d",arr[k-1]);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/Qmilumilu/article/details/114646566
今日推荐