内部排序:是指在排序期间元素全部存放在内存中的排序
外部排序:是指在排序期间元素无法全部同时存在内存中,必须在排序的过程中根据要求不断地在内、外存之间移动的排序
排序 | 空间复杂度 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 稳定性 |
---|---|---|---|---|---|
直接插入排序 | 1 | n | n² | n² | 稳定 |
折半插入排序 | 1 | n² | n² | 稳定 | |
希尔排序 | 1 | n² | n² | 不稳定 | |
冒泡排序 | 1 | n | n² | n² | 稳定 |
快速排序 | log₂n n n | nlog₂n | n² | n² | 不稳定 |
简单选择排序 | 1 | n² | n² | n² | 不稳定 |
堆排序 | 1 | nlog₂n | nlog₂n | nlog₂n | 不稳定 |
2-路归并排序 | n | nlog₂n | nlog₂n | nlog₂n | 稳定 |
基数排序 | r | d*(n+r) | d*(n+r) | d*(n+r) | 稳定 |
插入排序是一种简单直观的排序方法,其基本思想在于每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。
直接插入排序基本思想是每一步将一个待排序的记录,从右到左依次比较插入到前面已经排好序的有序序列中去,直到插完所有元素为止。
直接插入排序算法的性能分析如下:
O(1)
在最好的情况下,表中元素已经有序,此时每插入一个元素,都只需比较一次而不用移动元素,因而时间复杂度为O(n)
。
平均情况下考虑待排元素是随机的,此时可以取上述最好与最坏情况下的平均值作为平均情况下的时间复杂度,总的比较次数和总的移动次数均为n²/4。由此,直接插入排序算法的最坏和平均的时间复杂度为O(n²)
public class InsertSort {
public void insertSort(int[] array, int first, int last) {
int temp, i, j;
for (i = first + 1; i <= last - 1; i++) {// 默认以第一个数为有序序列,后面的数为要插入的数。
temp = array[i];
j = i - 1;
while (j >= first && array[j] > temp) {// 从后进行搜索如果搜索到的数小于temp则该数后移继续搜索,直到搜索到小于或等于temp的数即可
array[j + 1] = array[j];
j--;
}
array[j + 1] = temp;
// 打印每次排序结果
for (int m = 0; m <= array.length - 1; m++) {
System.out.print(array[m] + "\t");
}
System.out.println();
}
}
public static void main(String[] args) {
InsertSort insertSort = new InsertSort();
int[] array = { 5, 69, 12, 3, 56, 789, 2, 5648, 23 };
insertSort.insertSort(array, 0, array.length);// 注意此处是0-9而不是0-8
for (int i = 0; i <= array.length - 1; i++) {
System.out.print(array[i] + "\t");
}
}
}
public void insertSort(int[] arr) {
//一开始只能保证第一个元素是已有序的序列。所以从第二个元素开始,逐个插入到已有序序列
for(int i=1;i<arr.length;i++) {
int j=i;
while(j>0 && arr[j]<arr[j-1]) { //这种情况就说明顺序开始出现问题,需要通过交换位置来调整
swap(arr,j-1,j);
j--;//一旦需要调整位置,j每次都要变化,和前面的已有序元素都要进行比较,保证当前遍历的元素在正确的位置
}
}
}
注意:大部分排序算法都仅适用于顺序存储的线性表
折半插入排序仅仅是减少了比较元素的次数,该比较的次数与待排序的初始状态无关,仅取决于表中的元素个数n,而元素的移动次数没有改变,它依赖于待排序表的初始状态。
折半排序的性能分析:
O(1)
O(n²)
希尔排序又称“缩小增量排序”,该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某 个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插 入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
希尔排序是插入排序的改进版。主要改进思路是减少插入排序中数据的移动次数,设置步长,在初始数组较大时取较大步长,通常初始步长为待排数组长度1/2,此时只有两个元素比较,交换一次,此时数组为部分有序数组;之后步长依次减半直至步长为1,即为插入排序,此时数组已接近有序,所以插入元素时数据移动次数会相对较少,效率得到提高。
原始数组,元素颜色相同的为一组:
初始增量gap = length / 2 = 5,也就是说整个数组分成5组:[8, 3] [9, 5] [1, 4] [7, 6] [2, 0]
对这5组进行插入排序,结果如下,之后缩小增量gap = 5 / 2 = 2,数组分成了2组:[3, 1, 0, 9, 7] [5, 6, 8, 4, 2]
再对上面两个数组进行插入排序,结果如下,再次缩小增量gap = 2 / 2 = 1,整个数组只有一组数据了[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
之后只需对这个数组进行微调,无需进行大量的移动操作,即可完成整个数组的排序
希尔排序算法性能分析:
O(1)
O(n²)
void shellsort(int a[], int n) {
int gap = n / 2;
int i, j;
int tmp;
for(gap = n / 2; gap > 0; gap /= 2) { //增量起始值为n/2,之后逐次减半
//从第gap个元素,逐个对其所在组进行直接插入排序操作
for(i = gap; i < n; i++) {
tmp=a[i];
j = i;
if(a[j] < a[j - gap]) {
while(j - gap >= 0 && tmp < a[j - gap]) {
//移动法
a[j] = a[j - gap];
j = j - gap;
}
a[j] = temp;
}
}
}
}
所谓交换,就是根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。
每趟冒泡的结果把序列中的最小元素放到序列的最终位置
public void bubbleSort(int[] arr) {
// 冒泡趟数--首先确定最大的元素放到了最后一个位置.一共要确定arr.length-1个元素的位置,所以一共冒泡了arr.length-1趟
for( int i=0;i<arr.length-1;i++) {
for(int j=0; j<arr.length-1-i; j++) { // 一趟冒泡
if(arr[j]>arr[j+1]) { //j和j+1比较,所以j最多只能到倒数第二个元素,即j<arr.length-1
swap(arr,j,j+1);
}
}
}
}
//交换数组中两个位置上的元素
public void swap(int[] arr,int i,int j) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
冒泡排序的性能分析:
O(1)
O(n)
;当初始序列为逆序时,需要进行n-1趟排序,第i趟排序要进行n-i词关键字的比较,而且每次比较都必须移动元素3次来交换元素位置。这种情况下比较次数=n(n-1)/2,移动次数=3n(n-1)/2。从而最坏情况下时间复杂度为O(n²)
,其平均时间复杂度也为O(n²)
。冒泡排序中所产生的的有序子序列一定是全局有序的(不同于直接插入排序),也就是说有序子序列中的所有元素的关键字一定小于或大于无序子序列中所有元素关键字,这样每一趟排序都会将一个元素放置到其最终的位置上。
快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然 后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
快速排序算法的性能分析:
O(n)
,平均情况下为O(log₂n)
O(n²)
O(nlog₂n)
快速排序是所有内部排序算法中平均性能最优的排序算法
选择排序的基本思想是:每一趟(例如第i趟)在后边n-i+1个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到n-1趟做完,待排序元素只剩下1个,就不用再选了。
简单选择排序的思想是:假设排序表为L[1 ....... n],第i趟排序即从L[i .... n]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可以使得整个排序表有序。
简单选择排序的性能分析:
O(1)
O(n²)
堆排序是一种树形选择排序方法,它的特点是:在排序过程中,将L[1 ..... n]看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内阻关系,在当前无序区中选择关键字最大(或最小)的元素。
堆的定义如下:n个关键字序列L[1 ..... n]称为堆,当且仅当该序列满足:
向下调整的时间与树的高度有关,为O(h)。建堆过程中每次向下调整时,大部分结点的高度都较小。因此,可以证明在元素个数为n的序列上建堆,其时间复杂度为O(n),这说明可以在一个线性时间内,将一个无序数据建成一个大顶堆。
应用堆这种数据结构进行排序的思路很简单,首先将存放在L[1......n]中的n个元素建成初始堆,由于堆本身的特点(以大顶堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆低元素送入堆顶,此时根结点以不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶的元素,如此重复,直到堆中剩下一个元素为止。
同时堆也支持删除和插入操作,由于堆顶元素或为最大值或为最小值,删除堆顶元素时,先将堆的最后一个元素与堆顶元素交换,由于此时堆的性质被破坏,需对此时的根结点进行向下调整操作。对堆进行插入操作时,先将新结点放在堆的末端,再对这个新结点执行向上调整操作。
堆算法的性能分析:
O(1)
O(nlog₂n)
“归并”的含义是将两个或者两个以上的有序表组成一个新的有序表。
2-路归并排序算法性能分析:
O(n)
O(nlog₂n)
注意:一般而言,对于N个元素进行k-路归并排序时,排序的趟数m满足k^m=N,从而m=logk^N,又考虑到m为整数,所以m=⌈logk^N⌉。这个和前面的2-路归并是一致的。
基数排序是一种很特别的排序算法,它不是基于比较进行排序的,而是采用多关键字排序思想,借助“分配”和“收集”两种操作对单逻辑关键字进行排序。基数排序又分为最高位优先(MSD)排序和最低位优先(LSD)排序。
如下为基数排序作用于一个由7个3位数组成的表的过程。最左端为输入端,其余各列表示了对各个不断递增的有效位连续排序后的情况,阴影表示当前正被排序的数位。
基数排序算法的性能分析:
d 为位数,r 为基数,n 为原数组个数
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。