=====================================
Package "Bit::Vector" Version 4.2
=====================================
for Perl version 5.000 and higher
Copyright (c) 1995, 1996, 1997 by Steffen Beyer. All rights reserved.
This package is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
==================
Complexity
==================
This document provides an analysis of the complexity of the methods
implemented in the C library ("BitVector.c") of this package (which
is the core of the "Bit::Vector" module).
Although achieving the best possible efficiency was a major issue
in the development of this package, beware that using bit vectors
may not be the most efficient way to solve your problem!
See the "Kruskal" module in this distribution (and its associated
man page) for an example where this is true!
This complexity analysis is provided here in order to give you the
necessary information to carefully choose the most appropriate al-
gorithm for your problem.
Table of methods and their complexities:
----------------------------------------
In the following, "n" is the number of bits in your bit vector,
and "b" is the number of bits in a machine word on your system.
(If the number of bits in your bit vector is not a multiple of "b",
set "n" to the next greater multiple of "b" here!)
Numbers followed by a right parenthesis (to the right of the
following table) refer to the "Footnotes" section below.
Names in angle brackets "<<...>>" in the following table refer to
Perl programs to calculate the respective average case complexity
as shown in the section "Average case studies" below.
Note that the values given below should be read as "O( ... )"!
(The function "O()" (= "of the order of") is a special convention in
complexity theory which abstracts from constant terms and factors.
For a mathematically accurate definition, see the relevant literature
of complexity theory!)
best average worst
case case case
new n/b n/b n/b 0)
DESTROY 1 1 1 1)
Size 1 1 1
Resize 1 (n/b+1)/2 n/b 2)
Empty n/b n/b n/b
Fill n/b n/b n/b
Flip n/b n/b n/b
Interval_Empty 1 <> n/b
Interval_Fill 1 <> n/b
Interval_Flip 1 <> n/b
Interval_Scan_inc 1 ? n/b+2b 3)
Interval_Scan_dec 1 ? n/b+2b 3)
Bit_Off 1 1 1
Bit_On 1 1 1
bit_flip 1 1 1
bit_test 1 1 1
Norm n/b <> n/b+n 4)
Min 1 <> n/b+b 5)
Max 1 <> n/b+b 5)
Union n/b n/b n/b
Intersection n/b n/b n/b
Difference n/b n/b n/b
ExclusiveOr n/b n/b n/b
Complement n/b n/b n/b
is_empty 1 <> n/b 6)
is_full 1 <> n/b 6)
equal 1 <> n/b 6)
subset 1 <> n/b 6)
lexorder 1 <> n/b 7)
Compare 1 <> n/b 7)
Copy n/b n/b n/b
rotate_left n/b n/b n/b
rotate_right n/b n/b n/b
shift_left n/b n/b n/b
shift_right n/b n/b n/b
Move_Left --- see special section below ---
Move_Right --- see special section below ---
increment 1 <> n/b 8)
decrement 1 <> n/b 8)
to_String n/b+n/4 n/b+n/4 n/b+n/4 9)
from_string --- see special section below ---
Footnotes:
----------
0) "new": Complexity is n/b here because a bit vector is always initialized
so that all bits are cleared (set to the "off" state). The complexity
associated with the allocation of memory is NOT taken into account here.
1) "DESTROY": The complexity associated with the de-allocation of memory
is NOT taken into account here.
2) "Resize": "n" is the number of bits in the NEW bit vector here.
"best case" is when the new bit vector is smaller than the old bit
vector. "worst case" is when the new bit vector is larger than the
old one, in which case the new bit vector has to be initialized and
the old bit vector has to be copied to the new one. "average case"
assumes that half of the time the new bit vector is larger, half
of the time it is smaller than the old bit vector. Note that the
complexity associated with the allocation and de-allocation of
memory is NOT taken into account here.
3) "Interval_Scan_inc", "Interval_Scan_dec": In principle these two methods
perform the same function as "Min" and "Max", respectively, but they do
so TWICE: Once to find the beginning of a block of set bits, and once to
find the end of that block of set bits. In contrast to "Min" and "Max",
however, they can start their search at an arbitrary bit in the bit vector,
whereas "Min" and "Max" always start at the beginning (least significant
bit) and the end (most significant bit) of the given bit vector, respec-
tively.
The complexity is n/b for an empty bit vector, i.e., when all bits are
cleared (set to the "off" state).
No average case analysis is provided here because an analytical formula
is very hard to find for this pair of methods, and because a brute-force
approach (by generating all possible values for a bit vector of a given
length and calculating the complexity for all possible starting points)
is prohibitively time-consuming.
4) "Norm": "worst case" is when the bit vector is full, i.e., when all
bits are set to the "on" state. "best case" is when the bit vector
is empty, i.e., when all bits are cleared (set to the "off" state).
5) "Min", "Max": "best case" is when the first (= least significant) bit
(in the case of "Min") or the last (= most significant) bit (in the
case of "Max") is set. "worst case" is when ONLY the last (= most
significant) bit (in the case of "Min") or ONLY the first (= least
significant) bit (in the case of "Max") is set. The algorithm has
complexity n/b for a completely empty bit vector.
6) "is_empty", "is_full", "equal", "subset": "best case" is when the
condition is false at the first word. "worst case" is when the
condition is true or if the condition is true for all words except
for the last one.
7) "lexorder", "Compare": "best case" is when the two bit vectors differ
at the first word. "worst case" is when the two bit vectors differ
only at the last word or not at all.
8) "increment", "decrement": "best case" is when no carry-over accurs
from the first to the second word. "worst case" is when a carry-over
occurs in every word or every except the last word, so that the
carry-over has to propagate through the whole bit vector.
9) "to_String": Complexity is n/b plus n/4 here because each word of the
bit vector has to be fetched and then written one (hexadecimal) charac-
ter (one byte) at a time (and each hexadecimal digit is worth 4 bits!).
Methods "Move_Left" and "Move_Right":
-------------------------------------
Note that these methods are far more efficient than calling "shift_left()"
or "shift_right()" the desired number of times!
For the sake of simplicity, we will assume here that "n" (the number of
bits in your bit vector) and "b" (the number of bits in a machine word
on your system) are both powers of two.
Shifting your bit vector by "k" bits to the left or right then has the
following complexity:
(n div b) if k >= n
((k mod b) + 1) * (n div b) if k >= b and 1 <= k < n
k * (n div b) if k < b and 1 <= k < n
0 if k == 0
For k >= b and k < n, the complexity is as stated above because the two
methods "Move_Left" and "Move_Right" perform (k mod b) bitwise shifts
(by calling the methods "shift_left()" and "shift_right()" that number
of times) plus (k div b) wordwise shifts.
The bitwise shifts have a complexity of (n div b) each, whereas the (k div b)
wordwise shifts can be executed in a single sweep through the bit vector,
i.e., they can be achieved by ((n div b) - (k div b)) word copy operations
plus (k div b) word assignment operations with zero.
Taken together the word copy and word assignment operations give a total
complexity of (n div b). The sum of the two terms (for bitwise and for
wordwise shifts) yields the formula above.
Note the trade-off between the two factors in the resulting formula: The
size of a machine word, "b", should be as small as possible to minimize
the factor ((k mod b) + 1), whereas in order to minimize the factor
(n div b), "b" should be as large as possible.
This is in contrast with all other methods in this module whose complexity
is generally proportional to (n div b) (or which is 1, for some).
For k >= 1 and k < b, the methods "shift_left()" and "shift_right()" are
simply called the desired number of times, each call having a complexity
of (n div b).
For k >= n, the complexity is (n div b) because this is the same as setting
all bits to zero (i.e., to the "off" state).
Actually, the method "Empty()" gets called internally every time when k >= n
is detected. This is to avoid unnecessary extra calls of the two methods
"shift_left()" and "shift_right()" which would occur whenever (k mod b)
wasn't zero.
For an "average case" analysis, see the program "<>" in the section
"Average case studies" below. Note that this program assumes an evenly
distributed stochastic variable, i.e., it assigns the same probability
to all possible values of "k" (from 1 to "n")!
Finally, note that the formula given above is topped by "n". Here's the
proof:
(( k mod b) + 1) * (n div b)
<= (((b-1) mod b) + 1) * (n div b)
== ( (b-1) + 1) * (n div b)
== ( b ) * (n div b)
== n
(provided that "n" is a multiple of "b", which we may safely assume here
because we said earlier that "n" and "b" were both powers of two!)
(If "n" is initially smaller than "b", we set "n" equal to "b" here because
we said in the beginning that we would set "n" to the next greater multiple
of "b" if "n" wasn't a multiple of "b" yet!)
(Note that it is trivial to show that the other parts of the formula given
above are also topped by "n"!)
This is a reasonable complexity for this type of problem: Think of it
as copying every single of the "n" bits over the required distance of
"k" (which gives (n-k) bit copy operations) and setting the remaining
"k" bits to zero.
Method "from_string":
---------------------
The complexity of this method is determined by the formula
(n div b) + l if 0 <= 4 * l <= n
(n div b) + (n div 4) if 4 * l > n
where "l" is the number of bytes in (i.e., the length of) the input
string.
(This formula assumes that "n" is a multiple of "b" and that "b" is
a power of two which is divisible by 4!)
When the length "l" of the input string times 4 (every byte in the input
string (= one hexadecimal character) is worth 4 bits!) is less than or
equal to "n", the complexity is "l" plus (n div b) because every byte has
to be read and shifted to its appropriate location in a word (hence the
term "l"!), then that word has to be copied to its correct location in
the bit vector (hence the term (n div b)!).
If the length "l" of the input string is less than (n div 4), i.e., if the
input string is shorter than what is needed to fill the given bit vector
(i.e., if it would leave some of the bits in the bit vector undefined),
or if an invalid character is detected after "l" valid hexadecimal digits,
then the remaining words in the bit vector are set to zero. This is another
reason for the term (n div b) in the formula above.
If the length "l" of the input string is greater than (n div 4), i.e., if
there are more input characters than are needed to fill the given bit vector,
then the superfluous characters are simply ignored. This results in the
term (n div 4) as the number of bytes being processed, as stated in the
formula above.
Note that no average case analysis is provided for this method for the
following two reasons:
First, it is unrealistic to assume an even distribution for all values
of "l".
It is also very difficult to assign a realistic probability to all values
of "l" greater than (n div 4).
Second, the formula given above is very simple, so it is extremely easy
to write a program yourself which sums up the complexities for each value
of "l", multiplying each value with the probability you choose to assign
to it (you could use an array, for instance, to store these probability
values).
Average case studies:
---------------------
For values of "n" not too large, the average case complexity of the
methods Interval_Empty, Interval_Fill, Interval_Flip, Norm, Min, Max,
is_empty, is_full, equal, lexorder, Compare, increment, decrement,
subset, Move_Left and Move_Right can be calculated with the following
Perl subroutines (note the terms "2**$n" and even "2**(2*$n)" below
which limit the computable complexities!) according to the formula:
E[X] = SUM{ i=1..n } i * p(i)
(where 1..n are the possible values the stochastic variable X can assume,
p(i) is the probability of X having the value i and E[X] is the average
outcome of X)
Note that the following programs all assume an evenly distributed stochastic
variable, i.e., they assign the same probability to every possible combination
of set/cleared bits of a given bit vector!
The result is almost constant n/3b for Interval_Empty, Interval_Fill and
Interval_Flip, more or less close to n for Norm, vaguely in the range n/2b
to n/b for Min and Max, close to 1 for is_empty, is_full, equal, lexorder,
Compare, increment, decrement and subset and approximately n/2 for Move_Left
and Move_Right.
<> :
sub average_case # methods "Interval_Empty", "Interval_Fill"
{ # and "Interval_Flip"
my($n,$b) = @_;
my($k,$s,$t);
$k = int($n / $b);
if (($k * $b) < $n) { $n = ++$k * $b; }
$s = $n * ( $n * ($n + 3 * $b) + $b * (3 - $b) ) / (6 * $b);
$t = $n * ($n + 1) / 2;
return($s / $t);
}
<> :
sub average_case # method "Norm"
{
my($n,$b) = @_;
my($i,@j,$c,$k,$l,$p,$s,$sum);
$k = int($n / $b);
if (($k * $b) < $n) { $n = ++$k * $b; }
$sum = 0;
for ( $i = $k; $i >= 1; --$i )
{
$j[$i] = 0;
}
$c = 0;
while (! $c)
{
$p = 1;
$s = $k;
for ( $i = $k; $i >= 1; --$i )
{
$l = $j[$i];
$s += $l;
if ($l >= 1) { --$l; }
$p *= 2**$l;
}
$sum += $s * $p;
$c = 1;
for ( $i = $k; ($i >= 1) && $c; --$i )
{
if ($c = (++$j[$i] > $b)) { $j[$i] = 0; }
}
}
return($sum / 2**$n);
}
<> :
sub average_case # methods "Min" and "Max"
{
my($n,$b) = @_;
my($i,$j,$k,$l,$o,$p,$sum);
$k = int($n / $b);
if (($k * $b) < $n) { $n = ++$k * $b; }
$sum = $k;
for ( $l = 2; $l <= ($k+$b); ++$l )
{
$o = $l - 2;
if ($o > $k - 1) { $o = $k - 1; }
$p = 0;
for ( $i = 0; $i <= $o; ++$i )
{
$j = $l - $i - 1;
if ($j <= $b)
{
$p += 2**($n-$i*$b-$j);
}
}
$sum += $l * $p;
}
return($sum / 2**$n);
}
<> :
sub average_case # methods "is_empty", "is_full", "equal", "lexorder",
{ # "Compare", "increment" and "decrement"
my($n,$b) = @_;
my($i,$k,$t1,$t2,$sum);
$k = int($n / $b);
if (($k * $b) < $n) { $n = ++$k * $b; }
$t1 = 0;
$t2 = 0;
$sum = 0;
for ( $i = $k; $i >= 1; --$i )
{
$t1 = 2**(($k-$i+1)*$b);
$sum += ($t1 - $t2) * $i;
$t2 = $t1;
}
return($sum / 2**$n);
}
sub average_case # alternative program
{
my($n,$b) = @_;
my($i,$k,$sum);
$k = int($n / $b);
if (($k * $b) < $n) { $n = ++$k * $b; }
$sum = 0;
for ( $i = 1; $i <= $k; $i++ )
{
$sum += (2 ** ($b * ($k - $i))) * $i;
}
$sum *= (2 ** $b) - 1;
$sum += $k;
return($sum / 2**$n);
}
<> :
sub average_case # method "subset"
{
my($n,$b) = @_;
my($i,$j,$k,$l,$s,$t1,$t2,$sum);
$k = int($n / $b);
if (($k * $b) < $n) { $n = ++$k * $b; }
$t1 = 0;
$t2 = 0;
$sum = 0;
for ( $i = $k; $i >= 1; --$i )
{
$s = 0;
$l = ($i - 1) * $b;
for ( $j = 0; $j <= $l; ++$j )
{
$s += 2**$j * &binomial($l,$j);
}
$t1 = $s * 2**(2*($n-$l));
$sum += ($t1 - $t2) * $i;
$t2 = $t1;
}
return($sum / 2**(2*$n));
}
sub binomial
{
my($n,$k) = @_;
my($prod) = 1;
my($j) = 0;
if (($n <= 0) || ($k <= 0) || ($n <= $k)) { return(1); }
if ($k > $n - $k) { $k = $n - $k; }
while ($j < $k)
{
$prod *= $n--;
$prod /= ++$j;
}
return(int($prod + 0.5));
}
<> :
sub average_case # methods "Move_Left" and "Move_Right"
{
my($n,$b) = @_;
my($k,$sum);
$k = int($n / $b);
if (($k * $b) < $n) { $n = ++$k * $b; }
$sum = 1; # accounts for $k == $n
for ( $k = 1; $k < $n; $k++ )
{
if ($k < $b)
{
$sum += $k;
}
else
{
$sum += ($k % $b) + 1;
}
}
return($sum / $b); # = (sum * n/b) / n
}