我在C中有一个循环的,静态分配的缓冲区,我将其用作 depth 广度优先搜索的队列。我想排队队列中的前N个元素。使用常规qsort()会很容易 - 除了它是一个循环缓冲区,并且前N个元素可能会环绕。当然,我可以编写自己的排序实现,它使用模块化算法并知道如何包装数组,但我一直认为编写排序函数是一个很好的练习,但最好留给库。
我想到了几种方法:
另一方面,花一个小时考虑如何优雅地避免编写我自己的快速排序,而不是添加那些25(左右)线可能也不是最有效...
更正:犯了一个转换DFS和BFS的愚蠢错误(我更喜欢编写DFS,但在这个特殊情况下我必须使用BFS),抱歉让人感到困惑。
原始问题的进一步说明:
我正在实施广度首次搜索(与fifteen拼图不同的东西,更复杂,每个州都有大约O(n ^ 2)个可能的扩展,而不是4)。完成了“强力”算法,但它是“愚蠢的” - 在每个点上,它以硬编码的顺序扩展所有有效状态。队列实现为循环缓冲区(unsigned queue [MAXLENGTH]),并将整数索引存储到状态表中。除了两个简单的函数来排队和出列索引之外,它没有封装 - 它只是一个简单的,静态分配的无符号数组。
现在我想添加一些启发式方法。我想要尝试的第一件事是在扩展后对扩展的子状态进行排序(“以更好的顺序扩展它们”) - 就像我编写一个简单的最佳第一个DFS一样。为此,我想参与队列的一部分(代表最近的扩展状态),并使用某种启发式对它们进行排序。我也可以按不同的顺序扩展状态(所以在这种情况下,如果我打破队列的FIFO属性,它就不是很重要了。)
我的目标不是实现A*,也不是基于深度优先搜索的算法(我无法扩展所有状态,但如果我不这样做,我将开始遇到无限循环问题状态空间,所以我必须使用类似iterative deepening)的东西。
答案 0 :(得分:5)
我认为您需要从问题中退一步并尝试将其作为一个整体来解决 - 半排序循环缓冲区不是存储数据的最佳方式。如果是,那么你已经提交了,你必须编写缓冲区来对元素进行排序 - 这是否意味着偶尔对外部库进行排序,或者在插入元素时进行,我不知道。 但是在一天结束时它会变得很难看,因为FIFO和排序缓冲区根本不同。
上一个答案,假设您的排序库具有强大且功能丰富的API(根据您的问题要求,这不需要您编写自己的mod排序或任何东西 - 它取决于支持任意位置的库数据,通常通过回调函数。如果你的排序不支持链表,它就无法解决这个问题):
循环缓冲区已经使用%(mod)算法解决了这个问题。 QSort等不关心内存中的位置 - 他们只需要一个以线性方式处理数据的方案。
它们对于链接列表(在内存中不是线性的)也适用于“真实”线性非圆形数组。
因此,如果您有一个包含100个条目的圆形数组,并且您发现需要对前10个进行排序,并且前十个恰好在顶部包裹了一半,那么您将对以下两位信息进行排序:
该函数将sort排序使用的地址转换为真实数组中使用的索引,并且数组环绕的事实是隐藏的,尽管将数组排序超过其边界(圆形数组)可能看起来很奇怪。定义,没有界限。 %运算符会为您处理,您也可以将数组的部分称为1295到1305,只关注它。
具有2 ^ n个元素的数组的加分点。
在我看来,你正在使用一个排序库,它不能排序除线性数组之外的任何东西 - 因此它不能对链接列表进行排序,也不能对除简单排序之外的任何数组进行排序。你真的只有三个选择:
现在,就我而言,我会重新编写排序代码,因此它更灵活(或复制它并编辑新副本,因此您可以对线性数组进行快速排序,并对非非常灵活的排序线性阵列)
但实际情况是,现在您的排序库非常简单,您甚至无法告诉它如何访问非线性存储的数据。
如果这很简单,应该毫不犹豫地使库本身适应您的特定需求,或者将缓冲区调整到库中。
尝试一个丑陋的kludge,就像以某种方式将你的缓冲区变成一个线性阵列,对它进行排序,然后把它重新放入 - 这是一个丑陋的kludge,你将不得不理解和维护。你将要“打破”你的先进先出,然后摆弄内脏。
- 亚当
答案 1 :(得分:2)
也许可以调整priority queue来解决您的问题。'
答案 2 :(得分:2)
您可以旋转循环队列,直到相关的子集不再包围。然后像往常一样将该子集传递给qsort
。如果您需要经常排序或者数组元素大小非常大,这可能会很昂贵。但是如果你的数组元素只是指向其他对象的指针,那么旋转队列可能足够快。事实上,如果它们只是指针,那么你的第一种方法也可能足够快:制作一个子集的单独线性副本,对其进行排序,然后将结果写回来。
答案 3 :(得分:2)
我没有看到你在c中要求的解决方案。您可以考虑以下其中一个想法:
如果您可以访问libc
的{{1}}的源代码,则可以复制它,只需用适当的通用等效代码替换所有数组访问和索引代码。这为您提供了一些适度的保证,即基础排序是有效的并且几乎没有错误。当然,没有帮助引入自己的错误的风险。 Big O喜欢系统qsort()
,但可能会有更差的乘数。
如果要排序的区域与缓冲区的大小相比较小,则可以使用直接线性排序,使用测试包装保护调用并执行copy-to-linear-仅在需要时缓冲 - 排序 - 然后 - 复制 - 例程。在导致守卫的情况下(对于要排序的区域的大小qsort
)引入额外的O(n)
操作,这使得平均n
。
我发现C ++不是你的选择。 ::叹气::我会留在这里以防其他人可以使用它。
O(n^2/N) < O(n)
运算符以使标准排序算法起作用。同样,应该像标准排序那样工作,并且有乘数惩罚。答案 4 :(得分:1)
您是否了解有关优化的规则?你可以谷歌他们(你会发现一些版本,但他们都说同样的事情,不要)。
听起来你在没有测试的情况下进行优化。这是一个巨大的禁忌。另一方面,你使用的是直接C,所以你可能在受限制的平台上需要一定程度的关注速度,所以我希望你需要跳过前两条规则,因为我认为你别无选择:
优化规则:
不要优化。
如果您知道自己在做什么,请参阅规则#1
您可以转到更高级的规则:
优化规则(续):
如果您的规范需要一定程度的性能,请编写未经优化的代码并编写测试以查看它是否符合该规范。如果遇到它,你就完成了。在达到这一点之前,千万不要考虑将性能考虑在内的代码。
如果您完成第3步并且您的代码不符合规范,请将其重新编码,将原始“最明显”的代码留在那里作为注释并重新测试。如果它不符合要求,请将其丢弃并使用未经优化的代码。
如果您的改进使测试通过,请确保测试保留在代码库中并重新运行,并且原始代码仍作为注释保留在那里。
注意:应该是3. 4. 5.有些东西搞砸了 - 我甚至没有使用任何标记标记。
好的,最后 - 我不是这样说的,因为我在某处读到了它。我花了DAYS试图解开其他人编码的一些可怕的混乱,因为它是“优化的” - 真正有趣的部分是10次中的9次,编译器可以比他们更好地优化它。 / p>
我意识到有时候你需要优化,我所说的只是写它未经优化,测试并重新编码。它真的不会花费你更长时间 - 甚至可以使编写优化代码更容易。
我发布此内容的唯一原因是因为您撰写的几乎所有内容都与性能有关,我担心下一个看到您的代码的人会像我一样陷入困境。
答案 5 :(得分:0)
这个例子怎么样?这个例子可以轻松地对一个部分或任何你想要的东西进行排序,而无需重新定义大量的额外内存。 它需要两个指针状态位和一个for循环计数器。
#define _PRINT_PROGRESS
#define N 10
BYTE buff[N]={4,5,2,1,3,5,8,6,4,3};
BYTE *a = buff;
BYTE *b = buff;
BYTE changed = 0;
int main(void)
{
BYTE n=0;
do
{
b++;
changed = 0;
for(n=0;n<(N-1);n++)
{
if(*a > *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
changed = 1;
}
a++;
b++;
}
a = buff;
b = buff;
#ifdef _PRINT_PROGRESS
for(n=0;n<N;n++)
printf("%d",buff[n]);
printf("\n");
}
#endif
while(changed);
system( "pause" );
}