当前位置: 首页 > 编程日记 > 正文

排序算法 Java实现

选择排序

核心思想

选择最小元素,与第一个元素交换位置;剩下的元素中选择最小元素,与当前剩余元素的最前边的元素交换位置。

分析

选择排序的比较次数与序列的初始排序无关,比较次数都是N(N-1)/2

移动次数最多只有n-1次。

因此,时间复杂度为O(N^2),无论输入是否有序都是如此,输入的顺序只决定了交换的次数,但是比较的次数不变。

选择排序是不稳定的,比如5 6 5 3的情况。

代码

public class SelectionSort {public void selectionSort(int[] nums){if(nums==null)return;for(int i=0;i<nums.length;i++) {int index = i;for (int j = i; j < nums.length; j++) {if (nums[j] < nums[index]) {index = j;}}swap(nums, i, index);}}
}
复制代码

冒泡排序:

核心思想

从左到右不断交换相邻逆序的元素,这样一趟下来把最大的元素放到了最右侧。不断重复这个过程,知道一次循环中没有发生交换,说明已经有序,退出。

分析

  • 当原始序列有序,比较次数为 n-1 ,移动次数为0,因此最好情况下时间复杂度为 O(N)
  • 当逆序排序时,比较次数为 N(N-1)/2,移动次数为 3N(N-1)/2,因此最坏情况下时间复杂度为 O(N^2)
  • 平均时间复杂度为 O(N^2)。

元素两两交换时,相同元素前后顺序没有改变,因此具有稳定性。

代码

public class BubbleSort {public void bubbleSort(int[] nums){for(int i=nums.length-1;i>0;i--){boolean sorted=false;for(int j=0;j<i;j++){if(nums[j]>nums[j+1]){Sort.swap(nums,j,j+1);sorted=true;}}if(!sorted)break;}}
复制代码

插入排序

核心思想

每次将当前元素插入到左侧已经排好序的数组中,使得插入之后左侧数组依然有序。

分析

因为插入排序每次只能交换相邻元素,令逆序数量减少1,因此交换次数等于逆序数量。

因此,插入排序的复杂度取决于数组的初始顺序。

  • 数组已经有序,需要 N-1 次比较和0次交换,时间复杂度为 O(N)。
  • 数组完全逆序,需要 N(N-1)/2 次比较和交换 N(N-1)/2 次,时间复杂度为 O(N^2)
  • 平均情况下,时间复杂度为 O(N^2)

插入排序具有稳定性

代码

public class InsertionSort {public void insertionSort(int[] nums){for(int i=1;i<nums.length;i++){for(int j=i;j>0;j--){if(nums[j]<nums[j-1])swap(nums,j,j-1);elsebreak;//已经放到正确位置上了}}}
}
复制代码

希尔排序

对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少1。

核心思想

希尔排序为了解决插入排序的局限性,通过交换不相邻的元素,每次将逆序数量减少大于1。希尔排序使用插入排序对间隔为 H 的序列进行排序,不断减少 H 直到 H=1 ,最终使得整个数组是有序的。

时间复杂度

希尔排序的时间复杂度难以确定,并且 H 的选择也会改变其时间复杂度。

希尔排序的时间复杂度是低于 O(N^2) 的,高级排序算法只比希尔排序快两倍左右。

稳定性

希尔排序不具备稳定性。

代码

public class ShellSort {public void shellSort(int[] nums){int N=nums.length;int h=1;while(h<N/3){h=3*h+1;}while(h>=1){for(int i=h;i<N;i++){for(int j=i;j>0;j--){if(nums[j]<nums[j-1]){swap(nums,j,j-1);}else{break;//已经放到正确位置上了}}}}}
}
复制代码

归并排序

核心思想

将数组分为两部分,分别进行排序,然后进行归并。

归并方法

public void merge(int[] nums, int left, int mid, int right) {int p1 = left, p2 = mid + 1;int[] tmp = new int[right-left+1];int cur=0;//两个指针分别指向左右两个子数组,选择更小者放入辅助数组while(p1<=mid&&p2<=right){if(nums[p1]<nums[p2]){tmp[cur++]=nums[p1++];}else{tmp[cur++]=nums[p2++];}}//将还有剩余的数组放入到辅助数组while(p1<=mid){tmp[cur++]=nums[p1++];}while(p2<=right){tmp[cur++]=nums[p2++];}//拷贝for(int i=0;i<tmp.length;i++){nums[left+i]=tmp[i];}}
复制代码

代码实现

递归方法:自顶向下

通过递归调用,自顶向下将一个大数组分成两个小数组进行求解。

public void up2DownMergeSort(int[] nums, int left, int right) {if(left==right)return;int mid=left+(right-left)/2;mergeSort(nums,left,mid);mergeSort(nums,mid+1,right);merge(nums,left,mid,right);}
复制代码

非递归:自底向上

public void down2UpMergeSort(int[] nums) {int N = nums.length;for (int sz = 1; sz < N; sz += sz) {for (int lo = 0; lo < N - sz; lo += sz + sz) {merge(nums, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));}}}
复制代码

分析

把一个规模为N的问题分解成两个规模分别为 N/2 的子问题,合并的时间复杂度为 O(N)。T(N)=2T(N/2)+O(N)。

得到其时间复杂度为 O(NlogN),并且在最坏、最好和平均情况下时间复杂度相同。

归并排序需要 O(N) 的空间复杂度。

归并排序具有稳定性。

快速排序

核心思想

快速排序通过一个切分元素 pivot 将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将子数组分别进行排序,最终整个排序。

partition

取 a[l] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[l] 和 a[j] 交换位置。

private int partition(int[] nums, int left, int right) {int p1=left,p2=right;int pivot=nums[left];while(p1<p2){while(nums[p1++]<pivot&&p1<=right);while(nums[p2--]>pivot&&p2>=left);swap(nums,p1,p2);}swap(nums,left,p2);return p2;}
复制代码

代码实现

public void sort(T[] nums, int l, int h) {if (h <= l)return;int j = partition(nums, l, h);sort(nums, l, j - 1);sort(nums, j + 1, h);}
复制代码

分析

最好的情况下,每次都正好将数组对半分,递归调用次数最少,复杂度为 O(NlogN)。

最坏情况下,是有序数组,每次只切分了一个元素,时间复杂度为 O(N^2)。为了防止这种情况,在进行快速排序时需要先随机打乱数组。

不具有稳定性。

改进

  1. 切换到插入排序:递归的子数组规模小时,用插入排序。
  2. 三数取中:最好的情况下每次取中位数作为切分元素,计算中位数代价比较高,采用取三个元素,将中位数作为切分元素。

三路快排

对于有大量重复元素的数组,将数组分为小于、等于、大于三部分,对于有大量重复元素的随机数组可以在线性时间内完成排序。

public void threeWayQuickSort(int[] nums,int left,int right){if(right<=left)return;int lt=left,cur=left+1,gt=right;int pivot=nums[left];while(cur<=gt){if(nums[cur]<pivot){swap(nums,lt++,cur++);}else if(nums[cur]>pivot){swap(nums,cur,gt--);}else{cur++;}}threeWayQuickSort(nums,left,lt-1);threeWayQuickSort(nums,gt+1,right);}
复制代码

基于 partition 的快速查找

利用 partition() 可以在线性时间复杂度找到数组的第 K 个元素。

假设每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。

public int select(int[] nums, int k) {int l = 0, h = nums.length - 1;while (h > l) {int j = partition(nums, l, h);if (j == k) {return nums[k];} else if (j > k) {h = j - 1;} else {l = j + 1;}}return nums[k];
}
复制代码

堆排序

堆可以用数组来表示,这是因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。在这里,从下标为1的索引开始 的位置,是为了更清晰地描述节点的位置关系。

上浮和下沉

当一个节点比父节点大,不断交换这两个节点,直到将节点放到位置上,这种操作称为上浮。

private void shiftUp(int k) {while (k > 1 && heap[k / 2] < heap[k]) {swap(k / 2, k);k = k / 2;}}
复制代码

当一个节点比子节点小,不断向下进行比较和交换,当一个基点有两个子节点,与最大节点进行交换。这种操作称为下沉。

private void shiftDown(int k){while(2*k<=size){int j=2*k;if(j<size&&heap[j]<heap[j+1])j++;if(heap[k]<heap[j])break;swap(k,j);k=j;}}
复制代码

堆排序

把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列。

构建堆 建立堆最直接的方法是从左到右遍历数组进行上浮操作。一个更高效的方法是从右到左进行下沉操作。叶子节点不需要进行下沉操作,可以忽略,因此只需要遍历一半的元素即可。

交换堆顶和最坏一个元素,进行下沉操作,维持堆的性质。

public class HeapSort {public void sort(int[] nums){int N=nums.length-1;for(int k=N/2;k>=1;k--){shiftDown(nums,k,N);}while(N>1){swap(nums,1,N--);shiftDown(nums,1,N);}System.out.println(Arrays.toString(nums));}private void shiftDown(int[] heap,int k,int N){while(2*k<=N){int j=2*k;if(j<N&&heap[j]<heap[j+1])j++;if(heap[k]>=heap[j])break;swap(heap,k,j);k=j;}}private void swap(int[] nums,int i,int j){int t=nums[i];nums[i]=nums[j];nums[j]=t;}
}
复制代码

分析

建立堆的时间复杂度是O(N)。

一个堆的高度为 logN, 因此在堆中插入元素和删除最大元素的复杂度都是 logN。

在堆排序中,对N个节点进行下沉操作,复杂度为 O(NlogN)。

现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较和交换。

比较

排序算法最好时间复杂度平均时间复杂度最坏时间复杂度空间复杂度稳定性适用场景
冒泡排序O(N)O(N^2)O(N^2)O(1)稳定
选择排序O(N)O(N^2)O(N^2)O(1)不稳定运行时间和输入无关,数据移动次数最少,数据量较小的时候适用。
插入排序O(N)O(N^2)O(N^2)O(1)稳定数据量小、大部分已经被排序
希尔排序O(N)O(N^1.3)O(N^2)O(1)不稳定
快速排序O(NlogN)O(NlogN)O(N^2)O(logN)-O(N)不稳定最快的通用排序算法,大多数情况下的最佳选择
归并排序O(NlogN)O(NlogN)O(NlogN)O(N)稳定需要稳定性,空间不是很重要
堆排序O(NlogN)O(NlogN)O(NlogN)O(1)O(1)不稳定
  • 当规模较小,如小于等于50,采用插入或选择排序。
  • 当元素基本有序,选择插入、冒泡或随机的快速排序。
  • 当规模较大,采用 O(NlogN)排序算法。
  • 当待排序的关键字随机分布时,快速排序的平均时间最短。
  • 当需要保证稳定性的时候,选用归并排序。

非比较排序

之前介绍的算法都是基于比较的排序算法,下边介绍两种不是基于比较的算法。

计数排序

已知数据范围 x1 到 x2, 对范围中的元素进行排序。可以使用一个长度为 x2-x1+1 的数组,存储每个数字对应的出现的次数。最终得到排序后的结果。

桶排序

桶排序假设待排序的一组数均匀独立的分布在一个范围中,并将这一范围划分成几个桶。然后基于某种映射函数,将待排序的关键字 k 映射到第 i 个桶中。接着将各个桶中的数据有序的合并起来,对每个桶中的元素可以进行排序,然后输出得到一个有序序列。

转载于:https://juejin.im/post/5c9479a06fb9a0710f47e79c

相关文章:

正则表达式简介及在C++11中的简单使用

正则表达式(regular expression)是计算机科学中的一个概念&#xff0c;又称规则表达式&#xff0c;通常简写为regex、regexp、RE、regexps、regexes、regexen。 正则表达式是一种文本模式。正则表达式是强大、便捷、高效的文本处理工具。正则表达式本身&#xff0c;加上如同一…

经典再读 | NASNet:神经架构搜索网络在图像分类中的表现

&#xff08;图片付费下载于视觉中国&#xff09;作者 | Sik-Ho Tsang译者 | Rachel编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;【导读】从 AutoML 到 NAS&#xff0c;都是企业和开发者的热门关注技术&#xff0c;以往我们也分享了很多相关…

javascript面向对象技术基础(二)

数组我们已经提到过,对象是无序数据的集合,而数组则是有序数据的集合,数组中的数据(元素)通过索引(从0开始)来访问,数组中的数据可以是任何的数据类型.数组本身仍旧是对象,但是由于数组的很多特性,通常情况下把数组和对象区别开来分别对待(Throughout this book, objects and a…

MediaPipe:Google Research 开源的跨平台多媒体机器学习模型应用框架

作者 | MediaPipe 团队来源 | TensorFlow&#xff08;ID&#xff1a;tensorflowers&#xff09;【导读】我爱计算机视觉&#xff08;aicvml&#xff09;CV君推荐道&#xff1a;“虽然它是出自Google Research&#xff0c;但不是一个实验品&#xff0c;而是已经应用于谷歌多款产…

机器学习研究的七个迷思

作者 Oscar Chang 总结了机器学习研究中的七大迷思&#xff0c;每个问题都很有趣&#xff0c;也可能是你在研究机器学习的过程中曾经遇到过的“想当然”问题。AI 前线对这篇文章进行了编译&#xff0c;以飨读者。迷思之一&#xff1a;TensorFlow 是张量操作库 它实际上就是一个…

Caffe源码中common文件分析

Caffe源码(caffe version:09868ac , date: 2015.08.15)中的一些重要头文件如caffe.hpp、blob.hpp等或者外部调用Caffe库使用时&#xff0c;一般都会include<caffe/common.hpp>文件&#xff0c;下面分析此文件的内容&#xff1a;1. include的文件&#xff1a;boost中…

编程乐趣:C#彻底删除文件

经常用360的文件粉碎&#xff0c;删除隐私文件貌似还不错的。不过C#也可以实现彻底删除文件。试了下用360文件恢复恢复不了源文件了。代码如下&#xff1a;public class AbsoluteFile{public event EventHandler FinishDeleteFileEvent null;public event EventHandler Finish…

大数据工程师手册:全面系统的掌握必备知识与工具

作者 | Phoebe Wong译者 | 陆离编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;前言如何才能成为一名真正的“全栈&#xff08;full-stack&#xff09;”数据科学家&#xff1f;需要了解哪些知识&#xff1f;掌握哪些技能&#xff1f;概括来讲…

JSON.stringify()

写在前边 不言而喻&#xff0c;JSON.stringify() 是用来将合法的JSON数据字符串化的&#xff01;然而在正常的工作中我们用到的只是最基础的功能&#xff1b;今天我们就探索不一样的JSON.stringify()。 基础用法 基本数据类型 JSON.stringify(2) // "2" JSON.stringi…

C++中前置声明介绍

前置声明是指对类、函数、模板或者结构体进行声明&#xff0c;仅仅是声明&#xff0c;不包含相关具体的定义。在很多场合我们可以用前置声明来代替#include语句。类的前置声明只是告诉编译器这是一个类型&#xff0c;但无法告知类型的大小&#xff0c;成员等具体内容。在未提供…

在Java SE中使用Hibernate处理数据

如今&#xff0c;Hibernate正在迅速成为非常流行的&#xff08;如果不是最流行的&#xff09;J2EE O/R映射程序/数据集成框架。它为开发人员提供了处理企业中的关系数据库的整洁、简明且强大的工具。但如果外部需要访问这些已被包装在J2EE Web应用程序中的实体又该怎么办&#…

利用OpenCV、Python和Ubidots构建行人计数器程序(附完整代码)

作者 | Jose Garcia译者 | 吴振东校对 | 张一豪、林亦霖&#xff0c;编辑 | 于腾凯来源 | 数据派&#xff08;ID&#xff1a;datapi&#xff09;导读&#xff1a;本文将利用OpenCV&#xff0c;Python和Ubidots来编写一个行人计数器程序&#xff0c;并对代码进行了较为详细的讲解…

开源软件License汇总

开源软件英文为Open Source Software&#xff0c;简称OSS&#xff0c;又称开放源代码软件&#xff0c;是一种源代码可以任意获取的计算机软件&#xff0c;这种软件的著作权持有人在软件协议的规定之下保留一部分权利并允许用户学习、修改以及以任何目的向任何人分发该软件。 某…

前深度学习时代CTR预估模型的演化之路:从LR到FFM\n

本文是王喆在 AI 前线 开设的原创技术专栏“深度学习 CTR 预估模型实践”的第二篇文章&#xff08;以下“深度学习 CTR 预估模型实践”简称“深度 CTR 模型”&#xff09;。专栏第一篇文章回顾&#xff1a;《深度学习CTR预估模型凭什么成为互联网增长的关键&#xff1f;》。重看…

神器与经典--sp_helpIndex

每每和那些NB的人学习技术的时候&#xff0c;往往都佩服他们对各个知识点都熟捻于心,更佩服的是可以在很短时间找出很多业界大师写的文章和开发的工具,就像机器猫的口袋&#xff0c;让人羡慕嫉妒恨啊&#xff01;宋沄剑宋桑就是其中之一,打劫其硬盘的念头已计划很久,只待时机成…

评分9.7!这本Python书彻底玩大了?程序员:真香!

「超级星推官/每周分享」是一个围绕程序员生活、学习相关的推荐栏目。CSDN出品&#xff0c;每周发布&#xff0c;暂定5期。关键词&#xff1a;靠谱&#xff01;优质&#xff01;本期内容&#xff0c;我们将抽1人送出由我司程序员奉为“超级神作”的《疯狂Python讲义》1本&#…

Caffe源码中caffe.proto文件分析

Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件&#xff0c;这里介绍下caffe.proto文件。在src/caffe/proto目录下有一个caffe.proto文件。proto目录下除了caffe.proto文件外&#xff0c;还有caffe.pb.h和caffe.pb.cc两个文件&#xff0c;此两个文件是根…

这套完美的Java环境安装教程,完整,详细,清晰可观,让你一目了然,简单易懂。⊙﹏⊙...

JDK下载与安装教程 2017年06月18日 22:53:16 Danishlyy1995 阅读数&#xff1a;349980版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/u012934325/article/details/73441617学习JAVA&#xff0c;必须得安装一下JDK(java dev…

【畅谈百度轻应用】云时代·轻应用·大舞台

云时代轻应用大舞台刘志勇君不见&#xff0c;上下班的地铁上&#xff0c;低头看手机&#xff1b;同事吃饭聊天&#xff0c;低头看手机&#xff1b;甚至朋友聚会&#xff0c;忙里偷闲打个招呼&#xff0c;然后继续低头看手机。正如微博上一个流传甚广的段子&#xff1a;“世界上…

如何画出高级酷炫的神经网络图?优秀程序员都用了这几个工具

&#xff08;图片付费下载于视觉中国&#xff09;作者 | 言有三 来源 | 有三AI&#xff08;ID&#xff1a;yanyousan_ai&#xff09;【导读】本文我们聊聊如何才能画出炫酷高大上的神经网络图&#xff0c;下面是常用的几种工具。1、NN-SVGNN-SVG 可以非常方便的画出各种类型的…

OpenBLAS简介及在Windows7 VS2013上源码的编译过程

OpenBLAS(Open Basic Linear Algebra Subprograms)是开源的基本线性代数子程序库&#xff0c;是一个优化的高性能多核BLAS库&#xff0c;主要包括矩阵与矩阵、矩阵与向量、向量与向量等操作。它的License是BSD-3-Clause&#xff0c;可以商用&#xff0c;目前最新的发布版本是0.…

技本功丨请带上纸笔刷着看:解读MySQL执行计划的type列和extra列

本萌最近被一则新闻深受鼓舞&#xff0c;西工大硬核“女学神”白雨桐&#xff0c;获6所世界顶级大学博士录取通知书。货真价值的才貌双全&#xff0c;别人家的孩子高考失利与心仪的专业失之交臂&#xff0c;选择了软件工程这门自己完全不懂的专业.即便全部归零&#xff0c;也要…

精美素材分享:16套免费的扁平化图标下载

在这篇文章中你可以看到16套华丽的扁平化图标素材&#xff0c;对于设计师来说非常有价值&#xff0c;能够帮助他们节省大量的时间。这些精美的扁平化图标可用于多种用途&#xff0c;如&#xff1a;GUI 设计&#xff0c;印刷材料&#xff0c;WordPress 主题&#xff0c;演示&…

Caffe源码中math_functions文件分析

Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件&#xff0c;这里介绍下math_functions文件。1. include文件&#xff1a;(1)、<glog/logging.h>&#xff1a;GLog库&#xff0c;它是google的一个开源的日志库&#xff0c;其使用可以参考&…

论文推荐 | 目标检测中不平衡问题算法综述

&#xff08;图片付费下载于视觉中国&#xff09;作者 | CV君来源 | 我爱计算机视觉&#xff08;ID&#xff1a;aicvml&#xff09;今天跟大家推荐一篇前几天新出的投向TPAMI的论文&#xff1a;Imbalance Problems in Object Detection: A Review&#xff0c;作者详细考察了目标…

php使用redis的GEO地理信息类型

redis3.2中增中了对GEO类型的支持&#xff0c;该类型存储经纬度&#xff0c;提供了经纬设置&#xff0c;查询&#xff0c;范围查询&#xff0c;距离查询&#xff0c;经纬度hash等操作。 <?php$redis new Redis(); $redis->connect(127.0.0.1, 6379, 60); $redis->au…

Caffe源码中syncedmem文件分析

Caffe源码(caffe version:09868ac , date: 2015.08.15)中有一些重要文件&#xff0c;这里介绍下syncedmem文件。1. include文件&#xff1a;(1)、<caffe/common.hpp>&#xff1a;此文件的介绍可以参考&#xff1a;http://blog.csdn.net/fengbingchun/article/detail…

免费开源!新学期必收藏的AI学习资源,从课件、工具到源码都齐了

&#xff08;图片付费下载于视觉中国&#xff09;整理 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;2019 年 3 月 28 日&#xff0c;教育部公布了 2018 年度普通高等学校本科专业备案和审批结果&#xff0c;共有 35 所大学新增了独立的人工智能专…

win7利用remote连接服务器,显示发生身份验证错误 要求的函数不受支持

先参考1&#xff1a; https://blog.csdn.net/qq_35880699/article/details/81240010 发现我根本没找到oracle修正的那个文件&#xff01; 然后我搜索&#xff1a;win7没有oracle修正文件&#xff0c;-------按照参考2中的链接操作&#xff0c;我发现我根本没有CredSSP文件&…

java参数传递:值传递还是引用传递

2019独角兽企业重金招聘Python工程师标准>>> 基本类型作为参数传递时&#xff0c;是传递值的拷贝&#xff0c;无论你怎么改变这个拷贝&#xff0c;原值是不会改变的&#xff1b; 在Java中对象作为参数传递时&#xff0c;是把对象在内存中的地址拷贝了一份传给了参数…