手撕排序算法:归并排序


归并是一种常见的算法思想,在许多领域都有广泛的应用。归并排序的主要目的是将两个已排
序的序列合并成一个有序的序列。

1.算法思想

当然,对于一个非有序的序列,可以拆成两个非有序的序列,然后分别调用归并排序,然后对
两个有序的序列再执行合并的过程。所以这里的“归”其实是递归,“并”就是合并。
整个算法的执行过程用mergeSort(a[],l,r)描述,代表当前待排序数组a,左区间下标l,右区
间下标r,分以下几步:
第一步、计算中点mid=(l+r)/2;
第二步、递归调用mergeSort(a[,l,mid)和mergeSort(a[],mid+1,r);
第三步、第二步中两个有序数组进行有序合并,再存储到a[l:];调用时,调用mergeSort(a[],0,n-1)就能得到整个数组的排序结果了。

2.算法分析

2.1时间复杂度

我们假设「比较」和「赋值」的时间复杂度为O(1)。
我们首先讨论「归并排序」算法的最重要的子程序:O()的合并,然后解析这个归并排序算法。
给定两个大小为n1和2的排序数组A和B,我们可以在O(n)时间内将它们有效地归并成一个大
小为n=n1+n2的组合排序数组。可以通过简单地比较两个数组的前面的元素,并始终取两个中较小
的一个来实现的。
问题是这个归并过程被调用了多少次?
由于每次都是对半切,所以整个归并过程类似于一颗二叉树的构建过程,次数就是二叉树的高
度,即log2n,所以归并排序的时间复杂度为O(nlog2n)。

2.2空间复杂度

由于归并排序在归并过程中需要额外的一个「辅助数组」,并且最大长度为原数组长度,所
以「归并排序」的空间复杂度为O(n)。

3.算法优缺点

3.1算法的优点

1.稳定性:归并算法是一种稳定的排序算法,这意味着在排序过程中,相同元素的相对顺序保持不
变。
2.可扩展性:归并算法可以很容易地扩展到并行计算环境中,通过并行归并来提高排序效率。

3.2算法的缺点

1.额外空间:归并算法需要使用额外的辅助空间来存储合并后的结果,这对于内存受限的情况可自
是一个问题。
2.复杂性:归并算法的实现相对复杂,相比于其它一些简单的排序。

4.优化方案

「归并排序」在众多排序算法中效率较高,时间复杂度为O(nlog2n)。
但是,由于归并排序在归并过程中需要额外的一个「辅助数组」,所以申请「辅助数组」内存空间带来的时间消耗会比较大,比较好的做法是,实现用一个和给定元素个数一样大的数组,作为函数传参传进去,所有的「辅助数组」干的事情,都可以在这个传参进去的数组上进行操作,这样就免去了内存的频繁申请和释放。

5.代码演示

void merge(int arr[], int left, int mid, int right) {  
    int i, j, k;  
    int n1 = mid - left + 1;  
    int n2 = right - mid;  
      
    // 创建临时数组  
    int L[n1], R[n2];  
    // 将数据复制到临时数组 L[] 和 R[]    for (i = 0; i < n1; i++)  
        L[i] = arr[left + i];  
    for (j = 0; j < n2; j++)  
        R[j] = arr[mid + 1 + j];  
      
    // 归并临时数组到 arr[]    i = 0; // 初始化第一个子数组的索引  
    j = 0; // 初始化第二个子数组的索引  
    k = left; // 初始化合并后子数组的索引  
    while (i < n1 && j < n2) {  
        if (L[i] <= R[j]) {  
            arr[k] = L[i];  
            i++;  
        }  
        else {  
            arr[k] = R[j];  
            j++;  
        }  
        k++;  
    }  
    // 将 L[] 的剩余元素复制到 arr[]    while (i < n1) {  
        arr[k] = L[i];  
        i++;  
        k++;  
    }  
    // 将 R[] 的剩余元素复制到 arr[]    while (j < n2) {  
        arr[k] = R[j];  
        j++;  
        k++;  
    }  
}  
  
// 归并排序函数  
void merge_sort(int arr[], int left, int right) {  
    if (left < right) {  
        // 找到中间点  
        int mid = left + (right - left) / 2;  
          
        // 递归地对左右两半进行排序  
        merge_sort(arr, left, mid);  
        merge_sort(arr, mid + 1, right);  
          
        // 合并已排序的两个子数组  
        merge(arr, left, mid, right);  
    }  
}

6.实战

6.1力扣912 排序数组

给你一个整数数组 nums,请你将该数组升序排列。

void merge(int arr[], int left, int mid, int right) {

    int i, j, k;

    int n1 = mid - left + 1;

    int n2 = right - mid;

    // 创建临时数组

    int L[n1], R[n2];

    // 将数据复制到临时数组 L[] 和 R[]

    for (i = 0; i < n1; i++)

        L[i] = arr[left + i];

    for (j = 0; j < n2; j++)

        R[j] = arr[mid + 1 + j];

    // 归并临时数组到 arr[]

    i = 0; // 初始化第一个子数组的索引

    j = 0; // 初始化第二个子数组的索引

    k = left; // 初始化合并后子数组的索引

    while (i < n1 && j < n2) {

        if (L[i] <= R[j]) {

            arr[k] = L[i];

            i++;

        }

        else {

            arr[k] = R[j];

            j++;

        }

        k++;

    }

    // 将 L[] 的剩余元素复制到 arr[]

    while (i < n1) {

        arr[k] = L[i];

        i++;

        k++;

    }

    // 将 R[] 的剩余元素复制到 arr[]

    while (j < n2) {

        arr[k] = R[j];

        j++;

        k++;

    }
}

// 归并排序函数
void merge_sort(int arr[], int left, int right) {
    if (left < right) {
        // 找到中间点
        int mid = left + (right - left) / 2;

        // 递归地对左右两半进行排序
		merge_sort(arr, left, mid);
        merge_sort(arr, mid + 1, right);

        // 合并已排序的两个子数组
        merge(arr, left, mid, right);
    }

}

int* sortArray(int* nums, int numsSize, int* returnSize) {
    merge_sort(nums,0,numsSize - 1);
    *returnSize = numsSize;
    return nums;

}

6.2力扣148 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

struct ListNode* sortList(struct ListNode* head) {  
    //排序板子  
    struct ListNode *radix[10], *ptr[10];  
    for(int i = 0; i < 10; ++i){  
        radix[i] = (struct ListNode*)malloc(sizeof(struct ListNode));  
        radix[i]->next = NULL;  
        ptr[i] = radix[i];  
    }  
    //开始基数排序,cur为当前待排序节点  
    struct ListNode* cur = head, *temp;  
    for(int bit = 1; bit <= 100000; bit *= 10){  
        //插到板子上  
        while(cur){  
            temp = cur->next;  
            int px = ((cur->val+100000)/ bit) % 10;  
            ptr[px]->next = cur;  
            cur->next = NULL;  
            ptr[px] = ptr[px]->next;  
            cur = temp;  
        }  
        //收集  
        struct ListNode *prev = ptr[0];  
        for(int i = 1; i < 10; ++i){  
            if(radix[i]->next){  
                prev->next = radix[i]->next;  
                prev = ptr[i];  
            }  
        }  
        //新的开始节点  
        cur = radix[0]->next;  
        //恢复板子为空  
        for(int i = 0; i < 10; ++i){  
            radix[i]->next = NULL;  
            ptr[i] = radix[i];  
        }  
    }  
    return cur;  
}
posted @ 2024-10-15 19:28  写代码的大学生  阅读(17)  评论(0编辑  收藏  举报  来源