В этой статье мы рассмотрим нелинейные структуры данных в 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_hmmethod не требует пояснений, поэтому не нужно его описывать.
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, который принимает параметрыedgesas в качестве списка кортежей. Здесьroutesvariable - это пример переменной Edge, которая имеет список кортежей, в которых первое значение кортежа представляет начало края, а второе значение представляет конец этого конкретного края ((“Mumbai”, “Paris”)Mumbai - начало этого края и Париж является концом этого края, и все города представлены в виде узлов). - Теперь в
__init__method этого класса мы создадим переменнуюgraph_dict, которая представляет график в виде словаря. Итак, в этом методе мы пройдемся по всем кортежам спискаedgesи соответственно создадим график. Он проверит, добавлен ли какой-либо другой элемент начала ребра кgraph_dictvariable или нет, и если он добавлен, то добавит конечное значение кgraph_dict[start]. И если значение start добавляется впервые кgraph_dict, тогда будет создана новая пара "ключ-значение". - Теперь второй метод, который мы реализовали в классе Graph, - это метод
get_paths, который принимает в качестве параметров три аргумента -start,end,paths. Здесьstartобозначает начало пути, от которого мы хотим найти путь, аendобозначает конец пути, которым путь должен заканчиваться или заканчиваться. Этот метод найдет все пути от начала до конца. - Во-первых, этот метод проверяет, совпадают ли
startиendvariables, и если да, то он просто вернетstartvariable в качестве пути. Этот метод также проверяет, есть лиstartnode в списке ключейgraph_dict. А еслиstartnode нет в списке ключейgraph_dict, это означает, что нет пути отstartкend. Итак, тогда этот метод просто вернет пустой список. В противном случае этот метод создаст пустой список с именемpaths. В разделеforloop этого метода мы пройдемся по всем смежным узлам узлаstartи на этом соседнем узле мы будем использовать рекурсию, чтобы найти путь от соседнего узла к узлуend, а затем добавим его к основнаяpathпеременная. И он добавит этот путь в списокpaths, а в конце метода мы вернем списокpaths, содержащий все пути отstartдоend.
Заключение
Итак, в этой статье мы рассмотрели основы структур данных и нелинейных структур данных.
Если вы пропустили первую часть этой статьи, вот ссылка.
Надеюсь, вам понравилось читать и учиться.
Если вам понравилась эта статья, не стесняйтесь оставить аплодисменты и поделиться ею с другими, чтобы они тоже могли извлечь из нее уроки.