Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на языке Python. Учебное пособие.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
курсией, но ключом к пониманию рекурсии является глубокое понимание того, как работает стек.
Кроме того, стеки используются для реверсирования. Все, что кладется
в стопку, выходит в обратном порядке, когда ее снимают. Например, предпо­ложим, нужно перевернуть строку. Ожно было бы перевернуть строку, сначала поместив ее в стек, а затем извлекая ее, как в следующем фрагменте:
my_string = “Hello”
stack = s()
for c in my_string:
stack.push(c) new_string = “” for c in stack:
new_string += c print new_string >> olleH
В этом примере просмотрели каждый символ в слове «Hello» и поместили
его в стек. Затем перебрали стек и взяли из него все, что только что поместили в него, и сохранили порядок в переменной new_string. К тому времени, ко­гда дойдем до последней строки, слово будет перевернуто и программа напеча­тает «olleH».
Нужно написать программу, которая проверяет строку на наличие сбалан-
сированных скобок. Так, например, «(Hello)» пройдет, но «(Hello» не пройдет; «()()()» пройдет, но «()(» не пройдет. Сначала это выглядит просто, пока не по­лучим что-то вроде этого "((()((()((()((((()))))".
Как отслеживать все скобки? Ключ к решению этой проблемы заключается
в использовании стека. Каждый раз, когда мы сталкиваемся с открытой скоб­кой, помещаем ее в стек. Если сталкиваемся с закрытой скобкой, вытаскиваем из стека открытую скобку.
def balanced_paren(expression):
stack = []
for c in expression:
if c == ‘(‘:
161
stack.append(c)
elif c == ‘)’:
if len (stack) < 1:
return False
stack.pop()
if len (stack) == 0:
return True
return False
Если скобки сбалансированы, стек будет пуст после цикла, и можно вер­нуть «True»; если скобки не сбалансированы, а попытаться извлечь данные из пустого стека, то это вызовет ошибку. Вот почему, когда сталкиваемся с закры­той скобкой, должны сначала убедиться, что стек не пуст, прежде чем извлечь ее из стека. Если сталкиваемся с закрытой скобкой, а стек пуст, и знаем, что скобка не сбалансирована, можно вернуть «False». Если в конце цикла в стеке еще остались открытые скобки, также можно вернуть «False».

Связанные списки

Связанный список состоит из последовательности узлов, каждый из кото­рых указывает на следующий узел в списке. Вот отличная метафора для раз­мышлений о связных списках. Представьте, что вы в мафии и хотите отдавать приказы так, чтобы никто не знал, кто вы такой. Можно создать структуру, в которой анонимно отдаете приказ следующему человеку в цепочке подчине­ния. Вы знаете, кто они, но они не знают вас, а знают только следующего чело­века, которому они должны отдать приказ. Человек, которому они отдают сле­дующий приказ, не знает их, а знает только следующего человека, который по­лучит приказ. Эта цепочка информации и есть односвязный список. В одно­связном списке все узлы в списке знают только о следующем узле. Они не от­слеживают узел позади себя. Можно добраться до каждой части данных в свя­занном списке, начав с начала списка и перемещаясь один за другим к каждому следующему узлу. Двусвязный список – это то же самое, за исключением того, что каждый узел отслеживает узел, стоящий за ним, в дополнение к отслежива­нию следующего узла (т.е. двусвязный список отслеживает как следующий элемент, так и предыдущий). Связанный список также может быть упорядочен-
162
ным или неупорядоченным. Далее рассмотрим неупорядоченный односвязный и двусвязный список:
class Node:
“““Class representing one node in a linked list.””” def __init__(self, data):
self.data = Node(data) self.next = None
class LinkedList:
“““Class representing a linked list data structure””” def __init__(self, head):
self.head = head
def add(self, data):
“““Add a new node to the linked list.””” previous_head = self.head self.head = Node(data) self.head.next = previous_head
Класс связанных списков прост. Он хранит заголовок связанного списка и имеет метод под названием «add» для добавления нового узла в список. По­скольку список неупорядочен, нам все равно, куда мы поместим следующий узел, поэтому проще всего поместить его в начало списка. Метод add сохраняет текущий заголовок в переменной previous_head, чтобы не потерять его из виду, создает новый узел с переданными данными и устанавливает новый узел в ка­честве заголовка списка. Узел, который раньше был головным, затем устанав­ливается в качестве следующего узла после нового головного. Теперь можно создать связанный список и добавить в него данные:
linked_list = LinkedList() linked_list.add(1) linked_list.add(2) linked_list.add(3)
Чтобы получить данные из связанного списка, начинайте с головы и посе­щайте каждый узел, пока не наткнетесь на следующий узел, который не ра­вен нулю.
163
node = linked_list.head
while node:
print node.data
node = node.next >> 3 >> 2 >> 1
Можно изменить односвязный список на двусвязный, продолжая изменять класс узла, чтобы отслеживать узел, стоящий за ним.
class Node:
“““Class representing one node in a linked list.”””
def __init__ (self, data):
self.data = data
self.next = None
self.previous = None class LinkedList :
“““Class representing a linked list data structure”””
def __init__ (self, data):
self.head = Node(data)
def add(self, data):
“““Add a new node to the linked list.”””
previous_head = self.head
self.head = Node(data)
previous_head.previous = self.head
self.head.next = previous_head
Новый связанный список такой же, как и предыдущий, за исключением то­го, что теперь каждый узел знает узел перед ним и позади него.

Массивы

Массив – это реализация абстрактного типа данных списка. Каждый фрагмент данных в массиве должен быть одного типа. Так, например, есть мас­сив, состоящий из строк или целых чисел, но не может быть массива, состояще­го из обоих типов данных. Встроенная в Python структура данных списка реа­лизована внутри как массив указателей.
164

Бинарные деревья

Дерево – это еще одна структура данных, которая получила свое название из-
за того, что выглядит как дерево. Отличным примером древовидной структуры данных является файловая система на компьютере, реализованная с использовани­ем дерева. Существует множество различных видов деревьев, таких как красные и черные деревья, деревья AVL и бинарные деревья. Построим бинарное дерево.
Двоичное дерево состоит из узлов, содержащих данные. У каждого узла может быть левый дочерний элемент и правый дочерний элемент. Точно так же, как связанный список указывает на следующий элемент в списке, узел мо­жет указывать на два других узла, называемых левым и правым дочерними элементами. Подобно двусвязному списку, левый и правый потомки отслежи­вают своего родителя. Вот пример бинарного дерева, реализованного на Python:
class Node:
“““Binary tree node.”””
def __init__(self, value):
self.value = value
self.left_child = None
self.right_child = None class BinaryTree:
“““This class represents a binary tree data structure.”””
def __init__(self, root):
”””
:param root: Binary tree node.
”””
self.root = root
Бинарное дерево просто состоит из корня.

Поиск в ширину и в глубину

Если нужно посетить каждый узел бинарного дерева, можно использовать два алгоритма поиска: поиск в ширину и поиск в глубину. Если представить себе дерево, состоящее из строк и столбцов, то при поиске в ширину нужно по­сетить каждую строку одну за другой, тогда как при поиске в глубину – посе-
165
тить каждый столбец один за другим. Используем бинарное дерево из преды­дущего примера, чтобы создать дерево для обхода:
tree = BinaryTree(“a”) tree.left = TreeNode(“b”) tree.right = TreeNode(“c”) tree.left.left = TreeNode(“d”) tree.right.right = TreeNode(“e”)
Теперь можно написать функцию, которая принимает дерево в качестве пара-
метра и выполняет поиск в ширину, выводя значение каждого посещаемого узла:
def breadth_first (tree):
””” Breadth first search of binary tree print out each node. :param root: BinaryTree ”””
current_level = [root] next_level = []
while current_level:
for node in current_level:
print node.val if node.left_child:
next_level.append(node.left_child)
if node.right_child:
next_level.append(node.right_child) current_level = next_level next_level = []
Используем список current_level для отслеживания всех узлов на уровне
дерева, в котором сейчас находимся. Когда текущего уровня больше нет, алго­ритм останавливается. Дерево выглядит так:
166
«current_level» начинается с [a], перемещается в [b, c], переходит
и могут хранить ключи и значения
в [d, e] и, наконец, становится пустым списком [], на этом алгоритм завер­шается. Можно сделать это, отслеживая следующий уровень узлов в списке
next_level.
Перед циклом while добавляем корень дерева в current_level, затем
сообщаем циклу, чтобы он продолжался, пока current_level не будет пуст. В цикле while перебираем каждый узел в current_level, выводя его. За- тем проверяем, есть ли у узла дочерние элементы. Если это так, добавляем эти дочерние элементы в список next_level. В конце цикла for переключаем два списка так, чтобы список current_level устанавливался на список next_level, а список next_level становился пустым. Это позволяет пере­ходить с одного уровня на другой. Наконец, когда достигнем последнего уров­ня, у узлов не будет дочерних узлов, next_level будет пустым, current_level будет установлен на next_level, и поскольку он пуст, ал­горитм остановится.
Поиск в глубину выполняет поиск по дереву вертикально, а не пересекает
его по горизонтали. В дереве из предыдущего примера будет произведен поиск “a”, “b”, “d”, “e”, “c”. Таким образом можно реализовать поиск в глубину с по­мощью рекурсии.

Хэш-таблицы

В главе «Контейнеры» рассматривался встроенный в Python тип данных словаря. Словари полезны, потому что он и невероятно быстро получать и устанавливать данные. Напомним, словари со­поставляют ключи со значениями. Например, можно добавить ключ «super_computer» в словарь со значением «Watson» с помощью следую- щего кода:
my_dictionary = {} my_dictionary[“super_computer”] = “Watson”
Теперь можно получить ключ «super_computer» с помощью:
print my_dictionary[“super_computer”] >> Watson
167
Самое удивительное в словарях то, что они могут устанавливать и полу-
чать данные за постоянное время. Неважно, сколько строк данных в словаре. В нем может быть миллиард строк, но при этом добавление и извлечение зна­чения «super_computer» реализуется за фиксированное время O(1).
Python использует хэш-таблицу для реализации своего словаря. Хеш-
таблица – это структура данных, которая использует список и хэш-функцию для хранения данных за время O(1). Когда добавляете значение в хеш-таблицу, она использует хеш-функцию, чтобы получить индекс в списке для хранения данных. Когда извлекаете данные из хеш-таблицы, она использует ту же хеш­функцию для поиска индекса, чтобы получить его из списка. В этом примере хеш-таблица будет хранить только числа. Хэш-функция вернет результат числа по модулю одиннадцать. Так, например, хэш-функция для числа 1 вернет еди­ницу, поэтому сохраняется число 1 в индексе один в списке. Хеш-функция для числа 5 вернет пять, поэтому сохраняется число 5 в индексе пять в списке. Вот пример хеш-таблицы:
class HashTable :
“““Hash table data structure”””
def __init__ ( self ):
self .list = [ None ] * 11
@ staticmethod def hash (n):
”””
:param n: int :return : return index in list to store number.
”””
return n % 11
def set ( self , n , v):
”””
:param n: int :param v: can be any type.
”””
self .list[ self .hash(n)] = v
def get ( self , n):
”””
168
:param n: int
:return : int value from list
”””
return self .list[ hash (n)] hash_table = HashTable() hash_table.set(1, ‘Disrupted’) hash_table.set(5, ‘HubSpot’)
print(hash_table.get(1)) print(hash_table.get(5))
Это упрощенный пример, в котором явно есть проблемы. Однако цель со­стоит в том, чтобы проиллюстрировать, как работает хэш-таблица.
Таким образом, хеш-таблица – это структура данных, в которой все эле­менты хранятся в виде пары ключ-значение, где
• ключ – уникальное число, которое используется для индексации значений;
• значение – данные, которые с этим ключом связаны.
В хеш-таблице обработка новых индексов производится при помощи клю­чей, а элементы, связанные с этим ключом, сохраняются в индексе. Этот про­цесс называется хешированием.
169

ЗАКЛЮЧЕНИЕ

К сожалению, ограниченный объем пособия не позволяет осветить еще не­сколько важных тем, но автор надеется, что в ближайшее время будет написано еще одно пособие по Python, где будут рассмотрены не вошедшие в данное по­собие вопросы. Для усвоения изложенного материала приведем несколько практических рекомендаций начинающим программистам.
1. Ознакомьтесь со сборником методических рекомендаций по написанию
кода на Python. На английском языке документ PEP8 доступен по адресу
https://peps.python.org/pep-0008/, а на русском – https://defpython.ru/pep8
2. Используйте мощную интегрированную среду разработки (IDE). В каче­стве одной из наиболее продвинутых IDE автор рекомендует использовать обо­лочку PyCharm, разработанную компанией JetBrains. Руководство по использова­нию PyCharm доступно по адресу
start-guide.html на сайте JetBrains.
3. Пользуйтесь логированием – практикой записи данных во время работы программного обеспечения. Python поставляется с модулем logging, позволяю- щим записывать процессы работы в консоль или файл. С модулем logging мож­но ознакомиться на сайте https://docs.python.org/3/howto/logging.html
4. Активно тестируйте свой код с помощью фреймворка модульного те­стирования Python unittest. Прочитать об этом можно на странице
https://docs.python.org/3/library/unittest.html
5. Анализируйте код сообществом программистов на сайте Code Review,
www.jetbrains.com/help/pycharm/2016.1/quick-
.
.
.
расположенном по адресу https://codereview.stackexchange.com/
6. Найти работу для Python-программиста можно в разделе Job Board на сайте Python www.python.org/jobs
7. Получите начальный опыт работы. Для этого можно начать деятель­ность в качестве фрилансера либо в области открытого программного обеспе­чения open source.
8. Обретя опыт программирования, можно записываться на собеседование. Для этого используйте сайт LinkedIn. Чтобы подготовиться к собеседованию, можно использовать ресурс LeetCode: https://leetcode.com/
9. Не переставайте совершенствоваться. Рекомендую к прочтению статью Дэвида Биттоу «НПП: Не переставай программировать» на сайте
.
.
170
.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]