这里把常见的排序算法做个总结,包括
1). SelectSort–选择排序
2). InsertSort–插入排序
3). BubbleSort–冒泡排序
4). ShellSort–希尔排序
5). MergeSort–归并排序
6). QuickSort–快速排序
7). HeapSort–堆排序
8). CountSort–计数排序
9). RadixSort–基数排序
10). BucketSort–桶排序这里考虑从三个方面分析;
1. Time complexity:时间复杂度
2. Space requirement:空间要求
3. Stability:稳定性
1. SelectSort
2 层循环:
外层:共需要选择n-1次
内层:选择之后元素较小者索引,交换后左移。
public class SelectSort {
public void selectSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex])
minIndex = j;
}
swap(arr, minIndex, i);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(n^2) | constant | stable |
2. InsertSort
2层循环:左边元素为已排序数组,将新元素插入已排序数组中。
第一层:从第2个元素开始,依次插入;
第二层:找到插入位置。
public class InsertSort {
public void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
int temp = arr[i];
int j = i - 1;
for (; j >= 0 && arr[j] > temp; j--) {
arr[j + 1] = arr[j];
}
// Why here arr[j+1] = arr[i] not available? The sequence is not
// ordered then.
arr[j + 1] = temp;
}
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(n^2) | constant | stable |
3. BubbleSort
2层循环:如果为无序状况,运行速度超级慢:因为要进行多次交换。
第一层:共需进行n-1次冒泡操作。
第二层:每次将当前剩余最大值下沉。
public class BubbleSort{
public void bubbleSort(int[] arr){
for(int i=0; i<arr.length-1; i++){
for(int j=0; i+j<arr.length-1; j++){
if(arr[j]>arr[j+1])
swap(arr, j, j+1);
}
}
}
public void bubbleSortRevised(int[] arr){
boolean sorted = true;
for(int i=0; i<arr.length-1; i++){
for(int j=0; i+j<arr.length-1; j++){
if(arr[j]>arr[j+1]){
swap(arr, j, j+1);
sorted = false;
}
}
if(sorted)
break;
}
}
private void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(n^2) | constant | stable |
4. ShellSort
这是插入排序的一种变形; 基于2个原因提高了插入排序效率
1. 当待排序数组已经有序时,插入排序效率较高;
2. 插入排序整体效率较低,因为每次只移动一个元素。
public class ShellSort {
public void shellSort(int[] arr) {
for (int gap = arr.length / 2; gap >= 1; gap = (gap / 2 == 1) ? 1 : (int) (gap / 2.2)) {
for (int i = gap; i < arr.length; i++) {
int temp = arr[i];
int j = i - gap;
for (; j >= 0 && arr[j] > temp; j -= gap)
arr[j + gap] = arr[j];
arr[j + gap] = temp;
}
}
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(nlgn)~O(n^1.5) | constant | stable |
5. MergeSort
归并排序:思想是divide and conquer, 是一种递归的排序算法。
public class MergeSort {
public void mergeSort(int[] arr) {
if (arr.length < 2)
return;
int mid = arr.length / 2;
int[] left = new int[mid];
int[] right = new int[arr.length - mid];
for (int i = 0; i < mid; i++) {
left[i] = arr[i];
}
for (int i = mid; i < arr.length; i++) {
right[i - mid] = arr[i];
}
mergeSort(left);
mergeSort(right);
merge(arr, left, right);
}
private void merge(int[] arr, int[] left, int[] right) {
int i = 0, j = 0, k = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j])
arr[k++] = left[i++];
else
arr[k++] = right[j++];
}
while (i < left.length)
arr[k++] = left[i++];
while (j < right.length)
arr[k++] = right[j++];
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(nlgn) | O(n) | stable |
6. QuickSort
一种基于比较的递归排序算法,综合表现较好,故而名为‘快速排序算法’。
public class QuickSort {
public void quickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
private void quickSort(int[] arr, int low, int high) {
if (low > high)
return;
int pivot = partition(arr, low, high);
quickSort(arr, low, pivot - 1);
quickSort(arr, pivot + 1, high);
}
private int partition(int[] arr, int low, int high) {
int pivotIndex = low;
int pivotValue = arr[high];
for (int i = low; i < high; i++) {
if (arr[i] < pivotValue)
swap(arr, pivotIndex++, i);
}
swap(arr, pivotIndex, high);
return pivotIndex;
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(nlgn) | constant | stable |
7. HeapSort
堆排序是通过建立最大堆(最小堆),然后不断删除最大堆根节点元素实现排序的。是一种不稳定的排序算法。
public class HeapSort {
public void heapSort(int[] arr) {
int n = arr.length;
for (int k = n / 2; k >= 1; k--)
percolateDown(arr, k, n);
while (n > 1) {
swap(arr, 1, n--);
percolateDown(arr, 1, n);
}
}
private void percolateDown(int[] arr, int k, int n) {
while (2 * k <= n) {
int j = 2 * k;
if (j < n && arr[j - 1] < arr[j]) // off-by-one
j++;
if (arr[k - 1] >= arr[j - 1]) // off-by-one
break;
swap(arr, k, j);
k = j;
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i - 1];
arr[i - 1] = arr[j - 1];
arr[j - 1] = temp;
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(nlgn) | constant | unstable |
8. CountSort
之前所有的排序算法时间复杂度接近于nlgn, 空间要求一般为常量或O(n), 接下来的三种排序算法:计数排序、基数排序、桶排序都是在某种情况下性能为O(n)。
但为什么这几个排序算法并没有MergeSort、 QuickSort等出名呢?原因在于其时间复杂度和空间要求上:
1. O(n+k): 如果这里的k很大,则复杂度可能会比传统的归并、快高;
2. O(k): 这里的 k 是最大值,如果k值过大,则可能导致空间要求异常巨大;
3. 且这里排序要求条件是:非负数
public void countSort(int[] arr) {
// 1. Initialize maxValue to buckets array.
int maxValue = arr[0];
for (int val : arr) {
if (val > maxValue)
maxValue = val;
}
int[] buckets = new int[maxValue + 1];
// 2. Scatter elements into buckets.
for (int val : arr) {
buckets[val]++;
}
// 3. Gather elements back.
int index = 0;
int[] sortedArr = new int[arr.length];
for (int i = 0; i < buckets.length; i++) {
for (int j = 0; j < buckets[i]; j++)
sortedArr[index++] = i;
}
System.arraycopy(sortedArr, 0, arr, 0, arr.length);
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(n+k) | O(k) | stable |
9. RadixSort
计数排序中每个数据分配一个位置,而基数排序不同点是,多个元素分到基数个桶中,并按位进行排序。
时间复杂度:O(nw), 其中n代表的是数组的大小, w表示数据所占的位数。
空间复杂度:O(w+N),最差情形,考虑
public void radixSort(int[] arr) {
final int bucketCnt = 10;
// 1. Initialize bucketCnt buckets.
LinkedList<Integer>[] buckets = new LinkedList[bucketCnt];
for (int i = 0; i < bucketCnt; i++)
buckets[i] = new LinkedList<Integer>();
// 2. Scatter and Gather for a few iterations.
int exp = 1;
boolean sorted = false;
while (!sorted) {
sorted = true;
for (int item : arr) {
int bucket = item / exp % 10;
buckets[bucket].add(item);
if (bucket > 0)
sorted = false;
}
exp *= 10;
int index = 0;
for (LinkedList<Integer> list : buckets) {
while (!list.isEmpty())
arr[index++] = list.remove();
}
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(wn) | O(n+w) | stable |
10. BucketSort
桶排序:是一种泛化的计数排序算法,当桶排序的bucketCnt选择为最大元素个数时,即为计数排序。桶排序主要通过将数据映射到相应桶中、对每个桶进行排序、将排序后的数据收集到原数组中。这样就实现了数组的排序。
1. 确定桶排序中桶的数量,并初始化桶;
2. 将数组中数据以某种散列规则映射到桶中;
3. 将所有的桶中元素进行排序;
4. 将排序好的桶中元素收集到原数组中,这样就实现了排序。
public void bucketSort(int[] arr) {
// 1. Initialize the buckets.
final int bucketCnt = 10;
LinkedList<Integer>[] buckets = new LinkedList[bucketCnt];
for (int i = 0; i < bucketCnt; i++)
buckets[i] = new LinkedList<Integer>();
// 2. Scatter elements to buckets.
int minValue = arr[0], maxValue = arr[0];
for (int val : arr) {
if (val < minValue)
minValue = val;
if (val > maxValue)
maxValue = val;
}
double interval = ((double) (maxValue - minValue + 1)) / bucketCnt;
for (int val : arr) {
int bucket = (int) ((val - minValue) / interval);
buckets[bucket].add(val);
}
// 3. Sort each collection.
for (int i = 0; i < bucketCnt; i++)
Collections.sort(buckets[i]);
// 4. Gather the elements.
int index = 0;
for (int i = 0; i < bucketCnt; i++) {
while (!buckets[i].isEmpty())
arr[index++] = buckets[i].remove();
}
}
时间复杂度 | 额外空间 | 稳定性 |
---|---|---|
O(n+k) | O(n+k) | stable |