Подтвердить что ты не робот

Что такое эффективный алгоритм для подсчета числа треугольников в графе?

Что такое эффективный алгоритм для подсчета числа треугольников в неориентированном графе) (где граф - множество вершин и ребер)? Я искал Google и читал через свой полк учебников в течение нескольких часов каждый день в течение трех дней подряд.

Это для домашнего задания, где мне нужен такой алгоритм, но его разработка не учитывает ничего при назначении. Ожидается, что мы сможем просто найти такой алгоритм из внешних ресурсов, но я нахожусь в конце своей веревки.

Для пояснения треугольник в графе представляет собой цикл длиной три. Хитрость заключается в том, что он должен работать над наборами вершин с не более чем 10000 узлов.

В настоящее время я работаю на С#, но больше забочусь об общем подходе к решению этой проблемы, чем код для копирования и вставки.

На высоком уровне мои попытки до сих пор включали:

  • Первый поиск по ширине, который отслеживал все уникальные циклы длины три. Мне это показалось для меня прекрасной идеей, но я не мог получить ее функциональной.
  • Цикл над всеми узлами в графе, чтобы увидеть, разделили ли три вершины ребро. Это слишком медленное время работы для больших наборов данных. O (N ^ 3).

Сам алгоритм является частью вычисления коэффициента кластеризации.

4b9b3361

Ответ 1

Эта проблема так же сложна, как и матричное умножение. См. Это в ссылке.

Знаете ли вы что-нибудь о графиках? Они редкие? Если нет, я не думаю, что вы будете делать намного лучше, чем O (n ^ 3).

Ответ 2

Вам понадобится первый поиск глубины. Алгоритм будет:

1) Для текущего node задайте все невидимые смежные узлы

2) для каждого из этих узлов выполните проверку глубины два, чтобы увидеть, является ли node на глубине 2 вашим текущим node с первого шага

3) отметьте текущий node как посещенный

4), сделайте каждый невидимый смежный node ваш текущий node (1 на 1) и запустите тот же алгоритм

Ответ 3

Подсчет треугольников действительно сложный, и вычислительно дорого. Может быть, это хорошая отправная точка для понимания, почему: Эффективный подсчет треугольников в больших графах с помощью разбиения на вершины на основе степеней.

Соответствующий цикл должен проверять каждый из n узлов на каждом из своих соседей (n * (n-1)) и продолжить цикл, чтобы увидеть, является ли n соседних соседей-соседей n: (n * (n-1)) (n-1) (n-1), что составляет почти 10 ^ 16 для 10000 n. С миллионом узлов эти петли становятся глупыми, но для вашего 10000 у вас не должно быть никаких проблем, если вы хотите перетащить его:)

Вы упомянули, что вы набираете код на С#, а график (который доступен для C) имеет отличный алгоритм для подсчета треугольников, написанных Gabor Csardi. Он подсчитал 1,3 миллиона треугольников в моем случайном графике из 10000 узлов и один миллион ребер за 1,3 секунды на пятилетнем ноутбуке:) Габор Царди был бы парнем, чтобы спросить:)

С точки зрения различных программных подходов, возможно, вам стоит взглянуть на данные, в которых вы храните свою сеть. Если они хранятся в матрице смежности, количество циклов фиксировано, но в краевом списке сети из трех ребер количество циклов является кратным из трех независимых от числа узлов. Вы можете задать краевой список для соседей node, не проверяя каждую комбинацию i- > j.

Я написал педагогический script в R, чтобы проиллюстрировать подходы и измерить скорость разных алгоритмов очень простым способом. Существует множество проблем скорости, присущих использованию здесь R (версия краевого списка полностью забита слишком большим количеством ребер), но я думаю, что в примере кода должны появиться идеи о том, как думать о скорости перебора, заставляя подсчет треугольников. Это в R, и не очень аккуратно, но хорошо прокомментировано. Надеюсь, вы можете сломать языковой барьер.

Все самое лучшее.

# Counting triangles in a random graph using igraph and two different
# and almost equally stupid approaches looping through the 1) adjacency
# matrix and 2) the edge-list in R.

# Use igraph and these configs
library(igraph)
V <- 100
E <-  1700

# This is the random graph that we will use
g <- erdos.renyi.game(type="gnm", n=V, p=E, directed=FALSE, loops=FALSE)

# igraph has such a fast algorythm. Long live Gabor Csardi!
benchmark <- proc.time()["elapsed"]
       triangle.count <- sum(count_triangles(g)/3)
gabor.Csardi.benchmark <- proc.time()["elapsed"] - benchmark

# For not to big networks we can plot them to get a basic feel
if(length(V(g)) < 100){
       V(g)$size <- 5
       plot(g)
}

# The adjacency matrix approach will have to deal with a crazy
# ammount of looping through pairs of matrix combinations to
# find links:

# We'll loop through each node to check it participation in triangles
# notice that a triangle ijk will be participated in by nodes
# i, j, and k, and that each of those nodes have two triangular counts.
# All in all the structures ijk, ikj, jik, jki, kij, kji are each counted
# but shall be returned as 1 triangle. We will therefore devide our
# search-result by 6 later on.

# Store our progess in this matrix to look at how we did later on
progress <- matrix(0, nrow=length(V(g)), ncol=8)

# Loop through all nodes to find triangles in an adjacency matrix
benchmark <- proc.time()["elapsed"] # Measure time for this loop
for(i in 1:length(V(g))){
       # Node i has connections to these nodes:
       i.neighbors <- as.vector( neighborhood(g, 1, nodes=i)[[1]] )
       i.neighbors <- setdiff(i.neighbors, c(i)) # i should not be part of its own neighborhood

       # for each i, tri is the number of triangles that i is involved in
       # using any j or any k. For a triangle {1,2,3}, tri will be 2 for
       # i==1, since i is part of both triangles {1,2,3} and {1,3,2}:
       tri <- 0

       for(j in i.neighbors)
       {
              # Node j has connections to these nodes:
              j.neighbors <- as.vector( neighborhood(g, 1, nodes=j)[[1]] )
              j.neighbors <- setdiff(j.neighbors, c(j)) # j should not be part of its own neighborhood

              # Were any of j neighbors also a neighbor of i?
              k <- intersect(i.neighbors, j.neighbors)

              tri <- tri + length(k)
       }

       # Save our findings to the progress matrix
       progress[i,1] <- tri
       progress[i,7] <- proc.time()["elapsed"] - benchmark
}
progress[,2] <- sapply(1:length(progress[,1]), function(x) sum(progress[,1][1:x]))
progress[,3] <- round(progress[,2] / 6, digits=2)

# The edge-list approach uses a list of all edges in the network to loop through instead
# Here, I suppose, a lot of the extra speed could arise from R being better at looping
# with lapply() and at finding data in a data.frame that the brute-force loop above is.
el <- as.data.frame(as.matrix(get.edgelist(g, )))

# This is ugly. Make the edgelist contain all edges as both i->j and j->i. In
# the igraph object, they are only given as low i to high j by get.edgelist()
  el.rev <- data.frame(el[,2], el[,1])
  names(el) <- names(el.rev) <- c("i","j")
  el <- rbind(el, el.rev)

# these nodes are connected (we'd only need to bother abouth non isolates)
nodes <- sort(unique(c(el$i, el$j)))
tri <- 0

# Loop through connected nodes to find triangles in edge-list
benchmark <- proc.time()["elapsed"] # Measure time for this loop
for(i in nodes){
       i.neighbors <- el[el$i==i,]$j
       # i neighbors are the $j:s of the edgelist where $i:s are i. 

       k.list <- unlist(lapply(i.neighbors, function(x) intersect(i.neighbors,el[el$i==x, ]$j)))
       # lists nodes that can be a k in an ijk-triangle for each of i neighboring j:s
       # If 1 has neighbors 2 and 3, el[el$i==x, ]$j) will be first, the neighbors of 2 and then
       # the neighbors of 3. When intersected with the neighbors of i, k:s will be found. If
       # {1,2,3} is a triangle one k will be 3 for {i=1, j=2}, and another k will be 2 for {i=1, j=3}

       # k.list might be NULL
       tri.for.this.i <- (as.numeric(length(k.list)) / 2)
       # Here we devide by two since i can be in a triangle with j and k lik {ijk} and {ikj}
       # We will later have to devide by 3 more, since each triangle will be counted for
       # each node i that we loop through

       # Save the counting to the progress
       tri <- tri.for.this.i + tri
       progress[i,4] <- as.numeric(tri.for.this.i)
       mm <- c(mm, i)
       progress[i,8] <- proc.time()["elapsed"] - benchmark
}
progress[,5] <- sapply(1:length(progress[,4]), function(x) sum(progress[,4][1:x]))
progress[,6] <- round(progress[,5] / 3, digits=2)

# Fix the results into a usable format
results <- data.frame(c("igraph", "adjacency-loop", "edge-loop"),
                      c(triangle.count, max(progress[,3]), max(progress[,6])),
                      c(gabor.Csardi.benchmark, (max(progress[,7]) - min(progress[,7])), (max(progress[,8]) - min(progress[,8]))))
row.names(results) <- c("igraph", "Adjacensy-loop", "Edge-loop")
names(results) <- c("Routine", "Triangle count", "Execution-time")

# Now we have run two approaches of more or less the same thing.
# Not only should the igraph triangle.count, and the two loops
# be identical, but the progress of the two methods should too.
progress[,3] == progress[,6]
plot(progress[,6], type="l", col="blue")
lines(progress[,7], col="green")

# Look at the result:
View(results)

Ответ 4

Зависит от того, как представлены ваши графики.

Если у вас есть матрица смежности A, то количество треугольников должно быть tr (A ^ 3)/6, другими словами, в 1/6 раза сумма диагональных элементов (разделение заботится о ориентации и вращении).

ЕСЛИ у вас есть списки смежности, начинающиеся только с каждого node и выполняющие поиск глубины 3. Подсчитайте, как часто вы снова достигаете этого node → деления на 6.

Ответ 5

Если вы не заботитесь о точном числе треугольников, существует очень простой алгоритм потоковой передачи, который обеспечивает несмещенную оценку. См. Например здесь для объяснения.

Ответ 6

Как цитируется здесь: https://math.stackexchange.com/a/117030

Самый быстрый алгоритм, известный для нахождения и подсчета треугольников, основан на быстром матричном произведении и имеет сложность времени O (nω), где ω < 2,376 - показатель быстрой матрицы. Однако этот подход приводит к сложности пространства θ (n2).