排序算法的实现及性能分析
——(java版)
排序是对数据元素序列建立某种有序排列的过程。更确切的说,排序是把一个数据元素序列整理成按关键字递增(或递减)排列的过程。
不过首先,我们必须先解释一下关键字这个词。关键字是要排序的数据元素集合中的一个域,排序是以关键字为基准进行的。而关键字也分为主关键字和次关键字。对于要排序的数据元素集合来说,如果关键字满足数据元素值不同时,该关键字也不同,这样的关键字称为主关键字。不满足主关键字定义的就称为次关键字。
简单来说,排序分为内部排序和外部排序两种。内部排序是把待排序的数据元素全部调入内存中进行的排序。如果数据元素的数量过大,需要分批导入内存,分批导入内存的数据元素排好序后在分批导出到磁盘和磁带外介质上的排序方法称为外部排序。在本篇博客中将只讨论分析内部排序算法的性能。
那么通常比较内部排序算法优劣的标准有如下3个:
<!--[if !supportLists]-->(1)<!--[endif]-->时间复杂度。时间复杂度是衡量排序算法好坏最重要的标准,它是一个函数,定量描述了某个算法的运行时间,时间复杂度通常使用大O符号表述,比如说直接插入排序算法的最差时间复杂度为O(n^2)。
<!--[if !supportLists]-->(2)<!--[endif]-->空间复杂度。空间复杂度用于衡量算法中使用额外内存空间的多少。当排序算法中使用的额外内存空间与要排序数据元素的个数n无关时,其空间复杂度为O(1),大多数排序算法的空间复杂度都为O(1)。
<!--[if !supportLists]-->(3)<!--[endif]-->稳定性。当使用主关键字排序时,任何排序算法对相同的数据元素序列的排序结果必定是相同的。但使用次关键字排序时,其排序结果有可能相同,也用可能不同。设待排序的数据元素共有n个,设Ki和Kj分表表示第i个数据元素的关键字和第j个数据元素的关键字,设Ri和Rj分别表示第i个数据元素和第j个数据元素。若Ki=Kj,且在排序之前数据元素Ri排在数据元素Rj之前,在排序之后数据元素Ri仍然排在数据元素Rj之前的排序算法称为稳定的排序算法,否则称为不稳定的排序算法。
常见的内部排序算法有以下几种:
<!--[if !supportLists]-->(1)<!--[endif]-->直接插入排序;
<!--[if !supportLists]-->(2)<!--[endif]-->希尔排序;
<!--[if !supportLists]-->(3)<!--[endif]-->直接选择排序;
<!--[if !supportLists]-->(4)<!--[endif]-->堆排序;
<!--[if !supportLists]-->(5)<!--[endif]-->冒泡排序;
<!--[if !supportLists]-->(6)<!--[endif]-->快速排序;
<!--[if !supportLists]-->(7)<!--[endif]-->归并排序;
<!--[if !supportLists]-->(8)<!--[endif]-->基数排序;
直接插入排序
基本思想:顺序地将待排序的数据元素按其值得大小插入到已排序的数据元素子集合的适当位置。
实现代码:
public class InsertSort { public int[] insertSort(int[] a){ int i,j,temp; int n = a.length; for(i = 0;i < n-1;i++){ temp = a[i+1]; j = i; while(j > -1 && temp <= a[j]){ a[j+1] = a[j]; j--; } a[j+1] = temp; } return a; } }
直接插入排序算法的时间复杂度在O(n)和O(n^2)之间,即最好情况下是O(n),最坏情况下是O(n^2),其空间复杂度是O(1),是一种稳定的排序算法。
希尔排序
基本思想:把待排序的数据元素分为若干个小组,对同一组内的数据元素用直接插入排序;小组的个数逐次递减;当完成了所有数据元素都在一个组内的排序后排序过程结束。希尔排序又称为缩小增量排序。
演示效果图:
实现代码:
public class ShellSort { /** * 希尔排序实现方法 * @param a 需要排序的数组 * @param d 增量数组 * @param numOfD 增量数组的长度 * @return */ public int[] shellSort(int[]a,int[]d,int numOfD){ int i,j,k,m,span; int temp; int n = a.length; for(m = 0;m < numOfD;m++){ span = d[m]; for(k = 0;k < span;k++){ for(i = k;i < n - span;i = i + span){ temp = a[i + span]; j = i; while(j > -1&&temp <= a[j]){ a[j + span] = a[j]; j = j - span; } a[j + span] = temp; } } } return a; } }
希尔排序的时间复杂度是O(n(lbn)^2),空间复杂度是O(1),由于希尔排序是按增量分组进行的排序,两个相同的数据元素有可能分在不同的组内,因此希尔排序算法是一种不稳定的排序算法。
直接选择排序
基本思想:从待排序的数据元素集合中选取最小的数据元素并将它与原始数据元素集合中的第一个数据元素交换位置;然后从不包括第一个位置上的数据元素集合中选取最小的数据元素并将它与原始数据元素集合中的第二个数据元素交换位置;如此重复,知道数据元素集合只剩下一个数据元素为止。
动态演示图:
实现代码:
public class SelectSort { /** * 不稳定的直接插入排序算法 * @param a 待排序数组 * @return 已排序数组 */ public int[] selectSort(int[] a){ int i,j,min; int temp; int n = a.length; for(i = 0;i < n - 1;i++){ min = i; //找出无序区中最小元素 for(j = i + 1;j < n;j++){ if(a[j] < a[min]){ min = j; } } //将无序区的最小元素与无序区第一个元素交换位置 if(min != i){//当最小元素为i时不交换位置 temp = a[i]; a[i] = a[min]; a[min] = temp; } } return a; } public int[] selectSort2(int[] a){ int i,j,min; int temp; int n = a.length; for(i = 0;i < n - 1;i++){ min = i; //找出无序区中最小元素 for(j = i + 1;j < n;j++){ if(a[j] < a[min]){ min = j; } } //将无序区的最小元素与无序区第一个元素交换位置 if(min != i){//当最小元素为i时不交换位置 temp = a[min]; for(j = min;j > i;j--){ a[j] = a[j-1]; } a[i] = temp; } } return a; } }
直接选择排序算法的时间复杂度是O(n^2),其空间复杂度O(1),直接选择排序算法是不稳定的排序算法,这是由于每次从无序区下半选出最小元素后,与无序区第一个元素交换而引起的,因为交换可能引起相同的数据元素位置发生变化。如果在选出最小元素后,将它前面的无序记录依次后移,然后再将最小记录放在有序区的后面,这样就能保证排序算法的稳定性
堆排序
在直接选择排序中,放在数组的n个数据元素排成一个线性序列(即线性结构),要从有n个数据元素的数组中选择出一个最小的数据元素需要比较n-1次,如果能把待排序的数据元素集合构造成一个完全二叉树结构,则每次选择出一个最大(或最小)的数据元素只需比较完全二叉树的高度次,即lbn次。
基本思想:循环执行过程(1)——(3)直到数组为空为止。
<!--[if !supportLists]-->(1)<!--[endif]-->把堆顶a[0]元素(最大元素)和当前最大堆的最后一个元素交换;
<!--[if !supportLists]-->(2)<!--[endif]-->最大堆元素个数减一;
<!--[if !supportLists]-->(3)<!--[endif]-->调节新的堆使之满足最大堆的定义。
动态演示:
实现代码:
* 保持最大堆的性质 * @param a 原始元素序列 * @param n 数组的长度,即待排序元素的个数 * @param h 以h为根结点元素下标 */ public static void createHeap(int[] a,int n,int h){ int i,j,flag; int temp; i = h; j = 2 * i + 1; temp = a[i]; flag = 0; //沿左右孩子较大者重复向下筛选 while(j < n&&flag != 1){ //寻找左右孩子结点中的较大者,j为其下标 if(j < n - 1&&a[j] < a[j+1]){ j++; } if(temp > a[j]){ flag = 1; }else{ a[i] = a[j]; i = j; j = 2 * i + 1; } } a[i] = temp; } public static void initCreateHeap(int[] a){ int n = a.length; for(int i = (n - 1) / 2;i >= 0;i--){ createHeap(a, n, i); } } public static int[] heapSort(int[] a){ int temp; int n = a.length; initCreateHeap(a); for(int i = n - 1;i > 0;i--){ temp = a[0]; a[0] = a[i]; a[i] = temp; createHeap(a, i, 0); } return a; }
推排序算法的时间复杂度是O(nlbn),其空间复杂度是O(1),而且该算法是一种不稳定的排序算法。
冒泡排序
基本思想:设数组a中存放了n个数据元素,循环进行n-1次如下的排序过程:第一次时,依次比较相邻两个数据元素a[i]和a[i+1](i=0,1,2……,n-2),若为逆序,即a[i]>a[i+1],则交换两个数据元素,否则不交换,这样数值最大的数据元素将被放置在a[n-1]中。第2次时,循环次数减一,即数据元素个数为n-1,操作方法和第一次的类似,这样整个n个数据元素集合中数值较大的数据元素将放置在a[n-2]中。当第n-1次结束时,整个n个数据元素中较小的数据元素中次小的数据元素将被放置在a[1]中,a[0]中放置了最小的数据元素。
动态演示:
实现代码:
public static int[] bubbleSort(int[] a){ int i,j,flag = 1; int temp; int n = a.length; for(i = 1;i < n&&flag == 1;i++){ flag = 0; for(j = 0;j < n - i;j++){ if(a[j] > a[j+1]){ flag = 1; temp = a[j]; a[j] = a[j+1]; a[j+1] = temp; } } } return a; }
冒泡排序算法最好情况的时间复杂度是O(n),最坏情况下的时间复杂度是O(n^2),而冒泡排序算法的空间复杂度是O(1),冒泡排序算法是一种稳定的排序算法。
快速排序
基本思想:设数组a中存放了n个数据元素,low为数组的低端下标,high为数组的高端下标,从数组a中任取一个元素(通常取a[low])作为标准元素,以该标准元素为基准来调整数组a中其他各个元素的位置,使排在标准元素前面的元素均小于标准元素,排在标准元素后面均大于或等于标准元素。这样一次排序过程结束后,一方面将标准元素为中心分为了两个子数组,位于标准元素左边子数组中的元素均小于标准元素,位于标准元素右边子数组中的元素均大于或等于标准元素。对于这两个子数组中的元素分别在进行方法类同的递归快速排序。算法的递归出口条件是low>=high。
动态演示:
实现代码:
public static int[] quickSort(int[] a,int low,int high){ int i,j; int temp; i = low; j = high; temp = a[low]; while(i < j){ //在数组右端开始扫描 while(i < j&&temp <= a[j]){ j--; } if(i < j){ a[i] = a[j]; i++; } //在数组左端开始扫描 while(i < j&&a[i] < temp){ i++; } if(i < j){ a[j] = a[i]; j--; } } a[i] = temp; if(low < i){ quickSort(a, low, i-1); } if(i < high){ quickSort(a, j+1, high); } return a; }
快速排序的时间复杂度是O(nlbn),空间复杂度为O(lbn),最坏情况下的空间复杂度是O(n),而且该排序算法是一种不稳定的排序算法。
归并排序
基本思想:设数组a中存放了n个数据元素,初始时我们把它们看成是n个长度为1的有序子数组,然后从第一个子数组开始,把相邻的子树组两两合并,得到n/2个(若n/2为小数则向上取整)长度为2的新的有序子数组(当n为奇数时最后一个新的有序子数组的长度为1);对这些新的有序子数组在两两归并,如此重复,直到得到一个长度为n的有序数组为止。
动态演示:
实现代码:
/** * 归并排序的实现 * @param a 待排序数组 * @param swap 在排序过程中用于存放数组 * @param k 第一个有序子数组的长度 */ public static void merge(int[] a,int[] swap,int k){ int n = a.length; int m = 0,u1,l2,i,j,u2; int l1 = 0;//是第一个有界数组的下界为0 while(l1 + k <= n - 1){ l2 = l1 + k;//计算第二个有序子数组下界 u1 = l2 - 1;//计算第一个有序子数组上界 u2 = (l2 + k - 1 <= n - 1)?l2 + k -1:n - 1;//计算第二个有序子数组上界 for(i = l1,j = l2;i <= u1&&j <= u2;m++){ if(a[i] <= a[j]){ swap[m] = a[i]; i++; }else{ swap[m] = a[j]; j++; } } //子数组2已归并完毕,将子数组1中剩余的元素存放到数组swap中 while(i <= u1){ swap[m] = a[i]; m++; i++; } //子数组1已归并完毕,将子数组2中剩余的元素存放到数组swap中 while(j <= u2){ swap[m] = a[j]; m++; j++; } l1 = u2 + 1; } //将原数组中只够一组的数据元素存放在数组swap中 for(i = l1;i < n;i++,m++){ swap[m] = a[i]; } } public static int[] mergeSort(int[] a){ int i; int n = a.length; int k = 1;//归并长度从1开始 int[] swap = new int[n]; while(k < n){ merge(a,swap,k); for(i = 0;i < n;i++){ a[i] = swap[i]; } k = 2*k;//归并长度翻倍 } return a; }
对n个元素进行一次二路归并排序时,归并的次数约为lbn,任何一次的二路归并排序元素的比较次数都约为n-1,所以,二路归并排序算法的时间复杂度是O(nlbn);二路归并排序时使用了n个临时内存空间存放数据元素,所以,二路归并排序算法的空间复杂度是O(n)。二路归并排序算法是一种稳定的排序算法。
基数排序
基数排序也成为桶排序,是一种当待排序数据元素为整数类型时非常高效的排序方法。
基本思想:设待排序的数据元素是m位d进制整数(不足m位的在高位补0),设置n个桶,令其编号分别为0,1,2,…,d-1。首先按最低位(即个位)的数值一次把各数据元素放到相应的桶中,然后按照桶号从小到大和进入桶中数据元素的先后次序手机分配在各桶中的数据元素,这样就形成了数据元素集合的一个新的排列,我们称这样的一次排序过程为一次基数排序;对一次基数排序得到的数据元素序列再按次低位(即十位)的数值一次把各数据元素放到相应的桶中,然后按照桶号从小到大和进入桶中数据元素的先后次序收集分配在各桶中的数据元素;这样的过程重复进行,当完成了第m次基数排序后,就得到了排好序的数据元素序列。
实现代码:
/** * 基数排序的实现方法 * @param a 尚未排序的方法 * @param m 数据元素的最大位数 * @param d 进制的基数 * @return 已排好序的数组 * @throws Exception 抛出异常 */ public static int[] radixSort(int[] a,int m,int d) throws Exception{ int n = a.length; int i,j,k,l,power = 1; LinQueue[] queue_sort = new LinQueue[d]; //创建链式队列数组对象 for(i = 0;i < d;i++){ LinQueue queue = new LinQueue(); queue_sort[i] = queue; } //进行m次排序 for(i = 0;i < m;i++){ if(i == 0){ power = 1; }else{ power = power * d; } //一次将n个数据元素按第k为的大小放到相应的队列中 for(j = 0;j < n;j++){ k = a[j]/power - (a[j]/(power * d)) * d;//计算k值 queue_sort[k].insert(new Integer(a[j]));//将a[j]存储到队列k中 } //顺序回收各队列中的数据元素到数组a中 l = 0; for(j = 0;j < d;j++){ while(queue_sort[j].notEmpty()){ a[l] = ((Integer)queue_sort[j].delete()).intValue(); l++; } } } return a; } //生成100个从零到1000的随机数 public static int[] random(int[] a){ for(int i = 0;i < 100;i++){ a[i] = (int)(Math.random()*1000); } for(int i = 0;i < 100;i++){ System.out.print(" a["+i+"]:"+a[i]); } return a; }
基数排序算法的时间复杂度为O(m*n),该排序算法的空间复杂度是O(n),并且基数排序算法是一种稳定的排序算法。
各排序算法的性能比较
排序方法 |
最好时间复杂度 |
平均时间复杂度 |
最坏时间复杂度 |
空间复杂度 |
稳定性 |
插入排序 |
O(n) |
O(n^2) |
O(n^2) |
O(1) |
稳定 |
希尔排序 |
|
O(n^1.3) |
|
O(1) |
不稳定 |
选择排序 |
O(n^2) |
O(n^2) |
O(n^2) |
O(1) |
稳定 |
堆排序 |
O(nlbn) |
O(nlbn) |
O(nlbn) |
O(1) |
不稳定 |
冒泡排序 |
O(n) |
O(n^2) |
O(n^2) |
O(1) |
稳定 |
快速排序 |
O(nlbn) |
O(nlbn) |
O(n^2) |
O(lbn) |
不稳定 |
归并排序 |
O(nlbn) |
O(nlbn) |
O(nlbn) |
O(n) |
稳定 |
基数排序 |
O(m*n) |
O(m*n) |
O(m*n) |
O(n) |
稳定 |
相关推荐
常见排序算法的实现与性能比较JAVA 问题描述:实现合并排序,插入排序,希尔排序,快速排序,冒泡排序,桶排序算法 实验要求: A. 在随机产生的空间大小分别为 N = 10, 1000,10000,100000 的排序样本(取值为[0...
根据给定文件的信息,本文将对五种内部排序算法(插入排序、冒泡排序、简单选择排序、快速排序以及归并排序)进行性能比较,并简要介绍这些算法的基本原理及其实现过程。以下是对每种排序算法的具体分析: ### 一、...
【排序算法实现与性能分析】 排序算法在计算机科学中占据着至关重要的位置,因为它们直接影响程序执行效率。本文主要探讨了五种常见的排序算法:插入排序、冒泡排序、堆排序、合并排序和快速排序,并进行了性能分析...
各种排序算法性能的比较 在计算机科学中,排序算法是将一组数据按照一定的顺序排列的过程。排序算法的性能直接影响到数据处理和分析的效率。本课程设计中,我们将对八种内部排序算法的性能进行分析和比较。 1. ...
### 常见排序算法的实现与性能比较 #### 实验背景及目的 排序算法是计算机科学中的一个重要组成部分,广泛应用于各种数据处理场景之中。通过本实验,我们旨在实现六种常见的排序算法——合并排序、插入排序、希尔...
【排序算法实现】 排序算法是计算机科学中基础且至关重要的概念,主要分为内部排序和外部排序,本讨论主要关注内部排序。以下将详细介绍并比较五种经典的内部排序算法:插入排序、冒泡排序、堆排序、合并排序和快速...
`Algorithm.java`文件可能包含了这些排序算法的Java实现代码,而`常见排序算法的实现与性能比较.doc`文档则可能详细比较了这些算法的性能和适用场景。`readme.txt`文件可能是对整个项目的简要说明,包括如何运行和...
**排序算法实现**: - **合并排序**:一种分治策略的排序算法,将待排序的序列分成若干子序列,对每个子序列进行排序,最后将子序列合并为有序序列。 - **插入排序**:一种简单直观的排序算法,通过构建有序序列,...
在"设计说明书(排序算法时间性能比较).docx"文档中,应详细记录了每种排序算法的实现细节、性能分析和实验结果。通过实验,可以观察不同排序算法在处理相同数据集时所需的时间,从而了解其时间性能的差异。此外,...
六种排序算法的c语言实现,可选择排序数据量大小,文件在D盘生成
实现合并排序,插入排序,希尔排序,快速排序,冒泡排序,桶排序算法 随机产生空间大小为: N = 10, 1000,10000,100000 的排序样本,取值为[0,1]区间 输出: 1) N=10时,排序结果。 2) N=1000,10000,...
本文将探讨如何使用这两种语言实现几种基本的排序算法:冒泡排序、选择排序,以及两种全比较排序(并行和串行)。 首先,让我们了解一下排序算法。排序是计算机科学中最基础的操作之一,它涉及到将一组数据按照特定...
1.(必做题) 常见排序算法的实现与性能比较 问题描述:实现合并排序,插入排序,希尔排序,快速排序,冒泡排序,桶排序算法 实验要求: A. 在随机产生的空间大小分别为 N = 10, 1000,10000,100000 的...
本主题主要关注各大内部排序算法的性能分析,通过深入理解这些算法的工作原理,我们可以更好地选择适合特定应用场景的排序方法。 1. **冒泡排序**:是最基础的排序算法,通过不断地交换相邻的逆序元素来逐步排序。...
本资源提供了七大经典排序算法的实现程序,包括快速排序、冒泡排序、选择排序、归并排序、插入排序、希尔排序和堆排序。下面将逐一详细介绍这些排序算法及其原理。 1. 快速排序:由C.A.R. Hoare提出,是一种采用...
本文将深入探讨七种经典排序算法,包括它们的工作原理、代码实现、性能测试以及适用场景。** 1. **冒泡排序(Bubble Sort)** 冒泡排序是最基础的排序算法之一,通过不断交换相邻的逆序元素来逐步排序。它的时间...
数据结构各种排序算法实现及比较 在本文中,我们将讨论各种排序算法的实现和比较,包括简单选择排序、直接插入排序、希尔排序、冒泡排序、快速排序等。这些算法都是数据结构课程设计报告中常用的排序算法。 简单...