数据结构(C语言)之对归并排序的介绍与理解

简介: 归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。

一·归并排序介绍:

首先,归并排序可以理解为用分治策略的一种排序算法,这里可以用递归的思想去理解,对一个数组进行不断分割,每次分为两个子数组,直到最后剩下的是一个数据也就是不可再分割,那么就开始对末两个子数组进行归并,然后归回去,在原数组得到有序的数组。(也就是说再每次归并的两个数组一定要是有序的)。

二·归并排序递归版本:
2.1·递归思路:
实现代码的同时,首先要先分割原数组为两个子数组,这里就用到了分割方法,分割的区间为[left,mid][mid+1,right]这样分割,可以避免出现区间循环的问题([偶数,偶数+1])。

(注:其它细节见代码处注释)

2.2·递归代码实现:
//归并的时候要确保每两个区间内数据都是有序的
//这里可以是递归,但是不让它每次都开辟空间,故这里用了一个子函数来完成递归操作

//这里可以假设完成的是最后一次归并操作,通过调用两次子函数假设已经把最后两个区间排好序了,最后
//再对它们归并即可。
void _mergesort(int a, int tmp, int begin, int end) {
if (begin >= end) {
return;
}//递归终止条件:多为不断分割区间到只剩下一个数据结束直接归并
int mid = (begin + end) / 2;
_mergesort(a, tmp, begin, mid);//这里由于如果选mid-1和mid的话,当区间为【偶数,偶数+1】就会分割死循环
_mergesort(a, tmp, mid + 1, end);
int begin1 = begin;
int begin2 = mid + 1;
int end1 = mid;
int end2 = end;
//由于每次归并都是从原数组归到tmp,而最后又要把tmp对应的位置数据copy回原数组,故当我们归并排序到tmp数组
//应对应原数组下标放入
int i = begin;
while (begin1 <= end1 && begin2 <= end2) {
if (a[begin1] < a[begin2]) {
tmp[i++] = a[begin1++];
}
else {
tmp[i++] = a[begin2++];
}
}
if (begin1 > end1) {
while (begin2 <= end2) {
tmp[i++] = a[begin2++];
}
}
else {
while (begin1 <= end1) {
tmp[i++] = a[begin1++];
}
}
memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
//这里开辟的tmp数组,可防止原数组被覆盖,每次归并完为有序的数组copy回原数组原位置

}
//这里递归每次分割,最后成一个数据自然有序,接着每次归并后归回去。
void mergesort(int a, int n) {
int
tmp = (int)malloc(sizeof(int) n);
if (tmp == NULL) {
perror(malloc);
return;
}
_mergesort(a, tmp, 0, n - 1);
free(tmp);
tmp = NULL;
}

三·归并排序非递归版本:
3.1·非递归思路:

上面这个非递归的归并排序,是先是gap=1,归并当出现gap可以=2的时候再整体归并,这时整个数组并未被gap=1遍历完分好组,也是可以的,下面介绍一种直接被gap遍历完分好组再进行归并的方法:

非递归的话,就是把数组先分为一个个的每个子区间只有一个数据,然后让它们每两个成一对进行归并操作,等这一轮进行完后,从数组首开始给它们两个数据为一个区间,每两个区间就会满足区间内数据均有序,从而再次进行归并操作,依次类推,最后会生成两组有序归并完后得到原数组即为有序的原数组。

这里用gap来记录每组数据个数,通过循环来改变gap,gap定值时候用for循环来确定每次分两组情况。

而这里需要考虑的重点就是越界问题,当分区间的时候无论奇数个还是偶数个数据都会存在越界现象,而如果为奇数个的话,当gap为1的时候,最后会存在越界,而偶数的时候,可能往后面才出现越界,而画图可知道,由于每次第一组的区间首位是i不会越界故越界的是第二组要么是都越界,要么第二个区间的第二个数字越界。(其他细节见源代码注释)

画图解释:

3.2·非递归代码实现:
//这里非递归,可以从每组一个数据开始归并,然后有序,然后每两个就有序了,
// 最后会变为最后的两组要么归并要么舍弃一组
// 接着每组两个归并成四个,依次每组gap个数据调整到最后剩下两组,即再次归并得到最后有序的数组
//画图可知道每次如果出现越界只能是最后两组,而这两组的第一组的end1为i不可能越界
//故可以分数据为偶数个还是奇数个,如果偶数个那么gap为1时不越界但是之后会,为奇数时gap为1最后一组
//越界,然后出现越界肯定是第二组,然后begin2如果越,就break,而end2越界就变为n-1接着归并
//可发现gap跳的时候每次都是跳的2的多少次方,即当剩下的组区间有越界但里面有数据一定是有序的,变为n-1归并
void mergesortNoR(int a, int n) {
int
tmp = (int)malloc(sizeof(int) n);
if (tmp == NULL) {
perror(malloc);
return;
}

for (int gap = 1; gap < n; gap = 2 * gap) {
    for (int i = 0; i < n; i += 2 * gap) {
        //两边闭区间
        int begin1 = i;
        int end1 = i + gap - 1;
        int begin2 = i + gap;
        int end2 = i + 2 * gap - 1;
        if (begin2 >= n) {
            break;
            //由于最后两组如果出现越界的话end1始终不会越界,一旦越界begin2一定越界
            //那么就防止后面的归并出错,就停止归并
        }
        if (end2 >= n) {
            end2 = n - 1;
            //当最后一次gap+的循环,肯定第二组begin2不越界,越界可能是end2,而前几次的归并
            //已经把最后一次第二组的数据排好序了那么更改end2然后再次归并就可以了
        }
        int i = begin1;
        int start = begin1;
        int last = end2;
        while (begin1 <= end1 && begin2 <= end2) {
            if (a[begin1] < a[begin2]) {
                tmp[i++] = a[begin1++];
            }
            else {
                tmp[i++] = a[begin2++];
            }
        }
        if (begin1 > end1) {
            while (begin2 <= end2) {
                tmp[i++] = a[begin2++];
            }
        }
        else {
            while (begin1 <= end1) {
                tmp[i++] = a[begin1++];
            }
        }
        memcpy(a + start, tmp + start, sizeof(int) * (last - start + 1));


    }
}

}

四·归并排序性能分析:
复杂度:首先由于归并排序每次是折半归,故它的时间复杂度类似于二叉树为o(n*logn),而由于多开了n个空间的数组作为归并暂存数组用来copy。空间复杂度为:o(n)。

稳定性:首先稳定性就是当用排序算法给数组排序的时候,它里面原本的相同的元素相对位置不变化就称为其的稳定性。对于归并排序而言,每次两个数组归并成一个数组,只要我们改动一下当begin1与begin2对应数字相等,就放入begin1对应的数据,这样顺序就不变了,也可以说归并排序是稳定的。

就是把<改成=。

应用:可用于正常的排序,或者大文件的排序,由于归并排序是在内存中进行,有的时候文件太大无法正常进行,可以把它分为一个个小文件到内存归为有序,最终整合使得大文件也有序。

相关文章
|
2月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
74 1
|
2月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
74 4
|
2月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
60 4
|
2月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
58 4
|
2月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
63 4
|
2月前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
54 4
|
2月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
42 4
|
2月前
|
存储 算法 C语言
【趣学C语言和数据结构100例】51-55
本文介绍了五个关于链表操作的C语言实现案例,包括删除单链表中的重复元素、从两个有序链表中查找公共元素、判断一个链表是否为另一链表的连续子序列、判断循环双链表是否对称及合并两个循环单链表。每个案例都详细解析了算法思路与实现方法,涵盖了链表操作的多种场景,旨在帮助读者深入理解链表数据结构的应用,提升算法设计与编程能力。
52 4
|
21天前
|
存储 人工智能 算法
【C++数据结构——内排序】二路归并排序(头歌实践教学平台习题)【合集】
本关任务是实现二路归并算法,即将两个有序数组合并为一个有序数组。主要内容包括: - **任务描述**:实现二路归并算法。 - **相关知识**: - 二路归并算法的基本概念。 - 算法步骤:通过比较两个有序数组的元素,依次将较小的元素放入新数组中。 - 代码示例(以 C++ 为例)。 - 时间复杂度为 O(m+n),空间复杂度为 O(m+n)。 - **测试说明**:平台会对你编写的代码进行测试,提供输入和输出示例。 - **通关代码**:提供了完整的 C++ 实现代码。 - **测试结果**:展示代码运行后的排序结果。 开始你的任务吧,祝你成功!
33 10
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
92 5