微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

抽象数据结构-C

如何解决抽象数据结构-C

假设我们有一个树数据结构:

#ifndef TREE_H
#define TREE_H

#include <stdarg.h>

typedef struct tree {
  char *tag;
  struct tree **children;
  int num_children;
} tree;

tree *leaf(char *tag);

tree *node(char *tag,int num_children,...);

#endif

以此,我们可以通过以下语法上令人愉悦的方式定义一棵树:

#include "tree.h"

int main() {

  tree *test =
        node("Tywin",2,node("Cersei",leaf("Joffrey"),leaf("Tommen")
            ),leaf("Tyrion")
        );
  return 0;
}

现在,我想到了实现此目标的两种可能方法。当我们定义节点和叶子时,我们可以复制所有数据,如下所示:

#include "tree.h"
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

tree *leaf(char *tag) {
  tree *res = malloc(sizeof(*res));
  size_t tag_length = strlen(tag) + 1;
  res->tag = malloc(tag_length * sizeof((*res->tag)));
  strcpy(res->tag,tag);
  res->children = NULL;
  res->num_children = 0;
  return res;
}

tree *node(char *tag,...) {
  tree *res = malloc(sizeof(*res));
  size_t tag_length = strlen(tag) + 1;
  res->tag = malloc(tag_length * sizeof((*res->tag)));
  strcpy(res->tag,tag);
  
  res->children = malloc(num_children * sizeof(*res->children));

  va_list ap;
  va_start(ap,num_children);
  tree *current_child;
  for (int i = 0; i < num_children; ++i) {
    *(res->children + i) = malloc(sizeof(**(res->children + i)));
    current_child = va_arg(ap,tree *);
    memcpy(*(res->children + i),current_child,sizeof(*current_child));
  }
  va_end(ap);
  res->num_children = num_children;
  return res;
}

但是,与此相关的是,node的中间调用(如创建Tyrion和Cersei的调用)会导致内存泄漏。另外,即使您可以某种方式清理泄漏,您分配的内存也可能比“应有”的更多。因此,除了复制指针之外,显然还有另一种选择:

#include "tree.h"
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>

tree *leaf(char *tag) {
  tree *res = malloc(sizeof(*res));
  res->tag = tag;
  res->children = NULL;
  res->num_children = 0;
  return res;
}

tree *node(char *tag,...) {
  tree *res = malloc(sizeof(*res));
  res->tag = tag;
  
  res->children = malloc(num_children * sizeof(*res->children));

  va_list ap;
  va_start(ap,num_children);
  for (int i = 0; i < num_children; ++i) {
    *(res->children + i) = va_arg(ap,tree *);
  }
  va_end(ap);
  res->num_children = num_children;
  return res;
}

然后,您可以定义一个递归清除方法,该方法在完成后将调用您的顶级对象(例如,递归free并设置为NULL指针,因此如果是{{ 1}} d两次,就可以了。但是我对此的担心是,您希望用户以后不会更改基础数据,即不能保证此数据结构是不变的。

问题:

  1. 第二种方法是在C中定义抽象数据结构的规范方法吗?
  2. 我可以完全调整代码以提供某种不变性保证吗?

解决方法

  1. 您定义的内容可以称为“侵入式”数据结构,因为您将层次结构数据(children)和有效负载(tag)存储在每个tree实例中。几乎没有任何“规范”的方式来定义这种结构-定义通常取决于需求。一些教科书和开放库可能完全以此方式定义了发束,某些代码可能提供了自定义内存分配器,以使分配的节点在内存中保持彼此靠近。

考虑其他选项,最好将节点有效负载和实际层次结构数据分开。例如,如果您的树仅是(完整的)二叉树,则可以避免将节点的左/右兄弟索引存储在数组中,并且完全避免分配/指针。如果您需要不需太多节点插入/删除的k进制树,则可以查看LCRS tree representation来表示层次结构,并将与节点关联的所有数据(标签和其他内容)存储在单独的位置。

我的answer有一个关于看似相似的关于简单数据结构的问题。您可以看看它。

也就是说,您的表示形式(考虑到没有错误和正确的递归cleanup例程)在C语言中是有效的树表示形式。

  1. 在C中,您可以选择在修改树时锁定所有内容或应用“写时复制”策略。这再次取决于要求。您是否必须搜索树,是否经常更新结构或仅重新计算/重新分配节点数据?您是仅使用CPU在本地处理此数据,还是需要与GPU / DSP共享树或通过网络传输树?这些问题可以帮助您选择所需的表示形式和同步原语。否则,就没有一种“标准的”方法可以一次性使所有内容都是线程安全的/不可变的,并且一直使用。我可能会提出的一个建议是,使用LCRS时,您每次需要修改树结构时,都可以以“功能方式”编程并从旧的“计算”新的层次结构数组。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。