Files
spqrtree/docs/source/intro.rst
依瑪貓 23282de176 Initial commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 09:33:44 +08:00

4.6 KiB

<?xml version="1.0" encoding="utf-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <style type="text/css"> /* :Author: David Goodger (goodger@python.org) :Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. */ /* used to remove borders from tables and images */ .borderless, table.borderless td, table.borderless th { border: 0 } table.borderless td, table.borderless th { /* Override padding for "table.docutils td" with "! important". The right padding separates the table cells. */ padding: 0 0.5em 0 0 ! important } .first { /* Override more specific margin styles with "! important". */ margin-top: 0 ! important } .last, .with-subtitle { margin-bottom: 0 ! important } .hidden { display: none } .subscript { vertical-align: sub; font-size: smaller } .superscript { vertical-align: super; font-size: smaller } a.toc-backref { text-decoration: none ; color: black } blockquote.epigraph { margin: 2em 5em ; } dl.docutils dd { margin-bottom: 0.5em } object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { overflow: hidden; } /* Uncomment (and remove this text!) to get bold-faced definition list terms dl.docutils dt { font-weight: bold } */ div.abstract { margin: 2em 5em } div.abstract p.topic-title { font-weight: bold ; text-align: center } div.admonition, div.attention, div.caution, div.danger, div.error, div.hint, div.important, div.note, div.tip, div.warning { margin: 2em ; border: medium outset ; padding: 1em } div.admonition p.admonition-title, div.hint p.admonition-title, div.important p.admonition-title, div.note p.admonition-title, div.tip p.admonition-title { font-weight: bold ; font-family: sans-serif } div.attention p.admonition-title, div.caution p.admonition-title, div.danger p.admonition-title, div.error p.admonition-title, div.warning p.admonition-title, .code .error { color: red ; font-weight: bold ; font-family: sans-serif } /* Uncomment (and remove this text!) to get reduced vertical space in compound paragraphs. div.compound .compound-first, div.compound .compound-middle { margin-bottom: 0.5em } div.compound .compound-last, div.compound .compound-middle { margin-top: 0.5em } */ div.dedication { margin: 2em 5em ; text-align: center ; font-style: italic } div.dedication p.topic-title { font-weight: bold ; font-style: normal } div.figure { margin-left: 2em ; margin-right: 2em } div.footer, div.header { clear: both; font-size: smaller } div.line-block { display: block ; margin-top: 1em ; margin-bottom: 1em } div.line-block div.line-block { margin-top: 0 ; margin-bottom: 0 ; margin-left: 1.5em } div.sidebar { margin: 0 0 0.5em 1em ; border: medium outset ; padding: 1em ; background-color: #ffffee ; width: 40% ; float: right ; clear: right } div.sidebar p.rubric { font-family: sans-serif ; font-size: medium } div.system-messages { margin: 5em } div.system-messages h1 { color: red } div.system-message { border: medium outset ; padding: 1em } div.system-message p.system-message-title { color: red ; font-weight: bold } div.topic { margin: 2em } h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { margin-top: 0.4em } h1.title { text-align: center } h2.subtitle { text-align: center } hr.docutils { width: 75% } img.align-left, .figure.align-left, object.align-left, table.align-left { clear: left ; float: left ; margin-right: 1em } img.align-right, .figure.align-right, object.align-right, table.align-right { clear: right ; float: right ; margin-left: 1em } img.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } table.align-center { margin-left: auto; margin-right: auto; } .align-left { text-align: left } .align-center { clear: both ; text-align: center } .align-right { text-align: right } /* reset inner alignment in figures */ div.align-right { text-align: inherit } /* div.align-center * { */ /* text-align: left } */ .align-top { vertical-align: top } .align-middle { vertical-align: middle } .align-bottom { vertical-align: bottom } ol.simple, ul.simple { margin-bottom: 1em } ol.arabic { list-style: decimal } ol.loweralpha { list-style: lower-alpha } ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } p.attribution { text-align: right ; margin-left: 50% } p.caption { font-style: italic } p.credits { font-style: italic ; font-size: smaller } p.label { white-space: nowrap } p.rubric { font-weight: bold ; font-size: larger ; color: maroon ; text-align: center } p.sidebar-title { font-family: sans-serif ; font-weight: bold ; font-size: larger } p.sidebar-subtitle { font-family: sans-serif ; font-weight: bold } p.topic-title { font-weight: bold } pre.address { margin-bottom: 0 ; margin-top: 0 ; font: inherit } pre.literal-block, pre.doctest-block, pre.math, pre.code { margin-left: 2em ; margin-right: 2em } pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } pre.code .literal.string, code .literal.string { color: #0C5404 } pre.code .name.builtin, code .name.builtin { color: #352B84 } pre.code .deleted, code .deleted { background-color: #DEB0A1} pre.code .inserted, code .inserted { background-color: #A3D289} span.classifier { font-family: sans-serif ; font-style: oblique } span.classifier-delimiter { font-family: sans-serif ; font-weight: bold } span.interpreted { font-family: sans-serif } span.option { white-space: nowrap } span.pre { white-space: pre } span.problematic, pre.problematic { color: red } span.section-subtitle { /* font-size relative to parent (h1..h6 element) */ font-size: 80% } table.citation { border-left: solid 1px gray; margin-left: 1px } table.docinfo { margin: 2em 4em } table.docutils { margin-top: 0.5em ; margin-bottom: 0.5em } table.footnote { border-left: solid 1px black; margin-left: 1px } table.docutils td, table.docutils th, table.docinfo td, table.docinfo th { padding-left: 0.5em ; padding-right: 0.5em ; vertical-align: top } table.docutils th.field-name, table.docinfo th.docinfo-name { font-weight: bold ; text-align: left ; white-space: nowrap ; padding-left: 0 } /* "booktabs" style (no vertical lines) */ table.docutils.booktabs { border: 0px; border-top: 2px solid; border-bottom: 2px solid; border-collapse: collapse; } table.docutils.booktabs * { border: 0px; } table.docutils.booktabs th { border-bottom: thin solid; text-align: left; } h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { font-size: 100% } ul.auto-toc { list-style-type: none } </style> </head>

Introduction

What is an SPQR-Tree?

An SPQR-tree is a tree data structure that represents the decomposition of a biconnected graph into its triconnected components. Each node of the tree corresponds to one of four types:

  • S-node (series): a simple cycle.
  • P-node (parallel): a bundle of parallel edges between two poles.
  • Q-node: a single real edge (degenerate case).
  • R-node (rigid): a 3-connected subgraph that cannot be further decomposed.

SPQR-trees are widely used in graph drawing, planarity testing, and network reliability analysis.

Features

  • Pure Python --- no compiled extensions or external dependencies.
  • Handles multigraphs (parallel edges between the same vertex pair).
  • Implements the Gutwenger & Mutzel (2001) algorithm with corrections to Hopcroft & Tarjan (1973).
  • Simple dict-based input for quick prototyping.
  • Typed package with PEP 561 support.

Installation

Install from PyPI with pip:

pip install spqrtree

Or install the latest development version from GitHub:

pip install git+https://github.com/imacat/spqrtree.git

Requires Python 3.10 or later.

Quick Start

Build an SPQR-tree from an adjacency-list dictionary:

from spqrtree import SPQRTree

# A simple diamond graph (K4 minus one edge)
graph = {
    1: [2, 3, 4],
    2: [1, 3, 4],
    3: [1, 2, 4],
    4: [1, 2, 3],
}
tree = SPQRTree(graph)
print(tree.root.type)    # NodeType.R
print(len(tree.nodes()))  # number of SPQR-tree nodes

The input dictionary maps each vertex to its list of neighbors. For each pair (u, v) where u < v, one edge is added.

Usage Guide

Inspecting the Tree

The :class:`~spqrtree.SPQRTree` object exposes two main attributes:

System Message: ERROR/3 (<stdin>, line 80); backlink

Unknown interpreted text role "class".
from spqrtree import SPQRTree, NodeType

graph = {
    0: [1, 2],
    1: [0, 2, 3],
    2: [0, 1, 3],
    3: [1, 2],
}
tree = SPQRTree(graph)

for node in tree.nodes():
    print(node.type, node.poles)

Each :class:`~spqrtree.SPQRNode` has the following attributes:

System Message: ERROR/3 (<stdin>, line 101); backlink

Unknown interpreted text role "class".
  • type --- a :class:`~spqrtree.NodeType` enum (S, P, Q, or R).

    System Message: ERROR/3 (<stdin>, line 103); backlink

    Unknown interpreted text role "class".

  • skeleton --- the skeleton graph containing the real and virtual edges of the component.

  • poles --- the two vertices shared with the parent component.

  • parent --- the parent node (None for the root).

  • children --- the list of child nodes.

Understanding Node Types

S-node (series): Represents a cycle. The skeleton is a simple polygon whose edges alternate between real edges and virtual edges leading to children.

P-node (parallel): Represents parallel edges between two pole vertices. The skeleton contains three or more edges (real and/or virtual) between the same pair of poles.

R-node (rigid): Represents a 3-connected component that cannot be further decomposed by any separation pair. The skeleton is a 3-connected graph.

Q-node: Represents a single real edge. Q-nodes appear as leaves of the tree.

Using a MultiGraph

For more control, build a :class:`~spqrtree._graph.MultiGraph` directly:

System Message: ERROR/3 (<stdin>, line 135); backlink

Unknown interpreted text role "class".
from spqrtree._graph import MultiGraph
from spqrtree import SPQRTree

g = MultiGraph()
g.add_edge(0, 1)
g.add_edge(1, 2)
g.add_edge(2, 0)
g.add_edge(1, 3)
g.add_edge(3, 2)

tree = SPQRTree(g)

References

The implementation is based on the following papers:

  • J. Hopcroft and R. Tarjan, "Dividing a graph into triconnected components," SIAM Journal on Computing, vol. 2, no. 3, pp. 135--158, 1973. doi:10.1137/0202012
  • G. Di Battista and R. Tamassia, "On-line planarity testing," SIAM Journal on Computing, vol. 25, no. 5, pp. 956--997, 1996. doi:10.1137/S0097539794280736
  • C. Gutwenger and P. Mutzel, "A linear time implementation of SPQR-trees," Proc. 8th International Symposium on Graph Drawing (GD 2000), LNCS 1984, pp. 77--90, Springer, 2001. doi:10.1007/3-540-44541-2_8

Acknowledgments

The test suite was validated against the SPQR-tree implementation in SageMath, which served as the reference for verifying correctness of the decomposition results.

</html>