数据结构与算法——常用排序算法及其Java实现
冒泡排序
原理:依次比较相邻的两个数,将小数放在前面(左边),大数放在后面(右边),就像冒泡一样
具体操作:第一趟,首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后,这样第一趟下来最大的数就在最后一位了。然后还是从第一个数开始重复第一趟步骤比较,但是这次不比较最后一个数了,第二趟结束后第二大的数就在倒数第二位......以此类推,直至全部排序完成。
所有代码在这,关键代码如下:
private static void sort(Comparable[] a) throws IllegalAccessException, InstantiationException {
Object tmp;
boolean noChange = false;//用来标识输入序列的排序情况,
for (int i = 0;i<a.length-1 && !noChange;i++){
noChange = true;//如果某一趟没有交换,说明数据已经排好序无需再进行接下来的排序
for (int j=0;j<a.length-1-i;j++){
if(a[j].compareTo(a[j+1])>0){
tmp = a[j];
a[j] = a[j+1];
a[j+1] = (Comparable) tmp;
noChange = false;//有交换
}
}
System.out.println(noChange);//展示跑了多少趟,几个打印就对应几趟
}
}
时间复杂度, 最好:正序O(n)、最坏:逆序O(n^2)、平均:O(n^2)
空间复杂度, O(1)
稳定性,因为相同的元素不会交换,所以是稳定的
选择排序
原理:每次选择未排序序列中的最小元素。
具体操作:首先在未排序序列中找到最小元素,放到序列的起始位置,然后从剩余未排序元素中寻找最小元素放到已排序序列的末尾,以此类推,直至排序完毕。
所有代码在这,关键代码如下:
public static void sort(Comparable[] a) {
int n = a.length;
for (int i = 0; i < n; i++) {
int min = i;
for (int j = i+1; j < n; j++) {
if (less(a[j], a[min])) min = j;//找到剩下元素的最小值
}
exch(a, i, min);//将本次最小值与已经排好序的队列的最后一个元素交换
}
}
时间复杂度, 都是:O(n^2)
空间复杂度, O(1)
稳定性,不稳定。举个例子,序列5 8 5 2 9, 第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了
插入排序
原理:将未排序的序列中的每一个数据依次按合理的顺序插入已排列的数据中。
具体操作:构建有序序列,对于未排序数据,在已排序序列中从头扫描,找到相应位置并插入。第一趟第一个就是有序数据,第二趟把第二个数据和第一个有序数据排序,第三趟把第三个数据和一、二个有序数据排序,以此类推直至排序完毕。
所有代码在这,关键代码如下:
public static void sort(Comparable[] a) {
int n = a.length;
for (int i = 0; i < n; i++) {
for (int j = i; j > 0 && less(a[j], a[j-1]); j--) {
exch(a, j, j-1);//将未排序的第一个数据插入已排序的数据汇中的合适位置
}
}
}
时间复杂度, 最好:O(n)、最坏:O(n^2)、平均:O(n^2)。插入排序一般来说比选择排序快,因为插入排序每次都是在已排序的数据中找(插入点),而选择排序每次都是在未排序的数据中找(最小值),所以插入排序很好的利用了已有有序结果,当然更快。
空间复杂度, O(1)
稳定性,稳定,因为待插入元素和有序序列比较都是从最大值开始比较的,如果小于某个元素才放到该元素前面否则放该元素后面,也就是说,相同元素在有序队列中的顺序和其进入有序队列的先后(也就是原本相对位置)是一致的
希尔排序
原理:使数组中的任意间隔为 h 的元素都是有序的
具体操作:选择一个递增的增量序列t1,t2,…,tk,tk=1;按增量序列个数k,对序列进行k 趟排序;每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子序列进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
所有代码在这,关键代码如下:
public static void sort(Comparable[] a) {
int n = a.length;
// 3x+1 increment sequence: 1, 4, 13, 40, 121, 364, 1093, ...
int h = 1;
while (h < n/3) h = 3*h + 1;
while (h >= 1) {
// h-sort the array
for (int i = h; i < n; i++) {
for (int j = i; j >= h && less(a[j], a[j-h]); j -= h) {
exch(a, j, j-h);
}
}
h /= 3;
}
}
时间复杂度, 具体取决于间隔 h,最好:O(nlogn)、最坏:O(n^2)、平均:无。希尔算法的性能与h有很大关系。只对特定的待排序记录序列,可以准确地估算关键词的比较次数和对象移动次数。想要弄清关键词比较次数和记录移动次数与h选择之间的关系,并给出完整的数学分析,至今仍然是数学难题。
空间复杂度, O(1)
稳定性,一次插入排序是稳定的,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,shell排序每个不同的增量都是插入排序,有多次,实际上是分组插入排序(又叫缩小增量排序),所以是不稳定的。
归并排序
原理:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
具体操作:把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别递归调用归并排序(终止条件是只有1个元素的最小子序列,两个最小子序列直接merge);将两个排序好的子序列合并成一个最终的排序序列。
所有代码在这,关键代码如下:
// stably merge a[lo .. mid] with a[mid+1 ..hi] using aux[lo .. hi]
private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
// precondition: a[lo .. mid] and a[mid+1 .. hi] are sorted subarrays
assert isSorted(a, lo, mid);
assert isSorted(a, mid+1, hi);
// copy to aux[]
for (int k = lo; k <= hi; k++) {
aux[k] = a[k];
}
// merge back to a[]
int i = lo, j = mid+1;
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (less(aux[j], aux[i])) a[k] = aux[j++];
else a[k] = aux[i++];
}
// postcondition: a[lo .. hi] is sorted
assert isSorted(a, lo, hi);
}
时间复杂度, 都是:O(nlogn)。通过使用插入排序来处理小规模子序列(如长度小于15)一般可以提升归并排序的效率10%~15%
空间复杂度, O(n)
稳定性,稳定
快速排序
原理:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后分别对这两部分记录进行排序,以达到整个序列有序
具体操作:从数列中挑出一个元素,称为 "基准"(pivot);重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
所有代码在这,关键代码如下:
public static void sort(Comparable[] a) {
StdRandom.shuffle(a);//打乱数组,消除输入依赖
sort(a, 0, a.length - 1);
assert isSorted(a);
}
// quicksort the subarray from a[lo] to a[hi]
private static void sort(Comparable[] a, int lo, int hi) {
if (hi <= lo) return;
int j = partition(a, lo, hi);
sort(a, lo, j-1);
sort(a, j+1, hi);
assert isSorted(a, lo, hi);
}
// partition the subarray a[lo..hi] so that a[lo..j-1] <= a[j] <= a[j+1..hi]
// and return the index j.
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo;
int j = hi + 1;
Comparable v = a[lo];
while (true) {
// find item on lo to swap
while (less(a[++i], v))
if (i == hi) break;
// find item on hi to swap
while (less(v, a[--j]))
if (j == lo) break; // redundant since a[lo] acts as sentinel
// check if pointers cross
if (i >= j) break;
exch(a, i, j);
}
// put partitioning item v at a[j]
exch(a, lo, j);
// now, a[lo .. j-1] <= a[j] <= a[j+1 .. hi]
return j;
}
时间复杂度, 最好:O(nlogn)、最坏:O(n^2)、平均:O(nlogn)。一般快于归并排序,虽然比较次数可能多些,但是移动数据次数更少。同样的小规模数据转换为插入排序会有效果提升。对于包含大量重复元素的数据,使用三向切分也能提高性能。
空间复杂度, 最好:每次划分都在中间O(logn)、最坏:退化为冒泡O(n)
稳定性,不稳定,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻
堆排序
原理:利用堆这种数据结构的一种排序算法,堆是一个近似完全二叉树的结构,满足堆的性质:即子结点的键总是小于(或者大于)它的父节点
具体操作:将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区; 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n]; 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
所有代码在这,堆的相关代码,关键代码如下:
public static void sort(Comparable[] pq) {
int n = pq.length;
for (int k = n/2; k >= 1; k--)//构造堆,从最后一个有子节点的节点开始比较和下沉,直至根节点
sink(pq, k, n);
while (n > 1) {//堆排序
exch(pq, 1, n--);//将最大值(根节点)和无序数组最后一元素交换,并将无序标志前移
sink(pq, 1, n);//下沉交换后的根节点
}
}
private static void sink(Comparable[] pq, int k, int n) {
while (2*k <= n) {
int j = 2*k;
if (j < n && less(pq, j, j+1)) j++;//先比较左右子节点,找到较大的
if (!less(pq, k, j)) break;//大于较大的子节点,无需下沉
exch(pq, k, j);//否则下沉
k = j;//继续比较以这个节点为根的子树
}
}
时间复杂度, 都是:O(nlogn)
空间复杂度, O(1)
稳定性,比如:3 27 36 27,堆顶3先输出,则第三层的27(最后一个27)跑到堆顶,然后堆稳定,继续输出堆顶,是刚才那个27,这样说明后面的27先于第二个位置的27输出,不稳定
计数排序
原理:使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。不是比较排序,排序的速度快于任何比较排序算法
具体操作:找出待排序的数组中最大和最小的元素;统计数组中每个值为i的元素出现的次数,存入数组C的第i项;对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
所有代码在这,关键代码如下:
private static int[] countSort(int[] A,int k){
int[] C=new int[k+1];//构造C数组
int length=A.length,sum=0;//获取A数组大小用于构造B数组
for (int anArray : A) { C[anArray] += 1;}// 统计A中各元素个数,存入C数组
for(int i=0;i<k+1;i++){ //修改C数组,使得A中小于等于元素i的元素有C[i]个,亦即i在B中的自然序号(减去1得到数组序号)
sum+=C[i];
C[i]=sum;
}
int[] B=new int[length];//构造B数组
for(int i=length-1;i>=0;i--){//倒序遍历A数组(保证稳定性,因为相同的元素中靠后的个体的序号也相对较大),构造B数组
B[C[A[i]]-1]=A[i];//将A中该元素放到排序后数组B中指定的位置
C[A[i]]--;//将C中该元素-1,方便存放下一个同样大小的元素
}
return B;//将排序好的数组返回,完成排序
}
时间复杂度, 都是:O(n+k),(输入的元素是n 个0到k之间的整数)
空间复杂度, O(k)
稳定性,稳定
桶排序
原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)
具体操作: 设置一个定量的数组当作空桶;遍历输入数据,并且把数据一个一个放到对应的桶里去;对每个不是空的桶进行排序;从不是空的桶里把排好序的数据拼接起来。
所有代码在这,关键代码如下:
private static void bucketSort(int[] arr){
int max = Integer.MIN_VALUE,min = Integer.MAX_VALUE;
for (int anArr : arr) {
max = Math.max(max, anArr);
min = Math.min(min, anArr);
}
//桶数
int bucketNum = (max - min) / arr.length + 1;
ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
for(int i = 0; i < bucketNum; i++){
bucketArr.add(new ArrayList<Integer>());
}
//将每个元素放入桶
for (int anArr : arr) {
int num = (anArr - min) / (arr.length);
bucketArr.get(num).add(anArr);
}
//对每个桶进行排序,调用自带的排序
for (ArrayList<Integer> aBucketArr : bucketArr) {
Collections.sort(aBucketArr);
}
//打印结果
for (ArrayList<Integer> anA : bucketArr) {StdOut.print(anA + "\t"); }
StdOut.println();
}
时间复杂度, 最好:O(n+k)、最坏:O(n^2)、平均:O(n+k)
空间复杂度, O(n+k)
稳定性,稳定,因为相同的元素肯定在同一个桶里,并且加入桶的顺序和原顺序一致
基数排序
原理:将待排序数据拆分成多个关键字进行排序,基数排序的实质是多关键字排序,将待排数据里的关键字拆分成多个排序关键字,第1个排序关键字,第2个排序关键字,......,第k个排序关键字,然后根据子关键字对待排序数据进行排序(必须借助于另一种排序方法,而且这种排序方法必须是稳定的)
具体操作:取得数组中的最大数,并取得位数;arr为原始数组,从最低位开始取每个位组成radix数组;对radix进行排序;换句话说,第一轮下来,数组按照个位有序,第二轮下来数组按照十位有序,依次类推,由于子关键字排序稳定所以最终的数组是有序的
所有代码在这,关键代码如下:
private static void radixSort(int[] array,int d){
int n=1;//代表位数对应的数:1,10,100...
int k=0;//保存每一位排序后的结果用于下一位的排序输入
int length=array.length;
int[][] bucket=new int[10][length];//二维数组排序桶,用于保存每次排序后的结果,这一位上排序结果相同的数字放在同一个桶里
int[] order=new int[length];//用于保存每个桶里有多少个数字,默认初始化为0
while(n<d){
for(int num:array) {//将数组array里的每个数字放在相应的桶里
int digit=(num/n)%10;
bucket[digit][order[digit]]=num;
order[digit]++;
}
for(int i=0;i<length;i++){//将前一个循环生成的桶里的数据覆盖到原数组中用于保存这一位的排序结果
if(order[i]!=0){//这个桶里有数据,从上到下遍历这个桶并将数据保存到原数组中
for(int j=0;j<order[i];j++){
array[k]=bucket[i][j];
k++;
}
}
order[i]=0;//将桶里计数器置0,用于下一次位排序
}
n*=10;
k=0;//将k置0,用于下一轮保存位排序结果
}
}
时间复杂度, 都是:O(nxk)
空间复杂度, O(nxk)
稳定性,稳定
数据结构与算法——常用排序算法及其Java实现的更多相关文章
- Java常用排序算法+程序员必须掌握的8大排序算法+二分法查找法
Java 常用排序算法/程序员必须掌握的 8大排序算法 本文由网络资料整理转载而来,如有问题,欢迎指正! 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排 ...
- Java 常用排序算法/程序员必须掌握的 8大排序算法
Java 常用排序算法/程序员必须掌握的 8大排序算法 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排序(直接选择排序.堆排序) 4)归并排序 5)分配 ...
- 我们一起来排序——使用Java语言优雅地实现常用排序算法
破阵子·春景 燕子来时新社,梨花落后清明. 池上碧苔三四点,叶底黄鹂一两声.日长飞絮轻. 巧笑同桌伙伴,上学径里逢迎. 疑怪昨宵春梦好,元是今朝Offer拿.笑从双脸生. 排序算法--最基础的算法,互 ...
- Java常用排序算法及性能测试集合
测试报告: Array length: 20000 bubbleSort : 573 ms bubbleSortAdvanced : 596 ms bubbleSortAdvanced2 : 583 ...
- 常用排序算法的总结以及编码(Java实现)
常用排序算法的总结以及编码(Java实现) 本篇主要是总结了常用算法的思路以及相应的编码实现,供复习的时候使用.如果需要深入进行学习,可以使用以下两个网站: GeeksForGeeks网站用于学习相应 ...
- 面试中常用排序算法实现(Java)
当我们进行数据处理的时候,往往需要对数据进行查找操作,一个有序的数据集往往能够在高效的查找算法下快速得到结果.所以排序的效率就会显的十分重要,本篇我们将着重的介绍几个常见的排序算法,涉及如下内容: 排 ...
- 常用排序算法java实现
写在前面:纸上得来终觉浅.基本排序算法的思想,可能很多人都说的头头是到,但能说和能写出来,真的还是有很大区别的. 今天整理了一下各种常用排序算法,当然还不全,后面会继续补充.代码中可能有累赘或错误的地 ...
- 转载部长一篇大作:常用排序算法之JavaScript实现
转载部长一篇大作:常用排序算法之JavaScript实现 注:本文是转载实验室同门王部长的大作,找实习找工作在即,本文颇有用处!原文出处:http://www.cnblogs.com/ywang172 ...
- 常用排序算法的python实现和性能分析
常用排序算法的python实现和性能分析 一年一度的换工作高峰又到了,HR大概每天都塞几份简历过来,基本上一天安排两个面试的话,当天就只能加班干活了.趁着面试别人的机会,自己也把一些基础算法和一些面试 ...
随机推荐
- IPD术语
集成产品开发(Integrated Product Development,简称IPD)是一套产品开发的模式.理念与方法. ABC 基于活动的成本核算 ABM 基于活动的管理 ADCP 可获得性决策 ...
- 洛谷 题解 CF711A 【Bus to Udayland】
先用一个字符数组存每行的座位情况(字符变量也可以) 接下来用另一个数组存最后的座位情况 好了,看代码 #include<iostream> using namespace std; boo ...
- Redis内存分析工具—redis-rdb-tools (转载http://www.voidcn.com/article/p-axfdqxmd-bro.html)
redis-rdb-tools是由Python写的用来分析Redis的rdb快照文件用的工具,它可以把rdb快照文件生成json文件或者生成报表用来分析Redis的使用详情.使用标准的diff工具比较 ...
- windows下使用命令行编译、链接C++源文件
目录 1.流程 2.操作 1.流程 .cpp-->.o-->.exe 分别为 源文件-->中间目标文件-->可执行文件 两个-->的过程分别为编译.链接 p.s.多个 . ...
- 1.3Security:权限管理,过滤、监听、拦截
Security:权限管理 常用权限拦截器 SecurityContextPersistenceFilter 以前是HttpSesstionContextIntegrationFilter,位于过滤器 ...
- 串的模式匹配,KMP算法
串的模式匹配 现考虑一个常用操作,在字符串s(我们称为主串)中的第pos开始处往后查找,看在主串s中有没有和子串p相匹配的的,如果有,则返回字串p第一次出现的位置. 暴力求解 int Index(ch ...
- 基于bootstrap selectpicker ,实现select下拉框模糊查询功能
1.html代码块 需要引入bootstrap的css js jquery bootstrap.css bootstrap-select.min.css jquery-1.11.3.min.js bo ...
- R-corrplot相关性绘图,只有你想不到的
初步接触数据集,探索性分析后,经常需要做一个相关分析,得到各变量间的相关系数以及显著性水平. 本文介绍一下R-corrplot包进行相关可视化展示. 一 数据准备 载入所需的R包,利用公共数据集mtc ...
- HTML类
class Html: def __init__(self,name): self.name = name @staticmethod def full_name(): print('全称:Hype ...
- el表达式获取url中携带的参数
使用JSTL时,URL会被隐含的对象param包裹起来,使用param.变量名,直接获取值 <body>hello:${param.name}</body> 在使用jquery ...