Линейно търсене
Накратко: Линейното търсене проверява елементите един по един, докато намери търсения или стигне края: O(n) сравнения, и работи с неподреден списък.
Проблем
Скенерът на склада
В склада продуктите са записани в списък в реда, в който са дошли: брашно от понеделник, захар от вторник, олио, ориз. Работникът сканира кутия, а програмата трябва да намери продукта по баркод:
shelf = [ Product("3800200101112", "Брашно"), Product("3800200104403", "Захар"), Product("3800200107251", "Олио"), Product("3800200102690", "Ориз"), Product("3800200109958", "Сол"), Product("3800200105837", "Кафе"),]Баркодовете не са подредени. Не можеш да отвориш списъка по средата и да кажеш „оризът е по-нататък“: следващият баркод може да е всякакъв. Как намираш продукта, и колко струва това, когато продуктите са 50 000, а сканиранията 2000 на ден?
Идеята
Поглеждаш всяка кутия
Щом редът не помага, остава едно: започваш от първата кутия и сравняваш баркода ѝ с търсения. Ако съвпада, готово: връщаш мястото. Ако не, минаваш на следващата. Ако кутиите свършат, продуктът го няма.
Това е линейно търсене: минаваш елементите един по един, докато намериш търсения или стигнеш края. Връща се мястото (индексът) или знак за „няма“: None.
Виж го
Указателят по рафта
def find(shelf, barcode): for i, product in enumerate(shelf): if product.barcode == barcode: return i return Nonerice = find(shelf, "3800200102690")print("3800200102690 ->", rice, shelf[rice].name)print("3800200100000 ->", find(shelf, "3800200100000"))3800200102690 -> 3 Ориз3800200100000 -> Nonedef find(shelf, barcode): for i, product in enumerate(shelf): if product.barcode == barcode: return i return None
rice = find(shelf, "3800200102690")missing = find(shelf, "3800200100000")Стъпка по стъпка, с думи
- Скенерът чете баркода на ориза: 3800200102690. find тръгва от първата кутия.
- Сравнява баркода на всяка кутия с търсения: брашно, захар, олио. Не съвпада.
- Четвъртата кутия е оризът. return 3: намерен след 4 сравнения.
- Баркод, който складът го няма: 3800200100000. find тръгва пак от началото.
- Указателят минава всяка кутия и нито една не съвпада.
- Кутиите свършиха: return None след 6 сравнения. Липсващият продукт е най-лошият случай: n сравнения при n кутии.
Оризът е намерен след 4 сравнения. Липсващият баркод минава всички 6 кутии: само така може да се каже, че го няма.
В Python -1 е истински индекс: shelf[-1] е последният продукт. Ако find връщаше -1, код като shelf[find(shelf, code)].name щеше да даде кафето за баркод, който го няма, без никаква грешка. Затова „няма“ е None, а shelf[None] спира веднага с TypeError.
Пробвай
Попълни
Какво връща find, когато кутиите свършат?
def find(shelf, barcode):
for i, product in enumerate(shelf):
if product.barcode == barcode:
return i
return None значи „няма стойност“. -1 е истински индекс в Python (последният елемент), затова не става за „няма“.Идеята
Колко сравнения
Броят сравнения зависи от това къде е продуктът:
- най-добрият случай: първата кутия, 1 сравнение;
- най-лошият случай: последната кутия или продукт, който го няма, n сравнения;
- обикновеният случай: ако всяко място е еднакво вероятно, средно около n/2.
Най-лошият и обикновеният случай растат с n, затова линейното търсене е O(n). Програмата брои:
def comparisons(shelf, barcode): count = 0 for product in shelf: count += 1 if product.barcode == barcode: return count return countprint("Брашно:", comparisons(shelf, "3800200101112"))print("Ориз:", comparisons(shelf, "3800200102690"))print("Липсващ:", 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): един индекс.
Пробвай
Предвиди изхода
Солта е петата кутия на рафта. Колко сравнения прави търсенето?
def comparisons(shelf, barcode): count = 0 for product in shelf: count += 1 if product.barcode == barcode: return count return count
print("Сол:", comparisons(shelf, "3800200109958"))Виж го
Вградените търсят по същия начин
Всеки език има готово търсене в списък. Отвътре то е същият цикъл:
barcodes = [ "3800200101112", "3800200104403", "3800200107251", "3800200102690", "3800200109958", "3800200105837",]print(barcodes.index("3800200102690"))print("3800200100000" in barcodes)try: barcodes.index("3800200100000")except ValueError as e: print("ValueError:", e)3FalseValueError: '3800200100000' is not in listindex връща мястото, а ако го няма, хвърля ValueError. in дава само True или False. И двете минават от първия елемент нататък: O(n). Затова if code in barcodes: barcodes.index(code) търси два пъти.Едно извикване изглежда като една операция, но вътре са до n сравнения. Вградената функция е по-бърза от твоя цикъл с някакъв постоянен множител, не с порядък.
Пробвай
Намери грешката
Колега е написал търсенето така. Брашното се намира, а оризът „го няма“:
def find(shelf, barcode): for i, product in enumerate(shelf): if product.barcode == barcode: return i else: return None return Noneprint(find(shelf, "3800200101112"))print(find(shelf, "3800200102690"))0NoneНа кой ред е грешката?
Избери реда с грешката.
else с return вътре в цикъла връща „няма“ още след първата кутия, която не съвпада. Цикълът никога не стига до втората. Тестът с брашното минава, защото то е първо. „Няма“ може да се каже само след като кутиите свършат: махни else и остави единственото „няма“ след цикъла.
Идеята
Кога е точно на място и кога не
Линейното търсене е точният избор, когато:
- продуктите са малко: десет или сто сравнения не се усещат;
- търсиш веднъж: всеки по-бърз начин първо иска поне n стъпки за подготовка;
- данните са неподредени или се менят постоянно, и няма какво да поддържаш.
Не е, когато търсенията са много. 2000 сканирания на ден в 50 000 продукта са до 100 000 000 сравнения. Тогава подготвяш данните веднъж:
- таблица баркод → продукт (
dict): 50 000 стъпки веднъж, а после всяко търсене е O(1) средно; - подреден списък и двоично търсене: подреждането е O(n log n) веднъж, а всяко търсене после е O(log n), около 16 сравнения при 50 000.
by_barcode = {product.barcode: product for product in shelf}print(by_barcode["3800200102690"].name)print(by_barcode.get("3800200100000"))ОризNoneТаблицата струва памет: още n места. Това е същата размяна време срещу памет като set-а в урока „Сложност по памет“.
Упражнение
Всички кутии на един продукт
Реши задачата в редактора си.
Ревизия. Един продукт може да е дошъл с няколко доставки и да е на няколко места в списъка. Напиши find_all(shelf, barcode), която връща всички места на баркода, а не само първото. Рафтът е като горе, плюс още ориз и още сол накрая (места 6 и 7).
Ориз: 3, 6Сол: 4, 7Още задачи
- Последното място. Върни последното място на баркода. Откъде е по-добре да започнеш?
- По име, без значение главни и малки. Намери продукта по име („ориз“ и „Ориз“ са същото). Колко сравнения има в най-лошия случай?
- Продукти под минимума. Изреди всички продукти с наличност под 10 бройки. Може ли да спреш преди края?
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Времева сложност: броене на стъпки · Big-O: как расте работата · Най-добър, среден и най-лош случай · Сложност по памет
- Продължава в: Двоично търсене · Двоично търсене по отговора · Trie · Префиксни суми
- Задачи по темата: Търсене