编写通用的二分查找方法
Writing a generic binary search method
我写了这个二进制搜索方法,returns Book
对象在 ArrayList
中的索引,其中 book id
匹配输入的 book id
.
我如何才能将它变成一个通用方法,将不同类型的 ArrayList
对象和搜索输入作为参数并针对该输入进行搜索?有什么办法可以概括它吗?
public static int bSearch(ArrayList<Book> a, String input)
{
int low = 0;
int high = a.size() - 1;
int index = -1;
while(low <= high)
{
int mid = (low + high) / 2;
if(input.compareTo(a.get(mid).getID()) == 0) //input == target
{
//a binary search that returns index of min or max
index = mid;
return index;
}
else if(input.compareTo(a.get(mid).getID())) < 0) //input < target
high = mid - 1;
else if(input.compareTo(a.get(mid).getID()) > 0) //input > target
low = mid + 1;
}
return index;
}
请注意,您正在重新发明轮子,Collections.binarySearch
中已经实现了通用二分搜索。
考虑到这一点,为了练习,确保可以转换您的实现以使其通用,并进行一些更改:
- 声明类型参数
<T>
- 使输入列表的元素具有类型
T
而不是Book
(严格来说,? extends T
将是理想的)
- 设置要搜索的元素类型
T
- 添加一个比较器参数,并用它来替换
Book::getID
上的比较
像这样:
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> comparator) {
int low = 0;
int high = list.size() - 1;
int index = 0;
while (low <= high) {
int mid = (low + high) / 2;
int cmp = comparator.compare(key, list.get(mid));
if (cmp == 0) {
index = mid;
return index;
} else if (cmp < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return index;
}
当您使用此函数通过ID在列表中查找Book
时,您可以将比较器参数写为Comparator.comparing(Book::getID)
。
(不用说,列表参数必须已经按书号排序,否则二分查找就没有意义了。)
最后,正如 @nic 在评论中指出的那样,您的实现有一个非常不受欢迎的行为:当找不到元素时,它 returns 0。这不太好,因为两个原因:
无法判断该元素是在列表的第一个位置找到,还是没有找到。在 return 值为 0 时,调用者必须验证列表的第一个元素(如果存在)是否等于搜索到的元素。那是丑陋和痛苦的。
它没有提示如果插入缺失元素可能适合的位置,这是一个非常有趣的信息。
当元素在列表中找不到时,通常的做法是return-1 -index
,其中index
是元素应该出现的位置 如果它在排序列表中。您可以通过更改方法的最后一行来实现:
return -1 - low;
对于您的 follow-up 问题,如果您希望此方法将图书 ID 字符串作为要搜索的键,那么第三个参数可以是从中提取键的函数,而不是比较器书籍实例。然后,您可以将密钥与从实例中提取的密钥进行比较,而不是通过比较器来比较书籍实例:
public static <T> int binarySearchByStringField(List<? extends T> list, String key, Function<T, String> keyExtractor) {
int low = 0;
int high = list.size() - 1;
int index = 0;
while (low <= high) {
int mid = (low + high) / 2;
int cmp = key.compareTo(keyExtractor.apply(list.get(mid)));
if (cmp == 0) {
index = mid;
return index;
} else if (cmp < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1 - low;
}
我写了这个二进制搜索方法,returns Book
对象在 ArrayList
中的索引,其中 book id
匹配输入的 book id
.
我如何才能将它变成一个通用方法,将不同类型的 ArrayList
对象和搜索输入作为参数并针对该输入进行搜索?有什么办法可以概括它吗?
public static int bSearch(ArrayList<Book> a, String input)
{
int low = 0;
int high = a.size() - 1;
int index = -1;
while(low <= high)
{
int mid = (low + high) / 2;
if(input.compareTo(a.get(mid).getID()) == 0) //input == target
{
//a binary search that returns index of min or max
index = mid;
return index;
}
else if(input.compareTo(a.get(mid).getID())) < 0) //input < target
high = mid - 1;
else if(input.compareTo(a.get(mid).getID()) > 0) //input > target
low = mid + 1;
}
return index;
}
请注意,您正在重新发明轮子,Collections.binarySearch
中已经实现了通用二分搜索。
考虑到这一点,为了练习,确保可以转换您的实现以使其通用,并进行一些更改:
- 声明类型参数
<T>
- 使输入列表的元素具有类型
T
而不是Book
(严格来说,? extends T
将是理想的) - 设置要搜索的元素类型
T
- 添加一个比较器参数,并用它来替换
Book::getID
上的比较
像这样:
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> comparator) {
int low = 0;
int high = list.size() - 1;
int index = 0;
while (low <= high) {
int mid = (low + high) / 2;
int cmp = comparator.compare(key, list.get(mid));
if (cmp == 0) {
index = mid;
return index;
} else if (cmp < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return index;
}
当您使用此函数通过ID在列表中查找Book
时,您可以将比较器参数写为Comparator.comparing(Book::getID)
。
(不用说,列表参数必须已经按书号排序,否则二分查找就没有意义了。)
最后,正如 @nic 在评论中指出的那样,您的实现有一个非常不受欢迎的行为:当找不到元素时,它 returns 0。这不太好,因为两个原因:
无法判断该元素是在列表的第一个位置找到,还是没有找到。在 return 值为 0 时,调用者必须验证列表的第一个元素(如果存在)是否等于搜索到的元素。那是丑陋和痛苦的。
它没有提示如果插入缺失元素可能适合的位置,这是一个非常有趣的信息。
当元素在列表中找不到时,通常的做法是return-1 -index
,其中index
是元素应该出现的位置 如果它在排序列表中。您可以通过更改方法的最后一行来实现:
return -1 - low;
对于您的 follow-up 问题,如果您希望此方法将图书 ID 字符串作为要搜索的键,那么第三个参数可以是从中提取键的函数,而不是比较器书籍实例。然后,您可以将密钥与从实例中提取的密钥进行比较,而不是通过比较器来比较书籍实例:
public static <T> int binarySearchByStringField(List<? extends T> list, String key, Function<T, String> keyExtractor) {
int low = 0;
int high = list.size() - 1;
int index = 0;
while (low <= high) {
int mid = (low + high) / 2;
int cmp = key.compareTo(keyExtractor.apply(list.get(mid)));
if (cmp == 0) {
index = mid;
return index;
} else if (cmp < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1 - low;
}