Линейно търсене
Накратко: Линейното търсене проверява елементите един по един, докато намери търсения или стигне края: O(n) сравнения, и работи с неподреден списък.
Проблем
Скенерът на склада
В склада продуктите са записани в списък в реда, в който са дошли: брашно от понеделник, захар от вторник, олио, ориз. Работникът сканира кутия, а програмата трябва да намери продукта по баркод:
List<Product> shelf = List.of( new Product("3800200101112", "Брашно"), new Product("3800200104403", "Захар"), new Product("3800200107251", "Олио"), new Product("3800200102690", "Ориз"), new Product("3800200109958", "Сол"), new Product("3800200105837", "Кафе"));Баркодовете не са подредени. Не можеш да отвориш списъка по средата и да кажеш „оризът е по-нататък“: следващият баркод може да е всякакъв. Как намираш продукта, и колко струва това, когато продуктите са 50 000, а сканиранията 2000 на ден?
Идеята
Поглеждаш всяка кутия
Щом редът не помага, остава едно: започваш от първата кутия и сравняваш баркода ѝ с търсения. Ако съвпада, готово: връщаш мястото. Ако не, минаваш на следващата. Ако кутиите свършат, продуктът го няма.
Това е линейно търсене: минаваш елементите един по един, докато намериш търсения или стигнеш края. Връща се мястото (индексът) или знак за „няма“: -1, защото място -1 няма.
Виж го
Указателят по рафта
static int find(List<Product> shelf, String barcode) { for (int i = 0; i < shelf.size(); i++) { if (shelf.get(i).barcode().equals(barcode)) { return i; } } return -1;}int rice = find(shelf, "3800200102690");System.out.println("3800200102690 -> " + rice + " " + shelf.get(rice).name());System.out.println("3800200100000 -> " + find(shelf, "3800200100000"));3800200102690 -> 3 Ориз3800200100000 -> -1static int find(List<Product> shelf, String barcode) { for (int i = 0; i < shelf.size(); i++) { if (shelf.get(i).barcode().equals(barcode)) { return i; } } return -1;}
int rice = find(shelf, "3800200102690");int missing = find(shelf, "3800200100000");Стъпка по стъпка, с думи
- Скенерът чете баркода на ориза: 3800200102690. find тръгва от първата кутия.
- Сравнява баркода на всяка кутия с търсения: брашно, захар, олио. Не съвпада.
- Четвъртата кутия е оризът. return 3: намерен след 4 сравнения.
- Баркод, който складът го няма: 3800200100000. find тръгва пак от началото.
- Указателят минава всяка кутия и нито една не съвпада.
- Кутиите свършиха: return -1 след 6 сравнения. Липсващият продукт е най-лошият случай: n сравнения при n кутии.
Оризът е намерен след 4 сравнения. Липсващият баркод минава всички 6 кутии: само така може да се каже, че го няма.
Баркодовете се сравняват с equals, не с ==. == при текст пита дали е същият обект в паметта, а баркодът от скенера е друг обект със същите цифри.
Пробвай
Попълни
Какво връща find, когато кутиите свършат?
static int find(List<Product> shelf, String barcode) {
for (int i = 0; i < shelf.size(); i++) {
if (shelf.get(i).barcode().equals(barcode)) {
return i;
}
}
return ;
}-1 не е място в списъка, затова не може да се обърка с намерен продукт. Който вика find, проверява за -1, преди да ползва резултата.Идеята
Колко сравнения
Броят сравнения зависи от това къде е продуктът:
- най-добрият случай: първата кутия, 1 сравнение;
- най-лошият случай: последната кутия или продукт, който го няма, n сравнения;
- обикновеният случай: ако всяко място е еднакво вероятно, средно около n/2.
Най-лошият и обикновеният случай растат с n, затова линейното търсене е O(n). Програмата брои:
static int comparisons(List<Product> shelf, String barcode) { int count = 0; for (Product product : shelf) { count++; if (product.barcode().equals(barcode)) { return count; } } return count;}System.out.println("Брашно: " + comparisons(shelf, "3800200101112"));System.out.println("Ориз: " + comparisons(shelf, "3800200102690"));System.out.println("Липсващ: " + comparisons(shelf, "3800200100000"));Брашно: 1Ориз: 4Липсващ: 6Пусни двата крайни случая на голям склад. Броим по едно сравнение на кутия:
- Баркодът липсва (най-лош случай)
O(n) - Първият продукт (най-добър случай)
O(1)
| Алгоритъм | n = 10 | n = 1000 | n = 100 000 |
|---|---|---|---|
| Баркодът липсва (най-лош случай) | 10 | 1000 | 100 000 |
| Първият продукт (най-добър случай) | 1 | 1 | 1 |
Първата кутия струва 1 сравнение при всяко n: O(1). Липсващият продукт струва n: при 100 000 продукта са 100 000 сравнения. Паметта е O(1): един индекс.
Пробвай
Предвиди изхода
Солта е петата кутия на рафта. Колко сравнения прави търсенето?
static int comparisons(List<Product> shelf, String barcode) { int count = 0; for (Product product : shelf) { count++; if (product.barcode().equals(barcode)) { return count; } } return count;}
System.out.println("Сол: " + comparisons(shelf, "3800200109958"));Виж го
Вградените търсят по същия начин
Всеки език има готово търсене в списък. Отвътре то е същият цикъл:
List<String> barcodes = List.of( "3800200101112", "3800200104403", "3800200107251", "3800200102690", "3800200109958", "3800200105837");System.out.println(barcodes.indexOf("3800200102690"));System.out.println(barcodes.indexOf("3800200100000"));System.out.println(barcodes.contains("3800200102690"));3-1trueindexOf връща мястото или -1, а contains само „да“ или „не“. И двете сравняват с equals от първия елемент нататък: O(n). За списък от продукти по баркод Java няма готов метод; пишеш цикъла или ползваш поток (IntStream.range(...).filter(...).findFirst()), който също минава подред.Едно извикване изглежда като една операция, но вътре са до n сравнения. Вградената функция е по-бърза от твоя цикъл с някакъв постоянен множител, не с порядък.
Пробвай
Намери грешката
Колега е написал търсенето така. Брашното се намира, а оризът „го няма“:
static int find(List<Product> shelf, String barcode) { for (int i = 0; i < shelf.size(); i++) { if (shelf.get(i).barcode().equals(barcode)) { return i; } else { return -1; } } return -1;}System.out.println(find(shelf, "3800200101112"));System.out.println(find(shelf, "3800200102690"));0-1На кой ред е грешката?
Избери реда с грешката.
else с return вътре в цикъла връща „няма“ още след първата кутия, която не съвпада. Цикълът никога не стига до втората. Тестът с брашното минава, защото то е първо. „Няма“ може да се каже само след като кутиите свършат: махни else и остави единственото „няма“ след цикъла.
Идеята
Кога е точно на място и кога не
Линейното търсене е точният избор, когато:
- продуктите са малко: десет или сто сравнения не се усещат;
- търсиш веднъж: всеки по-бърз начин първо иска поне n стъпки за подготовка;
- данните са неподредени или се менят постоянно, и няма какво да поддържаш.
Не е, когато търсенията са много. 2000 сканирания на ден в 50 000 продукта са до 100 000 000 сравнения. Тогава подготвяш данните веднъж:
- таблица баркод → продукт (
HashMap): 50 000 стъпки веднъж, а после всяко търсене е O(1) средно; - подреден списък и двоично търсене: подреждането е O(n log n) веднъж, а всяко търсене после е O(log n), около 16 сравнения при 50 000.
Map<String, Product> byBarcode = new HashMap<>();for (Product product : shelf) { byBarcode.put(product.barcode(), product);}System.out.println(byBarcode.get("3800200102690").name());System.out.println(byBarcode.get("3800200100000"));ОризnullТаблицата струва памет: още n места. Това е същата размяна време срещу памет като set-а в урока „Сложност по памет“.
Упражнение
Всички кутии на един продукт
Реши задачата в редактора си.
Ревизия. Един продукт може да е дошъл с няколко доставки и да е на няколко места в списъка. Напиши findAll(shelf, barcode), която връща всички места на баркода, а не само първото. Рафтът е като горе, плюс още ориз и още сол накрая (места 6 и 7).
Ориз: 3, 6Сол: 4, 7Още задачи
- Последното място. Върни последното място на баркода. Откъде е по-добре да започнеш?
- По име, без значение главни и малки. Намери продукта по име („ориз“ и „Ориз“ са същото). Колко сравнения има в най-лошия случай?
- Продукти под минимума. Изреди всички продукти с наличност под 10 бройки. Може ли да спреш преди края?
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Времева сложност: броене на стъпки · Big-O: как расте работата · Най-добър, среден и най-лош случай · Сложност по памет
- Продължава в: Двоично търсене · Двоично търсене по отговора · Trie · Префиксни суми
- Задачи по темата: Търсене