常见排序算法汇总学习
简介
本文包含的排序算法:冒泡排序、快速排序、堆排序、归并排序、java内置排序,使用java语言实现。
问题描述:对列表A[p..r]进行排序,使得A元素按非递减顺序排列。
1. 冒泡排序
基本思想
从起始位置p开始,相邻元素比较,如果当前元素较大,则交换到相邻元素组的靠后位置。这样,一直比较、交换到末尾,就能得出本轮比较的最大元素,也是末尾元素,剩下的无序元素-1,有序元素+1.
这样进行n-1趟排序,就能得到n-1个最大元素。也就得到有序序列了。
时间复杂度
O(n2)
实现代码
public class BubbleSort {
private static void exchange(int[] A, int d1, int d2) {
int temp = A[d1];
A[d1] = A[d2];
A[d2] = temp;
}
public static void bubbleSort(int []A, int p, int r) {
for (int i = 0; i < r-p; i++) { // r-p times sort
// A[r-i+1] has been sorted, A[p..r-i] has not been sorted
// get A[r-i] every sort time, namely max element in non-sorted list A, as the minimum element in sorted list
for (int j = p+1; j <= r-i; j++) {
if (A[j-1] > A[j])
exchange(A, j-1, j);
}
}
}
}
2. 快速排序
基本思想
每趟排序找枢轴(pivot),所有左侧元素<=pivot,所有右侧元素>=pivot,pivot所在位置pivotLoc即为枢轴位置。递归地进行划分,对划分后每段列表找枢轴,直到列表无法划分下去,即完成快速排序。
找枢轴(位置简记q)的过程,也叫一趟快速排序,这里有2种方法:
第一种,是从两边往中间进行查询,以初始元素为枢轴(pivot=A[p]),发现右侧有较大元素,就放到左边,然后查看左边如果有较大元素,就放到右边。这样交替进行,最终确认枢轴位置。
另外一种,从左至右查询,以末尾元素为枢轴(pivot=A[r]),新游标 i 作为记录<=枢轴元素的最新位置,当发现如果有元素<枢轴,立马放到位置i,并且更新i 位置。这样轮训完A[p..r]后,A[i+1]左边元素A[p..i]<=pivot,右边元素>=pivot (否则就会移动到左边)
时间复杂度
O(nlogn)
实现代码
public class QuickSort {
/**
* sort A[p..r] in order
* @param A array to be sorted
* @param p first element's index of A
* @param r last element's index of A
*/
public static void quickSort(int[] A, int p, int r) {
// check index p, r
if (p < r) {
int q = partition(A, p, r);
if(p < q)
quickSort(A, p, q-1);
if(q < r)
quickSort(A, q+1, r);
}
}
/**
* print all data of array A
* @param title title to be printed
* @param A array to be printed
*/
public static void printData(String title, int[] A) {
System.out.println(title);
System.out.println(A.length);
System.out.print("[");
for (int i = 0; i < A.length; i++) {
if(i > 0) System.out.print(",");
System.out.print(A[i]);
}
System.out.println("]");
}
/**
* exchange A[d1] with A[d2]
* @param A array
* @param d1 first element to be exchanged
* @param d2 second element to be exchanged
*/
private static void exchange(int[] A, int d1, int d2) {
int temp = A[d1];
A[d1] = A[d2];
A[d2] = temp;
}
/**
* find out the pivot's location pivotLoc and relocate all element so that A[p..pivotLoc-1] <= A[pivotLoc] <= A[pivotLoc+1..r]
* @param A array to be sorted
* @param p first element's index of array A
* @param r last element's index of array A
* @return pivot's location pivotLoc
* @note sweep A from p to r
*/
public static int partition(int[] A, int p, int r) {
int x = A[r];
int i = p - 1;
for (int j = p; j < r; j++) {
if (A[j] <= x) {
i++;
// exchange A[i] and A[j]
exchange(A, i, j);
}
}
// exchange A[i+1] and A[r]
exchange(A, i+1, r);
return i+1;
}
/**
* find out the pivot's location pivotLoc and relocate all element so that A[p..pivotLoc-1] <= A[pivotLoc] <= A[pivotLoc+1..r]
* @param A array to be sorted
* @param p first element's index of array A
* @param r last element's index of array A
* @return pivot's location pivotLoc
* @note sweep A from two side to center
*/
public static int partition_2(int[] A, int p, int r) {
int pivot = A[p];
int low = p;
int high = r;
while (low < high) {
while (low < high && A[high] >= pivot) { high--;}
A[low] = A[high];
while (low < high && A[low] <= pivot) {low++;}
A[high] = A[low];
}
A[low] = pivot;
return low;
}
}
3. 堆排序
基本思想
- 构建最大堆,通过从最后一个非终端结点到根节点调整,确保整个堆都符合大堆性质。
- 输出堆顶元素到列表末尾(堆顶元素与堆末尾元素交换),从堆顶进行调整,已经交换出来的堆顶元素就形成新的有序列表。
调整:以当前结点i为根节点,确保大堆性质。如果不满足大堆性质,就将子节点最大元素与当前根节点交换,递归的调整下去,确保交换之后也是符合大堆特性。
大堆特性:A[i] >= A[left(i), 并且A[i] >= A[right(i)] , left(i), right(i)分别表示i的左孩子和右孩子。
时间复杂度
O(nlogn)
实现代码
public class HeapSort {
private static int heapSize;
/**
* get root's location in array A
* @return root's location
* @note store all heap elements using A[0..length-1]
*/
public static int root() {
return 0;
}
/**
* get last node's location in array A
* @return last node's location
*/
public static int lastNodeOfHeap() {
return heapSize - 1;
}
/**
* get left child's location in array A
* @param i current node's location in array A
* @return left child's location
*/
public static int left(int i) {
return i * 2 + 1;
}
/**
* get right child's location in array A
* @param i current node's location in array A
* @return right child's location
*/
public static int right(int i) {
return i*2 + 2;
}
private static void exchange(int[] A, int d1, int d2) {
int temp = A[d1];
A[d1] = A[d2];
A[d2] = temp;
}
/**
* adjust heap to ensure max heap feature
* @param A
* @param i
*/
public static void maxHeapify(int[] A, int i) {
int l = left(i);
int r = right(i);
int largest;
if(l < heapSize && A[l] > A[i])
largest = l;
else largest = i;
if(r < heapSize && A[r] > A[largest]) {
largest = r;
}
if (largest != i) {
// exchange A[largest] with A[i]
exchange(A, largest, i);
maxHeapify(A, largest); // dont forget adjust the child tree if adjust current node
}
}
/**
* build max heap
* @param A
*/
public static void buildMaxHeap(int[] A) {
heapSize = A.length;
// sweep from last non-leaf node to root node
for (int i = A.length / 2 - 1; i >= 0; i--) {
maxHeapify(A, i);
}
}
/**
* sort A[0..length-1]
* @param A array to be sorted
* @note store heap using A[0..length-1], so the root node is A[0], and the last node is A[heapSize-1]
*/
public static void heapSort(int[] A) {
// build max heap
buildMaxHeap(A);
// exchange root with last node, then max heapify the heap
// output heap peek element to ordered array A from length-1 to 1. the left 0 should be the minimum element
for (int i = A.length - 1; i >= 1; i--) {
exchange(A, 0, i);
heapSize--;
maxHeapify(A, 0);
}
}
}
4. 归并排序
基本思想
利用分治法,将待排序列表进行递归均分,划分到最后每个列表只有一个元素,再逐层合并为有序列表。分治法一般需要对划分后子问题进行处理(问题解决),不过因为归并排序划分到最后只有每个列表1个元素,而排序是无需做特殊处理的。
本文提供2种子有序列表归并为一个大的有序列表的方法:
设当前列表A[p..r]划分成的2个子列表A[p..q], A[q+1..r]已有序,现在要将2个子列表合并回。
- 分别复制2个子列表A[p..q]和A[q+1..r]到L[0..n1-1]和R[0..n2-1],然后再合并回A[p..r]。代码中L[n1]和R[n2]都用到了∞(无穷大)作为哨兵;
- 创建出一个与A[p..r]一样大的新列表L,然后按序号从A[p..q]和A[q+1..r]中找到最小数填入L中,得到有序的L。最后将L复制回A即为有序列表。
时间复杂度
O(nlogn)
实现代码
public class MergeSort {
/**
* merge two ordered list A[p..q] and A[q+1..r] to ordered A[p..r]
* @param A list to be sorted
* @param p first element location of A
* @param q partition element location of A
* @param r last element location of A
* @note use two list and sentry, then merge the two sorted list to A[p..r]
*/
public static void merge(int[] A, int p, int q, int r) {
int n1 = q - p + 1;
int n2 = r - q;
int[] L = new int[n1 + 1];
int[] R = new int[n2 + 1];
// copy A[p..q] to L[0..n1-1]
for (int i = 0; i < L.length - 1; i++) {
L[i] = A[p+i];
}
// copy A[q+1..r] to R[0..n2-1]
for (int j = 0; j < R.length - 1; j++) {
R[j] = A[q+1+j];
}
// set sentry using ∞ (max value)
L[n1] = Integer.MAX_VALUE;
R[n2] = Integer.MAX_VALUE;
int i = 0;
int j = 0;
for (int k = p; k <= r; k++) { // case k==r is necessary, because r is the element with max index in A , not the length
if (L[i] <= R[j]) { // "=" can't be absent, otherwise the sort is not stable
A[k] = L[i];
i++;
}else {
A[k] = R[j];
j++;
}
}
}
/**
* merge two ordered list A[p..q] and A[q+1..r] to ordered A[p..r]
* @param A list to be sorted
* @param p first element location of A
* @param q partition element location of A
* @param r last element location of A
* @note merge two segment to new list, then re-copy back to origin list
*/
public static void merge_2(int[] A, int p, int q, int r) {
int[] L = new int[r - p + 1];
/* merge A[p..q] and A[q+1..r] to L[0..length-1] by asc order */
int i = p;
int j = q+1;
int k = 0;
while (i <= q && j <= r) {
if(A[i] <= A[j]) { // "=" can't be absent, otherwise the sort is not stable
L[k] = A[i];
i++;
}
else {
L[k] = A[j];
j++;
}
k++;
}
// copy left element to L
while (i <= q) {
L[k++] = A[i++];
}
while (j <= r) {
L[k++] = A[j++];
}
// copy new sorted list L to origin list A
for (int t = 0; t < L.length; t++) {
A[p+t] = L[t];
}
}
public static void mergeSort(int[] A, int p, int r) {
if (p < r) {
int q = (p + r) / 2;
mergeSort(A, p, q);
mergeSort(A, q+1, r);
merge(A, p, q, r);
}
}
}
5. Java内置数组排序
基本思想
利用Java内置Arrays.sort 进行排序(底层是调用归并排序(对象类数组)或者快速排序(基础类型数组))。如果是对对象列表(Set, Map, List等)进行排序的话,需要使用Collections.sort()以及提供Comparator,或者实现Comparable的compareTo方法。
时间复杂度
同归并排序O(nlogn)
public class ArraysSort {
public static void arraysSort(int[] A, int p, int r) {
if (p < r)
Arrays.sort(A, p, r);
}
}
C/C++ 也有类似的库函数:qsort(), 需要#include <ctype.h>
可参见:C 库函数 - qsort()
6. 计数排序
基本思想
计数排序对输入数据范围有严格要求,因为要通过计数数组索引直接映射为输入数组值,如果输入数据范围过大,会导致计数数组所需空间膨胀,但是利用率却很低。
这里简化成对输入数据A[i] ∈ [0, k], k< 50000且k∈Z, i=0,1,2..,n-1 .
通过对A[i]同样大小元素计数,运算得到<= A[i]元素个数k, A[i]的位置即为k (A从0开始存储数据,如果是从1开始A[i]位置k+1)
例如,< x的元素有17个,那么x就应该存放在位置17 (假设不存在相同元素)。对于包含相同元素情况,为了使排序稳定,可以从最后一个元素开始放起,直到最先位置的元素 。
这样,问题就主要转换成了如何求解 <= A[i]元素个数了。
由于A[i]范围有限,所以可以直接用一个数组C[0..k] (k = max{A[i]} )用来对A各元素计数。c[A[j]]表示A[j]出现次数,∑c[A[j]]表示 <= A[j] 次数
伪代码:
最初, c[i] = count(x = A[j]), i = A[j]
计算后, C[i] = ∑count(x = A[j]), i = A[j],
也就是C[i] = count(x<=A[j]), i = A[j]
时间复杂度
O(logn) , 当k=O(logn)时
实现代码
public class CountingSort {
/**
* counting sort
* @param A input list, raw data
* @param B output sorted list
* @param k A[i] ∈ [0, k] and k ∈ Z, i=0,1,2,...,A.length-1
*/
public static void cSort(int[] A, int[] B, int k) {
if (k <= 0) return;
int[] c = new int[k + 1]; // counting array, c[0..k] stores count(A[0..length-1])
// init counting array c[]
for (int i = 0; i < c.length; i++) {
c[i] = 0;
}
// set c[i] = count(x == A[j]), i = A[j]
for (int j = 0; j < A.length; j++) {
c[A[j]] ++;
}
// set c[i] = count(x <= A[j]), i = A[j]
for (int i = 1; i < c.length; i++) {
c[i] = c[i-1] + c[i];
}
// now, we know element i (i = A[j]) should be located in index c[i] of B
// Provided that count(i) > 1, let j vary from A.length-1 to 0 to ensure a stable sorting
for (int j = A.length-1; j >= 0 ; j--) {
B[c[A[j]] - 1] = A[j]; // B[0..length-1] stores sorted result of A[0..length-1], and location should exclude count value of itself.
// B[c[A[j]]] = A[j];
c[A[j]] --;
}
}
/**
* interface of counting sort, sort A[p..r]
* @param A input list, raw data
* @param p first element's location
* @param r last element's location
*/
public static void countingSort(int[] A, int p, int r) {
int max = A[p];
int min = A[p];
for (int i = p+1; i < r; i++) {
if (max < A[i]) max = A[i];
if (min > A[i]) min = A[i];
}
int[] B = new int[A.length];
cSort(A, B, max);
// copy sorted B[0..length-1] to A[0..length-1]
for (int i = 0; i < A.length; i++) {
A[i] = B[i];
}
}
}
完整Code
包括测试代码:gitee_fortunely_algorithm
参考
- 算法导论(第三版)