调试算法,合并已使用循环移位排序的两个列表

问题描述 投票:0回答:1

您能帮我调试这段涉及下面解释的合并算法的 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。

c++ sorting data-structures merge
1个回答
0
投票

首先,算法描述有一个错误的指令:“我们应该执行 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

循环来提高效率。
    

© www.soinside.com 2019 - 2024. All rights reserved.