首页 > 编程语言 >算法基础1.1.2归并排序

算法基础1.1.2归并排序

时间:2023-02-26 10:35:15浏览次数:56  
标签:归并 1.1 int 复杂度 mid 数组 排序

前言

归并排序的思路其实和快速排序的很像,都有递归的过程。

但是区别是:快速排序是先处理好这一层,然后再进行传递,在传递到底后,其实排序就已经完成了。而归并排序是先直接一直传递到最底层,相当于先把区间细分好,然后在归的过程中处理数据。

所以我们说分治算法基本都是三步:

  1. 分成子问题
  2. 递归处理子问题
  3. 子问题合并

而实际上快速排序并没有第三步

递归中独特的就是他有第三步,而第三步的处理也不是很难。可能是因为比较认真的分析过快速排序了,感觉归并排序里面没有什么特别大的疑问

正文

动图:

7789414-2737ec30a70ff74f

这里我要专门解释一下了,上次的快速排序我直接放图但是自己没看,结果后来看发现根本理解不了它的逻辑

这个图我倒是理解了:这个图是已经分治完毕(传递完毕),第一次进行最小的一层,将两个只有一个数的区间进行合并,然后将两个分别含有两个数的区间合并,也就是1-2-4-8-16这样,最后就回归完毕了。

合并的思路也很简单,就是给两个数组各一个指针指向他们的开头,由于他们都是已经从小到大排列好的,所以我们就可以一直对两个数组中当前指针指向的数进行比较(他们分别代表剩下未处理的数中的最小值)。

现在我们来看看代码

#include<bits/stdc++.h>

using namespace std;

// 防止数组栈溢出
const int N = 1e6 + 10;
int a[N], tmp[N];

void merge_sort(int a[],int l,int r){
    if(l>=r)
        return;
    int mid = (l + r) >> 1;
    merge_sort(a, l, mid), merge_sort(a, mid + 1, r);
    int k = 0, i = l, j = mid + 1;
    // i和j是两个指针,这里虽然是一个数组,但是我们要将他模拟成两个数组
    while(i<=mid&&j<=r)
        if(a[i]<=a[j])
            //k++,先使用k以后再让k+1
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    // 一般会有一个数组里有多出来的
    // 因为已经排好序了,所以直接将他们全部放到数组后面
    while(i<=mid)
        tmp[k++] = a[i++];
    while(j<=r)
        tmp[k++] = a[j++];
    //将临时数组赋值给正式数组,改变正式数组中的内容,这样才能回归
    for (i = l, j = 0; i <= r;i++,j++) a[i] = tmp[j];
}

int main(){
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n;i++){
        scanf("%d", &a[i]);
    }
    merge_sort(a, 0, n - 1);
    for (int i = 0; i < n;i++){
        printf("%d ", a[i]);
    }
    return 0;
}

分析

声明一下,这里面很多问题点都是和快速排序共通的,我就不进行说明了。所以建议先看完快速排序的文章点这里进入快速排序文章

分析1

归并排序的时间复杂度严格为nlogn

这里有两个值得一提

  1. 这里的log底数为2
  2. 快速排序的理想时间复杂度也是这个,但是那是它的平均时间复杂度(每次都能将数组平均分的话就是最理想的状态,而它每轮取的位置的期望值正好就是n/2,也就是取到中间),最差情况时间复杂度是n^2,我的快速排序文章里有分析

其实归并排序的时间复杂度分析和快速排序理想状态一样:第一层将数组分为两份,第二层分为四份....最后第logn次可以将数组分为n份,其中n是数组长度。也就是说现在每一份里面的数都只有一个。计算方法也很简单,我们假设需要n次,那么就是n=2^x,解方程即可。

我们进行了logn层,每一层都会把所有数据遍历一次,所以最后我们的时间复杂度就是nlogn

分析2

为什么不使用mid-1作为分界线呢

merge_sort(q, l, mid - 1 ), merge_sort(q, mid, r)

在快速排序中我们讲了位运算中的>>相当于除法,而这个除法是向下取整的,所以有可能会为l(比如l为1,r为2,那么mid就是1),这样就会造成无限划分

如果想要解决这个问题,可以mid=l+r+1>>1,这样就会向上取整。至于为什么我这里没有加括号,是因为位运算的优先级是比加减法要低的,所以可以不加,只不过有些编译器会提醒你这样不好。

分析3

知识点:对于自增自减的利用

老师代码中的这种用法我是第一次见,真的耳目一新,以后我也会去尝试使用这种方法

tmp[k++] = a[i++];这里就是先让这个语句中的值分别是k,i,在这条语句执行完毕后再让这两个变量各自进行一次自增。这种方法相当于把三行代码变成了一行

分析4

知识点:for循环括号中进行一些变量的改动

它的效果与分析3相同,都是一种很好的方法

结语

image-20230226084927574

标签:归并,1.1,int,复杂度,mid,数组,排序
From: https://www.cnblogs.com/zaughtercode/p/17156208.html

相关文章

  • 基本排序算法的C语言实现
    为了复习DS临时起意,大概率会咕咕咕...目录插入排序快速排序归并排序堆排序插入排序voidinsertion_sort(intp[],intn){for(inti=2;i<=n;i++){......
  • QT多线程实现冒泡排序和快速排序方法一
    qt4的线程方式#include"mythread.h"#include<QElapsedTimer>#include<QDebug>Generate::Generate(QObject*parent):QThread(parent){}voidGenerate::recvNum(intnum......
  • qt多线程实现快速排序和冒泡排序方法二
    qt5多线程处理方式#include"mainwindow.h"#include"ui_mainwindow.h"#include"mythread.h"#include<QThread>MainWindow::MainWindow(QWidget*parent):QMainWindo......
  • 简单选择排序
    classSelectSort{publicvoidsort(int[]a){for(inti=0;i<a.length-1;i++){//一共进行n-1趟intmin=i;//记录最小元素位置......
  • 快速排序
    classQuickSort{intpartition(int[]a,intlow,inthigh){intpivot=a[low];//第一个元素作为枢轴while(low<high){//用low、high搜索......
  • 希尔排序
    //希尔排序classShellSort{publicvoidsort(int[]a){intd,i,j,temp;for(d=a.length/2;d>=1;d=d/2){for(i=d;i<......
  • 冒泡排序
    //冒泡排序classBubbleSort{publicvoidsort(int[]a){for(inti=0;i<a.length-1;i++){booleanflag=false;for......
  • 插入排序
    //插入排序classInsertSort{publicvoidsort(int[]a){inti,j,temp;for(i=1;i<a.length;i++){if(a[i]<a[i-1]){......
  • 【基础算法】简单排序-选择排序
    【基础算法】简单排序-选择排序将待排序数组分成有序部分和无序部分,无序部分初始长度为0,每次遍历有序部分,找到有序部分最小(最大)的数,和无序部分第一个数进行交换,使其变成有......
  • set的自定义排序
    看下面的代码就好了structcmp{ booloperator()(constpair<int,int>&a,constpair<int,int>&b)const{ intlena=a.second-a.first+1; intlenb=b.second-b.firs......