При решении ряда задач возникает необходимость сохранять связи между объектами. К примеру, для поисковых задач нахождения пути - схемы маршрутов из точки А в точку Б. В социальных сетях необходимо хранить информацию о друзьях или подписчиках. На форумах присутствуют связи между пользователями и их сообщениями, сообщениями и ветками форума к которым они принадлежат. В корпоративных порталах необходимы связи пользователей и отделов корпорации, взаимоотношения коллег и так далее.
В большинстве случаев это односторонние связи - к примеру, принадлежность сообщения пользователю или пользователя к отделу в компании. Для этих связей признак направленности не нужен.
Но есть и случаи когда связи требуют признака направленности - маршруты из точки А в точку Б и из точки в Б в точку А могут кардинально отличаться, ведь существуют дороги с односторонним движением. Да и для определения взаимоотношений между пользователями - А может быть подписчиком Б, при этом Б в принципе может и не знать о существовании А.
Давайте рассмотрим самый неблагоприятный для нас случай:
Но прежде, чем начать практическое воплощение, давайте освежим воспоминания по курсу математики.
Граф - это совокупность непустого множества вершин и множества пар вершин (связей между вершинами).
Вершинами графа, в нашем случаи, будут рассматриваемые объекты (пользователи, сообщения, точки на карте, отделы компании и т.д.). В качестве ребер графа будут выступать связи между нашими объектами.
В теории графов, есть понятие «ориентированный граф». Этот тип графов описывает не только наличие связей между вершинами, но и направленность этих связей - как раз то что нам нужно. Ну а раз есть такое понятие, то математическое описание скорее всего тоже существует. Давайте не будем гадать - существует даже несколько описаний.
На мой взгляд это не самое лучший вариант представления графа. Более того, на данный момент я не вижу практической пользы от его использования. Но как сами понимает, простоя просто я еще не столкнулся с задачей, которая потребовала бы именно этого представления. Так что давайте рассмотри и его.
В чем суть. Дана матрица, столбцы которой это ребра графа, а строки - вершины графа. Каждая ячейка отображает взаимоотношение между конкретной вершиной и ребром. Возьмем для примера ячейку (10,2) - значение в этой ячейки будет определять взаимоотношение между 10ым ребром и 2ой вершиной.
Взаимоотношения определяются достаточно просто:
Размеры таблицы в базе данных:
При воплощении данного представления могут возникнуть следующие затруднения:
Достоинств представления, к сожалению, я пока назвать не могу, но они несомненно должны быть.
Я вижу возможность применения представления в виде матрицы инцидентности для следующих случаев:
На мой взгляд это менее емкий способ представления графа. В данном случаи мы имеем матрицу, столбцы и строки которой являются вершинами графа, а значения ячеек отображают связи. Так например, ячейка (10,2) отображает отношение 10ой вершины ко 2ой. Значения на главной диагонали матрицы отображают наличие петель у вершины.
Взаимоотношения определяются элементарно:
Для определения двусторонности связи потребуется проверить всего две зеркальные относительно главной диагонали ячейки.
Размеры таблицы в базе данных:
Недостатки представления:
Достоинства:
Я вижу возможность применения представления в виде матрицы смежности для следующих случаев:
На мой взгляд это самый адекватный способ хранения информации о связях. Посмотрите хотя бы на название.
Итак, у нас есть список, каждая строка которого содержит номера двух вершин. Просто, не правда ли?
Для определения двусторонности связи потребуется так же две записи из списка.
Размеры таблицы в базе данных:
Достоинства:
Мне кажется, что данное представление подходит для подавляющего большинства проектов.
В этом разделе мы рассмотрим практическую реализацию способа хранения связей. Как вы уже догадались мы будем разбирать представление «Список ребер», а так же, как я и обещал, посмотрим вариант реализации хранения связей для высоконагруженных систем.
Общий принцип остается без изменения. Рассмотрим структуру и поисковые запросы на примере таблице links.
Итак каждая запись отображает связь между двумя вершинами, причем направление определяется порядком следования идентификаторов вершин. Другими словами запись (1, 10, 2) отображает связь от 10ой вершины ко 2ой, а запись (2, 2, 10) отображает обратную связь.
Запрос на внесение связи от вершины node1 к вершине node2:
SQL: INSERT INTO `links` SET `object1` = 'node1', `object2` = 'node2';
Запрос для определения всех связей идущих от вершины node1:
SQL: SELECT `object2` FROM `links` WHERE `object1` = 'node1';
Запрос для определения всех связей идущих к вершине node1:
SQL: SELECT `object1` FROM `links` WHERE `object2` = 'node1';
Запрос на проверку наличия двухсторонней связи между вершинами node1 и node2:
SQL: SELECT COUNT(`key`) FROM `links` WHERE (`object1` = 'node1' AND `object2` = 'node2') OR (`object1` = 'node2' AND `object2` = 'node1');
Представим, что мы проектируем высоконагруженную систему и предполагается высокая нагрузка на таблицу связей. Поэтому нам придется как-то распределять нагрузку между несколькими серверами. Будем считать, что мы используем метод шардирования базы данных.
Согласно этой концепции мы будем распределять записи по разным базам данных на основании некого хеша от поля object1. Теперь давайте подумаем. Для поиска связей исходящих из вершины все просто - мы ищем по полю object1, а все эти записи хранятся в одной базе. Но что будет если мы начнем искать связи идущие к заданной вершине? Тут придется искать по полю object2, а такие записи могут быть в разных базах.
Для решения этого конфликта я предлагаю такую простую схему:
Перенос информации о направленности связей в одну запись позволит получив всего лишь одну запись сказать, есть ли связь между вершинами и какой она направленности.
А две зеркальные записи позволят нам формировать все поисковые запросы только полю object1. Соответственно разрешается проблема разноса информации по разным базам данных.
Запрос на внесение связи от вершины node1 к вершине node2:
SQL: INSERT INTO `links` SET `object1` = 'node1', `object2` = 'node2', `link1` = 1, `link2` = 0; INSERT INTO `links` SET `object1` = 'node2', `object2` = 'node1', `link1` = 0, `link2` = 1;
Эти два запроса позволят внести эквивалентные записи, но они будут распределены по разным базам данных в соответствии с алгоритмом шардирования, а следовательно все поисковые запросы по связям какой либо вершины будут производится только по одно базе данных.
Запрос для определения всех связей вершины node1:
SQL: SELECT `object2`, `link1`, `link2` FROM `links` WHERE `object1` = 'node1';
Каждая полученная запись будет нести следующую информацию:
link1 = 1 - есть связь от вершины node1 к вершине object2 link2 = 1 - есть связь от вершины object2 к вершине node1