470 likes | 819 Views
Поиск и сортировка. Лекция 14. Зачем?. Классические алгоритмы необходимо изучать, чтобы: понимать, насколько затратной может оказаться использование той или иной библиотечной функции ; создать у себя запас идей, которые можно использовать при разработке собственных программ ;
E N D
Поиск и сортировка Лекция 14
Зачем? Классические алгоритмы необходимо изучать, чтобы: понимать, насколько затратной может оказаться использование той или иной библиотечной функции; создать у себя запас идей, которые можно использовать при разработке собственных программ; на хорошо отработанных примерах научиться писать простой и понятный код.
Последовательный поиск Задача поиска Есть последовательность объектов. Вернуть номер объекта, который удовлетворяет заданному условию. Если такого нет, вернуть -1. 0 1 2 3 4 5 6 7 def search1(lst, cond): for i in range(len(lst)): if cond(lst[i]): return i return -1;
Частный случай В частном случае ищем объект, совпадающий с заданным (какэто делает метод list.index ) • def search2(lst, key): • for i in range(len(lst)): • if lst[i] == key: • return i • return -1;
Обобщение поиска • def search(lst, key = 0, cond = None): • if cond == None: • cond = lambda x: x == key • for i in range(len(lst)): • if cond(lst[i]): • return i • return -1; >>> t = [2,43,5,67,88,7] >>> print(search(t, 99)) >>> -1 >>> print(search(t, 88)) >>> 4 >>> print(search(t, cond = lambda x: x > 50)) >>> 3
Бинарный поиск Условие может быть не качественным, а количественным. В этом случае элементы можно упорядочить по значению условия, в частности, по величине элементов. Если последовательность упорядочена, то применяют бинарный поиск, а не последовательный. Игра в 20 вопросов. Некто задумал целое число до 1 млн. Как угадать его, задав не более 20 вопросов с ответами "да" и "нет"?
Алгоритм бинарного поиска • Сначала областью поиска является вся последовательностьL . • Пока область поиска не пуста, повторяем: • Поделим область поиска пополам и определим средний индекс – m. • Если K == L[m], задача решена, ответ – m. • Если K<L[m], продолжим поиск в левой половине. • Если K>L[m], продолжим поиск в правой половине. • Когда область поиска опустела, возвращаем -1 L – исходная последовательность K – искомое значение b – начало области поиска e – конец области поиска m – индекс середины области поиска
Функция binarySearch() def binarySearch(lst, key): b, e = 0, len(lst) while b < e: m = (b + e)// 2 if lst[m] == key: return m if key > lst[m]: b = m + 1 else: e = m - 1 return -1 l = [0,3,5,7,10,20,28,30,45,56] print(binarySearch(l, 5)) print(binarySearch(l, 0)) print(binarySearch(l, 56)) print(binarySearch(l, 25)) print(binarySearch(l, 100))
Самостоятельно • Объявите функцию, которая выполняет поиск в последовательности по условию, зависящему от двух аргументов:значения элемента и номера элемента в последовательности, например, "найти первое простое число, стоящее на нечетной позиции". • Найти индекс наименьшего числа в последовательности. • Сколько вопросов достаточно, чтобы угадать целое число из отрезка [1, n]?
Сортировка • Задача сортировки • Имеется изменяемая последовательность и функция сравнения двух элементов: • < 0, если x < y • f (x, y)= 0, если x = y • > 0, если x > y • Необходимо переставить элементы таким образом, чтобы для любых двух соседних элементов a и b f(a, b) < 0. • В частном случае, f(x, y) = x - y, сортировка происходит по возрастанию значений элементов.
Обменная сортировка • Алгоритм • Повторяем n – 1 раз: • Проходим последовательность и сравниваем пары соседних элементов a b . • Если f(a, b) > 0, меняем элементы местами. • Улучшения • С каждым повторением укорачивать проход на 1 шаг. • Если за проход не сделано ни одного обмена, последовательность отсортирована.
Сортировка выбором • В исходном списке имеются две части – упорядоченная и хаотическая Изначально упорядоченная часть состоит из одного первого элемента. В процессе сортировки упорядоченная часть растет, а хаотическая сокращается. • Алгоритм • Повторяем, пока существует хаотическая часть (т.е. n – 1 раз): • В хаотической части списка находим наименьший элемент. • Перемещаем его в начало хаотической части.
Самостоятельно • Запрограммировать алгоритм обменной сортировки с улучшениями в виде функции bubbleSort(). • Запрограммировать алгоритм сортировки выбором в виде функции selectSort. • Написать графическую программу для демонстрации работы алгоритмов сортировки. В качестве модели использовать список чисел.
Рекурсия Лекция 15
Рекурсия Рекурсия по-латыни "повторение".
Рекурсивные объекты Факториал: 0! = 1 N! = (N-1)! * N НОД: НОД(a, 0) = a НОД(a, b) = НОД(b, a%b) Ряд Фибоначчи: 1, 1, 2, 3, 5, 8, 13, … F1 = F2 = 1 Fn = Fn-1 + Fn-2 Слово ::= Буква Слово ::= Слово Буква Имя ::= Буква Имя ::= Имя Буква Имя ::= Имя Цифра Треугольник Серпинского Фрактал Папоротник
Рекурсивные функции Рекурсивные функции сами собой получаются из рекурсивных определений. Факториал: 0! = 1 N! = (N-1)! * N def fact(n): if n == 0: return 1 return fact(n-1) * n НОД: НОД(a, 0) = a НОД(a, b) = НОД(b, a%b) def gcd(a, b): if b == 0: return a return gcd(b, a%b) def isWord(s): if len(s) == 1: return s.isalpha() return isWord(s[:-1]) and s[-1].isalpha() Слово ::= Буква Слово ::= Слово Буква
Глубина рекурсии Каждый раз при вызове функции в памяти сохраняются все ее параметры и локальные переменные. Область памяти, где это происходит, называется стек вызовов. Проектируя рекурсивный алгоритм, нужно оценивать, сколько одновременно загруженных функций может оказаться в стеке вызовов (глубина рекурсии). Стек вызовов ограничен и сделано это только для того, чтобы быстрее обнаруживать бесконечную рекурсию. • Замечание. В Питоне максимально допустимую глубину рекурсии можно узнать при помощи функции sys.getrecursionlimit() и изменить при помощи функции sys.setrecursionlimit(). Стек
Бинарный поиск Итерация Рекурсия def binarySearch1(lst, k): b, e = 0, len(lst) while b < e: m = (b + e)// 2 if k == lst[m]: return m if k > lst[m]: b = m + 1 else: e = m - 1 return -1 def binarySearch(lst, k): if lst == []: return -1 m = len(lst) // 2 if k == lst[m]: return m if k > lst[m]: return binarySearch(lst[m+1:], k) return binarySearch(lst[0:m], k)
Рекурсия или итерация? Рекурсию всегда можно заменить итерацией и наоборот. Пример. Определить величину наименьшего числа в списке. def minList(list): m = list[0] for i in range(1, list(n)) if list[i] < m: m = list[i] return m def minList(list): if len(list) == 1: return list[0] m = minList(list[1:]) return list[0] if list[0] < m else m
Более общий случай Итерация # начало def i(): return 1 # условие def u(x): return x < 10 # действие def f(x): print (x) return x + 1 x = i() while u(x): x = f(x) Рекурсия def R(x): if u(x): return R(f(x)) x = i() R(x)
Плохая рекурсия Определение ряда Фибоначчи: f1 = f2 = 1; fi = fi-1 + fi-2; def fib(i): if i < 3: return 1 return fib(i-1) + fib(i-2) fib(5) fib(4) fib(3) fib(3) fib(2) fib(2) fib(1) fib(2) fib(1)
Задача о ханойских башнях Имеются три стержня. На первый стержень нанизаны n дисков в виде пирамидки. Переложить по одному все диски на третий стержень, используя второй стержень как промежуточный. Ни в какой момент перекладывания больший по диаметру диск не должен оказаться над меньшим.
Алгоритм • Переложить пирамидку из n-1 диска с первого стержня на второй, используя третий стержень как промежуточный. • Переложить один диск, оставшийся на первом стержне, на третий. • Переложить пирамидку из n-1 диска со второго стержня на третий, используя первый стержень как промежуточный.
Программа res = [] def hanoy(n, s1, s3): if n: s2 = 6 - s1 - s3 hanoy(n - 1, s1, s2) res.append((s1, s3)) hanoy(n - 1, s2, s3) hanoy(3, 1, 3) print(res) def hanoy(n, s1, s3, ac): if n > 0: s2 = 6 - s1 - s3 ac = hanoy(n - 1, s1, s2, ac) ac.append((s1, s3)) ac = hanoy(n - 1, s2, s3, ac) return ac res = hanoy(3, 1, 3, []) print(res) С побочным эффектом Без побочного эффекта
Самостоятельно • Объявить рекурсивную функцию, которая получает строку и возвращает True, если входная строка является именем в языке Питон, и возвращает False, если строка именем не является. • Предложите рекурсивный алгоритм: • для суммирования элементов списка; • для обращения строки; • для последовательного поиска; • Сделать плохую рекурсию при вычислении n-го члена ряда Фибоначчи хорошей. • Нарисовать треугольник Серпинского заданной глубины. Использовать tkinter и объект Canvas. import tkinter from tkinter import * root = Tk() canvas = Canvas(master=root, width = 800, height = 800) canvas.pack() def t(a, z): q3 = pow(3, 0.5) b = (a[0] - z / 2, a[1] + z * q3 / 2) c = (a[0] + z / 2, a[1] + z * q3 / 2) d = (a[0] - z / 4, a[1] + z * q3 / 4) f = (a[0] + z / 4, a[1] + z * q3 / 4) e = (a[0], a[1] + z * q3 / 2) # canvas.create_polygon(a, b, c, fill = "yellow", outline = "black") canvas.create_polygon(d, e, f, fill = "yellow", outline = "black") return (d, f) def ts(a, z): if z < 4: return (d, f) = t(a, z) ts(a, z / 2) ts(d, z / 2) ts(f, z / 2) ts((400, 0), 700) root.mainloop()
Оценка алгоритмов Лекция 16
Оценка временной сложности Временную сложность алгоритма измеряют не в минутах или секундах, а в виде зависимости количества выполненных команд от количества входных данных. Иными словами, оценкой временной сложности является не число, а функция t = f(n).
Оценка последовательного поиска def search(lst, cond): i = 0 • while i < len(lst): • if cond(lst[i]): return i i += 1 return -1; n – длина списка t(n) = 3 * n + 2 – в худшем случае t (n) = c * n
Оценка обменной сортировки • Повторяем n – 1 раз: • Повторяем n – 1 раз: • Сравниваем пары соседних элементов a b . • Если f(a, b) > 0, меняем элементы местами. n – длина списка t(n) = (n – 1) * (n – 1) * с t (n) = c*n2
Бинарный поиск • Сначала областью поиска является вся последовательностьL . • Пока область поиска не пуста, повторяем: • Поделим область поиска пополам и определим средний индекс – m. • Если K == L[m], задача решена, ответ – m. • Если K<L[m], продолжим поиск в левой половине. • Если K>L[m], продолжим поиск в правой половине. • Когда область поиска опустела, возвращаем -1 t(n) = c + t(n / 2) - 1-ый шаг t(n / 2) = с + t(n / 4) - 2-ой шаг t(n) = c + t(n / 2) = 2*с + t(n / 4) = 3*c + t(n / 8) = …= k*c + t(n / 2k) -k-ый шаг Если на k-ом шаге область поиска составит 1 элемент, значит n = 2k или k = log2(n) t(n) = k*c + t(n / 2k) = log2(n) * c + t(1) = c * log2(n) + c0 t (n) = c * log2(n)
Последовательный или бинарный? Соотношение t(103) = 1 секозначает, что за 1 сек алгоритм сокращает область поиска в 1000 раз.
c*f(n) g(n) n n0 «О» большое O(f(n)) – обозначение множества функций переменной n, которые с ростом n растут не быстрее, чем f(n). Слова «g растет не быстрее, чем f» означают, что начиная с некоторого n0, для всех n > n0 g(n)/f(n) <= C, где C– константа. С точки зрения «O большого» все линейные функции одинаковы, и n, и 0.001*n, и 1000* n. Поэтому сложность линейного поиска обозначают как O(n), поскольку функция t(n) = n выглядит проще других. Сложность двоичного поиска обозначают O(logn). Постоянная сложность - O(1).
Иерархия сложности Есть несколько простых функций, которые часто используются при оценке временной сложности. Перечислим их по возрастанию скорости роста. lgn < n½ < n < n2 < nС < 2n < n!
Самостоятельно • Оцените сложность алгоритма поиска наименьшего числа в массиве. • Оцените сложность алгоритма сортировки выбором. • Оцените сложность алгоритма определения простоты числа. • По легенде изобретатель шахмат попросил у султана в качестве вознаграждения положить на первую клетку шахматной доски одно пшеничное зерно, на вторую два, на третью 4, и т.д., на последнюю – 263 зерен. Сколько миллиардов тонн пшеницы получил бы изобретатель шахмат, если одно зерно весит 0.1 г? • Посчитайте, чему равно 65!. Ответ дайте в показательной форме с точностью 10 знаков. • Логарифм от n по основанию 2 растет так же быстро, как и логарифм от n по основанию 3. Одинаково ли быстро растут функции 2n и 3n?
Быстрая сортировка Лекция 17
Сортировка в ограниченном диапазоне Задача Имеется список символов. Отсортировать его в алфавитном порядке. Решение Заведем последовательность счетчиков – по одному на каждый символ. Пройдем по списку символов и сосчитаем количество каждого символа. Перепишем список заново, руководствуясь показаниями счетчиков Cложность O(n)
Быстрая сортировка Хоара Чарльз Энтони Ричард Хо́ар - английский ученый в области информатики и вычислительной техники. Вместе с Дейкстрой разрабатывал структурное программирование и методы доказательства правильности программ. Работал над Algol-60. Работал в МГУ над машинным переводом под руководством Колмогорова. Знает русский. Получил премию Тьюринга 1980 г. – за достижения в разработке языков программирования. Еще в 1960 году Чарльз Хоар предложил алгоритм сортировки, оценка которого близка к теоретическому пределу.
Алгоритм частичной сортировки Задача частичной сортировки – выбрать число из массива (можно самое первое) и добиться того, чтобы все числа, меньшие выбранного, предшествовали ему, а все числа, большие выбранного, следовали за ним. • Выбираем первое число. • Идя слева вдоль массива, находим число, которое больше выбранного. • Идя справа, находим число, которое меньше выбранного. • Меняем найденные числа местами. • Повторяем пункты 2-4, продолжая движение, пока левый текущая позиция просмотра не окажется правее правой текущей позиции. • Меняем местами выделенное число и число в правой текущей позиции. 50 70 60 40 10 80 20 50 20 60 40 10 80 70 50 20 10 40 60 80 70 40 20 10 50 60 80 70
Алгоритм сортировки Хоара Изложенный выше алгоритм частичной сортировки требует лишь одного прохода массива, поэтому его сложность – O(n). • Выполняем частичную сортировку, в результате чего массив делится на две части: в левой части находятся числа меньше выбранного, в правой части – числа больше выбранного. • Если левая часть состоит более чем из одного элемента, применяем к ней алгоритм быстрой сортировки. • То же самое делаем с правой частью.
Оценка сортировки Хоара На каждом уровне рекурсии частичной сортировке подвергается в общей сложности не более, чем весь массив, т.е. не более, чем n чисел. Частичная сортировка выполняется алгоритмом с временной сложностью O(n). Уровней рекурсии – log2(n), т.к. длины сортируемых участков каждый раз сокращаются примерно вдвое. Следовательно, временная сложность быстрой сортировки – O(n * log n). O(n * log n)
Самостоятельно Запрограммировать: частичную сортировку, быструю сортировку Хоора,