【洛谷 | 算法1-2】排序 重点题解记录

P1177快速排序

题目描述

利用快速排序算法将读入的 NN 个数从小到大排序后输出。

快速排序是信息学竞赛的必备算法之一。对于快速排序不是很了解的同学可以自行上网查询相关资料,掌握后独立完成。(C++ 选手请不要试图使用 STL,虽然你可以使用 sort 一遍过,但是你并没有掌握快速排序算法的精髓。)

输入格式

第 1 行为一个正整数 N,第 2 行包含 N 个空格隔开的正整数 ai,为你需要进行排序的数,数据保证了 Ai 不超过 109

输出格式

将给定的 N个数从小到大输出,数之间空格隔开,行末换行且无空格

输入输出样例

输入#1输出#1
5
4 2 4 5 1
1 2 4 4 5

说明/提示

对于 20% 的数据,有 N≤103

对于 100% 的数据,有 N≤105

题解

分析

题目已经很明确地指出了是快速排序,所以直接使用快速排序算法即可。快速排序是C++中非常重要的排序算法,需要熟练掌握。本题中需要注意的有几点:①一般我们使用的快排算法都是将数组中的第一个元素当做划分数来划分,此时当输入数据本身基本有序时,为最坏情况,此时快排的时间复杂度退化为O(n2),导致算法超时。如果利用随机数来选取划分数可以优化此算法;②当排序数组中存在大量的与划分数相同的元素时,就会导致每次划分后两边元素数量差别巨大,导致算法时间增加从而超时。可以将这些元素均匀分到划分数两边,从而平衡两边的元素数量,从而优化算法,节省时间。

思路

快速排序的思路就是先选取数组中的一个数作为划分数pivot,然后剩下数中小于等于pivot的数放在pivot的左边,大于等于pivot的数放在pivot的右边。然后递推划分pivot左边的数组和pivot右边的数组,以此类推,最后即可得到排序好的数组。在随机选择划分数时,利用以时间作随机数种子来实现真随机数,并将该随机数作为索引来选取划分数。选中划分数后,将其与数组首元素交换位置,其后则与首元素作划分数的快速排序算法过程相同。在判断大量与划分数相同的元素的归属时,可以将这些元素一次记为大于一次记为小于,从而均匀的分至两边。

实现

#include <iostream>
#include <vector>
#include <stdlib.h>
#include <time.h>
using namespace std;

//划分
int getStandard(vector<int> &a, int s, int e)
{
    //引入随机化
    srand((unsigned)time(NULL));
    int rd = (rand() % (e - s + 1)) + s;
    int key = a[rd];
    swap(a[s], a[rd]); //将随机到的元素与首元素互换,即在快排中引入了随机化
    int pri = s;
    int flag = 0;
    while (s < e)
    {
        //优化判断条件,避免最坏情况超时
        while (s < e && a[e] >= key)
        {
            if (a[e] == key)
            {
                flag = 1 - flag;
                if (!flag)
                {
                    break;
                }
            }
            e--;
        }
        while (s < e && a[s] <= key)
        {
            if (a[e] == key)
            {
                flag = 1 - flag;
                if (!flag)
                {
                    break;
                }
            }
            s++;
        }
        swap(a[s], a[e]);
    }
    swap(a[pri], a[s]);
    return s;
}

void quickSort(vector<int> &a, int s, int e)
{
    if (s < e)
    {
        int st = getStandard(a, s, e);
        quickSort(a, st + 1, e);
        quickSort(a, s, st - 1);
    }
}

int main()
{
    int n;
    cin >> n;
    vector<int> a;
    for (int i = 0; i < n; i++)
    {
        int x;
        cin >> x;
        a.push_back(x);
    }
    quickSort(a, 0, n - 1);
    for (int i = 0; i < n; i++)
    {
        cout << a[i] << ' ';
    }
}

输入/输出

PS F:\VSC\exercise> cd "f:\VSC\exercise\" ; if ($?) { g++ main.cpp -o main } ; if ($?) { .\main }
6
1 2 2 4 5 1
1 1 2 2 4 5

P1923求第k小的数

题目描述

输入 n(n<5000000且 n为奇数) 个数字 ai(0<ai<109) ,输出这些数字的第 k 小的数。最小的数是第 0 小。

请尽量不要使用 nth_element 来写本题,因为本题的重点在于练习分治算法。

输入格式

输出格式

输入/输出样例

输入#1输出#1
5 1
4 3 2 1 5
2

题解

分析

想要找到第k小的数,最简单的方法是将从小到大数组排序,然后选取第k个元素即可。但是在本题中,由于n的范围是n<5000000,所以如果对数组进行完整的排序,尽管是用快排,也有很大可能超时。所以不能简单地对数组进行排序。解决方法是利用快排中的划分函数,对数组进行划分,每次划分都判断我们目标数是在划分数左边还是右边,这样每次平均下来划分都能舍去数组中的一半元素,优化了算法的时间复杂度。本题还需要注意的是,因为需要输入大量元素,所以通常使用的cinscanf()耗时太长,需要使用快读来读取输入数据

思路

划分思路同快排中的划分算法,不同的是在递推时只需要判断一下划分数和目标数的大小关系,然后根据判断结果选择某一边的数组进行再划分即可。使用cinscanf()都太慢会超时的情况下,需要使用更快的getchar()函数来读取输入数据,但是getchar()一次只能读取一个字符,单纯使用getchar()不能代替cinscanf()获取想要的输入,所以需要用getchar()自定义快读算法,读取想要的数据类型。

实现

#include <iostream>
#include <vector>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <string>
using namespace std;

//划分
int getStandard(vector<int> &a, int s, int e)
{
    //引入随机化
    srand((unsigned)time(NULL));
    int rd = (rand() % (e - s + 1)) + s;
    int key = a[rd];
    swap(a[s], a[rd]); //将随机到的元素与首元素互换,即在快排中引入了随机化
    int pri = s;
    cout << "rd:" << rd << " key:" << key << endl;
    int flag = 0;
    while (s < e)
    {
        //优化遇到大量与基元素重复的最坏情况,避免最坏情况超时
        while (s < e && a[e] >= key)
        {
            if (a[e] == key)
            {
                flag = 1 - flag;
                if (!flag)
                {
                    break;
                }
            }
            e--;
        }
        while (s < e && a[s] <= key)
        {
            if (a[e] == key)
            {
                flag = 1 - flag;
                if (!flag)
                {
                    break;
                }
            }
            s++;
        }
        cout << "s:" << s << " e:" << e << endl;
        cout << "before:" << a[s] << ' ' << a[e] << endl;
        swap(a[s], a[e]);
        cout << "after:" << a[s] << ' ' << a[e] << endl;
        for (int i = 0; i < a.size(); i++)
        {
            cout << a[i] << ' ';
        }
        cout << endl;
    }
    swap(a[pri], a[s]);
    for (int i = 0; i < a.size(); i++)
    {
        cout << a[i] << ' ';
    }
    cout << endl;
    return s;
}

int kmin(vector<int> &a, int k, int s, int e)
{
    int rg = e - s + 1;
    int rt;
    if (rg == 1)
    {
        rt = a[s];
    }
    else
    {
        int mid = getStandard(a, s, e);
        if (mid == k)
        {
            rt = a[mid];
        }
        else if (mid > k)
        {
            rt = kmin(a, k, s, mid - 1);
        }
        else if (mid < k)
        {
            rt = kmin(a, k, mid + 1, e);
        }
    }
    return rt;
}

//快读 并使用内联函数加快读取速度
inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
    {
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    return s * w;
}

int main()
{
    vector<int> a;
    int n, k;
    cin >> n >> k;
    for (int i = 0; i < n; i++)
    {
        int x = read();
        a.push_back(x);
    }
    int ans = kmin(a, k, 0, n - 1);
    cout << ans;

    return 0;
}

输入/输出

PS F:\VSC\exercise> cd "f:\VSC\exercise\" ; if ($?) { g++ main.cpp -o main } ; if ($?) { .\main }
5 2
4 3 2 1 5
3

P1012拼数

题目描述

设有 n 个正整数 a1…an,将它们联接成一排,相邻数字首尾相接,组成一个最大的整数。

输入格式

第一行有一个整数,表示数字个数 n。

第二行有 n 个整数,表示给出的 n 个整数 ai

输出格式

一个正整数,表示最大的整数

输入/输出样例

输入#1输出#1
3
13 312 343
34331213
输入#2输出#2
4
7 13 4 246
7424613

说明/提示

对于全部的测试点,保证 1<n<20,1<ai<109

题解

分析

需要组成最大的整数,就需要有保证大数字尽量在前,小数字尽量在后进行组合。由于每个整数位数不一定相同,存在多于一位整数的情况,所以组合的是若干位的整数,组合方式就是通过判断数组中任意两个字符串前后组合后的整数大小,来确定这两个字符串的相对顺序,全部排序后拼接即为所求。

思路

利用冒泡排序算法对数组进行排序,自定义比较函数,规则为当字符串a[j+1]+a[j]组成的整数大于字符串a[j]+a[j+1]组成的整数时,需要交换a[j]和a[j+1],否则不交换。

实现

#include <iostream>
#include <vector>
#include <string>
#include <math.h>
#include <iomanip>
using namespace std;

//判断两个字符串的“大小”,即组成一个数后的前后位置
//返回1则b+a组成的数较大,需要交换;返回0则a+b组成的数较大,不需要交换
int cmp(string a, string b)
{
    //stoi()函数将string字符串转换成数字
    int v1 = stoi(a+b);
    int v2 = stoi(b+a);
    if (v1 < v2)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

//冒泡排序
void sorta(vector<string> &a)
{
    int sz = a.size();
    for (int i = 0; i < sz; i++)
    {
        for (int j = 0; j < sz-i-1; j++)
        {
            if (cmp(a[j],a[j+1]))
            {
                swap(a[j],a[j+1]);
            }
        }
    }
}

int main()
{
    int n;
    cin >> n;
    vector<string> a;
    for (int i = 0; i < n; i++)
    {
        string x;
        cin >> x;
        a.push_back(x);
    }
    sorta(a);
    for (int i = 0; i < n; i++)
    {
        cout<<a[i];
    }   

    return 0;
}

输入/输出

PS F:\VSC\exercise> cd "f:\VSC\exercise\" ; if ($?) { g++ main.cpp -o main } ; if ($?) { .\main }
4
432 43 3 12
43343212
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值