排序(一)
1.如何分析一个排序算法?
- 排序算法的执行效率。
- 最好、最坏、平均时间复杂度。
- 时间复杂度的系数、低阶、常数。
- 比较次数和交换的次数。
- 排序算法的内存消耗(空间复杂度)。
- 其实就是此排序算分的空间复杂度。
- 原地排序。空间复杂度为O(1)的排序。
- 排序算法的稳定性。
- 排序的数据中存在相同的元素,排序的过程中不会改变这些用元素的位置,就说这个排序算法是稳定的。
有序度
一个数组中有序元素对的个数。
有序元素对: a[i]<a[j],且i<j 就是一个有序对。
对于一个数组他的数据为。9,8,7,6,5,他的有序度就是0,相反如果是5,6,7,8,9,有序度就是n*(n-1)/2,也就是15。完全有序的数组的有序度就叫做满有序度。
逆序度
逆序度的概念刚好和有序度相反。
逆序元素对: a[i]<a[j],且i>j,就是一个逆序对。
逆序度 = 满有序度 - 有序度
我们看出当一个数组的有序度达到满有序度的时候,这个数组就是从小到大排列好的数组。
2.冒泡排序(Bubble Sort)
冒泡排序就是每次比较相邻两个元素的大小,满足条件就不交换,不满足就交换,依次比较完n个元素的大小,每次找出来一个元素,直到找出来所有的n个元素为止。
代码:
/**
* BubbleSort
* @param arr
* @param n 数组的大小
*
* 最好 O(n) 最坏(O(n2)) 平均(O( n2)) 空间复杂度 O(1)
* 原地排序 稳定的排序算法
*/
public static void bubbleSort(int[] arr,int n){
if(n<=1)return;
for(int i=0;i<n;i++){
//提前结束循环的标记
boolean flag = false;
int temp;
for(int j=0;j<n-i-1;j++){
if(arr[j]>arr[j+1]){//交换数据
temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]= temp;
flag=true; //true 表示有数据交换
}
}
if(!false)break; //没有数据交换了就跳出循环,如果不挑出循环剩下的操作都是无用的,浪费资源
}
}
冒泡排序性能分析
1.排序算法的执行效率。
- 最好时间复杂度
- 如果我们要排序的数据已经是有序的,可以很容易的看出最好时间复杂度为O(n)。
- 最坏时间复杂度
- 如果要排序的数据完全无序的,时间复杂度为O(n^2)。
- 平均时间复杂度
- 根据前边的有序度、逆序度的分析,最坏情况的逆序度为n*(n-1)/2,最好情况的逆序度为0,平均情况的就取一个中间值n*(n-1)/4,平均情况下需要n*(n-1)/4次交换,而且比较操作是要比交换次数多的,而复杂度的上限就是O(n2),所以平均情况下的时间复杂度也是O(n2)。
2.是否为原地排序。
上边的代码可以看出,冒泡排序的操作并没有用到多余的空间,所以是一个原地排序。
3.是否为稳定的排序算法。
我们代码可以判断,当两个元素的大小相等的时候,我们不做交换,所以冒泡排序是一个稳定的排序算法。
3.插入排序(Insertion Sort)
插入排序将数组中的数据分为两个区间,已排序区间和未排序区间,初始已排序区间就是第一个元素,然后依次拿未排序区间的数据和已经排序的区间比较找到合适的插入位置。直到未排序区间为空,排序算法结束。
/**
* InsertionSort
* @param arr
* @param n
* 将为排序区间的值 插入到已排序区间
* 最好 O(n) 最坏(O(n2)) 平均(O( n2)) 空间复杂度 O(1)
* 原地排序 稳定的排序算法
*
*
*/
public static void insertionSort(int[] arr,int n){
if(n<=1)return;
for(int i=1;i<n;++i){
int value = arr[i];
int j = i-1;
for(;j>=0;--j){
if(value<arr[j]){
arr[j+1]=arr[j];
}else{
break;
}
}
arr[j+1]=value;
}
}
插入排序性能分析
- 原地排序
- 稳定的排序算法
- 平均时间复杂度为O(n^2)
4.选择排序
选择排序类似与插入排序,也是分为已排序区间和未排序区间,但是选择排序是每次从未排序区间之中找到最小的元素放入到已排序区间的末尾。
/**
* SelectionSort
* @param arr
* @param n
*
* 跟插入排序一样也是一个 有序 和一个无序序列 只不过 每次要在无序序列找到最小值插入 每次找到最小值的索引
*
* 最好 O(n) 最坏(O(n2)) 平均(O( n2)) 空间复杂度 O(1)
* 原地排序 但是 不是稳定的排序算法
*
*/
public static void selectionSort(int[] arr,int n){
if(n<=n)return;
for(int i=0;i<n;i++){
int minIndex = i;
for(int j=i+1;j<n;++j){
if(arr[j]<arr[minIndex]){
minIndex = arr[j];
}
}
if(i==minIndex)
continue;
int temp = arr[i];
arr[i]= arr[minIndex];
arr[minIndex]=temp;
}
}
选择排序性能分析
- 不稳定的排序,因为选择排序每次找出最小元素的时候,会和前边的数据进行交换。
- 原地排序
- 平均时间复杂度O(n^2)
三种排序时间复杂度对比
-排序算法- | -平均时间复杂度- | -是否为原地排序- | -是否稳定- |
---|---|---|---|
冒泡排序 | O(n^2) | 是 | 是 |
插入排序 | O(n^2) | 是 | 是 |
选择排序 | O(n^2) | 否 | 是 |
因为选择排序不是原地排序,所以他的性能不如另外的两个排序,就不多说了。
接下来比较一下插入排序和冒泡排序,为什么各种性能都一样的情况下插入排序使用比较多。
其实根据插入排序的代码和冒泡排序的代码就可以看出插入排序的交换次数要比冒泡排序少,插入排序需要交换一次,冒泡排序却需要三次。你可以自己写代码试一下相同的数据进行排序插入排序的效率要比冒泡的效率高的多。
有兴趣的朋友可以看一看我的下篇博客时间复杂度为O(nlogn)的排序算法 归并排序与快速排序