排序算法
常见的排序算法包括以下几种:
-
冒泡排序(Bubble Sort):将相邻的元素进行比较和交换,每轮将最大(或最小)的元素推至末尾,重复进行直到所有元素有序为止。时间复杂度为O(n^2)。
-
插入排序(Insertion Sort):将数组分为已排序和未排序两部分,每次从未排序部分取一个元素插入到已排序部分的合适位置,重复进行直到所有元素有序为止。时间复杂度为O(n^2)。
-
希尔排序(Shell Sort):希尔排序是直接插入排序的一种改进算法,它通过预先将数组分成多个较小的子序列进行插入排序,从而达到减少比较和交换次数的目的。希尔排序的时间复杂度取决于增量序列的选择,一般平均情况下为O(n log n),最坏情况下为O(n^2)。
-
选择排序(Selection Sort):将数组分为已排序和未排序两部分,每次从未排序部分选取最小(或最大)的元素,并将其放到已排序部分的末尾,重复进行直到所有元素有序为止。时间复杂度为O(n^2)。
-
快速排序(Quick Sort):选择一个基准元素,将数组分为比基准小和比基准大的两部分,然后对这两部分分别进行递归排序,最终将整个数组有序化。时间复杂度为O(nlogn)。
-
归并排序(Merge Sort):将数组不断地二分为小的子数组,然后将这些子数组进行有序合并,最终得到完全有序的数组。时间复杂度为O(nlogn)。
-
堆排序(Heap Sort):将数组构建成一个堆数据结构,然后不断地从堆顶取出最大(或最小)的元素,并调整堆保持有序,最终得到有序的数组。时间复杂度为O(nlogn)。
-
计数排序(Counting Sort):统计元素出现的次数,然后根据元素的顺序依次输出,适用于元素范围较小的情况。时间复杂度为O(n+k),其中k为元素的范围。
这些排序算法各有特点和适用场景,选择合适的算法可以提高排序的效率。
数据定义
-
数据类型包括两种 :数组、链表
-
值类型 : int 类型
-
生成代码
- 数组
public static int[] generateRandomIntArray(int size, int min, int max) { int[] array = new int[size]; Random random = new Random(); for (int i = 0; i < size; i++) { array[i] = random.nextInt(max - min + 1) + min; } return array; } public static int size = 10; // 数组长度 public static int min = 1; // 随机数范围的最小值 public static int max = 100; // 随机数范围的最大值 int[] array = generateRandomIntArray(size, min, max); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); }
- 链表
static class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; } } public static ListNode generateRandomLinkedList(int size, int min, int max) { ListNode dummy = new ListNode(0); ListNode curr = dummy; Random random = new Random(); for (int i = 0; i < size; i++) { int randomNumber = random.nextInt(max - min + 1) + min; ListNode newNode = new ListNode(randomNumber); curr.next = newNode; curr = curr.next; } return dummy.next; } public static void printLinkedList(ListNode head) { ListNode curr = head; while (curr != null) { System.out.print(curr.val + " "); curr = curr.next; } } int size = 10; // 数组长度 int min = 1; // 随机数范围的最小值 int max = 100; // 随机数范围的最大值 ListNode head = generateRandomLinkedList(size, min, max); // 打印生成的随机链表 printLinkedList(head);
-
工具函数
//交换数组中的值 public static void swap(int[] arr,int i,int j){ arr[j] += arr[i]; arr[i] = arr[j] - arr[i]; arr[j] = arr[j] - arr[i]; }
一.冒泡排序
冒泡排序是一种简单直观的排序算法,它重复地比较相邻的元素,并交换它们的位置,直到整个序列有序为止。冒泡排序的基本思想是每次将最大(或最小)的元素冒泡到序列的末尾,类似气泡从水底冒出的过程,因此得名冒泡排序。
具体步骤如下:
- 从序列的第一个元素开始,依次比较相邻的两个元素。
- 如果前一个元素大于后一个元素,则交换它们的位置。
- 继续比较下一对相邻元素,重复步骤2。
- 重复执行步骤1~3,直到序列末尾,这样最大(或最小)的元素就会被冒泡到末尾。
- 缩小排序范围,重复执行步骤1~4,直到整个序列有序。
冒泡排序的关键在于每次循环比较相邻的元素,并进行交换。通过多次循环,每次将一个最大(或最小)的元素放在正确的位置上,逐渐实现整个序列的排序。
冒泡排序的时间复杂度为O(n^2),其中n是待排序序列的长度。这是因为每个元素需要与其余元素进行比较和交换,导致循环嵌套。最好情况下,如果序列已经有序,冒泡排序可通过添加一个标志位来提前结束,达到线性时间复杂度O(n)。
冒泡排序是一种稳定的排序算法,即相等元素的相对次序不会改变。它的空间复杂度为O(1),因为只需要常数级别的额外空间来存储临时变量。
尽管冒泡排序的时间复杂度较高,但由于其简单直观的操作方式,适用于小规模的数据排序。对于大规模的数据排序,通常会选择更高效的排序算法,如快速排序、归并排序等。
1. 对于数组的冒泡排序
- 排序
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = n - 1; i >= 0; i--) {
// 相较于传统冒泡,添加一个标志位,当当前循环结束,未发生元素交换,说明排序结束
boolean tag = true;
for (int j = 0; j < i ; j++) {
if(arr[j]>arr[j+1]){
arr[j] +=arr[i];
arr[i] = arr[j]-arr[i];
arr[j] = arr[j]-arr[i];
tag = false;
}
}
if (tag){
break;
}
}
}
-
测试代码
int size = 10; // 数组长度 int min = 1; // 随机数范围的最小值 int max = 100; // 随机数范围的最大值 int[] array = generateRandomIntArray(size, min, max); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); } System.out.println(); bubbleSort(array); for (int num : array) { System.out.print(num + " "); }
-
结果
2. 对于链表的冒泡排序
- 排序
public static ListNode bubbleSortLink(ListNode head) {
if (head == null || head.next == null) {
// 链表为空或只有一个节点,直接返回
return head;
}
ListNode dummy = new ListNode(min-1);
dummy.next = head;
ListNode curr, prev;
// 标志位,当前循环结束,未发生元素交换,说明排序结束
boolean tag;
do {
tag = false;
// 不能写成 head 因为在每次循环后 dummy.next 可能会改变
curr = dummy.next;
prev = dummy;
while (curr != null && curr.next != null) {
if (curr.val > curr.next.val) {
prev.next = curr.next;
curr.next = prev.next.next;
prev.next.next = curr;
tag = true;
}
prev = prev.next;
curr = prev.next;
}
} while (tag);
return dummy.next;
}
-
测试代码
ListNode head = generateRandomLinkedList(size, min, max); System.out.print("\n排序前"); // 打印生成的随机链表 printLinkedList(head); head = bubbleSortLink(head); System.out.print("\n排序后"); printLinkedList(head);
-
结果
二. 插入排序
插入排序是一种简单直观的排序算法,它将待排序的序列不断地分为已排序和未排序两部分,每次从未排序部分取出一个元素,将其插入到已排序部分的适当位置,直到整个序列有序为止。插入排序的基本思想是将元素插入到已排序部分,类似打扑克牌时按照大小顺序插入到合适的位置。
具体步骤如下:
- 从第二个元素开始,将其视为已排序部分。
- 取出下一个元素,将它与已排序部分从右到左进行比较。
- 如果已排序部分中的元素大于待插入的元素,则将已排序部分中的元素向右移动一个位置。
- 重复步骤3,直到找到待插入元素的正确位置。
- 将待插入元素插入到正确位置。
- 重复步骤2~5,直到所有元素都插入到已排序部分,整个序列有序。
插入排序的关键在于不断将未排序部分的元素插入到已排序部分的合适位置。通过多次迭代,每次将未排序部分中的一个元素插入到已排序部分,逐渐实现整个序列的排序。
插入排序的时间复杂度为O(n^2),其中n是待排序序列的长度。这是因为每个元素需要与已排序部分的元素进行比较和移动,导致循环嵌套。最理想情况下,如果序列已经有序,则插入排序可以达到线性时间复杂度O(n),因为只需要遍历一次。
插入排序是一种稳定的排序算法,即相等元素的相对次序不会改变。它的空间复杂度为O(1),因为只需要常数级别的额外空间来存储临时变量。
与冒泡排序相比,插入排序的交换操作更加高效,因为只需要移动元素而不需要每次都进行交换。插入排序尤其适用于基本有序的序列,可有效地对局部的逆序对进行排序。
尽管插入排序在大规模数据排序时性能较低,但对于小规模数据或已接近有序的数据排序,插入排序是一个简单且有效的选择。此外,插入排序也常用作其他高级排序算法的辅助排序阶段。
1. 对于数组的插入排序
-
排序
public static void insertionSort(int[] arr) { int n = arr.length; // 从 1 开始排序,因为 0 位时只有一个元素,肯定是有序的 for (int i = 1; i < n ; i++) { // 插入时操作类似于冒泡,每次跟前一个元素比较,若小于,则交换元素,当比前一个大时,结束冒泡 // for (int j = i ; j > 0 ; j--) { // if (arr[j]>=arr[j-1]){ // break; // } // arr[j]+=arr[j-1]; // arr[j-1] = arr[j]-arr[j-1]; // arr[j] = arr[j]-arr[j-1]; // } //前一种方式,值交换次数过多废弃 int temp = arr[i]; int j = i; for (; j > 0 ; j--) { if (temp>=arr[j-1]){ break; } arr[j]=arr[j-1]; } arr[j]= temp; } }
-
测试代码
int[] array = generateRandomIntArray(size, min, max);
System.out.print("\n排序前");
// 打印生成的随机数组
for (int num : array) {
System.out.print(num + " ");
}
System.out.print("\n排序后");
insertionSort(array);
for (int num : array) {
System.out.print(num + " ");
}
- 结果
2. 对于链表的插入排序
- 排序
public static ListNode insertionSortLink(ListNode head) {
if (head == null || head.next == null) {
// 链表为空或只有一个节点,直接返回
return head;
}
ListNode dummy = new ListNode(min-1);
ListNode curr = head;
while (curr!=null){
ListNode next = curr.next;
ListNode insertPos = dummy;
// 寻找插入点
while (insertPos.next!=null&&insertPos.next.val<curr.val){
insertPos = insertPos.next;
}
curr.next = insertPos.next;
insertPos.next=curr;
curr = next;
}
return dummy.next;
}
- 测试代码
ListNode head = generateRandomLinkedList(size, min, max);
System.out.print("\n排序前");
// 打印生成的随机链表
printLinkedList(head);
head = insertionSortLink(head);
System.out.print("\n排序后");
printLinkedList(head);
- 结果
三. 希尔排序
希尔排序(Shell Sort),也称为缩小增量排序,是插入排序的一种改进算法。希尔排序通过将待排序序列拆分成多个子序列来进行排序,使得子序列中的元素间隔较远,然后逐步缩小间隔,最终使得整个序列有序。
具体步骤如下:
- 选择一个增量序列,通常是按照一定规则生成的,如n/2,n/4,n/8等。
- 根据增量序列,将待排序序列分为多个子序列,每个子序列包含间隔为增量的元素。
- 对每个子序列进行插入排序,即将每个子序列按照插入排序的方式进行排序。
- 逐步缩小增量序列,重复步骤2和步骤3,直到增量序列为1。
- 最后进行一次增量为1的插入排序,将整个序列排序成最终有序状态。
希尔排序的关键在于选择合适的增量序列。增量序列的选择会影响希尔排序的性能。常用的增量序列的选择方法有希尔增量、Hibbard增量、Sedgewick增量等。不同的增量序列会导致希尔排序的时间复杂度和性能不同。
增量序列的选择方法是希尔排序算法中的一个重要部分,不同的增量序列会影响希尔排序的性能和效率。下面介绍几种常用的增量序列选择方法:
- 希尔增量(Shell’s Increment):
希尔增量是最简单的增量序列,它的选取方式是将原始序列长度不断除以2,直到增量为1。例如,如果序列长度为n,希尔增量的选择方式可以是 n/2,n/4,n/8,直到增量为1。希尔增量序列的性能依赖于具体的序列长度,在一定程度上可以提高性能。 - Hibbard增量:
Hibbard增量序列的选择方式是将2^k - 1作为增量序列的元素。其中k从大到小递减,直到增量为1。也就是说,Hibbard增量序列的选择方式是1,3,7,15,…,2^k - 1。Hibbard增量序列在一开始有较大的增量,逐渐减小增量,有助于快速减小逆序对的数量,因此在一些特定情况下具有较好的性能。 - Sedgewick增量:
Sedgewick增量序列是由Sedgewick提出的,其选择方式基于经验和实践。Sedgewick增量序列的选择方式是通过组合两个序列生成的:{1, 5, 19, 41, 109, …} 和 {9, 23, 57, 149, 393, …}。Sedgewick增量序列在大多数情况下表现良好,具有较好的性能。
选择合适的增量序列对于希尔排序的性能和效率非常重要。增量序列的选择应该能够减少逆序对的数量,使得插入排序部分的比较和交换操作尽可能高效。然而,并不存在一个通用的最优增量序列,因为不同的序列长度和数据特征可能对不同的增量序列有不同的最佳选择。因此,对于不同的排序问题,需要根据实际情况进行实验和分析来选择合适的增量序列。
希尔排序的时间复杂度是和增量序列有关的,最坏情况下为O(n^2),最好情况下可以达到O(n log n)。希尔排序的时间复杂度一般较低,尤其对于中等大小的数组,具有比较高的效率。
希尔排序是不稳定的排序算法,即相等元素的相对次序可能会改变。这是因为子序列之间的插入排序可能会改变相等元素的顺序。
希尔排序是一种原地排序算法,不需要额外的空间。希尔排序的空间复杂度为O(1)。
希尔排序相对于简单的插入排序,在大规模数据和随机数据排序时具有更优的性能。它是一种简单但有效的排序算法,适用于各种规模的数据排序。
1. 对于数组的希尔排序
-
排序
第一版: 按照希尔排序定义进行代码实现 public static void shellSort(int[] arr){ int n = arr.length; // 本算法采用的增量序列是希尔增量(Shell’s Increment) // 选取方式是将原始序列长度不断除以2,直到增量为1。例如,如果序列长度为n,希尔增量的选择方式可以是 n/2,n/4,n/8,直到增量为1。 int shellCode = n/2; while (shellCode>0){ for (int k = 0; k < shellCode; k++) { for (int i = k; i < n; i+=shellCode) { for (int j = i ; j - shellCode>= 0 ; j = j - shellCode) { if (arr[j]>=arr[j-shellCode]){ break; } arr[j]+=arr[j-shellCode]; arr[j-shellCode] = arr[j]-arr[j-shellCode]; arr[j] = arr[j]-arr[j-shellCode]; } } } shellCode /=2; } } 第二版: 部分优化 对于原来的四层循环,优化成三层循环,同时通过 temp 变量优化了对于值交换时的步骤 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 ; for (; j >= gap&&temp<arr[j-gap]; j-=gap) { arr[j] = arr[j-gap]; } arr[j] = temp; } } }
-
测试代码
System.out.print("\n排序前"); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); } System.out.print("\n排序后"); shellSort(array); int pero = -1; boolean isRight = true; for (int num : array) { if (num < pero) { isRight = false; } pero = num; System.out.print(num + " "); } System.out.println("\nisRight " + isRight);
-
结果
2. 对于链表的希尔排序
-
希尔排序是一种适用于数组的排序算法,因为它需要在序列中进行跳跃式的访问,这在链表结构中并不适用。链表不支持随机访问,因此无法像在数组中那样通过索引来访问元素。
然而,你可以把链表转换成数组,然后对数组应用希尔排序,最后再将排序后的数组重新构造成链表
四. 选择排序
选择排序(Selection Sort)是一种简单而直观的排序算法。它的基本思想是在未排序的部分中选择最小(或最大)的元素,并将其与未排序部分的第一个元素交换位置,将该元素移到已排序部分的末尾。重复这个过程,直到整个序列排序完成。
选择排序的具体步骤如下:
- 遍历数组,找到最小元素的索引。
- 将最小元素与未排序部分的第一个元素交换位置。
- 将排序部分的末尾向前移动一位,扩大排序部分的范围。
- 重复执行步骤1-3,直到排序完成。
下面以升序为例,给出选择排序的详细解释:
初始数组: [5, 3, 8, 2, 1]
第1次遍历:
找到最小元素1,并将其与第一个元素5交换位置,得到数组 [1, 3, 8, 2, 5]
第2次遍历:
在从第二个元素开始的未排序部分([3, 8, 2, 5])中找到最小元素2,并将其与第二个元素3交换位置,得到数组 [1, 2, 8, 3, 5]
第3次遍历:
在从第三个元素开始的未排序部分([8, 3, 5])中找到最小元素3,并将其与第三个元素8交换位置,得到数组 [1, 2, 3, 8, 5]
第4次遍历:
在从第四个元素开始的未排序部分([8, 5])中找到最小元素5,并将其与第四个元素8交换位置,得到数组 [1, 2, 3, 5, 8]
排序完成。
最终排序结果:[1, 2, 3, 5, 8]
选择排序是一种稳定的排序算法,每次遍历中都会找到未排序部分的最小元素,并将其放到已排序部分的末尾。然而,选择排序的时间复杂度为O(n^2),因为它需要执行n次遍历,每次遍历中都需要查找未排序部分的最小元素。由于算法的不稳定性和较高的时间复杂度,选择排序在大规模和需要高效排序的情况下并不适用。
需要注意的是,选择排序的优势在于其简单性和对于较小规模数据的排序效率。对于小规模的数组或者是基本有序的数组,选择排序相对来说是一种不错的选择。但是当面临大规模数据的排序时,我们通常会采用更高效的排序算法,如快速排序、归并排序或堆排序等。
1. 对于数组的选择排序
-
排序
public static void selectionSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { int min = i; for (int j = i + 1; j < n; j++) { if (arr[j]<arr[min]){ min = j; } } if (min!=i){ arr[i] = arr[i]+arr[min]; arr[min] = arr[i]-arr[min]; arr[i] = arr[i]-arr[min]; } } }
-
测试代码
System.out.print("\n排序前"); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); } System.out.print("\n排序后"); selectionSort(array); int pero = -1; boolean isRight = true; for (int num : array) { if (num < pero) { isRight = false; } pero = num; System.out.print(num + " "); }
-
结果
2. 对于链表的选择排序
-
排序
public static ListNode selectionSortLink(ListNode head){ if (head == null || head.next == null) { // 链表为空或只有一个节点,直接返回 return head; } ListNode dummy = new ListNode(min - 1); dummy.next = head; ListNode prov = dummy; ListNode minPre; while (prov.next!=null){ minPre = prov; ListNode node = minPre; while (node.next!=null){ if (node.next.val<minPre.next.val){ minPre = node; } node = node.next; } ListNode min = minPre.next; minPre.next = min.next; min.next = prov.next; prov.next = min; prov = prov.next; } return dummy.next; }
-
测试代码
ListNode head = generateRandomLinkedList(size, min, max); System.out.print("\n排序前"); // 打印生成的随机链表 printLinkedList(head); head = selectionSortLink(head); System.out.print("\n排序后"); printLinkedList(head);
-
结果
五. 快速排序
快速排序(Quick Sort)是一种常用的高效排序算法,它采用了分治的策略。快速排序的基本思想是选择一个基准元素,将数组分成两部分,使得左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素。然后递归地对左右两部分进行排序,直到整个序列排序完成。
下面详细解释快速排序的步骤:
- 选择基准元素:从待排序的数组中选择一个基准元素。通常选择数组的第一个元素作为基准元素。
- 划分操作:将数组中大于基准元素的放到右边,小于基准元素的放到左边,相同的元素可以放到任意一边。划分操作可以通过定义两个指针,从数组的两端开始遍历,将不符合顺序的元素进行交换。
- 递归排序:递归地对左右两个部分进行排序,直到每个部分只有一个元素或为空。
- 合并操作:将排好序的左右两部分合并在一起。
1. 对于数组的快速排序
-
排序
public static void quickSort(int[] arr) { quickSort(arr, 0, arr.length - 1); } 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[low]; int i = low + 1; int j = high; while (true) { while (i <= j && arr[i] < pivot) { i++; } while (i <= j && arr[j] > pivot) { j--; } if (i>=j){ break; } swap(arr,i,j); i++; j--; } swap(arr,j,low); return j; }
-
测试代码
int[] array = generateRandomIntArray(size, min, max); System.out.print("\n排序前"); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); } System.out.print("\n排序后"); quickSort(array); int pero = min - 1; boolean isRight = true; for (int num : array) { if (num < pero) { isRight = false; } pero = num; System.out.print(num + " "); }
-
结果
2. 对于链表的快速排序
-
排序
public static ListNode quickSortLink(ListNode head){ if (head==null||head.next==null){ return head; } return quickSortLink(head,null); } public static ListNode quickSortLink(ListNode head, ListNode end) { if(head != end){ ListNode pivotNode = partitionLink(head,end); quickSortLink(head,pivotNode); quickSortLink(pivotNode.next,end); } return head; } private static ListNode partitionLink(ListNode head, ListNode end) { int key = head.val; ListNode pero = head; ListNode curr = pero.next; while (curr!=end){ if (curr.val<key){ pero=pero.next; int temp = pero.val; pero.val = curr.val; curr.val = temp; } curr=curr.next; } int temp = head.val; head.val = pero.val; pero.val = temp; return pero; }
-
测试代码
ListNode head = generateRandomLinkedList(size, min, max); System.out.print("\n排序前"); // 打印生成的随机链表 printLinkedList(head); head = quickSortLink(head); System.out.print("\n排序后"); printLinkedList(head);
-
结果
六. 归并排序
归并排序(Merge Sort)是一种分治算法,它将一个待排序的数组或链表不断拆分为两个子序列,然后对子序列进行排序,最后再将排好序的子序列合并,从而得到完全有序的序列。归并排序的基本思想是先将数组/链表拆分为最小的单元,然后不断合并两个有序的子序列,直到整个序列排序完成。
下面是归并排序的详细步骤:
- 拆分:将数组/链表拆分为两个子序列,分别递归调用归并排序。
- 合并:将排好序的子序列合并,生成一个更大的有序序列。
- 对于数组,使用两个指针分别指向两个子序列的起始位置,比较两个指针所指元素的大小,将较小的元素放入临时数组,移动指针。重复此步骤,直到将两个子序列合并为一个完整的有序序列。
- 对于链表,使用两个指针分别指向两个子序列的起始位置,遍历两个子序列的节点,比较节点的值,将较小的节点链接到结果链表中,并移动指针。重复此步骤,直到将两个子序列合并为一个完整的有序链表。
- 最终合并:当整个序列只剩一个元素时,已经是有序的,无需再次合并。
1. 对于数组的归并排序
-
排序
public static int[] mergeSort(int[] arr) { return mergeSort(arr, 0, arr.length - 1); } private static int[] mergeSort(int[] arr, int begin, int end) { if (begin == end) { return new int[]{arr[begin]}; } int mid = (end - begin) / 2 + begin; int[] arr1 = mergeSort(arr, begin, mid); int[] arr2 = mergeSort(arr, mid + 1, end); arr = mergeArr(arr1, arr2); return arr; } public static int[] mergeArr(int[] arr1, int[] arr2) { int n = arr1.length, m = arr2.length; int[] arr = new int[m + n]; int i = 0, j = 0, k = 0; while (k < arr.length) { int a = i < n ? arr1[i] : Integer.MAX_VALUE; int b = j < m ? arr2[j] : Integer.MAX_VALUE; if (a < b) { i++; arr[k++] = a; }else { j++; arr[k++] = b; } } return arr; }
-
测试代码
int[] array = generateRandomIntArray(size, min, max); System.out.print("\n排序前"); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); } System.out.print("\n排序后"); array = mergeSort(array); int pero = min - 1; boolean isRight = true; for (int num : array) { if (num < pero) { isRight = false; } pero = num; System.out.print(num + " "); } System.out.println("\nisRight " + isRight);
-
结果
2. 对于链表的归并排序
-
排序
public static ListNode mergeSortLink(ListNode head) { if (head == null || head.next == null) { return head; } ListNode mid = getListMid(head); ListNode midNext = mid.next; mid.next = null; ListNode l1 = mergeSortLink(head); ListNode l2 = mergeSortLink(midNext); return mergeLink(l1, l2); } public static ListNode mergeLink(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode pero = dummy; while (l1!=null||l2!=null){ int a = l1!=null?l1.val:Integer.MAX_VALUE; int b = l2!=null?l2.val:Integer.MAX_VALUE; if (a<b){ pero.next = l1; l1 = l1.next; }else { pero.next = l2; l2 = l2.next; } pero = pero.next; pero.next = null; } return dummy.next; } public static ListNode getListMid(ListNode head) { if (head == null) { return null; } ListNode slow = head; ListNode fast = head; while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }
-
测试代码
ListNode head = generateRandomLinkedList(size, min, max); System.out.print("\n排序前"); // 打印生成的随机链表 printLinkedList(head); head = mergeSortLink(head); System.out.print("\n排序后"); printLinkedList(head);
-
结果
七. 堆排序
堆排序(Heap Sort)是一种基于堆数据结构的排序算法。堆是一种特殊的二叉树,满足以下性质:
- 堆是一个完全二叉树,即除了最后一层可能不满外,其他层都是满的。
- 堆中的节点值具有特殊的顺序性:
- 对于每个节点i,其父节点j的值不大于(或不小于)节点i的值,这取决于是最大堆还是最小堆。
堆排序的基本思想是:
- 构建一个最大堆(或最小堆),将待排序的数组构建成一个堆。
- 将堆顶元素(最大元素或最小元素)与数组最后一个元素交换,使最大(或最小)元素放入数组的末尾。
- 重新构建堆,再次将堆的顶部元素与倒数第二个元素交换,继续将最大(或最小)元素放入数组末尾。
- 重复上述步骤,直到整个数组有序化。
以下是堆排序的详细步骤:
- 建堆:将待排序的数组构建成堆。从最后一个非叶子节点开始,依次向上调整节点,使得每个节点都满足堆的性质。
- 排序:将堆顶元素(最大元素或最小元素)与数组最后一个元素交换,然后将堆的大小减1,从堆顶开始自上而下调整堆,使得整个数组再次满足堆的性质。
- 重复执行步骤2,直到堆的大小为1,即整个数组有序化。
1. 对于数组的堆排序
-
排序
public static void heapSort(int[] arr) { int n = arr.length; for(int i = n/2-1;i>=0;i--){ maxHeapify(arr, i, n - 1); } for (int i = n-1; i > 0 ; i--) { swap(arr,0,i); maxHeapify(arr, 0, i - 1); } } public static void maxHeapify(int[] arr, int start, int end) { //建立父节点指标和子节点指标 int dad = start; int son = dad * 2 + 1; while (son <= end) { //若子节点指标在范围内才做比较 if (son + 1 <= end && arr[son] < arr[son + 1]) { //先比较两个子节点大小,选择最大的 son++; } if (arr[dad] > arr[son]) { //如果父节点大于子节点代表调整完毕,直接跳出函数 return; } else { //否则交换父子内容再继续子节点和孙节点比较 swap(arr,dad,son); dad = son; son = dad * 2 + 1; } } return; }
-
测试代码
int[] array = generateRandomIntArray(size, min, max); System.out.print("\n排序前"); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); } System.out.print("\n排序后"); heapSort(array); int pero = min - 1; boolean isRight = true; for (int num : array) { if (num < pero) { isRight = false; } pero = num; System.out.print(num + " "); } System.out.println("\nisRight " + isRight);
-
结果
2. 对于链表的堆排序
对于单链表的堆排序
堆排序是基于数组实现的,由于单链表无法像数组那样进行随机访问和交换操作,因此直接在单链表上进行堆排序是困难的。
如果要对单链表进行堆排序,可以先将单链表转换为数组,进行堆排序后再将排序好的数组转换回单链表的形式。具体的步骤如下:
- 将单链表的节点值存储到一个数组中。
- 在数组上进行堆排序。
- 将排序好的数组中的元素重新连接成一个有序的单链表。
八. 计数排序
计数排序(Counting Sort)是一种线性时间复杂度的排序算法,适用于整数排序,并且对输入数据的范围有一定的限制。计数排序不是基于比较的排序算法,而是通过确定每个元素之前有多少个元素来确定其在排序后的位置。
以下是计数排序的详细步骤:
- 找出待排序数组中的最大值max和最小值min。
- 创建一个计数数组count,长度为max-min+1,用于记录每个元素出现的次数。
- 遍历待排序数组,统计每个元素出现的次数,将其存放在计数数组的相应位置上。
- 根据计数数组,重新构造排序后的数组:从最小值开始,依次将计数数组的索引值放入结果数组,并将对应的计数数组的值减一。
- 排序完成后,得到的结果数组即为排序好的数组。
计数排序的优势在于其具有稳定性和线性时间复杂度的特点,但也有一些限制:
- 由于需要额外的计数数组,所以空间复杂度较高。
- 当排序的数据范围很大时,计数排序的效率会降低。
需要注意的是,计数排序对于待排序数组元素的范围有一定的限制,如果范围很大,会导致计数数组的长度过大,从而影响算法的效率和空间复杂度。
1. 对于数组的计数排序
-
排序
public static void countingSort(int[] arr) { int min = arr[0]; int max = arr[0]; // 找出待排序数组的最大值和最小值 for (int i = 1; i < arr.length; i++) { if (arr[i] < min) { min = arr[i]; } if (arr[i] > max) { max = arr[i]; } } // 创建计数数组并统计元素出现的次数 int[] count = new int[max - min + 1]; for (int i = 0; i < arr.length; i++) { count[arr[i] - min]++; } // 根据计数数组重新构造排序后的数组 int index = 0; for (int i = 0; i < count.length; i++) { while (count[i] > 0) { arr[index] = i + min; index++; count[i]--; } } }
-
测试代码
int[] array = generateRandomIntArray(size, min, max); System.out.print("\n排序前"); // 打印生成的随机数组 for (int num : array) { System.out.print(num + " "); } System.out.print("\n排序后"); countingSort(array); int pero = min - 1; boolean isRight = true; for (int num : array) { if (num < pero) { isRight = false; } pero = num; System.out.print(num + " "); } System.out.println("\nisRight " + isRight);
-
结果
2. 对于链表的计数排序
-
排序
public static ListNode countingSortLink(ListNode head){ int min = head.val; int max = head.val; ListNode node = head; while (node!=null){ if (node.val < min){ min = node.val; } if (node.val >max){ max = node.val; } node = node.next; } // 创建计数数组并统计元素出现的次数 int[] count = new int[max - min + 1]; node = head; while (node!=null){ count[node.val - min]++; node = node.next; } node = head; int index = 0; while (node!=null){ while (count[index]>0){ node.val = index+min; count[index]--; node=node.next; } index++; } return head; }
-
测试代码
ListNode head = generateRandomLinkedList(size, min, max); System.out.print("\n排序前"); // 打印生成的随机链表 printLinkedList(head); head = countingSortLink(head); System.out.print("\n排序后"); printLinkedList(head);
-
结果
附加:全部代码
package com.kc.hexo;
import java.util.Random;
public class MySort {
public static int size = 10; // 数组长度
public static int min = 1; // 随机数范围的最小值
public static int max = 1000; // 随机数范围的最大值
public static Random random = new Random();
static class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
}
public static ListNode generateRandomLinkedList(int size, int min, int max) {
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
for (int i = 0; i < size; i++) {
int randomNumber = random.nextInt(max - min + 1) + min;
ListNode newNode = new ListNode(randomNumber);
curr.next = newNode;
curr = curr.next;
}
return dummy.next;
}
public static void printLinkedList(ListNode head) {
ListNode curr = head;
while (curr != null) {
System.out.print(curr.val + " ");
curr = curr.next;
}
}
public static int[] generateRandomIntArray(int size, int min, int max) {
int[] array = new int[size];
Random random = new Random();
for (int i = 0; i < size; i++) {
array[i] = random.nextInt(max - min + 1) + min;
}
return array;
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] array = generateRandomIntArray(size, min, max);
System.out.print("\n排序前");
// 打印生成的随机数组
for (int num : array) {
System.out.print(num + " ");
}
System.out.print("\n排序后");
countingSort(array);
int pero = min - 1;
boolean isRight = true;
for (int num : array) {
if (num < pero) {
isRight = false;
}
pero = num;
System.out.print(num + " ");
}
System.out.println("\nisRight " + isRight);
System.out.println("\n====================分隔符====================");
ListNode head = generateRandomLinkedList(size, min, max);
System.out.print("\n排序前");
// 打印生成的随机链表
printLinkedList(head);
head = countingSortLink(head);
System.out.print("\n排序后");
printLinkedList(head);
}
public static ListNode countingSortLink(ListNode head){
int min = head.val;
int max = head.val;
ListNode node = head;
while (node!=null){
if (node.val < min){
min = node.val;
}
if (node.val >max){
max = node.val;
}
node = node.next;
}
// 创建计数数组并统计元素出现的次数
int[] count = new int[max - min + 1];
node = head;
while (node!=null){
count[node.val - min]++;
node = node.next;
}
node = head;
int index = 0;
while (node!=null){
while (count[index]>0){
node.val = index+min;
count[index]--;
node=node.next;
}
index++;
}
return head;
}
public static void countingSort(int[] arr) {
int min = arr[0];
int max = arr[0];
// 找出待排序数组的最大值和最小值
for (int i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > max) {
max = arr[i];
}
}
// 创建计数数组并统计元素出现的次数
int[] count = new int[max - min + 1];
for (int i = 0; i < arr.length; i++) {
count[arr[i] - min]++;
}
// 根据计数数组重新构造排序后的数组
int index = 0;
for (int i = 0; i < count.length; i++) {
while (count[i] > 0) {
arr[index] = i + min;
index++;
count[i]--;
}
}
}
public static void heapSort(int[] arr) {
int n = arr.length;
for(int i = n/2-1;i>=0;i--){
maxHeapify(arr, i, n - 1);
}
for (int i = n-1; i > 0 ; i--) {
swap(arr,0,i);
maxHeapify(arr, 0, i - 1);
}
}
public static void maxHeapify(int[] arr, int start, int end) {
//建立父节点指标和子节点指标
int dad = start;
int son = dad * 2 + 1;
while (son <= end) { //若子节点指标在范围内才做比较
if (son + 1 <= end && arr[son] < arr[son + 1]) {
//先比较两个子节点大小,选择最大的
son++;
}
if (arr[dad] > arr[son]) {
//如果父节点大于子节点代表调整完毕,直接跳出函数
return;
}
else { //否则交换父子内容再继续子节点和孙节点比较
swap(arr,dad,son);
dad = son;
son = dad * 2 + 1;
}
}
return;
}
public static ListNode mergeSortLink(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode mid = getListMid(head);
ListNode midNext = mid.next;
mid.next = null;
ListNode l1 = mergeSortLink(head);
ListNode l2 = mergeSortLink(midNext);
return mergeLink(l1, l2);
}
public static ListNode mergeLink(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode pero = dummy;
while (l1!=null||l2!=null){
int a = l1!=null?l1.val:Integer.MAX_VALUE;
int b = l2!=null?l2.val:Integer.MAX_VALUE;
if (a<b){
pero.next = l1;
l1 = l1.next;
}else {
pero.next = l2;
l2 = l2.next;
}
pero = pero.next;
pero.next = null;
}
return dummy.next;
}
public static ListNode getListMid(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public static int[] mergeSort(int[] arr) {
return mergeSort(arr, 0, arr.length - 1);
}
private static int[] mergeSort(int[] arr, int begin, int end) {
if (begin == end) {
return new int[]{arr[begin]};
}
int mid = (end - begin) / 2 + begin;
int[] arr1 = mergeSort(arr, begin, mid);
int[] arr2 = mergeSort(arr, mid + 1, end);
arr = mergeArr(arr1, arr2);
return arr;
}
public static int[] mergeArr(int[] arr1, int[] arr2) {
int n = arr1.length, m = arr2.length;
int[] arr = new int[m + n];
int i = 0, j = 0, k = 0;
while (k < arr.length) {
int a = i < n ? arr1[i] : Integer.MAX_VALUE;
int b = j < m ? arr2[j] : Integer.MAX_VALUE;
if (a < b) {
i++;
arr[k++] = a;
} else {
j++;
arr[k++] = b;
}
}
return arr;
}
public static ListNode quickSortLink(ListNode head) {
if (head == null || head.next == null) {
return head;
}
return quickSortLink(head, null);
}
public static ListNode quickSortLink(ListNode head, ListNode end) {
if (head != end) {
ListNode pivotNode = partitionLink(head, end);
quickSortLink(head, pivotNode);
quickSortLink(pivotNode.next, end);
}
return head;
}
private static ListNode partitionLink(ListNode head, ListNode end) {
int key = head.val;
ListNode pero = head;
ListNode curr = pero.next;
while (curr != end) {
if (curr.val < key) {
pero = pero.next;
int temp = pero.val;
pero.val = curr.val;
curr.val = temp;
}
curr = curr.next;
}
int temp = head.val;
head.val = pero.val;
pero.val = temp;
return pero;
}
public static void quickSort(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
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[low];
int i = low + 1;
int j = high;
while (true) {
while (i <= j && arr[i] < pivot) {
i++;
}
while (i <= j && arr[j] > pivot) {
j--;
}
if (i >= j) {
break;
}
swap(arr, i, j);
i++;
j--;
}
swap(arr, j, low);
return j;
}
public static ListNode selectionSortLink(ListNode head) {
if (head == null || head.next == null) {
// 链表为空或只有一个节点,直接返回
return head;
}
ListNode dummy = new ListNode(min - 1);
dummy.next = head;
ListNode prov = dummy;
ListNode minPre;
while (prov.next != null) {
minPre = prov;
ListNode node = minPre;
while (node.next != null) {
if (node.next.val < minPre.next.val) {
minPre = node;
}
node = node.next;
}
ListNode min = minPre.next;
minPre.next = min.next;
min.next = prov.next;
prov.next = min;
prov = prov.next;
}
return dummy.next;
}
public static void selectionSort(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
int min = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
if (min != i) {
arr[i] = arr[i] + arr[min];
arr[min] = arr[i] - arr[min];
arr[i] = arr[i] - arr[min];
}
}
}
// public static void shellSort(int[] arr){
// int n = arr.length;
// // 本算法采用的增量序列是希尔增量(Shell’s Increment)
// // 选取方式是将原始序列长度不断除以2,直到增量为1。例如,如果序列长度为n,希尔增量的选择方式可以是 n/2,n/4,n/8,直到增量为1。
// int shellCode = n/2;
// while (shellCode>0){
// for (int k = 0; k < shellCode; k++) {
// for (int i = k; i < n; i+=shellCode) {
// for (int j = i ; j - shellCode>= 0 ; j = j - shellCode) {
// if (arr[j]>=arr[j-shellCode]){
// break;
// }
// arr[j]+=arr[j-shellCode];
// arr[j-shellCode] = arr[j]-arr[j-shellCode];
// arr[j] = arr[j]-arr[j-shellCode];
// }
// }
// }
// shellCode /=2;
// }
// }
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;
for (; j >= gap && temp < arr[j - gap]; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
public static void insertionSort(int[] arr) {
int n = arr.length;
// 从 1 开始排序,因为 0 位时只有一个元素,肯定是有序的
for (int i = 1; i < n; i++) {
// 插入时操作类似于冒泡,每次跟前一个元素比较,若小于,则交换元素,当比前一个大时,结束冒泡
// for (int j = i ; j > 0 ; j--) {
// if (arr[j]>=arr[j-1]){
// break;
// }
// arr[j]+=arr[j-1];
// arr[j-1] = arr[j]-arr[j-1];
// arr[j] = arr[j]-arr[j-1];
// }
//前一种方式,值交换次数过多废弃
int temp = arr[i];
int j = i;
for (; j > 0; j--) {
if (temp >= arr[j - 1]) {
break;
}
arr[j] = arr[j - 1];
}
arr[j] = temp;
}
}
public static ListNode insertionSortLink(ListNode head) {
if (head == null || head.next == null) {
// 链表为空或只有一个节点,直接返回
return head;
}
ListNode dummy = new ListNode(min - 1);
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
ListNode insertPos = dummy;
// 寻找插入点
while (insertPos.next != null && insertPos.next.val < curr.val) {
insertPos = insertPos.next;
}
curr.next = insertPos.next;
insertPos.next = curr;
curr = next;
}
return dummy.next;
}
public static void bubbleSort(int[] arr) {
int n = arr.length;
for (int i = n - 1; i >= 0; i--) {
// 相较于传统冒泡,添加一个标志位,当当前循环结束,未发生元素交换,说明排序结束
boolean tag = true;
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
arr[j] += arr[i];
arr[i] = arr[j] - arr[i];
arr[j] = arr[j] - arr[i];
tag = false;
}
}
if (tag) {
break;
}
}
}
public static ListNode bubbleSortLink(ListNode head) {
if (head == null || head.next == null) {
// 链表为空或只有一个节点,直接返回
return head;
}
ListNode dummy = new ListNode(min - 1);
dummy.next = head;
ListNode curr, prev;
// 标志位,当前循环结束,未发生元素交换,说明排序结束
boolean tag;
do {
tag = false;
curr = dummy.next;
prev = dummy;
while (curr != null && curr.next != null) {
if (curr.val > curr.next.val) {
prev.next = curr.next;
curr.next = prev.next.next;
prev.next.next = curr;
tag = true;
}
prev = prev.next;
curr = prev.next;
}
} while (tag);
return dummy.next;
}
}