如何编写一个简单的树来归档并读回?

时间:2019-03-25 09:46:31

标签: c

我有一些代码可以根据指向节点的指针创建一个简单的树,并且希望将此树(及其数据)写入文件,然后将其从文件读回内存。我怎样才能做到这一点 ?这是我创建简单树的代码:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>

struct Node 
{ 
    void *data; 

    struct Node *left; 
    struct Node *right;
};

int main(void) 
{ 
    unsigned int_size = sizeof(int);
    int data;

    /* Tree root node */
    struct Node *start = (struct Node*)malloc(sizeof(struct Node));

    data = 5;

    start->data = malloc(int_size);
    memcpy(start->data, &data, int_size);

    /* Left node of root */
    start->left = (struct Node*)malloc(sizeof(struct Node));

    data = 4;

    start->left->data = malloc(int_size);
    memcpy(start->left->data, &data, int_size);

    start->left->left = NULL;
    start->left->right = NULL;

    /* Right node of root */
    start->right = (struct Node*)malloc(sizeof(struct Node));

    data = 3;

    start->right->data = malloc(int_size);
    memcpy(start->right->data, &data, int_size);

    start->right->left = NULL;
    start->right->right = NULL;

    /* Print data */

    printf("%d\n",*(int*)(start->data));
    printf("%d\n",*(int*)(start->left->data));
    printf("%d\n",*(int*)(start->right->data));

    return 0; 
}

2 个答案:

答案 0 :(得分:1)

这里有个建议,我使用预处理器宏TYPE来更改类型,并使用分离的函数来读写该类型的元素,我还使用函数 mk 轻松创建节点,而不是像对每个节点一样复制代码。

对于序列化,我使用一种非常简单的方法

  • 'e'表示一个空节点
  • 'n'表示一个非空节点,然后我写一个值,然后是左节点,然后是右节点

我还添加了 pr 来轻松打印树作为调试功能。

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>

#define TYPE int

struct Node 
{ 
  TYPE data; 

  struct Node *left; 
  struct Node *right;
};

/* make a new Node */
struct Node * mk(TYPE v, struct Node * l, struct Node * r)
{
  struct Node * n = malloc(sizeof(struct Node));

  if (n == NULL) {
    fprintf(stderr, "not enough memory\n");
    exit(-1);
  }

  n->data = v;
  n->left = l;
  n->right = r;

  return n;
}

/* write/read data */
void wrData(TYPE v, FILE * fp)
{
  fprintf(fp, "%d", v); /* dedicated to int */
}

TYPE rdData(FILE * fp)
{
  TYPE v;

  fscanf(fp, "%d", &v); /* dedicated to int */
  return v;
}

/* serialize a tree */
void wr(struct Node * n, FILE * fp)
{
  if (n == NULL)
    fputc('e', fp);
  else {
    fputc('n', fp);
    wrData(n->data, fp);
    wr(n->left, fp);
    wr(n->right, fp);
  }
}

/* unserialize a tree */
struct Node * rd(FILE * fp)
{
  switch (fgetc(fp)) {
  case 'e':
    return NULL;
  case 'n':
    {
      TYPE v = rdData(fp);
      struct Node * l = rd(fp);

      return mk(v, l, rd(fp));
    }
  default:
    fprintf(stderr, "invalid file");
    exit(-1);
  }
}

/* for debug, show tree */
void pr(struct Node * t)
{
  if (t == NULL)
    printf("()");
  else {
    putchar('(');
    pr(t->left);
    putchar(' ');
    wrData(t->data, stdout);
    putchar(' ');
    pr(t->right);
    putchar(')');
  }
}

/* free a tree */
void del(struct Node * t)
{
  if (t != NULL) {
    del(t->left);
    del(t->right);
    free(t);
  }
}

int main() 
{ 
    /* Tree root node */
    struct Node *start = mk(5, mk(4, NULL, NULL), mk(3, NULL, NULL));

    /* show it */
    pr(start);
    putchar('\n');

    /* serialize */
    FILE * fp;

    if ((fp = fopen("/tmp/t", "w")) == 0) {
      fprintf(stderr, " cannot open /tmp/t to write");
      exit(-1);
    }
    wr(start, fp);
    fclose(fp);

    /* free tree */
    del(start);

    /* unserialize */
    if ((fp = fopen("/tmp/t", "r")) == 0) {
      fprintf(stderr, " cannot open /tmp/t to read");
      exit(-1);
    }
    start = rd(fp);
    fclose(fp);

    /* show it */
    pr(start);
    putchar('\n');

    /* free it */
    del(start);

    return 0; 
}

编译和执行:

/tmp % gcc -pedantic -Wextra -Wall t.c
/tmp % ./a.out
((() 4 ()) 5 (() 3 ()))
((() 4 ()) 5 (() 3 ()))
/tmp % cat t ; echo
n5n4een3ee

valgrind 下执行:

/tmp % valgrind ./a.out
==19907== Memcheck, a memory error detector
==19907== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==19907== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==19907== Command: ./a.out
==19907== 
((() 4 ()) 5 (() 3 ()))
((() 4 ()) 5 (() 3 ()))
==19907== 
==19907== HEAP SUMMARY:
==19907==     in use at exit: 0 bytes in 0 blocks
==19907==   total heap usage: 8 allocs, 8 frees, 1,280 bytes allocated
==19907== 
==19907== All heap blocks were freed -- no leaks are possible
==19907== 
==19907== For counts of detected and suppressed errors, rerun with: -v
==19907== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 6)

答案 1 :(得分:1)

有两种常见的方式来序列化在其他对象上具有指针的对象。第一个是用文件中的偏移量替换指针,第二个是使用简单索引。实际上,偏移方式主要用于直接从文件(例如数据库文件)中使用数据时,而索引方式则用于简单存储。

一种简单的方法是将节点以left_node-right_node-main_node的形式递归存储到文件中。这样,当您存储节点时,您已经知道该节点的左右子节点的索引。然后可能的实现是:

static size_t do_save(FILE *fd, struct Node *node, unsigned data_size, int curr) {
    size_t left=0, right=0;
    if (node->left != NULL) {   // first left sub_hierarchy
        left = do_save(fd, node->left, data_size, curr);
        curr = left;
    }
    if (node->left != NULL) {   // then right one
        right = do_save(fd, node->right, data_size, curr);
        curr = right;
    }
    fwrite(&left, sizeof(left), 1, fd);       // store index of left child
    fwrite(&right, sizeof(right), 1, fd);     // then index of right child
    fwrite(node->data, data_size, 1, fd);     // then the data
    return curr + 1;                          // and return current index
}
size_t save(FILE *fd, struct Node *root, unsigned data_size) {
    size_t nb = do_save(fd, root, data_size, 0);
    return nb;
}

这里,索引为0表示空指针,而非null索引是文件中基于1的索引

要反序列化,因为我假设您希望分别分配每个节点,我将使用一个临时指针数组来保留分配节点的实际地址:

struct Node* load(FILE *fd, unsigned data_size) {
    size_t nb_elts, left, right;
    fseek(fd, 0, SEEK_END);                // computer number of nodes in the file
    nb_elts = ftell(fd) / (data_size + 2*sizeof(size_t));
    struct Node** ptx = malloc(nb_elts * sizeof(*ptx));  // allocate array of pointers
    fseek(fd, 0, SEEK_SET);
    for(size_t i=0; i<nb_elts; i++) {                    // loop reading nodes
        ptx[i] = malloc(sizeof(struct Node));            // allocate node and data
        ptx[i]->data = malloc(data_size);
        fread(&left, sizeof(size_t), 1, fd);             // extract child indices
        fread(&right, sizeof(size_t), 1, fd);
        fread(ptx[i]->data, data_size, 1, fd);                 // read data
        ptx[i]->left = (left == 0) ? NULL : ptx[left - 1];     // convert indices to pointers
        ptx[i]->right = (right == 0) ? NULL : ptx[right - 1];
    }
    struct Node *last = ptx[nb_elts - 1];
    free(ptx);                                     // free the temporary array
    return last;                                   // and return the root node
}