编程珠玑:开篇

## 编程珠玑:开篇

  1. 问题:

    输入:所输入的是一个文件,至多包含n个正整数,每个正整数都要小于n,这里 n = 1 0 7 n = 10^7 n=107。如果输入时某一整数出现两次,就会产生一个致命的错误,这些数和其他任何数据都不关联,

    输出:以增序形式输出经过排序的整数列表。

    约束:至多(大概)只有1MB的可用内存,但是可用的磁盘空间非常充足,运行时间最多只允许几分钟,10分钟是最适宜的运行时间。

  2. 解法思路

    1. 在解决问题之前我们需要一个包含至少有 1 0 7 10^7 107个不重复的 1 ∼ 1 0 7 1\sim10^7 1107之间的整数的文件用于测试,我们可以自己在网上找一个这样的文件,不过我希望大家可以想一下如何生成一个乱序的,不重复的至少包含 1 0 7 个 1 ∼ 1 0 7 10^7个1\sim 10^7 1071107之间的整数呢?下面是一种生成的方法,供参考:

      #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;
      	
      }
      
    2. 解法1:使用归并排序:

      1. 思路:我们先说一下归并排序的基本思想,假如我们有两个增序的数列,我们如何把这两个数列合成一个增序的队列呢?一种方法就是:从这两个数列中各取出最小的整数,然后让这两个整数进行比较,把两者较小的放入第三个数列中,然后把较小的整数从原本所在的数列中删除,然后重复以上操作,直到某个数列为空,把另一个数列的所有整数都放入第三个数列中,最终第三个数列就是合并后的增序的数列,那么问题是一个数列如何分成两个有序的数列呢?这需要使用递归的思想,具体实现可以自行百度查看递归排序的排序算法。
      2. 那么我们是否可以使用这种思想在内存只有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个整数进行排序呢?
      3. 我们可以将这个有着 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;
      }
      
  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值