【基础算法】小白鼠排队问题

169 阅读5分钟

🌹作者:云小逸
📝个人主页:云小逸的主页
📝Github:云小逸的Github
🤟motto:要敢于一个人默默的面对自己,==强大自己才是核心==。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前,其次就是现在!学会自己和解,与过去和解,努力爱自己。==希望春天来之前,我们一起面朝大海,春暖花开!==🤟 👏专栏:C++👏 👏专栏:Java语言👏👏专栏:Linux学习👏
👏专栏:C语言初阶👏👏专栏:数据结构👏👏专栏:备战蓝桥杯👏

@TOC


前言

今天我们继续学习算法,加油。这篇文章写的是小白鼠排队问题。希望这篇可以有幸帮助到你,码字不易,请多多支持。 在这里插入图片描述

——————————————————————————————

题目描述

一群小白鼠站成一排,从左到右依次编号为1,2,…,n。它们的身高互不相同,且都是正整数。每只小白鼠都希望自己在这个队列中的相对位置,在未排序之前就已经确定了。

现在,你可以执行以下操作:

交换相邻两只小白鼠(仅适用于相邻的两只小白鼠)。

多次执行上述操作后,你需要把每只小白鼠的身高按从矮到高的顺序排列。

请帮助计算出,最少需要进行多少次操作,才能使所有小白鼠的身高按照要求有序排列。

输入格式: 第一行包含一个整数 n,表示小白鼠的数量。 第二行包含 n 个正整数,每个数字表示小白鼠的身高。

输出格式: 共一行,包含一个整数,表示最少交换次数。

数据范围: 1≤n≤10000, 1≤a[i]≤10000

输入样例: 5 5 3 4 2 1

输出样例: 7

题解思路

方法一:选择一种排序算法,统计其交换次数

  • 这道题首先需要找到规律,然后使用数学方法和排序方法来解决。
  • 规律:如果一个小白鼠相对位置确定,那么交换它和另一个小白鼠时,肯定至少有一只小白鼠需要交换两次位置,也就是说,这个操作至少可以让小白鼠的相对顺序改变1位。
  • 因此,我们可以先用选择排序等排序算法来将小白鼠按照需求的顺序排序。这样,每次交换相邻二者后都至少能够使得某只小白鼠的相对顺序发生一次改变,最终通过m次交换来使所有小白鼠顺利入队。则问题转化为求选择排序的交换次数。
  • 由于选择排序的时间复杂度是O(n^2),可能会超时,因此不可取,但我们可以统计选择排序中交换的次数(即每一次确定位置处进行的交换),然后输出结果。

方法二:归并排序

  • 归并排序的过程本质上就是排序+合并的过程,其中排序部分即比较和交换的过程,因此使用归并排序直接实现该题。从底层开始,每组仅有一个元素,两两合并
  • cnt[i]表示数字i的正确位置是第几位,例如输入样例中5的正确位置应该是第5位,则cnt[5]=5cnt[5]=5。则对于当前正在合并的两个有序数组A和B,如果在A中存在数字i(即cnt[i]<mid,其中 mid 表示 A 和 B 数组之间的分界点),而在B中存在数字 j(即cnt[j]>mid, j>i),那么就需要交换 i,j 这两只小白鼠原来的位置了。具体实现可以使用归并排序中的merge操作,在调用时统计一下交换次数即可。

C++代码

方法一:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010;
int n;
int a[N];
int pos[N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> a[i], pos[a[i]] = i;
    // 先将小白鼠按身高排序
    sort(a + 1, a + n + 1);
    int res = 0;
    for(int i = 1; i <= n; ++i)
        if(pos[a[i]] != i) // 只要当前数字不在正确位置上,就进行交换操作,并累加交换次数
        {
            swap(pos[a[i]], pos[a[pos[a[i]]]]);
            res++;
        }
    cout << res << endl;
    return 0;
}

复杂度分析:时间复杂度O(nlogn),空间复杂度O(N)

完整AC代码: AcWing 3425. 小白鼠排队

方法二:

#include <iostream>

using namespace std;

const int N = 1e4 + 10;
int n;
int a[N], tmp[N];
int cnt[N];

// 归并排序中的merge操作,顺便统计交换次数
int merge(int l, int mid, int r)
{
    int i = l, j = mid + 1, k = l, res = 0;

    while(i <= mid && j <= r)
        if(a[i] <= a[j]) tmp[k ++ ] = a[i ++ ];
        else {
            res += mid - i + 1; // 统计交换次数
            tmp[k ++ ] = a[j ++ ];
        }

    while(i <= mid) tmp[k ++ ] = a[i ++ ];
    while(j <= r) tmp[k ++ ] = a[j ++ ];

    for(int i = l; i <= r; ++i) a[i] = tmp[i];

    return res;
}

// 自顶向下地进行归并排序
int mergeSort(int l, int r)
{
    if(l >= r) return 0;
    int mid = (l + r) >> 1;
    int res = 0;
    res += mergeSort(l, mid);
    res += mergeSort(mid + 1, r);
    res += merge(l, mid, r);

    return res;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> a[i], cnt[a[i]] = i;
    int res = mergeSort(1, n);
    cout << res << endl;
    return 0;
}

复杂度分析:时间复杂度O(nlogn),空间复杂度O(N)

完整AC代码: AcWing 3425. 小白鼠排队

最后

十分感谢你可以耐着性子把它读完和我可以坚持写到这里,送几句话,对你,也对我:

1.把时间尺度拉长,拉长十年看当下

2.不说负面情绪,只描述事实;

3.越专注于过好自己,能量和幸运越会照顾你; 只解决问题,不做没有意义的担心,输了就认;

4.学会原谅自己,要允许自己做错事,允许自己出现情绪波动,我知道你已经很努力很努力在做好了

5.所有你害怕的、想逃避的事情,最终都要面对,既然这样不如选择坦然面对。即使结果不如人愿,没关系,至少这个过程是享受的,而不是一路带着恐惧和害怕。

最后如果觉得我写的还不错,请不要忘记==点赞==✌,==收藏==✌,加==关注==✌哦(。・ω・。)

愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚==菜鸟==逐渐成为==大佬==。加油,为自己点赞!