【问题标题】:Initialization and usage of a graph图的初始化和使用
【发布时间】:2013-03-30 23:23:26
【问题描述】:

我正在尝试实现一个图表来存储来自文本文件的数据列表,如下所示:

0,1 (node 0 links to 1)
0,2 (node 0 links to 2)
1,2 (node 1 links to 2)
2,1 (node 2 links to 1)

无论如何,我在定义结构时遇到了麻烦。我在使用矩阵或相邻列表之间犹豫不决,但我想我会使用列表,我只是不确定如何定义结构。我应该使用可变大小的数组、链表还是其他东西?哪种方式最简单?

struct grph{

};

struct node{

    //ID of the node
    int id;

};

其次,我如何将数据存储到这个图表中,这是我遇到的最麻烦的地方。本质上,我认为这很容易,就像您只需在末尾添加一个节点的链表一样。这里的区别在于每个节点可以指向许多不同的节点或根本不指向。如何将图形结构与所有链接的节点结构链接?

例如,当使用链表时,我将如何存储上例中连接的节点 0?我知道您使用矩阵或列表/数组,但是由于缺乏 C 中此类实现的示例,我感到非常困惑。我发现的任何示例都使情况变得比以前更糟。

【问题讨论】:

    标签: c data-structures graph


    【解决方案1】:

    这只是一个例子:

    struct node{
            int id; 
            struct node **out;
            int num_out;
            /* optional: if you want doubly links */
            struct node **in;
            int num_in;
    };
    
    /* quick access to a node given an id */
    struct node *node_list;
    
    /* connect 'from' to 'to' */
    void link(struct node *graph, int from, int to) {
            struct node *nfrom = &node_list[from], 
                        *nto   = &node_list[to];
            nfrom->num_out++;
            nfrom->out = realloc(nfrom->out, 
                 sizeof(struct node*) * nfrom->num_out);
            nfrom->out[num_out-1] = nto;
            /* also do similar to nto->in if you want doubly links */
    }
    

    【讨论】:

      【解决方案2】:

      回答您的第一个问题:邻接矩阵与邻接列表?如果您希望您的图是密集的,即大多数节点与大多数其他节点相邻,那么请选择矩阵,因为大多数操作在矩阵上要容易得多。如果你真的需要一个传递闭包,那么矩阵可能也更好,因为它们往往是密集的。否则邻接表会更快更小。

      图表如下所示:

      typedef struct node * node_p;
      typedef struct edge * edge_p;
      
      typedef struct edge
      {       node_p  source, target;
              /* Add any data in the edges */
      } edge;
      
      typedef struct node
      {       edge_p  * pred, * succ;
              node_p  next;
              /* Add any data in the nodes */
      } node;
      
      typedef struct graph
      {       node_p  N;
      } graph;
      

      graphN 字段将使用 nodenext 字段链接列表来启动图形节点的链接列表。 predsucc 可以是使用mallocrealloc 为图中的后继和前驱边分配的数组(指向边的指针 数组和NULL 终止的数组)。尽管同时保留后继和前任似乎是多余的,但您会发现大多数图算法都喜欢能够双向行走。边缘点的sourcetarget 字段返回到节点。如果您不希望将数据存储在边缘中,那么您可以让 predsucc 数组直接指向节点,而忘记 edge 类型。

      不要尝试在graph 中的N 上使用realloc,因为节点的所有地址都可能发生变化,并且这些地址在图表的其余部分中被大量使用。

      P.S:我个人更喜欢循环链表而不是 NULL 结束链表,因为大多数(如果不是全部)操作的代码要简单得多。在这种情况下,graph 将包含(虚拟)node 而不是指针。

      【讨论】:

        【解决方案3】:

        你可以这样做:

        #include <stdio.h>
        #include <string.h>
        #include <stdlib.h>
        
        typedef struct
        {
          void* pElements;
          size_t ElementSize;
          size_t Count; // how many elements exist
          size_t TotalCount; // for how many elements space allocated
        } tArray;
        
        void ArrayInit(tArray* pArray, size_t ElementSize)
        {
          pArray->pElements = NULL;
          pArray->ElementSize = ElementSize;
          pArray->TotalCount = pArray->Count = 0;
        }
        
        void ArrayDestroy(tArray* pArray)
        {
          free(pArray->pElements);
          ArrayInit(pArray, 0);
        }
        
        int ArrayGrowByOne(tArray* pArray)
        {
          if (pArray->Count == pArray->TotalCount) // used up all allocated space
          {
            size_t newTotalCount, newTotalSize;
            void* p;
        
            if (pArray->TotalCount == 0)
            {
              newTotalCount = 1;
            }
            else
            {
              newTotalCount = 2 * pArray->TotalCount; // double the allocated count
              if (newTotalCount / 2 != pArray->TotalCount) // count overflow
                return 0;
            }
        
            newTotalSize = newTotalCount * pArray->ElementSize;
            if (newTotalSize / pArray->ElementSize != newTotalCount) // size overflow
              return 0;
        
            p = realloc(pArray->pElements, newTotalSize);
            if (p == NULL) // out of memory
              return 0;
        
            pArray->pElements = p;
            pArray->TotalCount = newTotalCount;
          }
        
          pArray->Count++;
          return 1;
        }
        
        int ArrayInsertElement(tArray* pArray, size_t pos, void* pElement)
        {
          if (pos > pArray->Count) // bad position
            return 0;
        
          if (!ArrayGrowByOne(pArray)) // couldn't grow
            return 0;
        
          if (pos < pArray->Count - 1)
            memmove((char*)pArray->pElements + (pos + 1) * pArray->ElementSize,
                   (char*)pArray->pElements + pos * pArray->ElementSize,
                   (pArray->Count - 1 - pos) * pArray->ElementSize);
        
          memcpy((char*)pArray->pElements + pos * pArray->ElementSize,
                 pElement,
                 pArray->ElementSize);
        
          return 1;
        }
        
        typedef struct
        {
          int Id;
        
          int Data;
        
          tArray LinksTo; // links from this node to other nodes (array of Id's)
          tArray LinksFrom; // back links from other nodes to this node (array of Id's)
        } tNode;
        
        typedef struct
        {
          tArray Nodes;
        } tGraph;
        
        void GraphInit(tGraph* pGraph)
        {
          ArrayInit(&pGraph->Nodes, sizeof(tNode));
        }
        
        void GraphPrintNodes(tGraph* pGraph)
        {
          size_t i, j;
        
          if (pGraph->Nodes.Count == 0)
          {
            printf("Empty graph.\n");
          }
        
          for (i = 0; i < pGraph->Nodes.Count; i++)
          {
            tNode* pNode = (tNode*)pGraph->Nodes.pElements + i;
        
            printf("Node %d:\n  Data: %d\n", pNode->Id, pNode->Data);
        
            if (pNode->LinksTo.Count)
            {
              printf("  Links to:\n");
        
              for (j = 0; j < pNode->LinksTo.Count; j++)
              {
                int* p = (int*)pNode->LinksTo.pElements + j;
                printf("    Node %d\n", *p);
              }
            }
          }
        }
        
        void GraphDestroy(tGraph* pGraph)
        {
          size_t i;
        
          for (i = 0; i < pGraph->Nodes.Count; i++)
          {
            tNode* pNode = (tNode*)pGraph->Nodes.pElements + i;
            ArrayDestroy(&pNode->LinksTo);
            ArrayDestroy(&pNode->LinksFrom);
          }
        
          ArrayDestroy(&pGraph->Nodes);
        }
        
        int NodeIdComparator(const void* p1, const void* p2)
        {
          const tNode* pa = p1;
          const tNode* pb = p2;
        
          if (pa->Id < pb->Id)
            return -1;
          if (pa->Id > pb->Id)
            return 1;
          return 0;
        }
        
        int IntComparator(const void* p1, const void* p2)
        {
          const int* pa = p1;
          const int* pb = p2;
        
          if (*pa < *pb)
            return -1;
          if (*pa > *pb)
            return 1;
          return 0;
        }
        
        size_t GraphFindNodeIndexById(tGraph* pGraph, int Id)
        {
          tNode* pNode = bsearch(&Id,
                                 pGraph->Nodes.pElements,
                                 pGraph->Nodes.Count,
                                 pGraph->Nodes.ElementSize,
                                 &NodeIdComparator);
        
          if (pNode == NULL)
            return (size_t)-1;
        
          return pNode - (tNode*)pGraph->Nodes.pElements;
        }
        
        int GraphInsertNode(tGraph* pGraph, int Id, int Data)
        {
          size_t idx = GraphFindNodeIndexById(pGraph, Id);
          tNode node;
        
          if (idx != (size_t)-1) // node with this Id already exist
            return 0;
        
          node.Id = Id;
          node.Data = Data;
          ArrayInit(&node.LinksTo, sizeof(int));
          ArrayInit(&node.LinksFrom, sizeof(int));
        
          if (!ArrayInsertElement(&pGraph->Nodes, pGraph->Nodes.Count, &node))
            return 0;
        
          qsort(pGraph->Nodes.pElements,
                pGraph->Nodes.Count,
                pGraph->Nodes.ElementSize,
                &NodeIdComparator); // maintain order for binary search
        
          return 1;
        }
        
        int GraphLinkNodes(tGraph* pGraph, int IdFrom, int IdTo)
        {
          size_t idxFrom = GraphFindNodeIndexById(pGraph, IdFrom);
          size_t idxTo = GraphFindNodeIndexById(pGraph, IdTo);
          tNode *pFrom, *pTo;
        
          if (idxFrom == (size_t)-1 || idxTo == (size_t)-1) // one or both nodes don't exist
            return 0;
        
          pFrom = (tNode*)pGraph->Nodes.pElements + idxFrom;
          pTo = (tNode*)pGraph->Nodes.pElements + idxTo;
        
          // link IdFrom -> IdTo
          if (bsearch(&IdTo,
                      pFrom->LinksTo.pElements,
                      pFrom->LinksTo.Count,
                      pFrom->LinksTo.ElementSize,
                      &IntComparator) == NULL) // IdFrom doesn't link to IdTo yet
          {
            if (!ArrayInsertElement(&pFrom->LinksTo, pFrom->LinksTo.Count, &IdTo))
              return 0;
        
            qsort(pFrom->LinksTo.pElements,
                  pFrom->LinksTo.Count,
                  pFrom->LinksTo.ElementSize,
                  &IntComparator); // maintain order for binary search
          }
        
          // back link IdFrom <- IdTo
          if (bsearch(&IdFrom,
                      pTo->LinksFrom.pElements,
                      pTo->LinksFrom.Count,
                      pTo->LinksFrom.ElementSize,
                      &IntComparator) == NULL) // IdFrom doesn't link to IdTo yet
          {
            if (!ArrayInsertElement(&pTo->LinksFrom, pTo->LinksFrom.Count, &IdFrom))
              return 0;
        
            qsort(pTo->LinksFrom.pElements,
                  pTo->LinksFrom.Count,
                  pTo->LinksFrom.ElementSize,
                  &IntComparator); // maintain order for binary search
          }
        
          return 1;
        }
        
        int main(void)
        {
          tGraph g;
        
          printf("\nCreating empty graph...\n");
          GraphInit(&g);
          GraphPrintNodes(&g);
        
          printf("\nInserting nodes...\n");
          GraphInsertNode(&g, 0, 0);
          GraphInsertNode(&g, 1, 101);
          GraphInsertNode(&g, 2, 202);
          GraphPrintNodes(&g);
        
          printf("\nLinking nodes...\n");
          GraphLinkNodes(&g, 0, 1);
          GraphLinkNodes(&g, 0, 2);
          GraphLinkNodes(&g, 1, 2);
          GraphLinkNodes(&g, 2, 1);
          GraphPrintNodes(&g);
        
          printf("\nDestroying graph...\n");
          GraphDestroy(&g);
          GraphPrintNodes(&g);
        
          // repeat
          printf("\nLet's repeat...\n");
        
          printf("\nCreating empty graph...\n");
          GraphInit(&g);
          GraphPrintNodes(&g);
        
          printf("\nInserting nodes...\n");
          GraphInsertNode(&g, 1, 111);
          GraphInsertNode(&g, 2, 222);
          GraphInsertNode(&g, 3, 333);
          GraphPrintNodes(&g);
        
          printf("\nLinking nodes...\n");
          GraphLinkNodes(&g, 1, 2);
          GraphLinkNodes(&g, 2, 3);
          GraphLinkNodes(&g, 3, 1);
          GraphPrintNodes(&g);
        
          printf("\nDestroying graph...\n");
          GraphDestroy(&g);
          GraphPrintNodes(&g);
        
          return 0;
        }
        

        输出(ideone):

        Creating empty graph...
        Empty graph.
        
        Inserting nodes...
        Node 0:
          Data: 0
        Node 1:
          Data: 101
        Node 2:
          Data: 202
        
        Linking nodes...
        Node 0:
          Data: 0
          Links to:
            Node 1
            Node 2
        Node 1:
          Data: 101
          Links to:
            Node 2
        Node 2:
          Data: 202
          Links to:
            Node 1
        
        Destroying graph...
        Empty graph.
        
        Let's repeat...
        
        Creating empty graph...
        Empty graph.
        
        Inserting nodes...
        Node 1:
          Data: 111
        Node 2:
          Data: 222
        Node 3:
          Data: 333
        
        Linking nodes...
        Node 1:
          Data: 111
          Links to:
            Node 2
        Node 2:
          Data: 222
          Links to:
            Node 3
        Node 3:
          Data: 333
          Links to:
            Node 1
        
        Destroying graph...
        Empty graph.
        

        【讨论】:

          【解决方案4】:

          这看起来很像我的工作、社交网络…… 您可以单独定义节点和链接。在c语言中,你可以定义为:

          struct graph_node{
              int id;
              struct node_following *following;
              struct graph_node *next_node;
          }
          
          struct node_following{
              int id;
              struct node_following *next_node;
          }
          

          对于您的示例,结果是: root -> node0 -> node1 -> node2

          root 的内容可能是:id = -1;以下=空; next_node=node0

          node0的内容可能是:id = 0;下一个节点=节点1;以下指向 node_following 的列表: 以下 -> {1,下一个节点的地址} -> {2,NULL}

          node1的内容可能是:id = 1;下一个节点=节点2;以下指向 node_following 的列表: 以下 -> {2, NULL}

          node2的内容可能是:id = 2;下一个节点=空;以下指向 node_following 的列表: 以下 -> {1, NULL}

          本质上是一个二维矩阵如何存储的问题。如果矩阵稀疏,则使用链表。否则,位图是更好的解决方案。

          【讨论】:

            猜你喜欢
            • 2013-10-16
            • 2016-06-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多