排序算法及实现

排序算法及实现

一、排序的分类

  1. 内部排序

内部排序把需要处理的数据加载到内存后排序。内部排序包括插入排序、选择排序、交换排序、归并排序、基数排序。插入排序分为直接插入排序和希尔排序。选择排序分为简单选择排序和堆排序。交换排序分为冒泡排序和快速排序。

  1. 外部排序

外部排序数据量很大,无法全部加载到内存中。

二、算法时间复杂度

  1. 事后统计方法
  2. 事前估算方法,分析时间复杂度
  3. 算法时间复杂度

一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间越多。一个算法中语句执行的次数称为语句频度或时间频度,记为T(n)。一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用==T(n)==表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的某个常数,则称f(n)是T(n)的同数量级函数,记为T(n)=O(f(n)),称为算法的时间复杂度。用常数1替代运行时间中的所有加法常数;修改后的运行次数函数中,只保留最高阶项;去除最高阶项的系数。平均时间复杂度是所有可能的输入实例均以等概率出现的情况下该算法的运行时间。一般讨论最坏时间复杂度。

  1. 算法空间复杂度

算法的空间复杂度定义为该算法所耗费的存储空间,它也是问题规模n的函数。空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。算法分析主要讨论的是时间复杂度。一些缓存产品和基数排序本质就是用空间换时间。

排序方法 平均时间 最差情形 稳定度 额外空间 备注
冒泡 O(n2) O(n2) 稳定 O(1) n小时较好
交换 O(n2) O(n2) 不稳定 O(1) n小时较好
选择 O(n2) O(n2) 不稳定 O(1) n小时较好
插入 O(n2) O(n2) 稳定 O(1) 大部分已排序时较好
基数 O(logRB) O(logRB) 稳定 O(n) B是真数(0-9),R是基数。
希尔 O(nlogn) O(nx) 1 不稳定 O(1) n是所选分组
快速 O(nlogn) O(n2) 不稳定 O(nlogn) n大时较好
归并 O(nlogn) O(nlogn) 稳定 O(1) n大时较好
O(nlogn) O(nlogn) 不稳定 O(1) n大时较好

三、 冒泡排序

通过对待排序序列从前向后,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就像水底的气泡一样逐渐向上冒。

import java.util.Arrays;

public class BubbleSort {

    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        System.out.println("排序前数组:" + Arrays.toString(arr));
        bubbleSort(arr);
        System.out.println("排序后数组:" + Arrays.toString(arr));

        //排序性能测试
        long t1 = System.currentTimeMillis();
        int[] benchMarkArr = new int[80000];
        for(int i=0;i<80000;i++){
            benchMarkArr[i] = (int) (Math.random() * 80000 * 10);
            bubbleSort(benchMarkArr);
        }

        long t2 = System.currentTimeMillis();
        System.out.println("排序时间:" + (t2 - t1));
    }

    /**
     * 冒泡排序:第i轮需要进行n-1-i次比较,如果已经有序了则直接退出排序
     * @param arr 待排序数组
     */
    private static void bubbleSort(int[] arr){
        //表示是否需要交换
        boolean flag = false;
        //外层循环控制是第几趟
        for(int i=0;i<arr.length;i++){
            //内层循环控制每趟需要比较的次数
            for(int j=0;j<arr.length-1-i;j++){
                int tmp;
                if(arr[j] > arr[j+1]){
                    flag = true;
                    tmp = arr[j+1];
                    arr[j+1] = arr[j];
                    arr[j] = tmp;
                }
            }

            if(!flag){
                break;
            }else{
                //重置flag进行下次判断
                flag = false;
            }
        }
    }

    /**
     * 冒泡排序优化
     * @param arr 待排序数组
     */
    private static void bubbleSortV2(int[] arr){
        int n = arr.length - 1;
        do {
            //表示最后一次交换索引位置
            int last = 0;
            for (int i = 0; i < n; i++) {
                System.out.println("比较次数:" + i);
                if (arr[i] > arr[i + 1]) {
                    int tmp = arr[i + 1];
                    arr[i + 1] = arr[i];
                    arr[i] = tmp;
                    last = i;
                }
            }
            n = last;
        } while (n != 0);
    }
}

四、简单选择排序

简单选择排序是一种简单的排序方法。第一次从arr[0]~arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]~arr[n-1]中最小值,与arr[1]交换,依次类推。总共通过n-1次,得到一个按排序码从小到大排列的有序序列。

import java.util.Arrays;

public class PickupSort {

    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        pickupSortV2(arr);
    }

    private static void pickupSort(int[] arr) {
        for(int i=0;i<arr.length-1;i++){
            //index定义为最小值的下标,i表示每轮选择的最小元素要交换的目标索引
            int index = i;
            int min = arr[i];
            for(int j=i+1;j<arr.length;j++){
                if(min > arr[j]){
                    min = arr[j];
                    index = j;
                }
            }

            if(index != i){
                arr[index] = arr[i];
                arr[i] = min;
            }

            System.out.println("第" + (i+1) + "轮排序结果:" + Arrays.toString(arr));
        }
    }

    private static void pickupSortV2(int[] arr){
        int s;
        for(int i=0;i<arr.length-1;i++){
            s = i;
            for(int j=s+1;j<arr.length;j++){
                if(arr[j] < arr[s]){
                    s = j;
                }
            }

            int tmp;
            if(s != i){
                tmp = arr[s];
                arr[s] = arr[i];
                arr[i] = tmp;
            }

            System.out.println("第" + (i+1) + "轮排序结果:" + Arrays.toString(arr));
        }
    }
}

五、直接插入排序

把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响。

import java.util.Arrays;

public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        insertSortV2(arr);
    }

    private static void insertSort(int[] arr) {
        //把序列分成有序数组和无需数组,其中第0位默认有序
        for(int i=1;i<arr.length;i++){
            //i表示待插入元素的索引,tmp表示待插入的值
            int tmp = arr[i];
            int j = i;
            while(j > 0 && tmp < arr[j-1]){
                arr[j] = arr[j-1];
                j--;
            }

            //发生了交换
            if(j != i){
                arr[j] = tmp;
            }

            System.out.println("第" + (i+1) + "轮排序结果:" + Arrays.toString(arr));
        }
    }

    private static void insertSortV2(int[] arr){
        for(int i=1;i<arr.length;i++){
            //i表示待插入元素的索引,tmp待插入的元素值
            int tmp = arr[i];
            int j = i - 1; //表示已排序取余的元素索引
            while(j>=0){
                if(arr[j] > tmp){
                    arr[j+1] = arr[j];
                }else{
                    break; //退出循环,减少比较次数
                }
                j--;
            }
            arr[j+1]=tmp;
            System.out.println("第" + i + "轮排序结果:" + Arrays.toString(arr));
        }
    }
}

六、希尔排序

把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量的减少,每组包含的关键词越来越多,当增量减至1时,整个文件被分成一组,算法便终止。

import java.util.Arrays;

public class ShellSort {

    public static void main(String[] args) {
        //int[] arr = {53, 3, 542, 748, 14, 214};
        long t1 = System.currentTimeMillis();
        int[] arr = new int[800];
        for(int i=0;i<800;i++){
            arr[i] = (int) (Math.random() * 800 * 10);
        }

        shellSort(arr);
        System.out.println("希尔排序耗时:" + (System.currentTimeMillis() - t1));
    }

    //使用移位法
    private static void shellSort(int[] arr) {
        for(int gap = arr.length/2; gap > 0; gap /= 2){
            for(int i = gap; i < arr.length; i++){
                int j = i;
                int tmp = arr[j];
                if(arr[j] < arr[j-gap]){
                    while(j - gap >= 0 && tmp < arr[j-gap]){
                        arr[j] = arr[j-gap];
                        j -= gap;
                    }

                    arr[j] = tmp;
                }
            }

            System.out.println(Arrays.toString(arr));
        }
    }
}

七、快速排序

快速排序是对冒泡排序的一种改进。基本思想是通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据都变成有序序列。

import java.util.Arrays;

public class QuickSort {

    public static void main(String[] args) {
        int[] arr = {-9, 78, 0, 23, -567, 70};
        //quickSort(arr, 0, arr.length-1);
        quickSortV3(arr, 0, arr.length-1);
        System.out.println("排序结果:" + Arrays.toString(arr));

    }

    private static void quickSort(int[] arr, int left, int right) {
        int l = left;
        int r = right;
        int pivot = arr[(left + right)/2];
        while(l < r){
            while(arr[l] < pivot){
                l++;
            }

            while(arr[r] > pivot){
                r--;
            }

            if(l >= r){
                break;
            }

            swap(arr, l, r);
            if(arr[l] == pivot){
                l++;
            }

            if(arr[r] == pivot){
                r--;
            }
        }

        if(l == r){
            l++;
            r--;
        }

        if(left < r){
            quickSort(arr, left, r);
        }

        if(right > l){
            quickSort(arr, l, right);
        }
    }

    private static void quickSortV2(int[] arr, int low, int high){
        if(low >= high){
            return;
        }

        int p = partition(arr, low, high); //获得第一次分区后的索引值
        quickSortV2(arr, low, p-1); //左边分区的范围确定
        quickSortV2(arr, p+1, high);
    }

    private static void quickSortV3(int[] arr, int low, int high){
        if(low >= high){
            return;
        }

        int p = partitionV2(arr, low, high); //获得第一次分区后的索引值
        quickSortV3(arr, low, p-1); //左边分区的范围确定
        quickSortV3(arr, p+1, high);
    }

    /**
     *
     * @param arr 待排序数组
     * @param low 下边界
     * @param high 上边界
     * @return 基准点元素所在的正确的索引,用来确定下一轮分区的边界
     */
    private static int partition(int[] arr, int low, int high){
        int pivot = arr[high]; //基准点元素
        int i = low;
        for(int j=low;j<high;j++){
            if(arr[j] < pivot){
                if(i != j) {
                    swap(arr, i, j);
                }
                i++;
            }
        }

        if(i != high) { //优化点:i与high相等时没必要交换
            swap(arr, high, i);
        }
        System.out.println(Arrays.toString(arr) + " i=" + i);
        return i;
    }

    private static int partitionV2(int[] arr, int low, int high){
        int pivot = arr[low];
        int i = low;
        int j = high;
        while(i < j){
            //j从右往左找比基准点小的数,i从左向右找到比基准点大的数
            while(i < j && arr[j] > pivot) j--;
            while(i < j && arr[i] <= pivot) i++;
            swap(arr, i, j);
        }
        swap(arr, low, j);
        System.out.println(Arrays.toString(arr) + ",j=" + j);
        return j;
    }

    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

八、归并排序

归并排序时利用归并的思想实现的排序方式,该算法采用经典的分治策略分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案修补在一起,即分而治之。

import java.util.Arrays;

public class MergeSort {

    public static void main(String[] args) {
        int[] arr = {-9, 78, 0, 23, -567, 70};
        mergeSort(arr);
        System.out.println("归并排序后数组:" + Arrays.toString(arr));
    }

    private static void mergeSort(int[] arr){
        sort(arr, 0, arr.length - 1);
    }

    private static void sort(int[] arr, int left, int right){
        if(left >= right){
            return;
        }

        int mid = (left + right)/2;
        sort(arr, left , mid);
        sort(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }

    private static void merge(int[] arr, int left, int mid, int right){
        //创建一个临时数组
        int[] tmp = new int[arr.length];
        int i = left;
        int j = mid + 1;
        int t = 0;

        //1.把左右两边数据按照规则填充到tmp数组,直到左右两边的有序序列有一边处理完毕为止
        while(i <= mid && j<= right){
            if(arr[i] <= arr[j]){
                tmp[t++] = arr[i++];
            }else{
                tmp[t++] = arr[j++];
            }
        }

        //2.把有剩余数据的一边所有数据依次填充到tmp
        while(i <= mid){
            tmp[t++] = arr[i++];
        }

        while(j <= right){
            tmp[t++] = arr[j++];
        }

        //3.把tmp中的数据移入arr数组
        t = 0;
        int temp = left;
        while(temp <= right){
            arr[temp++] = tmp[t++];
        }
    }
}

九、基数排序

基数排序属于分配式排序,又称桶子法(bucket sort)。通过键值的各个位的值,将要排序的元素分配至某些桶中,达到排序的作用。基数排序属于稳定性的排序,效率较高,是桶排序的扩展。将所有待比较的数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序数列。

import java.util.Arrays;

/**
 * 基数排序:桶排序的扩展,是分配式的排序,通过将数字的各个位的值,将要排序的元素分配到这些桶中,达到
 * 排序的目的。基数排序是稳定排序,效率较高。
 */
public class RadixSort {

    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        radixSort(arr);

        long t1 = System.currentTimeMillis();
        int[] benchMarkArr = new int[80000];
        for(int i=0;i<8000;i++){
            benchMarkArr[i] = (int) (Math.random() * 80000 * 10);
            radixSort(benchMarkArr);
        }

        long t2 = System.currentTimeMillis();
        System.out.println("排序时间:" + (t2 - t1));
    }

    /**
     * 基本思想:把所有待排序的数统一为同样的数位长度,数位较短的数前面补零。
     * 然后从最低为开始依次排序,这样从最低位排序一直到最高位排序完成以后,
     * 数列就变成了一个有序数列。
     */
    private static void radixSort(int[] arr){
        if(arr == null){
            return;
        }

        //第一轮(针对每个排序数的各位进行排序)
        //定义一个二维数组表示10个桶,每个桶就是一维数组,为了防止放入数据时溢出,一维数组大小就是数组大小
        int[][] bucket = new int[10][arr.length];
        //记录每个桶中实际存放元素的个数,bucketElemCounts[0]记录0号桶中元素的个数
        int[] bucketElemCounts = new int[10];
        //一维数组中最大位的位数
        int max = arr[0];
        for(int i=1;i<arr.length;i++){
            if(arr[i] > max){
                max = arr[i];
            }
        }

        int maxLength = (max + "").length();

        for(int i=0, n=1; i<maxLength; i++,n*=10) {
            for (int j = 0; j < arr.length; j++) {
                //取出每个元素对应的位,放入到对应的桶中
                int digitOfElem = arr[j]/n % 10;
                bucket[digitOfElem][bucketElemCounts[digitOfElem]] = arr[j];
                bucketElemCounts[digitOfElem]++;
            }
            //按照桶的顺序,一维数组的下标依次取出数据,放入原来的数组
            int index = 0;
            for (int k = 0; k < bucketElemCounts.length; k++) {
                if(bucketElemCounts[k] != 0){
                    for(int l=0;l<bucketElemCounts[k];l++){
                        arr[index++] = bucket[k][l];
                    }
                }

                //处理后重置桶
                bucketElemCounts[k] = 0;
            }

            System.out.println("第" + (i+1) + "轮排序:" + Arrays.toString(arr));
        }
    }

}

十、堆排序

堆排序是利用堆这种数据结构而设计的一种排序方法,堆排序是一种选择排序,最坏、最好、平均时间复杂度均为O(nlogn),它也是不稳定排序。堆是二叉树,每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆,没有要求节点的左孩子和右孩子的值大小关系;每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆。堆排序的步骤是把待排序序列构造成一个大顶堆;此时整个序列的最大值就是堆顶的根节点;将其与末尾元素进行交换,此时末尾就为最大值;然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值,如此反复执行,就能得到一个有序序列了。

import java.util.Arrays;

/**
 * 堆排序
 */
public class HeapSort {
    public static void main(String[] args) {
        int[] arr = {4, 6, 8, 5, 9, 10, 13};
        heapSort(arr);
        System.out.println("堆排序结果:" + Arrays.toString(arr));
    }

    private static void heapSort(int[] arr){
        //从最后一个非叶子节点从右至左,从下至向上调整最大堆
        for(int i=arr.length/2-1;i>=0;i--){
            adjustHeap(arr, i, arr.length);
        }

        //把栈顶元素与末尾元素进行交换,然后继续调整堆
        for(int i=arr.length-1; i>0;i--){
            swap(arr, 0, i);
            adjustHeap(arr, 0, i);
        }
    }

    /**
     * 把一个数组(二叉树)调整成一个大顶堆
     * @param arr 待排序数组
     * @param i 非叶子节点在数组中的索引
     * @param len 堆多少个元素进行调整
     */
    private static void adjustHeap(int[] arr, int i, int len){
        int current = arr[i];
        //k指向i的左子节点
        for(int k=2*i+1;k<len;k=k*2+1){
            if(k+1 < len && arr[k] < arr[k+1]){
                k++;
            }

            if(arr[k] > current){
                arr[i] = arr[k];
                i = k;
            }else{
                break;
            }
        }

        arr[i] = current;
    }


    private static void swap(int[] arr, int i, int j){
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

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