快速排序

快排是不稳定的排序算法, 如随机选择 pivot, partition 时相同的大小的值可能互换
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
- 从数列中挑出一个元素,称为”基准”(pivot),
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
- 递归到最底部时,数列的大小是 0 或 1,也就是已经排序好了。
这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
解法 1
#include <iostream>
#include <vector>
using namespace std;
// 传入参数区间左开右闭 [0, nums.size())
void quickSort(vector<int>& nums, int start, int end) {
// 区间为 [start, end)
// 当区间没有元素或者区间元素个数为1时, 返回
if (end - start <= 1) {
return;
}
// 随机算法只是多两行
// 0 ≤ rand()%(right - start) < end-start, 故加 1
// start <=start + rand()%(right - start) < end
int random_index = start + rand()%(end - start);
swap(nums[start], nums[random_index]);
//
int pivot = nums[start];
int low = start;
int high = end;
while (true) {
do {
low++;
// 这里需要加判断 high < end,
// 如果 nums[start] 之后所有值都小于 pivot
// 数组索引就会越界.
} while (low < end && nums[low] < pivot); // <
do {
high--;
} while (nums[high] > pivot); // >
// 由于前面两个 do-while 中条件判断使用的是 >, <,
// 所以 low 和 high 可能指向同一个值(该值等于 pivot),
// 所以条件判断符号为 >=
if (low >= high) { // >=
break;
}
swap(nums[low], nums[high]);
}
// high 就是 pivot 分区的位置.
// 由于 pivot 装在 nums[start] 中,
// 处于不大于 pivot 值的一端,
// 故交换时应该交换一个小于等于 pivot 的值过来
// nums[low] >= pivot
// nums[high] <= pivot
// 故交换 nums[high]
swap(nums[start], nums[high]);
quickSort(nums, start, high);
quickSort(nums, high+1, end);
}
void quickSort(vector<int>& nums) {
// 初始化区间左开右闭 [0, nums.size())
quickSort(nums, 0, nums.size());
}
int main() {
vector<int> nums;
srand(47);
for (int i = 0; i < 10; i++) {
int e = rand() % 20;
nums.push_back(e);
cout << e <<'\t';
}
cout << endl;
quickSort(nums);
for (int e : nums) {
cout << e <<'\t';
}
return 0;
}
解法 2
#include <iostream>
#include <vector>
using namespace std;
int partition(vector<int>& vec, int left, int right) {
// 随机算法最简单的使用方法就是随机一个位置, 然后和最后一个元素对调, 然后走正常路线
int pivot_idx = left + rand()%(right - left + 1);
swap(vec[pivot_idx], vec[right]);
// 上面两行是比正常路线多的代码
int pivot = vec[right];
int cursor = left;
for (int i = left; i < right; i++) {
if (vec[i] < pivot) {
swap(vec[i], vec[cursor++]);
}
}
swap(vec[right], vec[cursor]);
return cursor;
}
void quickSort(vector<int>& vec, int left, int right) {
if (left >= right) {
return;
}
int middle = partition(vec, left, right);
quickSort(vec, left, middle-1);
quickSort(vec, middle+1, right);
}
int main() {
vector<int> vec;
srand(47);
for (int i = 0; i < 10; i++) {
vec.push_back(rand() % 20);
}
for (int e : vec) {
cout << e <<'\t';
}
quickSort(vec, 0, vec.size()-1);
cout << endl;
for (int e : vec) {
cout << e <<'\t';
}
return 0;
}
链表的快速排序
leetcode 148.
Sort List: Sort a linked list in O(n log n) time using constant space complexity.
解法 1
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (!head) return nullptr;
int pivot = head->val;
ListNode small(0), large(0);
ListNode *pSmall = &small, *pMid = head, *pLarge = &large;
for (ListNode* cur = head->next; cur; cur = cur->next) {
if (cur->val == pivot) {
pMid->next=cur;
pMid=pMid->next;
} else if (cur->val < pivot) {
pSmall->next=cur;
pSmall=pSmall->next;
} else {
pLarge->next=cur;
pLarge=pLarge->next;
}
}
pSmall->next = nullptr;
pLarge->next = nullptr;
pMid->next = nullptr;
small.next = sortList(small.next);
pSmall = &small;
// 这是为了找前半部分的尾巴, 很关键
while (pSmall->next) {
pSmall=pSmall->next;
}
pSmall->next=head;
pMid->next=sortList(large.next);
return small.next;
}
};
解法 2
#include<iostream>
#include<vector>
using namespace std;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
struct FrontEndNode {
ListNode* front;
ListNode* end;
FrontEndNode():front(nullptr), end(nullptr) {}
FrontEndNode(ListNode* front_, ListNode* end_):front(front_), end(end_) {}
};
class Solution {
public:
ListNode* sortList(ListNode* head) {
return quick_sort(head).front;
}
FrontEndNode quick_sort(ListNode *head) {
FrontEndNode ans(head, head);
// 1. 链表为空链表, 不用处理, 返回 (nullptr, nullptr)
// 2. 链表只有一个节点, 不用处理, 返回 (font=head, end=head)
if (!head || !head->next) return ans;
ListNode* pHead = head;
ListNode small(0), middle(0), large(0);
ListNode* pSmall = &small;
ListNode* pMiddle = &middle;
ListNode* pLarge = &large;
int pivot = head->val;
while (pHead) {
if (pHead->val < pivot) {
pSmall->next = pHead;
pSmall = pSmall->next;
} else if (pHead->val > pivot) {
pLarge->next = pHead;
pLarge = pLarge->next;
} else {
pMiddle->next = pHead;
pMiddle = pMiddle->next;
}
pHead = pHead->next;
}
pSmall->next = nullptr;
pMiddle->next = nullptr;
pLarge->next = nullptr;
FrontEndNode low = quick_sort(small.next);
FrontEndNode high = quick_sort(large.next);
// 假设前半段和后半段都为空
ans.front = middle.next;
ans.end = pMiddle;
// 拼接前半段和中间段
if (low.front) {
ans.front = low.front;
low.end->next = middle.next;
}
// 拼接中间段和后半段
if (high.front) {
pMiddle->next = high.front;
ans.end = high.end;
}
return ans;
}
};
归并排序

算法思路:
- 把 n 个记录看成 n 个长度为 1 的有序子表
- 进行两两归并使记录关键字有序,得到 n/2 个长度为 2 的有序子表
- 重复第 2 步直到所有记录归并成一个长度为 n 的有序表为止。
归并: 将两个已排序文档合并成一个更大的已排序文件的过程;
归并排序的性质:
- 归并排序对输入初始次序不敏感, 时间复杂度是 O(nlgn);
- 归并排序是稳定的排序算法;
- 归并排序不是原址排序(数组归并排序需要辅助数组, 空间复杂度 O(n), 链表的归并排序空间复杂度是 O(1));
- 归并排序适用于链表排序(leetcode 148);


解法 1
#include <iostream>
#include <vector>
using namespace std;
void merge(vector<int>& nums, int start, int middle, int end) {
int i = start;
int j = middle;
int k = 0;
vector<int> temp(end-start);
while (i < middle && j < end) {
if (nums[i] < nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
int m = end - 1;
int n = middle - 1;
while (i++ < middle) {
nums[m--] = nums[n--];
}
for (int l = 0; l < k; l++) {
nums[start+l] = temp[l];
}
}
void merge_sort(vector<int>& nums, int start, int end) {
if (end-start <= 1) {
return;
}
int middle = (start+end) >> 1;
merge_sort(nums, start, middle);
merge_sort(nums, middle, end);
merge(nums, start, middle, end);
}
void merge_sort(vector<int>& nums) {
merge_sort(nums, 0, nums.size());
}
int main() {
int N;
while (cin >> N) {
vector<int> nums(N);
for (int i = 0; i < N; i++) {
nums[i] = rand()%50;
}
merge_sort(nums);
bool is_sorted = true;
int pre = INT_MIN;
for (auto&e : nums) {
cout << e << " ";
if (e < pre) is_sorted = false;
pre = e;
}
cout << endl << "sorted =>" << boolalpha<< is_sorted << endl;
}
}
链表的归并排序
leetcode 148. Sort List: Sort a linked list in O(n log n) time using constant space complexity.
Example 1:
Input: 4->2->1->3
Output: 1->2->3->4
Example 2:
Input: -1->5->3->4->0
Output: -1->0->3->4->5
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* slow = head;
ListNode* fast = head->next; // slow 输在起跑线上!
while (fast != nullptr && fast->next != nullptr) {
// 当链表长度为偶数时, slow 走一半,
// 当链表长度是奇数时, slow 位置为链表中点的前一个位置
slow = slow->next;
fast = fast->next->next;
}
ListNode* lhs = head;
ListNode* rhs = slow->next;
// 这个很重要
slow->next = nullptr;
// 这个很重要
lhs = sortList(lhs);
rhs = sortList(rhs);
return merge(lhs, rhs);
}
ListNode* merge(ListNode* l1, ListNode* l2) {
ListNode head(-1);
ListNode* p = &head;
while (l1 && l2) {
if (l1->val < l2->val) {
p->next = l1;
l1 = l1->next;
} else {
p->next = l2;
l2 = l2->next;
}
p = p->next;
}
if (l1) p->next = l1;
if (l2) p->next = l2;
return head.next;
}
};