topK问题程序及实验报告

一、题目陈述

给定一个长度为 n 的整数数列,以及一个整数 k ,请你求出数列从小到大排序后的第k个数。

题目翻译一下就是要从给定的集合中选定第k小的元素。本题采用分治算法进行处理较为适宜。

二、代码说明

分治算法本质是将一个大问题分解成若干个小问题,随后分别解决这些小问题,最后将解集合起来得到整个问题的解。在这个前提下,代码参考了快速排序的分区思想,但并没有对整个数组进行排序。通过分区操作,问题的规模被逐渐缩小,最终找到数组中第k小的元素。下面对代码进行具体的介绍。

快速排序分区示意图

Swap函数的作用是交换两个整数的值,它接受两个指向整数的指针作为参数,然后交换这两个指针所指向的整数的值。

void swap(int* a, int* b) {

    int temp = *a;

    *a = *b;

    *b = temp;

}

Partition函数是快速排序算法中的关键步骤,其对数组进行分区,将小于基准值的数放在左边,大于基准值的数放在右边。其接受的参数中,’arr[]’是待分区的数组,’low’和’high’是分区范围。这个函数选择数组中最后一个元素作为基准值,通过遍历数组,将≤基准值的元素移到基准值的左边,>基准值的元素移到右边,最后将基准值放到合适的位置,它的返回值是基准值的索引位置。

int partition(int arr[], int low, int high) {

    int pivot = arr[high]; // 选择最后一个元素作为基准值

    int i = low - 1;

    for (int j = low; j < high; j++) {

        if (arr[j] <= pivot) {

            i++;

            swap(&arr[i], &arr[j]);

        }

    }

    swap(&arr[i + 1], &arr[high]);

    return i + 1;

}

FindKthSmallest函数用于找到数组中第k小的元素,包含了快速排序算法。接受的参数中,’arr[]’是待查找的数组,’low’和’high’是查找范围,‘k’是要找到的第k小的元素的位置。它通过调用’partition’函数将数组分区,然后根据基准值的索引位置与k的关系,递归地在左边或右边的子数组中继续查找,直到找到第k小的元素为止,最后返回第k小元素的值。

int findKthSmallest(int arr[], int low, int high, int k) {

    if (low <= high) {

        int pivot = partition(arr, low, high);

        if (pivot == k - 1) {

            return arr[pivot];

        } else if (pivot > k - 1) {

            return findKthSmallest(arr, low, pivot - 1, k);

        } else {

            return findKthSmallest(arr, pivot + 1, high, k);

        }

    }

    return -1; // 数组为空或k超出范围时返回-1

}

main函数中,首先读取数组大小’n’和要查找的第k小的元素的位置’k’,随后进行数组的输入,最后调用findKthSmallest函数完成计算。

int main() {

    int n, k;

    scanf("%d %d", &n, &k);

    int arr[n];

    for (int i = 0; i < n; i++) {

        scanf("%d", &arr[i]);

    }

   

    int kthSmallest = findKthSmallest(arr, 0, n - 1, k);

    printf("%d\n", kthSmallest);

    return 0;

}

三、算法分析

通常来说,面对一般性选择问题的算法步骤为先对数组进行排序,随后根据索引取出第k小的数。这样的作法时间复杂度最优时为O(nlogn),通常为O(n^2)。

下面进行一般性选择问题解法示例。

#include <stdio.h>

// 冒泡排序

void bubbleSort(int arr[], int n) {

    for (int i = 0; i < n - 1; i++) {

        for (int j = 0; j < n - i - 1; j++) {

            if (arr[j] > arr[j + 1]) {

                int temp = arr[j];

                arr[j] = arr[j + 1];

                arr[j + 1] = temp;

            }

        }

    }

}

int main() {

    int n, k;

    printf("输入数组S的长度: ");

    scanf("%d", &n);

    int arr[n];

    printf("输入数组S的元素: ");

    for (int i = 0; i < n; i++) {

        scanf("%d", &arr[i]);

    }

    printf("输入k的值: ");

    scanf("%d", &k);

    bubbleSort(arr, n);

    printf("第k小的元素为: %d\n", k, arr[k - 1]);

    return 0;

}

这段代码使用冒泡排序,并根据输入的k值输出第k小的元素。冒泡排序在最坏情况下需要进行n次遍历,每次遍历要比较n-i-1次相邻元素并进行交换,因此冒泡排序的时间复杂度为O(n^2)。随后的输出第k小元素部分时间复杂度为O(1),因此这段代码的时间复杂度为O(n^2)。

下面将实验别的排序方法对时间复杂度的影响。

先测试堆排序

#include <stdio.h>

// 堆排序核心函数

void heapify(int arr[], int n, int i) {

    int largest = i;  // 将当前节点初始化为最大值

    int left = 2 * i + 1;

    int right = 2 * i + 2;

    // 如果左子节点大于根节点

    if (left < n && arr[left] > arr[largest]) {

        largest = left;

    }

    // 如果右子节点大于根节点

    if (right < n && arr[right] > arr[largest]) {

        largest = right;

    }

    // 如果最大值不是根节点,交换根节点和最大值

    if (largest != i) {

        int temp = arr[i];

        arr[i] = arr[largest];

        arr[largest] = temp;

        // 递归调用堆化函数处理子树

        heapify(arr, n, largest);

    }

}

// 堆排序函数

void heapSort(int arr[], int n) {

    // 建立最大堆,从最后一个非叶子节点开始

    for (int i = n / 2 - 1; i >= 0; i--) {

        heapify(arr, n, i);

    }

    // 逐个取出堆顶元素并调整堆

    for (int i = n - 1; i > 0; i--) {

        // 将当前堆顶元素(最大值)与最后一个元素交换

        int temp = arr[0];

        arr[0] = arr[i];

        arr[i] = temp;

        // 调整堆,将剩余元素重新堆化

        heapify(arr, i, 0);

    }

}

int main() {

    int n, k;

    printf("输入数组S的长度: ");

    scanf("%d", &n);

    int arr[n];

    printf("输入数组S的元素: ");

    for (int i = 0; i < n; i++) {

        scanf("%d", &arr[i]);

    }

    printf("输入k的值: ");

    scanf("%d", &k);

    heapSort(arr, n);

    printf("第k小的元素为: %d\n", arr[k - 1]);

    return 0;

}

堆排序的时间复杂度为O(nlogn),与冒泡排序相比,在处理大型数据集时更占优势,不过在处理较少数据时,性能可能会低于冒泡排序。

下面测试快速排序

#include <stdio.h>

// 交换两个元素

void swap(int* a, int* b) {

    int temp = *a;

    *a = *b;

    *b = temp;

}

// 分区函数,将小于等于基准值的数放在左边,大于基准值的数放在右边

int partition(int arr[], int low, int high) {

    int pivot = arr[high]; // 选择最后一个元素作为基准值

    int i = low - 1;

    for (int j = low; j < high; j++) {

        if (arr[j] <= pivot) {

            i++;

            swap(&arr[i], &arr[j]);

        }

    }

    swap(&arr[i + 1], &arr[high]);

    return i + 1;

}

// 使用快速排序算法对数组进行排序

void quickSort(int arr[], int low, int high) {

    if (low < high) {

        int pivot = partition(arr, low, high);

        quickSort(arr, low, pivot - 1);

        quickSort(arr, pivot + 1, high);

    }

}

int main() {

    int n, k;

    printf("输入数组S的长度: ");

    scanf("%d", &n);

    int arr[n];

    printf("输入数组S的元素: ");

    for (int i = 0; i < n; i++) {

        scanf("%d", &arr[i]);

    }

    printf("输入k的值: ");

    scanf("%d", &k);

    quickSort(arr, 0, n - 1);

    printf("第k小的元素为: %d\n", arr[k - 1]);

    return 0;

}

快速排序算法与实验代码思想较为相似,均有分治的思想,不同之处在于前者先进行排序后直接取索引对应的值,后者则是通过一次次的分区进行不完全排序。

最后是本实验代码的运行时间。

通过几种方法的对比可以看出,冒泡排序在处理大量数据时速度显著减缓,堆排序和快速排序则受影响较小。然而,即便使用了高效率的排序算法,代码的运行速度仍然比不过使用分治算法的实验代码,分治算法节约了7~8倍的运行时间

四、实验总结

本次实验以分治算法为核心思想,实现了找出第k小元素的代码。随后,将分治算法与一般选择性算法进行对比,并分别比较了冒泡排序、堆排序、快速排序等排序方式,通过程序输出运行时间,具体准确地进行时间复杂度比较。

首先,冒泡排序在数据量增大时算法效率明显下降,而即使是效率较高的快速排序与堆排序,在处理相同较大数据量时效率仍不及分治选择算法,后者平均节约7~8倍运行时间,得出结论:分治算法在面对较大数据量时是具有时间复杂度上的优势的。

最后结论是:

对于处理大规模数据集的排序问题,应优先选择堆排序或快速排序等时间复杂度较低的算法,以减少运行时间;而对于topK问题等需要找到第k小或第k大元素的情况,分治算法是更好的选择。

五、实验代码

#include <stdio.h>

// 交换两个整数的值
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 分区函数,将小于等于基准值的数放在左边,大于基准值的数放在右边
int partition(int arr[], int low, int high) {
    int pivot = arr[high]; // 选择最后一个元素作为基准值
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    
    swap(&arr[i + 1], &arr[high]);
    return i + 1;
}

// 使用快速排序的思想找到第k小的数
int findKthSmallest(int arr[], int low, int high, int k) {
    if (low <= high) {
        int pivot = partition(arr, low, high);
        
        if (pivot == k - 1) {
            return arr[pivot];
        } else if (pivot > k - 1) {
            return findKthSmallest(arr, low, pivot - 1, k);
        } else {
            return findKthSmallest(arr, pivot + 1, high, k);
        }
    }
    
    return -1; // 数组为空或k超出范围时返回-1
}

int main() {
    int n, k;
    scanf("%d %d", &n, &k);
    
    int arr[n];
    for (int i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
    }
    
    int kthSmallest = findKthSmallest(arr, 0, n - 1, k);
    printf("%d\n", kthSmallest);
    
    return 0;
}

  • 8
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值