目录
一、排序算法的分类
在介绍排序算法之前,我们先根据时间复杂度,罗列主流的排序算法,作为一个了解。大致分为以下三类:
1.时间复杂度为O(n^2)的排序算法
- 冒泡排序
- 选择排序
- 插入排序
- 希尔排序(希尔排序比较特殊,性能略胜于O(n^2),但比不上O(nlogn))
2.时间复杂度为O(nlogn)的排序算法
- 快速排序
- 归并排序
- 堆排序
3.时间复杂度为线性的排序算法
- 计数排序
- 桶排序
- 基数排序
排序算法根据稳定性,又可分为稳定排序和不稳定排序。稳定排序是值相同的元素在排序后仍然保持着排序前的顺序,不稳定排序则是相反。
二、冒泡排序
思想:相邻元素两两比较,当一个元素大于右侧相邻元素时,交换它们的位置(右侧为最大元素位置);小于等于右侧元素时,位置不变(由此可见冒泡排序是稳定排序)。
冒泡排序每一轮都要比较剩余的所有元素,总共遍历n-1轮(最后还剩一个元素时不用比较),时间复杂度为O(n^2)。
1.原始的冒泡排序
最原始的冒泡排序通过上述的描述,我们可以用双层循环来实现,第一层循环控制遍历的次数,第二层循环进行两两比较。代码实现非常简单,如下所示。
/*
原始冒泡排序
前后两个元素进行比较,大的元素往后移
冒泡排序是稳定的排序
*/
public static void sort(int[] array){
int temp;
for(int j = 0; j < array.length - 1; j++)//控制进行几轮比较
for(int i = 0; i < array.length-1-j; i++){//控制每一轮的比较
if(array[i] > array[i+1]){
temp = array[i+1];
array[i+1] = array[i];
array[i] = temp;
}
}
}
2.改进一步的冒泡排序
若我们在某一轮比较之后,序列已经是有序的,那么就不用继续进行排序。基于此思想,我们设置一个布尔值(isSorted),在每一轮遍历之前将其设为true,若某轮比较发生了元素的交换,则将该值设置为false。最后在未发生交换元素的那轮,该值为true,我们可以跳出循环,得到的便是排好序的序列。
/*
冒泡排序第二版改进(一轮有序后不必再排序)
当有一轮排序后若已经有序,则剩下的几轮排序就不必执行了
*/
public static void sort1(int[] array){
int temp;
boolean isSorted;
for(int j = 0; j < array.length - 1; j++)//控制进行几轮比较
{
isSorted = true;//用isSorted假设每次序列都是有序的
for(int i = 0; i < array.length-1-j; i++){//控制每一轮的比较
if(array[i] > array[i+1]){
temp = array[i+1];
array[i+1] = array[i];
array[i] = temp;
isSorted = false;//若有元素交换,则是无序的还需进行下一趟遍历
}
}
if(isSorted)
break;
}
}
3.更进一步的冒泡排序
适用于序列中部分序列已经为有序的情况(如:3,4,2,1,5,6,7,8,前半部分无序,后半部分有序),我们无需每轮都比较它们,因此可以设置一个无序的边界值,记录交换的元素的位置。(个人觉得该方法不是非常实用(但书本上是这样介绍的),效果其实和在一次交换元素后就跳出内层循环一样)
/*
冒泡排序第三版改进(记录最后一次元素交换的位置,该位置为无序边界区)
该方法相当于交换一次之后就退出当前循环,可以减少交换次数
*/
public static void sort2(int[] array){
for(int j = 0; j < array.length-1; j++)//控制进行几轮比较
{
boolean isSorted = true;//用isSorted假设每次序列都是有序的
int sortBorder = array.length - 1;
for(int i = 0; i < sortBorder; i++){//控制每一轮的比较
int temp = 0;
if(array[i] > array[i+1]){
temp = array[i+1];
array[i+1] = array[i];
array[i] = temp;
isSorted = false;//若有元素交换,则是无序的还需进行下一趟遍历
sortBorder = i;//把无序数列的边界更新为最后一次交换元素的位置
}
}
if(isSorted)
break;
}
}
三、鸡尾酒排序
首先声明,鸡尾酒排序从本质上来说也是冒泡排序。
鸡尾酒排序和原始的冒泡排序的区别是,该排序的比较和交换元素的顺序是双向的(即第一轮从左到右两两比较比较,第二轮从右到左比较,第三轮又从左到右比较......)。
如,我们给出要排序的序列为{2,3,4,5,6,7,8,1},若按照正常的冒泡排序,则每次排序都需要从左到右比较,将最后一个1放到最前面需要进行7轮比较,但若我们采用上面鸡尾酒排序的思想,则只需进行3轮排序:第一轮从左到右排序为{2,3,4,5,6,7,1,8},第二轮从右到左排序为{1,2,3,4,5,6,7,8},在两轮之后其实已经有序,但我们还要再进行一轮判断。因此总共只需3次即可,大大减少了比较次数。
鸡尾酒排序中,我们用外循环中套两个小循环(分别为奇数次和偶数次)来实现。
//鸡尾酒排序,从左到右比较和从右到左比较轮流进行,适用于部分有序的情况如2,3,4,5,6,7,8,1
public static void sort3(int[] array){
int temp = 0;
for(int i = 0; i < array.length/2; i++){
boolean isSorted = true;
//奇数轮,从左到右比较和交换
for(int j = i; j < array.length - i - 1; j++){
if(array[j] > array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSorted = false;
}
}
if(isSorted)
break;
isSorted = true;
//偶数轮,从右到左比较和交换
for(int j = array.length-i-1; j > i; j--){
if(array[j] < array[j-1]){
temp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
isSorted = false;
}
}
if(isSorted)
break;
}
}
四、完整测试代码
import java.util.Arrays;
public class MySort {
/*
原始冒泡排序
前后两个元素进行比较,大的元素往后移
冒泡排序是稳定的排序
*/
public static void sort(int[] array){
int temp;
for(int j = 0; j < array.length - 1; j++)//控制进行几轮比较
for(int i = 0; i < array.length-1-j; i++){//控制每一轮的比较
if(array[i] > array[i+1]){
temp = array[i+1];
array[i+1] = array[i];
array[i] = temp;
}
}
}
/*
冒泡排序第二版改进(一轮有序后不必再排序)
当有一轮排序后若已经有序,则剩下的几轮排序就不必执行了
*/
public static void sort1(int[] array){
int temp;
boolean isSorted;
for(int j = 0; j < array.length - 1; j++)//控制进行几轮比较
{
isSorted = true;//用isSorted假设每次序列都是有序的
for(int i = 0; i < array.length-1-j; i++){//控制每一轮的比较
if(array[i] > array[i+1]){
temp = array[i+1];
array[i+1] = array[i];
array[i] = temp;
isSorted = false;//若有元素交换,则是无序的还需进行下一趟遍历
}
}
if(isSorted)
break;
}
}
/*
冒泡排序第三版改进(记录最后一次元素交换的位置,该位置为无序边界区)
该方法相当于交换一次之后就退出当前循环,可以减少交换次数
*/
public static void sort2(int[] array){
for(int j = 0; j < array.length-1; j++)//控制进行几轮比较
{
boolean isSorted = true;//用isSorted假设每次序列都是有序的
int sortBorder = array.length - 1;
for(int i = 0; i < sortBorder; i++){//控制每一轮的比较
int temp = 0;
if(array[i] > array[i+1]){
temp = array[i+1];
array[i+1] = array[i];
array[i] = temp;
isSorted = false;//若有元素交换,则是无序的还需进行下一趟遍历
sortBorder = i;//把无序数列的边界更新为最后一次交换元素的位置
}
}
if(isSorted)
break;
}
}
//鸡尾酒排序,从左到右比较和从右到左比较轮流进行,适用于部分有序的情况如2,3,4,5,6,7,8,1
public static void sort3(int[] array){
int temp = 0;
for(int i = 0; i < array.length/2; i++){
boolean isSorted = true;
//奇数轮,从左到右比较和交换
for(int j = i; j < array.length - i - 1; j++){
if(array[j] > array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
isSorted = false;
}
}
if(isSorted)
break;
isSorted = true;
//偶数轮,从右到左比较和交换
for(int j = array.length-i-1; j > i; j--){
if(array[j] < array[j-1]){
temp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
isSorted = false;
}
}
if(isSorted)
break;
}
}
public static void main(String[] args) {
int[] array = new int[]{5,8,6,3,9,2,1,7};
sort1(array);
System.out.println(Arrays.toString(array));
int[] array1 = new int[]{3,4,2,1,5,6,7,8};
sort2(array1);
System.out.println(Arrays.toString(array1));
int[] array2 = new int[]{2,3,4,5,6,7,8,1};
sort3(array2);
System.out.println(Arrays.toString(array2));
}
}