这是在Internet上找到的插值搜索算法的常见C / C ++实现。但是,当与大约100000个整数的排序数组一起使用时,中间变量开始生成负数组索引,从而导致分段错误。问题是什么?
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
int interpolationSearch(int sortedArray[], int toFind, int len) {
// Returns index of toFind in sortedArray, or -1 if not found
int low = 0;
int high = len - 1;
int mid;
while (sortedArray[low] <= toFind && sortedArray[high] >= toFind) {
mid = low + ((toFind - sortedArray[low]) * (high - low)) /
(sortedArray[high] - sortedArray[low]);
if (sortedArray[mid] < toFind) {
low = mid + 1;
} else if (sortedArray[mid] > toFind) {
high = mid - 1;
} else {
return mid;
}
}
if (sortedArray[low] == toFind)
return low;
else
return -1; // Not found
}
int main(void) {
srand(time(0));
int arr[100000];
for (int i=0; i<100000; i++) {
arr[i] = rand()%100000;
}
int length = sizeof(arr)/sizeof(int);
qsort(arr,length,sizeof(int),order);
for (int j=0; j<10000; j++) {
interpolationSearch(arr,rand()%100000,length);
}
}
答案 0 :(得分:4)
问题在于计算mid
的表达式。即使使用32位整数,该产品也很容易溢出。然后它变成消极的。在产品之前进行分割可能会更好。
将中间计算更改为使用64位整数(至少用于中间计算)可以解决问题。
以下是我的修改版本(int64_t在<stdint.h>
中定义:
int interpolationSearch(int sortedArray[], int toFind, int len) {
// Returns index of toFind in sortedArray, or -1 if not found
int low = 0;
int high = len - 1;
int mid;
int l = sortedArray[low];
int h = sortedArray[high];
while (l <= toFind && h >= toFind) {
int64_t high_low = (high - low);
int64_t toFind_l = (toFind - l);
int64_t product = high_low*toFind_l;
int64_t h_l = h-l;
int64_t step = product / h_l;
mid = low + step;
/* mid = (low + high)/2;*/
int m = sortedArray[mid];
if (m < toFind) {
l = sortedArray[low = mid + 1];
} else if (m > toFind) {
h = sortedArray[high = mid - 1];
} else {
return mid;
}
}
if (sortedArray[low] == toFind)
return low;
else
return -1; // Not found
}
更简单的解决方法是通过使用:mid = (low + high) / 2
使其成为二分法搜索而不是插值。即使它收敛比插值稍慢,它也避免了包括产品和除法在内的多个操作,从而使内循环更快。不确定插值的潜在更快收敛是否可以弥补这种简单性的损失。
我做了一些性能测试。我的测试程序的来源包含在this question
中令人惊讶的是(对我而言)使用浮点数提供了比使用大整数更有效的程序。在我的系统中,对于阵列中的大约1000个项目,二进制搜索变得更快。对于大小为100000的数组,插值搜索比简单二进制搜索快近两倍。
答案 1 :(得分:4)
子表达式:((toFind - sortedArray[low]) * (high - low))
...可以轻松评估为:((99999-0) * (99999-0)) == 99999^2
...远大于2 ^ 31(= = 32位有符号整数的范围)。
一旦超过2 ^ 31-1,整数将溢出为负数,因此您的负指数。如果它超过2 ^ 32(它也可以做到),那么(很可能,技术上未定义)你将失去高阶位,你最终会得到有效的随机偏移,包括正负。
为避免所有这些,您需要仔细进行数学计算,以确保没有任何子表达式产生整数溢出。通常,最简单的方法是转换为浮点,其范围比32位整数大许多个数量级。
在最后的分析中,二进制搜索这样的插值通常是不值得的 - 计算插值的费用通常大于它“保存”的循环的少量额外迭代。
答案 2 :(得分:4)
正如其他答案所解释的那样,您正在尝试计算表单
的表达式A * B / C
但这是错误的,因为A * B
溢出。建议将表达式修改为
A * (B / C)
不起作用,因为B
通常小于C
,因此整数除法将截断为零。
切换到浮点的建议会起作用,但成本会很高。但您可以通过将表达式转换为:
来使用定点A * ((B * F) / C) / F
(其中F
是精心挑选的2的力量。)