title: 算法 - 排序
date: 2022-5-5
tags:
- 算法
- LeetCode
- 排序
categories: - 算法
- 排序
排序
一、归并排序
1.1 链表的归并排序
方法一(自顶向下):
- 通过快慢指针寻找中间节点(需要注意 fast 指针的初始化为 head 的 next )
- head 和 slow 指针分别指向两个链表,递归这两个链表
- 合并这两个链表
class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null){
return head;
}
// 需要注意的是:这里fast指针的初始指向head指针的next
ListNode fast = head.next, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// tmp 指向第二段链表的头部
ListNode tmp = slow.next;
// 断开链表
slow.next = null;
ListNode left = sortList(head);
ListNode right = sortList(tmp);
// h 用来合并链表
ListNode h = new ListNode(0);
// res 记录头节点
ListNode res = h;
while (left != null && right != null) {
if (left.val < right.val) {
h.next = left;
left = left.next;
} else {
h.next = right;
right = right.next;
}
h = h.next;
}
h.next = left != null ? left : right;
return res.next;
}
}
二、快速排序
快速排序的模板
步骤:
- 结束条件:左指针 >= 右指针
- 取 pivot :取 left 或者 取 (left, right] 区间的随机数
- 将区间内的 大于 pivot 的数放到 pivot 的右边,小于 pivot 的数放到 pivot的左边(这里需要注意的是:一定是先以动 right 指针,再以动 left 指针,因为最开始 pivot 会保存 left 指针位置的数,所以 当 right 指针找到一个 pivot 小的数,并将 left 指针位置的数置为 right 指针所指向的数时,由于 pivot 已经提前保存了该数,所以,不用怕覆盖掉,后续的循环中,left 和 right 会交替保存对方的数)
- 先移动 right 指针:找到一个比 pivot 小的数的 right 停止 while,将 left 索引的位置的数置为 right 指针所指的数
- 后移动 left 指针:找到一个比 pivot 大的数的 left 停止 while,将 right 指针所指向的的位置的数置为 left 位置所指向的数;
- 循环结束后,left 会等于 right,该位置就是 pivot 的最终位置,将该处索引的值置为 pivot 即可
- 递归 left 到 i - 1 和 i + 1 到 right
// 普通的快速排序
public static void quickSort(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int i = left, j = right;
int pivot = nums[left];
while (i < j) {
while (nums[j] >= pivot && i < j) {
j--;
}
nums[i] = nums[j];
while (nums[i] <= pivot && i < j) {
i++;
}
nums[j] = nums[i];
}
nums[i] = pivot;
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}
// 随机 pivot 的快速排序
public static void quickSortRandomPivot(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int i = left, j = right;
int randomIndex = left + 1 + (int) (Math.random() * (right - left));
swap(nums, left, randomIndex);
int pivot = nums[left];
while (i < j) {
while (nums[j] >= pivot && i < j) {
j--;
}
nums[i] = nums[j];
while (nums[i] <= pivot && i < j) {
i++;
}
nums[j] = nums[i];
}
nums[i] = pivot;
quickSortRandomPivot(nums, left, i - 1);
quickSortRandomPivot(nums, i + 1, right);
}
public static void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
2.1 数组中的第K个最大元素
思路:由于再经过一次快排后 pivot 的位置就是他的最终位置,[left, pivot索引) 和 (pivot索引 , right] 区间的数也再其最终位置的区间,所以只要再次递归目标值所在的区间的数就可以了。
public class Solution {
int target;
public int findKthLargest(int[] nums, int k) {
this.target = nums.length - k;
quickSortRandomPivot(nums, 0, nums.length - 1);
return nums[nums.length - k];
}
public void quickSortRandomPivot(int[] nums, int left, int right) {
if (left >= right) {
return;
}
int i = left, j = right;
int randomIndex = left + 1 + (int) (Math.random() * (right - left));
swap(nums, left, randomIndex);
int pivot = nums[left];
while (i < j) {
while (nums[j] >= pivot && i < j) {
j--;
}
nums[i] = nums[j];
while (nums[i] <= pivot && i < j) {
i++;
}
nums[j] = nums[i];
}
nums[i] = pivot;
if (i < target) {
quickSortRandomPivot(nums, i + 1, right);
} else if (i > target) {
quickSortRandomPivot(nums, left, i - 1);
}
}
private void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}