文件内的二分查找

      对于数量较大但已经排好序的内容,比如英语字典,想要快速查找里面是否包含某个单词,二分查找是再适合不过的了。但当字典内容太多时,将其调入内存来进行查找则不太现实,这里就讨论一下如何实现直接在文件内进行二分查找。

      想要在文件内部进行二分查找,则必须要能够随意移动文件指针的位置,这里通过 seekg() 函数实现。seekg() 函数的主要功能是将文件指针移动一定的偏移量,详细介绍见这里。

      文件二分的过程就是文件指针偏移量变化的过程,得到一个中间偏移量则调用 seekg() 函数将文件指针移到那儿。但是这里有个问题,文件指针不一定能移动到某一行的行首(大多数情况下都应该移不到行首)。一种解决办法是往回移动,直到碰到换行符;这种方法在某一行特别长的时候效率较低。另一种方法则是使用 getline() 函数将文件指针移至下一行的行首,这里使用的就是这种方法。

      整个文件二分的过程和普通二分的区别不大,主要是处理一些特殊情况以便能使文件指针准确定位在某一行的行首。

      特殊情况包括:

      1. 查找内容位于第一行或最后一行的情况

      2. 由于换行符占两个偏移量,故当中间偏移量 middle 刚好指在换行符上时也要特别考虑

      具体实现代码:

#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
using namespace std;

ifstream file("words.txt");
int times = 0; //用于计算查找次数

//used to binary search in the file
long BinarySearch(long b, long e, string target) {
	long begin = b;
	long end = e; //end一直位于某一行的行首,即最后一行的行尾
	long middle = 0;	
	string temp;
	
	while(begin < end) {
		times++;
		middle = (begin+end)/2;
		file.seekg(middle,file.beg);

		//判断是否刚好截在行尾
		char ch = file.get();
		if(ch == '\n') { //若刚好截在行尾,则往前进一格(换行符占两个偏移量),方便后续操作
			middle -= 1;
		}
		file.seekg(middle,file.beg);
		getline(file,temp);

		if(middle+temp.size()+2 == end) { //若为最后一行,则将middle移至行首
			long i = 1;
			ch = '\0';
			while (ch != '\n' && middle-i >= begin) {
				file.seekg(middle-i, file.beg);
				ch = file.get();
				i++;
			}
			if(ch == '\n')
				middle = middle - i + 2;
			else
				middle = begin;
		}
		else { //若非最后一行,则将middle移至下一行行首
			middle += temp.size() + 2;
		}
		
		file.seekg(middle,file.beg);
		getline(file,temp);
		
		if(temp == target) {
			return middle;
		}
		else {
			if(temp < target) {
				if(middle+temp.size()+2==end) return -1; //比最后一行要大,故不存在
				begin = middle;
			}
			else if(temp > target) {
				if(middle==begin) return -1; //比第一行要小,故不存在
				end = middle;
			}
		}
	}
	return -1;
}

int main() {
	string str, target;		
	cin>>target;

	getline(file,str);	
	if(str == target) { //第一行即为查找内容的情况
		cout<<"Found: "<<target<<endl;
	}
	else if(str > target) {
		cout<<"Too little!"<<endl;
		return 0;
	}
	else {
		//获取最后一行的内容
		char ch = '\0';
		long i = 2; //文件最后一行至少有一个字符
		while(ch != '\n') {
			file.seekg(-i, file.end);
			ch = file.get();
			i++;
		}
				
		getline(file,str);
		if(str == target) { //最后一行即为查找内容的情况
			cout<<"Found: "<<target<<endl;
		}
		else if(str < target) {
			cout<<"Too large!"<<endl;
			return 0;
		}
		else { //查找内容在文件中间的情况
			file.seekg(0,file.end);
			long len = file.tellg();
			file.seekg(0,file.beg);
			long res = BinarySearch(0,len-str.size(),target);
			cout<<"times: "<<times<<endl;			
			if(res == -1) {
				cout<<"Not Found!"<<endl;
			}
			else {
				file.seekg(res,file.beg);
				getline(file,str);
				cout<<"Found: "<<str<<endl;
			}
		}
	}
	return 0;
}

注意: getline() 函数会造成 tellg() 检测出来的偏移量出现偏差(具体原因不详,若有读者知道,烦请告知),故在 getline() 之后不能直接用 tellg() 函数获取当前偏移量,而要通过加上 getline() 得到的字符长度来获得。

另外 words.txt 的内容长这样:

文件内的二分查找

你可能感兴趣的:(C++,二分查找,文件操作,seekg)