问题描述
我正在研究CS50 pset5拼写器,在讲座中,他们介绍了一种称为节点的新东西。什么是节点?我真的不明白他们在视频中说的话。当我尝试使用Google搜索时,我得到了一些解释什么是节点的站点,但我并不太了解。我是c语言的新手,所以我不习惯所谓的“编码单词”。例如,我在有关节点的站点上发现了这一点:可以通过将大小增加一倍来扩展动态数组,但是复制旧数据和释放与旧数据结构相关的内存的操作会带来开销。 strong>那是什么意思?请帮助我弄清楚什么是节点,因为它们似乎很重要且很有用,尤其是对于pset5。 我的节点是这样定义的:
typedef struct node
{
char word[LENGTH + 1];
struct node *next;
}
node;
以下是拼写pset5演练的链接:https://cs50.harvard.edu/x/2020/psets/5/speller/
解决方法
节点是一种通用术语,用于演示linked list
或tree
或相关数据结构的单个块。
为节点命名是一种惯例,否则您可以使用任何名称来调用它。
标准
C ++
struct node{
int data;
int *next;
};
或使用Python
class Node:
def __init__(self,data,next= None):
self.data = data
self.next = next
但是您可以使用任何名称来调用
非标准
C ++
struct my_own_name{
int data;
int *nextptr;
};
或在python
中
class my_own_name:
def __init__(self,next=None):
self.data = data
self.next = next
,
“节点”是图论的概念。图由节点(顶点)和连接节点的边组成。
C中的节点可以表示为结构(struct
),该结构具有“所有”实现图形的所有必要数据元素。可选地,可能需要代表边缘的结构。
示例:
typedef struct NODE {
int node_id;
struct EDGE *edgelist;
} tNode;
typedef struct EDGE {
tNode *from,*to;
struct EDGE *next;
} tEdge;
注意:术语“节点”也可以在其他上下文中使用,例如,二叉树的节点,列表的节点等。
,“节点”不是C关键字。
其含义:
可以通过将大小增加一倍来扩展动态数组,但是复制旧数据和释放与旧数据结构相关的内存的操作会带来开销
动态分配意味着在堆上分配内存。像静态内存分配一样,不是分配的内存空间大小不必是编译时间常数,因此可以在以后的程序执行中通过重新分配更多的内存来进行修改。
间接费用是指与进行同一操作的其他方式相比,进行一项操作的额外费用。在这种情况下,与直接分配所需的总空间相比,增加动态数组的大小是一项开销。
,扩展了Ahmad的答案,有许多数据结构是由通常称为“节点”的元素构建的-每个节点都包含一些数据和对一个或多个其他数据的某种引用(通常是C和C ++中的指针)节点。对于单链列表,节点定义通常看起来像
struct node {
data_t data; // for some arbitrary data_t type
struct node *next;
};
每个节点包含以下节点的地址。图形表示通常看起来像
+------+------+ +------+------+ +------+------+
| data | next |------->| data | next |----->| data | next |------|||
+------+------+ +------+------+ +------+------+
您还可以具有双向链接列表,其中每个节点都指向前面和后面的节点:
struct node {
data_t data;
struct node *prev;
struct node *next;
};
还有二叉树,其中每个节点都指向左右子节点:
struct node {
data_t data;
struct node *left;
struct node *right;
};
使用术语“节点”只是一种常见的命名约定。
可以通过将大小增加一倍来扩展动态数组,但是复制旧数据和释放与旧数据结构相关的内存会增加开销。这是什么意思?
您可以使用realloc
库函数来调整动态分配的缓冲区的大小。例如,假设我们要动态分配一个缓冲区给
存储字符串"foo"
。我们将这样写:
size_t bufsize = 4;
char *buffer = malloc( bufsize );
if ( buffer )
strcpy( buffer,"foo" );
我们将假设从malloc
返回的地址是0x1000
:
+---+---+---+---+
0x1000: |'f'|'o'|'o'| 0 |
+---+---+---+---+
0x1004: | ? | ? | ? | ? |
+---+---+---+---+
... ... ... ...
现在,假设我们要将字符串"bar"
附加到"foo"
。我们没有分配足够大的缓冲区来做到这一点,因此我们需要使用realloc
库函数来调整其大小:
char *tmp = realloc( buffer,bufsize * 2 ); // double the buffer size
if ( tmp )
{
buffer = tmp;
bufsize *= 2;
strcat( buffer,"bar" );
}
else
{
// could not extend buffer,handle as appropriate
}
现在,如果可能的话,realloc
只会获取当前缓冲区后面的空间,因此该代码的结果将是:
+---+---+---+---+
0x1000: |'f'|'o'|'o'|'b'|
+---+---+---+---+
0x1004: |'a'|'r'| 0 | ? |
+---+---+---+---+
... ... ... ...
但是,如果0x1004
处的内存已经分配给其他东西,那么我们就不能这样做。 realloc
将不得不在另一个地址分配一个 new 缓冲区,并将当前缓冲区的内容复制到其中,然后取消分配原始缓冲区。我们可以想象足够大的自由空间的第一个区域从0x100c
开始:
+---+---+---+---+
0x1000: |'f'|'o'|'o'| 0 |
+---+---+---+---+
0x1004: | ? | ? | ? | ? |
+---+---+---+---+
... ... ... ...
+---+---+---+---+
0x100c: | ? | ? | ? | ? |
+---+---+---+---+
0x1010: | ? | ? | ? | ? |
+---+---+---+---+
因此realloc
必须首先分配从0x100c
开始的8个字节,然后必须将当前缓冲区的内容复制到该新空间:
+---+---+---+---+
0x1000: |'f'|'o'|'o'| 0 |
+---+---+---+---+
0x1004: | ? | ? | ? | ? |
+---+---+---+---+
... ... ... ...
+---+---+---+---+
0x100c: |'f'|'o'|'o'| 0 |
+---+---+---+---+
0x1010: | ? | ? | ? | ? |
+---+---+---+---+
,然后最后释放0x1000
处的空格。我们将"bar"
附加到此 new 缓冲区中,从而得到:
+---+---+---+---+
0x1000: |'f'|'o'|'o'| 0 | // free'd memory is not overwritten
+---+---+---+---+
0x1004: | ? | ? | ? | ? |
+---+---+---+---+
... ... ... ...
+---+---+---+---+
0x100c: |'f'|'o'|'o'|'b'|
+---+---+---+---+
0x1010: |'a'|'r'| 0 | ? |
+---+---+---+---+
如果realloc
找不到足够大的区域来满足请求,它将返回NULL
并保留当前缓冲区。这就是为什么我们将realloc
的返回值分配给另一个指针变量的原因-如果我们将NULL
分配回buffer
,那么我们将失去对原始缓冲区的访问权限。 / p>