手撕排序算法:归并排序

news/2024/8/26 14:15:38 标签: 排序算法, 算法, 数据结构

文章目录

  • 1.算法思想
  • 2.算法分析
    • 2.1时间复杂度
    • 2.2空间复杂度
  • 3.算法优缺点
  • 4.优化方案
  • 5.代码演示
  • 6.实战
    • 6.1力扣912 排序数组
    • 6.2力扣148 排序链表

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

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;  
}

http://www.niftyadmin.cn/n/5558160.html

相关文章

Linux网络编程-socket套接字使用详解

1.概念 在Linux中&#xff0c;套接字&#xff08;socket&#xff09;是一种通信机制&#xff0c;用于实现不同进程之间或同一主机上的不同线程之间的数据交换。它是网络编程的基础&#xff0c;允许应用程序通过网络进行通信&#xff0c;也可以在同一台机器上的不同进程间进行通…

热门软件缺陷管理工具2024:专业评测与建议

国内外主流的10款软件缺陷管理工具软件对比&#xff1a;PingCode、Worktile、禅道、Tapd、Teambition、Tower、JIRA、Bugzilla、MantisBT、Trac。 在软件开发过程中&#xff0c;管理缺陷和漏洞常常成为一项挑战&#xff0c;尤其是在项目规模庞大时。选择一个高效的软件缺陷管理…

【STM32 IDE】使用STM32CubeIDE创建一个工程

关于IDE的下载安装和环境配置这里暂且不介绍&#xff0c;我们直接使用STM32F407ZGT6创建工程。 这里需要注意两点&#xff1a; 创建工程时&#xff0c;默认使用最新版本的固件包&#xff08;HAL库&#xff09;&#xff0c;好像还不让更改。如果本地电脑位置没有该版本的包&…

掌握Conda环境管理:使用conda env remove命令的精要

掌握Conda环境管理&#xff1a;使用conda env remove命令的精要 在Python项目开发过程中&#xff0c;环境管理是确保不同项目依赖隔离的关键步骤。Conda作为Anaconda发行版中的包管理器&#xff0c;提供了强大的环境管理功能&#xff0c;允许用户轻松创建、管理、配置和删除环…

SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表

Sharding-JDBC系列 1、Sharding-JDBC分库分表的基本使用 2、Sharding-JDBC分库分表之SpringBoot分片策略 3、Sharding-JDBC分库分表之SpringBoot主从配置 4、SpringBoot集成Sharding-JDBC-5.3.0分库分表 5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表 前言 …

数据采集监控平台:挖掘数据价值 高效高速生产!

在当今数字化的时代&#xff0c;数据已成为企业非常宝贵的资产之一。然而&#xff0c;要充分发挥数据的潜力&#xff0c;离不开一个强大的数据采集监控平台&#xff0c;尤其是生产制造行业。它不仅是数据的收集者&#xff0c;更是洞察生产的智慧之眼&#xff0c;高效高速处理产…

CP AUTOSAR标准之CellularV2XDriver(AUTOSAR_SWS_CellularV2XDriver)(更新中……)

1 简介和功能概述 该规范描述了AUTOSAR基础软件模块Cellular V2X驱动程序的功能、API和配置。   在AUTOSAR分层软件架构中,如果Cellular V2X控制器为片上类型(内部),则Cellular V2X驱动程序属于微控制器抽象层;如果Cellular V2X控制器为片外类型(外部),则Cellular V2X驱动…

Laravel速率限制:保护API的盾牌

Laravel速率限制&#xff1a;保护API的盾牌 在构建API时&#xff0c;速率限制&#xff08;Rate Limiting&#xff09;是一个关键的安全特性&#xff0c;它能够防止API被滥用或遭受恶意攻击。Laravel框架提供了一种简单而强大的机制来实现API速率限制&#xff0c;确保你的应用程…