How to implement segment trees with lazy propagation?

后端 未结 5 1331
刺人心
刺人心 2021-01-30 05:25

I have searched on internet about implementation of Segment trees but found nothing when it came to lazy propagation. There were some previous questions on stack overflow but th

5条回答
  •  遇见更好的自我
    2021-01-30 06:05

    Lazy propagation almost always includes some kind of sentry-mechanism. You have to verify that the current node doesn't need to be propagated, and this check should be easy and fast. So there are two possibilities:

    1. Sacrifice a little bit of memory to save a field in your node, which can be checked very easily
    2. Sacrifice a little bit of runtime in order to check whether the node has been propagated and whether its child nodes have to be created.

    I sticked myself to the first. It's very simple to check whether a node in a segmented tree should have child nodes (node->lower_value != node->upper_value), but you would also have to check whether those child node are already built (node->left_child, node->right_child), so I introduced a propagation flag 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;
    

    Initialization

    To initialize a node we call initialize with a pointer to the node pointer (or NULL) and the desired 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;
    }
    

    Access

    So far nothing special has been done. This looks like every other generic node creation method. However, in order to create the actual child nodes and set the propagation flags we can use a function which will return a pointer on the same node, but propagates it if needed:

    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;
    }
    

    As you can see, a propagated node will exit the function almost immediately. A not propagated node will instead first check whether it should actually contain child nodes and then create them if needed.

    This is actually the lazy-evaluation. You don't create the child nodes until needed. Note that accessErr also provides an additional error interface. If you don't need it use access instead:

    lazy_segment_node * access(lazy_segment_node* node){
      return accessErr(node,NULL);
    }
    

    Free

    In order to free those elements you can use a generic node deallocation algorithm:

    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);
    }
    

    Complete example

    The following example will use the functions described above to create a lazy-evaluated segment tree based on the interval [1,10]. You can see that after the first initialization test has no child nodes. By using access you actually generate those child nodes and can get their values (if those child nodes exists by the segmented tree's logic):

    Code

    #include 
    #include 
    
    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;
    }
    

    Result (ideone)

    Lazy evaluation test
    test->lower_value: 1
    test->upper_value: 10
    
    Node not propagated
    test->left_child: (nil)
    test->right_child: (nil)
    
    Node propagated with access:
    access(test)->left_child: 0x948e020
    access(test)->right_child: 0x948e038
    
    Node propagated with access, but subchilds are not:
    access(test)->left_child->left_child: (nil)
    access(test)->left_child->right_child: (nil)
    
    Can use access on subchilds:
    access(test->left_child)->left_child: 0x948e050
    access(test->left_child)->right_child: 0x948e068
    
    It's possible to chain:
    access(access(access(test)->right_child)->right_child)->lower_value: 9
    access(access(access(test)->right_child)->right_child)->upper_value: 10

提交回复
热议问题