【问题标题】:Recursively obtaining kth smallest element in an array递归获取数组中的第k个最小元素
【发布时间】:2014-12-07 15:47:27
【问题描述】:

我正在尝试从我的教科书中提供的伪代码中“翻译”这个算法。我的程序一直在崩溃,我不确定我的实现哪里出了问题。这是图像中的伪代码,下面是我的代码:

int kSmallFirst (int k, int anArray[], int first, int last) {
    int pivotIndex = 0;

    if (k < pivotIndex - first + 1)
        return kSmallFirst(k, anArray, first, pivotIndex - 1);
    else if (k == pivotIndex - first + 1)
        return pivotIndex;
    else
        return kSmallFirst(k - (pivotIndex - first + 1), anArray, pivotIndex + 1, last);
}

int main () {
    int i = 0;
    int arr[512];
    fstream data;
    data.open("data.txt");

    while (!data.eof()) {
        data >> arr[i];
        i++;
    }

    data.close();

    cout << kSmallFirst(42, arr, 0, i-1);

    return 0;
}

非常感谢!

【问题讨论】:

  • 这里有一个想法:将排序和输入分开并分别进行测试。这可能会为您提供一些有用的信息。
  • 首先,有一个叫做调试器的新发明,在请其他人为您调试代码之前,您可能应该尝试一下。其次,如果您确实要求其他人这样做,您至少可以提供有关崩溃的一些详细信息,例如错误消息是什么,哪一行等。
  • 即使我能够对您的代码进行修改,我也无法真正调试它,因为您的输入来自一个文件,而我很难猜测其中的内容文件。
  • 打印出 k、first 和 last 的值,用于多次递归调用 kSmallFirst 并将它们发布在这里。

标签: c++ arrays algorithm recursion partition


【解决方案1】:

问题是:您没有实现算法的主要部分,这在您的书中用斜体字体进行了描述:

从 'anArray[first..last]' 中选择一个枢轴值 'p'

将 'anArray[first..last]' 关于 'p' 的值分区

这两行不是注释!它们是您将要转换为 C/C++ 的伪代码,以使您的代码执行您希望它执行的操作。

【讨论】:

    【解决方案2】:

    请注意,kSmallFirst 从不使用anArray,因此与JoriO 所说的相反,这不是输入问题。即使它确实尝试使用范围 main 将范围 [0 .. -1] 传递为 kSmallFirst 的 first 和 last。

    您需要了解算法在做什么,否则正如CiaPan 所提到的,您将无法实现最重要的部分。

    kSmall 是:

    1. 接收由first和last定义的anArray的分区
    2. 在anArray的分区中在first和last之间选择一个pivotIndex
    3. 将所有小于anArray[pivotIndex] 的元素移到pivotIndex 之下,将所有大于anArray[pivotIndex] 的元素移到pivotIndex 之上
    4. 这将定义anArray的下一对分区,从first到pivotIndex的分区和从pivotIndex到last的分区
    5. kSmall 将递归包含 kth 元素的范围

    考虑到这一点重写kSmall会产生:

    #include <algorithm>
    #include <functional>
    
    int kSmall(int k, int* anArray, int first, int last){
        int p = anArray[(last - first) / 2 + first]; // Choose a pivot value from anArray[first .. last]
        int* pivotIndex = std::partition(anArray + first, anArray + last, std::bind2nd(std::less<int>(), p)); // Partition the values of anArray around p
    
        if(k < pivotIndex - anArray){
            return kSmall(k, anArray, first, pivotIndex - anArray);
        }
        else if(k == pivotIndex - anArray){
            return pivotIndex - anArray;
        }
        else{
            return kSmall(k, anArray, pivotIndex - anArray, last);
        }
    }
    

    我相信您会注意到 if 语句中的数学运算与书中有所不同。我选择实现kSmall,就像你使用int参数一样,本书选择使用int*参数。

    【讨论】:

    • 假设递归调用句柄anArray[2..4],那么您建议的指令int p = anArray[(last - first) / 2]; 将选择索引-1处的项目。
    • @CiaPan 谢谢,我忘了加回first
    • (upvote) 很好,你简化了k 参数处理,在基本代码中通过更改first 来偏移k 非常难看。
    • @CiaPan 谢谢,我本可以通过 int pivotIndex = std::partition(...) - anArray; 进一步简化,但我认为它与书籍的实现差别太大了。
    • 好吧,按照thinkig的这种方式,最终的简化是调用std::sort并获取结果anArray[k]。然而,这可能有点违背 OP 手册的教学课程...... :)
    【解决方案3】:

    您选择的枢轴索引值是错误的。

    由于这是一个递归,因此您需要为每次调用更新您的枢轴。

    让它更通用。

    在kSmallFirst函数中,

    使用 pivotIndex = first,而不是 pivotIndex = 0。

    【讨论】:

    • 这可能会返回数组的第 k 项,但不会返回第 k-th 最小的项。
    • 是的,他从不围绕枢轴划分值。我只是指出初始化部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-13
    • 2018-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-22
    相关资源
    最近更新 更多