本文紧接上文,总结尚未介绍的6个排序算法
算法技巧-各种排序算法(一)
经典排序算法
冒泡排序(Bubble Sort)
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历待排序序列,每次比较相邻的两个元素,如果它们的顺序错误就交换位置,直到整个序列排序完成。冒泡排序的基本思想是通过不断交换相邻元素,将最大(或最小)的元素逐步"冒泡"到序列的末尾(或开头)。冒泡排序的步骤如下:
- 从第一个元素开始,依次比较相邻的两个元素,如果它们的顺序错误(例如,当前元素大于下一个元素),则交换它们的位置。
- 继续遍历序列,重复步骤1,直到没有任何元素需要交换,序列排序完成。
java
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
// 标记是否进行了交换操作
boolean swapped = false;
// 每次遍历将最大元素冒泡到末尾
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换相邻元素的位置
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果没有进行交换操作,表示序列已经有序,提前结束排序
if (!swapped) {
break;
}
}
}
动图如下:
快速排序(Quick Sort)
快速排序(Quick Sort)是一种高效的排序算法,采用了分治的思想。它通过选择一个基准元素,将待排序序列划分为左右两个子序列,其中左子序列的元素都小于等于基准元素,右子序列的元素都大于等于基准元素,然后分别对左右子序列进行递归排序,最终完成整个序列的排序。快速排序的步骤如下:
- 选择一个基准元素(通常选择第一个或最后一个元素)。
- 将序列划分为两个子序列,使得左子序列的元素都小于等于基准元素,右子序列的元素都大于等于基准元素。可以使用双指针法或者挖坑法进行划分。
- 对左右子序列分别进行递归排序,直到子序列的长度为1或0,即递归终止条件。
- 合并左子序列、基准元素和右子序列,得到有序序列。
java
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 划分序列并获取基准元素的位置
int pivotIndex = partition(arr, low, high);
// 对左子序列进行递归排序
quickSort(arr, low, pivotIndex - 1);
// 对右子序列进行递归排序
quickSort(arr, pivotIndex + 1, high);
}
}
public static int partition(int[] arr, int low, int high) {
// 选择最后一个元素作为基准元素
int pivot = arr[high];
// 定义指针i记录小于基准元素的位置
int i = low - 1;
// 从左到右遍历序列,将小于基准元素的元素放置在指针i的位置
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
// 交换arr[i]和arr[j]的位置
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准元素放置在指针i的下一个位置
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
// 返回基准元素的位置
return i + 1;
}
动图如下:
归并排序(Merge Sort)
归并排序(Merge Sort)是一种稳定且高效的排序算法,采用了分治的思想。它将待排序序列划分为若干个子序列,然后对每个子序列进行递归排序,最后将排好序的子序列合并成一个有序序列。归并排序的步骤如下:
- 将待排序序列划分为两个子序列,直到子序列的长度为1或0,即递归终止条件。
- 对左右两个子序列分别进行递归排序,即重复步骤1。
- 合并左右两个已排好序的子序列,得到一个有序序列。
归并排序的关键在于合并两个有序序列的操作。合并时,需要定义两个指针分别指向两个有序子序列的起始位置,然后依次比较两个指针所指的元素,将较小(或较大)的元素放入新的序列中,并将指针向后移动。当其中一个子序列遍历完后,将另一个子序列中剩余的元素依次放入新序列中。最终,合并完成后得到完全有序的序列。
java
public static void mergeSort(int[] arr, int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
// 对左子序列进行递归排序
mergeSort(arr, low, mid);
// 对右子序列进行递归排序
mergeSort(arr, mid + 1, high);
// 合并左右子序列
merge(arr, low, mid, high);
}
}
public static void merge(int[] arr, int low, int mid, int high) {
int n1 = mid - low + 1; // 左子序列的长度
int n2 = high - mid; // 右子序列的长度
// 创建临时数组存储左右子序列的元素
int[] left = new int[n1];
int[] right = new int[n2];
// 将原数组的元素复制到临时数组中
for (int i = 0; i < n1; i++) {
left[i] = arr[low + i];
}
for (int j = 0; j < n2; j++) {
right[j] = arr[mid + 1 + j];
}
// 合并左右子序列
int i = 0; // 左子序列的指针
int j = 0; // 右子序列的指针
int k = low; // 原数组的指针
while (i < n1 && j < n2) {
if (left[i] <= right[j]) {
arr[k] = left[i];
i++;
} else {
arr[k] = right[j];
j++;
}
k++;
}
// 将剩余的元素放入原数组
while (i < n1) {
arr[k] = left[i];
i++;
k++;
}
while (j < n2) {
arr[k] = right[j];
j++;
k++;
}
}
动图如下:
计数排序(Counting Sort)
计数排序(Counting Sort)是一种线性时间复杂度的排序算法,适用于待排序的元素范围较小且已知的情况。它通过统计每个元素出现的次数,然后根据元素的顺序依次将其放入输出序列中。
计数排序的步骤如下:
- 统计待排序序列中每个元素的出现次数,得到一个计数数组(或频率数组)。
- 根据计数数组,计算每个元素在排序后的序列中的位置。
- 创建一个与待排序序列相同大小的输出序列。
- 遍历待排序序列,根据计数数组将每个元素放入输出序列中,并更新计数数组的值。
- 输出序列即为排序后的结果。
计数排序的核心思想是将待排序序列中的元素映射到计数数组的索引上,通过计数数组统计每个元素的出现次数。然后根据计数数组,将每个元素按照顺序放入输出序列中。
计数排序是一个稳定的排序算法。当输入的元素是 n 个 0到 k 之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。当k不是很大并且序列比较集中时,计数排序是一个很有效的排序算法。
java
public static void countingSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
// 找到待排序序列的最大值
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
// 创建计数数组,并统计每个元素的出现次数
int[] count = new int[max + 1];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
// 根据计数数组,计算每个元素在排序后的序列中的位置
for (int i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 创建输出序列
int[] output = new int[arr.length];
// 遍历待排序序列,根据计数数组将每个元素放入输出序列中
for (int i = arr.length - 1; i >= 0; i--) {
output[count[arr[i]] - 1] = arr[i];
count[arr[i]]--;
}
// 将输出序列复制回原数组
for (int i = 0; i < arr.length; i++) {
arr[i] = output[i];
}
}
动图如下:
桶排序(Bucket Sort)
桶排序(Bucket Sort)是一种排序算法,它将待排序元素分配到不同的有限数量的桶中,然后对每个桶中的元素进行排序,最后将所有桶中的元素按顺序合并得到排序结果。
桶排序的步骤如下:
- 确定桶的数量,并创建对应数量的桶。
- 遍历待排序序列,将每个元素根据某种映射关系放入对应的桶中。
- 对每个非空的桶进行单独排序,可以使用其他排序算法或递归地使用桶排序。
- 将各个桶中的元素按照顺序合并得到排序结果。
桶排序的关键在于确定元素和桶之间的映射关系。通常情况下,可以根据待排序元素的大小范围和桶的数量来设计映射关系,确保桶中的元素是相对有序的。
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。
java
public static void bucketSort(int[] arr, int bucketSize) {
if (arr == null || arr.length <= 1) {
return;
}
int minValue = arr[0];
int maxValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i];
} else if (arr[i] > maxValue) {
maxValue = arr[i];
}
}
int bucketCount = (maxValue - minValue) / bucketSize + 1;
ArrayList<ArrayList<Integer>> buckets = new ArrayList<>(bucketCount);
for (int i = 0; i < bucketCount; i++) {
buckets.add(new ArrayList<>());
}
for (int i = 0; i < arr.length; i++) {
int bucketIndex = (arr[i] - minValue) / bucketSize;
buckets.get(bucketIndex).add(arr[i]);
}
int currentIndex = 0;
for (int i = 0; i < bucketCount; i++) {
ArrayList<Integer> bucket = buckets.get(i);
Collections.sort(bucket);
for (int j = 0; j < bucket.size(); j++) {
arr[currentIndex++] = bucket.get(j);
}
}
}
图片演示:
基数排序(Radix Sort)
基数排序(Radix Sort)是一种非比较排序算法,它根据元素的位数进行排序。基数排序逐位地对待排序的元素进行排序,从最低位(个位)到最高位(最高位由元素中最大位数决定)。每个位数上使用稳定的排序算法,通常是计数排序或桶排序。通过多次按位排序,最终得到排序结果。
基数排序的步骤如下:
- 确定待排序元素中的最大位数,记为maxDigits。
- 对待排序元素的每一位进行排序,从最低位到最高位。
- 使用稳定的排序算法(如计数排序或桶排序)对每个位数上的元素进行排序。
- 重复步骤3,直到对所有位数都进行了排序。
- 最后得到的排序结果即为基数排序的结果。
java
public static void radixSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
// 寻找最大值确定最大位数
int max = getMaxValue(arr);
int maxDigits = getDigitCount(max);
// 进行基数排序
for (int digitPlace = 1; digitPlace <= maxDigits; digitPlace++) {
countingSort(arr, digitPlace);
}
}
private static int getMaxValue(int[] arr) {
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
private static int getDigitCount(int number) {
if (number == 0) {
return 1;
}
return (int) Math.floor(Math.log10(number)) + 1;
}
private static void countingSort(int[] arr, int digitPlace) {
final int RADIX = 10;
int n = arr.length;
int[] output = new int[n];
int[] count = new int[RADIX];
Arrays.fill(count, 0);
for (int i = 0; i < n; i++) {
int digit = (arr[i] / digitPlace) % RADIX;
count[digit]++;
}
for (int i = 1; i < RADIX; i++) {
count[i] += count[i - 1];
}
for (int i = n - 1; i >= 0; i--) {
int digit = (arr[i] / digitPlace) % RADIX;
output[count[digit] - 1] = arr[i];
count[digit]--;
}
System.arraycopy(output, 0, arr, 0, n);
}
动图如下:
未完待续
参考