认识O(N*logN)的排序(总结)

2023-12-01 18:10
文章标签 总结 认识 排序 logn

本文主要是介绍认识O(N*logN)的排序(总结),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在总结之前看看下面这张表:

从表中可以看到归并排序、快速排序、堆排序的平均时间复杂度是 O(nlogn) 。我要总结的便是这三种排序算法,它们都适合于数据量比较大的排序运算中。

一. 归并排序:

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数开始,谁小就先取谁。然后再进行比较,如果有数列比较完了,那直接将另一个数列的数据依次取出即可。

 

可以看出合并有序数列的效率是比较高的,可以达到O(n)。

解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?

可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归地分解数列,再合并数列就完成了归并排序。

//将有二个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{int i = first, j = mid + 1;int m = mid,   n = last;int k = 0;while (i <= m && j <= n){if (a[i] <= a[j])temp[k++] = a[i++];elsetemp[k++] = a[j++];}while (i <= m)temp[k++] = a[i++];while (j <= n)temp[k++] = a[j++];for (i = 0; i < k; i++)a[first + i] = temp[i];//first+i不能丢掉first
}
void mergesort(int a[], int first, int last, int temp[])
{if (first < last){int mid = (first + last) / 2;mergesort(a, first, mid, temp);    //左边有序mergesort(a, mid + 1, last, temp); //右边有序mergearray(a, first, mid, last, temp); //再将二个有序数列合并}
}bool MergeSort(int a[], int n)
{int *p = new int[n];if (p == NULL)return false;mergesort(a, 0, n - 1, p);delete[] p;return true;
}
//此处引用的是https://blog.csdn.net/morewindows/article/details/6678165#的代码

 

归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。

二. 快速排序:

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

该方法的基本思想是:

1.先从数列中取出一个数作为基准数。

2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

3.再对左右区间重复第二步,直到各区间只有一个数。

对快速排序可以作进一步的说明:挖坑填数 + 分治法

对挖坑填数进行总结

1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。

2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。
 

//挖坑填数
int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置
{int i = l, j = r;int x = s[l]; //s[l]即s[i]就是第一个坑while (i < j){// 从右向左找小于x的数来填s[i]while(i < j && s[j] >= x) j--;  if(i < j) {s[i] = s[j]; //将s[j]填到s[i]中,s[j]就形成了一个新的坑i++;}// 从左向右找大于或等于x的数来填s[j]while(i < j && s[i] < x)i++;  if(i < j) {s[j] = s[i]; //将s[i]填到s[j]中,s[i]就形成了一个新的坑j--;}}//退出时,i等于j。将x填到这个坑中。s[i] = x;return i;
}//分治
void quick_sort1(int s[], int l, int r)
{if (l < r){int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]quick_sort1(s, l, i - 1); // 递归调用 quick_sort1(s, i + 1, r);}
}
//代码引用于https://blog.csdn.net/morewindows/article/details/6684558

三. 堆排序:

堆是一棵顺序存储的完全二叉树
堆排序的时间复杂度: O(nlogn),属于不稳定排序。

大根堆小根堆
每个节点的值大于等于孩子节点得堆每个节点得值小于等于孩子节点得值

堆排序就是利用堆得性质堆数组进行排序,待排序元素存放在一个数组Arr[0 ……n] 中,将Arr用一颗完全二叉树来表示,数组第一个元素就是完全二叉树的根,后面依次按层从左至右为,左孩子,右孩子,任意节点Arr[ i ] 的左孩子是Arr[ 2i+1 ],右孩子是 Arr[ 2i+2 ],对这个二叉树进行调整。

通过大佬的图像来介绍一下:

1.构建初始堆:


2.完整的堆排序:

//通过传指针交换两个元素的位置
void Swap(int *num1, int *num2)
{int tmp = *num1;*num1 = *num2;*num2 = tmp;
}//给定父节点的索引,得到左子节点的索引,跟的索引为0
#define LeftChild(i) (2*(i)+1)//元素向下调整方法
void PercDown(int A[], int i, int N)
{//子节点的索引号int child;//存储当前父节点元素的临时变量//(注:每一个节点都可以看作是其子树的根节点)int tmp;for (tmp = A[i]; LeftChild(i)<N; i = child){child = LeftChild(i); //左子节点索引//挑选出左、右子节点中较大者if (child != N - 1 && A[child + 1] > A[child]){child++;}//比较当前父节点与较大子节点if (A[i]<A[child]){//交换当前父节点处的元素值与较大子节点的元素值//此处也可以调用:Swap(A[i], A[child])tmp = A[i];A[i] = A[child];A[child] = tmp;}else{break;}}
}
//堆排序
void HeapSort(int A[], int N)
{int i;//步骤一:根据A数组元素,创建大根堆//从第 n/2 个记录开始进行建堆for (i = N / 2; i >= 0; i--){PercDown(A, i, N);}//步骤二:调整大根堆for (i = N - 1; i > 0; i--){//首尾交换Swap(&A[0], &A[i]);//将最后一个叶子节点与根节点进行交换,//再根据堆性质进行调整PercDown(A, 0, i);}
}

 

这篇关于认识O(N*logN)的排序(总结)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:https://blog.csdn.net/zhiyeegao/article/details/86411172
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/442159

相关文章

MySQL基本查询示例总结

《MySQL基本查询示例总结》:本文主要介绍MySQL基本查询示例总结,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Create插入替换Retrieve(读取)select(确定列)where条件(确定行)null查询order by语句li

Java List排序实例代码详解

《JavaList排序实例代码详解》:本文主要介绍JavaList排序的相关资料,Java排序方法包括自然排序、自定义排序、Lambda简化及多条件排序,实现灵活且代码简洁,文中通过代码介绍的... 目录一、自然排序二、自定义排序规则三、使用 Lambda 表达式简化 Comparator四、多条件排序五、

JAVA数组中五种常见排序方法整理汇总

《JAVA数组中五种常见排序方法整理汇总》本文给大家分享五种常用的Java数组排序方法整理,每种方法结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录前言:法一:Arrays.sort()法二:冒泡排序法三:选择排序法四:反转排序法五:直接插入排序前言:几种常用的Java数组排序

Linux区分SSD和机械硬盘的方法总结

《Linux区分SSD和机械硬盘的方法总结》在Linux系统管理中,了解存储设备的类型和特性是至关重要的,不同的存储介质(如固态硬盘SSD和机械硬盘HDD)在性能、可靠性和适用场景上有着显著差异,本文... 目录一、lsblk 命令简介基本用法二、识别磁盘类型的关键参数:ROTA查询 ROTA 参数ROTA

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

Python实现图片分割的多种方法总结

《Python实现图片分割的多种方法总结》图片分割是图像处理中的一个重要任务,它的目标是将图像划分为多个区域或者对象,本文为大家整理了一些常用的分割方法,大家可以根据需求自行选择... 目录1. 基于传统图像处理的分割方法(1) 使用固定阈值分割图片(2) 自适应阈值分割(3) 使用图像边缘检测分割(4)

Windows Docker端口占用错误及解决方案总结

《WindowsDocker端口占用错误及解决方案总结》在Windows环境下使用Docker容器时,端口占用错误是开发和运维中常见且棘手的问题,本文将深入剖析该问题的成因,介绍如何通过查看端口分配... 目录引言Windows docker 端口占用错误及解决方案汇总端口冲突形成原因解析诊断当前端口情况解

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

java常见报错及解决方案总结

《java常见报错及解决方案总结》:本文主要介绍Java编程中常见错误类型及示例,包括语法错误、空指针异常、数组下标越界、类型转换异常、文件未找到异常、除以零异常、非法线程操作异常、方法未定义异常... 目录1. 语法错误 (Syntax Errors)示例 1:解决方案:2. 空指针异常 (NullPoi

C++快速排序超详细讲解

《C++快速排序超详细讲解》快速排序是一种高效的排序算法,通过分治法将数组划分为两部分,递归排序,直到整个数组有序,通过代码解析和示例,详细解释了快速排序的工作原理和实现过程,需要的朋友可以参考下... 目录一、快速排序原理二、快速排序标准代码三、代码解析四、使用while循环的快速排序1.代码代码1.由快