Поэтому я пытаюсь научить себя, как реализовать бинарный поиск на Java, поскольку тема может быть отброшена, но у меня проблемы.
Понимаете, я склонен быть немного упрямым, и я бы предпочел не просто скопировать какую-то реализацию из Интернета.
Чтобы научить меня этому, я создал очень (ОЧЕНЬ) грубый маленький класс, который выглядит следующим образом:
public class bSearch{
/**
* @param args
*/
public static void main(String[] args) {
int one = 1;
int two = 2;
int three = 3;
int four = 4;
int five = 5;
int six = 6;
ArrayList tab = new ArrayList();
tab.add(one);
tab.add(two);
tab.add(three);
tab.add(four);
tab.add(five);
tab.add(six);
System.out.println(bSearch(tab, 53));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public static int bSearch(ArrayList tab, int key) {
if (tab.size() == 0)
return 0;
if ((int) tab.get(tab.size() / 2) == key)
return key;
ArrayList smallerThanKey = new ArrayList();
ArrayList largerThanKey = new ArrayList();
for (int i = 0; i < (tab.size() + 1) / 2; i++) {
smallerThanKey.add(tab.get(i));
}
System.out.println("Smaller array = " + smallerThanKey);
for (int i = (tab.size() + 1) / 2; i < tab.size(); i++) {
largerThanKey.add(tab.get(i));
}
System.out.println("Larger array = " + largerThanKey);
if (key < (int) tab.get(tab.size() / 2)) {
bSearch(smallerThanKey, key);
} else {
bSearch(largerThanKey, key);
}
return key;
}
}
Как вы можете видеть, это довольно далеко от красивой, но это ясно для того, чтобы noobie, как и я, чтобы понять, так или иначе.
Теперь, вот проблема; когда я подаю ему номер, который находится в ArrayList
, он возвращает мне номер (ура!), но когда я подаю ему номер, который не находится в ArrayList
, он по-прежнему возвращает мне мой номер (boo!),
У меня такое чувство, что моя ошибка очень незначительна, но я просто не вижу ее.
Или я ошибаюсь, и есть какая-то большая фундаментальная ошибка?
Ваша помощь глубоко оценена!
ОБНОВИТЬ
Спасибо за все конструктивные комментарии и ответы! Несколько полезных указателей в правильном направлении несколькими из вас. +1 для всех, кто ударил меня по правильному пути.
Следуя советам, которые вы дали, в основном касаясь моих рекурсий, не заканчивающихся должным образом, я добавил несколько return
утверждений следующим образом;
if (key < (int) tab.get(tab.size() / 2)) {
return bSearch(smallerThanKey, key);
} else {
return bSearch(largerThanKey, key);
}
Теперь, что это делает, это на один шаг ближе к тому, чего я хочу достичь.
Теперь я получаю 0
если число нигде не найдено, и само число, если оно должно быть найдено. Таким образом, прогресс идет!
Однако это не сработает, если у меня есть поиск отрицательного числа или нуля (не то, что я знаю, почему я должен, но просто выбрасываю это там).
Есть ли исправление для этого, или я лаяю неправильное дерево в вопросе?
РЕДАКТИРОВАТЬ
Точно так же, как быстрое решение заданного вами вопроса: вам нужно изменить последние несколько строк на следующие
if (key < (int) tab.get(tab.size() / 2)) {
return bSearch(smallerThanKey, key);
} else {
return bSearch(largerThanKey, key);
}
}
Сказав это, позвольте мне отметить еще несколько проблем, которые я вижу здесь:
(а) вы можете использовать дженерики. Это использование ArrayList<Integer>
а не просто ArrayList
это избавит вас от всех этих приведений.
(b) Вместо того, чтобы вернуть найденное значение, вам лучше вернуть индекс в ArrayList, где находится значение, или -1, если он не был найден. Вот почему: возврат key
предоставляет вызывающей стороне очень мало новой информации. Я имею в виду - вызывающий уже знал, какой ключ. Если вы вернете указатель к ключу, вы позволяете вызывающему абоненту узнать, был ли найден ключ или нет, и если он был найден там, где он находится в списке.
(c) Вы по существу копируете весь список каждый раз, когда вы bSearch()
в bSearch()
: вы копируете примерно половину списка в smallerThanKey
и (примерно) наполовину в greaterThanKey
. Это означает, что сложность этой реализации не O(log n)
а вместо O(n)
.
(EDIT # 2)
Суммируя пункты (a), (b), (c) здесь, как можно написать этот метод:
public static int bSearch(ArrayList<Integer> tab, int key) {
return bSearch(tab, 0, tab.size(), key);
}
public static int bSearch(ArrayList<Integer> tab, int begin, int end, int key) {
int size = end - begin;
if (size <= 0)
return -1;
int midPoint = (begin + end) / 2;
int midValue = tab.get(midPoint);
if (midValue == key)
return midPoint;
if (key < midValue) {
return bSearch(tab, begin, midPoint, key);
} else {
return bSearch(tab, midPoint + 1, end, key);
}
}
Как вы можете видеть, я добавил второй метод, который принимает begin
и end
параметры. Эти параметры позволяют методу, в котором часть списка должна выглядеть. Это намного дешевле, чем создание нового списка и копирование элементов. Вместо этого рекурсивная функция просто использует объект списка и просто вызывает себя с новыми значениями begin
, end
.
Возвращаемое значение теперь является индексом key
внутри списка (или -1, если не найден).
Ваша рекурсия закончилась неправильно. В конце метода вы рекурсивно вызываете метод bSearch
для левой или правой части массива. В этот момент вам нужно вернуть результат поиска рекурсивных вызовов.
Идея двоичного поиска: если ваш текущий узел не является key
, посмотрите влево, если значение текущего узла больше, чем key
или посмотрите вправо, если оно меньше. Поэтому, посмотрев туда, вам нужно вернуть результат поиска.
if (key < (int) tab.get(tab.size() / 2)) {
return bSearch(smallerThanKey, key);
} else {
return bSearch(largerThanKey, key);
}
Как побочное замечание, посмотрите на System.arraycopy
и всегда полезно не подавлять предупреждения.
Я думаю, что проблема здесь:
if (key < (int) tab.get(tab.size() / 2)) {
bSearch(smallerThanKey, key);
} else {
bSearch(largerThanKey, key);
}
return key;
Вы просто отбрасываете результат своего рекурсивного вызова на bSearch
и возвращаете key
. Таким образом, на самом деле не удивительно, что вы возвращаете все количество, которое вы подаете в этот метод.
Помните, как должен работать бинарный поиск - если значение не находится в середине, верните результат поиска в левой/правой половине массива. Поэтому вам нужно что-то сделать с этими рекурсивными вызовами....
И с бинарным поиском вы действительно должны больше заботиться о том, чтобы найти место, которое вы ищете, а не его ценность - вы уже знаете это! Так что, по вашему мнению, бинарный поиск работал правильно, было немного ошибочно - поиск 1
должен был вернуть 0
- индекс/местоположение 1
.
Кроме того, вам не нужно иметь дело с копированием массивов и т.д. - что операция, которая не нужна для поиска. Просто используйте параметры, чтобы указать, где начать/завершить поиск.