【最小堆+堆排序】数据结构实验之排序四:寻找大富翁

本文介绍了一种利用最小堆及堆排序算法快速找出资产排名前M位的大富翁的方法。通过C语言实现,详细展示了如何构建最小堆并进行调整以确保每次都能找到最大的资产值。

Think:
1知识点:最小堆+堆排序
(1)最小堆定义:H(id) <= H(id<<1) && H(id) <= H(id<<1|1)

SDUTOJ题目链接

数据结构实验之排序四:寻找大富翁
Time Limit: 200MS Memory Limit: 512KB

Problem Description
2015胡润全球财富榜调查显示,个人资产在1000万以上的高净值人群达到200万人,假设给出N个人的个人资产值,请你快速找出排前M位的大富翁。

Input
首先输入两个正整数N( N ≤ 10^6)和M(M ≤ 10),其中N为总人数,M为需要找出的大富翁数目,接下来给出N个人的个人资产,以万元为单位,个人资产数字为正整数,数字间以空格分隔。

Output
一行数据,按降序输出资产排前M位的大富翁的个人资产值,数字间以空格分隔,行末不得有多余空格。

Example Input
6 3
12 6 56 23 188 60

Example Output
188 60 56

Hint
请用堆排序完成。

Author
xam

以下为Accepted代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1004014;

int rec[N];

/*小顶堆*/
void heap_adjust(int H[], int s, int num);/*堆的调整操作*/
void heap_min(int H[], int num);/*建立最小堆*/
void heap_sort(int H[], int num);/*堆排序*/

int main(){
    int n, m, i, x;
    while(~scanf("%d %d", &n, &m)){
        m = min(m, n);
        for(i = 1; i <= m; i++)
            scanf("%d", &rec[i]);
        heap_min(rec, m);/*初始建立一个小顶堆*/
        for(i = m+1; i <= n; i++){
            scanf("%d", &x);
            if(x > rec[1]){/*判断x是否大于当前堆中最小的元素,若大于,则更新堆*/
                rec[1] = x;
                heap_adjust(rec, 1, m);
            }
        }
        heap_sort(rec, m);/*小顶堆堆排序之后元素顺序为从大到小*/
        for(i = 1; i <= m; i++)
            printf("%d%c", rec[i], i == m? '\n': ' ');
    }
    return 0;
}
void heap_adjust(int H[], int s, int num){
    int t = H[s];/*记录初始开始更新的根节点*/
    int j = s<<1;
    for(; j <= num; j = s<<1){
        if(j < num && H[j] > H[j+1]) j++;/*寻找左右儿子中最小的儿子*/
        if(t < H[j]) break;/*若t小于当前s节点的左右儿子,则说明t可以放在s节点位置,结束调整过程*/
        H[s] = H[j];/*将左右儿子中最小的节点提上去*/
        s = j;/*将左右儿子中最小的节点作为新的查询位置*/
    }
    H[s] = t;/*将t放在符合要求的s节点位置*/
    return;
}
void heap_min(int H[], int num){
    for(int i = num/2; i > 0; i--){/*num/2节点是最后一个非叶子节点*/
        heap_adjust(H, i, num);/*从底向上(可以保证到达当前节点为止左右子树已经符合小顶堆的特点)进行小顶堆的调整*/
    }
    return;
}
void heap_sort(int H[], int num){
    for(int i = num; i > 1; i--){
        swap(H[1], H[i]);/*将最小的节点与最后一个节点交换,然后节点数-1,然后再从最后一个节点开始调整使其成为一个小顶堆*/
        heap_adjust(H, 1, i-1);
    }
    return;
}
<think>好的,我需要解决用户关于PTA平台“寻找大富翁”问题的Java实现或解题思路。首先,我得理解题目要求。题目要求从N个人出资产前M位的富翁,其中N可能很大(到10^6),而M最多是10。这种情况下,效率很重要。 根据提供的引用,特别是引用[4]和[5],提到当M较小的时候,可以采用维护一个大小为M的最小,或者先填满M个元素,之后每个新元素如果比顶大就替换,然后调整。这样可以保证时间复杂度是O(N log M),比完全排序的O(N log N)更高效,特别是当M远小于N时。 不过用户提到的是Java实现,所以需要考虑Java中的数据结构。Java的PriorityQueue默认是最小,但可以通过比较器改为最大。但在这个问题中,可能需要维护一个最小,以便快速替换最小的元素。例如,保持的大小为M,当新元素比顶大时,替换顶并调整。 另外,引用[3]和[5]指出输入可能很大,N到10^6,所以在读取输入时要注意效率,不能用Scanner,而应该用BufferedReader来加快读取速度。特别是处理大量数据时,输入输出效率很关键。 解题步骤大致如下: 1. 读取N和M。 2. 处理特殊情况,比如当M为0或者N为0的情况。 3. 如果N <= M,直接排序输出即可。 4. 否则,使用一个最小,初始填充前M个元素。 5. 遍历剩余的元素,每个元素与顶比较,如果更大就替换顶,并调整。 6. 最后将中的元素排序(降序)输出。 在Java中,可以用PriorityQueue实现最小。不过要注意,默认的PriorityQueue是最小,所以比较器需要正确设置。或者,可以自定义比较器让小的元素在顶,这样每次替换的时候,顶是最小的,方便比较。 另外,处理输入的时候需要注意,由于输入可能有多个空格分隔,需要用split方法分割,并且处理可能的多个空格。或者使用流式处理,逐个读取数字。 还有,题目中提到输出是前M个富翁的资产,按非递增顺序排列。所以最后需要将中的元素取出并逆序排序。 需要注意的是,当N小于M时,应该输出所有N个元素的降序排列,而不是补零或者其他处理。 可能的问题点包括输入处理效率、的正确维护,以及最后的排序步骤。此外,Java的PriorityQueue的默认行为是否符合需求,是否需要自定义比较器。 例如,创建PriorityQueue时,使用自然顺序的话是最小顶是最小元素。这正是需要的,因为当新元素比顶大时,替换掉顶,然后重新调整,保持的大小为M。这样遍历完所有元素后,中保存的就是最大的M个元素。 最后,将中的元素取出,由于是最小,需要逆序排序后输出。可以用一个数组来保存结果,然后反转数组,或者使用Collections.reverse方法。 现在需要考虑代码的结构: - 读取输入,使用BufferedReader。 - 处理输入中的N和M,注意可能的错误情况。 - 读取资产数组,可能很大,所以用长整型。 - 根据N和M的大小选择处理方式。 - 使用优先队列维护前M大的元素。 - 最后处理输出。 例如,代码的大致结构可能是: import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int N = Integer.parseInt(firstLine[0]); int M = Integer.parseInt(firstLine[1]); if (M <= 0 || N <= 0) { System.out.println(); return; } long[] assets = new long[N]; String[] parts = br.readLine().split("\\s+"); for (int i = 0; i < N; i++) { assets[i] = Long.parseLong(parts[i]); } // 处理逻辑 if (M > N) M = N; PriorityQueue<Long> minHeap = new PriorityQueue<>(); for (int i = 0; i < M; i++) { minHeap.offer(assets[i]); } for (int i = M; i < N; i++) { if (assets[i] > minHeap.peek()) { minHeap.poll(); minHeap.offer(assets[i]); } } List<Long> result = new ArrayList<>(minHeap); Collections.sort(result, Collections.reverseOrder()); // 输出 StringBuilder sb = new StringBuilder(); for (int i = 0; i < result.size(); i++) { sb.append(result.get(i)); if (i != result.size() - 1) { sb.append(" "); } } System.out.println(sb.toString()); } } 但需要注意,当N <= M时的情况,此时应该对整个数组排序,取降序的前N个元素。例如: 如果N <= M,则直接对数组进行降序排序,输出前N个。 例如: if (N <= M) { M = N; Arrays.sort(assets); // 然后逆序 // 转换为列表然后反转 List<Long> list = new ArrayList<>(); for (long num : assets) { list.add(num); } Collections.sort(list, Collections.reverseOrder()); // 然后输出 } else { // 使用处理 } 或者更高效的是,直接排序数组然后取前M个元素逆序。 不过对于N非常大的情况,比如1e6,Arrays.sort可能会使用快速排序,时间复杂度是O(N log N),而如果M很小的话,方法更高效。但根据题目中的条件,当N <= M时,直接全排序即可。而题目中的M<=10,所以在一般情况下,当N > M时,用方法更高效。 另外,在输入处理中,split("\\s+")可以处理多个空格的情况,这样分割后的数组不会有空字符串。 但需要注意,当输入行可能非常长时(例如1e6个数字),split方法可能效率不高,或者导致内存问题。此时,可以考虑使用StreamTokenizer或者逐个读取的方式。但Java中处理这种情况可能比较麻烦,而题目中的输入可能允许用split,因为PTA的测试用例可能不会让行超过内存限制。不过对于N=1e6,用split会导致生成一个长度为1e6的字符串数组,这在Java中可能会占用较多内存,但通常PTA的题目会考虑到这一点,允许通过。 综上,正确的Java实现应该采用优先队列维护前M个最大的元素,最后进行逆序输出。同时处理输入输出的效率问题。</think>### PTA 寻找大富翁 Java 实现思路 #### 核心思路 1. **输入处理**:使用 `BufferedReader` 高效读取大规模数据,注意处理空格分隔的资产值。 2. **维护**:利用最小(`PriorityQueue`)动态维护前 $M$ 大元素: - 大小固定为 $M$,新元素若大于顶则替换并调整。 3. **时间复杂度优化**:时间复杂度为 $O(N \log M)$,优于完全排序的 $O(N \log N)$[^4]。 #### 代码实现 ```java import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int N = Integer.parseInt(firstLine[0]); int M = Integer.parseInt(firstLine[1]); if (M <= 0 || N <= 0) { System.out.println(); return; } long[] assets = Arrays.stream(br.readLine().split("\\s+")) .mapToLong(Long::parseLong) .toArray(); M = Math.min(M, N); // 修正M值 if (N <= M) { Arrays.sort(assets); reverseArray(assets); printResult(assets, M); return; } // 最小维护前M大元素 PriorityQueue<Long> minHeap = new PriorityQueue<>(); for (int i = 0; i < M; i++) { minHeap.offer(assets[i]); } for (int i = M; i < N; i++) { if (assets[i] > minHeap.peek()) { minHeap.poll(); minHeap.offer(assets[i]); } } // 转换为数组并降序输出 Long[] result = minHeap.toArray(new Long[0]); Arrays.sort(result, Collections.reverseOrder()); printResult(result, M); } private static void reverseArray(long[] arr) { int left = 0, right = arr.length - 1; while (left < right) { long temp = arr[left]; arr[left] = arr[right]; arr[right] = temp; left++; right--; } } private static void printResult(Long[] arr, int M) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < M; i++) { sb.append(arr[i]); if (i != M - 1) sb.append(" "); } System.out.println(sb); } private static void printResult(long[] arr, int M) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < M; i++) { sb.append(arr[i]); if (i != M - 1) sb.append(" "); } System.out.println(sb); } } ``` #### 关键步骤说明 1. **输入修正**:若 $M > N$,修正 $M = N$ 避免越界。 2. **操作**:通过最小筛选出最大的 $M$ 个元素,顶始终为当前最小值。 3. **结果输出**:对中元素降序排序后输出,满足题目要求的非递增顺序[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值