一、冒泡排序
Java源码
- V1版本
/**
* 冒泡排序基础版本
* @param a 待排序的数组
*/
public static void bubbleSortV1(int[] a){
// 边界条件判断
if(a==null || a.length<2){
return;
}
int j = 0;
int tmp = 0;
for(int i=0; i<a.length-1; i++){
for(j=a.length-1; j>i; j--){
// 一次交换将最小的值移动到最左边
if(a[j]<a[j-1]){
tmp = a[j];
a[j] = a[j-1];
a[j-1] = tmp;
}
}
}
}
- V2版本
/**
* 优化后的冒泡排序,此处可能减少冒泡的轮数
* @param a
*/
public static void bubbleSortV2(int[] a){
// 边界条件判断
if(a==null || a.length<2){
return;
}
int j = 0;
int tmp = 0;
boolean flag = false; // 表示是否会交换
for(int i=0; i<a.length-1; i++){
if(flag){
// 如果标志为为true,表示上次未发生交换,也就是已经是有序的了,这样的话就不需要再进行交换了
break;
}
flag = true;
for(j=a.length-1; j>i; j--){
if(a[j]<a[j-1]){
tmp = a[j];
a[j] = a[j-1];
a[j-1] = tmp;
}
}
}
}
性能总结
版本 | 最坏时间复杂度 | 平均时间复杂度 | 最好时间复杂度 | 空间复杂度 | 是否稳定 | 是否为本地排序 | 是否为外地排序 |
---|---|---|---|---|---|---|---|
V1 | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(1) O ( 1 ) | 是 | 是 | 否 |
V2 | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(n) O ( n ) | O(1) O ( 1 ) | 是 | 是 | 否 |
最坏的情况是输入逆序数组,最要好的应该是输入为排好序的数组
二、选择排序
Java源码
- V1版本
/**
* 选择排序,本地排序,无需额外数组
* @param a
*/
public static void selectionSortLocal(int[] a){
if(a==null || a.length<=1){
return;
}
int minIndex = 0;
int j = 0;
int tmp = 0;
for(int i=0; i<a.length-1; i++){
minIndex = i;
// 一次遍历选取剩余数字的最小值的索引
for(j=i; j<a.length; j++){
if(a[j]<a[minIndex]){
minIndex = j;
}
}
if(minIndex!=i) {
// 交换最小值和第一个的位置
tmp = a[i];
a[i] = a[minIndex];
a[minIndex] = tmp;
}
}
}
性能总结
版本 | 最坏时间复杂度 | 平均时间复杂度 | 最好时间复杂度 | 空间复杂度 | 是否稳定 | 是否为本地排序 | 是否为外地排序 |
---|---|---|---|---|---|---|---|
V1 | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(1) O ( 1 ) | 是 | 是 | 否 |
对于选择排序,因为每次都要去找最小值,做n次比较,所以最好的情况是输入排序好的,可以减少n次交换,但是比较次数不会减少;但他最坏的情况并不是输入逆序数组,因为逆序再一次交换后刚好将最大的交换至末尾了。
三、插入排序
Java源码
- V1版本
/**
* 插入排序,本地排序
* @param a
*/
public static void insertionSortLocal(int[] a){
if(a==null || a.length<2){
return;
}
int j = 0;
int tmp = 0;
for(int i=1; i<a.length; i++){
tmp = a[i];
if(tmp>=a[i-1]){
// 如果该值已经是排好序的数的最大值
continue;
}
for(j=i-1; j>=0; j--){
if(a[j]>tmp){
a[j+1] = a[j];
}else{
// 找到插入位置后即退出
break;
}
}
a[j+1] = tmp;
}
}
- V2版本
/**
* 插入排序,外地排序
* @param a
*/
public static int[] insertionSortOuter(int[] a){
if(a==null || a.length==0){
return null;
}
int[] b = new int[a.length];
b[0] = a[0];
if(a.length==1){
return b;
}
int j = 0;
for(int i=1; i<a.length; i++){
if(a[i]>=b[i-1]){
b[i] = a[i];
}else{
for(j=i-1; j>=0; j--){
if(b[j]>a[i]){
b[j+1] = b[j];
}else{
break;
}
}
b[j+1] = a[i];
}
}
return b;
}
性能总结
版本 | 最坏时间复杂度 | 平均时间复杂度 | 最好时间复杂度 | 空间复杂度 | 是否稳定 | 是否为本地排序 | 是否为外地排序 |
---|---|---|---|---|---|---|---|
V1 | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(n) O ( n ) | O(1) O ( 1 ) | 是 | 是 | 否 |
V2 | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(n) O ( n ) | O(n) O ( n ) | 是 | 否 | 是 |
最坏情况是输入逆序数字,那么每次查找位置时都需要比较和移动n次值;最好情况是输入排序好的数组,此时每次要插入的值都是最大值,直接与已排好序的数字的最大值比较即可获得位置,只需要遍历一次即可。
四、希尔排序
希尔排序的时间复杂度分析好像比较复杂,博主这里也是摘自网上资料的分析。所以欢迎各位在底下讨论。。。
Java源码
- V1版本
public void localSort(Item[] items) {
// 边界条件检测
if(items==null || items.length<2){
return;
}
int k = items.length/2; // 初始增量为元素总数的一半
while (k>=1){
for(int i=0; i<k; i++){
for(int j=i+k; j<items.length; j+=k){
Item key = items[j];
int m = j-k;
if(key.compareTo(items[m])<0){
for(; m>=i; m-=k){
if(items[m].compareTo(key)>=0){
items[m+k] = items[m];
}else{
break;
}
}
items[m+k] = key;
}
}
}
k--;
}
}
- V2版本
public void localSort(Item[] items) {
// 边界条件检测
if(items==null || items.length<2){
return;
}
int k = items.length/2; // 初始增量为元素总数的一半
while (k>=1){
for(int i=0; i<k; i++){
for(int j=i+k; j<items.length; j+=k){
Item key = items[j];
int m = j-k;
if(key.compareTo(items[m])<0){
for(; m>=i; m-=k){
if(items[m].compareTo(key)>=0){
items[m+k] = items[m];
}else{
break;
}
}
items[m+k] = key;
}
}
}
k/=2;
}
}
- V3版本
public void localSort(Item[] items) {
// 边界条件检测
if(items==null || items.length<2){
return;
}
int n = (int)Math.floor(Math.log(items.length+1));
int k = (int)Math.pow(2, n)-1; // 初始增量为元素总数的一半
while (k>=1){
for(int i=0; i<k; i++){
for(int j=i+k; j<items.length; j+=k){
Item key = items[j];
int m = j-k;
if(key.compareTo(items[m])<0){
for(; m>=i; m-=k){
if(items[m].compareTo(key)>=0){
items[m+k] = items[m];
}else{
break;
}
}
items[m+k] = key;
}
}
}
n--;
k = (int)Math.pow(2, n)-1;
}
}
性能分析
版本 | 最坏时间复杂度 | 平均时间复杂度 | 最好时间复杂度 | 空间复杂度 | 是否稳定 | 是否为本地排序 | 是否为外地排序 |
---|---|---|---|---|---|---|---|
V1 | − − | − − | 是 | 是 | 否 | ||
V2 | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(n2) O ( n 2 ) | O(1) O ( 1 ) | 是 | 是 | 否 |
V3 | O(n32) O ( n 3 2 ) | O(n32) O ( n 3 2 ) | O(n32) O ( n 3 2 ) | O(1) O ( 1 ) | 是 | 是 | 否 |
希尔排序是插入排序的一种改进,因为插入排序每次插入时所有元素向后最多移动一位,这样的话加入一个元素距离它排序后的位置较远的话需要移动很多次,而希尔排序过程中每次移动的步幅由大变小,对于一些数据可能更快的移动到正确位置。希尔排序是第一批打破 O(n2) O ( n 2 ) 的排序方式,它的时间复杂度与增量序列的选取有关:如上第二个版本的增量序列为 {gap,gap2,gap4,...,1} { g a p , g a p 2 , g a p 4 , . . . , 1 } ,其中 gap=数组长度2 g a p = 数 组 长 度 2 ,该序列称为希尔增量,它的时间复杂度为 O(n2) O ( n 2 ) ,而第三个版本的增量序列为 1,3,...,2k−1 1 , 3 , . . . , 2 k − 1 ,它的时间复杂度为 O(n32) O ( n 3 2 ) 。希尔排序的时间复杂度下限为 nlog22n n l o g 2 2 n 。它在中小规模表现良好,但是对于大规模数据不是一个好选择。另外,它在最好和最坏的情况下执行效率差不多,但是快排在最坏情况下却会性能下降好多。