携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
一、前言
快速排序:是由东尼·霍尔提出的一种高效的排序算法,简称快排。
可以用 3 个步骤 6 个字来概括:选基、分割、递归:
- 选基: 首先挑选基准值
- 分割: 分割数组,把小于基准值的元素放到基准值前面,大于基准值的元素放到基准值后面
- 递归: 递归地对小于基准值的子序列和大于基准值的子序列进行排序
举栗,动图如下:
为什么快排会比冒泡等快?
因为其是跳跃式交换,即每次交换相隔较远的 2个元素。
快排实现方式有两种:Lomuto 分割方法 和 霍尔分割方法
Lomuto分割方法:选择开头或者结尾作为基准值。(实现简单,不易出错,效率差一些)- 霍尔分割方法:选中间值为基准值。
Lomuto快排实现:
public class QuickSort {
// Time: O(n * log(n)), Space: O(n)
public void lomutoSort(int [] arr) {
if (arr == null || arr.length == 0) return;
lomutoSort(arr, 0, arr.length - 1);
}
private void lomutoSort(int [] arr, int low, int high) {
if (low < high) {
int k = lomutoPartition(arr, low, high); // 1. 选基
// 2. 分割数组
lomutoSort(arr, low, k - 1); // 3. 递归处理:左侧边
lomutoSort(arr, k + 1, high); // 3. 递归处理:右侧边
}
}
private int lomutoPartition(int[] arr, int low, int high) {
int pivot = arr[high]; // 选择数组尾部为基准值 pivot
int i = low;
for (int j = low; j < high; ++j) {
if (arr[j] < pivot) {
swap(arr, i, high);
++i;
}
}
swap(arr, i, high); // 最后交换基准值
return i;
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
- 霍尔分割快排实现(推荐)
-
基准线:以中间点
-
i,j 两游标分别指向数组的头尾,向中间移动
i < privot,就向右移动j > privot,就向左移动- 当
i > privot || j < privot,则进行交换
public class QuickSort {
// Time: O(n * log(n)), Space: O(n)
public void hoareSort(int [] arr) {
if (arr == null || arr.length == 0) return;
hoareSort(arr, 0, arr.length - 1);
}
private void hoareSort(int [] arr, int low, int high) {
if (low < high) {
int k = hoarePartition(arr, low, high); // 1. 选基:中间点
// 2. 分割数组
hoareSort(arr, low, k); // 3. 递归:左侧边
hoareSort(arr, k + 1, high); // 3. 递归:右侧边
}
}
private int hoarePartition(int [] arr, int low, int high) {
int pivot = arr[low + (high - low) / 2];
int i = low, j = high;
while (true) {
while (arr[i] < pivot) ++i;
while (arr[j] > pivot) --j;
if (i >= j) return j;
swap(arr, i++, j--);
}
}
private void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
二、题目
单链表排序(中)
题干分析
这个题目说的是,给你一个单链表,你要写一个函数,对它进行排序,然后返回排序后的链表。
# 比如说,给你的单链表是:
4 -> 8 -> 2 -> 1
# 你要返回排序后的链表:
1 -> 2 -> 4 -> 8
思路解法
解法有二: 快排 和 归并排序。
- 方法一:快排
- 选基: 首先挑选基准值
- 分割: 分割数组,把小于基准值的元素放到基准值前面,大于基准值的元素放到基准值后面
- 递归: 递归地对小于基准值的子序列和大于基准值的子序列进行排序
Tips:链表交换,只需要交换节点里的值即可。
// Time: O(n*log(n)), Space: O(n), Faster: 6.60%
public ListNode quickSortList(ListNode head) {
quickSort(head, null);
return head;
}
private void quickSort(ListNode head, ListNode end) {
if (head == end || head.next == end) return;
int pivot = head.val; // 1. 选择基准线:头节点
ListNode slow = head, fast = head.next;
while (fast != end) {
if (fast.val <= pivot) {
slow = slow.next;
swap(slow, fast);
}
fast = fast.next;
}
// 2. 分割链表
swap(head, slow);
quickSort(head, slow); // 3. 递归:左侧链表
quickSort(slow.next, end); // 3. 递归:右侧链表
}
private void swap(ListNode a, ListNode b) {
int tmp = a.val;
a.val = b.val;
b.val = tmp;
}