## 编程珠玑:开篇
-
问题:
输入:所输入的是一个文件,至多包含n个正整数,每个正整数都要小于n,这里 n = 1 0 7 n = 10^7 n=107。如果输入时某一整数出现两次,就会产生一个致命的错误,这些数和其他任何数据都不关联,
输出:以增序形式输出经过排序的整数列表。
约束:至多(大概)只有1MB的可用内存,但是可用的磁盘空间非常充足,运行时间最多只允许几分钟,10分钟是最适宜的运行时间。
-
解法思路
-
在解决问题之前我们需要一个包含至少有 1 0 7 10^7 107个不重复的 1 ∼ 1 0 7 1\sim10^7 1∼107之间的整数的文件用于测试,我们可以自己在网上找一个这样的文件,不过我希望大家可以想一下如何生成一个乱序的,不重复的至少包含 1 0 7 个 1 ∼ 1 0 7 10^7个1\sim 10^7 107个1∼107之间的整数呢?下面是一种生成的方法,供参考:
#include<bitset> #include<iostream> #include<vector> #include<random> #include<algorithm> #include<fstream> using namespace std; //使用洗牌算法把vector的元素进行打乱 void shuffle(vector<int>& arr){ random_device rd; mt19937 gen(rd()); for(int i= arr.size()-1;i>0;i--){ uniform_int_distribution<int> dist(0,i); int j = dist(gen); swap(arr[i],arr[j]); } } const int SIZE_ARR = 900000;//整型数组大小,要求数量在10000000以内,我们取9000000 const int MAX_NUM = 1000000;//数组元素的最大值 const int MIN_NUM = 10;//数组元素的最小值 int main(){ vector<int> arr; for(int i =MIN_NUM;i<MAX_NUM;i++){ arr.push_back(i); } shuffle(arr); //进行洗牌 arr.resize(SIZE_ARR);//arr的size会大于SIZW_ARR,调用resize()后会自动截取到SIZE_ARR大小 //此时arr中已经有我们需要的乱序的不重复的整型数组,下面我们把这些整型数组存储到文件中 ofstream outputFile("./numbers.txt");//打开一个文件流,用于写入数组中的数字 if(!outputFile){ cerr<<"无法打开文件"<<endl; } for(int num : arr){ outputFile<<num; outputFile<<","; } outputFile.close();//关闭流 cout<<"读写结束"<<endl; }
-
解法1:使用归并排序:
- 思路:我们先说一下归并排序的基本思想,假如我们有两个增序的数列,我们如何把这两个数列合成一个增序的队列呢?一种方法就是:从这两个数列中各取出最小的整数,然后让这两个整数进行比较,把两者较小的放入第三个数列中,然后把较小的整数从原本所在的数列中删除,然后重复以上操作,直到某个数列为空,把另一个数列的所有整数都放入第三个数列中,最终第三个数列就是合并后的增序的数列,那么问题是一个数列如何分成两个有序的数列呢?这需要使用递归的思想,具体实现可以自行百度查看递归排序的排序算法。
- 那么我们是否可以使用这种思想在内存只有1MB的内存中对全部装入内存可能需要要40MB的数列进行排序呢?答案是可以的,首先我们知道,全部将文件中的整数放入内存是装不下,但是我们的硬盘是够用的,我们知道1MB可以放大约 2.5 × 1 0 5 2.5 \times 10^5 2.5×105个整数,但是我们为了让内存不太紧凑,我们就一次性放入 2 × 1 0 5 2 \times 10^5 2×105个整数,而我们的文件中有将近 1 × 1 0 7 1\times 10^7 1×107个整数。那么我们要如何借用归并排序对这 1 × 1 0 7 1\times 10^7 1×107个整数进行排序呢?
- 我们可以将这个有着 1 × 1 0 7 1\times 10^7 1×107个整数的数列分成50份,每份有 2 × 1 0 5 2\times 10^5 2×105个整数,我们可以把每份放入内存中进行排序,得到50个有序的数列,各存储到一个文件中,得到50个有序的整数文件,编号分别为1到50,然后我们从这50个文件中各取出最小的整数,得到50个整数,从这50个整数中取出最小的整数,放入到一个输出文件A中,并从那50个整数中删除这个最小的整数,然后看这个最小的整数来自那50个文件中的哪一个文件,再从这个文件中取出最小的整数并和那49个整数组成50个整数,再次取出50个整数中的最小的整数,加入输出文件A中,并从中删除,以此类推,直到50个文件合并成一个输出文件,最终得到的输出文件A就是我们排好序的文件。我们这里求50个整数的最小值可以使用堆来进行优化。实现代码如下:
#include<bitset> #include<iostream> #include<vector> #include<random> #include<algorithm> #include<fstream> #include<errno.h> #include<queue> #include<string> using namespace std; struct MyInt{ ifstream* input;//该整数所来自的文件 int value;//整数的值 bool operator<(const MyInt& other) const { return value < other.value; } }; struct CompareMyInt { bool operator()(MyInt*& a, MyInt*& b){ return a->value > b->value; // 使用大于号反转排序顺序 } }; int main(){ const int GROUP_SIZE = 200000; ifstream inputFile("./numbers.txt"); if(!inputFile){ perror("文件打开失败"); return 0; } //把乱序大文件分成多份有序小文件, int nums[GROUP_SIZE + 10]; int k = 0; int tmp; int len = 0; while(!inputFile.eof()){ //从要排序的文件中取出一部分(200000个)到一个数组中,形成一个分组 len = 0; while(len<GROUP_SIZE&&!inputFile.eof()) { inputFile>>tmp; nums[len]= tmp; len++; } //对这个分组进行排序,sort里面内置的有快速排序,不用自己手动实现了。 sort(nums,nums+len); //排序后的结果存入一个带有编号的文件中,并对文件编号,文件名就是文件的编号 ofstream outFile("./"+to_string(++k)+".txt"); for(int j = 0;j<len;j++){ outFile<<nums[j]; if(j!=len-1){ outFile<<" "; } } outFile.close(); } inputFile.close(); //获取多个有序小文件的流,建立文件编号和文件流的映射表 vector<ifstream*> streamMap(k+1);//文件编号和文件流的映射表 for(int i=1;i<=k;i++){ ifstream* inFile = new ifstream("./" +to_string(i) + ".txt"); if(!inFile){ perror("文件打开失败"); return 0; } streamMap[i] = inFile; } // 使用归并算法,把多个有序的小文件并称一个大的有序的文件results.txt中。 //建立堆 priority_queue<MyInt*,vector<MyInt*>,CompareMyInt> minHeap; for(int i = 1;i<=k;i++){ if(!(*streamMap[i]).eof()){ (*streamMap[i])>>tmp; MyInt* tmpInt = new MyInt{streamMap[i], tmp}; minHeap.push(tmpInt); } } //打开输出文件results.txt ofstream outputFile("./results.txt"); if(!outputFile){ perror("文件打开失败"); return 0; } while(k){ MyInt* min = minHeap.top(); minHeap.pop(); outputFile<<min->value; outputFile<<" "; if((*(min->input)).eof()){ k--; (*(min->input)).close(); delete min; }else{ (*(min->input))>>tmp; min->value = tmp; minHeap.push(min); } } outputFile.close(); return 0; }
-