提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
面试中常见的三种排序算法,通俗易懂。
快速排序主要步骤如下:
(1)首先选定一个基准值pivot,然后将数组元素逐个与pivot比较,从而将数组分成左右两部分。
(2)一次比较过后将大于或等于pivot的数据集中到数组右边,小于pivot的数据集中到数组的左边。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个pivot,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,对左右两边元素进行递归排序,当左右两边递归完成后就完成了对整个数组的排序。
(5)快速排序优化:快速排序最坏时间复杂度为O(Nlog(N)),即当数组元素基本有序时快速排序会退化成简单插入排序。为了避免这种情况可以对快速排序进行优化(参考中国大学MOOC浙江大学陈越),通过median3
函数,每次排序前将pivot位置上的元素置为三个数的中位数。
void median3(vector<int>& arr, int left, int right) {
/*将pivot位置上元素置为三者中的中位数*/
int mid = (left + right) / 2;
if (arr[left] > arr[mid]) swap(arr[left], arr[mid]);
if (arr[right] < arr[mid]) swap(arr[right], arr[mid]);
if (arr[left] < arr[mid]) swap(arr[left], arr[mid]);
}
void quickSort(vector<int>& arr, int left, int right) {
if (left >= right) return;
median3(arr, left, right);
int i = left + 1; int j = right;
while (true) {
while (i <= j && arr[i] <= arr[left]) i++;
while (i <= j && arr[j] >= arr[left]) j--;
if (i >= j) break;
swap(arr[i], arr[j]);
}
swap(arr[left], arr[j]);
quickSort(arr, left, j - 1);
quickSort(arr, j + 1, right);
}
void Qsort(vector<int>& arr){ /*统一接口*/
quickSort(arr, 0, arr.size() - 1);
}
堆排序主要流程如下(以最大堆为例)
(1)最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
(2)创建最大堆(Build Max Heap):将堆中的所有数据重新排序
(3)堆排序(HeapSort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
void heapBuild(vector<int>& nums, int parent, int end_Index) {/*调整最大堆*/
int left_c = 2 * parent + 1; int right_c = 2 * parent + 2; //左右孩子节点坐标
int max_c = parent; //用于记录左右孩子中较大的坐标
if (left_c <= end_Index && nums[left_c] > nums[max_c]) max_c = left_c;
if (right_c <= end_Index && nums[right_c] > nums[max_c]) max_c = right_c;
if (max_c != parent) { //如果父节点比孩子节点小,则调整使父子节点,其满足最大堆
swap(nums[parent], nums[max_c]);
heapBuild(nums, max_c, end_Index); //循环调整
}
}
void heapSort(vector<int>& nums) {/*堆排序*/
if (nums.size() == 1 || nums.empty()) return; //特例
int end = nums.size() - 1; int parent = (end - 1) / 2;
for (int i = parent; i >= 0; i--) { // 从最后一个父节点开始建立最大堆
heapBuild(nums, i, end);
}
for (int ind = end; ind >= 0; ind--) { // 每次将最大值元素移动到数组末尾
swap(nums[ind], nums[0]);
heapBuild(nums, 0, ind - 1); // 每次移动以后都要重新维护最大堆
}
归并排序的主要思想是分治法。主要过程是:
(1) 将n个元素从中间切开,分成两部分。(左边可能比右边多1个数) 将步骤1分成的两部分
(2) 再分别进行递归分解。直到所有部分的元素个数都为1。
(3)从最底层开始逐步合并两个排好序的数列(合并两个有序数组)。
void MergeSort_recursion(vector<int>& input_vec, int start, int end) { /*递归*/
if (start >= end) return;
int mid = (start + end) >> 1;
MergeSort_recursion(input_vec, start, mid);
MergeSort_recursion(input_vec, mid + 1, end);
int start1 = start, end1 = mid, start2 = mid + 1, end2 = end;
vector<int> temp;
while (start1 <= mid && start2 <= end)
temp.push_back(input_vec[start1] < input_vec[start2] ? input_vec[start1++] : input_vec[start2++]);
while (start1 <= mid)
temp.push_back(input_vec[start1++]);
while (start2 <= end)
temp.push_back(input_vec[start2++]);
for (int k = 0; k < temp.size(); k++)
input_vec[start + k] = temp[k];
}
void MergeSort_iterate(vector<int>& input_vec) { /*迭代*/
for (int step = 1; step <= input_vec.size(); step *= 2) {
int len = input_vec.size() % (2 * step) == 0 ? input_vec.size() : input_vec.size() + 2 * step;
for (int j = 0; j < len; j += 2 * step) {
vector<int> temp;
int start1 = j, start2 = j + step;
while (start1 < j + step && start1 < input_vec.size() &&
start2 < j + step * 2 && start2 < input_vec.size())
temp.push_back(input_vec[start1] < input_vec[start2] ? input_vec[start1++]
: input_vec[start2++]);
while (start1 < j + step && start1 < input_vec.size())
temp.push_back(input_vec[start1++]);
while (start2 < j + step * 2 && start2 < input_vec.size())
temp.push_back(input_vec[start2++]);
for (int k = 0; k < temp.size(); k++)
input_vec[j + k] = temp[k];
}
}
}