-
Notifications
You must be signed in to change notification settings - Fork 0
/
graph.R
348 lines (313 loc) · 11.3 KB
/
graph.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
is_valid = function(g){
# Check each of 6 criteria to determine if g is a graph #
# If a test fails at any step, function stops and returns FALSE #
# Check if input is a list and not an empty list#
if(is.list(g) == FALSE | length(g) == 0) {
return(FALSE)
}
# if gr is a list, get number of verticies #
numVert = length(g)
# Check if g is a list of lists and that nested lists not empty. #
for(i in 1:numVert){
if(is.list(g[[i]]) == FALSE | length(g[[i]]) == 0) {
return(FALSE)
}
}
# Check if each secondary list contains edges and weights vectors #
for(i in 1:numVert){
if(length(g[[i]]) == 0) {
return(FALSE)
}
if(!(all(c("edges", "weights") %in% names(g[[i]])))){
return(FALSE)
}
}
namesGraph = names(g)
# Check if there are names for the primary list that they are all unique #
if(length(namesGraph) == 0){
return(FALSE)
}
#Vector of unique names should have same length as vector of all vertex names#
if(length(unique(namesGraph))!=length(namesGraph)){
return(FALSE)
}
for(j in 1:numVert){
# Check that each secondary list contains only edges and weights #
# vectors that are of the appropriate type. #
# First elements in list for each vertex are the edges, #
# should be integers or NULL #
if(typeof(g[[namesGraph[j]]][["edges"]]) !="integer" &
typeof(g[[namesGraph[j]]][["edges"]]) !="NULL"){
return(FALSE)
}
# Second elements in list for each vertex are the weights, #
# should be doubles or NULL #
if(typeof(g[[namesGraph[j]]][["weights"]]) !="double" &
typeof(g[[namesGraph[j]]][["weights"]]) !="NULL"){
return(FALSE)
}
# Check that all edges have weights #
if(length(g[[namesGraph[j]]][["edges"]]) !=
length(g[[namesGraph[j]]][["weights"]])){
return(FALSE)
}
# Check that there is no duplicated edges #
if(length(g[[namesGraph[j]]][["edges"]]) !=
length(unique(g[[namesGraph[j]]][["edges"]]))){
return(FALSE)
}
# Check that all weights are not less than or equal to 0 #
if(length(g[[namesGraph[j]]][["edges"]]) != 0 &
length(g[[namesGraph[j]]][["weights"]]) != 0){
if(g[[j]][["weights"]] <= 0 || is.na(g[[j]][["weights"]]) ||
is.na(g[[j]][["edges"]])){
return(FALSE)
}
# Check that there are not any edges to non-existent vertices. #
# Assume vertices labeled as integers in numerical order. #
if(any(g[[j]][["edges"]] > numVert)){
return(FALSE)
}
}
}
return(TRUE)
}
# function to tell if a graph is undirected #
# input g is a graph #
# returns TRUE if function undirected #
# returns FALSE otherwise #
is_undirected = function(g) {
# checks that graph is valid #
if (is_valid(g) == FALSE) {
stop("The input is not a valid graph")
}
# create adjacency matrix from helper function #
ajmatrix = adjacency_matrix(g)
# takes transpose of adjacency matrix #
ajmatrix.tran = t(ajmatrix)
# checks to see if matrix and tranpose are identical #
if(identical(ajmatrix, ajmatrix.tran) && length(g) >= 1) {
return(TRUE)
} else {
return(FALSE)
}
}
is_isomorphic = function(g1, g2){
if (is_valid(g1) == FALSE || is_valid(g2) == FALSE) {
stop("The input is not a valid graph")
}
# Check if both graphs are of equal length, return FALSE if not #
if (length(g1) != length(g2)){
return(FALSE)
}
# Check if both graphs have the sames labels, return FALSE if not #
if (identical(sort(names(g1)), sort(names(g2))) == FALSE){
return(FALSE)
}
for(count1 in 1:length(g1)){
#Get label of ith vertice in graph1 #
label.track = names(g1[count1])
# Get indexes of edges of ith vertice in graph1 (could be vector) #
edge.trackg1 = g1[[label.track]]$edges
# Get weight of associated with each edge(could also be a vector) #
weight.trackg1 = g1[[label.track]]$weights
# define a list that will hold labels of each edge and and its associated weight of graph1 for ith vertice #
g1comp_list = list()
# loop through the edges, and determine the associated label in graph1 and store it in a vector #
if(length(edge.trackg1)>0){
for (count2 in 1:length(edge.trackg1)){
g1comp_list[[count2]] = list(names(g1[edge.trackg1[count2]]), weight.trackg1[count2])
}
}
# Find label of ith vertice in graph1 in graph2, and determine associated indexes and weights #
edge.trackg2 = g2[[label.track]]$edges
weight.trackg2 = g2[[label.track]]$weights
# define a list that will hold labels of each edge and #
# its associated weight for graph2 for vertice associated with label.track #
g2comp_list = list()
# loop through the edges, and determine the associated label in graph2 and store it in a vector #
if(length(edge.trackg2)>0){
for (count3 in 1:length(edge.trackg2)){
g2comp_list[[count3]] = list(names(g2[edge.trackg2[count3]]), weight.trackg2[count3])
}
}
# Find intersection of g1comp_list and g2comp_list and see it is equal to g1comp_list #
# If it is equal, the result implies that equated labels in both graphs have the same structure #
# If not flag is updated to 1 #
if (!identical(g1comp_list, g1comp_list[g1comp_list %in% g2comp_list]) | !identical(g2comp_list, g2comp_list[g2comp_list %in% g1comp_list])){
return(FALSE)
}
}
return(TRUE)
}
# helper function to create adjacency matrix #
# input is a graph g #
# output is an N by N matrix#
adjacency_matrix = function(g){
# set size of adjacency matrix #
N <- length(names(g))
# initiate the adjacency matrix #
ajmatrix <- matrix(0, nrow = N, ncol = N)
# assign values to the adjacency matrix #
for(i in 1:N){
ajmatrix[i, g[[i]][["edges"]]] <- g[[i]][["weights"]]
}
return(ajmatrix)
}
is_connected = function(g, v1, v2){
# store the vertex label #
names.graph <- names(g)
# check validity of the graph #
if(is_valid(g) == FALSE){
stop("The input is not a valid graph")
}
# check if v1 and v2 are in g #
if(!(v1 %in% names.graph & v2 %in% names.graph)){
stop("invalid vertex label")
}
# create adjacency matrix from helper function #
ajmatrix <- adjacency_matrix(g)
# set loop to size of matrix #
N <- nrow(ajmatrix)
# assign values to the adjacency matrix to several powers #
for(i in 1:N){
if(i == 1){
ajmatrix.N <- ajmatrix
} else {
ajmatrix.N <- ajmatrix %*% ajmatrix.N
# check if there is a path from v1 to v2 #
}
if(ajmatrix.N[which(names.graph == v1), which(names.graph == v2)] > 0){
return(TRUE)
}
}
return(FALSE)
}
# Influenced by Dijkstra's Algorithm with C code provided from: #
# http://www.codewithc.com/dijkstras-algorithm-in-c/ #
# Also referenced Wikipedia article on Dijkstra's Algorithm #
# Returns shortest path between two verticies in a graph #
# Input is a graph, g, a source vertex v1 and a target vertex v2 #
shortest_path = function(g, v1, v2){
# Function can't run if input not a graph #
if(is_valid(g)==FALSE){
stop("Input is not a valid graph")
}
# Return empty vector if v1 and v2 are not connected #
if(is_connected(g, v1, v2) == FALSE){
return(c())
}
#Sum_weight finds the sum of all weights in a graph, g#
sum_weight = function(g){
sum.edge = 0
for(i in 1:length(g)){
sum.edge = sum.edge + sum(g[[i]]$weights)
}
return(sum.edge)
}
# Define infinity as 10 more than the sum of all weights in g #
Infinity = sum_weight(g)+10
N = length(g)
# Convert vertex labels to integers based on their position #
int.v1 = which(names(g) == v1)
int.v2 = which(names(g) == v2)
# create adjacency matrix of connected weights #
# if elements not connected, weight between them is INF, initialize matrix to INF #
cost = matrix(Infinity, nrow = N, ncol = N)
for (i in 1:N) {
for (j in 1:N) {
if(length(g[[i]]$edges) == 0){
if (i == j) { #elements on diagonal are 0
cost[i, j] = Infinity
}
}
else{
for(k in 1:length(g[[i]]$edges)){
if (g[[i]]$edges[[k]] == j) {
# Find which edge k for vertex i is connected to vertex j #
# Set cost[i, j] = the weight for this edge #
cost[i, j] = g[[i]]$weights[[which(g[[i]]$edges == j)]]
}
}
}
}
}
#Algortithm does not revist already visited verticies#
#Special case when v1 = v2#
if(v1 == v2){
currentMin = which.min(cost[int.v1, ])
#If shortest path from v1 to v1 is just through itself, return "v1" "v1"#
if(currentMin == int.v1){
return(c(v1, v2))
}
else if(cost[int.v1, int.v1] == min(cost[int.v1,])){
return(c(v1, v2))
}
#Otherwise, might have to visit another vertex first for shortest path#
else if(which.min(cost[currentMin,]) == int.v1){
return(names(g)[c(int.v1, currentMin, int.v1)])
}
}
# Initialize various vectors to 0 #
# Initialize distances between vertices to infinity to begin #
distance = rep(Infinity,N)
# Initialize previous vertex to -1 so it is undefined to begin #
prev = rep(-1,N)
# Vector of verteces selected for comparison, initalize to 0 since none selected #
selected = rep(0,N)
# Vector of final shortest path #
path = rep(0,N)
# Path starts at v1, start then denotes the current vertex that we are considering #
# as the source (from which distances to other verticies are measured) #
start = int.v1
# We select the vertix at start #
selected[start] = 1
# Initial distance to start is 0 #
distance[start] = 0
# Find shortest path while the target v2 is unselected #
while(selected[int.v2] == 0) {
min = Infinity
m = 0
for(k in 1:N){
# For each current vertex, calculate new distance between this vertex and all #
# other unvisited verticies #
d = distance[start] + cost[start, k]
# If this new distance between the current vertex and vertex k #
# is less than the previous distance[k] and if vertex k is unvisited, set #
# distance[k] to this new smaller distance d #
# and note that we have now visited this vertex, prev[k] = start #
if((d < distance[k]) & (selected[k] == 0)){
distance[k] = d
prev[k] = start
}
# If the distance between the two considered verticies is less than min #
# which is initialized to INF and we have not yet selected or visited the #
# vertex k, we set min to distance[k] and index m as k #
# m updates what our current vertex considered as the source is #
if((min > distance[k]) & (selected[k] == 0)){
min = distance[k]
m = k
}
}
# Current vertex considered (which equals start) is set to m #
start = m
# Note that we have now visited vertex start and do not visit it again #
selected[start] = 1
}
# After looping through all verticies as the start vertix, we set start to be #
# vertex v2 #
start = int.v2
l = 0
# We then select the verticies visited on our shortest path #
# If a vertex is not in shortest path, start = -1, so start != -1 is the condition #
# for the while loop to select all verticies in the path #
while (start != -1) {
l = l+1
path[l] = start
start = prev[start]
}
# Need to reverse order of elements in path to go from v1 to v2 #
out.path = rev(path)
out.verts = names(g)[out.path]
return(out.verts)
}