算法学习笔记(5.0)-基于比较的高效排序算法-归并排序

2024-05-16 00:04

本文主要是介绍算法学习笔记(5.0)-基于比较的高效排序算法-归并排序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

##时间复杂度O(nlogn)

目录

##时间复杂度O(nlogn)

##递归实现归并排序

##原理

##图例

##代码实现

##非递归实现归并排序

##释

#代码实现


##递归实现归并排序

##原理

是一种基于分治策略的基础排序算法。

1.划分阶段:通过不断递归地将数组从中点处分开,将长数组的排序问题转化成段数组的排序问题。

2.合并阶段:当子数组的长度为1时终止划分,开始合并,持续不断地将左右两个较短的有序数组合并为一个较长的有序数组,直到结束。

##图例

##代码实现

1.向下递归,对半分割

2.递归返回条件:递归到最小,1个就是有序【控制的是范围,归并的是区间】

3.递归到最深处,最小时,就递归回去,开始分割按对半分割出的范围,将已有子序列合并,在tmp里面进行合并

4.再将tmp里形成的有序序列,拷贝回原数组【因下一次递归回去上一层的归并过程中,会将数据在tmp中进行归并,会将tmp中的数据覆盖,因此要及时,拷】

//python代码示例
def Merge(a, start, mid, end) :tmp = [] #创建一个临时列表,用来存储合并的值#两段起点的开始l = startr = mid + 1#当两段都没到达末尾,则继续循环,将其添加到tmpwhile l <= mid and r <= end :if a[l] <= a[r] :tmp.append(a[l])l += 1else :tmp.append(a[r])r += 1#此时肯定只剩下,单一的一个值,即将剩余元素塞入到tmp中tmp.extend(a[l:mid+1])tmp.extend(a[r:end+1])#将合并完成的元素,赋值到a原数组中for i in range(start,end+1):a[i] = tmp[i - start]print(start,end,tmp)def MergeSort(a,start,end) :#利用递归来实现归并排序,将大的问题分解成小的子问题if start == end : #当递归到数组只有一个元素时,直接返回returnmid = (start + end) // 2 #计算出mid,找到递归点MergeSort(a,start,mid) #左递归MergeSort(a,mid+1,end) #右递归Merge(a,start,mid,end)#左递归,左合并,右递归,右合并,类似于树的后序遍历a = [7,3,2,6]
MergeSort(a,0,3)

//c++代码实现示例
#include<iostream>
#include<vector>
using namespace std;
//c++代码实现示例
void merge(vector<int> &a, int left, int mid, int right)
{int i = left ;int j = mid + 1 ;int k = left ;vector<int> tmp(right - mid + 1) ;while ( i <= mid && j <= right ){if ( a[i] <= a[j] ){tmp[k++] = a[i++] ;}else{tmp[k++] = a[j++] ;}}while ( i <= mid ){tmp[k++] = a[i++] ;}while ( j <= right ){tmp[k++] = a[j++] ;}for (int m = left ; m <= right ; m++){a[m] = tmp[m - left] ;}cout << left << right << " " ;for (int n = 0 ; n < tmp.size() ; ++n){cout << tmp[n] << " " ;}
}void mergeSort(vector<int> &a, int left, int right)
{if (left >= right){return ;}int mid = (left + right) / 2 ;mergeSort(a,left,mid) ;mergeSort(a,mid+1,right) ;merge(a,left,mid,right) ;
}int main()
{
//	vector<int> arr = {1,2,3,4,5};
//	vector<int> arr;arr.push_back(7);arr.push_back(3);arr.push_back(2);arr.push_back(6);
//    arr.push_back(5);mergeSort(arr,0,3) ;return 0 ;}
void _MergerSort(DataType* a ,DataType* tmp, int begin,int end)
{//递归返回条件,不正常的范围,或只剩下一个数 if (begin >= end){return ;}int mid = (begin + end) / 2 ;//递归过程 _MergeSort(a,tmp,begin,mid) ;_MergeSort(a,tmp,mid+1,end) ;//合并过程 //归并到tmp数组中,再拷贝回去 int begin1 = begin ;int end1 = mid ;int begin2 = mid + 1 ;int end2 = end ;//指向tmp,=begin是 根据要进行比较插入的数组的位置 找到其对应在tmp中所对应的位置,则不会覆盖前面已经排好序的数据int index = begin ;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin++] ;}else{tmp[index++] = a[begin2++] ;}}//剩下还没有插入进去的 while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++] ;}//拷贝回去原数组a中memcpy(a+begin,tmp+begin,sizeof(DataType)*(end-begin+1)) ; 
}void MergeSort(DataType* a, int n)
{DataType* tmp = (DataType*)malloc(sizeof(DataType)*n) ;if (tmp == NULL){perror("malloc fail") ;return ;}_MergeSort(a,tmp,0,n-1) ;free(tmp) ;
}

对于链表,归并排序相较于其他排序算法具有显著优势,可以将链表排序任务的空间复杂度优化至 𝑂(1) 。

  • 划分阶段:可以使用“迭代”替代“递归”来实现链表划分工作,从而省去递归使用的栈帧空间。
  • 合并阶段:在链表中,节点增删操作仅需改变引用(指针)即可实现,因此合并阶段(将两个短有序链表合并为一个长有序链表)无须创建额外链表。

##非递归实现归并排序

##释

归并排序时二分的思想=>logN层=>递归不会太深、且现在编译器优化后,递归、非递归的性能差距没有特别大了=>所以可以不用考虑非递归。

递归的缺点:递归消耗栈帧,递归的层数太深,容易爆栈

【栈的空间比较小,在x86(32位)环境下,只有8M。(对比同一环境下的堆,则有2G+)。因为平时函数调用开不了多少个栈帧。理论上递归深度>1w 可能就会爆 ,但实际上5k左右就爆掉了】,这时就需要改非递归了。

非递归改写方法:

1.改变循环

2.利用数据结构栈(本质是通过malloc在堆上开辟的存储空间,内存空间需要足够大)

3.递归逆着求-实际上也差不多也是递归思想(如斐波那契数列逆着来求也是可行的)

#代码实现

  1. 开辟新的数组(临时存放)用于归并排序过程
  2. int gap=1;gap*=2【gap控制归并的范围:11归并,22归并,44归并】
  3. for (int i = 0; i < n; i += 2 * gap) { 【i 控制进行比较轮到的组号,控制进行归并的组号】
  4. 归并完一轮,将归并好的有序数组拷贝回原数组memcpy 。
  5. 进入新的一轮归并,直至gap>n则归并完成

     ☆注意的两个情况

     6. if (begin2 >= n) { break; } 第二组不存在,这一组不用归并了 

     7. if (end2 > n) { end2 = n - 1; } 第二组右边界越界问题,修正一下

void MergerSortNonR(DataType* a, int n)
{DataType* tmp = (DataType*)malloc(sizeof(DataType) * n);   //开辟新的数组(临时存放)用于归并排序过程if (tmp == NULL){perror("malloc fail") ;return ;}int gap = 1 ;while (gap < n) {for (int i = 0 ; i < n ; i += 2 * gap){
//            // 1 , 1 归并 
//            // 2 , 2 归并
//            //4 , 4 归并 
//            //[begin1,end1][begin2,end2]归并 int begin1 = i ;int end1 = i + gap - 1 ;int begin2 = i + gap ;int end2 = i + 2 * gap - 1 ;//            //如果第二组不存在,这一组不用归并了if (begin2 >= n) {break;}//第二组右边界越界问题,修正一下if (end2 > n) {end2 = n - 1;}
//            
//            //当beigin2 超过 n 的时候,直接break掉就OK了
//            //但end2 超过 n 的时候,需要修改边界问题 n - 1 
//            int index = i ;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++] ;}else{tmp[index++] = a[begin2++] ;}}while (begin1 <= end1){tmp[index++] = a[begin1++] ;}while (begin2 <= end2){tmp[index++] = a[begin2++];}
//            //为什么这里不能是,2 * gap呢,不一定是2*gap的数都拷贝过去,memcpy(a+i,tmp+i,sizeof(DataType)*(end2 - beigin1 + 1)) 错误 
//            // memcpy(a+i,tmp+i,sizeof(DataType)*(end2 - begin1 + 1)) ; 因为begin1++会发生改变的,因此不可以,错误 memcpy(a+i,tmp+i,sizeof(DataType)*(end2 - i + 1)) ;
//            
//        }printf("\n");
//        for (int k = 0 ; tmp.size() ; k ++)
//        {
//            printf("%d",tmp[k]) ;
//        }gap = gap * 2 ;}free(tmp);
}
}
def MergeSort(a, n) :tmp = [0] * (n+1)gap = 1while (gap < n) :z = gap * 2for i in range(0,n,z) :begin1 = iend1 = i + gap - 1begin2 = i + gapend2 = i + 2 * gap - 1if begin2 > n :breakif end2 > n :end2 = n - 1index = iwhile begin1 <= end1 and begin2 <= end2 :if a[begin1] < a[begin2] :tmp[index] = a[begin1]index += 1begin1 += 1else :tmp[index] = a[begin2]index += 1begin2 += 1while begin1 <= end1 :tmp[index] = a[begin1]index += 1begin1 += 1while begin2 <= end2 :tmp[index] = a[begin2]index += 1begin2 += 1for j in range(i,end2 + 1) :a[j] = tmp[j - i]print()# print(tmp)gap = gap * 2

这篇关于算法学习笔记(5.0)-基于比较的高效排序算法-归并排序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/993295

相关文章

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

Tolua使用笔记(上)

目录   1.准备工作 2.运行例子 01.HelloWorld:在C#中,创建和销毁Lua虚拟机 和 简单调用。 02.ScriptsFromFile:在C#中,对一个lua文件的执行调用 03.CallLuaFunction:在C#中,对lua函数的操作 04.AccessingLuaVariables:在C#中,对lua变量的操作 05.LuaCoroutine:在Lua中,

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备

《offer来了》第二章学习笔记

1.集合 Java四种集合:List、Queue、Set和Map 1.1.List:可重复 有序的Collection ArrayList: 基于数组实现,增删慢,查询快,线程不安全 Vector: 基于数组实现,增删慢,查询快,线程安全 LinkedList: 基于双向链实现,增删快,查询慢,线程不安全 1.2.Queue:队列 ArrayBlockingQueue:

硬件基础知识——自学习梳理

计算机存储分为闪存和永久性存储。 硬盘(永久存储)主要分为机械磁盘和固态硬盘。 机械磁盘主要靠磁颗粒的正负极方向来存储0或1,且机械磁盘没有使用寿命。 固态硬盘就有使用寿命了,大概支持30w次的读写操作。 闪存使用的是电容进行存储,断电数据就没了。 器件之间传输bit数据在总线上是一个一个传输的,因为通过电压传输(电流不稳定),但是电压属于电势能,所以可以叠加互相干扰,这也就是硬盘,U盘

代码随想录算法训练营:12/60

非科班学习算法day12 | LeetCode150:逆波兰表达式 ,Leetcode239: 滑动窗口最大值  目录 介绍 一、基础概念补充: 1.c++字符串转为数字 1. std::stoi, std::stol, std::stoll, std::stoul, std::stoull(最常用) 2. std::stringstream 3. std::atoi, std