Two Pointer:归并排序

本文详细介绍了2-路归并排序的基本原理与过程,包括递归与非递归版本的实现方式,通过实例展示了排序步骤,并提供了关键代码片段。
摘要由CSDN通过智能技术生成

归并排序是一种基于“归并”思想的排序方法,本节主要介绍其中最基本的2-路归并排序。

2-路归并排序的原理是,将序列两两分组,将序列归并为 n/2 个组,组内单独排序;然后将这些组再两两归并,生成 n/4 个组,组内再单独排序;以此类推,直到只剩下一个组为止。归并排序的时间复杂度为O(nlogn)。

例子

将序列 { 66,12,33,57,64,27,18 }进行2-路归并排序

① 第一趟排序:两两分组,得到四组:{ {66,12}、{33,57}、{64,27}、{18} }。组内单独排序,得到新序列:{ {12,66},{33,57},{27,64},{18}}。
② 第二趟排序:将四个组继续两两分组,得到两组:{ {12,66,33,57}、{27,64,18} },组内单独排序,得到新序列{{12,33,57,66},{18,27,64} }。

③ 第三趟排序:将两个组继续两两分组,得到一组:{12,33,57,66,18,27,64},组内单独排序,得到新序列 {12,18,27,33,57,64,66}。算法结束。

整个过程如 图4-9 所示

图4-9

从上面的过程中可以发现,2-路归并排序的核心在于如何将两个有序序列合并为一个有序序列,而这个过程在Two Pointer的“序列合并问题”中已经讲解。

接下来讨论2-路归并排序的递归版本和非递归版本的具体实现。

递归实现

2-路递归只要把 [left, right] 分成两部分 [left, mid] 和 [mid+!, right]分别递归进行归并排序即可

代码如下,其中 marger 函数为 Two Pointer 这一章节中代码修改而来

// 将数组 A 的 [L1, R1] 和 [L2, R2]区间合并为有序区间,此处 L2 即为 R1+1
public static void merge(int[] A, int L1, int R1, int L2, int R2){
    int i = 0, j = 0;	// i为A[L1]下标,j为A[L2下标
    int[] temp = new int[(R1 - L1) + (R2 - L2)];	// temp存放临时合并的数数组
    int index = 0;	// temp的下标
    while(i <= R1 && j < R2){
        if(A[i] <= A[j]){
            temp[index++] = A[i++];
        }else{
            temp[index++] = A[j++];
        }
    }
    while(i <= R1)
        temp[index++] = A[i++];
    while(j <= R2)
        temp[index++] = A[j++];
    for(int i = 0; i < index; i++){	// 将temp替换回A的L1到R2
        A[L1 + i] = temp[i];
    }
}
// 将array数据当前区间 [left, right] 进行归并排序
public static void mergeSort(int[] A, int[] left, int right){
    if(left < right){
        int mid = left +  (right - left) / 2;	// 防止超限
        mergeSort(A, left, mid);	// 递归左区间 [left, mid]
        mergeSort(A, mid+1, right);	// 递归右区间 [mid+1, right]
        merge(A, left, mid, mid+1, right);	// 将左右子区间合并
    }
}

非递归实现

2-路归并排序的非递归实现主要考虑到这样一点:每次分组时组内元素个数上限都是2的幂次。

于是就可以想到这样的思路:令步长 step 的初值为 2,然后将数组中每 step 个元素作为一组,将其内部进行排序(即把左 step/2 个元素与右step/2个元素合并,而若元素个数不超过step/2,则不操作);再令step乘以2

重复上面的操作,直到 step/2 超过元素个数 n

代码如下

public static void mergeSort(int A[]){
    // step 为组内元素个数,step/2 为左子区间元素个数,注意等号可以不写(那就没必要存在①处的判断)
    for(int step = 2; step / 2 <= n; step *= 2){
        // 每 step 个元素一组,组内前 step/2 和后 step/2 个元素进行合并
        for(int i=0; i<n; i+=step){	// 对每一组
            int mid = i + step / 2;	
            if(mid + 1 <= n){	// ①:判断右子区间是否有元素,有就合并
                // 左子区间[i, mid], 右子区间为[mid+1,Math.min(i+step, n)] (这是为了防止超过步长)
                merge(A, i, mid, mid+1, Math.min(i+step, n-1);
            }
        }
    }
}

注意点:

  1. n 是元素个数,不是最后一个元素的下标,最后元素下标应为 n-1

  2. 当最外层循环为 step/2<n 时,就不会出现右子区间无元素的情况,所以①处的判断可以省略

  3. 合并时的 右子区间上限 需要判断是否会超过 n-1 (即最后一个元素的下标)

    例如:n 为 5,步长为 4 时

当然,如果时间充足,merge() 处完全可以使用语言API自带的排序


参考:胡凡《算法笔记》
### C++ 中单链表基本操作与排序 #### 单链表的基本操作 在C++中,单链表的操作主要包括创建节点、遍历链表、插入节点以及删除节点等。这些操作构成了更复杂功能的基础。 对于节点定义而言,通常会采用如下形式: ```cpp struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(nullptr) {} }; ``` 此结构体`ListNode`用于表示链表中的每一个节点[^5]。 #### 插入和删除操作 为了向链表中添加新的元素或者移除已有元素,可以编写相应的成员函数或全局辅助函数来进行处理。例如,要在头部插入一个新的结点,则可按照下面的方式编码: ```cpp void insertAtHead(ListNode*& head, int value){ ListNode* newNode = new ListNode(value); newNode->next = head; head = newNode; } ``` 而要从指定位置处摘下某个特定数值所在的单元格,就需要先定位到前驱的位置再做调整连接关系的动作: ```cpp bool removeNode(ListNode*& head, int targetValue){ if (!head) return false; // Remove from the beginning of list. if (head->val == targetValue){ ListNode* temp = head; head = head->next; delete temp; return true; } // Search and remove node that is not at start. ListNode* current = head; while(current->next && current->next->val != targetValue){ current = current->next; } if(!current->next) return false; // Target was not found. ListNode* toDelete = current->next; current->next = current->next->next; delete toDelete; return true; } ``` 以上展示了两种典型情况下的增删逻辑实现方法。 #### 排序算法的应用 当涉及到对整个序列进行有序化排列时,考虑到性能因素,推荐使用归并排序而非简单的冒泡或是选择方式。因为后者时间开销较大(O(n²)),难以满足实际应用需求。相比之下,基于分治策略的Merge Sort能够达到线性对数级别的时间消耗O(n log n),更适合大规模数据集上的排序任务。 具体来说,就是利用递归来不断分割原始列表直到只剩余单独项为止,之后再逐步组合成完整的升序/降序链条。这里给出一个具体的例子来说明这一过程是如何运作的: ```cpp // Function to merge two sorted lists into one. ListNode* Merge(ListNode* a, ListNode* b){ if(a==nullptr)return b; if(b==nullptr)return a; if(a->val<b->val){ a->next=Merge(a->next,b); return a; }else{ b->next=Merge(a,b->next); return b; } } // Recursive function implementing mergesort on linked list. ListNode* sortInList(ListNode* head){ if(head==nullptr||head->next==nullptr){ return head; } // Splitting phase using slow-fast pointer technique. ListNode* fast=head,*slow=head,*prev=nullptr; while(fast!=nullptr&&fast->next!=nullptr){ prev=slow; slow=slow->next; fast=fast->next->next; } prev->next=nullptr; // Sorting each half recursively then merging them back together. return Merge(sortInList(head),sortInList(slow)); } ``` 上述代码片段实现了针对单链表版本的自顶向下归并排序算法[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值