I am trying to find the Time Complexity of this algorithm.
The iterative: algorithm produces all the bit-strings within a given Hamming distance, from the input bit-string. It generates all increasing sequences 0 <= a[0] < ... < a[dist-1] < strlen(num)
, and reverts bits at corresponding indices.
The vector a
is supposed to keep indices for which bits have to be inverted. So if a contains the current index i
, we print 1 instead of 0 and vice versa. Otherwise we print the bit as is (see else-part), as shown below:
// e.g. hamming("0000", 2);
void hamming(const char* num, size_t dist) {
assert(dist > 0);
vector<int> a(dist);
size_t k = 0, n = strlen(num);
a[k] = -1;
while (true)
if (++a[k] >= n)
if (k == 0)
return;
else {
--k;
continue;
}
else
if (k == dist - 1) {
// this is an O(n) operation and will be called
// (n choose dist) times, in total.
print(num, a);
}
else {
a[k+1] = a[k];
++k;
}
}
What is the Time Complexity of this algorithm?
My attempt says:
dist * n + (n choose t) * n + 2
but this seems not to be true, consider the following examples, all with dist = 2:
len = 3, (3 choose 2) = 3 * O(n), 10 while iterations
len = 4, (4 choose 2) = 6 * O(n), 15 while iterations
len = 5, (5 choose 2) = 9 * O(n), 21 while iterations
len = 6, (6 choose 2) = 15 * O(n), 28 while iterations
Here are two representative runs (with the print to be happening at the start of the loop):
000, len = 3
k = 0, total_iter = 1
vector a = -1 0
k = 1, total_iter = 2
vector a = 0 0
Paid O(n)
k = 1, total_iter = 3
vector a = 0 1
Paid O(n)
k = 1, total_iter = 4
vector a = 0 2
k = 0, total_iter = 5
vector a = 0 3
k = 1, total_iter = 6
vector a = 1 1
Paid O(n)
k = 1, total_iter = 7
vector a = 1 2
k = 0, total_iter = 8
vector a = 1 3
k = 1, total_iter = 9
vector a = 2 2
k = 0, total_iter = 10
vector a = 2 3
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gsamaras@pythagoras:~/Desktop/generate_bitStrings_HammDistanceT$ ./iter
0000, len = 4
k = 0, total_iter = 1
vector a = -1 0
k = 1, total_iter = 2
vector a = 0 0
Paid O(n)
k = 1, total_iter = 3
vector a = 0 1
Paid O(n)
k = 1, total_iter = 4
vector a = 0 2
Paid O(n)
k = 1, total_iter = 5
vector a = 0 3
k = 0, total_iter = 6
vector a = 0 4
k = 1, total_iter = 7
vector a = 1 1
Paid O(n)
k = 1, total_iter = 8
vector a = 1 2
Paid O(n)
k = 1, total_iter = 9
vector a = 1 3
k = 0, total_iter = 10
vector a = 1 4
k = 1, total_iter = 11
vector a = 2 2
Paid O(n)
k = 1, total_iter = 12
vector a = 2 3
k = 0, total_iter = 13
vector a = 2 4
k = 1, total_iter = 14
vector a = 3 3
k = 0, total_iter = 15
vector a = 3 4
Notice, that given
n
which represents the length, andt
which represents the distance required, the number of increasing, non-negative series oft
integers between1
andn
(or in indices form, between0
andn-1
) is indeedn choose t
, since we pickt
distinct indices.The problem occurs with your generation of those series:
-First, notice that for example in the case of length 4, you actually go over 5 different indices, 0 to 4.
-Secondly, notice that you are taking in account series with identical indices (in the case of
t=2
, its0 0, 1 1, 2 2
and so on), and generally, you would go through every non-decreasing series, instead of through every increasing series.So for calculating the TC of your program, make sure you take that into account.
Hint: try to make one-to-one correspondence from the universe of those series, to the universe of integer solutions to some equation.
If you need the direct solution, take a look here : https://math.stackexchange.com/questions/432496/number-of-non-decreasing-sequences-of-length-m
The final solution is
(n+t-1) choose (t)
, but noticing the first bullet, in your program, its actually((n+1)+t-1) choose (t)
, since you loop with one extra index. Denote((n+1)+t-1) choose (t) =: A
,n choose t =: B
overall we get
O(1) + B*O(n) + (A-B)*O(1)
The while loop is somewhat clever and subtle, and it's arguable that it's doing two different things (or even three if you count the initialisation of
a
). That's what's making your complexity calculations challenging, and it's also less efficient than it could be.In the abstract, to incrementally compute the next set of indices from the current one, the idea is to find the last index,
i
, that's less thann-dist+i
, increment it, and set the following indexes toa[i]+1
,a[i]+2
, and so on.For example, if dist=5, n=11 and your indexes are:
Then
5
is the last value less thann-dist+i
(becausen-dist
is 6, and 10=6+4, 9=6+3, but 5<6+2).So we increment
5
, and set the subsequent integers to get the set of indexes:Now consider how your code runs, assuming
k=4
a[k] + 1
is 11, sok
becomes 3.++a[k]
is 10, soa[k+1]
becomes 10, andk
becomes 4.++a[k]
is 11, sok
becomes 3.++a[k]
is 11, sok
becomes 2.++a[k]
is 6, soa[k+1]
becomes 6, andk
becomes 3.++a[k]
is 7, soa[k+1]
becomes 7, andk
becomes 4.++a[k]
is 8, and we continue to call theprint
function.This code is correct, but it's not efficient because
k
scuttles backwards and forwards as it's searching for the highest index that can be incremented without causing an overflow in the higher indices. In fact, if the highest index isj
from the end, the code uses a non-linear number iterations of the while loop. You can easily demonstrate this yourself if you trace how many iterations of the while loop occur whenn==dist
for different values ofn
. There is exactly one line of output, but you'll see an O(2^n) growth in the number of iterations (in fact, you'll see 2^(n+1)-2 iterations).This scuttling makes your code needlessly inefficient, and also hard to analyse.
Instead, you can write the code in a more direct way:
Now, each time through the while loop produces a new set of indexes. The exact cost per iteration is not straightforward, but since
print
is O(n), and the remaining code in the while loop is at worst O(dist), the overall cost is O(N_INCR_SEQ(n, dist) * n), where N_INCR_SEQ(n, dist) is the number of increasing sequences of natural numbers < n of length dist. Someone in the comments provides a link that gives a formula for this.