В этой статье мы рассмотрим нелинейные структуры данных в Python, такие как Tree, Hash-map и Graph.
Предварительное условие
Вы должны иметь базовые знания языка программирования Python.
Весь код для этой статьи доступен в моем репозитории GitHub.
В первой части этой статьи мы рассмотрели основы структур данных и линейных структур данных, таких как стек, очередь, связанный список, массивы.
Теперь в этой статье мы рассмотрим нелинейные структуры данных, такие как Tree, Hash-map и Graph.
Что такое нелинейные структуры данных?
Структура данных, в которой данные не хранятся в линейном формате, таком как массивы или стек. Этот тип структуры данных известен как нелинейные структуры данных.
Нелинейная структура данных - это рекурсивный тип структуры данных.
В нелинейных структурах данных мы не можем перемещаться по структуре данных в линейном формате; есть несколько уровней для хранения данных.
Реализация нелинейных структур данных немного сложнее, чем линейных структур данных.
Существуют различные типы нелинейных структур данных, но мы рассмотрим -
- Хеш-карта
- Дерево
- График
Нелинейные структуры данных
1. Хеш-карта -
По сути, Hash-map - это тип структуры данных пары ключ-значение, в которой ключ вычисляется с использованием функции Hash, а затем эти данные сохраняются как ключ (вычисляется функцией Hash).
Теперь сначала мы должны понять, что такое хеш-функция. Таким образом, хеш-функция - это функция, которая будет вычислять ключ с использованием любой математической функции, чтобы мы могли хранить данные в ключевой позиции. Здесь математическая функция может быть чем угодно. Не существует фиксированной функции для поиска ключа с помощью хеш-функции.
Но основным недостатком этой хеш-функции является то, что хеш-функция возвращает один и тот же ключ для двух разных данных. Таким образом, в этой ключевой позиции произойдет столкновение. Это главный недостаток хеш-функций.
Есть разные способы справиться с этим столкновением. Мы справимся с этим, создав список для этого ключа, если хеш-функция возвращает один и тот же ключ для разных данных. А затем добавьте данные в список этого конкретного ключа.
Хеш-таблицы обычно реализуются с использованием массивов.
Основные операции, которые вы можете выполнять на Hash-карте
- Insert - вставить элемент в хеш-карту
- Удалить - удалить элемент из хеш-карты
- Get Item - получить элемент из хеш-карты с помощью ключей
Реализация хеш-карты с использованием массивов -
class Hashmap: def __init__(self): self.MAX = 100 self.arr = [[] for i in range(self.MAX)] def get_hash(self,key): hash_key = 0 for char in key: hash_key += ord(char) return hash_key % 100 def add(self,key,value): hash_key = self.get_hash(key) found = False for index,element in enumerate(self.arr[hash_key]): if element[0] == key and len(element) == 2: self.arr[hash_key][index] = (key,value) found = True break if not found: self.arr[hash_key].append((key,value)) def get(self,key): hash_key = self.get_hash(key) for element in self.arr[hash_key]: if element[0] == key: return element[1] def __delitem__(self,key): hash_key = self.get_hash(key) for index,element in enumerate(self.arr[hash_key]): if element[0] == key: del self.arr[hash_key][index] def print_hm(self): values = [i for i in self.arr if i != []] return values
Объяснение кода -
- Прежде всего, мы должны создать класс с именем
Hashmap
, и в этом классе мы должны инициализировать список с именемself.arr
размераself.MAX
. И в этомself.arr
у нас будет пустой вложенный список (Список в списке, поэтому, еслиself.MAX
равно 10, вself.arr
будет 10 пустых списков). - Теперь мы реализуем самый важный метод в структурах данных хэш-карты -
get_hash
. Этот метод принимает ключ в качестве аргумента и вычисляет и возвращаетhash_key
с помощью функцииord
(эта функция возвращает числовое значение для заданного строкового ключа). Таким образом, используя этот метод, вычисляются всеhash_key
. - Теперь следующий метод -
add
, который используется для добавления элемента в хеш-карту. Этот метод принимает в качестве параметраdata
иkey
. Во-первых, этот метод вычислит хеш-значение (или хеш-ключ) для данного параметраkey
с помощью функцииget_hash
. После этого мы пройдемся по всем спискам, чтобы найти этуhash_key
позицию и проверить, есть ли какие-либо элементы, добавленные в этойhash_key
перед этими данными. И если в данном спискеhash_key
есть элемент, то мы добавим новыйdata
как форму кортежа в этот список. Этот кортеж будет включатьkey
иdata
. И если в этом hash_key нет данных, мы просто добавим эти данные в этот списокhash_key
. - Теперь следующий метод -
get
, который используется для извлечения элементов из хэш-карты. Этот метод приметkey
в качестве параметра. Во-первых, этот метод вычислит хеш-значение (илиhash_key
) для данного ключевого параметра с помощью функцииget_hash
. После этого мы пройдемся по списку даннойhash_key
позиции и проверим, есть ли какие-либо элементы в этом списке, и если есть(key, data)
кортеж, тогда он вернетdata
для данногоkey
параметра. - Далее идет
__delitem__
. Этот метод принимает ключ в качестве параметра и, как и все другие методы, вычисляетhash_key
с использованием методаget_hash
. После этого он пройдется по списку с заданнымhash_key
, найдет кортеж с заданным параметромkey
и удалит этот элемент. - И
print_hm
method не требует пояснений, поэтому не нужно его описывать.
2. Дерево—
Древовидная структура данных - это иерархический тип структуры данных, в котором есть главный корневой узел, который имеет два поля - данные и дочерние элементы. В поле данных он хранит данные, а в дочернем поле он хранит свои дочерние узлы.
Итак, в основном дерево состоит из двух основных частей - корневого узла и поддерева.
Как следует из названия, деревья имеют ту же структуру, что и настоящее дерево, но только в перевернутом виде.
Деревья - это очень широкая структура данных, и я не могу описать все о них в этом блоге.
Есть много видов деревьев -
- Общее дерево (это дерево, у родительского узла которого может быть столько дочерних элементов, сколько мы хотим добавить).
- Двоичное дерево (в этом типе дерева родительский узел может иметь только 2 или менее 2 дочерних).
- Двоичное дерево поиска (это дерево такое же, как двоичное дерево; единственная разница в том, что левый дочерний узел родительского узла может иметь значение меньше, чем его родительский узел, а правый дочерний узел может иметь значение больше, чем его родительский).
Есть много других деревьев, таких как красно-черное дерево, AVL-дерево, N-арное дерево и т. Д.
Основные операции, которые вы можете выполнять с деревом -
- Добавить ребенка (добавить ребенка к узлу)
- Удалить ребенка (удалить ребенка из узла)
- Печать (Распечатать все дерево)
- Get Level (Получить уровень узла)
class TreeNode: def __init__(self,data): self.data = data self.parent = None self.children = [] # For Adding child to tree def add_child(self,child): child.parent = self self.children.append(child) # For Getting level to any node def get_level(self): level = 0 p=self.parent while p: p=p.parent level+=1 return level # For Printinf whole tree def print_tree(self): spaces = " " * self.get_level() * 3 prefix = spaces+"|---" if self.parent else '' print(prefix+self.data) if self.children: for child in self.children: child.print_tree() # For Deleting child from tree def delete_child(self,child): if child in self.children: self.children.remove(child) print(f"{child.data} Removed from f{child.parent.data}") else: print(f'There is no {child.data} in Tree') #Example for making code explanation Easy def electronincs_tree(): root = TreeNode('Electronics') hp = TreeNode('HP') mac = TreeNode('Mac-Book') dell = TreeNode('Dell') laptops = TreeNode('Laptops') laptops.add_child(hp) laptops.add_child(mac) laptops.add_child(dell) motorola = TreeNode('Motorola') apple = TreeNode('Apple') nokia = TreeNode('Nokia') mobiles = TreeNode('Mobiles') mobiles.add_child(motorola) mobiles.add_child(apple) mobiles.add_child(nokia) reconnect = TreeNode('Reconnect') lg = TreeNode('LG') samsung = TreeNode('Samsung') tvs = TreeNode('TVs') tvs.add_child(reconnect) tvs.add_child(lg) tvs.add_child(samsung) root.add_child(laptops) root.add_child(mobiles) root.add_child(tvs) root.print_tree() tvs.delete_child(lg) root.print_tree() electronincs_tree()
Объяснение кода -
- Итак, в этом объяснении кода я использовал пример, иначе было бы очень сложно объяснить, используя только код.
- Итак, сначала мы создадим класс для узла дерева. Здесь мы инициализируем три переменные
self.data
,self.parent
,self.children
, используя метод__init__
. Здесьself.data
будет хранить данные этого узла дерева.self.parent
сохранит ссылку на родительский узел этого узла дерева, аself.children
будет хранить список всех узлов дочернего дерева текущего узла дерева. - Теперь первый метод, который мы реализуем, - это
add_child
, который имеет один параметр с именемchild
. Этот метод просто добавит новый дочерний элемент к родительскому узлу дерева (например, в методеelectronincs_tree
сначала мы создали несколько экземпляров узла дерева, а затем добавляем этот узел дерева к их родительским узлам с помощью методаadd_child
). - Второй метод, который мы реализуем, - это
delete_child
, который также имеет один параметр с именемchild
. Во-первых, этот метод проверяет, есть ли у родительского узла дерева дочерний элемент, который мы хотим удалить; и если да, то он удалит его из спискаself.children
. И если нет дочернего элемента, который мы хотим удалить, он напечатает, что «нет такого дочернего элемента вself.children
списке» (например, в методеelectronincs_tree
мы удаляемlg
дерево узел изtvs
узла родительского дерева). - Теперь реализуем метод
get_level
. Этот метод не принимает никаких параметров. Этот метод вернет уровень узла дерева, по которому мы вызываем этот метод. Таким образом, этот метод будет создавать одну переменную с именемlevel
и присваивать значение0
, а затем мы создадим циклwhile
и переберем все родительские элементы данного узла дерева, и если узел дерева имеет родительский узел дерева, тогда значениеlevel
будет увеличивается на 1. И цикл завершится, когда мы достигнем вершины дерева, то есть корневого узла дерева. А затем мы вернем этотlevel
. - Теперь последний метод - это метод
print_tree
. Этот метод просто печатает все иерархии узлов, чтобы мы могли визуализировать все дерево. Этот метод определяет иерархию методомget_level
. Затем назначьте соответствующий префикс для данных, чтобы распечатать их в иерархии в соответствии с уровнем узла дерева.
3. График—
График - это нелинейная структура данных, состоящая из вершины и ребра. Здесь вершина также называется узлом. В структуре данных Graph вершина или узел содержат данные или какое-то значение для сохранения. И ребра соединяют эти вершины. И, комбинируя одну или несколько вершин и ноль или несколько ребер, мы получаем структуру данных графа.
Графические и древовидные структуры данных являются наиболее распространенными и любимыми структурами данных, которые задают при собеседовании.
Существует так много проблем, которые мы можем решить с помощью структуры данных Graph.
Например:
- Нахождение кратчайшего пути между точкой А и точкой Б.
- Поиск связи между двумя пользователями в социальных сетях (Facebook, LinkedIn).
- Поиск оптимального маршрута в компьютерных сетях.
- Поиск лучшего маршрута между двумя городами или местами.
Есть еще много проблем, которые можно решить с помощью структуры данных графа.
Кроме того, существует много типов графиков, таких как Null Graph, Infinite Graph, но мы не можем охватить все о графиках в этой статье, поэтому здесь - это ссылка, чтобы узнать больше об этом.
Объяснение кода -
- Теперь в этом коде мы создали класс с именем
Graph
, который принимает параметрыedges
as в качестве списка кортежей. Здесьroutes
variable - это пример переменной Edge, которая имеет список кортежей, в которых первое значение кортежа представляет начало края, а второе значение представляет конец этого конкретного края ((“Mumbai”, “Paris”)
Mumbai - начало этого края и Париж является концом этого края, и все города представлены в виде узлов). - Теперь в
__init__
method этого класса мы создадим переменнуюgraph_dict
, которая представляет график в виде словаря. Итак, в этом методе мы пройдемся по всем кортежам спискаedges
и соответственно создадим график. Он проверит, добавлен ли какой-либо другой элемент начала ребра кgraph_dict
variable или нет, и если он добавлен, то добавит конечное значение кgraph_dict[start]
. И если значение start добавляется впервые кgraph_dict
, тогда будет создана новая пара "ключ-значение". - Теперь второй метод, который мы реализовали в классе Graph, - это метод
get_paths
, который принимает в качестве параметров три аргумента -start
,end
,paths
. Здесьstart
обозначает начало пути, от которого мы хотим найти путь, аend
обозначает конец пути, которым путь должен заканчиваться или заканчиваться. Этот метод найдет все пути от начала до конца. - Во-первых, этот метод проверяет, совпадают ли
start
иend
variables, и если да, то он просто вернетstart
variable в качестве пути. Этот метод также проверяет, есть лиstart
node в списке ключейgraph_dict
. А еслиstart
node нет в списке ключейgraph_dict
, это означает, что нет пути отstart
кend
. Итак, тогда этот метод просто вернет пустой список. В противном случае этот метод создаст пустой список с именемpaths
. В разделеfor
loop этого метода мы пройдемся по всем смежным узлам узлаstart
и на этом соседнем узле мы будем использовать рекурсию, чтобы найти путь от соседнего узла к узлуend
, а затем добавим его к основнаяpath
переменная. И он добавит этот путь в списокpaths
, а в конце метода мы вернем списокpaths
, содержащий все пути отstart
доend
.
Заключение
Итак, в этой статье мы рассмотрели основы структур данных и нелинейных структур данных.
Если вы пропустили первую часть этой статьи, вот ссылка.
Надеюсь, вам понравилось читать и учиться.
Если вам понравилась эта статья, не стесняйтесь оставить аплодисменты и поделиться ею с другими, чтобы они тоже могли извлечь из нее уроки.