名称 | 最差时间复杂度 | 最佳时间复杂度 | 空间复杂度 | 备注 |
选择排序 | O(n^2) | O | ||
插入排序 | O(n^2) | |||
冒泡排序 | O(n^2) | O(n) | ||
归并排序 | O(nlogn) | 时间效率高,但是归并时需要临时数组,空间复杂度较高 | ||
快速排序 | O(n^2) | O(nlogn) (平均时间) | ||
堆排序 | O(nlogn) | |||
桶排序 | O(n+N) | 限于小整数排序,时间效率高于前几种 | ||
基数排序 | O(dn) | 限于小整数排序,时间效率高于前几种 |
1. 选择排序(Selection Sort)
选择排序的思想是从数组中选择最小,次小,次次小, .......,的元素依次加到前面的数组中。
Code:
package sort;
public class SelectionSort {
public static void selectionSort(int[] list) {
int index;
int currentElement;
for (int i = 0; i < list.length - 1; i++) {
index = i;
currentElement = list[i];
for (int j = i + 1; j < list.length; j++) {
if (list[j] < currentElement) {
currentElement = list[j];
index = j;
}
}
if (index != i) {
list[index] = list[i];
list[i] = currentElement;
}
}
}
public static void main(String[] args) {
int[] list = {7,3,5,7,3,2,1,2};
selectionSort(list);
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
}
}
2.插入排序(Insertion Sort)
插入算法的思想是从第二个元素开始,依次插入到前面排好序的序列中,注意插入遍历从后往前遍历数组,还有下标越界问题!。程序就参考教材上的比较简单。
Code:
package sort;
public class InsertionSort {
public static int[] insertionSort(int[] list) {
for (int i = 1; i < list.length; i++) {
int k;
int currentElement = list[i];
for (k = i - 1; k >= 0 && list[k] > currentElement; k--) {
list[k + 1] = list[k];
}
list[k + 1] = currentElement;
}
return list;
}
public static void main(String[] args) {
int[] list = {7,3,5,7,3,2,1,2};
list = insertionSort(list);
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
}
}
3. 冒泡排序(Bubble Sort)
冒泡排序算法需要遍历几次数组,在每次遍历中,比较 连续相邻 的元素。如果某一对元素是降序,则互换它们的位置;否则,保持不变。冒泡排序法需要遍历 n-1 次数组,每次遍历最佳情况是不用交换,则时间复杂度为 O(n) ,最差情况是每次都要交换,对于第 k 个要交换 n-1-k 次,此时时间复杂度是 O(n^2)
Trick:如果在某次遍历中没有发生交换,说明所有元素都已排好序了,不需再次交换。使用此特性可以改进算法。
Code:
package test;
public class Test {
public static void main(String[] args) {
int[] list = {3,2,1};
list = bubbleSort(list);
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
}
public static int[] bubbleSort (int[] list) {
boolean nextPass = true;
for (int i = 0; i < list.length - 1 && nextPass; i++) {
nextPass = false;
for (int j = 0; j < list.length - 1 - i; j++) {
if (list[j] > list[j + 1]) {
int tmp = list[j];
list[j] = list[j + 1];
list[j + 1] = tmp;
nextPass = true;
}
}
}
return list;
}
}
4. 归并算法(MergeSort)
归并算法可以递归地描述为:算法将数组分为两半,对每部分递归地应用归并排序。在两部分都排好序后,对它们进行归并。归并排序的复杂度为 O(n log n)。
Code:
package sort;
import java.awt.List;
public class MergeSort {
public static int[] merge(int[] list) {
int[] firstHalf = new int[list.length / 2];
System.arraycopy(list, 0, firstHalf, 0, firstHalf.length);
if (firstHalf.length > 1)
firstHalf = merge(firstHalf);
int[] secondHalf = new int[list.length - list.length / 2];
System.arraycopy(list, list.length / 2, secondHalf, 0, secondHalf.length);
if (secondHalf.length > 1)
secondHalf = merge(secondHalf);
list = merge(firstHalf, secondHalf);
return list;
}
public static int[] merge(int[] list1, int[] list2) {
int[] tmp = new int[list1.length + list2.length];
int current1 = 0, current2 = 0, current3 = 0;
while (current1 < list1.length && current2 < list2.length) {
if (list1[current1] < list2[current2])
tmp[current3++] = list1[current1++];
else
tmp[current3++] = list2[current2++];
}
while (current1 < list1.length) {
tmp[current3++] = list1[current1++];
}
while (current2 < list2.length) {
tmp[current3++] = list2[current2++];
}
return tmp;
}
public static void main(String[] args) {
int[] list = {4,2,7,4,7,9,8,1};
list = merge(list);
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
}
}
5. 快速排序 (QuickSort)
快速排序算法在数组中选择一个称为主元(pivot)的元素,将数组分为两部分,使得 第一部分中的所有元素都小于或等于主元,而第二部分的所有元素都大于主元。对第一部分递归地应用快速排序算法,然后对第二部分递归地应用快速排序算法。
在最差情况下,划分由 n 个元素构成的数组需要进行 n 次比较和 n 次移动。因此划分所需时间为 O(n) 。最差情况下,每次主元会将数组划分为一个大的子数组和一个空数组。这个大的子数组的规模是在上次划分的子数组的规模减 1 。该算法需要 (n-1)+(n-2)+...+2+1= O(n^2) 时间。
在最佳情况下,每次主元将数组划分为规模大致相等的两部分。设 T(n) 表示使用快速排序算法对包含 n 个元素的数组排序所需的时间,因此,和归并排序的分析相似,快速排序的 T(n)= O(nlogn)。
package sort;
import java.awt.List;
public class QuickSort {
public static int[] quickSort(int[] list) {
quickSort(list, 0, list.length - 1);
return list;
}
public static int[] quickSort(int[] list, int first, int last) {
if (first < last) {//递归地对主元(pivot)前后的数组进行快排
int pivotIndex = partition(list, first, last);
quickSort(list, first, pivotIndex - 1);
quickSort(list, pivotIndex + 1, last);
}
return list;
}
public static int partition(int[] list, int first, int last) {//操作过程见下图
int pivot = list[first], low = first + 1, high = last;
//寻找前半数组中大于主元的元素下标和后半数组中小于或等于主元的元素下标
while (high > low) {
while (pivot >= list[low] && low <= high)
low++;
while (pivot < list[high] && low <= high)
high--;
//交换两个元素
if (low < high) {
int tmp = list[low];
list[low] = list[high];
list[high] = tmp;
}
}
//插入主元进适当位置
while (list[high] >= pivot && high > first)
high--;
if (list[high] < pivot) {
list[first] = list[high];
list[high] = pivot;
return high;
}
else {
return first;
}
}
public static void main(String[] args) {
int[] list = {2,6,3,5,4,1,8,45,2};
list = quickSort(list);
for (int i = 0; i < list.length; i++) {
System.out.println(list[i]);
}
}
}
public class QuickSort {
public static void main(String[] args) {
int[] array = {1,1,1,1,1,1,1,1};
QuickSort qs = new QuickSort();
qs.quickSort(array);
for(int ele : array)
System.out.print(ele + " ");
}
public void quickSort(int[] array) {
quickSort(array, 0, array.length - 1);
}
public void quickSort(int[] array, int start, int end) {
if(start > end) //注意判断终止条件
return;
int index = partation(array, start, end);//主元所在位置
quickSort(array, start, index - 1);//递归地对主元前的数组进行快排
quickSort(array, index + 1, end);//递归地对主元后的数组进行快排
}
public int partation(int[] array, int start, int end) {
int pivot = array[start];
int low = start;
int high = end + 1;
while(high > low) {
//这里写法一定要注意,首先确保没有数组指针越界访问,然后要确保当low和high指向元素大小相同时不陷入死循环如
while(low < end && array[++low] < pivot) {}
while(high > start && array[--high] > pivot) {}
/* 如果用下面两行代码代替上面两行代码的话,当array[low] == array[high] 的时候就会陷入死循环
while(array[low] < pivot) low++;
while(array[high] > pivot) high--;
*/
if(high > low)
swap(array, low, high);
}
swap(array, start, high);
return high;
}
public void swap(int[] array, int a, int b) {
int tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
}
归并和快排都用了分治法。
对于归并排序,大量的工作是将两个子线性表进行归并,归并是在子线性表都排好序之后进行的。
对于快速排序,大量的工作是将线性表划分为两个子线性表,划分是在子线性表排好序后进行的。
在最差情况下, 归并时间效率 > 快排时间效率 ,但是在平均情况下,两者的效率相同。
空间效率上, 归并空间效率 < 快排空间效率。 原因是 归并排序在归并两个子数组时需要一个临时数组,而快速排序不需要额外的数组空间。
6. 堆排序(HeapSort)
堆排序使用的是二叉堆,它是一棵完全二叉树。首先了解一下堆的结构和特性,这对我们理解堆排序有着重要的意义。
======================================扩展=============================================
扩展:堆(heap)是一棵具有以下属性的二叉树:
- 它是一棵完全二叉树(除了最后一层没填满以及最后一层的叶子都是偏左放置的,如果一棵二叉树的每一层都是满的,那么这颗二叉树就是完全的)
- 每个节点大于或等于它的任意一个孩子
- 首先将新结点加到堆的末尾
- 将它与它的父亲比较,如果比它父亲大,就交换两者位置
- 重复 2 直到它小于或等于父亲
- 删除根结点
- 将根结点替换为堆的最后一个元素
- 比较替换的元素与子结点大小,交换替换的元素和较小元素的位置
- 重复 3 ,直到替换的元素到合适的位置
=======================================================================================================
下面继续正题,由于堆保持 父结点总是大于子结点 的特性,我们可以将 list 中的元素依次 add 到堆中,重建堆的过程中已经排好序了,接下来我们利用 删除根结点 的办法依次获取由大到小的元素。
堆排序的时间复杂度与归并排序相同,为 O(n logn),但是堆排序不需要额外的数组空间,相较于归并排序,堆排序的空间效率更高。
7.桶排序(BucketSort)
上面所讲的四种算法可以用在比较任何键值类型(如 整数、字符串以及任何 Comparable 的对象)上,但是这些基于比较的排序算法中没有时间复杂度好过 O(n logn) 的算法。桶排序适用于 小整数 的排序,无需比较键值。
桶排序的工作方式如下。假设键值的范围是 0 到 N-1 。我们需要 N 个标记为 0, 1, ...,N-1 的桶。如果元素的键值是 i, 那么久将钙元素放入桶 i 中。每个桶中都存在和键值具有相同值的元素,可以使用 ArrayList 来实现一个桶。
桶排序的时间复杂度为 O(n+N) ,空间复杂度也为 O(n+N) ,其中 n 是指线性表的大小。
8. 基数排序
当 N 过大时,在 5 中介绍的桶排序就不是很可取了。此时可以用基数排序。
基数排序是基于桶排序的,不过它只用 十个 桶。
通常,基数排序需要耗费 O(dn) 时间对带整数键值的 n 个元素排序,其中 d 是所有键值中基数位置的最大值。