在数组的中间添加新索引

Add new index in the middle of an array

本文关键字:索引 新索引 添加 数组 中间      更新时间:2023-10-16

我知道我可以移除数组中间的一些东西,比如

char* cArray = (char*) malloc(sizeof(char) * sizeTracker);

使用CCD_ 1函数。通过这种方式,将从数组中删除某些内容,而无需使用临时数组、切换到向量等。这里的问题是,我可以在数组的中间添加一个新索引吗(是否有函数)?或者假设使用realloc在末尾添加一个新索引,那么如何有效地向下移动值?

备选答案

我一直在思考这个问题,以及@DietmarKühl开始谈论像deque一样插入块的评论。这个问题是deque是一个块的链表,所以你不能从数组开始。如果你从一个数组开始,然后想在中间插入一些东西,你必须做一些其他的事情,我想我有一个想法-它不是很充实,所以它可能不起作用,但我无论如何都会分享它。请留言告诉我你对这个想法的看法。

如果你有一个项目数组,然后想在中间添加一个项目,那么你真正想做的就是添加一个块并更新映射。映射是使其全部工作的原因,但它会减慢访问速度,因为每次访问数组之前都需要检查映射。

映射将是一个二叉树。它将以空开始,但节点将包含一个值:如果您想要的索引是<遍历左指针的值,如果是>=你穿过右边的指针。

因此,举个例子:

插入前:

root -> (array[100000], offset: 0)

插入5000后:

root -> {value: 5000,
left:  (array[100000], offset: 0),
right: {value: 5001,
left:  (newarray[10], offset: -5000),
right: (array[100000], offset: 1),
}
}

我在这里使用了10个块——newarray的大小是10。如果你只是在各处随机插入索引,块大小应该是1,但如果你插入blovk大小大于1的连续索引组,那就好了。这真的取决于你的使用模式。。。

当您检查索引7000时,您会检查根节点:7000是>=5000,所以你跟随右指针:7000是>=5001,所以你跟随右指针:它指向偏移量为1的原始数组,所以你访问数组[index+offset]。

当您检查索引700时,您检查根节点:700是<5000,所以你跟随左指针:它指向偏移量为0的原始数组,所以你访问数组[index+offset]。

当您检查索引5000时,您会检查根节点:5000是>=5000,所以你跟随右指针:5000是<5001,所以你跟随左指针:它指向偏移量为-5000的新数组,所以你可以访问newarray[index+offset]。

当然,对这一点的优化对于使其有用非常重要——您必须在每次插入后平衡树,否则右侧将比左侧长得多。

这样做的缺点是,现在对数组的访问是O(日志插入)而不是O(1),所以如果有很多插入,您会希望每隔一段时间重新分配一次,以将数据结构压缩回数组,但您可以将其保存到合适的时间。

就像我说的,它不是很充实,所以在实践中可能不起作用,但我希望它无论如何都值得分享。

原始答案

如果你有一个C风格的数组,并且想在中间插入一个索引,你需要一个比你需要的大的数组(加上一个像sizeTracker这样的变量来跟踪大小)。

然后,如果有剩余的空间,你可以将数组的最后一半移动到一个位置,在在中间创建一个点。

如果没有任何空间,您可以malloc另一个包含额外空间的整个阵列,然后分别移动前半部分和后半部分,留下一个间隙。

如果你想让malloc摊销的时间恒定,每次重新分配它时,你需要将数组的大小增加一倍。memmove在x86上变成一条机器指令,但即使这样,由于移动了每个值,它仍然是O(n)。

但性能并不比删除技巧差——如果你可以在整个数组中的任何地方删除,那么成本也是O(n),因为你在删除时平均移动了一半的值。

没有自定义的C函数可以使用C内存函数增加数组并在中间插入对象。从本质上讲,您应该使用malloc()free()memmove()(当有足够的空间并且元素刚刚在内存中移回时)或memcpy()(如果您需要分配新内存,并且希望避免先复制然后移动尾部)来构建功能。

在对象位置往往很重要的C++中,您显然会使用std::copy()std::reverse_copy()和/或std::move()(两者都是),因为可能存在相关对象的结构体。很可能您也会获得不同的内存,例如,如果您真的在原始内存方面旅行,则使用memmove0和/或分配器。

实际插入的有趣实现(假设有足够的空间容纳另一个元素)是使用std::rotate()构建最后一个元素,然后对元素进行洗牌:

void insert(T* array, std::size_t size, T const& value) {
// precodition: array points to at least size+1 elements
new(array + size) T(value);
std::rotate(array, array + size, array + size + 1);
}

当然,这并不能避免在需要重新定位数组时可能不必要地打乱元素。在这种情况下,更有效的方法是分配新内存,将初始对象移动到起点,添加新插入的元素,将尾部对象移动到新对象旁边的位置。

如果使用手动分配的内存,则必须重新分配,并且希望此操作不会将内存块移动到新位置。那么最好是使用旋转算法。

顺便说一句,比起为这类任务手动分配内存,更喜欢向量等stl容器。如果您正在使用向量,则应该保留内存。

您已经将这篇文章标记为C++。

我可以在数组的中间添加一个新索引吗(是否有函数对它来说)

否。来自cppreference.com,std::array:

std::array是一个封装固定大小数组的容器。

我认为这意味着您可以更改元素,但不能更改索引。


(叹气)但我怀疑C风格的数组仍然是允许的。我注意到Dietmar的回答也说不。