合并两个列表的 O 复杂性

ggo*_*d75 4 algorithm merge time-complexity singly-linked-list

给定 2 个已排序的单链表,合并这些列表。

示例:
列表 1:1 2 3 5 7
列表 2:0 4 6 7 10
---> 0 1 2 3 4 5 6 7 7 10

尽管解决方案非常简单,并且使用或不使用递归有几种不同的问题实现(例如http://www.geeksforgeeks.org/merge-two-sorted-linked-lists/ 参见方法3) ),

我想知道这个实现的复杂性是多少:

  1. 如果其中一个列表为空,则返回另一个
  2. 否则,使用sortedInsert函数将第二个列表的每个节点插入第一个列表,该函数基本上扫描列表直到找到正确的位置。因为两个列表都已经排序,所以不需要每次都将节点与第一个列表中的所有节点进行比较,我可以从最后添加的节点开始比较。

例如:如果已经添加了 4,则继续前面的示例,我可以安全地从 4 开始下一个比较:
list1: 0 1 2 3 4 5 7
list2: 6 7 10
现在将 6 与 4 进行比较,而不是与 1 2 3 4 进行比较。 ..

如果我将一个元素与第一个列表中的所有元素进行比较,则其复杂度为 O(m*n),其中 m=#list2 和 n=#list1,但考虑到这种“优化”,复杂性是多少?

执行:

// Insert a new node in a sorted list
int sortedInsert(struct ListNode **head, struct ListNode* newNode) {
    int headUpdated = 0;
    struct ListNode *current;

    // The list is empty or the new element has to be added as first element
    if (*head == NULL || (*head)->data >= newNode->data) {
        newNode->next = *head;
        *head = newNode;
        headUpdated = 1;
    }
    else {
        // Locate the node before the point of insertion
        current = *head;
        while (current->next != NULL && current->next->data < newNode->data)
            current = current->next;

        newNode->next = current->next;
        current->next = newNode;
    }

    return headUpdated;
}


struct ListNode *mergeLists(struct ListNode *head1, struct ListNode *head2) {
    if (head1 == NULL)
        return head2;

    if (head2 == NULL)
        return head1;

    // Store the node in the first list where to start the comparisons
    struct ListNode *first = head1;

    while (head2) {
        struct ListNode *head2Next = head2->next;

        printf("Adding %d starting comparison from %d\n", head2->data, first->data);
        if (sortedInsert(&first, head2))
            head1 = first;

        first = head2;
        head2 = head2Next;
    }

    return head1;
}
Run Code Online (Sandbox Code Playgroud)

Dan*_*her 5

实际上,您这里的合并算法是O(m + n),不是O(m * n)

由于您有一个指向最后插入的节点的指针,并开始寻找从该节点开始插入下一个节点的位置,因此总数

current = current->next
Run Code Online (Sandbox Code Playgroud)

的操作sortedInsert最多为m + n - 1(结果长度减一)。插入操作(重新链接指针)的数量nextn(第二个列表的长度)。对于每次比较

current->next->data < newNode->data
Run Code Online (Sandbox Code Playgroud)

下一个操作要么是插入,要么是 a current = current->next,因此比较次数最多为

m + 2*n - 1
Run Code Online (Sandbox Code Playgroud)

假设结果列表m_0从第一个列表中的元素开始,然后是n_1第二个列表中的元素,然后m_1是第一个列表中的元素,第二个n_2列表中的元素,...,n_r第二个列表中的元素,最后m_r是第一个列表中的元素。m_0并且m_r可能为 0,所有其他数字都严格为正数。

将块的第一个元素与块的每个元素和块的第一个元素n_1进行比较。该块的所有其他元素都与第二个列表中的前一个元素以及该块的第一个元素进行比较[除非和,在这种情况下比较较少]。m_0m_1m_1r = 1m_1 = 0

这使得m_0 + 1 + (n_1 - 1)*2 = m_0 + 2*n_1 - 1比较(或更少)。

对于后面的n_k块,第一个元素与块的最后一个元素n_(k-1)、块的所有元素m_(k-1)以及块的第一个元素m_k[如果存在]进行比较。该块的其他元素都与列表 2 中的前一个元素以及该m_k块的第一个元素[如果存在]进行比较,这使得

 1 + m_(k-1) + 1 + (n_k - 1)*2 = m_(k-1) + 2*n_k
Run Code Online (Sandbox Code Playgroud)

比较(或更少)。由于所有比较至少涉及第二个列表的一个元素,因此比较总数最多为

m_0 + 2*n_1 - 1 + m_1 + 2*n_2 + m_2 + 2*n_3 + ... + m_(r-1) + 2*n_r <= m + 2*n - 1.
Run Code Online (Sandbox Code Playgroud)

我们可以通过初始化来稍微改进它

    struct ListNode *first = head1;
Run Code Online (Sandbox Code Playgroud)

并删除

    if (!first)
        first = head1;
Run Code Online (Sandbox Code Playgroud)

从循环中进行测试。