常见排序算法(Java实现)

目录

排序算法概述

一、基于比较的排序

1.选择排序

2.冒泡排序

3.插入排序

4.希尔排序

5.快速排序

6.归并排序

7.堆排序

二、线性时间的排序

8.计数排序

9.桶排序

10.基数排序


排序算法概述

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

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

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

常见排序算法(Java实现)_第1张图片

算法复杂度:

表格引自: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²)) 不稳定
     * 

* 选择排序:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置, * 然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 * * @param a an array of Comparable items * @param item type */ public static > 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 item type */ private static > void swapReference(T[] a, int pos1, int pos2) { T temp = a[pos1]; a[pos1] = a[pos2]; a[pos2] = temp; }

2.冒泡排序

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

    /**
     * 冒泡排序(O(N²)) 稳定
     * 

* 冒泡排序:把较小的元素往前调或者把较大的元素往后调,通过对相邻两个元素进行大小的比较, * 根据比较结果和算法规则对该二元素的位置进行交换,这样逐个依次进行比较和交换。 * * @param a an array of Comparable items * @param item type */ public static > 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²)) 稳定
     * 

* 插入排序:由N-1趟排序组成,对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。 * * @param a an array of Comparable items * @param item type */ public static > 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³)) 不稳定
     * 

* 希尔排序:使用一个序列h1,h2,...,ht,叫做增量序列。 * 只要 h1=1,任何增量序列都是可行的。不过,有些增量序列比另外一些增量序列要好。Shell建议的序列:ht=N/2和h(k)=h(k+1)/2。 * * @param a an array of Comparable items * @param item type */ public static > 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)) 不稳定
     * 

* 快速排序:分治的递归算法,在一趟排序中选定一个枢纽元,将要排序的数据分割成两部分, * 其中一部分的所有数据都比枢纽元大,另外一部分的所有数据都比枢纽元小,对这两部分数据分别进行快速排序。 * * @param a an array of Comparable items * @param item type */ public static > 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 item type */ public static > 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 item type * @return median */ private static > 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 item type */ private static > 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)) 稳定
     * 

* 归并排序:分治的递归算法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。 * 若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法。 * * @param a an array of Comparable items * @param item type */ public static > 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 item type */ public static > 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 item type */ public static > 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)) 不稳定
     * 

* 堆排序:堆排序先建立堆,这个阶段花费O(N)时间。然后我们执行N次deleteMin操作,这个阶段花费O(N logN)时间。O(N + N logN) => O(N logN) * 执行deleteMin操作后,按照顺序最小的元素先离开堆,通过将这些元素记录到第二个数组然后再将数组拷贝回来,得到N个元素的顺序。 * * @param a an array of Comparable items * @param item type */ public static > 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 item type */ private static > 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 item type */ private static > 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)) 稳定
     * 

* 计数排序:数组由小于 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)) 稳定
     * 

* 桶排序:将数组元素分到数量为 M 的桶子中,每个桶子再进行排序(此段代码用的插入排序)。 * * @param arr an array * @param bucketSize 桶子数目 M,元素上界 */ public static void bucketSort(int[] arr, int bucketSize) { ArrayList> 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 bucket : buckets) { for (Integer e : bucket) { arr[index++] = e; } } } /** * 插入排序 * * @param bucket 待排序的桶子 * @param value 待放入桶子的值 */ public static void insertionSort(List bucket, int value) { ListIterator 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))) 稳定
     * 

* 基数排序:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。 * 有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序(例如String)。 * * @param arr an array * @param digit 位数 */ public static void radixSort(int[] arr, int digit) { int mod = 10; int pos = 1; ArrayList> 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 bucket : buckets) { for (Integer e : bucket) { arr[index++] = e; } } for (LinkedList bucket : buckets) { bucket.clear(); } } } /** * 插入排序 * * @param bucket 待排序的桶子 * @param value 待放入桶子的值 */ public static void insertionSort(List bucket, int value) { ListIterator 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))) 稳定
     * 

* 计数基数排序:基数排序另一种实现,它避免使用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); } }

 

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