您能帮我调试这段涉及下面解释的合并算法的 C++ 代码吗?我怀疑索引指针导致了该错误。以下是包含所需规格的段落。
作业规范: 下面的问题要求我们将两个已经排序的列表 (x1, ... , xm) 和 (x_(m+1), ..., x_n) 合并为一个排序列表 (x_l, ..., x_n )。让
s = floor(sqrt(n))
。假设其中一个列表的记录少于 s 条,并且第一个列表比第二个列表短。我们被要求编写一个函数来合并两个排序列表。此合并函数必须使用circularShiftRight() 函数,该函数将记录向右循环移动p 个位置。如果第一个列表的元素少于 s,那么我们应该在第一个列表的第一个元素的合并列表中找到位置 q。然后我们应该使用circularShiftRight()函数执行q-1个位置的循环移位。这种循环移位仅涉及第一个列表的记录和第二个列表的前 q - 1 条记录。循环移位后,前 q 条记录位于其最终合并位置。对初始第一个列表的第二个、第三个和剩余元素重复此过程。
这是下面的 MRE:
#include <cmath>
#include <iostream>
class Element {
public:
int getKey() const { return key; }
void setKey(int k) { key = k; }
Element(int k = 0) : key(k) {}
private:
int key;
};
void reverseSegment(Element* arr, int start, int end) {
while (start < end) {
int tempKey = arr[start].getKey();
arr[start].setKey(arr[end].getKey());
arr[end].setKey(tempKey);
start++;
end--;
}
}
void circularShiftRight(Element* arr, int n, int p) {
if (n <= 1 || p == 0) return;
p = p % n;
if (p == 0) return;
reverseSegment(arr, 0, n - 1);
reverseSegment(arr, 0, p - 1);
reverseSegment(arr, p, n - 1);
}
void printArray(Element* arr, int n) {
std::cout << "Array: ";
for (int i = 0; i < n; i++)
std::cout << arr[i].getKey() << " ";
std::cout << std::endl;
}
void mergeWithShift(Element* arr, int start1, int len1, int start2, int len2) {
int n = len1 + len2;
int s = static_cast<int>(floor(sqrt(n)));
std::cout << "sqrt(n) = " << s << std::endl;
std::cout << "First list length = " << len1 << std::endl;
if (len1 <= s) {
std::cout << "Processing: First list is shorter than or equal to sqrt(n)" << std::endl;
for (int i = 0; i < len1; i++) {
int currentElement = arr[start1 + i].getKey();
std::cout << "\nProcessing element " << currentElement << " from position " << (start1 + i) << std::endl;
// Find position q where current element belongs by comparing with second list
int q = start2;
while (q < start2 + len2 && arr[q].getKey() < currentElement) {
q++;
}
std::cout << "Element " << currentElement << " should move to before position " << q << std::endl;
if (q > start1 + i) {
// Calculate number of positions to involve in the shift
// This includes elements from current position to q - 1
int shiftRange = q - (start1 + i);
std::cout << "Performing circular shift on range [" << (start1 + i) << ", " << (q-1)
<< "] right by " << shiftRange - 1 << " positions" << std::endl;
// circularShiftRight on the range from current position to q - 1
// The shift amount is (shiftRange - 1) to move current element to position q - 1
circularShiftRight(arr + start1 + i, shiftRange, shiftRange - 1);
}
std::cout << "Array after processing element " << currentElement << ": ";
printArray(arr, n);
}
} else {
std::cout << "First list is longer than sqrt(n)" << std::endl;
}
}
int main() {
Element arr[10] = {1, 4, 7, 2, 3, 5, 6, 8, 9, 10};
int start1 = 0, len1 = 3; // First list: {1, 4, 7}
int start2 = 3, len2 = 7; // Second list: {2, 3, 5, 6, 8, 9, 10}
std::cout << "Original ";
printArray(arr, len1 + len2);
mergeWithShift(arr, start1, len1, start2, len2);
std::cout << "Merged ";
printArray(arr, len1 + len2);
return 0;
}
输出如下所示:
Test Case 1:
Original Array: 1 4 7 2 3 5 6 8 9 10
sqrt(n) = 3
First list length = 3
Processing: First list is shorter than or equal to sqrt(n)
Processing element 1 from position 0
Element 1 should move to before position 3
Performing circular shift on range [0, 2] right by 2 positions
Array after processing element 1: Array: 4 7 1 2 3 5 6 8 9 10
Processing element 7 from position 1
Element 7 should move to before position 7
Performing circular shift on range [1, 6] right by 5 positions
Array after processing element 7: Array: 4 1 2 3 5 6 7 8 9 10
Processing element 2 from position 2
Element 2 should move to before position 3
Performing circular shift on range [2, 2] right by 0 positions
Array after processing element 2: Array: 4 1 2 3 5 6 7 8 9 10
Merged Array: 4 1 2 3 5 6 7 8 9 10
结果 4 1 2 3 5 6 7 8 9 10 应该是 1 2 3 4 5 6 7 8 9 10。
首先,算法描述有一个错误的指令:“我们应该执行 q - 1 个位置的循环移位”。他们将 q
定义为“合并列表中的位置”,因此不是从最左边开始计数,而是从第二个列表的开头开始计数。这从短语“第二个的前 q - 1 条记录” 中也可以清楚地看出。但这意味着移位范围的大小大于
q
,因为我们应该将第一个分区的元素包含到移位中。但如果我们只向右移动
q - 1
个位置,目标元素不一定会到达其所需的索引。就好像作者突然将
q
定义为整个数组最左边的索引。为了使算法正常工作,移位应该将最左边的值带到第二个分区中的排序位置。如果
q
表示overall 数组中的索引,那么正确的说法是
q
元素应向右移动
q-1
次。以下是如何逐步处理示例输入:
我们有两个初始分区:
(1 4 7) (2 3 5 6 8 9 10)
然后我们确定 1 在第二个分区中应该结束的位置,即 2 之前(用箭头指示):
(1 4 7) (2 3 5 6 8 9 10)
↑
现在 1 应该旋转到该位置。移位将涉及箭头左侧的所有值。一旦该转变完成,1 将成为第二个分区的一部分:
Rotated
-----------
(4 7) (1 2 3 5 6 8 9 10)
然后我们继续使用值 4,它应该在第二个列表中位于 3 和 5 之间:
(4 7) (1 2 3 5 6 8 9 10)
↑
4 应旋转到该位置:
Rotated
-------------------
(7) (1 2 3 4 5 6 8 9 10)
最后要处理的值是 7,它要放在 6 和 8 之间:
(7) (1 2 3 4 5 6 8 9 10)
↑
最后的旋转使这一切发生:
Rotated
---------------------------
(1 2 3 4 5 6 7 8 9 10)
现在第二个分区是唯一剩下的分区,并且已排序。您的方法中的一些问题
“循环移位只涉及第一个列表的记录和第二个列表的前q-1条记录”,也就是说第一个列表的全部都参与了移位。因此,您的轮班调用应始终传递 0 作为第一个索引。
“对初始第一个列表的第二个、第三个和剩余元素重复此过程。”。由于第一个列表中的所有值在每次迭代中都会移动,因此当您需要处理它们时,第一个列表中的“第二个、第三个和剩余”元素将位于索引 0 处。因此,您的代码不应从索引 i
获取当前值,而应从索引 0 获取当前值。
不是问题,但由于
start1
始终为 0,并且 start2
len1
,我会减少代码中变量和参数的数量,只需
n
和
len1
就足够了为了这个目的。
这是建议的改编代码:
void mergeWithShift(Element* arr, int n, int len1) {
int s = static_cast<int>(floor(sqrt(n)));
if (len1 > s) {
std::cout << "First list is longer than sqrt(n). Merge aborted." << std::endl;
return;
}
while (len1 > 0) { // The first partition shrinks in each iteration
int currentElement = arr[0].getKey(); // Get the now left-most element
// Find index in the second list before which this element should be rotated
int q = len1;
while (q < n && arr[q].getKey() < currentElement) {
q++;
}
// The shift always starts at index 0: rotate the range [0, q-1]
circularShiftRight(arr, q, q - 1);
len1--; // The merged (second) partition has grown.
}
}
int main() {
Element arr[10] = {1, 4, 7, 2, 3, 5, 6, 8, 9, 10};
int n = 10; // Total length of both partitions together
int len1 = 3; // First partition: {1, 4, 7}
std::cout << "Original ";
printArray(arr, n);
mergeWithShift(arr, n, len1);
std::cout << "Merged ";
printArray(arr, n);
return 0;
}
如果我们假设移位操作是一个可以在常数时间内执行的黑盒操作,那么您可以通过用二分查找替换内部while