2022-04-07 16:41:26 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# IMPORTS
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
from __future__ import annotations;
|
2022-04-07 16:41:26 +02:00
|
|
|
from enum import Enum;
|
2022-04-18 19:04:42 +02:00
|
|
|
from dataclasses import dataclass;
|
|
|
|
from dataclasses import field
|
|
|
|
from platform import node;
|
2022-04-07 16:41:26 +02:00
|
|
|
|
|
|
|
from src.local.typing import *;
|
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
from src.core.log import *;
|
2022-04-07 16:41:26 +02:00
|
|
|
from src.stacks.stack import *;
|
2022-04-18 19:04:42 +02:00
|
|
|
from src.graphs.graph import *;
|
2022-04-07 16:41:26 +02:00
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# EXPORTS
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
'tarjan_algorithm',
|
|
|
|
];
|
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# CONSTANTS
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
class State(Enum):
|
|
|
|
UNTOUCHED = 0;
|
|
|
|
PENDING = 1;
|
|
|
|
FINISHED = 2;
|
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# Tarjan Algorithm
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
def tarjan_algorithm(G: Graph, debug: bool = False) -> List[Any]:
|
|
|
|
'''
|
|
|
|
# Tarjan Algorithm #
|
|
|
|
Runs the Tarjan-Algorithm to compute the strongly connected components.
|
|
|
|
'''
|
|
|
|
# initialise state - mark all nodes as UNTOUCHED:
|
|
|
|
ctx = Context(G, debug=debug);
|
|
|
|
# loop through all nodes and carry out Tarjan-Algorithm, provided node not already visitted.
|
|
|
|
for u in G.nodes:
|
|
|
|
if ctx.get_state(u) == State.UNTOUCHED:
|
|
|
|
tarjan_visit(G, u, ctx);
|
2022-04-07 16:41:26 +02:00
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
return ctx.components;
|
2022-04-07 16:41:26 +02:00
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
def tarjan_visit(G: Graph, u: Any, ctx: Context):
|
2022-04-07 16:41:26 +02:00
|
|
|
'''
|
2022-04-18 19:04:42 +02:00
|
|
|
Recursive depth-first search algorithm to compute strongly components of a graph.
|
2022-04-07 16:41:26 +02:00
|
|
|
'''
|
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
# Place node on stack + initialise visit-index + component-index.
|
|
|
|
ctx.max_index += 1;
|
|
|
|
ctx.push(u);
|
|
|
|
ctx.set_least_index(u, ctx.max_index);
|
|
|
|
ctx.set_index(u, ctx.max_index);
|
|
|
|
ctx.set_state(u, State.PENDING);
|
2022-04-07 16:41:26 +02:00
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
'''
|
|
|
|
Compute strongly connected components of each child node.
|
|
|
|
NOTE: Child nodes remain on stack, if and only if parent is in same component.
|
|
|
|
'''
|
|
|
|
for v in G.successors(u):
|
|
|
|
# Visit child node only if untouched:
|
|
|
|
if ctx.get_state(v) == State.UNTOUCHED:
|
|
|
|
tarjan_visit(G, v, ctx);
|
|
|
|
ctx.set_least_index(u, min(ctx.get_least_index(u), ctx.get_least_index(v)));
|
|
|
|
# Otherwise update associated component-index of parent node, if in same component as child:
|
|
|
|
elif ctx.stack_contains(v):
|
|
|
|
ctx.set_least_index(u, min(ctx.get_least_index(u), ctx.get_index(v)));
|
|
|
|
|
|
|
|
ctx.set_state(u, State.FINISHED);
|
|
|
|
ctx.log_info(u);
|
|
|
|
|
|
|
|
# If at least-index of component pop everything from stack up to least index and add component:
|
|
|
|
if ctx.get_index(u) == ctx.get_least_index(u):
|
|
|
|
component = [];
|
|
|
|
while True:
|
|
|
|
v = ctx.top();
|
|
|
|
ctx.pop();
|
|
|
|
component.append(v);
|
|
|
|
if u == v:
|
|
|
|
break;
|
|
|
|
ctx.components.append(component);
|
|
|
|
return;
|
2022-04-07 16:41:26 +02:00
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# AUXILIARY context variables for algorithm
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class NodeInformationDefault:
|
|
|
|
node: Any = field(default=None);
|
|
|
|
least_index: int = field(default=0);
|
|
|
|
index: int = field(default=0);
|
|
|
|
state: State = field(default=State.UNTOUCHED, repr=False);
|
|
|
|
|
|
|
|
class NodeInformation(NodeInformationDefault):
|
|
|
|
def __init__(self, u: Any):
|
|
|
|
super().__init__();
|
|
|
|
self.node = u;
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class ContextDefault:
|
|
|
|
max_index: int = field(default=0);
|
|
|
|
debug: bool = field(default=False);
|
|
|
|
stack: Stack = field(default_factory=lambda: Stack());
|
|
|
|
components: list[list[Any]] = field(default_factory=lambda: []);
|
|
|
|
infos: dict[Any, NodeInformation] = field(default_factory=lambda: dict());
|
|
|
|
|
|
|
|
class Context(ContextDefault):
|
|
|
|
def __init__(self, G: Graph, debug: bool):
|
|
|
|
super().__init__();
|
|
|
|
self.debug = debug;
|
|
|
|
self.infos = { u: NodeInformation(u) for u in G.nodes };
|
|
|
|
|
|
|
|
def push(self, u: Any):
|
|
|
|
self.stack.push(u);
|
2022-04-07 16:41:26 +02:00
|
|
|
|
2022-04-18 19:04:42 +02:00
|
|
|
def top(self) -> Any:
|
|
|
|
return self.stack.top();
|
|
|
|
|
|
|
|
def pop(self) -> Any:
|
|
|
|
return self.stack.pop();
|
|
|
|
|
|
|
|
def update_infos(self, u: Any, info: NodeInformation):
|
|
|
|
self.infos[u] = info;
|
|
|
|
|
|
|
|
def set_state(self, u: Any, state: State):
|
|
|
|
info = self.infos[u];
|
|
|
|
info.state = state;
|
|
|
|
self.update_infos(u, info);
|
|
|
|
|
|
|
|
def set_least_index(self, u: Any, least_index: int):
|
|
|
|
info = self.infos[u];
|
|
|
|
info.least_index = least_index;
|
|
|
|
self.update_infos(u, info);
|
|
|
|
|
|
|
|
def set_index(self, u: Any, index: int):
|
|
|
|
info = self.infos[u];
|
|
|
|
info.index = index;
|
|
|
|
self.update_infos(u, info);
|
|
|
|
|
|
|
|
def stack_contains(self, u: Any) -> bool:
|
|
|
|
return self.stack.contains(u);
|
|
|
|
|
|
|
|
def get_info(self, u: Any) -> NodeInformation:
|
|
|
|
return self.infos[u];
|
|
|
|
|
|
|
|
def get_state(self, u: Any) -> State:
|
|
|
|
return self.get_info(u).state;
|
|
|
|
|
|
|
|
def get_least_index(self, u: Any) -> int:
|
|
|
|
return self.get_info(u).least_index;
|
|
|
|
|
|
|
|
def get_index(self, u: Any) -> int:
|
|
|
|
return self.get_info(u).index;
|
|
|
|
|
|
|
|
def log_info(self, u: Any):
|
|
|
|
if not self.debug:
|
|
|
|
return;
|
|
|
|
info = self.get_info(u);
|
|
|
|
log_debug(info);
|