常见排序算法(Java实现)

目录

排序算法概述

一、基于比较的排序

1.选择排序

2.冒泡排序

3.插入排序

4.希尔排序

5.快速排序

6.归并排序

7.堆排序

二、线性时间的排序

8.计数排序

9.桶排序

10.基数排序


排序算法概述

常见排序算法主要分为两种(本文log皆以2为底):

  • 基于比较的排序:被排序对象属于Compareable类型,使用CompareTo()进行比较排序。除了赋值、引用运算外,这是仅有的对输入数据的操作。由决策树可证,对于含n个元素的一个输入序列,任何比较排序算法在最坏情况下,都需要做Ω(NlogN)次比较。比较排序算法的运行速度不会快于NlogN,这就是基于比较的排序算法的时间下界。

  • 线性时间的排序:在某些特殊情况下以线性时间进行排序依然是可能的,不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

算法复杂度:

表格引自:https://www.cnblogs.com/onepixel/articles/7674659.html

算法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性
选择排序O()O()O()O(1)不稳定
冒泡排序O()O()O(N)O(1)稳定
插入排序O()O()O(N)O(1)稳定
希尔排序O(N^1.3) (shell序列)O()O(N^1.3)O(1)不稳定
快速排序O(N logN)O()O(N logN)O(N logN)不稳定
归并排序O(N logN)O(N logN)O(N logN)O(N)稳定
堆排序O(N logN)O()O(N logN)O(1)不稳定
      
计数排序O(N+M)O(N+M)O(N+M)O(N+M)稳定
桶排序O(N+M) (M个桶)O()O(N)O(N+M)稳定
基数排序O(p(N+M)) (p是趟数)O(p(N+M))O(p(N+M))O(N+M)稳定

一、基于比较的排序

1.选择排序

选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

    /**
     * 选择排序(O(N²)) 不稳定
     * <p>
     * 选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
     * 然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
     *
     * @param a   an array of Comparable items
     * @param <T> item type
     */
    public static <T extends Comparable<? super T>> void selectionSort(T[] a) {
        int minIndex;
        for (int i = 0; i < a.length - 1; i++) {
            minIndex = i;
            for (int j = i; j < a.length; j++) {
                if (a[j].compareTo(a[minIndex]) < 0) {
                    minIndex = j;
                }
            }
            swapReference(a, i, minIndex);
        }

    }

    /**
     * 交换数组两个位置的元素
     *
     * @param a    an array of Comparable items
     * @param pos1 position1
     * @param pos2 position2
     * @param <T>  item type
     */
    private static <T extends Comparable<? super T>> void swapReference(T[] a, int pos1, int pos2) {
        T temp = a[pos1];
        a[pos1] = a[pos2];
        a[pos2] = temp;
    }

2.冒泡排序

冒泡排序:把较小的元素往前调或者把较大的元素往后调,通过对相邻两个元素进行大小的比较,根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换。

    /**
     * 冒泡排序(O(N²)) 稳定
     * <p>
     * 冒泡排序:把较小的元素往前调或者把较大的元素往后调,通过对相邻两个元素进行大小的比较,
     * 根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换。
     *
     * @param a   an array of Comparable items
     * @param <T> item type
     */
    public static <T extends Comparable<? super T>> void bubbleSort(T[] a) {
        for (int i = 0; i < a.length - 1; i++) {
            for (int j = 0; j < a.length - 1 - i; j++) {
                if (a[j].compareTo(a[j + 1]) > 0) {
                    swapReference(a, j, j + 1);
                }
            }
        }

    }

3.插入排序

插入排序:由N-1趟排序组成,对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。

    /**
     * 插入排序(O(N²)) 稳定
     * <p>
     * 插入排序:由N-1趟排序组成,对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。
     *
     * @param a   an array of Comparable items
     * @param <T> item type
     */
    public static <T extends Comparable<? super T>> void insertionSort(T[] a) {
        int j;
        //p=1到N-1趟排序(只有一个元素时是已排序的状态,既p=0已排序)
        for (int p = 1; p < a.length; p++) {
            T tmp = a[p];
            for (j = p; j > 0 && tmp.compareTo(a[j - 1]) < 0; j--) {
                a[j] = a[j - 1];
            }
            a[j] = tmp;
        }
    }

4.希尔排序

希尔排序:使用一个序列h1,h2,...,ht,叫做增量序列。只要 h1=1,任何增量序列都是可行的。不过,有些增量序列比另外一些增量序列要好。Shell序列:ht=N/2和h(k)=h(k+1)/2。

    /**
     * 希尔排序(O(N)~O(N³)) 不稳定
     * <p>
     * 希尔排序:使用一个序列h1,h2,...,ht,叫做增量序列。
     * 只要 h1=1,任何增量序列都是可行的。不过,有些增量序列比另外一些增量序列要好。Shell建议的序列:ht=N/2和h(k)=h(k+1)/2。
     *
     * @param a   an array of Comparable items
     * @param <T> item type
     */
    public static <T extends Comparable<? super T>> void shellSort(T[] a) {
        int j;
        for (int gap = a.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < a.length; i++) {
                T tmp = a[i];
                for (j = i; j >= gap && tmp.compareTo(a[j - gap]) < 0; j -= gap) {
                    a[j] = a[j - gap];
                }
                a[j] = tmp;
            }
        }
    }

5.快速排序

快速排序:分治的递归算法,在一趟排序中选定一个枢纽元,将要排序的数据分割成两部分,其中一部分的所有数据都比枢纽元大,另外一部分的所有数据都比枢纽元小,对这两部分数据分别进行快速排序。

public class QuickSort {
    /**
     * 递归终止条件
     */
    private static final int CUTOFF = 3;

    /**
     * 快速排序(O(N logN)) 不稳定
     * <p>
     * 快速排序:分治的递归算法,在一趟排序中选定一个枢纽元,将要排序的数据分割成两部分,
     * 其中一部分的所有数据都比枢纽元大,另外一部分的所有数据都比枢纽元小,对这两部分数据分别进行快速排序。
     *
     * @param a   an array of Comparable items
     * @param <T> item type
     */
    public static <T extends Comparable<? super T>> void quickSort(T[] a) {
        quickSort(a, 0, a.length - 1);
    }

    /**
     * 快排主例程
     *
     * @param a     an array of Comparable items
     * @param left  start point
     * @param right end point
     * @param <T>   item type
     */
    public static <T extends Comparable<? super T>> void quickSort(T[] a, int left, int right) {
        T pivot = median3(a, left, right);
        if (right - left + 1 > CUTOFF) {
            int i = left;
            int j = right - 1;
            while (i < j) {
                while (a[++i].compareTo(pivot) <= 0) { }
                while (a[--j].compareTo(pivot) >= 0) { }
                if (i < j) {
                    swapReference(a, i, j);
                }
            }
            swapReference(a, i, right - 1);
            quickSort(a, left, i - 1);
            quickSort(a, i + 1, right);
        }
    }

    /**
     * 三数中值分割法
     *
     * @param a     an array of Comparable items
     * @param left  start point
     * @param right end point
     * @param <T>   item type
     * @return median
     */
    private static <T extends Comparable<? super T>> T median3(T[] a, int left, int right) {
        if (right - left + 1 >= CUTOFF) {
            int center = (left + right) / 2;
            if (a[center].compareTo(a[left]) < 0) {
                swapReference(a, center, left);
            }
            if (a[right].compareTo(a[left]) < 0) {
                swapReference(a, right, left);
            }
            if (a[right].compareTo(a[center]) < 0) {
                swapReference(a, right, center);
            }
            swapReference(a, center, right - 1);
            return a[right - 1];
        } else {
            if (a[left].compareTo(a[right]) > 0) {
                swapReference(a, left, right);
            }
            return a[left];
        }
    }

    /**
     * 交换数组两个位置的元素
     *
     * @param a    an array of Comparable items
     * @param pos1 position1
     * @param pos2 position2
     * @param <T>  item type
     */
    private static <T extends Comparable<? super T>> void swapReference(T[] a, int pos1, int pos2) {
        T temp = a[pos1];
        a[pos1] = a[pos2];
        a[pos2] = temp;
    }
}

6.归并排序

归并排序:分治的递归算法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。

public class MergeSort {
    /**
     * 归并排序(O(N logN)) 稳定
     * <p>
     * 归并排序:分治的递归算法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
     * 若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。
     *
     * @param a   an array of Comparable items
     * @param <T> item type
     */
    public static <T extends Comparable<? super T>> void mergeSort(T[] a) {
        T[] tmpArr = (T[]) new Comparable[a.length];
        mergeSort(a, tmpArr, 0, a.length - 1);
    }

    /**
     * 归并排序主例程
     *
     * @param a     an array of Comparable items
     * @param left  start point
     * @param right end point
     * @param <T>   item type
     */
    public static <T extends Comparable<? super T>> void mergeSort(T[] a, T[] tmpArr, int left, int right) {
        if (left < right) {
            int center = (left + right) / 2;
            mergeSort(a, tmpArr, left, center);
            mergeSort(a, tmpArr, center + 1, right);
            merge(a, tmpArr, left, center + 1, right);
        }
    }

    /**
     * 合并已排序的两部分
     *
     * @param a        an array of Comparable items
     * @param tmpArr   temp array
     * @param leftPos  start point
     * @param rightPos center point
     * @param rightEnd end point
     * @param <T>      item type
     */
    public static <T extends Comparable<? super T>> void merge(T[] a, T[] tmpArr, int leftPos, int rightPos, int rightEnd) {
        int leftEnd = rightPos - 1;
        int tmpPos = leftPos;
        int numElements = rightEnd - leftPos + 1;

        while (leftPos <= leftEnd && rightPos <= rightEnd) {
            if (a[leftPos].compareTo(a[rightPos]) <= 0) {
                tmpArr[tmpPos++] = a[leftPos++];
            } else {
                tmpArr[tmpPos++] = a[rightPos++];
            }
        }
        while (leftPos <= leftEnd) {
            tmpArr[tmpPos] = a[leftPos++];
        }
        while (rightPos <= rightEnd) {
            tmpArr[tmpPos++] = a[rightPos++];
        }
        for (int i = 0; i < numElements; i++, rightEnd--) {
            a[rightEnd] = tmpArr[rightEnd];
        }
    }
}

7.堆排序

堆排序:先建立堆,这个阶段花费O(N)时间。然后我们执行N次deleteMin操作,这个阶段花费O(N logN)时间。O(N + N logN) => O(N logN)执行deleteMin操作后,按照顺序最小的元素先离开堆,通过将这些元素记录到第二个数组然后再将数组拷贝回来,得到N个元素的顺序。

public class HeadSort {

    /**
     * 堆排序(O(N logN)) 不稳定
     * <p>
     * 堆排序:堆排序先建立堆,这个阶段花费O(N)时间。然后我们执行N次deleteMin操作,这个阶段花费O(N logN)时间。O(N + N logN) => O(N logN)
     * 执行deleteMin操作后,按照顺序最小的元素先离开堆,通过将这些元素记录到第二个数组然后再将数组拷贝回来,得到N个元素的顺序。
     *
     * @param a   an array of Comparable items
     * @param <T> item type
     */
    public static <T extends Comparable<? super T>> void heapSort(T[] a) {
        for (int i = a.length / 2 - 1; i >= 0; i--) {
            perDown(a, i, a.length);
        }
        for (int i = a.length - 1; i > 0; i--) {
            swapReference(a, 0, i);
            perDown(a, 0, i);
        }
    }

    /**
     * 获取左儿子节点位置
     *
     * @param i 当前位置
     * @return 左儿子节点位置
     */
    private static int leftChild(int i) {
        return 2 * i + 1;
    }

    /**
     * 堆下滤
     *
     * @param a   an array of Comparable items
     * @param i   the position from which to percolate down
     * @param n   the logical size of the binary heap
     * @param <T> item type
     */
    private static <T extends Comparable<? super T>> void perDown(T[] a, int i, int n) {
        int child;
        T tmp;
        for (tmp = a[i]; leftChild(i) < n; i = child) {
            child = leftChild(i);
            if (child != n - 1 && a[child].compareTo(a[child + 1]) < 0) {
                child++;
            }
            if (tmp.compareTo(a[child]) < 0) {
                a[i] = a[child];
            } else {
                break;
            }
        }
        a[i] = tmp;
    }

    /**
     * 交换数组两个位置的元素
     *
     * @param a    an array of Comparable items
     * @param pos1 position1
     * @param pos2 position2
     * @param <T>  item type
     */
    private static <T extends Comparable<? super T>> void swapReference(T[] a, int pos1, int pos2) {
        T temp = a[pos1];
        a[pos1] = a[pos2];
        a[pos2] = temp;
    }
}

二、线性时间的排序

8.计数排序

计数排序:数组由小于 M 的正整数组成,使用一个大小为 M 的 count 数组,初始化为全0。循环数组,每读入一个 item,count[item] + 1,循环完毕后扫描 count 数组。

    /**
     * 计数排序(O(N+M)) 稳定
     * <p>
     * 计数排序:数组由小于 M 的正整数组成,使用一个大小为 M 的 count 数组,初始化为全0。
     * 循环数组,每读入一个 item,count[item] + 1,循环完毕后扫描 count 数组。
     *
     * @param arr an array
     * @param top 桶子数目 M,元素上界
     */
    public static void countingSort(int[] arr, int top) {
        int[] count = new int[top + 1];
        for (int e : arr) {
            count[e]++;
        }
        int index = 0;
        for (int i = 0; i < top + 1; i++) {
            if (count[i] > 0) {
                for (int j = count[i]; j > 0; j--) {
                    arr[index++] = i;
                }
            }
        }
    }

9.桶排序

桶排序:将数组元素分到数量为 M 的桶子中,每个桶子再进行排序(此段代码用的插入排序)。

    /**
     * 桶排序(O(N+M)) 稳定
     * <p>
     * 桶排序:将数组元素分到数量为 M 的桶子中,每个桶子再进行排序(此段代码用的插入排序)。
     *
     * @param arr        an array
     * @param bucketSize 桶子数目 M,元素上界
     */
    public static void bucketSort(int[] arr, int bucketSize) {
        ArrayList<LinkedList<Integer>> buckets = new ArrayList<>(bucketSize);
        for (int i = 0; i < bucketSize; i++) {
            buckets.add(new LinkedList<>());
        }
        for (int e : arr) {
            insertionSort(buckets.get(e / bucketSize), e);
        }
        int index = 0;
        for (LinkedList<Integer> bucket : buckets) {
            for (Integer e : bucket) {
                arr[index++] = e;
            }
        }
    }

    /**
     * 插入排序
     *
     * @param bucket 待排序的桶子
     * @param value 待放入桶子的值
     */
    public static void insertionSort(List<Integer> bucket, int value) {
        ListIterator<Integer> iterator = bucket.listIterator();
        boolean insertFlag = true;
        while (iterator.hasNext()) {
            Integer e = iterator.next();
            if (e >= value) {
                iterator.add(value);
                insertFlag = false;
            }
        }
        if (insertFlag) {
            bucket.add(value);
        }
    }

10.基数排序

基数排序:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序(例如String)。

    private static final int SIZE = 10;

    /**
     * 基数排序(O(p(N+M))) 稳定
     * <p>
     * 基数排序:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。
     * 有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序(例如String)。
     *
     * @param arr an array
     * @param digit 位数
     */
    public static void radixSort(int[] arr, int digit) {
        int mod = 10;
        int pos = 1;
        ArrayList<LinkedList<Integer>> buckets = new ArrayList<>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            buckets.add(new LinkedList<>());
        }
        for (int i = 0; i < digit; i++, mod *= 10, pos *= 10) {
            for (int e : arr) {
                int index = e % mod / pos;
                insertionSort(buckets.get(index), e);
            }
            int index = 0;
            for (LinkedList<Integer> bucket : buckets) {
                for (Integer e : bucket) {
                    arr[index++] = e;
                }
            }
            for (LinkedList<Integer> bucket : buckets) {
                bucket.clear();
            }
        }
    }

    /**
     * 插入排序
     *
     * @param bucket 待排序的桶子
     * @param value 待放入桶子的值
     */
    public static void insertionSort(List<Integer> bucket, int value) {
        ListIterator<Integer> iterator = bucket.listIterator();
        boolean insertFlag = true;
        while (iterator.hasNext()) {
            Integer e = iterator.next();
            if (e >= value) {
                iterator.add(value);
                insertFlag = false;
            }
        }
        if (insertFlag) {
            bucket.add(value);
        }
    }

计数基数排序:基数排序另一种实现,它避免使用ArrayList,取而代之一个计数器(类比计数排序)。

    private static final int BUCKETS = 256;

    /**
     * 计数基数排序(O(p(N+M))) 稳定
     * <p>
     * 计数基数排序:基数排序另一种实现,它避免使用ArrayList,取而代之一个计数器(类比计数排序)。
     *
     * @param arr an array
     * @param strLen 字符串长度(全体元素定长)
     */
    public static void radixSort(String[] arr, int strLen) {
        String[] buffer = new String[arr.length];

        String[] in = arr;
        String[] out = buffer;

        for (int pos = strLen - 1; pos >= 0; pos--) {
            //初始化桶子BUCKETS + 1个
            int[] count = new int[BUCKETS + 1];
            //计数
            for (int i = 0; i < arr.length; i++) {
                count[in[i].charAt(pos) + 1]++;
            }
            //计算起始位置
            for (int b = 1; b <= BUCKETS; b++) {
                count[b] += count[b - 1];
            }
            for (int i = 0; i < arr.length; i++) {
                out[count[in[i].charAt(pos)]++] = in[i];
            }
            String[] tmp = in;
            in = out;
            out = tmp;
        }

        if (strLen % 2 == 1) {
            System.arraycopy(in, 0, out, 0, arr.length);
        }
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值