DenseZDD : A Compact and Fast Index for Families of Sets

: In this article, we propose a succinct data structure of zero-suppressed binary decision diagrams (ZDDs). A ZDD represents sets of combinations efﬁciently and we can perform various set operations on the ZDD without explicitly extracting combinations. Thanks to these features, ZDDs have been applied to web information retrieval, information integration, and data mining. However, to support rich manipulation of sets of combinations and update ZDDs in the future, ZDDs need too much space, which means that there is still room to be compressed. The paper introduces a new succinct data structure, called DenseZDD, for further compressing a ZDD when we do not need to conduct set operations on the ZDD but want to examine whether a given set is included in the family represented by the ZDD, and count the number of elements in the family. We also propose a hybrid method, which combines DenseZDDs with ordinary ZDDs. By numerical experiments, we show that the sizes of our data structures are three times smaller than those of ordinary ZDDs, and membership operations and random sampling on DenseZDDs are about ten times and three times faster than those on ordinary ZDDs for some datasets, respectively.


Introduction
A Binary Decision Diagram (BDD) [1] is a graph-based representation of a Boolean function, widely used in very-large-scale integration (VLSI) logic design, verification, and so on. A BDD is regarded as a compressed representation that is generated by reducing a binary decision tree, which represents a decision-making process such that each inner node means an assignment of a 0/1-value to an input variable of a Boolean function and the terminal nodes mean its output values (0 or 1) of the function. By fixing the order of the input variables (i.e., the order of assignments of variables), deleting all nodes whose two children are identical, and merging all equivalent nodes (having the same variable and the same children), we obtain a minimal and canonical form of a given Boolean function.
Although various unique canonical representations of Boolean functions such as conjunctive normal form (CNF), disjunctive normal form (DNF), and truth tables have been proposed, BDDs are often smaller than them for many classes of Boolean functions. Moreover, BDDs have the following features: (i) multiple functions are stored by sharing common substructures of BDDs compactly; and (ii) fast logical operations of Boolean functions such as AND and OR are executed efficiently.
A Zero-suppressed Binary Decision Diagram (ZDD) [2] is a variant of traditional BDDs, used to manipulate families of sets. As well as BDDs, ZDDs have the feature that we can efficiently perform set operations of them such as Union and Intersection. Thanks to the feature of ZDDs, we can treat combinatorial item sets as a form of a compressed expression without extracting them one by one. For example, we can implicitly enumerate combinatorial item sets frequently appearing in given data [3].
Although the size of a ZDD is exponentially smaller than the cardinality of the family of sets represented by the ZDD in many cases, it may be still too large to be stored into a memory of a single server computer. Since a ZDD is a directed acyclic graph whose nodes have a label representing a variable and two outgoing arcs, we use multiple pointers to represent the structure of a ZDD, which is unacceptable for many applications including frequent item set mining [3,4].
We classify operations on ZDDs into two types: dynamic and static. A dynamic operation is one that constructs another ZDD when (one or more) ZDD is given. For example, given two families of sets as two ZDDs, we can efficiently construct the ZDD representing the union of the two families [5]. On the other hand, a static operation is one that computes a value related to a given ZDD but does not change the ZDD itself. For example, there are cases where we just want to know whether a certain set is included in the family or not, and we want to conduct random sampling, that is, randomly pick a set from the family. To support dynamic operations, we need to store the structure of ZDDs as it is, which increases the size of the representation of ZDDs. Therefore, there is a possibility that we can significantly reduce the space to store ZDDs by restricting to only static operations. To the best of the authors' knowledge, there has been no work on representations of ZDDs supporting only static operations.
This paper proposes a succinct data structure of ZDDs, which we call DenseZDDs, which support only static operations. The size of ZDDs in our representation is much smaller than an existing representation [6], which fully supports dynamic operations. Moreover, DenseZDD supports much faster membership operations than the representation of [6]. Experimental results show that the sizes of our data structures are three times smaller than those of ordinary ZDDs, and membership operations and random sampling on DenseZDDs are about ten times and three times faster than those on ordinary ZDDs for some datasets, respectively. This paper is an extended version of the paper published at the 13th International Symposium on Experimental Algorithms held in 2014 [7]. The main updates of this paper from the previous version are as follows: (i) we propose algorithms for counting and fast random sampling on DenseZDD; (ii) we propose a static representation of a variant of ZDDs called Sequence BDD; and (iii) we conduct more experiments on new large data sets using algorithms (including new proposed ones). Note that our technique can be directly applied to reduce the size of traditional BDDs as well as ZDDs.
The organization of the paper is as follows. In Section 2, we introduce our notation and data structures used throughout this paper. In Section 3, we propose our data structure DenseZDD and show the algorithms to convert a given ZDD to a DenseZDD. In Section 4, we show how to execute ZDD operations on a DenseZDD. In Section 5, we study the space complexities of DenseZDD and the time complexities of operations discussed in Section 4. In Section 6, we show how to implement dynamic operations on a DenseZDD. In Section 7, we show how to apply our technique to decision diagrams for sets of strings. In Section 8, we show results of experiments for real and artificial data to evaluate construction time, search time and compactness of DenseZDDs.

Preliminaries
Let e 1 , . . . , e n be items such that e 1 < e 2 < · · · < e n . Throughout this paper, we denote the set of all n items as U n = {e 1 , . . . , e n }. For an itemset S = {a 1 , . . . , a c } (⊆ U n ), c ≥ 0, we denote the size of S by |S| = c. The empty set is denoted by ∅. A family is a subset of the power set of all items. A finite family F of sets is referred to as a set family (In the original ZDD paper by Minato [2], a set is called a combination, and a set family is called a combinatorial set.). The join of families F 1 and F 2 is defined as

Succinct Data Structures for Rank/Select
Let B be a binary vector of length u, that is, B[i] ∈ {0, 1} for any 0 ≤ i < u. The rank value rank c (B, i) is defined as the number of c's in B[0..i], and the select value select c (B, j) is the position of j-th c (j ≥ 1) in B from the left, that is, the minimum k such that the cardinality The Fully Indexable Dictionary (FID) is a data structure for computing rank and select on binary vectors [8].
Proposition 1 (Raman et al. [8]). For a binary vector of length u with n ones, its FID uses log ( u n ) + O(u log log u log u ) bits of space and computes rank c (B, i) and select c (B, i) in constant time on the Ω(log u)-bit word RAM.
This data structure uses asymptotically optimal space because any data structure for storing the vector uses log ( u n ) bits in the worst case. Such a data structure is called a succinct data structure.

Succinct Data Structures for Trees
An ordered tree is a rooted unlabeled tree such that children of each node have some order. A succinct data structure for an ordered tree with n nodes uses 2n + o(n) bits of space and supports various operations on the tree such as finding the parent or the i-th child, computing the depth or the preorder of a node, and so on, in constant time [9]. An ordered tree with n nodes is represented by a string of length 2n, called a balanced parentheses sequence (BP), defined by a depth-first traversal of the tree in the following way: starting from the root, we write an open parenthesis '(' if we arrive at a node from above, and a close parenthesis ')' if we leave from a node upward. For example, imagine the complete binary tree that consists of three branching nodes and four leaves. When we traverse the complete binary tree in the depth-first manner, the sequence of the transition is "down, down, up, down, up, up, down, down, up, down, up, up". Then, we can encode the tree as "(, (, ), (, ), ), (, (, ), (, ), )" by replacing 'down' with '(' and 'up' with ')', respectively.
In this paper, we use the following operations. Let P denote the BP sequence of a tree. A node in the tree is identified with the position of the open parenthesis in P representing the node: The operations take constant time.
A brief overview of the data structure is the following. The BP sequence is partitioned into equal-length blocks. The blocks are stored in leaves of a rooted tree called range min-max tree. In each leaf of the range min-max tree, we store the maximum and the minimum values of node depths in the corresponding block. In each internal node, we store the maximum and the minimum of values stored in children of the node. By using this range min-max tree, all tree operations are implemented efficiently.

Zero-Suppressed Binary Decision Diagrams
A zero-suppressed binary decision diagram (ZDD) [2] is a variant of a binary decision diagram [1], customized to manipulate finite families of sets. A ZDD is a directed acyclic graph satisfying the following. A ZDD has two types of nodes, terminal and nonterminal nodes. A terminal node v has as an attribute a value value(v) ∈ {0, 1}, indicating whether it is the 0-terminal node or the 1-terminal node, denoted by 0 and 1, respectively. A nonterminal node v has as attributes an integer index(v) ∈ {1, . . . , n} called the index, and two children zero(v) and one(v), called the 0-child and 1-child. The edge from a nonterminal to its 0-child (1-child resp.) is called the 0-edge (1-edge resp.). In the figures in the paper, terminal and nonterminal nodes are drawn as squares and circles, respectively, and 0-edges and 1-edges are drawn as dotted and solid arrows, respectively. We define triple(v) = index(v), zero(v), one(v) , called the attribute triple of v. For any nonterminal node v, index(v) is larger than the indices of its children (In ordinary BDD or ZDD papers, the indices are in ascending order from roots to terminals. For convenience, we employ the opposite ordering in this paper).We define the size of a ZDD G as the number of its nonterminals and denote it by |G|. Definition 1 (set family represented by a ZDD). A ZDD G = (V, E) rooted at a node v ∈ V represents a finite family of sets F(v) on U n defined recursively as follows: (1) If v is a terminal node: The example in Figure 1  . . , c } describes a path in the graph G starting from the root in the following way: At each nonterminal node with label c i , the path continues to the 0-child if c i ∈ S and to the 1-child if c i ∈ S, and the path eventually reaches the 1-terminal (0-terminal resp.), indicating that S is accepted (rejected resp). We employ the following two reduction rules, shown in Figure 2, to compress ZDDs: (a) Zerosuppress rule: A nonterminal node whose 1-child is the 0-terminal node is deleted; (b) sharing rule: two or more nonterminal nodes having the same attribute triple are merged. By applying the above rules, we can reduce the size of the graph without changing its semantics. If we apply the two reduction rules as much as possible, then we obtain the canonical form for a given family of sets. We implement the sharing rule by using a hash table such that a key is an attribute triple and the value of the key is the pointer to the node corresponding to the attribute triple. When we want to create a new node with an attribute triple i, v 0 , v 1 , we check whether such a node has already existed or not. If such a node exists, we do not create a new node and use the node. Otherwise, we create a new node v with i, v 0 , v 1 and we register it to the hash table. After that, V and E are updated to V ∪ {v} and Zero-suppress rule We can further reduce the size of ZDDs by using a type of attributed edges [2,10], named 0-element edges. A ZDD with 0-element edges is defined as follows. A ZDD with 0-element edges has the same property as an ordinary ZDD, except that it does not have the 1-terminal and that the 1-edge of each nonterminal node has as an attribute a one bit flag, called ∅-flag. The ∅-flag of the 1-edge of each nonterminal node v is denoted by empflag(v), whose value is 0 or 1. In the figures in this paper, ∅-flags are drawn as small circles at the starting points of 1-edges. Throughout this paper, we always use ZDDs with 0-element edges and simply call it ZDDs. We always denote by m the number of nodes of a given ZDD. An example of a ZDD with 0-element edges is shown in Figure 3. When we use ZDDs with 0-element edges, we employ a pair v, c , v ∈ V, c ∈ {0, 1} to point a node instead of only v, considering that a pair v, c represents the family of sets  Table 1 summarizes operations of ZDDs. The upper half shows the primitive operations, while the lower half shows other operations that can be implemented by using the primitive operations. The operations index(v), zero(v), one(v), topset(v, i) and member(v, S) do not create new nodes. Therefore, they are static operations. Note that the operation count(v) does not create any node; however, we need an auxiliary array to memorize which nodes have already been visited. Table 1. Main operations supported by ZDD. The first group are the primitive ZDD operations used to implement the others, yet they could have other uses.

index(v)
Returns the index of node v. zero(v) Returns the 0-child of node v. one(v) Returns the 1-child of node v. getnode(i, v 0 , v 1 ) Generates (or makes a reference to) a node v with index i and two child nodes v 0 = zero(v) and v 1 = one(v). topset(v, i) Returns a node with the index i reached by traversing only 0-edges from v. If such a node does not exist, it returns the 0-terminal node.

member(v, S)
Returns true if S ∈ F(v), and returns false otherwise.
Returns a set S ∈ F(v) uniformly and randomly.

Problem of Existing ZDDs
Existing ZDD implementations (supporting dynamic operations) have the following problem in addition to the size of representations discussed in Section 1. The member(v, S) operation needs Θ(n) time in the worst case. In practice, the sizes of query sets are often much smaller than n, so an O(|S|) time algorithm is desirable. Existing implementations need Θ(n) time for the member(v, S) operation because it is implemented by repeatedly using the zero(v) operation.
For example, we traverse 0-edges 255 times when we search S = {e 1 } on the ZDD for F = {{e 1 }, . . . , {e 256 }}. If we translate the ZDD to an equivalent automaton by using an array to store pointers (see Figure 4), we can execute the searching in O(|S|) time. ZDD nodes correspond to labeled edges in the automaton. However, the size of such an automaton via straightforward translation can be Θ(n) times larger than the original ZDD [11] in the worst case. Therefore, we want to perform member(v, S) operations in O(|S|) time on ZDDs. Minato proposed Z-Skip Links [12] to accelerate the traversal of ZDDs of large-scale sparse datasets. Their method adds one link per node to skip nodes that are concatenated by 0-edges. Therefore, the amount of memory requirement cannot be smaller than original ZDDs. Z-Skip-Links make the membership operations much faster than using conventional ZDD operations when handling large-scale sparse datasets. However, the computation time is probabilistically analyzed only for the average case.

DenseZDD
In this subsection, we are going to show what DenseZDD is for a given ZDD Z. We define a DenseZDD for Z as DZ(Z) = U, M, I , which consists of the BP U of a zero-edge tree, the bit vector M to indicate dummy nodes, and the integer array I to store one-children.

Zero-Edge Tree
We construct the zero-edge tree from a given ZDD G as follows. First of all, we delete all the 1-edges of G. Then, we reverse all the 0-edges, that is, if there is a (directed) edge from v to w, we delete the edge and create the edge from w to v. Note that the tree obtained by this procedure is known as the left/right tree of a DAG whose nodes have two distinguishable arcs, originally used for representing a context-free grammar [13]. We also note that the obtained tree is a spanning one whose root node is the 0-terminal node. Next, we insert dummy nodes into 0-edges so that the distance from the 0-terminal to every node is index(v). Specifically, for each node w that is pointed by 0 If c = 0, we add no dummy node for the 0-edges pointing at w. For example, see Figure 5. We call the resulting tree the zero-edge tree of G and denote it by T Z . To avoid confusion, we call the nodes in T Z except for dummy nodes real nodes. We construct the BP of T Z and denote it by U. We let U be the first element of the DenseZDD triplet (described in the beginning of this section).
We define the real preorder rank of a real node v in T Z (and the corresponding node in G) as the preorder rank of v in the tree before adding dummy nodes and edges connecting nodes.
On BP U, as we will show later, introducing dummy nodes enable to simulate the index and topset operations in constant time by using the depth or level_ancestor operation of BP. The length of U is O(mn) because we create at most n − 1 dummy nodes per one real node. The dummy nodes make the length of U O(n) times larger, whereas this technique saves O(m log n) bits because we do not store the index of each node explicitly.
An example of a zero-edge tree and its BP are shown in Figure 6. Black circles are dummy nodes and the number next to each node is its real preorder rank.

Dummy Node Vector
A bit vector of the same length as U is used to distinguish dummy nodes and real nodes. We call it the dummy node vector of T Z and denote it by B D . The i-th bit is 1 if and only if the i-th parenthesis of U is '(' and its corresponding node is a real node in T Z . An example of a dummy node vector is also shown in Figure 6. We construct the FID of B D and denote it by M. We let M be the second element of the DenseZDD triplet. Using M, as we will show later, we can determine whether a node is dummy or real, and compute real preorder ranks in constant time. Moreover, for a given real preorder rank i, we can compute the position of '(' on U that corresponds to the node with real preorder rank i in constant time. ( ( ) ) ( ) ) ) ) ) ( ) ) ) ( (

One-Child Array
We now construct an integer array to indicate the 1-child of each nonterminal real node in G by values of real preorder ranks. We call it the one-child array and denote it by C O . More formally, for i = 1, . . . , m, C O [i] = v means the following: Let w T be the real node with real preorder rank i in T Z , w G be the node corresponding to w T in G, w G1 be the 1-child of w G , and w T1 be the node corresponding to w G1 in T Z . Then, C O [i] = v means that the real preorder rank of w T1 is the absolute value of v and empflag(v) = 0 if v > 0; otherwise empflag(v) = 1. An example of a one-child array is shown in Figure 7. As an implementation of C O , we use the fixed length array of integers (see e.g., [14]). We denote it by I. In I, one integer is represented by log(m + 1) + 1 bits, including one bit for the ∅-flag. We let I be the third element of the DenseZDD triplet.
DenseZDD solves the problems as described in Section 2.4. The main results of the paper are the following two theorems.

Theorem 1.
A ZDD Z with m nodes on n items can be stored in 2u + m log m + 3m + o(u) bits so that the primitive operations except getnode(i, v 0 , v 1 ) are done in constant time, where u is the number of real and dummy nodes in the zero edge tree of DZ(Z).

Theorem 2.
A ZDD with m nodes on n items can be stored in O(m(log m + log n)) bits so that the primitive The proofs are given in Section 5. The time complexity of getnode(i, v 0 , v 1 ) is discussed in Section 6.

Convert Algorithm
We show our algorithm to construct the DenseZDD DZ from a given ZDD G in detail. The pseudocode of our algorithm is given in Algorithm 6. First, we describe how to build the zero-edge tree from G.
The zero-edge tree consists of all 0-edges of the ZDD, with their directions being reversed. For a nonterminal node v, we say that v is a 0 r -child of zero(v). To make a zero-edge tree, we prepare a list revzero v for each node v, which stores 0 r -children of the node v. For all nonterminal nodes v, we visit v by a depth-first traversal of the ZDD and add v to revzero zero(v) . This is done in O(m) time and O(m) space because each node is visited at most twice and the total size of revzero v for all v is the same as the number of nonterminal nodes.
Let T be the zero-edge tree before introducing dummy nodes. Let us introduce an order of the elements in revzero v for each v in T so that the getnode operation, described later, can be executed efficiently. Note that the preorder ranks of nodes in T are determined by the order of children of every node in T.
Here, we observe the following fact. Consider a node v in T, and suppose that v has 0 r -children v 1 , . . . , v k , which are ordered as v 1 < · · · < v k . Let stsize(v) be the subtree size of a node v in T. Then, if the preorder rank of v is p, that of v i is p + ∑ i−1 j=1 stsize(v j ) + 1 for i = 1, . . . , k. Note that, even if the order of v 1 , . . . , v i−1 is not determined (it is only determined that v i is located in the i-th position), the preorder rank of v i , p + ∑ i−1 j=1 stsize(v j ) + 1, can be computed (see Figure 8). Figure 8. Computing real preorder ranks from the 0-terminal node to real nodes with higher indices. Now, we introduce an order of the elements in revzero v for each node v. First, for each node v, we order the elements in revzero v in the descending order of their indices. If the indices of two nodes are the same, we temporarily and arbitrarily determine their order (we will change the order later). Then, we do the following procedure for i = 1, . . . , n. In the i-th procedure, suppose that the preorder ranks of all the nodes with index smaller than i have already been determined. We consider a node v. Let {v 1 , . . . , v p }, {v p+1 , . . . , v p+q } and {v p+q+1 , . . . , v p+q+r } be the sets of nodes with index larger than i, equals to i, and smaller than i in revzero v , respectively. By our assumption, v p+q+1 , . . . , v p+q+r have already been ordered and we now determine the order of v p+1 , . . . , v p+q . We let the order of v p+1 , . . . , v p+q be the descending order of the preorder ranks of their one-children. Note that the preorder ranks of the one-children of v p+1 , . . . , v p+q have already been determined. Thus, since the positions of v p+1 , . . . , v p+q in revzero v are determined, the above observation implies that the preorder ranks of v p+1 , . . . , v p+q are also determined. We do it for all nodes v. After the procedure is finished, the preorder ranks of all the nodes (that is, the order of the children of nodes in T) are determined. As a result, the 0 r -children v of each node are sorted in the lexicographical order of index(v ), prank(one(v )) . The pseudo-code is given in Algorithm 1.

Algorithm 1 Compute_Preorder:
Algorithm that computes the preorder rank prank(v) of each node v. Sets of nodes are implemented by arrays or lists in this code.
. . , L n are lists that are empty initially. 3: for i = 0, . . . , n do L i includes sets of nodes that have the same index 4: for each A, [l, r] ∈ L i in arbitrary order do A is a set of nodes that have the same index and 0-child Note that prank of nodes with indices less than i are already computed 5: for each v ∈ A in descending order of prank(one(v)) do 6: prank(v) ← l; 7: l ← l + 1; 8: for each j ∈ { j | w ∈ revzero(v), j = index(w) } in descending order do 9: B ← { w | w ∈ revzero(v), index(w) = j }; 10: r ← l + sum{ stsize(w) | w ∈ B }; 11: append B, [l, r] to L j ; That is, the prank of descendants of nodes in B are in [l, r].

12:
l ← r + 1; 13: end for 14: end for 15: end for 16: end for 17: return; To compute prank efficiently, we construct the temporary BP for the zero-edge tree. Using the BP, we can compute the size of each subtree rooted by v in T in constant time and compact space. Since we can compute the size of the subtrees in T, we can know the ranges of real preorder ranks that are assigned to the subtrees by bottom-up processing. The whole tree, the subtree rooted by the 0-terminal node, is assigned the range of preorder rank [0, m]. Let w be a node rooting a subtree that is assigned a range of real preorder ranks [l, l + stsize(w) − 1] and assume that the revzero(w) is sorted. Then, the real preorder rank of w is l and the subtree rooted by v j ∈ revzero(w) is assigned the range [l j , l j+1 − 1], where l j = l j−1 + stsize(v j ) and l 1 = l + 1.
The DenseZDD for the given ZDD G is composed of these three data structures. We traverse T in depth-first search (DFS) order based on assigned real preorder ranks and construct the BP representation U, the dummy node vector M, and the one-child array I. The BP and dummy node vector are constructed as if dummy nodes exist. Finally, we obtain the DenseZDD DZ(G) = U, M, I . The pseudo-code is given in Algorithms 2 and 3.

ZDD Operations
We show how to implement primitive ZDD operations on DenseZDD DZ = U, M, I except getnode. We give an algorithm for getnode in Section 6.
We identify each node with its real preorder rank. We can convert the real preorder rank i of a node to the its position p in the BP sequence U, that is, the position of corresponding '(' by p := select 1 (M, i) and i := rank 1 (M, p), and vice versa. Algorithms in Table 1 are as follows:

index(i)
Since the index of a node is the same as the depth, i.e., the distance from the 0-terminal node, of the node in the zero-edge tree T Z , we can obtain index(i) := depth(U, select 1 (M, i)).

topset(i, d)
The node topset(i, d) is the ancestor of node i in T Z with index d. A naive solution is to iteratively climb up T Z from node i until we reach the node with index d. However, as shown above, the index of a node is identical to its depth. By using the power of the succinct tree data structure, we can directly find the answer by topset(i, d) := rank 1 (M, level_ancestor(U, select 1 (M, i), d)). If such a node with the index i is not reachable by traversing only 0-edges, the node obtained by topset(i, d) is a dummy node. We check whether the node is a dummy node or not by using the dummy node vector. If the node is a dummy node, we return the 0-terminal node.

zero(i)
Implementing the zero operation requires a more complicated technique. Recall the insertion of dummy nodes when we construct T Z in Section 3.1. Consider the subtree in T Z induced by the set of the nodes consisting of the node i, its 0-child w, the dummy nodes d 1 , . . . , d c between i and w, and the real nodes v 1 , . . . , v k pointed by d 1 , . . . , d c . Note that one of v 1 , . . . , v k is i. Without loss of generality, v 1 has the smallest real preorder rank (highest index) among v 1 , . . . , v k , and there are edges (w, Figure 5). Computing zero(i) is equivalent to finding w. In the BP U of T Z , '('s corresponding to w, d 1 , d 2 , . . . , d c−1 , d c , v 1 continuously appear, and the values of them in B D is 1, 0, 0, . . . , 0, 0, 1, respectively. Noticing that the parent of i is one of w, d 1 , . . . , d c and that the real preorder rank of a real node is obtained by rank 1 (M, r) if the position of the corresponding '(' is r in U, we obtain zero(i) := rank 1 (M, parent(U, select 1 (M, i))).

one(i)
The operation one(i) is quite easy. The 1-child of the node i is stored in the i-th element of the one-child array I. Note that the real preorder rank of the 1-child of i is abs(i), where abs(i) is a function to get the absolute value of i. The ∅-flag of i is 1 if i ≤ 0. Otherwise, it is 0.

count(i)
Counting the number of sets in the family represented by the ZDD whose root is a node i, i.e., |F(i)|, is implemented by the same algorithm as counting on ordinary ZDDs. The pseudo-code is given in Algorithm 4. It requires an additional array C to store the cardinality of each node (for a node i , we call the cardinality of the family represented by the ZDD whose root is the node i the cardinality of the node i ). After we execute this algorithm, C[i] equals |F(i) − {∅}|. The cardinalities are computed recursively. The algorithm count(i ) is called for each node i only once in the recursion. The time complexity of count is Θ(m), where m is the number of nodes.

sample(i)
We propose two algorithms to implement sample(i). The first one is the same algorithm as random sampling on ordinary ZDDs. Before executing these algorithms, we have to run the counting algorithm to prepare the array C that stores the cardinalities of nodes. The pseudo-code is given in Algorithm 5. We begin traversal from a root node of the ZDD that represents a set family F. At each node i, we decide whether or not the index of node i is included in the output set. We know that the number of the sets including index(i) is C[one(i)]. We also know that the number of the sets not including index(i) is C[zero(i)]. Then, we generate an integer r ∈ [0, C[i]) uniformly and randomly. If r < C[zero(i)], we do not choose index(i) and go to zero(i). Otherwise, we add index(i) to the output set and go to one(i).
We continue this process until we reach the 1-terminal node. At last, we obtain a set uniformly and randomly chosen from F. The time complexity of this algorithm is O(n). return C[i]; 6: end if 7: i 0 ← zero(i); 8: card 0 ← Count(i 0 ); 9: i 1 ← one(i); 10: card 1 ← Count(abs(i 1 )); 11 Algorithm 5 Random_naive(i, empflag): Algorithm that returns a set uniformly and randomly chosen from the family of sets that is represented by a ZDD whose root is node i. Assume that Count has already been executed. The argument empflag ∈ {0, 1} means whether or not the current family of sets has the empty set. If empflag = 1, this family has the empty set. The second algorithm is based on binary search. The main idea of this algorithm is that we consider multiple nodes at once whose indices are possibly chosen as the next element of the output set. The pseudo-code is given in Algorithm 6. As well as the first algorithm, we begin traversal from a root node. Note that the first element of the output set is one of the indices of the nodes that are reachable only by 0-edges from the current node. We use topset operation to decide which index of a node is chosen. Let the current node be i. We execute binary search on these nodes. As an initial state, we consider the range (l = −1, h = index(i)] as candidates. Next, we divide this range by finding a node with index less than or equal to c = l + h + 1 /2. Such a node can be found by topset(i, c). Recall that topset(i, d) := rank 1 (M, level_ancestor(U, select 1 (M, i), d)). It is the real preorder rank of a node whose index is less than c or equal to c. If the index of the found node is less than or equal to l, we update l by l ← c and repeat the execution of topset. When a node with index x, l < x ≤ h, is found, we choose either (l, c] or (c, h] as a next candidate range for further binary search. We know the cardinality of nodes with indices h, c, and l. The cardinality card of the family of sets we consider now is the cardinality of the node with index h minus the cardinality of the node with index l. Then, generate a random integer r ∈ [0, card). If r is less than the cardinality of the node with index c, update h by h ← c. Otherwise, update l by l ← c. We continue this procedure until l + 1 = h. After that, we choose the index h as an element of the output set, and go to the 1-child of the node with index h. Again, we start binary search on the next nodes connected by continuous 0-edges. This algorithm stops when it reaches the 1-terminal node. Algorithm 6 Random_bin(i, empflag): Algorithm that returns a set uniformly and randomly chosen from the family of sets represented by the ZDD whose root is node i. This algorithm chooses the index by binary search on nodes linked by 0-edges.

Algorithm 4 Count(i):
if j = k then 10: j ← k; 11: continue; 12: end if 13: Generate an integer r ∈ [0, C[i] + empflag − card j ) uniformly and randomly. 14: if r < C[j] + empflag − card j then (idx j , idx k ] is chosen 15: i ← k; 16: idx i ← idx k ; 17: This algorithm takes O(log n) time to choose one element of an output. The time complexity of this algorithm is O(n log n). This looks worse than the previous algorithm. However, this can be better for set families consisting of small sets. Let s be the size of the largest sets in the family. Then, its time complexity is O(s log n). Therefore, this algorithm is efficient for large data sets consisting of small sets of many items.

Complexity Analysis
Let the length of balanced parentheses sequence U be 2u, where u is the number of ZDD nodes with dummy nodes. When a ZDD has m nodes and the number of items is n, u is mn in the worst case. Here, we show how to compress the BP sequence U.
We would like to decrease the space used by DenseZDD. However, we added extra data, dummy nodes, to the given ZDD. We want to bound the memory usage caused by dummy nodes. From the pseudo-code in Algorithm 2, we observe that the BP sequence U consists of at most 2m runs of identical symbols. To see this, consider the substring of U between the positions for two real nodes. There is a run consisting of consecutive ')' followed by a run consisting of consecutive '(' in the substring. We compress U by using some integer encoding scheme such as the delta-code or the gamma-code [15]. An integer x > 0 can be encoded in O(log x) bits. Since the maximum length of a run is n, U can be encoded in O(m log n) bits. The range min-max tree of U has 2m/ log m leaves. Each leaf of the tree corresponds to a substring of U that contains log m runs. Then, any tree operation can be done in O(log m) time. The range min-max tree is stored in O(m(log n + log m)/ log m) bits.
We also compress the dummy node vector B D . Since its length is 2u ≤ 2mn and there are only m ones, it can be compressed in m(2 + log m) + o(u) bits by FID. The operations select 1 and rank 1 take constant time. We can reduce the term o(u) to o(m) by using a sparse array [16]. Then, the operation select 1 is done in constant time, while rank 1 takes O(log m) time. We call the DenseZDD whose zero-edge tree and dummy node vector are compressed dummy-compressed DenseZDD.
From the discussion in the section, we can prove Theorems 1 and 2.

Hybrid Method
In this section, we show how to implement dynamic operations on DenseZDD. Namely, we implement the getnode(i, v 0 , v 1 ) operation. Our approach is to use a hybrid data structure using both the DenseZDD and a conventional dynamic ZDD. Assume that initially all the nodes are represented by a DenseZDD. Let m 0 be the number of initial nodes. In a conventional dynamic ZDD, the operation We first show how to check whether the node v := getnode(i, v 0 , v 1 ) has already existed or not. That is, we want to find a node v such that index(v) = i, zero(v) = v 0 , one(v) = v 1 . If v exists in the zero-edge tree, v is one of the 0 r -children of v 0 . Consider again the subtree of the zero-edge tree rooted at v 0 and the 0 r -children of v 0 (see the right of Figure 5). Let d p be the (possibly dummy) parent of v in the zero edge tree. The parent of all the 0 r -children of v 0 with index i in the zero edge tree is also d p . The node d p is located on the path from v 0 to the first node, say v f , among the 0 r -children of v 0 (note that since the zero edge tree is an ordered tree, we can well-define the "first" node). That is, d p is an ancestor of v f . Since the position of v f is the next of the position of v 0 in M (in the preorder), we can obtain the position of v f by select 1 (M, rank 1 (M, v 0 ) + 1). Noting that the index of d p is i − 1, we obtain the position of d p by d p = level_ancestor(U, select 1 (M, rank 1 (M, v 0 ) + 1), i − 1). Our task is to find the node v such that one(v) = v 1 among the children of d p . Since the 0 r -children v of d p are sorted in the lexicographic order of index(v ), prank(one(v )) values by the construction algorithms, we can find v by a binary search. For this, we use the degree and child operations on the zero-edge tree (recall that degree is used for obtaining the number of children of a node).
If v does not exist, we create such a node and register it to the hash table as well as a dynamic ZDD. Note that we do not update the DenseZDD, and thus if we want to treat the ZDD, say Z new , whose root is the new node as the DenseZDD, we need to construct the DenseZDD structure for Z new .
We obtain the following theorem on the time complexity.

Theorem 5.
A ZDD with m nodes on n items can be stored in O(m(log m + log n)) bits so that the getnode(i, v 0 , v 1 ) operation is done in O(log 2 m) time.

Sets of Strings
A sequence binary decision diagram (SeqBDD) [17] is a variant of a zero-suppressed binary decision diagram, customized to manipulate sets of strings. The terminology of SeqBDDs is almost the same as that of ZDDs. Let c 1 , . . . , c n be letters such that c 1 < c 2 < · · · < c n and Σ n = {c 1 , . . . , c n } be an alphabet. Let s = x 1 , . . . , x l , l ≥ 0, x 1 , . . . , x ∈ Σ n , be a string. We denote the length of s by |s| = . The empty string is denoted by ε. The concatenation of strings s = x 1 , . . . , x 1 and t = y 1 , . . . , y 2 is defined as s · t = x 1 , . . . , x 1 , y 1 , . . . , y 2 . The product of string sets L 1 and L 2 is defined as A SeqBDD is a directed acyclic graph. The difference between SeqBDD and ZDD is a restriction for indices of nodes connected by edges. For any SeqBDD nonterminal node v, the index of v's 0-child must be smaller than that of v, but the index of v's 1-child can be larger than or equal to that of v. This relaxation is required to represent string sets because a string can have the same letters at multiple positions. The definition of SeqBDDs is the following: Definition 3 (string set represented by a SeqBDD). A SeqBDD G = (V, E) rooted at a node v ∈ V represents a finite sets of strings L(v) whose letters are in Σ n defined recursively as follows: (1) If v is a terminal node: A string s = x 1 , . . . , x describes a path in the graph G starting from the root in the same way as ZDDs. For SeqBDDs, we also employ the zero-suppress rule and the sharing rule. By applying these rules as much as possible, we can obtain the canonical form for given sets of strings.
We can compress SeqBDDs by the same algorithm as the DenseZDD construction algorithm. We call it DenseSeqBDD. Since the index restriction between nodes connected by 0-edges is still valid on SeqBDDs, we can represent indices of nodes and connection by 0-edges among nodes by zero-edge trees. The main operations of SeqBDD such as index, zero, one, topset, member, count, and sample are also implemented by the same algorithms. Recall that a longest path on a ZDD is bounded by the number of items n. Therefore, the time complexities of member and sample on a ZDD are at most O(n), which means that the benefit we can gain by skipping continuous 0-edges in topset algorithm is not so large because the total number of nodes we can skip is less than n. However, a longest path on a SeqBDD is not bounded by the number of letters, and thus we can gain more benefit of skipping 0-edges because indices of nodes reached after traversing 1-edges can be the largest index. The time complexities of member and sample on a SeqBDD are O(maxlen) and O(maxlen log |Σ n |), respectively, where maxlen is the length of the longest string included in the SeqBDD.

Boolean Functions
In the above subsection, we applied our technique to decision diagrams for sets of strings. Next, we consider another decision diagram for Boolean functions, BDD. Is it possible to compress BDDs by the same technique, and are operations fast on such compressed BDDs? The answer to the first question is "Yes", but to the second question is "No". Since a BDD is also a directed acyclic graph consisting of two terminal nodes and nonterminal nodes with distinguishable two edges, the structure of a BDD can be represented by the zero-edge tree, dummy node vector, and one-child array. Therefore, we can obtain a compressed representation of a BDD. On the other hand, the membership operation on a ZDD corresponds to the operation to determine whether or not an assignment of Boolean variables satisfies the Boolean function represented by a BDD. Since the size of query for the assignment operation is always the number of all Boolean variables, we cannot skip any assignment to variables. As a result, membership operation and random sampling operation are not accelerated on a BDD even if we use the DenseZDD technique.

Experimental Results
We ran experiments to evaluate the compression, construction, and operation times of DenseZDDs. We implemented the algorithms described in Sections 3 and 3.2 in C/C++ languages on top of the SAPPORO BDD package, which is available at https://github.com/takemaru/graphillion/tree/ master/src/SAPPOROBDD and can be found as an internal library of Graphillion [18]. The package uses 32 bytes per ZDD node. The breakdown of 32 bytes of a ZDD node is as follows: 5 bytes as a pointer for the 1-child, 5 bytes as a pointer for the 0-child, 2 bytes as an index. In addition, we use a closed hash table to execute getnode operation. The size of the hash table of SAPPOROBDD is 5 × 2 × n bytes, and 5 bytes per each node to chain ZDD nodes that have the same key computed from its attribute-triple. Since DenseZDD does not require such hash table to execute getnode, we consider that the space used by the hash table should be included in the memory consumption of ZDD nodes. The experiments are performed on a machine with 3.70 GHz Intel Core i7-8700K and 64 GB memory running Windows Subsystem for Linux (Ubuntu) on Windows 10.
We show the characteristics of the data sets in Table 2. As artificial data sets, we use rectrxw, which represents families of sets r−1 i=0 {{iw + 1}, . . . , {iw + w}}. Data set randomjoink is a ZDD that represents the join C 1 · · · C 4 of four ZDDs for random families C 1 , . . . , C 4 consisting of k sets of size one drawn from the set of n = 32768 items. Data set bddqueenk is a ZDD that stores all solutions for k-queen problem.
As real data sets, data set filename:p is a ZDD that is constructed from itemset data (http://fimi.ua. ac.be) by using the algorithm of all frequent itemset mining, named LCM over ZDD [3], with minimum support p.
The other ZDDs are constructed from Boolean functions data (https://ddd.fit.cvut.cz/ prj/Benchmarks/). These data are commonly used to evaluate the size of ZDD-based data structures [2,19,20]. These ZDDs represent Boolean functions in a conjunctive normal form as families of sets of literals. For example, a function x 1 x 2 + x 2 x 3 + x 3 x 1 is translated into the family of sets {{x 1 , x 2 }, {x 2 , x 3 }, {x 3 , x 1 }}. In Table 3, we show the sizes of the original ZDDs, the DenseZDDs, the dummy-compressed DenseZDDs and their compression ratios. The dummy node ratio, denoted by δ, of a DenseZDD is the ratio of the number of dummy nodes to that of both real nodes and dummy nodes in the DenseZDD. We compressed FID for the dummy node vector if the dummy node ratio is more than 75%. We observe that DenseZDDs are five to eight times smaller than original ZDDs, and dummy-compressed DenseZDDs are six to ten times smaller than original ZDDs. We also observe that dummy node ratios highly depend on data. They ranged from about 5% to 30%. For each data set, the higher the dummy node ratio was, the lower the compression ratio of the size of the DenseZDD to that of the ZDD became, and the higher the compression ratio of the size of the dummy-compressed DenseZDD to that of the DenseZDD became. In Table 4, we show the conversion time from the ZDD to the DenseZDD and the getnode time on each structure for each data set. Conversion time is composed of three parts: converting a given ZDD to raw parentheses, bits, and integers, constructing the succinct representation of them, and compressing the BP of the zero-edge tree. The conversion time is almost linear in the input size. This result shows its scalability for large data. For each data set except for rectrxw, the getnode time on the DenseZDD is almost two times larger than that on the ZDD and that on the dummy-compressed DenseZDD is five to twenty times larger than that on the ZDD. In Table 5, we show the traversal time and the search time. The traverse operation uses zero(i) and one(i), while the membership operation uses topset(i, c) and one(i). We observe that the DenseZDD requires about three or four times longer traversal time and about 3-1500 times shorter search time than the original ZDD for each data set except for Boolean functions. These results show the efficiency of our algorithm of the topset(i, c) operation on DenseZDD using level ancestor operations. The traversal times on dummy-compressed DenseZDDs are seven times slower and the search time on them are two times slower than DenseZDDs. In Table 6, we show the counting time and the random sampling time. For each data set, the counting time on the DenseZDD and the random sampling time of the naive algorithm on both the DenseZDD and the dummy-compressed DenseZDD are not so far from those on the ZDD, while the counting time on the dummy-compressed DenseZDD is two to ten times larger than the ZDD. For each data set, the random sampling time of the binary search-based algorithm is two to hundred times smaller than the ZDD. For Boolean functions, the algorithm is three to six times slower. There is not much difference between DenseZDDs and dummy-compressed DenseZDDs.
From the above results, we conclude that DenseZDDs are more compact than ordinary ZDDs unless the dummy node ratio is extremely high, and the membership operations for DenseZDDs are much faster if n is large or the number of 0-edges from terminal nodes to each node is large. We observed that DenseZDD makes traversal time approximately triple, search time approximately one-tenth, and random sampling time approximately one-third compared to the original ZDDs.

Conclusions
In this paper, we have presented a compressed index for a static ZDD, named DenseZDD. We have also proposed a hybrid method for dynamic operations on DenseZDD such that we can manipulate a DenseZDD and a conventional ZDD together.