2

I have a problem and would appreciate your help. I have to generate a DAG with 2^N nodes which are valued form 0 to 2^(N-1), with this property: There is directed edge between nodes x and y (x and y being their values) if x < y and there is non-negative integer p such as x ⊕ y = 2^p. So far I've tried two nested for loops but this solution is too slow when it comes to number of nodes as high as 2^15. Here is a code snippet:

#include <iostream>
#include <vector>
#include <math.h>
typedef unsigned int unint;
using namespace std;
class Node
{
    friend class DAG;
private:
    unint value;
    vector<Node* > neighbourTo;
    vector<Node* > neighbors;
public:
    Node(unint );
};
Node::Node(unint _value)
    : value(_value) {}
class DAG
{
private:
    int noNodes;
    vector<Node* > nodes;
public:
    DAG(int );
    void initializeNodes(int ,int );
    int isPowerOf2(unsigned int );
    int getMaxNaighbourTo(int );
    int getMinNeighbor(int );
    int numberOfPathsLengthK(int );
    int recursion(Node& , int );
    void print();
};
DAG::DAG(int size)
{
    noNodes = size;

    nodes.resize(noNodes);
    int i, j;

    initializeNodes(0, noNodes-1);
    for(i = 0; i < noNodes-1; i++)
    {
        for(j = i+1; j < noNodes; j++)
        {
            if(isPowerOf2(i ^ j))
            {
                nodes[i]->neighbors.push_back(nodes[j]);
                nodes[j]->neighbourTo.push_back(nodes[i]);
            }
        }
    }
}
void DAG::initializeNodes(int min, int max)
{
    if(max == min)
        nodes[max] = new Node(max);
    else
    {
        int s = (max + min)/2;
        initializeNodes(min, s);
        initializeNodes(s+1, max);
    }
}
int DAG::isPowerOf2(unsigned int value)
{
    return ((value != 0) && !(value & (value - 1)));
}
int DAG::getMaxNaighbourTo(int index)
{
    if(index > 0 && index <= (noNodes-1))
    {
        int size = nodes[index]->neighbourTo.size();
        return nodes[index]->neighbourTo[size-1]->value;
    }
    return -1;
}
int DAG::getMinNeighbor(int index)
{
    if(index >= 0 && index < (noNodes-1))
        return nodes[index]->neighbors[0]->value;
    return -1;
}
int DAG::numberOfPathsLengthK(int K)
{
    if(K <= 0)
        return 0;
    long int paths = 0;
    for(int i = 0; i < nodes.size(); i++)
    {
        paths += recursion(*nodes[i], K - 1);
    }
    return (paths % 100003);
}
int DAG::recursion(Node& node, int K)
{
    if( K <= 0 )
        return node.neighbors.size();
    else
    {
        long int paths = 0;
        for(int i = 0; i < node.neighbors.size(); i++)
        {
            paths += recursion(*node.neighbors[i], K - 1);
        }
        return paths;
    }
}
void DAG::print()
{
    for(int i = 0; i < nodes.size(); i++)
    {
        cout << "Node: " << nodes[i]->value << "\tNeighbors: ";
        for(int j = 0; j < nodes[i]->neighbors.size(); j++)
        {
            cout << nodes[i]->neighbors[j]->value << " ";
        }
        cout << endl;
    }
}
int main()
{
    int
    N, M, K,
    i, j;
    cin >> N >> M >> K;
    DAG graf(pow(2, N));
    graf.print();
    cout << "==1==" << endl;
    cout << graf.getMaxNaighbourTo(M) << endl;
    cout << "==2==" << endl;
    cout << graf.getMinNeighbor(M) << endl;
    cout << "==3==" << endl;
    cout << graf.numberOfPathsLengthK(K) << endl;
    return 0;
}

Here is a simple output:

4 3 2
Node: 0     Neighbors: 1 2 4 8
Node: 1     Neighbors: 3 5 9
Node: 2     Neighbors: 3 6 10
Node: 3     Neighbors: 7 11
Node: 4     Neighbors: 5 6 12
Node: 5     Neighbors: 7 13
Node: 6     Neighbors: 7 14
Node: 7     Neighbors: 15
Node: 8     Neighbors: 9 10 12
Node: 9     Neighbors: 11 13
Node: 10    Neighbors: 11 14
Node: 11    Neighbors: 15
Node: 12    Neighbors: 13 14
Node: 13    Neighbors: 15
Node: 14    Neighbors: 15
Node: 15    Neighbors:
2
7
48

nodes is a vector of Node pointers, and Node a is a class which holds the node value and two vectors, one Node pointers to the neighbors of the current node, and another is a Node pointers to the nodes that the current node is neighbor to. The above code is in C++. I apologize for any grammatical errors. English is not my mother tongue.

mkorunoski
  • 71
  • 2
  • 7
  • Instead of generating `j` and testing whether they differ in 1 bit with `i`, why not just start from `i` and generate only numbers that differ from it in one bit? – harold Jun 02 '15 at 10:30
  • All the class methods and attributes are on my mother tongue. It'll take a wile to translate. Should I do so? – mkorunoski Jun 02 '15 at 10:31
  • @harold do you mean iterate through numbers that differ by one bit from i when it comes to initializing the vector of neighbors of the current i? – mkorunoski Jun 02 '15 at 10:34
  • 1
    If i understand the question correctly, You could just add 2,4,8,16,32... to i instead of testing. – anselm Jun 02 '15 at 10:34
  • Yes exactly. Note that that's not the same as adding a power of two as anselm suggests, you have to xor with powers of two. – harold Jun 02 '15 at 10:36

2 Answers2

2

The first obvious non-algorithm performance gain would be not to build the graph, if you only need to print the neighbors you can do so without having to create the data structure. The second improvement here would be to avoid flushing the stream with each output line...

For the algorithmic improvements, given a number N=0011010 (for example, any number is valid), you need to figure out which number fulfill the two requirements, N xor M is a power of two, and N > M. The first requirement means that the two numbers differ exactly in one bit, the second requirement means that the bit must be lit in M and not lit in N, so the answer looking just at the bits above would be: M = { 1011010, 0111010, 0011110, 0011011 }. Now you can get all those by scanning each bit in N, if it is 0 then set it and print the value.

// assert that 'bits < CHAR_BITS * sizeof(unsigned)'
const unsigned int max = 1u << bits;
for (unsigned int n = 1; n < max; ++n) {
   std::cout << "Node: " << n << " Neighbors: ";
   for (unsigned int bit = 0; i < bits; ++i) {
      unsigned int mask = 1 << bit;
      if (!(n & mask)) {
         std::cout << (n | mask);
      }
   }
   std::cout << '\n';
}

For the min and max neighbors of a given node, you can apply the same reasoning, the max reachable neighbor of a given number N would be M such that the highest 0 bit in N is lit. For the minimum reachable neighbor you need M such that the lowest 0 bit is set to 1.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • David, I need the data for additional analysis, that is why I store all of it. About your solution proposal, that is what I did lastly with the help of few on the forum. However, thank you for your time. – mkorunoski Jun 02 '15 at 12:35
  • 1
    @user242760: a single call to malloc is worth a few bit operations. It might be worth the effort to try to *generate* the neighbors as needed for whatever processing you are doing. For example, the number of neighbors reachable from a node is the number of 0s in the bit pattern, the max reachable neighbor is setting the highest 0 to 1, the min is setting the lowest 1 to 0... – David Rodríguez - dribeas Jun 02 '15 at 12:38
0

I had some free time to write a sketch, have a look:

struct node
{
    std::vector<std::shared_ptr<node> > link;
};

int main()
{
    int N = 4;

    int M = 1<<N;
    std::vector<std::shared_ptr<node> > tree(M, std::make_shared<node>());

    for(int i=0;i<M;++i)
    {
        std::cout<<"node: "<<i<<" is connected to:\n";

        for(int p=0;p<N;++p)
        {
            int j= (1<<p) ^ i;    //this is the evaluation you asked for
                                  //it's the inverse of i ^ j = 2^p

            if(j<=i) continue;                
            tree[i]->link.push_back(tree[j]);

            std::cout<<j<<"  ";
        }
        std::cout<<std::endl;        
    }
}

DEMO

For N=4, i.e. 2^4=16 nodes, the program prints

node: 0 is connected to:
1  2  4  8  
node: 1 is connected to:
3  5  9  
node: 2 is connected to:
3  6  10  
node: 3 is connected to:
7  11  
node: 4 is connected to:
5  6  12  
node: 5 is connected to:
7  13  
node: 6 is connected to:
7  14  
node: 7 is connected to:
15  
node: 8 is connected to:
9  10  12  
node: 9 is connected to:
11  13  
node: 10 is connected to:
11  14  
node: 11 is connected to:
15  
node: 12 is connected to:
13  14  
node: 13 is connected to:
15  
node: 14 is connected to:
15  
node: 15 is connected to:

I hope that is what you were looking for. Have fun.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • I see you made an edit while I was answering. Please have a look whether the results are correct, otherwise I can refine it. – davidhigh Jun 02 '15 at 11:14
  • can you tell me your algorithm's time complexity? – mkorunoski Jun 02 '15 at 11:17
  • @user242760: the complexity is `N * 2^N`, but the code is wrong ... I interpreted your xor-sign as a "plus". I'll try to fix it. – davidhigh Jun 02 '15 at 11:23
  • @user242760: fixed it with the help of [this thread](http://stackoverflow.com/questions/14279866/what-is-inverse-function-to-xor). The complexity is still `O(N * 2^N)` (instead of the `O(2 ^ 2N)` of the brute force approach) – davidhigh Jun 02 '15 at 11:30
  • davidhigh, can you propose a faster algorithm for finding the number of paths of length K in the generated graph? I tried recursive algorithm but it is slower than I expected. The code is above. Thank you in advance – mkorunoski Jun 02 '15 at 15:20