Caveat: I'm taking the code seriously
Background
The abstractions are leaky.
What is a Node?
A node has two parts: the value it contains and pointers to other nodes. Either, both, or neither may be null. When the pointer part is null we have a terminal node. Depending on context a terminal node might be called a leaf or an atom.
Graphs
One or more nodes form a graph. A graph may be directed or non-directed. Some, all, or none of the nodes in a graph may point to other nodes within it. The way in which nodes point to each other is what distinguishes one type of graph from another. Whether a node contains null or some other value is a detail of a graph implementation not nodes. A node's pointers to other nodes are the edges of a graph. The value stored at the node is a record.
The structure of a record is not a function of the graph or the node. It is an implementation detail of a particular program. That is to say that the record structure reflects business logic.
The edges of a directed graph are directional. The directed edge a -> b
is different from the directed edge b -> a
. The edges of a non-directed graph are not directional. The non-directed edge a - b
is indistinguishable from the non-directed edge b - a
.
Traversal is an operation on graphs. The order is not a property of the graph. It is a property of the business logic. The efficiency with which we traverse a graph often depends on the alignment between business logic and graph type.
Trees
Trees are a class of directed acyclic graphs. A graph consisting of a single node is a tree provided that it does not have an edge pointing to itself. A graph that consists of several trees is called a forest.
A tree in which each node n
has two outgoing edges is a binary tree. Typically, one edge is labeled left, the other right. A binary tree may or may not store values at internal nodes depending on the business logic being implemented. Binary trees are of particular interest in computing due to their isomorphism with binary logic. Another important class of trees for computing is the b-tree.
Code Improvements
To a first approximation, the leaky abstractions can be removed by redefining Node
and then using it in a definition of a BinaryTree
.
A Node Implementation
Since the number of edges a node has is a function of both the graph type and a particular instantiation of that type, an iterable data structure makes sense. A list is probably the place to start.
class Node():
def __init__(self, record):
self.edge = []
self.record = record
A Binary Tree Implementation
Now, binary trees can be implemented in terms of nodes.
class BinaryTree():
def __init__(self, record):
node = Node(record)
node.edge = [False, False]
self.record = node.record
self.left = node.edge[0]
self.right = node.edge[1]
Note that the Node
is encapsulated. There are accessors for the left and right edges, but no way to delete or add elements to node.edge
. Its length will always be two, there's no way to add a third edge to a BinaryTree
.
However, More Work Remains
The abstraction is still poor because Node
contains knowledge of edges. The right abstraction is a graph. A graph consists of nodes, usually called vertices, and edges.
- All a vertex should know is how to return a value. Anything it knows about edges is the graph abstraction leaking downward.
- Likewise all an edge should know is the name of two vertices. If it knows anything about direction, the graph abstraction is leaking down. If it knows anything about the contents behind the labels, the vertex abstraction is leaking up.
Graph Implementation
class Vertex():
def __init__(self, value):
self.value = value
class Graph():
def __init__(self, V, E):
self.V = []
self.E = {}
Using a dictionary for edges allows finding the edges that start at a node. Vertices are implicitly labelled by position in the list. A fast lookup array would be better but requires declaring datatypes.