目录
一、排序定义
1.输入:n个数,a1,a2,……,an
2.输出:n个数,a1',a2',……,an',使得a1'≤a2'≤……≤an'。
二、排序分类
1.in-place sort(不占用额外内存或占用常数内存):插入排序、选择排序、冒泡排序、堆排序、快速排序;
out-place sort:归并排序、计数排序、基数排序、桶排序。
2.稳定排序:插入排序、冒泡排序、归并排序、基数排序、计数排序、桶排序;
不稳定排序:选择排序、快速排序、堆排序。
稳定性:如果ai=aj,排序前ai在aj之前,排序后ai还在aj之前。
3.比较排序:冒泡排序、选择排序、插入排序、归并排序、堆排序、快速排序;
非比较排序:计数排序、基数排序、桶排序。
三、常见排序算法性能
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
简单选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
直接插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(nlogn)—O(n^2) | O(n^2) | O(1) | 不稳定 | |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(logn)—O(n) | 不稳定 |
四、冒泡排序
1.重复地走访过要排序的元素,依次比较相邻的两个元素,如果他们的顺序错误就把它们调换过来,知道没有元素在需要交换,排序完成。
2.图解
3.编程实现
//分类——内部比较排序
//数据结构——数组
//最差时间复杂度——O(n^2)
//最优时间复杂度——O(n)
//平均时间复杂度——O(n^2)
//辅助空间——O(1)
//稳定性——稳定
public class BubbleSort {
public static void Swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void bubbleSort(int[] a, int n) {
for(int i = 0; i < n - 1; i++) {
for(int j = 0; j < n - 1 - i; j++) {
if(a[j] > a[j + 1]) {
Swap(a, j, j + 1);
}
}
}
}
}
4.冒泡排序改进——鸡尾酒排序
(1)鸡尾酒排序,也叫定向冒泡排序,与传统的冒泡排序的区别在于从低到高比较然后从高到低比较。
(2)代码实现
public class CocktailSort {
public static void cocktailSort(int[] a, int n) {
int left = 0;
int right = n - 1;
while (left < right) {
for(int i = left ; i < right; i++) {
if(a[i] > a[i + 1]) {
new Swap();
Swap.swap(a, i, i + 1);
}
}
right--;
for(int i = right; i > left; i--) {
if(a[i - 1] > a[i]) {
new Swap();
Swap.swap(a, i - 1, i);
}
}
left++;
}
}
}
五、选择排序
1.初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列,然后再从剩余未排序序列中继续寻找最小(大)元素,以此类推。
2.图解
3.代码实现
//分类——内部比较排序
//数据结构——数组
//最差时间复杂度——O(n^2)
//最优时间复杂度——O(n^2)
//平均时间复杂度——O(n^2)
//辅助空间——O(1)
//稳定性——不稳定
public class SelectionSort {
public static void selectionSort(int[] a, int n) {
for(int i = 0; i < n - 1; i++) {
int min = i;
for(int j = i + 1; j < n; j++) {
if(a[j] < a[min]) {
min = j;
}
}
if(min != i) {
new Swap();
Swap.swap(a, min, i);
}
}
}
}
六、插入排序
1.将未排序数据,在已排序序列中从后向前扫描,找到相应位置插入。
2.代码实现:
//分类——内部比较排序
//数据结构——数组
//最差时间复杂度——降序序列O(n^2)
//最优时间复杂度——升序序列O(n)
//平均时间复杂度——O(n^2)
//辅助空间——O(1)
//稳定性——稳定
public class InsertSort {
public static void insertSort(int[] a, int n) {
for(int i = 1; i < n; i++) {
int get = a[i];
int j = i - 1;
while (j >= 0 && a[j] >= get) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = get;
}
new Display();
Display.display(a, n);
}
}
3.插入排序的改进——二分插入排序
(1)代码实现:
//最优时间复杂度——O(nlogn)
public class InsertSortDichotomy {
public static void insertSortDichotomy(int[] a, int n) {
for (int i = 1; i < n; i++) {
int get = a[i];
int left = 0;
int right = i - 1;
while (left <= right) {
int mid = (left + right) / 2;
if(a[mid] > get) {
right = mid - 1;
}
else {
left = mid + 1;
}
}
for(int j = i - 1; j >= left; j--) {
a[j + 1] = a[j];
}
a[left] = get;
}
new Display();
Display.display(a, n);
}
}
4.插入排序的更高效改进——希尔排序
1.希尔排序,也叫作递减增量排序。
2.图解:
3.代码实现:
//最差时间复杂度—根据步长不同而不同
//最优时间复杂度——O(n)
//平均时间复杂度——根据步长不同而不同
//稳定性——不稳定
public class ShellSort {
public static void shellSort(int[] a, int n) {
for(int gap = n / 2; gap > 0; gap /= 2) {
for(int i = 0; i < gap; i ++) {
for(int j = i + gap; j < n; j += gap) {
if (a[j] < a[j - gap]) {
int temp = a[j];
int k = j - gap;
while (k >= 0 && a[k] > temp) {
a[k + gap] = a[k];
k = k - gap;
}
a[k + gap] = temp;
}
}
}
}
new Display();
Display.display(a, n);
}
}
七、堆排序
1.利用对这种数据结构所设计的一种选择排序算法,堆是一种近似完全二叉树的结构(通常堆是一维数组来实现的),并满足性质,以最大堆(也叫大根堆、大顶堆)为例,其父节点的值总是大于它的孩子节点。
2.步骤
(1)用输入的无序数组构造一个最大堆,作为初始的无序区;
(2)把最大值即堆顶元素和堆尾元素互换;
(3)把堆(无序区)的尺寸缩小1,并调用heapify从新的堆顶元素开始调整;
(4)重复步骤2,直到堆的尺寸为1。
3.代码实现
public class HeapSort {
public static void heapify(int[] a, int i, int n) {
int temp = a[i];
for(int k = 2 * i + 1; k < n; k = 2 * k + 1) {
if(k + 1 < n && a[k] < a[k + 1]) {
k++;
}
if(a[k] > temp) {
a[i] = a[k];
i = k;
}
}
a[i] = temp;
}
public static void heapSort(int[] a, int n) {
for(int i = n / 2 - 1; i >= 0; i--) {
heapify(a, i, n);
}
for(int j = n - 1; j > 0; j--) {
new Swap();
Swap.swap(a, 0, j);
heapify(a, 0, j);
}
}
}
八、快速排序
1.快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
2.快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
(1).从序列中挑出一个元素,作为"基准"(pivot).
(2).把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
(3).对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
3.代码实现
public class QuickSort {
public static void quickSort(int[] a, int low, int high) {
if(low < high) {
return;
}
int i = low;
int j = high;
int key = a[low];
while (i < j) {
while (a[j] < key && i < j) {
j--;
}
while (a[i] > key && i < j) {
i++;
}
if(i < j) {
new Swap();
Swap.swap(a, i, j);
}
}
Swap.swap(a, low, i);
quickSort(a, low, i - 1);
quickSort(a, i + 1, high);
}
}