排序算法(Java)

来源:互联网 发布:软件测试工作怎么样 编辑:程序博客网 时间:2024/05/23 18:51

控制台运行编译java程序带中文乱码问题解决办法: javac -encoding utf-8 Test.java

稳定性:排序算法需要保留数组中重复元素的相对位置。(具体详见算法第四版P217)

冒泡排序

思想: 两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。

时间复杂度: O(N2)

空间复杂度: O(1)

稳定性: 稳定

public class Sort {    public static void sort(int[] nums) {        int N = nums.length;        int temp = 0;        for (int i = 0; i < N-1; i++) {            for (int j = 0; j < N-i-1; j++) {                if (nums[j] > nums[j + 1]) {                    temp = nums[j];                    nums[j] = nums[j+1];                    nums[j+1] = temp;                }            }        }    }}

选择排序

思想: 每一趟在n-i+1(i=1,2,…,n-1)个记录中选取关键字最小的记录作为有序序列的第i个记录。

时间复杂度: O(N2)

空间复杂度: O(1)

稳定性: 不稳定(说明:举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法)

public class Sort {    public static void sort(int[] nums) {        int N = nums.length;        for (int i = 0; i < N; i++ ) {            int min = i;            for (int j = i+1; j < N; j++) {                if (nums[j] < nums[min]) {                    min = j;                }            }            if (min != i) {                temp = nums[i];                nums[i] = nums[min];                nums[min] = temp;               }               }    }}

插入排序

思想: 将一个记录插入到已经排序好的有序表中,从而得到一个新的、记录数增1的有序表

时间复杂度: O(N2)

空间复杂度: O(1)

稳定性: 稳定

public class Sort {    public static void sort(int[] nums) {        int N = nums.length;        int temp = 0;        for (int i = 1; i < N; i++ ) {            for (int j = i; j > 0 && nums[j] < nums[j-1]; j--) {                temp = nums[j];                nums[j] = nums[j+1];                nums[j+1] = temp;            }        }    }}

改进版本

public class Sort {    public static void sort(int[] nums) {        int N = nums.length;        int i = 0;         int j = 0;        for (i = 1; i < N; i++) {            if (nums[i] < nums[i-1]) {                int temp = nums[i];                for (j = i-1; (j >= 0) && (nums[j] > temp); j--) {                    nums[j+1] = nums[j];                }                nums[j+1] = temp;            }        }    }}

希尔排序(插入排序升级)

思想: 将数据分为若干组记录,然后分别对每一组做插入排序。

时间复杂度: O(NlgN)

空间复杂度: O(1)

稳定性: 不稳定

public class Sort {    public static void sort(int[] nums) {        int N = nums.length;        int temp = 0;        int h = 1;        int i = 0;         int j = 0;        //固定步长:1,4,13,40...        while (h < N/3) {            h = 3*h + 1;        }        while (h >= 1) {            //下面操作和插入排序算法基本相同            for (i = h; i < N; i++) {                if (nums[i] < nums[i - h]) {                    int temp = nums[i];                    for (j = i-h; (j >= 0) && (nums[j] > temp); j = j-h) {                        nums[j+h] = nums[j];                    }                    nums[j+h] = temp;                }            }            h = h / 3;        }    }}

快速排序(冒泡排序增强)

思想: 选取一个轴值(比较的基准),将待排序记录分为独立的两个部分,左侧记录都是小于或等于轴值,右侧记录都是大于或等于轴值,然后分别对左侧部分和右侧部分重复前面的过程,也就是左侧部分又选择一个轴值,又分为两个独立的部分,这就使用了递归了。到最后,整个序列就变得有序了。

时间复杂度: O(NlgN)

空间复杂度: O(lgN)~O(N)

稳定性: 不稳定

public class Sort {    public static void sort(int[] nums) {        //消除对输入的依赖        //StdRandom.shuffle(a);           sort(nums, 0, nums.length - 1);    }    public static void sort (int[] nums, int lo, int hi) {        if (hi <= lo) return;        //切分        int j = partition(nums, lo, hi);        sort(nums, lo, j-1);        sort(nums, j+1, hi);    }    public static int partition(int[] nums, int lo, int hi) {        //定义左右扫描的指针        int i = lo, j = hi + 1;        //定义切分的元素d        int point = nums[lo];        int temp = 0;        while (true) {            while (nums[++i] < point) if (i == hi) break;            while (point < nums[--j]) if (j == lo) break;            if (i >= j) break;            //交换两个元素            temp = nums[i];            nums[i] = nums[j];            nums[j] = temp;        }        //将切分元素放到数组中合适位置        temp = nums[lo];        nums[lo] = nums[j];        nums[j] = temp;        return j;    }}

堆排序(选择排序增强)

思想: 利用优先队列的删除最大元素的特点,依次将将删除的最大元素保存起来,就有序了。当然,这里的优先队列是用堆实现的。

第一步:需要保证堆有序,也就是每一个父节点要比它的任意子节点要大;

第二步:使用堆的下沉操作来实现排序;

时间复杂度: O(NlgN)

空间复杂度: O(1)

稳定性: 不稳定

public class Sort {    public static void heapSort(int[] a) {        int N = a.length;        //第一步:通过下沉操作来实现堆有序,这里只需要操作数组中前面的一半元素即可        for (int k = N / 2; k >= 1; k--) {            sink(a, k, N);        }        //第二步:将堆有序的数组使用下沉操作来得到有序数组        while (N > 1) {            exch(a, 1, N--);            sink(a, 1, N);        }    }    //堆下沉操作    public static void sink(int[] a, int k, int N) {        while (2*k <= N) {            int j = 2*k;            //取两个子节点中较大的一个            if (j < N && less(a, j, j+1)) j++;            //比较如果父节点比子节点中较大的一个小,则交换            if (!less(a, k, j)) break;            exch(a, k, j);            //继续往下面遍历            k = j;        }    }    //之所以取i-1,是因为堆中下标是从1开始的,需要还原到数组中从0开始的。    public static boolean less(int[] a, int i, int j) {        return a[i-1] < a[j-1];    }    public static void exch(int[] a, int i, int j) {        int temp = a[i-1];        a[i-1] = a[j-1];        a[j-1] = temp;    }        }

归并排序

思想: 要将一个数组排序,可以先(递归地)将它们分成两半分别排序,然后将它们的结果归并起来。

时间复杂度: O(NlgN)

空间复杂度: O(N)

稳定性: 稳定

public class Sort {    //1.将两个有序数组合并为一个有序数组    public static void merge(int[] a, int lo, int mid, int hi) {        //将a[lo..mid] 和 a[mid+1..hi]归并        //i代表左半边索引,j代表右半边索引        int i = lo, j = mid + 1;        //定义一个辅助数组        int[] aux;        //将a[lo..hi]复制到aux[lo..hi]        for (int k = lo; k <= hi; k++) {            aux[k] = a[k];        }        for (int k = lo; k <= hi; k++) {            //左半边元素用尽,取右半边的元素            if (i > mid) {                a[k] = aux[j++];            //右半边元素用尽,取左半边元素                } else if (j > hi) {                a[k] = aux[i++];            //右半边的当前元素小于左半边的当前元素,则取右半边的元素            } else if (aux[j] < aux[i]) {                a[k] = aux[j++];            //左半边的当前元素小于等于右半边的当前元素,则取左半边的元素            } else {                a[k] = aux[i++];            }        }    }    //自顶向下    pubic static void sort (int[] a, int lo, int hi) {        if (hi <= lo) {            return;        }        int mid = lo + (hi - lo) / 2;        //将左半边排序        sort(a, lo, mid);        //将右半部分排序        sort(a, mid+1, hi);        //归并结果        merger(a, lo, mid, hi);    }    //自底向上    public static void sort (int[] a) {        int N = a.length;        int[] aux = new int[N];        //定义子数组的大小        for (int sz = 1; sz < N; sz = sz + sz) {            //子数组的索引                                    for (int lo = 0; lo < N-sz; lo += sz+sz) {                  //lo+sz+sz-1可能越界,因此需要一个min函数来取边界                                merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));                   }        }    }}

总结

时间复杂度:
O(N)
计数排序、基数排序

O(N2)
冒泡排序、选择排序、插入排序

O(NlogN)
希尔排序、堆排序、快速排序、归并排序

空间复杂度:
O(1)
插入排序、选择排序、冒泡排序、堆排序、希尔排序

O(logN) ~ O(N)
快速排序

O(N)
归并排序

O(M) M为桶的数量
计数排序、基数排序

稳定性:

稳定的排序算法:
冒泡排序、插入排序、归并排序、计数排序、基数排序、桶排序

不稳定的排序算法:
选择排序、快速排序、希尔排序、堆排序

原创粉丝点击