从40亿个整数中找到不存在的一个

前言

给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数。(在文件中至少确实一个这样的数-为什么?)。在具有足够内存的情况下,如何解决该问题?如果有几个外部的“临时”文件可用,但是仅有几百字节的内存,又该如何解决该问题?

分析

这仍然是《编程珠玑》中的一个问题。前面我们曾经提到过《位图法》,我们使用位图法解决了这个问题。32位整型最多有4294967296个整数,而很显然40亿个数中必然会至少缺一个。我们同样也可以尝试使用位图法解决该问题,使用536 870 912个字节,约512M内存存储这40亿整数,存在该整数的位置1,最后遍历比特位,输出第一个比特位为0的位置即可。那如果仅借助几个“临时”文件,使用几百字节的内存的情况下该如何处理呢?

能否使用二分搜索呢?这40亿个整数是随机排列的,因此普通的二分搜索不能找到那个不存在的数。但是我们可以基于二分搜索的思想。

一个整数有32位,我们按照每个比特位是0还是1,将要查找的数据范围一分为二。从最高比特位开始:

  • 将最高比特位为0的放在一堆,为1的放在另外一堆
  • 如果一样多,则随意选择一堆,例如选0,则该位为0
  • 如果不一样多,选择少的一堆继续,如1更少,则该位为1

这里需要做一些解释:

  • 由于2^32个整数中,每一个比特位是1还是0的个数是相同的。如果在这40亿个整数中,某比特位为1和0的个数是相同的,则说明两边都有不存在的数。因此选择任意一堆即可。
  • 如果比特位1的整数比0的整数多,则说明,比特位为0的一堆数中,肯定缺少了一些数。而比特位为1的一堆数中,可能缺少一些数。因此,我们选择少的,也就是比特位为0的那一堆数。
  • 每一次选择,都记录选择的是0还是1,最多32次选择后,便可以至少找到一个整数,不存在这40亿数中。

实例说明

由于32位的整型数据量太多,不便说明,我们用一个4比特的数据对上面的思路再做一个说明。4比特最多有16个数。
假设有以下源数据:

1
3 5 2 6 7 -1 -4 -6 -3 1 -5

对应二进制形式如下(负数在内存中以补码形式存储):

1
0011 0101 0010 0110 0111 1111 1100 1010 1101 0001 1011

1.处理第1比特位被分为两部分数据,分别为:

  • 比特位为0的
1
0011 0101 0010 0110 0111 0001
  • 比特位为1的
1
1111 1100 1010 1101 1011

可以看到,第一比特位为1的数为5个,比比特位为0的数要少,因此选择比特位为1的数,继续处理。且第一比特位,获得1.

3.处理第2比特位仍然分为两部分数据,分别为:

  • 比特位为0的
1
1010 1011
  • 比特位为1的
1
1111 1100  1101

可以看到,第一比特位为1的数为3个,比比特位为0的数要多,因此选择比特位为0的数,继续处理。且第二比特位,获得0

2.处理第3比特位仍然被分为两部分数据,分别为:

  • 比特位为0的
1
  • 比特位为1的
1
1010 1011

明显看到第三比特位为0的数没有,因此选择比特位0,获得0。至此,已经没有必要继续查找了。

我们最终得到了前三个比特位100,因此不存在于这些数中至少有1000,1001,即-8,-7。

代码实现

C语言实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

#include <stdlib.h>

#define MAX_STR 10

#define SOURCE_FILE "source.txt" //最原始文件,需要保留

#define SRC_FILE "src.txt"       //需要分类的文件

#define BIT_1_FILE "bit1.txt"

#define BIT_0_FILE "bit0.txt"

#define INT_BIT_NUM  32

/*

FILE *src   源数据文件指针

FILE *fpBit1 存储要处理的比特位为1的数据

FILE *fpBit0 存储要处理的比特位为0的数据

int bit     要处理的比特位

返回值

0:选择比特位为0的数据继续处理

1:选择比特位为1的数据继续处理

-1:出错

*/

int (FILE *src,FILE *fpBit1,FILE *fpBit0,int bit,int *nums)

{

/*入参检查*/

if(NULL == src || NULL == fpBit1 || NULL == fpBit0 || NULL == nums)

{

printf("input para is NULL");

return -1;

}

/*bit位检查*/

if(bit < 0 || bit > INT_BIT_NUM )

{

printf("the bit is wrong");

return -1;

}

char string[MAX_STR] = {0};

int mask = 1<< bit;

int bit0num = 0;

int bit1num = 0;

int num = 0;

//printf("mask is %xn",mask);

/*循环读取源数据*/

while(fgets(string, MAX_STR, src ) != NULL)

{

num = atoi(string);

//printf("%d&%d %dn",num,mask, num&mask);

/*根据比特位的值,将数据写到不同的位置,注意优先级问题*/

if(0 == (num&mask))

{

//printf("bit 0 %dn",num);

fprintf(fpBit0, "%dn", num);

bit0num++;

}

else

{

//printf("bit 1 %dn",num);

fprintf(fpBit1, "%dn", num);

bit1num++;

}

}

//printf("bit0num:%d,bit1num:%dn",bit0num,bit1num);

if(bit0num > bit1num)

{

/*说明比特位为1的数少*/

*nums = bit1num;

return 1;

}

else

{

*nums = bit0num;

return 0;

}

}

/***

*关闭所有文件描述符

*

* **/

void closeAllFile(FILE **src,FILE **bit0,FILE **bit1)

{

if(NULL != src && NULL != *src)

{

fclose(*src);

*src = NULL;

}        

if(NULL != bit1 && NULL != *bit1)

{

fclose(*bit1);

*bit1 = NULL;

}        

if(NULL != bit0 && NULL != *bit0)

{

fclose(*bit0);

*bit0 = NULL;

}        

}

int findNum(int *findNum)

{

int loop = 0;

/*打开最原始文件*/

FILE *src = fopen(SOURCE_FILE,"r");

if(NULL == src)

{

printf("failed to open %s",SOURCE_FILE);

return -1;

}

FILE *bit1 = NULL;

FILE *bit0 = NULL;

int num = 0;

int bitNums = 0; //得到比特位的数字数量

int findBit = 0; //当前得到的比特位

for(loop = 0; loop < INT_BIT_NUM;loop++)

{

/*第一次循环不会打开,保留源文件*/

if(NULL == src)

{

src = fopen(SRC_FILE,"r");

}

if(NULL == src)

{

return -1;

}

/**打开失败时,注意关闭所有打开的文件描述符**/

bit1 = fopen(BIT_1_FILE,"w+");

if(NULL == bit1)

{

closeAllFile(&src,&bit1,&bit0);

printf("failed to open %s",BIT_1_FILE);

return -1;

}

bit0 = fopen(BIT_0_FILE,"w+");

if(NULL == bit0)

{

closeAllFile(&src,&bit1,&bit0);

printf("failed to open %s",BIT_0_FILE);

return -1;

}

findBit = splitByBit(src,bit1,bit0,loop,&bitNums);

if(-1 == findBit)

{

printf("process errorn");

closeAllFile(&src,&bit1,&bit0);

return -1;

}

closeAllFile(&src,&bit1,&bit0);

//printf("find bit %dn",findBit);

/*将某比特位数量少的文件重命名为新的src.txt,以便进行下一次处理*/

if(1 == findBit)

{

rename(BIT_1_FILE,SRC_FILE);

num |=  (1 << loop);

printf("mv bit1 file to src filen");

}

else

{

printf("mv bit0 file to src filen");

rename(BIT_0_FILE,SRC_FILE);

}

/*如果某个文件数量为0,则没有必要继续寻找下去*/

if(0 == bitNums)

{

printf("no need to continuen");

break;

}

}

*findNum = num;

return 0;

}

int main()

{

int num = 0;

findNum(&num);

printf("final num is %d or 0x%xn",num,num);

return 0;

}

代码说明:

  • 这里的splitByBit函数根据比特位将数据分为两部分
  • closeAllFile用于关闭文件描述符
  • findNum函数循环32个比特位,每处理一次得到一个比特位,最终可以得到不存在其中的整数。

利用脚本产生了约2000万个整数:

1

2
wc -l source.txt 

20000001 source.txt

编译运行:

1

2

3

4

5

6

7
$ gcc -o binarySearch binarySearch.c

$ time ./binarySearch

final num is 18950401 or 0x1212901

real    0m8.001s

user    0m6.466s

sys    0m0.445s

程序的主要时间花在了读写文件,且占用内存极小。

总结

本文从一个特别的角度用最常见的二分搜索解决了该问题,最多拆分32次,便可从中找到不存在的整数。你有什么更好的思路或优化点,欢迎留言。

原文:大专栏  从40亿个整数中找到不存在的一个

原文地址:https://www.cnblogs.com/wangziqiang123/p/11618442.html

时间: 2024-10-10 02:28:18

从40亿个整数中找到不存在的一个的相关文章

如何从40亿整数中找到不存在的一个

前言 给定一个最多包含40亿个随机排列的32位的顺序整数的顺序文件,找出一个不在文件中的32位整数.(在文件中至少确实一个这样的数-为什么?).在具有足够内存的情况下,如何解决该问题?如果有几个外部的"临时"文件可用,但是仅有几百字节的内存,又该如何解决该问题? 分析 这仍然是<编程珠玑>中的一个问题.前面我们曾经提到过<位图法>,我们使用位图法解决了这个问题.32位整型最多有4294967296个整数,而很显然40亿个数中必然会至少缺一个.我们同样也可以尝试使

【面试被虐】如何只用2GB内存从20亿,40亿,80亿个整数中找到出现次数最多的数?

这几天小秋去面试了,不过最近小秋学习了不少和位算法相关文章,例如 [面试现场]如何判断一个数是否在40亿个整数中? [算法技巧]位运算装逼指南 对于算法题还是有点信心的,,,,于是,发现了如下对话. 20亿级别 面试官:如果我给你 2GB 的内存,并且给你 20 亿个 int 型整数,让你来找出次数出现最多的数,你会怎么做? 小秋:(嗯?怎么感觉和之前的那道判断一个数是否出现在这 40 亿个整数中有点一样?可是,如果还是采用 bitmap 算法的话,好像无法统计一个数出现的次数,只能判断一个数是

只用2GB内存在20亿个整数中找到出现次数最多的数

[题目] 有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数. [要求] 内存限制为2GB. [解答] 想要在很多整数中找到出现次数最多的数,通常的做法是使用哈希表对出现的每一个数做词频统计,哈希表的key是某一个整数,value是这个数出现的次数.就本题来说,一共有20亿个数,哪怕只是一个数出现了20亿次,用32位的整数也可以表示其出现的次数而不会产生溢出,所以哈希表的key需要占用4B,value也是4B.那么哈希表的一条记录(key,value)需要占用8B,当哈希表记录

6.3 40亿个非负整数中找到没出现的数

[题目]: 32位无符号整数的范围是0~4294967295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然有没出现过的数.可以使用最多1GB的内存,怎么找到所有没出现过的数 [进阶]: 内存限制为10MB,但是只用找到一个没出现过的数即可 原文地址:https://www.cnblogs.com/latup/p/9942103.html

一个文件中有40亿个整数,每个整数为四个字节,内存为1GB,写出一个算法:求出这个文件里的整数里不包含的一个整数

4个字节表示的整数,总共只有2^32约等于4G个可能.为了简单起见,可以假设都是无符号整数.分配500MB内存,每一bit代表一个整数,刚好可以表示完4个字节的整数,初始值为0.基本思想每读入一个数,就把它对应的bit位置为1,处理完40G个数后,对500M的内存遍历,找出一个bit为0的位,输出对应的整数就是未出现的.算法流程:1)分配500MB内存buf,初始化为02)unsigned int x=0x1;  for each int j in file  buf=buf|x<<j;  e

9.10扩展性与存储限制(二)——给定一个输入文件,包含40亿个非负整数。产生一个不在该文件中的整数。内存限制:1GB

/** * 功能:给定一个输入文件,包含40亿个非负整数.产生一个不在该文件中的整数.内存限制:1GB * 进阶:内存限制10MB. */ /** * 思路: * * 1)创建包含40个亿个比特的位向量. * 位向量(BV,bit vector)其实就是数组,利用整数(或另一种数据类型)数组紧凑地储存布尔值.每个整数可存储一串32比特或布尔值. * 2)将BV的所有元素初始化为0. * 3)扫描文件中的所有数字(num),并调用BV.set(num,1). * 4)接着,再次从索引0开始扫描BV

判读40亿数字中是否有某个数字

网上大部分是使用的bitmap算法.大体思路是:一个字节(Byte) 在计算机中占8位(bit),每个位(bit)可以表示一个数字,1表示含有,0表示不含有.1个32位系统的int类型可以存储2的32次方个bit位,大约是42亿多点.这样40亿需要 40/8=5亿字节,1M =1024KB*1024=1042576Byte,故大概需要512M左右大小即可.空间复杂度是O(n)+O(1); 还有更好的方法: 这个问题在<编程珠玑>里有很好的描述,大家可以参考下面的思路,探讨一下:又因为2^32为

给40亿个不重复的unsigned int的数,没排序,然后再给一个数,如何快速间断这个数是否在那40亿个数中

40亿个数,如果用无符号的long long数组来存,那么使用数组里的每一个元素的每一位代表一个数,具体为: a[0]  ---- 0~63 a[1]  ---- 64~127 a[2]  ---- 128~190 ... 那么,40亿 bit/64 = 6.25*107 *8 byte = 500MB , 内存就满足了. #include <iostream> #include <bitset> #include <cstring> #include <vect

08-1. 求一批整数中出现最多的个位数字(20)

给定一批整数,分析每个整数的每一位数字,求出现次数最多的个位数字.例如给定3个整数1234.2345.3456,其中出现最多次数的数字是3和4,均出现了3次. 输入格式: 输入在第1行中给出正整数N(<=1000),在第2行中给出N个不超过整型范围的正整数,数字间以空格分隔. 输出格式: 在一行中按格式“M: n1 n2 ...”输出,其中M是最大次数,n1.n2.……为出现次数最多的个位数字,按从小到大的顺序排列.数字间以空格分隔,但末尾不得有多余空格. 输入样例: 3 1234 2345 3