java详解七大排序算法

6、排序算法

  • 内部排序:指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。
  • 外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序。
    java详解七大排序算法_第1张图片

1 时间复杂度

  1. 时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
  2. 时间复杂度:一般情况下,算法中的基本操作语句的重复执行次数是问题规模n 的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n 趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度。
  • T(n) 不同,但时间复杂度可能相同。如:T(n)=n²+7n+6 与T(n)=3n²+2n+2 它们的T(n) 不同,但时间复杂度相同,都为O(n²)。
  • 计算时间复杂度的方法:
    • 用常数1 代替运行时间中的所有加法常数T(n)=n²+7n+6 => T(n)=n²+7n+1
    • 修改后的运行次数函数中,只保留最高阶项T(n)=n²+7n+1 => T(n) = n²
    • 去除最高阶项的系数T(n) = n² => T(n) = n² => O(n²)
  • 常见时间复杂度:
    1. 常数阶O(1)
    2. 对数阶O(log2n)
    3. 线性阶O(n)
    4. 线性对数阶O(nlog2n)
    5. 平方阶O(n^2)
    6. 立方阶O(n^3)
    7. k 次方阶O(n^k)
    8. 指数阶O(2^n)
  • Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)< Ο(n^k) <Ο(2^n)
  • 平均时间复杂度和最坏时间复杂度
    1. 平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
    2. 最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。

2 冒泡排序(Bubble Sort)

  • 这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
  • 它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
  • 过程:
    1. 设立两个指针,指向开始的两个元素,如果两者逆序,就交换两者位置。
    2. 两个指针同时向后移动,如果两个相邻元素逆序,就交换位置。
    3. 通过一遍循环,我们就可以确认固定最后一个元素。
    4. 重复上述步骤,直至所有元素被固定。
  • 时间复杂度:
    1. 最坏时间复杂度(数组完全逆序):O(n^2)
    2. 最好时间复杂度(数组完全顺序):O(n)
    3. 平均时间复杂度:O(n^2)
  • 稳定性:稳定。因为两个相等元素不会交换位置。
  • 优化:
    1. 我们不必真的使用两个指针,因为指针是相邻的,后者位置为前者+1
    2. 如果在一次内部循环中,没有出现任何一次交换,就证明数组已经是顺序的,不需要继续交换,可以直接跳出外层循环。
  • 代码实现:
//冒泡排序
public class BubbleSortTest {
    public static void main(String[] args) {
//        int[] array = {1, 5, 8, 7, 6, 2};
//        int[] array = {1, 2, 3, 4, 5, 6, 7, 9};
        int[] array = {6, 5, 4, 2, 1, 0};
        int[] sortedArray = bubbleSort(array);
        for (int i = 0; i < array.length; i++) {
            System.out.print(sortedArray[i] + " ");
        }
    }

    private static int[] bubbleSort(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {  //array.length - 1是因为当后面n-1个元素固定后第一个元素自然就固定好了
            boolean flag = true;//判断当前循环是否出现过交换
            for (int j = 0; j < array.length - i - 1; j++) { //array.length - i - 1是因为最后i个元素已经固定好,其次我们比较的是两个数,所以最后还得-1
                if (array[j] > array[j + 1]) {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;
                    flag=false;
                }
            }
            if (flag == true) {
                break;//如果没有出现过交换,代表整个数组已经是顺序的,直接跳出循环
            }
        }
        return array;
    }
}

3 选择排序(Selection Sort)

  • 选择排序的原理就是每次循环都找出最小(大)的数放在最前面。
  • 流程:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。
  • 时间复杂度:O(n^2)
  • 稳定性:不稳定。比如序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中两个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。
  • 选择排序相较于冒泡排序而言,选择排序的判断次数是固定的,而且必须遍历完所有可能性,而冒泡排序可以检测是否顺序中断排序,但选择排序的交换次数相比冒泡排序可以减少很多,而且交换所需要消耗的时间是比判断的时间是更多的。
//选择排序
public class SelectionSortTest {
    public static void main(String[] args) {
        int[] array = {1, 5, 8, 7, 6, 2};
//        int[] array = {1, 2, 3, 4, 5, 6, 7, 9};
//        int[] array = {6, 5, 4, 2, 1, 0};
        int[] sortedArray = selectionSort(array);
        for (int i = 0; i < array.length; i++) {
            System.out.print(sortedArray[i] + " ");
        }
    }

    private static int[] selectionSort(int[] array) {
        int min, temp, key ;//min存储当前循环最小值,key存储最小值的下标
        for (int i = 0; i < array.length-1; i++) {
            min = array[i];
            key=i;
            for (int j = i + 1; j < array.length; j++) {
                if (array[j] < min) {
                    min = array[j];
                    key = j;
                }
            }
            if(key!=i){
                temp = array[i];
                array[i] = array[key];
                array[key] = temp;
            }
        }
        return array;
    }
}

4 插入排序(Insertion Sort)

  • 插入排序(Insertion Sorting)的基本思想是:把n 个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
  • 示意图:

  • 稳定性:稳定。插入排序如果碰到相等元素不会插入到相等元素的前面。
  • 时间复杂度:O(n^2)
//插入排序
public class InsertionSortTest {
    public static void main(String[] args) {
//        int[] array = {1, 5, 8, 7, 6, 2};
//        int[] array = {1, 2, 3, 4, 5, 6, 7, 9};
        int[] array = {6, 5, 4, 2, 1, 0};
        int[] sortedArray = InsertionSort(array);
        for (int i = 0; i < array.length; i++) {
            System.out.print(sortedArray[i] + " ");
        }
    }

    private static int[] InsertionSort(int[] array) {
        for (int i = 1; i < array.length; i++) {
            if (array[i] < array[i - 1]) {//如果第i个大于第i-1个,表明这个数字顺序是不需要调整的
                int temp=array[i];//保存当前第i个位置的值
                int j;
                for (j = i; j >0&&array[j-1]>temp ; j--) {
                    array[j]=array[j-1];//将所有大于temp的值向后移
                }
                array[j]=temp;//插入temp
            }
        }
        return array;
    }
}

5 希尔排序(Shell’s Sort)

  • 希尔排序是希尔(Donald Shell)于1959 年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
  • 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1 时,整个文件恰被分成一组,算法便终止。

java详解七大排序算法_第2张图片
java详解七大排序算法_第3张图片

  • 希尔排序是基于插入排序的一种算法, 在此算法基础之上增加了一个新的特性,提高了效率。希尔排序的时间的时间复杂度为O(n^3/2)希尔排序时间复杂度的下界是n*log2n。希尔排序没有快速排序算法快 O(n(logn)),因此中等大小规模表现良好,对规模非常大的数据排序不是最优选择。
public class ShellsSortTest {
    public static void main(String[] args) {
//        int[] array = {1, 5, 5, 7, 6, 2};
//        int[] array = {1, 2, 3, 4, 5, 6, 7, 9};
//        int[] array={8,9,1,7,2,3,5,4,6,0};
        int[] array = {6, 5, 4, 2, 1, 0};
        int[] sortedArray = ShellsSort(array);
        for (int i = 0; i < array.length; i++) {
            System.out.print(sortedArray[i] + " ");
        }
    }

    public static int[] ShellsSort(int[] array){
        int temp;
        // 增量gap, 并逐步的缩小增量
        for (int gap=array.length/2; gap >0 ; gap/=2) {
            // 从第gap 个元素,逐个对其所在的组进行直接插入排序
            for (int i = gap; i < array.length; i++) {
                int j=i;
                temp=array[i];
                if(temp<array[j-gap]){
                    while (j-gap>=0&&array[j-gap]>temp){
                        array[j]=array[j-gap];
                        j-=gap;
                    }
                }
                array[j]=temp;
            }
        }
        return array;
    }
}

6 快速排序(Quicksort)

  • 快速排序(Quicksort)是对冒泡排序的一种改进。
  • 基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
  • 基本流程:
    1. 首先创建left、right两个指针,和一个基准数(不同方法选择的基准数位置不同)
    2. 将比基准数小的放在基准数左边,比基准数大的放在基准数右边(不要求顺序)
    3. 通过递归向左右执行1、2两步,直到left和right刚进入一层递归就已经相等,完成排序。

挖坑法划分:https://blog.csdn.net/morewindows/article/details/6684558(参考博客)

//快速排序
public class QuickSortTest {
    public static void main(String[] args) {
        int[] array = {1, 5, 5,7, 6, 2};
//        int[] array = {8, 2, 3, 4, 5, 6, 7, 9};
//        int[] array = {6, 5, 4, 2, 1, 0};
//        int[] array = {5, 6, 3, 1, 8, 7, 99};
        int[] sortedArray = quickSort(array, 0, array.length - 1);
        for (int i = 0; i < array.length; i++) {
            System.out.print(sortedArray[i] + " ");
        }
    }

    /*
        挖坑法进行一次划分:
        1、通过将基准值作为坑,先向右找到小于基准值的数,将这个数填入坑中,而将这个数原来位置作为新坑
        2、然后,再向左找到小于基准值的数,将这个数填入新坑中,而将这个数原来位置作为新坑
        3、不断重复1、2两步,直至left==right,将基准值填入最后的坑
     */
    private static int[] quickSort(int[] array, int l, int r) {
        if (l < r) {
            int left = l;//左指针确保left左边的数都小于基准值
            int right = r;//右指针确保right右边的数都大于基准值
            int temp = array[left];//保存基准值,并挖坑
            while (left < right) {
                //从右向左找到第一个小于基准数的数
                while (left < right && array[right] >= temp) {
                    right--;
                }
                if (left < right) {
                    array[left++] = array[right];//填坑,并将array[right]作为新的坑
                }
                //从左向右找到第一个大于基准数的数
                while (left < right && array[left] <= temp) {
                    left++;
                }
                if (left < right) {
                    array[right--] = array[left];//填坑,并将array[left]作为新的坑
                }
            }
            array[right] = temp;//当left和right相等时,填入基准值
            quickSort(array, l, right - 1);//向左重复
            quickSort(array, left + 1, r);//向右重复
        }
        return array;
    }
}

交换法:逻辑更简单

public class QuickSortTest2 {

    public static void main(String[] args) {
        int[] arr = {1, 3, 5,7, 6, 2};
//        int[] arr = {1, 3, 5,5,7, 6, 2,2};
//        int[] arr = {-9,78,0,23,-567,70, -1,900, 4561};
        quickSort(arr,0,arr.length-1);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i]+" ");
        }
    }

    //交换法
    public static int[] quickSort(int arr[],int start,int end) {
        int pivot = arr[start];
        int i = start;
        int j = end;
        while (i<j) {
            while ((i<j)&&(arr[j]>pivot)) {
                j--;
            }
            while ((i<j)&&(arr[i]<pivot)) {
                i++;
            }
            if ((arr[i]==arr[j])&&(i<j)) {
                i++;
            } else {
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        if (i-1>start) arr=quickSort(arr,start,i-1);
        if (j+1<end) arr=quickSort(arr,j+1,end);
        return (arr);
    }
}

7 归并排序(MERGE-SORT)

  • 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
  • 归并排序是一种稳定的排序方法。
  • 时间复杂度:O(nlogn)

图解:

java详解七大排序算法_第4张图片
java详解七大排序算法_第5张图片

//归并排序
public class MergeSortTest {
    public static void main(String[] args) {
//        int[] array = {1, 5, 7, 6, 2};
//        int[] array = {6, 5, 4, 2, 1, 0};
//        int[] array = {5, 6, 3, 1, 8, 7, 99};
        int[] array = {8, 4, 5, 7, 1, 3, 6, 2};
        int[] temp = new int[array.length];
        mergeSort(array, 0, array.length - 1, temp);
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
    }

    private static void mergeSort(int[] array, int left, int right, int[] temp) {
        if (left < right) {
            int mid = (left + right) / 2;
            mergeSort(array, left, mid, temp);
            mergeSort(array, mid + 1, right, temp);
            merge(array, left, right, temp);
        }
    }

    private static void merge(int[] array, int left, int right, int[] temp) {
        int mid = (left + right) / 2;
        int i = left;
        int j = mid + 1;
        int index = 0;

        while (i <= mid && j <= right) {
            if (array[i] <= array[j]) {
                temp[index++] = array[i++];
            } else {
                temp[index++] = array[j++];
            }
        }

        while (i <= mid) {
            temp[index++] = array[i++];
        }

        while (j <= right) {
            temp[index++] = array[j++];
        }

        index = 0;
        while (left <= right) {
            array[left++] = temp[index++];
        }

    }


}

8 基数排序(Radix Sort)

  • 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配)至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog®m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
  • 基数排序是一种典型的以空间交换时间的算法实现。
  • 基数排序一般不对包含负数的数组进行排序,如果需要对负数排序,必须再改进算法。

图解:

java详解七大排序算法_第6张图片
java详解七大排序算法_第7张图片
java详解七大排序算法_第8张图片

//基数排序
public class RadixSortTest {
    public static void main(String[] args) {
//        int[] arr = {1, 3, 5, 7, 6, 2};
//        int[] arr = {1, 3, 5,5,7, 6, 2,2};
//        int[] arr = {-9,78,0,23,-567,70, -1,900, 4561};
        int[] arr = {53, 3, 542, 748, 14, 214,1000};
        radixSort(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }

    //注意,基数排序是不需要递归的
    private static void radixSort(int[] array) {
        int[][] bucket = new int[10][array.length];//使用最坏情况,arr.length防止溢出
        int[] count = new int[10];//记录每个桶中元素数量

        //找到最大值
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] > max) {
                max = array[i];
            }
        }

        //遍历出最大值的位数
        int max_len = 0;
        while (max != 0) {
            max/=10;
            max_len++;
        }

        for (int i = 0, n = 1; i < max_len; i++, n *= 10) {//通过位数确定排序次数
            for (int j = 0; j < array.length; j++) {//将每个数放入对应的桶
                int temp = array[j] / n % (10);
                bucket[temp][count[temp]++] = array[j];
            }

            //将每次排序好的按桶顺序放回数组
            int index=0;
            for (int i1 = 0; i1 < 10; i1++) {
                for (int j = 0; j < count[i1]; j++) {
                    array[index++] = bucket[i1][j];
                }
                count[i1] = 0;//把count数组清零
           }

        }

    }
}

你可能感兴趣的:(Java数据结构,java,数据结构,排序算法)