【问题标题】:Random numbers external sort随机数外部排序
【发布时间】:2017-09-05 15:12:22
【问题描述】:

我需要编写一个程序,生成 N 个随机数并按降序将它们写入二进制文件。它应该在不使用任何使用主内存的排序算法的情况下完成。这是我到目前为止所做的:

#include <iostream>
#include <fstream> 
#include <ctime>
#include <cstdlib>

using namespace std;
int main () {
  srand(time(0));
  rand();
  int N;
  do{
    cout << "Unesite N: ";
    cin >> N;
    } while(N<=0);

  ofstream br("broj.dat", ios::binary | ios::trunc);

  for(int i = 0; i<N; i++){
    int a = rand();
    br.write((char *)&a, sizeof(a));
  }
  br.close();

  return 0;
}

所以,我生成了随机数并将它们写入二进制文件,但我不知道如何对其进行排序。

【问题讨论】:

  • 您要查找的内容称为外部合并排序,那里有很多关于如何完成此任务的信息。请注意,它们都使用一定大小的缓冲区,否则您需要制作 N 个文件,而我认为您不想这样做。
  • 在 SO stackoverflow.com/questions/20802396/…... 上找到了一些关于外部排序的信息...并且接受的答案提供的链接是一个好的开始
  • @KrisBob 我认为你将要学习关于拖延的教训。编程不是在最后一刻完成的,调试时间总是你想象的两倍。至少。
  • @NathanOliver 缓冲区减少了通过,但很容易编写一个没有缓冲区且从不需要超过O(log(n)) 个文件的外部合并排序。诀窍是维护一堆文件。将 2 个元素写入文件,然后递归合并堆栈,直到文件大小不同。继续这样做,然后在最后递归合并堆栈。
  • 这可能是考虑排序后那些N数字之间差异分布的时候了。接下来,假设一个虚拟元素std::numeric_limits&lt;int&gt;::max(),从合适的源中提取差异,从先前的减去,输出并重复。 (如果到std::numeric_limits&lt;int&gt;::min()太快,需要调整来源。)(这样就完成了第一句的要求,与标题中的external sort没有任何关系。)

标签: c++ algorithm sorting random binaryfiles


【解决方案1】:

您可以在线性时间内按排序顺序生成数字。描述如何做到这一点的论文是:Generating Sorted Lists of Random Numbers by Bentley & Saxe

https://pdfs.semanticscholar.org/2dbc/4e3f10b88832fcd5fb88d34b8fb0b0102000.pdf

/**
 * Generate an sorted list of random numbers sorted from 1 to 0, given the size
 * of the list being requested.
 * 
 * This is an implementation of an algorithm developed by Bentley and Sax, and
 * published in in ACM Transactions on Mathematical Software (v6, iss3, 1980) on
 * 'Generating Sorted Lists of Random Numbers'.
 */
public class SortedRandomDoubleGenerator {
    private long       valsFound;
    private double     curMax;
    private final long numVals;

    /**
     * Instantiate a generator of sorted random doubles.
     * 
     * @param numVals the size of the list of sorted random doubles to be
     *        generated
     */
    public SortedRandomDoubleGenerator(long numVals) {
        curMax = 1.0;
        valsFound = 0;
        this.numVals = numVals;
    }

    /**
     * @return the next random number, in descending order.
     */
    public double getNext() {
        curMax = curMax
                * Math.pow(Math.E, Math.log(RandomNumbers.nextDouble())
                        / (numVals - valsFound));
        valsFound++;
        return curMax;
    }
}

【讨论】:

  • RandomNumbers 可以是标准的 Java 随机数生成器 -- 获取 0-1 之间的伪随机数的任何方式。
  • 酷,但我认为他的任务的重点是学习如何在不先将文件加载到内存的情况下对磁盘文件的内容进行排序;以已经排序的顺序写出文件会错过分配的重点吗?
  • @JeremyFriesner 我没有注意到这是家庭作业。不过,这回答了所提出的问题,所以我将保留它。
  • @JeremyFriesner:我看不到任何问题表明它需要对磁盘文件进行排序。它说,“生成 N 个随机数并按降序将它们写入二进制文件。”没有说将它们写入文件然后对它们进行排序。
  • @JeremyFriesner 很抱歉,如果描述得不好,作业由输入和输出组成。输入:int N;输出:二进制文件 numbers.dat,其中 N 个随机数按降序排列。 (不要使用任何使用主存的算法)
【解决方案2】:

这是我如何做的伪代码。

for i in 1..N:
    write rand() to new file
    push onto file stack (new file, size=1)
    while 2 < len(file stack) and size of top two files the same:
        pop top two and merge them
        push onto file stack (merged file, size=new size)

while 2 < len(file stack):
    pop top two and merge them
    push onto file stack (merged file, size=new size)

The top of the file stack is your new sorted file.

【讨论】:

  • @greybeard 不,这是O(log(n)) 文件,因为当文件大小不同时合并会停止。这是您在前几次迭代后得到的大小堆栈。 1, 2, 2 1, 4, 4 1, 4 2, 8, ...
  • 当我说4 1 时,我指的是底部有4 个元素的文件和顶部有1 个元素的文件。是的,堆栈大小确实适用于O(log(n))。最坏的情况是您需要n 文件来存储2^n-1 元素。 (然后它们都折叠成一个合并的文件。)至于我的帖子,主要是为了澄清我在上面留给@NathanOliver 的评论,描述了这个确切的策略。
  • 终于查看了 NathanOliver 和您的评论。对外部排序一心一意我会将数字/运行分配到 few (比如说,m)个文件(两个会这样做)并进行 m 路合并,再次尝试分配到 m 个输出文件.里程各不相同。
  • @greybeard 如果您要进行真正的 外部排序..unix 实用程序是一个很好的起点。它可以被击败,例如在 m 路合并中使用的确切 m 将取决于您的硬件。当然,在将任何内容写入磁盘之前,您应该在内存中进行一定数量的排序。但是,如果我只是想要一个易于从头开始编写的外部排序?我就是这样写的。
【解决方案3】:

标准库有归并排序,但您需要使用随机访问迭代器。如果你可以使用mmap(或它的等价物),你就有随机访问迭代器(是的,我知道你需要从命令行获取COUNT):

#include <algorithm>
#include <cstdio>
#include <random>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

const size_t COUNT = 4096 * 4096;

int main()
{
    // create file (using mmap for simplicity)
    int fd = open("out.dat", O_RDWR | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        std::perror("open failed");
        return 1;
    }
    if (ftruncate(fd, COUNT * sizeof(unsigned)) != 0) {
        std::perror("ftruncate failed");
        close(fd);
        return 1;
    }
    void* mm = mmap(nullptr, COUNT * sizeof(unsigned), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mm == MAP_FAILED) {
        std::perror("mmap failed");
        close(fd);
        return 1;
    }
    close(fd);

    // populate file
    unsigned* begin = static_cast<unsigned*>(mm);
    std::default_random_engine rng((std::random_device())());
    std::generate_n(begin, COUNT, rng);
    msync(mm, COUNT * sizeof(unsigned), MS_SYNC);
    std::puts("file written");

    // sort file
    std::stable_sort(begin, begin + COUNT);
    msync(mm, COUNT * sizeof(unsigned), MS_SYNC);
    std::puts("file sorted");

    if (std::is_sorted(begin, begin + COUNT)) {
        std::puts("it's properly sorted");
    }

    // close file
    munmap(mm, COUNT * sizeof(unsigned));
    return 0;
}

实际上并不需要msync 调用。老实说,我很惊讶它的表现不错。

【讨论】:

  • I'm honestly surprised 我不会。对另一种处理糟糕的问题陈述的方法表示敬意。 (不过,请注意您如何看待付费客户的需求描述。)
猜你喜欢
  • 2015-03-04
  • 1970-01-01
  • 1970-01-01
  • 2017-09-08
  • 2012-12-04
  • 2013-11-22
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多