排序算法是计算机科学中常见的算法类型,用于将一组数据按照特定的顺序进行排列。
本文主要是对排序算法的巩固和学习,以及对JDK中相关排序源码解读
排序算法分类
可以飞分为比较类排序和非比较类排序
- 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序。
- 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。
算法复杂度
排序 | 时间复杂度(平均) | 时间复杂度(最好) | 时间复杂度(最坏) | 空间复杂度 | 稳定与否 |
---|---|---|---|---|---|
插入排序 | O(N2) | O(N) | O(N2) | O(1) | 稳定 |
希尔排序 | O(NlogN) | O(N) | O(N2) | O(1) | 不稳定 |
选择排序 | O(N2) | O(N2) | O(N2) | O(1) | 不稳定 |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
冒泡排序 | O(N2) | O(N2) | O(N) | O(1) | 稳定 |
快速排序 | O(NlogN) | O(NlogN) | O(N2) | O(logN) | 不稳定 |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 |
计数排序 | O(N+K) | O(N+K) | O(N+K) | O(N+K) | 稳定 |
桶排序 | O(N+K) | O(N2) | O(N) | O(N+K) | 稳定 |
基数排序 | O(N*K) | O(N*K) | O(N*K) | O(N+K) | 稳定 |
稳定与不稳定解释:
- 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
- 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
经典排序算法
插入排序(Insertion Sort)
插入排序(Insertion Sort)是一种简单直观的排序算法,它的思想是将待排序的元素逐个插入到已排序序列中的适当位置,从而构建有序序列。插入排序的步骤如下:
- 初始时,将第一个元素视为已排序序列,将其作为参考点。
- 从第二个元素开始,将当前元素与已排序序列中的元素进行比较,找到插入的位置。
- 将当前元素插入到合适的位置,同时将已排序序列中的元素向后移动。
- 重复步骤2和3,直到所有元素都被插入到正确的位置,形成有序序列。
java
public static void insertionSort(int[] arr) {
int n = arr.length;
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
// 将比当前元素大的元素向后移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
// 插入当前元素到合适的位置
arr[j + 1] = key;
}
}
动图演示:
希尔排序(Shell Sort)
希尔排序(Shell Sort),也称为缩小增量排序,是插入排序的一种改进版算法。它通过将待排序的元素分组,每组进行插入排序,逐渐缩小增量(间隔),最终完成排序。希尔排序的步骤如下:
- 选择一个增量序列(间隔序列),通常选择初始增量为数组长度的一半,然后逐步缩小增量直至为1。
- 按照增量将数组分成多个子序列,对每个子序列进行插入排序。
- 逐步减小增量,重复步骤2,直到增量为1时,进行最后一次插入排序。
java
public static void shellSort(int[] arr) {
int n = arr.length;
// 初始增量为数组长度的一半,逐步缩小增量
for (int gap = n / 2; gap > 0; gap /= 2) {
// 对每个子序列进行插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j = i;
// 对当前子序列进行插入排序
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
}
动图演示如下:
选择排序(Selection Sort)
选择排序(Selection Sort)是一种简单直观的排序算法,它的思想是每次从未排序的元素中选择最小(或最大)的元素,将其放置在已排序序列的末尾(或开头),从而逐步构建有序序列。选择排序的步骤如下:
- 初始时,将整个数组视为未排序序列。
- 在未排序序列中,找到最小(或最大)的元素,将其与未排序序列的第一个元素进行交换。
- 将未排序序列的起始位置向后移动一个位置,即将已排序序列的末尾扩展一个元素。
- 重复步骤2和3,直到未排序序列为空,即所有元素都已排序。
java
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
// 找到未排序序列中的最小元素的索引
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 将最小元素与未排序序列的第一个元素进行交换
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}
}
动图演示如下:
堆排序
堆排序(Heap Sort)是一种高效的排序算法,它利用堆这种数据结构进行排序。堆是一个完全二叉树,具有以下性质:对于每个节点i,其父节点的值总是大于等于(或小于等于)其子节点的值。堆排序的步骤如下:
- 构建最大堆(或最小堆):将待排序的数组看作是一个完全二叉树,通过自下而上的方式,从最后一个非叶子节点开始,对每个节点进行堆化操作,使得每个节点满足堆的性质。
- 交换堆顶元素与最后一个元素:将堆顶元素与堆中最后一个元素进行交换,即将最大(或最小)元素放置在已排序部分的末尾。
- 重新调整堆:对交换后的堆顶元素进行堆化操作,使得堆顶元素重新满足堆的性质。
- 重复步骤2和3,直到堆中只剩下一个元素,即完成排序。
java
public static void heapSort(int[] arr) {
int n = arr.length;
// 构建最大堆
buildMaxHeap(arr);
// 交换堆顶元素与最后一个元素,并重新调整堆
for (int i = n - 1; i > 0; i--) {
swap(arr, 0, i);
heapify(arr, 0, i);
}
}
// 构建最大堆
private static void buildMaxHeap(int[] arr) {
int n = arr.length;
// 从最后一个非叶子节点开始,自下而上进行堆化操作
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, i, n);
}
}
// 堆化操作
private static void heapify(int[] arr, int root, int size) {
int largest = root;
int left = 2 * root + 1;
int right = 2 * root + 2;
// 找出根节点、左子节点和右子节点中的最大值
if (left < size && arr[left] > arr[largest]) {
largest = left;
}
if (right < size && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值不是根节点,则交换并继续进行堆化操作
if (largest != root) {
swap(arr, root, largest);
heapify(arr, largest, size);
}
}
// 交换数组中两个元素的位置
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
动图演示如下:
未完待续
参考