У меня есть тысячи строк 1 - 100 чисел, каждая строка определяют группу чисел и отношений среди них. Я должен получить наборы связанных чисел.
Мало Примера: Если у меня есть это 7 строк данных
T1 T2
T3
T4
T5
T6 T1
T5 T4
T3 T4 T7
Мне нужно не так медленный алгоритм, чтобы знать, что наборы здесь:
T1 T2 T6 (because T1 is related with T2 in the first line and T1 related with T6 in the line 5)
T3 T4 T5 T7 (because T5 is with T4 in line 6 and T3 is with T4 and T7 in line 7)
но то, когда у Вас есть очень большие наборы, мучительно не спешит делать поиск T (x) в каждом большом наборе и делает объединения множеств... и т.д.
У Вас есть подсказка для выполнения в этом не так способ грубой силы?
Я пытаюсь сделать это в Python.
После того, как вы построили структуру данных, какие именно запросы вы хотите выполнить к ней? Покажите нам свой существующий код. Что такое Т (х)? Вы говорите о «группах чисел», но в ваших примерах данных показаны T1, T2 и т. Д .; пожалуйста, объясни.
Вы читали это: http://en.wikipedia.org/wiki/Disjoint-set_data_structure
Попробуйте взглянуть на эту реализацию Python: http://code.activestate.com/recipes / 215912-union-find-data-structure /
ИЛИ вы можете сами придумать что-нибудь довольно простое и понятное, например
[Обновление: совершенно новый код]
class DisjointSet(object):
def __init__(self):
self.leader = {} # maps a member to the group's leader
self.group = {} # maps a group leader to the group (which is a set)
def add(self, a, b):
leadera = self.leader.get(a)
leaderb = self.leader.get(b)
if leadera is not None:
if leaderb is not None:
if leadera == leaderb: return # nothing to do
groupa = self.group[leadera]
groupb = self.group[leaderb]
if len(groupa) < len(groupb):
a, leadera, groupa, b, leaderb, groupb = b, leaderb, groupb, a, leadera, groupa
groupa |= groupb
del self.group[leaderb]
for k in groupb:
self.leader[k] = leadera
else:
self.group[leadera].add(b)
self.leader[b] = leadera
else:
if leaderb is not None:
self.group[leaderb].add(a)
self.leader[a] = leaderb
else:
self.leader[a] = self.leader[b] = a
self.group[a] = set([a, b])
data = """T1 T2
T3 T4
T5 T1
T3 T6
T7 T8
T3 T7
T9 TA
T1 T9"""
# data is chosen to demonstrate each of 5 paths in the code
from pprint import pprint as pp
ds = DisjointSet()
for line in data.splitlines():
x, y = line.split()
ds.add(x, y)
print
print x, y
pp(ds.leader)
pp(ds.group)
и вот результат последнего шага:
T1 T9
{'T1': 'T1',
'T2': 'T1',
'T3': 'T3',
'T4': 'T3',
'T5': 'T1',
'T6': 'T3',
'T7': 'T3',
'T8': 'T3',
'T9': 'T1',
'TA': 'T1'}
{'T1': set(['T1', 'T2', 'T5', 'T9', 'TA']),
'T3': set(['T3', 'T4', 'T6', 'T7', 'T8'])}
Как Jim указал выше, вы по существу ищете связанные компоненты простого неориентированного графа, где узлы - это ваши сущности (T1
, T2
и так далее), а ребра представляют парные отношения между ними. Простая реализация поиска связанных компонентов основана на поиске по принципу "сначала в ширину": вы начинаете BFS с первой сущности, находите все связанные с ней сущности, затем начинаете другой BFS с первой еще не найденной сущности и так далее, пока не найдете их все. Простая реализация BFS выглядит так:
class BreadthFirstSearch(object):
"""Breadth-first search implementation using an adjacency list"""
def __init__(self, adj_list):
self.adj_list = adj_list
def run(self, start_vertex):
"""Runs a breadth-first search from the given start vertex and
yields the visited vertices one by one."""
queue = deque([start_vertex])
visited = set([start_vertex])
adj_list = self.adj_list
while queue:
vertex = queue.popleft()
yield vertex
unseen_neis = adj_list[vertex]-visited
visited.update(unseen_neis)
queue.extend(unseen_neis)
def connected_components(graph):
seen_vertices = set()
bfs = BreadthFirstSearch(graph)
for start_vertex in graph:
if start_vertex in seen_vertices:
continue
component = list(bfs.run(start_vertex))
yield component
seen_vertices.update(component)
Здесь adj_list
или graph
- это структура данных списка смежности, в основном она дает вам соседей данной вершины в графе. Чтобы собрать его из вашего файла, вы можете сделать следующее:
adj_list = defaultdict(set)
for line in open("your_file.txt"):
parts = line.strip().split()
v1 = parts.pop(0)
adj_list[v1].update(parts)
for v2 in parts:
adj_list[v2].add(v1)
Затем вы можете выполнить:
components = list(connected_components(adj_list))
Конечно, реализация всего алгоритма на чистом Python, как правило, медленнее, чем реализация на C с более эффективной структурой данных графа. Вы можете рассмотреть возможность использования igraph или другой библиотеки графов, например NetworkX, для выполнения этой работы. Обе библиотеки содержат реализации для поиска связанных компонентов; в igraph
это сводится к следующему (при условии, что ваш файл не содержит строк с одиночными записями, принимаются только парные записи):
>>> from igraph import load
>>> graph = load("edge_list.txt", format="ncol", directed=False)
>>> components = graph.clusters()
>>> print graph.vs[components[0]]["name"]
['T1', 'T2', 'T6']
>>> print graph.vs[components[1]]["name"]
['T3', 'T4', 'T5']
Disclaimer: I am one of the authors of igraph
Вы можете смоделировать группу, используя набор
. В приведенном ниже примере я поместил набор в класс Group, чтобы упростить сохранение ссылок на них и отслеживание некоторого условного элемента «головы».
class Group:
def __init__(self,head):
self.members = set()
self.head = head
self.add(head)
def add(self,member):
self.members.add(member)
def union(self,other):
self.members = other.members.union(self.members)
groups = {}
for line in open("sets.dat"):
line = line.split()
if len(line) == 0:
break
# find the group of the first item on the row
head = line[0]
if head not in groups:
group = Group(head)
groups[head] = group
else:
group = groups[head]
# for each other item on the row, merge the groups
for node in line[1:]:
if node not in groups:
# its a new node, straight into the group
group.add(node)
groups[node] = group
elif head not in groups[node].members:
# merge two groups
new_members = groups[node]
group.union(new_members)
for migrate in new_members.members:
groups[migrate] = group
# list them
for k,v in groups.iteritems():
if k == v.head:
print v.members
Вывод:
set(['T6', 'T2', 'T1'])
set(['T4', 'T5', 'T3'])
Рассматривайте числа T1, T2 и т.д. как вершины графа. Любые два числа, появляющиеся вместе на линии, соединяются ребром. Тогда ваша задача сводится к поиску всех связных компонентов в этом графе. Вы можете сделать это, начав с точки T1, затем выполнить поиск в ширину или в глубину, чтобы найти все вершины, достижимые из этой точки. Пометьте все эти вершины как принадлежащие классу эквивалентности T1. Затем найдите следующую немаркированную вершину Ti, найдите все еще немаркированные вершины, достижимые из нее, и пометьте их как принадлежащие классу эквивалентности Ti. Продолжайте, пока все вершины не будут помечены.
Для графа с n вершинами и e ребрами этот алгоритм требует O(e) времени и места для построения списков смежности и O(n) времени и места для определения всех связных компонентов. после построения структуры графа.
Для достижения этой цели можно использовать структуру данных union find.
Псевдокод такого алгоритма выглядит следующим образом:
func find( var element )
while ( element is not the root ) element = element's parent
return element
end func
func union( var setA, var setB )
var rootA = find( setA ), rootB = find( setB )
if ( rootA is equal to rootB ) return
else
set rootB as rootA's parent
end func