冒泡排序
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复进行的,直到没有再需要交换的元素,也就是说该数列已经排序完成。
- 基本原理
冒泡排序的基本思想是通过不断比较相邻的两个元素,如果第一个比第二个大(升序排序),就交换它们的位置。这样,每一趟排序都会把当前未排序部分的最大值“浮”到数列的末尾。
- 算法步骤
- 比较与交换:从第一个元素开始,比较相邻的元素。如果第一个比第二个大,就交换它们的位置。
- 重复步骤:对每一对相邻元素做同样的比较和可能的交换,直到最后一个元素。这一过程称为一趟排序。
- 重复整个过程:重复上述步骤,除了最后一对元素,然后是倒数第二对,依此类推,直到没有任何一对数字需要比较为止。
- 时间复杂度
- 最好情况:当输入的数据已经是有序时,冒泡排序的时间复杂度为O(n),因为不需要进行任何交换。
- 平均情况:冒泡排序的时间复杂度为O(n^2)。
- 最坏情况:当输入的数据是逆序时,冒泡排序的时间复杂度同样为O(n^2)。
- 空间复杂度
冒泡排序的空间复杂度为O(1),因为它只需要一个额外的存储空间用于临时交换数据。
- 稳定性
冒泡排序是一个稳定的排序算法,这意味着相等的元素之间的相对顺序不会被改变。
- 适用场景
由于冒泡排序的效率较低,它通常只适用于小规模数据的排序,或者数据已经接近有序的情况。对于大规模数据集,更高效的排序算法如快速排序、归并排序或堆排序会是更好的选择
示例代码(伪代码)
procedure bubbleSort(array)
n = length(array)
for i from 0 to n-1 do:
swapped = false
for j from 0 to n-i-1 do:
if array[j] > array[j+1] then:
swap(array[j], array[j+1])
swapped = true
if not swapped then:
break
end procedure
java代码
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 13:16
*/
public class bubble {
/**
* 冒泡排序算法实现。
* 通过相邻元素的比较和交换,逐步将最大的元素“冒泡”到数组的末尾。
*
* @param nums 待排序的整数数组。
*/
public void bubbleSort(int []nums){
// 获取数组长度
int n = nums.length;
// 外层循环控制排序的轮数,每轮找到一个最大元素
for (int i = 0; i < n; i++) {
// 内层循环进行相邻元素比较,每次比较后可能交换位置
for (int j = 1; j < n - i; j++) {
// 如果前一个元素大于后一个元素,则交换它们
if (nums[j-1]>nums[j]){
// 交换元素
int temp = nums[j-1];
nums[j-1] = nums[j];
nums[j] = temp;
}
}
}
}
/**
* 冒泡排序并打印每轮排序后的结果。
* 冒泡排序是一种简单的排序算法,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
* 走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
*
* @param nums 待排序的整数数组
*/
public void bubbleSortPrint(int []nums){
// 获取数组长度
int n = nums.length;
// 外层循环控制排序轮数,每轮找到一个最大元素
for (int i = 0; i < n; i++) {
// 内层循环进行元素比较和交换
for (int j = 1; j < n - i; j++) {
// 如果前一个元素大于后一个元素,则交换它们
if (nums[j-1]>nums[j]){
int temp = nums[j-1];
nums[j-1] = nums[j];
nums[j] = temp;
}
}
// 打印每轮排序后的数组
for (int j = 0; j < n; j++) {
System.out.print(nums[j]+"\t");
}
// 输出当前轮次
System.out.println("第"+i+"轮排序");
}
}
public static void main(String[] args) {
bubble b = new bubble();
int[] arrays = new int[]{5,4,9,7,2,3,8,1,6};
//排序
//b.bubbleSort(arrays);
//排序并输出每一次排序的过程
b.bubbleSortPrint(arrays);
System.out.println("排序结果");
for (int array : arrays) {
System.out.print(array+"\t");
}
}
}
运行结果
4 5 7 2 3 8 1 6 9 第0轮排序
4 5 2 3 7 1 6 8 9 第1轮排序
4 2 3 5 1 6 7 8 9 第2轮排序
2 3 4 1 5 6 7 8 9 第3轮排序
2 3 1 4 5 6 7 8 9 第4轮排序
2 1 3 4 5 6 7 8 9 第5轮排序
1 2 3 4 5 6 7 8 9 第6轮排序
1 2 3 4 5 6 7 8 9 第7轮排序
1 2 3 4 5 6 7 8 9 第8轮排序
1 2 3 4 5 6 7 8 9 排序结果
选择排序
选择排序是一种简单直观的比较排序算法,其基本思想是在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
- 选择排序的详细步骤
- 初始化:假设数组中的第一个元素是最小的,设置一个变量minIndex指向数组的第一个元素。
- 查找最小元素:遍历数组的其余部分,寻找最小元素的索引。如果找到比当前minIndex所指向的元素更小的元素,更新minIndex。
- 交换元素:在一轮遍历结束后,将minIndex所指向的元素与数组的第一个元素交换位置。这样,最小的元素就被放置在了数组的起始位置。
- 重复步骤:将数组视为两部分,一部分是已经排序好的,另一部分是没有排序的。在下一次迭代中,从未排序的部分开始重复上述过程,直到整个数组都被排序。
- 时间复杂度分析
- 最佳情况:即使数组已经是有序的,选择排序仍然需要遍历整个数组,因此时间复杂度为O(n^2)。
- 平均情况:无论数组的初始状态如何,选择排序的时间复杂度始终为O(n^2)。
- 最坏情况:当数组完全逆序时,选择排序的时间复杂度仍为O(n^2)。
- 空间复杂度
- 选择排序的空间复杂度为O(1),因为它只需要一个额外的存储空间用于临时保存最小元素的索引。
- 稳定性
- 选择排序是一种不稳定的排序算法,因为在排序过程中,相等的元素可能由于交换而改变其原有的相对位置。
- 实用场景
- 选择排序通常用于数据量较小的场景,或者在内存资源有限的情况下,因为它的空间复杂度较低。然而,对于大规模数据集,选择排序的效率较低,不建议使用。
- 伪代码
function selectionSort(array)
for i from 0 to length(array) - 1
// 假设当前元素是最小的
minIndex = i
// 查找剩余元素中的最小值
for j from i + 1 to length(array)
if array[j] < array[minIndex]
minIndex = j
// 将找到的最小元素与当前元素交换
swap array[i] with array[minIndex]
end function
java代码
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 13:43
*/
public class selection {
/**
* 选择排序算法的实现。
* 该方法通过找到数组中的最小元素,并将其与数组的当前位置元素交换,从而达到排序的目的。
* 选择排序是一种简单的排序算法,它重复地从未排序的序列中找到最小(或最大)的元素,
* 并将其放到已排序序列的末尾。这个过程会一直持续到所有元素都排序完毕。
*
* @param nums 待排序的整数数组。
*/
public void selectionSort(int[] nums){
int n = nums.length;
// 外层循环控制排序的轮数,每轮找到一个最小元素
for (int i = 0; i < n-1; i++) {
int min = i;
// 内层循环找出当前未排序序列中的最小元素
for (int j = i+1; j < n; j++) {
if (nums[j]<nums[min]){
min = j;
}
}
// 交换找到的最小元素与当前未排序序列的第一个元素(即nums[i])
int temp = nums[min];
nums[min] = nums[i];
nums[i] = temp;
}
}
/**
* 选择排序并打印
* 使用选择排序算法对数组进行排序,并在每一轮排序后打印当前数组的状态。
* 选择排序算法的原理是每次从未排序的部分中找到最小(或最大)的元素,然后将其与未排序部分的第一个元素交换位置。
*
* @param nums 待排序的整数数组
*/
public void selectionSortPrint(int[] nums){
int n = nums.length;
// 外层循环控制排序的轮数,每轮找到一个最小元素
for (int i = 0; i < n - 1; i++) {
int min = i;
// 内层循环找到当前未排序部分的最小元素
for (int j = i+1; j < n; j++) {
if (nums[j]<nums[min]){
min = j;
}
}
// 交换找到的最小元素与未排序部分的第一个元素
int temp = nums[min];
nums[min] = nums[i];
nums[i] = temp;
// 打印每轮排序后的数组状态
for (int j = 0; j < n; j++) {
System.out.print(nums[j]+"\t");
}
System.out.println("第"+i+"轮排序");
}
}
public static void main(String[] args) {
selection s = new selection();
int[] arrays = new int[]{5,4,9,7,2,3,8,1,6};
s.selectionSortPrint(arrays);
for (int array : arrays) {
System.out.print(array+"\t");
}
System.out.println("排序结果");
}
}
运行结果
1 4 9 7 2 3 8 5 6 第0轮排序
1 2 9 7 4 3 8 5 6 第1轮排序
1 2 3 7 4 9 8 5 6 第2轮排序
1 2 3 4 7 9 8 5 6 第3轮排序
1 2 3 4 5 9 8 7 6 第4轮排序
1 2 3 4 5 6 8 7 9 第5轮排序
1 2 3 4 5 6 7 8 9 第6轮排序
1 2 3 4 5 6 7 8 9 第7轮排序
1 2 3 4 5 6 7 8 9 排序结果
插入排序
插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常使用in-place排序(即只需用到O(1)的额外空间的排序),因而在从序列的部分有序状况下,可以提高排序效率。
- 插入排序的基本步骤:
- 初始化:假设序列的第一个元素已经排好序了,从第二个元素开始处理。
- 遍历:从第二个元素开始,遍历整个序列。
- 比较和交换:对于每一个遍历到的元素,将其与前面已经排序好的子序列中的元素进行比较,如果当前元素小于或等于前面的某个元素,就将当前元素与该元素进行交换,直到找到当前元素应该插入的位置。
- 插入:将当前元素插入到正确的位置,完成一次插入操作。
- 重复:重复上述过程,直到所有元素都被处理过。
- 插入排序的时间复杂度:
- 最好情况:当输入的序列已经是有序时,插入排序的时间复杂度为O(n),这是因为每次插入操作都无需移动元素。
- 平均情况:当输入的序列随机时,插入排序的时间复杂度为O(n^2)。
- 最坏情况:当输入的序列是逆序时,插入排序的时间复杂度也为O(n^2)
- 插入排序的空间复杂度:
- 由于插入排序是原地排序,其空间复杂度为O(1)。
- 插入排序的优点:
- 实现简单,代码量少。
- 在部分有序的数据集上表现良好,效率较高。
- 稳定排序,即相等的元素不会改变原有的顺序。
- 插入排序的缺点:
- 当数据量大时,效率较低,时间复杂度较高。
- 对于完全无序的大数据集,性能不佳。
java代码
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 14:11
*/
public class insertion {
/**
* 使用插入排序算法对整数数组进行排序。
* 插入排序的工作原理是将未排序的元素逐个插入到已排序部分的正确位置,从而逐步构建有序的数组。
*
* @param nums 待排序的整数数组。
*/
public void insertionSort(int[] nums) {
// 获取数组长度
int n = nums.length;
// 从第二个元素开始遍历,因为第一个元素默认已排序
for (int i = 1; i < n; i++) {
// j用于跟踪当前要插入的元素的前一个位置
int j = i - 1;
// key是当前要插入的元素
int key = nums[i];
// 将当前元素key与之前的元素进行比较,并向前移动大于key的元素
while (j >= 0 && nums[j] > key) {
// 将大于key的元素向后移动一位
nums[j + 1] = nums[j];
// 向前移动到下一个要比较的元素
j--;
}
// 将key插入到已排序序列的正确位置
nums[j + 1] = key;
}
}
public void insertionSortPrint(int[] nums) {
// 获取数组长度
int n = nums.length;
// 从第二个元素开始遍历,因为第一个元素默认已排序
for (int i = 1; i < n; i++) {
// j用于跟踪当前要插入的元素的前一个位置
int j = i - 1;
// key是当前要插入的元素
int key = nums[i];
// 将当前元素key与之前的元素进行比较,并向前移动大于key的元素
while (j >= 0 && nums[j] > key) {
// 将大于key的元素向后移动一位
nums[j + 1] = nums[j];
// 向前移动到下一个要比较的元素
j--;
}
// 将key插入到已排序序列的正确位置
nums[j + 1] = key;
for (int k = 0; k < n; k++) {
System.out.print(nums[k]+"\t");
}
System.out.println("第"+i+"轮排序");
}
}
public static void main(String[] args) {
insertion i = new insertion();
int[] arrays = new int[]{5,4,9,7,2,3,8,1,6};
i.insertionSortPrint(arrays);
for (int array : arrays) {
System.out.print(array+"\t");
}
System.out.println("排序结果");
}
}
运行结果
4 5 9 7 2 3 8 1 6 第1轮排序
4 5 9 7 2 3 8 1 6 第2轮排序
4 5 7 9 2 3 8 1 6 第3轮排序
2 4 5 7 9 3 8 1 6 第4轮排序
2 3 4 5 7 9 8 1 6 第5轮排序
2 3 4 5 7 8 9 1 6 第6轮排序
1 2 3 4 5 7 8 9 6 第7轮排序
1 2 3 4 5 6 7 8 9 第8轮排序
1 2 3 4 5 6 7 8 9 排序结果
希尔排序
希尔排序(Shell Sort)是一种基于插入排序的算法,由 Donald Shell 在 1959 年提出。希尔排序的主要思想是通过将待排序的数组分为若干个子序列,对各个子序列进行插入排序,以此来减少数据项之间不必要的比较和移动,从而提高排序效率。随着排序的进行,子序列的划分逐渐细化,最终当增量(gap)为 1 时,算法退化为普通的插入排序,此时数组已经接近有序,因此插入排序的效率很高
- 希尔排序的步骤:
- 选择增量序列:首先选择一个增量序列,最常用的增量序列是希尔增量序列,即序列的每一项是前一项的一半,直到增量为 1。例如,对于长度为 n 的数组,增量序列可能是 n/2, n/4, n/8, …, 1。
- 分组并插入排序:根据当前的增量,将数组分为若干个子序列,每个子序列中的元素间隔为当前增量值。对每个子序列进行插入排序。
- 减小增量:将增量减半,重复上述步骤,直到增量为 1。
- 最终插入排序:当增量为 1 时,对整个数组进行一次插入排序。
- 时间复杂度:
- 希尔排序的时间复杂度依赖于增量序列的选择。最佳情况下,希尔排序的时间复杂度可以达到 O(n log n),但最坏情况下,如果增量序列选择不当,时间复杂度可能退化至 O(n^2)。
- 空间复杂度:
- 希尔排序是原地排序算法,不需要额外的存储空间,空间复杂度为 O(1)。
- 稳定性:
- 希尔排序是非稳定排序算法,因为在排序过程中,相等的元素可能会因为插入排序的移动而改变它们的相对位置。
- 适用场景:
- 希尔排序适合于数据量不大或部分有序的数据集。对于大规模数据集,希尔排序的效率可能不如快速排序、堆排序或归并排序等算法。然而,希尔排序的实现相对简单,且在某些特定情况下能够提供较好的性能
java 代码
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 14:36
*/
public class shell {
public void shellSort(int[] nums) {
int n = nums.length;
int gap = n / 2; // 初始增量
// 持续缩小增量,直到增量为1
while (gap > 0) {
// 对每个子数组进行插入排序
for (int i = gap; i < n; i++) {
int temp = nums[i];
int j = i;
// 插入排序的逻辑,但是只在当前增量的范围内进行
while (j >= gap && nums[j - gap] > temp) {
nums[j] = nums[j - gap];
j -= gap;
}
nums[j] = temp;
}
gap /= 2; // 缩小增量
}
}
public void shellSortPrint(int[] nums) {
int n = nums.length;
int gap = n / 2; // 初始增量
// 持续缩小增量,直到增量为1
while (gap > 0) {
// 对每个子数组进行插入排序
for (int i = gap; i < n; i++) {
int temp = nums[i];
int j = i;
// 插入排序的逻辑,但是只在当前增量的范围内进行
while (j >= gap && nums[j - gap] > temp) {
nums[j] = nums[j - gap];
j -= gap;
}
nums[j] = temp;
}
gap /= 2; // 缩小增量
// 打印每轮排序后的数组状态
for (int j = 0; j < n; j++) {
System.out.print(nums[j]+"\t");
}
System.out.println("第"+gap+"轮排序");
}
}
public static void main(String[] args) {
shell s = new shell();
int[] arrays = new int[]{5,4,9,7,2,3,8,1,6};
//s.shellSort(arrays);
s.shellSortPrint(arrays);
for (int array : arrays) {
System.out.print(array+"\t");
}
System.out.println("排序结果");
}
}
运行结果
2 3 8 1 5 4 9 7 6 第2轮排序
2 1 5 3 6 4 8 7 9 第1轮排序
1 2 3 4 5 6 7 8 9 第0轮排序
1 2 3 4 5 6 7 8 9 排序结果
归并排序
基本思想:归并排序是一种分而治之的算法。其核心是将一个大数组分成两个相等(或几乎相等)的部分,分别对这两部分进行排序,然后再将排序好的两部分合并在一起得到最终的有序数组。这一过程递归进行,直到数组不能再分割(即数组长度为1),此时认为单个元素的数组自然有序
- 算法步骤:
- 分解:将当前区间一分为二,即求中点。
- 解决:递归地对两个子区间a[low…mid]和a[mid+1…high]进行归并排序。
- 合并:将已排序的两个子区间合并成一个有序区间
- 代码解析
- mergesort方法:这是归并排序的主方法,接收一个整型数组作为参数。首先检查数组长度,如果小于等于1,则直接返回(因为单个元素的数组或空数组已经视为有序)。接着,找到数组的中点以进行分割,创建两个新数组left和right来存放原数组的左右两部分。通过递归调用自身对这两个子数组进行排序,最后调用merge方法将这两个有序数组合并成一个有序数组。
- merge方法:负责合并两个已排序的数组left和right到原数组nums中。这个过程是通过比较两个数组中的当前元素,将较小的元素先加入到结果数组,直至一个数组遍历完,然后将另一个数组剩余的元素依次加入到结果数组的末尾。
- 示例:在main方法中,创建了一个无序数组,然后实例化了merge类的对象并调用其mergesort方法对该数组进行排序。最后,通过循环打印排序后的数组,展示排序效果
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 14:57
*/
public class merge {
/**
* 使用归并排序对数组进行排序。
* 归并排序是一种分治算法,它将数组分成两部分,分别排序,然后将排序后的两部分合并成一个有序的数组。
* 这个方法不返回任何东西,它直接在输入的数组上进行排序。
*
* @param nums 输入的数组,将在这个方法调用后被排序。
*/
public void mergesort(int []nums){
// 如果数组长度小于等于1,已经是有序的,不需要排序
int n = nums.length;
if (n <= 1) return;
// 计算数组的中间位置,用于分割数组
int mid = n / 2;
// 创建两个数组,分别存储数组的左半部分和右半部分
int []left = new int[mid];
int []right = new int[n - mid];
// 将原数组的前半部分复制到左数组
// 切割数组
for (int i = 0; i < mid; i++) {
left[i] = nums[i];
}
// 将原数组的后半部分复制到右数组
for (int i = mid; i < n; i++) {
right[i - mid] = nums[i];
}
// 递归地对左数组和右数组进行排序
// 递归切割左右子数
mergesort(left);
mergesort(right);
// 将排序好的左数组和右数组合并到原数组中
merge(nums, left, right);
}
/**
* 合并两个有序数组。
* 将两个有序数组left和right合并到原始数组nums中,保持合并后的数组有序。
*
* @param nums 原始数组,合并后的结果将存储在这个数组中
* @param left 第一个有序数组
* @param right 第二个有序数组
*/
private void merge(int[] nums, int[] left, int[] right) {
// 初始化三个指针,分别指向left、right和nums数组的起始位置
int i = 0, j = 0, k = 0;
// 当两个数组都有元素时,比较两个数组当前元素的大小,并将较小的元素放入nums数组中
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
// 如果left数组当前元素较小,放入nums数组,并移动left指针
nums[k++] = left[i++];
} else {
// 如果right数组当前元素较小,放入nums数组,并移动right指针
nums[k++] = right[j++];
}
}
// 当其中一个数组仍有元素时,将剩余元素依次放入nums数组中
// 此时,另一个数组已经遍历完,对应的指针无需再移动
while (i < left.length) {
nums[k++] = left[i++];
}
while (j < right.length) {
nums[k++] = right[j++];
}
}
public static void main(String[] args) {
merge m = new merge();
int[] arrays = new int[]{5,4,9,7,2,3,8,1,6};
//排序
//b.bubbleSort(arrays);
//排序并输出每一次排序的过程
m.mergesort(arrays);
for (int array : arrays) {
System.out.print(array+"\t");
}
System.out.println("排序结果");
}
}
运行结果
1 2 3 4 5 6 7 8 9 排序结果
快速排序
快速排序是一种非常高效的排序算法,由英国计算机科学家托尼·霍尔于1960年提出。它采用分治法(Divide and Conquer)策略来把一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列
- 快速排序的基本步骤:
- 选择基准值:从数列中挑出一个元素,称为“基准”(pivot)。
- 分区操作:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分割(partition)操作。
- 递归排序子序列:递归地将小于基准值的子序列和大于基准值的子序列进行快速排序
- 复杂度分析:
- 最好情况:每次分割都均匀,时间复杂度为O(n log n)。
- 最坏情况:每次分割都是最不平衡的情况(每次都选最大或最小的作为基准),时间复杂度为O(n^2)。
- 平均情况:时间复杂度为O(n log n)
- 优点:
- 排序速度快,平均情况下性能优于大多数其他O(n log n)的排序算法。
- 在原地进行排序,不需要额外的存储空间,除了递归调用栈的空间。
- 缺点:
- 最坏情况下的性能较差,可以通过随机化选择基准值来避免这种情况。
- 递归调用栈深度在最坏情况下可能达到n,对于非常大的数据集可能会导致堆栈溢出。
- 实现细节:
- 选择基准值的方法有多种,如选择第一个元素、最后一个元素、中间元素,或者随机选择等。
- 分区操作可以通过多种方式实现,常见的有Lomuto分区方案和Hoare分区方案
java代码
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 16:21
*/
public class quick {
/**
* 快速排序主函数
*
* @param arr 要排序的数组
* @param low 数组的起始索引
* @param high 数组的终止索引
*/
public void quickSort(int[] arr, int low, int high) {
if (low < high) {
// pi 是分区操作后基准元素的索引
int pi = partition(arr, low, high);
// 递归地排序基准元素左边和右边的子数组
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
/**
* 分区操作函数
*
* @param arr 要排序的数组
* @param low 数组的起始索引
* @param high 数组的终止索引
* @return 基准元素的新索引位置
*/
private int partition(int[] arr, int low, int high) {
// 选择最后一个元素作为基准
int pivot = arr[high];
int i = (low - 1); // 比基准小的元素的索引
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;
}
}
// 交换 arr[i+1] 和 arr[high] (或 pivot)
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
public static void main(String[] args) {
quick q = new quick();
int[] arrays = new int[]{5,4,9,7,2,3,8,1,6};
//排序
q.quickSort(arrays,0,arrays.length-1);
for (int array : arrays) {
System.out.print(array+"\t");
}
System.out.println("排序结果");
}
}
运行结果
1 2 3 4 5 6 7 8 9 排序结果
堆排序
堆排序(Heap Sort)是一种基于比较的排序技术,利用了二叉堆的数据结构。堆排序可以分为两种类型:最大堆排序和最小堆排序。在最大堆中,父节点的值总是大于或等于任意子节点的值;而在最小堆中,父节点的值总是小于或等于任意子节点的值。堆排序的效率较高,平均和最坏情况下的时间复杂度均为O(n log n),并且是一种原地排序算法,但不是稳定的排序算法。
- 堆排序的基本步骤:
- 构建初始堆:将待排序序列构造成一个大顶堆(最大堆)或小顶堆(最小堆),此时整个序列的最大值或最小值就是堆顶的根节点。
- 交换与下沉:将堆顶元素(即最大值或最小值)与末尾元素交换,此时末尾就放置了一个最大值或最小值。然后将剩余n-1个元素重新调整为新的堆,重复此过程直到堆的大小为1。
- 重复上述过程:重复步骤2,直到整个序列有序
java代码
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 16:32
*/
public class heap {
public static void heapSort(int[] arr) {
int n = arr.length;
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 一个个从堆顶取出元素
for (int i = n - 1; i > 0; i--) {
// 将当前根节点与末尾元素交换
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 重新调整剩余堆
heapify(arr, i, 0);
}
}
// 用于调整堆的函数
private static void heapify(int[] arr, int n, int i) {
int largest = i; // 初始化最大值索引
int left = 2 * i + 1; // 左子节点索引
int right = 2 * i + 2; // 右子节点索引
// 如果左子节点大于根节点
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 如果右子节点大于当前最大值
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 如果最大值不是根节点
if (largest != i) {
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
// 递归地调整受影响的子树
heapify(arr, n, largest);
}
}
// 测试函数
public static void main(String[] args) {
int[] arrays = new int[]{5,4,9,7,2,3,8,1,6};
heapSort(arrays);
//排序
for (int array : arrays) {
System.out.print(array+"\t");
}
System.out.println("排序结果");
}
}
1 2 3 4 5 6 7 8 9 排序结果
计数排序
计数排序(Counting Sort)是一种非比较型的整数排序算法,其效率在某些情况下远超基于比较的排序算法,如快速排序或归并排序。计数排序的核心思想是将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
- 计数排序的基本步骤:
- 确定范围:找出数组中的最大值和最小值,确定数据的范围。
- 初始化计数数组:创建一个大小为数据范围的数组,用来统计每个数值出现的次数。
- 统计频率:遍历输入数组,对计数数组中对应位置的数值出现次数进行累加。
- 累计计数:对计数数组进行累加,使得每个元素的值表示小于等于该值的元素个数。
- 构建输出数组:从后向前遍历输入数组,根据计数数组的值将元素放置在输出数组的正确位置上,同时更新计数数组。
- 复制结果:将输出数组复制回原数组,完成排序。
package com.orchids.leetcode.sort;
/**
* @ Author qwh
* @ Date 2024/7/28 16:39
*/
public class counting {
/**
* 计数排序方法
*
* @param arr 输入的整数数组
*/
public static void sort(int[] arr) {
// 找出数组中的最大值和最小值
int max = arr[0];
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
} else if (arr[i] < min) {
min = arr[i];
}
}
// 创建计数数组
int range = max - min + 1;
int[] count = new int[range];
// 统计每个元素的出现次数
for (int num : arr) {
count[num - min]++;
}
// 累加计数数组
for (int i = 1; i < range; 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] - min] - 1] = arr[i];
count[arr[i] - min]--;
}
// 复制排序后的数组到原数组
System.arraycopy(output, 0, arr, 0, arr.length);
}
// 测试函数
public static void main(String[] args) {
int[] arrays = {5,4,9,7,2,3,8,1,6};
sort(arrays);
for (int array : arrays) {
System.out.print(array+"\t");
}
System.out.println("排序结果");
}
}
1 2 3 4 5 6 7 8 9 排序结果
桶排序
桶排序(Bucket Sort)是一种分布式排序算法,它将要排序的元素分布到多个“桶”中,然后再对每个桶中的元素进行排序。这种算法尤其适用于数据分布均匀的情况,可以达到线性时间复杂度O(n)
- 桶排序的基本步骤:
- 确定桶的数量:通常桶的数量等于数组的长度,但这并不是硬性规定,可以根据具体情况调整。
- 确定桶的范围:找到数组中的最大值和最小值,计算数据范围,然后根据桶的数量计算每个桶的范围。
- 分配元素到桶中:遍历数组,根据元素的值将其放入对应的桶中。这个过程通常涉及将元素值映射到桶的索引上。
- 对每个桶进行排序:每个桶中的元素可以使用任何排序算法进行排序,例如插入排序、冒泡排序等,甚至可以再次使用桶排序进行递归排序。
- 合并桶:将所有桶中的元素按顺序合并成一个完整的有序数组。
- 桶排序的特点:
- 时间复杂度:在最佳情况下(数据均匀分布),桶排序的时间复杂度为O(n + k),其中n是元素数量,k是桶的数量。但在最坏的情况下,如果所有元素都落入同一个桶中,则时间复杂度退化为O(n^2)。
- 空间复杂度:桶排序的空间复杂度为O(n + k),因为除了原始数组外,还需要额外的空间来存储桶。
- 稳定性:桶排序是稳定的排序算法,只要每个桶内的排序算法是稳定的。
- 适用场景:桶排序适用于数据分布均匀且数据范围已知的情况,比如处理大量浮点数的排序。
package com.orchids.leetcode.sort;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @ Author qwh
* @ Date 2024/7/28 16:44
*/
public class bucket {
public static void bucketSort(float[] arr) {
if (arr.length == 0) {
return;
}
// 确定桶的数量,这里设为数组长度
int numberOfBuckets = arr.length;
List<List<Float>> buckets = new ArrayList<>(numberOfBuckets);
// 初始化桶
for (int i = 0; i < numberOfBuckets; i++) {
buckets.add(new ArrayList<>());
}
// 将数组元素放入对应的桶中
for (float value : arr) {
// 计算桶的索引
int index = (int) Math.floor(numberOfBuckets * value);
buckets.get(index).add(value);
}
// 对每个桶进行排序,这里使用了Collections.sort()方法
int sortedIndex = 0;
for (List<Float> bucket : buckets) {
bucket.sort(null); // 自动使用Comparable接口排序
for (float v : bucket) {
arr[sortedIndex++] = v;
}
}
}
public static void main(String[] args) {
float[] array = {0.78f, 0.17f, 0.39f, 0.26f, 0.72f, 0.94f, 0.21f, 0.12f, 0.23f, 0.68f};
bucketSort(array);
System.out.println("Sorted Array: ");
System.out.println(Arrays.toString(array));
}
}
Sorted Array:
[0.12, 0.17, 0.21, 0.23, 0.26, 0.39, 0.68, 0.72, 0.78, 0.94]
基数排序
package com.orchids.leetcode.sort;
import java.util.Arrays;
/**
* @ Author qwh
* @ Date 2024/7/28 16:48
*/
public class radix {
public static void radixSort(int[] arr) {
// 找出数组中的最大值
int max = Arrays.stream(arr).max().getAsInt();
// 从个位数开始,一直到最高位
for (int exp = 1; max / exp > 0; exp *= 10) {
// 对每一位使用计数排序
countSort(arr, exp);
}
}
private static void countSort(int[] arr, int digit) {
int[] output = new int[arr.length];
int[] count = new int[10];
// 计算每个数字出现的次数
for (int i = 0; i < arr.length; i++) {
int index = (arr[i] / digit) % 10;
count[index]++;
}
// 累加计数数组
for (int i = 1; i < 10; i++) {
count[i] += count[i - 1];
}
// 构建输出数组
for (int i = arr.length - 1; i >= 0; i--) {
int index = (arr[i] / digit) % 10;
output[count[index] - 1] = arr[i];
count[index]--;
}
// 复制输出数组到原数组
System.arraycopy(output, 0, arr, 0, arr.length);
}
public static void main(String[] args) {
int[] array = {170, 45, 75, 90, 802, 24, 2, 66};
radixSort(array);
System.out.println("Sorted Array: ");
System.out.println(Arrays.toString(array));
}
}
Sorted Array:
[2, 24, 45, 66, 75, 90, 170, 802]
循环排序
循环排序是一种比较简单的排序算法,它试图将每个元素放到其正确的、最终的位置,从而避免了额外的交换操作。循环排序在最坏情况下的时间复杂度为O(n^2),但在平均情况下,它的性能比冒泡排序和插入排序要好,因为它减少了交换的次数。
- 循环排序的步骤:
- 遍历数组:从数组的第一个元素开始遍历。
- 找到目标位置:对于当前元素,找到它在排序数组中应有的位置。
- 移动元素:如果当前元素不在正确的位置,就将它移动到正确的位置。这可能涉及到一系列的元素移动。
- 重复步骤2和3:继续处理下一个元素,直到所有元素都被放置在正确的位置。
package com.orchids.leetcode.sort;
import java.util.Arrays;
/**
* @ Author qwh
* @ Date 2024/7/28 16:50
*/
public class cycle {
public static void cycleSort(int[] arr) {
int writes = 0;
// 循环遍历数组中的每个元素
for (int cycleStart = 0; cycleStart < arr.length - 1; cycleStart++) {
int item = arr[cycleStart];
int pos = cycleStart;
// 寻找元素的正确位置
for (int i = cycleStart + 1; i < arr.length; i++) {
if (arr[i] < item) {
pos++;
}
}
// 如果元素已经在正确的位置,跳过
if (pos == cycleStart) {
continue;
}
// 移动元素到正确的位置
while (item == arr[pos]) {
pos++;
}
int temp = item;
item = arr[pos];
arr[pos] = temp;
writes++;
// 继续移动其他元素
while (pos != cycleStart) {
pos = cycleStart;
for (int i = cycleStart + 1; i < arr.length; i++) {
if (arr[i] < item) {
pos++;
}
}
while (item == arr[pos]) {
pos++;
}
temp = item;
item = arr[pos];
arr[pos] = temp;
writes++;
}
}
}
// 测试函数
public static void main(String[] args) {
int[] array = {3, 1, 5, 4, 2};
cycleSort(array);
System.out.println("Sorted Array: ");
System.out.println(Arrays.toString(array));
}
}