我曾在互联网上搜索过“段树”的实现,但在进行惰性传播时一无所获。以前有一些关于堆栈溢出的问题,但它们专注于解决SPOJ的某些特定问题。尽管我认为这是用伪代码对段树的最好解释,但是我需要通过延迟传播来实现它。我发现以下链接:
http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=lowestCommonAncestor#Segment_Trees
除了上述链接之外,还存在一些博客,但是它们都引用了同一主题。
一个 例子 这种数据结构的应用会是这样的,说我已经被赋予了数字范围从1到n。现在,我执行一些操作,例如将某个常数添加到特定范围或从特定范围减去某个常数。执行完操作后,我应该告诉给定数字中的最小和最大数字。
一个 明显的解决方案 是对给定范围内的每个数字进行一次加法或减法。但这在没有执行大量操作的情况下是不可行的。
甲 较好的方法 是使用线段树懒传播技术。它说而不是单独对每个数字执行更新操作,只需跟踪所有操作,直到完成所有操作即可。然后,最后执行更新操作以获取该范围内的最小和最大数目。
真实数据示例
假设我给定范围[1,10],这意味着数字是1,2,3,4,5,6,7,8,9,10。现在假设我执行了一个操作,将[3,6]范围内的数字减少了4,所以现在数字看起来像1,2,-1,0,1,2,7,8,9,10。现在,我执行另一个操作,将[5,9]范围内的数字增加1,因此该数字现在看起来像1,2,-1,0,2,3,8,9,10,10。
现在,如果我要您告诉我最大和最小数目,那么答案将是:
Maximum = 10 Minimum = -1
这只是一个简单的例子,实际问题可能包含成千上万个这样的加/减运算,我希望现在已经清楚了。
到目前为止,这是我所了解的,但是我想Internet上没有统一的链接可以更好地解释概念和实现。
谁能给出一些很好的解释,包括用于段树中延迟传播的伪代码?
谢谢。
惰性传播几乎总是包含某种哨兵机制。您必须验证不需要传播当前节点,并且此检查应该简单快捷。因此,有两种可能性:
我坚持自己的第一个。检查分段树中的节点是否应具有子节点非常简单(node->lower_value != node->upper_value),但是您还必须检查这些子节点是否已经构建(node->left_child, node->right_child),因此我引入了传播标记node->propagated:
node->lower_value != node->upper_value
node->left_child, node->right_child
node->propagated
typedef struct lazy_segment_node{ int lower_value; int upper_value; struct lazy_segment_node * left_child; struct lazy_segment_node * right_child; unsigned char propagated; } lazy_segment_node;
要初始化我们称之为节点initialize的指针的节点指针(或NULL)和期望upper_value/ lower_value:
initialize
NULL
upper_value
lower_value
lazy_segment_node * initialize( lazy_segment_node ** mem, int lower_value, int upper_value ){ lazy_segment_node * tmp = NULL; if(mem != NULL) tmp = *mem; if(tmp == NULL) tmp = malloc(sizeof(lazy_segment_node)); if(tmp == NULL) return NULL; tmp->lower_value = lower_value; tmp->upper_value = upper_value; tmp->propagated = 0; tmp->left_child = NULL; tmp->right_child = NULL; if(mem != NULL) *mem = tmp; return tmp; }
到目前为止,还没有什么特别的事情。这看起来像其他所有通用节点创建方法。但是,为了创建实际的子节点并设置传播标志,我们可以使用一个函数,该函数将在同一节点上返回一个指针,但是在需要时传播它:
lazy_segment_node * accessErr(lazy_segment_node* node, int * error){ if(node == NULL){ if(error != NULL) *error = 1; return NULL; } /* if the node has been propagated already return it */ if(node->propagated) return node; /* the node doesn't need child nodes, set flag and return */ if(node->upper_value == node->lower_value){ node->propagated = 1; return node; } /* skipping left and right child creation, see code below*/ return node; }
如您所见,传播的节点几乎会立即退出该功能。相反,未传播的节点将首先检查它是否实际上应包含子节点,然后在需要时创建它们。
这实际上是惰性评估。直到需要时才创建子节点。注意,accessErr它还提供了一个附加的错误接口。如果您不需要它,请access改用:
accessErr
access
lazy_segment_node * access(lazy_segment_node* node){ return accessErr(node,NULL); }
为了释放这些元素,您可以使用通用节点释放算法:
void free_lazy_segment_tree(lazy_segment_node * root){ if(root == NULL) return; free_lazy_segment_tree(root->left_child); free_lazy_segment_tree(root->right_child); free(root); }
下面的示例将使用上述函数基于间隔[1,10]创建延迟评估的分段树。您可以看到,第一次初始化后test没有子节点。通过使用,access您实际上生成了那些子节点并可以获取它们的值(如果这些子节点通过分段树的逻辑存在):
test
#include <stdlib.h> #include <stdio.h> typedef struct lazy_segment_node{ int lower_value; int upper_value; unsigned char propagated; struct lazy_segment_node * left_child; struct lazy_segment_node * right_child; } lazy_segment_node; lazy_segment_node * initialize(lazy_segment_node ** mem, int lower_value, int upper_value){ lazy_segment_node * tmp = NULL; if(mem != NULL) tmp = *mem; if(tmp == NULL) tmp = malloc(sizeof(lazy_segment_node)); if(tmp == NULL) return NULL; tmp->lower_value = lower_value; tmp->upper_value = upper_value; tmp->propagated = 0; tmp->left_child = NULL; tmp->right_child = NULL; if(mem != NULL) *mem = tmp; return tmp; } lazy_segment_node * accessErr(lazy_segment_node* node, int * error){ if(node == NULL){ if(error != NULL) *error = 1; return NULL; } if(node->propagated) return node; if(node->upper_value == node->lower_value){ node->propagated = 1; return node; } node->left_child = initialize(NULL,node->lower_value,(node->lower_value + node->upper_value)/2); if(node->left_child == NULL){ if(error != NULL) *error = 2; return NULL; } node->right_child = initialize(NULL,(node->lower_value + node->upper_value)/2 + 1,node->upper_value); if(node->right_child == NULL){ free(node->left_child); if(error != NULL) *error = 3; return NULL; } node->propagated = 1; return node; } lazy_segment_node * access(lazy_segment_node* node){ return accessErr(node,NULL); } void free_lazy_segment_tree(lazy_segment_node * root){ if(root == NULL) return; free_lazy_segment_tree(root->left_child); free_lazy_segment_tree(root->right_child); free(root); } int main(){ lazy_segment_node * test = NULL; initialize(&test,1,10); printf("Lazy evaluation test\n"); printf("test->lower_value: %i\n",test->lower_value); printf("test->upper_value: %i\n",test->upper_value); printf("\nNode not propagated\n"); printf("test->left_child: %p\n",test->left_child); printf("test->right_child: %p\n",test->right_child); printf("\nNode propagated with access:\n"); printf("access(test)->left_child: %p\n",access(test)->left_child); printf("access(test)->right_child: %p\n",access(test)->right_child); printf("\nNode propagated with access, but subchilds are not:\n"); printf("access(test)->left_child->left_child: %p\n",access(test)->left_child->left_child); printf("access(test)->left_child->right_child: %p\n",access(test)->left_child->right_child); printf("\nCan use access on subchilds:\n"); printf("access(test->left_child)->left_child: %p\n",access(test->left_child)->left_child); printf("access(test->left_child)->right_child: %p\n",access(test->left_child)->right_child); printf("\nIt's possible to chain:\n"); printf("access(access(access(test)->right_child)->right_child)->lower_value: %i\n",access(access(access(test)->right_child)->right_child)->lower_value); printf("access(access(access(test)->right_child)->right_child)->upper_value: %i\n",access(access(access(test)->right_child)->right_child)->upper_value); free_lazy_segment_tree(test); return 0; }
惰性评估测试 test-> lower_value:1 test-> upper_value:10 节点未传播 test-> left_child :(无) test-> right_child :(无) 通过访问传播的节点: 访问(测试)-> left_child:0x948e020 访问(测试)-> right_child:0x948e038 节点随访问而传播,但子节点不是: 访问(测试)-> left_child-> left_child:(无) 访问(测试)-> left_child-> right_child :(无) 可以对子孩子使用访问权限: 访问(test-> left_child)-> left_child:0x948e050 访问(test-> left_child)-> right_child:0x948e068 可以链接: access(访问(access(test)-> right_child)-> right_child)->下限值:9 access(访问(访问(测试)-> right_child)-> right_child)->上限值:10