常用排序算法,java描述

来源:互联网 发布:神马软件 编辑:程序博客网 时间:2024/05/17 23:10

本文总结常见排序算法的java语言写法,希望加深自己对各个排序算法思路的理解,同时方便初学者阅览。

题目:

1. 冒泡排序
2. 插入排序
3. 选择排序
4. 快速排序
5. 希尔排序
6. 归并排序,递归与迭代
7. 堆排序


几种排序的算法的各种指标对比:

排序算法 平均情况 最好情况 最坏情况 辅助空间 稳定性 冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定 插入排序 O(n^2) O(n) O(n^2) O(1) 稳定 选择排序 O(n^2) O(n^2) O(n^2) O(1) (不)稳定 快速排序 O(nlogn) O(nlogn) O(n^2) O(nlogn)~O(n) 不稳定 希尔排序 O(nlogn)~O(n^2) O(n^1.3) O(n^2) O(1) 不稳定 归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定 堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定

Arrays.sort()方法底层对数组进行排序所采用的方法:
1. 基本数据类型数组进行排序采用的是快速排序
2. 对象类型数组采用的是归并排序


辅助方法:

    //交换数组中指定的两个元素    public static void swap(int[] arr, int a, int b) {        int tmp = arr[a];        arr[a] = arr[b];        arr[b] = tmp;    }

1.冒泡排序

    /**     * 冒泡排序     * 平均和最坏时间复杂度O(n^2)     * 最好时间复杂度O(n)     * 稳定     */    public static int[] bubbleSort(int[] arr) {        if(arr.length <= 1)            return arr;        //需要arr.length-1次循环,每一次循环找出当前循环中最大的数        for(int i = 0; i < arr.length-1; i++) {            //每次找到的最大的数沉到最末尾以后不再比较这个数            for(int j = 0; j < arr.length-i-1; j++) {                if(arr[j] > arr[j+1]) {                    swap(arr, j, j+1);                }            }        }        return arr;    }

2.插入排序

    /**     * 插入排序     * 平均和最坏时间复杂度O(n^2)     * 最好时间复杂度O(n)     * 稳定     */    public static int[] insertSort(int[] arr) {        if(arr.length <= 1)     return arr;        for(int i = 0; i < arr.length-1; i++) {//            if(arr[i] > arr[i+1]) {//                swap(arr, i, i+1);                //如果当前比较的两个数需要交换,则交换后较小的数需要继续和前面的数比较                for(int j = i+1; j > 0; j--) {  //j=i                    if(arr[j-1] > arr[j]) {                        swap(arr, j, j-1);                    }                    else                        break;                }//            }        }        return arr;    }

3.选择排序

以下摘自知乎选择排序究竟属于稳定排序还是不稳定排序?
关于选择排序,有些书说是稳定的,有些书说是不稳定的.
在我看来,不同的实现方法有不同的结果.
如果是在数组中交换,那么就有可能不稳定,如{5,5,2}
如果是链表或者开一个新的数组,那么又是稳定的了.

在我看来选择排序应该属于不稳定排序,不管是用数组也好,链表也好,只是实现的方式不一样,选择排序算法里面包括了交换,即每次从未排序的数中选择最小的与未排序的第一个交换,破坏了相对顺序,所以不是稳定的排序。至于开一个新的数组,这样的排序貌似就变成了插入排序了,插入排序是一种稳定的排序算法。

    /**     * 选择排序     * 最好、最坏、平均时间复杂度都是O(n^2)     * 稳定     */    public static int[] selectSort(int[] arr) {        if(arr.length <= 1)     return arr;        for(int i = 0; i < arr.length-1; i++) {            //每次循环固定最前面的一个数            //每次循环都取第一个元素与后面所有元素比较,如果有比第一个元素小的就交换,            //再用这个小的继续与后面的元素比较            for(int j = i+1; j < arr.length; j++) {                if(arr[i] > arr[j]) {                    swap(arr, i, j);                }            }        }        return arr;    }

4.快速排序

    /**     * 快速排序     * 利用递归的思想     * 最好、平均时间复杂度O(nlogn),最差时间复杂度O(n^2)     * 不稳定     */    public static int[] QuickSort(int[] arr) {        return QSort(arr, 0, arr.length-1);    }    private static int[] QSort(int[] arr, int low, int high) {        int mid;        if(low < high) {            mid = partition(arr, low, high);            QSort(arr, low, mid-1);     //对低子表递归排序            QSort(arr, mid+1, high);    //对高子表递归排序        }        return arr;    }    private static int partition(int[] arr, int low, int high) {        int pivotkey = arr[low];    //用子表的第一个值作为枢轴值        while (low < high) {    //从表的两端交替向中间扫描            while (low < high && arr[high] >= pivotkey)                high--;            swap(arr, low, high);    //将比枢轴小的数据交换到低端            while (low < high && arr[low] <= pivotkey)                low++;            swap(arr, low, high);    //将比枢轴大的数据交换到高端        }        return low;     //返回枢轴所在位置    }

5.希尔排序

    /**     * 希尔排序     * 平均时间复杂度O(nlogn)~O(n^2)     * 最好时间复杂度O(n^1.3)     * 最坏时间复杂度O(n^2)     * 不稳定     */    public static int[] shellSort(int[] arr) {        int i, j;        int increment = 0;        while (increment < arr.length/3) {            increment = increment * 3 + 1;        }        for (; increment > 0; increment/=3) {            for(i = increment; i < arr.length; i++) {                int tmp = arr[i];                for(j = i-increment; j >= 0 && tmp < arr[j]; j -= increment) {                    arr[j+increment] = arr[j];                }                arr[j+increment] = tmp;            }        }        return arr;    }

6.归并排序

    /**     * 归并排序,递归解法     * 时间复杂度O(nlogn)     * 空间复杂度O(n)     * 稳定     */    public static int[] MergeSort(int[] arr) {        if(arr.length <= 1) {            return arr;        }        return MSort(arr, 0, arr.length-1);    }    private static int[] MSort(int[] arr, int start, int end) {        if(arr.length <= 1) {            return arr;        }        if(start >= end) {            return arr;        }        int mid = (start + end)/2;        int start2 = mid+1;        MSort(arr, start, mid);   //递归归并前半段序列        MSort(arr, start2, end);  //递归归并后半段序列        //将前面排好序的两段序列合并        int[] tmp = new int[arr.length];   //辅助空间,存放排好序的序列        int index = start;        int temp = start;   //保存序列初始值        while (start <= mid && start2 <= end) {  //两个指针同时往后走,较小的先走            if(arr[start] <= arr[start2]) {                tmp[index++] = arr[start++];            }            else {                tmp[index++] = arr[start2++];            }        }        //如果前半段序列还没有走到最后,证明后面的数都比前面的数大,直接复制到辅助空间中        while (start <= mid) {               tmp[index++] = arr[start++];        }        //原理同上        while (start2 <= end) {            tmp[index++] = arr[start2++];        }        //将两个有序序列重新合并成一个有序序列后再存回数组中        for (int i = temp; i < end+1; i++) {            arr[i] = tmp[i];        }        return arr;    }
    /**     * 归并排序,迭代解法     */    public static int[] mergeSort2(int[] arr) {        if(arr.length <= 1) {            return arr;        }        int window = 1;        int[] tmp = new int[arr.length];        for (; window < arr.length; window*=2) {            for(int start = 0; start < arr.length; start += 2*window) {                int start1 = start;                int end1, end2;                if(start1+window < arr.length) {                    end1 = start1+window;                }                else {                    end1 = arr.length;                }                int start2 = end1;                if(start1+2*window < arr.length) {                    end2 = start1+2*window;                }                else {                    end2 = arr.length;                }                int index = start1;                while (start1 < end1 && start2 < end2) {                    if(arr[start1] < arr[start2]) {                        tmp[index++] = arr[start1++];                    }                    else {                        tmp[index++] = arr[start2++];                    }                }                while (start1 < end1) {                    tmp[index++] = arr[start1++];                }                while (start2 < end2) {                    tmp[index++] = arr[start2++];                }                for(int i = start; i < end2; i++) {                    arr[i] = tmp[i];                }            }        }        return arr;    }

7.堆排序

    /**     * 堆排序     * 时间复杂度O(nlogn)     *     * 二叉堆的数组从下标1开始存储,而不是0,若当前节点为i,则左子节点为2i,右子节点为2i+1,父节点为i/2     *     * 思路:     *     1、构建大顶堆maxHeap(下标从0开始,所以和二叉堆有细微差别,节点i的左子节点为2i+1)     *     2、大顶堆的0位置元素显然是最大的,将这个数沉到数组最末端     *     3、重复步骤1、2     */    public static int[] heapSort(int[] arr) {        if(arr.length <= 1) {            return arr;        }        for (int i = 0; i < arr.length; i++) {            maxHeap(arr, arr.length-1-i);            swap(arr, 0, arr.length-1-i);        }        return arr;    }    //构建大顶堆    public static void maxHeap(int[] arr, int lastIndex) {        for (int i = (lastIndex-1)/2; i >= 0; i--) {            int k = i;  //保存当前正在判断节点的索引            //如果当前节点存在子节点(判断依据是其左子节点索引在lastIndex范围内)            while (2*k+1 <= lastIndex) {                int biggerIndex = 2*k + 1;  //biggerIndex总是记录值较大的节点的索引,初始值为左子节点索引                if(biggerIndex < lastIndex) {   //如果右子节点存在,否则会有biggerIndex=lastIndex                    if(arr[biggerIndex] < arr[biggerIndex+1]) {                        //如果右子节点比左子节点的值大,则biggerIndex记录的是右子节点的索引                        biggerIndex++;                    }                }                if(arr[k] < arr[biggerIndex]) {                    //如果当前节点值小于其所有子节点中值最大的那个值,那么交换,//将当前节点索引设置为biggerIndex                    swap(arr, k, biggerIndex);                    k = biggerIndex;                }                else                    break;            }        }    }
0 0