【问题标题】:Fastest way to remove huge number of elements from an array in C从 C 中的数组中删除大量元素的最快方法
【发布时间】:2016-06-19 20:00:15
【问题描述】:

我有一个包含数千个甚至更多元素的动态数组,为了不消耗大量内存,我可以从中删除不需要的元素(即元素已被使用并且不再需要它们)所以从一开始我可以通过每次删除元素后估计所需的最大大小来分配较小的内存大小。

我使用这种方式,但是需要很长时间才能完成,有时需要30分钟!

int x, y ;
for (x = 0 ; x<number_of_elements_to_remove ; x++){
    for (y = 0 ; y<size_of_array; y++ ){
            array[y] = array[y+1];
    }
}

还有比这更快的方法吗?

【问题讨论】:

  • 这个例子我看不懂。
  • 除非我误读了您的代码示例,否则 x 不会在其他循环或数组索引中的任何地方使用,那么重点是什么?
  • 你想做什么?是要清除数组某些元素中的数据,还是要通过永久删除不需要的块来减少内存?
  • 您是否测试过这段代码(在较小的输入上)?是否如您所愿?
  • 一种方法是制作“指向数据的指针数组”而不是“数据数组”,并且每个元素都填充有动态分配的内存。这样 1) 放宽了内存连续性要求,因此在碎片化的内存池中分配更有可能成功; 2)阵列本身更小,因此调整大小更便宜; 3)或者您甚至不需要调整它的大小,因为您的用例是“仅缩小”,因此您可以释放指针,然后为其分配 null 以将其标记为无效。

标签: c arrays


【解决方案1】:

您可以创建一个具有单个读取和单个写入索引的单个循环,而不是一次删除一个元素,两个循环构成 O(n2) 解决方案。遍历数组,边走边复制项目:

int rd = 0, wr = 0;
while (rd != size_of_array) {
    if (keep_element(array[rd])) {
        array[wr++] = array[rd];
    }
    rd++;
}

循环末尾的wrarray 中保留的元素数。

【讨论】:

    【解决方案2】:

    我注意到你只想删除数组开头的元素,试试这个:

      int x;
            for(x = 0 ; x< size_of_array - number_of_elements_to_remove; x++)
               array[x] = array[number_of_elements_to_remove + x];
    

    这样你就使用了一个 for 循环,大大降低了复杂性

    【讨论】:

      【解决方案3】:

      看来你基本上是这样做的

      int y;
      for (y = 0; y<size_of_array; y++){
         array[y] = array[y+numbre_of_elements_to_remove];
      }
      

      上面应该更快,但是您的代码仍然存在一些警告/问题(例如,访问超出数组的末尾)。

      【讨论】:

      • 还有你的。如果您这样运行循环,您将到达y == size_of_array - 1 尝试访问array[y+number_of_elements_to_remove],它会在number_of_elements_to_remove &gt; 0 时访问数组越界。
      【解决方案4】:

      这是对数组进行碎片整理的代码。

      int sparse_to_compact(int*arr, int total, int*is_valid) {
              int i = 0;
              int last = total - 1;
              // trim the last invalid elements
              for(; last >= 0 && !is_valid[last]; last--); // trim invalid elements from last
      
              // now we keep swapping the invalid with last valid element
              for(i=0; i < last; i++) {
                      if(is_valid[i])
                              continue;
                      arr[i] = arr[last]; // swap invalid with the last valid
                      last--;
                      for(; last >= 0 && !is_valid[last]; last--); // trim invalid elements
              }
              return last+1; // return the compact length of the array
      }
      

      我从this 答案中复制了代码。

      我认为更有效的方法是使用存储桶的链接列表。桶由位串内存管理器管理。是这样的,

      struct elem {
           uint32_t index; /* helper to locate it's position in the array */
           int x; /* The content/object kept in the array */
      }
      

      假设,我们的数组内容是int,它被封装在一个名为struct elem的结构体中。

      enum {
           MAX_BUCKET_SIZE = 1024,
           MAX_BITMASK_SIZE = (MAX_BUCKET_SIZE + 63) >> 6,
      };
      
      struct bucket {
          struct bucket*next; /* link to the next bucket */
          uint64_t usage[MAX_BITMASK_SIZE]; /* track memory usage */
          struct elem[MAX_BUCKET_SIZE]; /* the array */
      };
      

      存储桶定义为struct elem 和使用掩码的数组。

      struct bucket_list {
          struct bucket*head; /* dynamically allocated bucket */
      }container;
      

      而桶列表是一个包含所有桶的链表。

      所以我们需要编写内存管理器代码。

      首先我们需要在需要时分配新的桶。

      struct bucket*bk = get_empty_bucket(&container);
      if(!bk) { /* no empty bucket */
          /* allocate a bucket */
          struct bucket*bk = (struct bucket*)malloc(sizeof(struct bucket));
          assert(bk);
          /* cleanup the usage flag */
          memset(bk->usage, 0, sizeof(bk->usage));
          /* link the bucket */
          bk->next = container.head;
          container.head = bk; 
      }
      

      现在我们有了桶,需要在需要时设置数组中的值。

      for(i = 0; i < MAX_BITMASK_SIZE; i++) {
          uint64_t bits = ~bk.usage[i];
          if(!bits) continue; /* no space */
          /* get the next empty position */
          int bit_index = _builtin_ctzl(bits);
          int index = (i<<6)+bit_index;
          /* set the array value */
          bk->elem[index].index = index;
          bk->elem[index].x = 34/* my value */;
          bk.usage[i] |= 1<<bit_index; /* mark/flag the array element as used */
      }
      

      删除数组元素很容易,因为将它们标记为未使用。现在,当桶中的所有元素都未使用时,我们可以从链接列表中删除桶。

      我们有时可以对存储桶进行碎片整理或优化它们以适应更小的空间。否则,当我们分配新元素时,我们可以选择更拥挤的桶而不是拥挤的桶。当我们删除时,我们可以将不太拥挤的元素交换为更拥挤的元素。

      可以有效地删除数组元素,

      int remove_element(int*from, int total, int index) {
              if(index != (total-1))
                      from[index] = from[total-1];
              return total; // **DO NOT DECREASE** the total here
      }
      

      这是通过将元素与最后一个值交换来完成的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-01
        • 2018-03-31
        • 2015-07-30
        • 2013-03-27
        相关资源
        最近更新 更多