面试常用到的排序算法
一、冒泡排序
1.思路:通过对待排序序列从前向后(从下标较小的元素开始),依次对相邻两个元素的值进行两两比较,若发现前一个数大于后一个数则交换,使值较大的元素逐渐从前移向后部,就如果水底下的气泡一样逐渐向上冒。
2.具体过程注意事项
(1)总计需要进行(n-1)轮排序,也就是(n-1)次大循环
(2)每轮排序比较的次数逐轮减少
(3)如果发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序
(4)时间复杂度是O(N^2) 在有序的时候,很快,因为有exchange变量优化了代码,在乱序的时候很慢很慢。
3.代码实现
#include <iostream>
#include <vector>
using namespace std;
void Swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void BubbleSort(vector<int>& nums)
{
int end = nums.size() - 1;
while (end)
{
int exchange = 0;
for (int i = 0; i < end; i++)
{
if (nums[i] > nums[i + 1])
{
swap(nums[i], nums[i + 1]);
exchange++;
}
}
end--;
if (exchange == 0) break;
}
}
二、插入排序
1.思路:在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。
但我们并不能确定待排元素中究竟哪一部分是有序的,所以我们一开始只能认为第一个元素是有序的,依次将其后面的元素插入到这个有序序列中来,直到整个序列有序为止。
2.具体过程注意事项
(1)需要两层循环,第一层循环判断是否前i个元素有序,第二层将非有序元素修正成有序的;
(2)最后找到正确位置 j + 1 之后需要将nums[i]放在这个位置;
3.代码实现
#include <iostream>
#include <vector>
using namespace std;
void InsertSort(vector<int>& nums)
{
int n = nums.size();
// 外层循环查看是否有序
for (int i = 1; i < n; i++)
{
if (nums[i] < nums[i - 1])
{
int temp = nums[i];
// 内层循环找到该元素需要放到什么位置
int j = 0;
for (j = i - 1; j >= 0 && nums[j] > temp; j--)
{
nums[j + 1] = nums[j];
}
// j + 1的位置是在i应该呆的位置
nums[j + 1] = temp;
}
}
}
三、希尔排序
1.思路:插入排序的优化版,相当于把直接插入排序中的1换成gap而已;先选定一个小于N的整数gap作为第一增量,然后将所有距离为gap的元素分在同一组,并对每一组的元素进行直接插入排序。然后再gap–,重复上述操作。
2.当gap==1时就是直接插入排序,就相当于整个序列被分到一组,进行一次直接插入排序,排序完成。
3.代码实现
#include <iostream>
#include <vector>
using namespace std;
void Swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void ShellSort(vector<int>& nums)
{
int n = nums.size();
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = gap; i < n; i++)
{
if (nums[i] < nums[i - gap])
{
int tmp = nums[i];
int j = 0;
for (j = i - gap; j >= 0 && tmp < nums[j]; j -= gap)
{
swap(nums[j + gap], nums[j]);
}
nums[j + gap] = tmp;
}
}
}
}
四、选择排序
1.思路:在待排序的元素中,找到未排序的元素中最小的那个放到已经排好序的数组后,即第i次循环就放到nums[i];
2.具体过程注意事项
(1)需要两层循环,第一层循环表示经过的循环数,第二层循环找到未排序数组中最小的数;
3.代码实现
#include <iostream>
#include <vector>
using namespace std;
void Swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void SelectSort(vector<int>& nums)
{
int n = nums.size();
for (int i = 0; i < n - 1; i++)
{
int min = i;
for (int j = i + 1; j < n; j++)
{
if (nums[min] > nums[j]) min = j;
}
swap(nums[i], nums[min]);
}
}
五、快速排序
1.思路:快排是分治算法的一种应用,分治算法都有三步:①分成子问题;②递归处理子问题;③子问题合并;
通过一趟排序将待记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个有序的目的。
2.代码实现
#include <iostream>
#include <vector>
using namespace std;
void Swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void QuickSort(vector<int>& nums, int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1;
int x = nums[l + r >> 1];
while (i < j)
{
do i++; while (nums[i] < x);
do j--; while (nums[j] > x);
if (i < j) swap(nums[i], nums[j]);
}
QuickSort(nums, l, j);
QuickSort(nums, j + 1, r);
}
六、归并排序
1.思路:
①有数组 q, 左端点 l, 右端点 r;
②确定划分边界 mid;
③递归处理子问题 q[l…mid], q[mid+1…r]
④合并子问题:
<1>主体合并:至少有一个小数组添加到 tmp 数组中;
<2>收尾:可能存在的剩下的一个小数组的尾部直接添加到 tmp 数组中;
<3>复制回来:tmp 数组覆盖原数组;
2.代码实现
#include <iostream>
#include <vector>
using namespace std;
void MergeSort(vector<int>& nums, int l, int r)
{
if (l >= r) return;
int mid = l + r + 1 >> 1;
MergeSort(nums, l, mid - 1);
MergeSort(nums, mid, r);
int k = 0, i = l, j = mid;
vector<int> tmp(r - l + 1, 0);
while (i < mid && j <= r)
{
if (nums[i] < nums[j]) tmp[k++] = nums[i++];
else tmp[k++] = nums[j++];
}
while(i < mid) tmp[k++] = nums[i++];
while(j <= r) tmp[k++] = nums[j++];
for (int k = 0, i = l; i <= r; i++, k++) nums[i] = tmp[k];
}
七、堆排序
1.什么是堆?
大堆:父亲大于儿子 小堆:父亲小于儿子(父亲,儿子是二叉树的概念)
堆的物理结构:数组 逻辑结构:完全二叉树;
2.思路:
①把无序数组构建成二叉堆;
②循环删除堆顶元素,移到集合尾部,调节堆产生新的堆顶;
3.堆排序是不稳定的排序,空间复杂度为O(1),平均的时间复杂度为O(nlogn),最坏情况下也稳定在O(nlogn)
4.代码实现
#include <iostream>
#include <vector>
using namespace std;
// 向下调整, 构建大根堆
void HeapJust(vector<int>& nums, int start, int end)
{
int temp = nums[start];
for (int i = start * 2 + 1; i <= end; i = i * 2 + 1)
{
// 找到左右节点中最大的节点
if (i < end && nums[i] < nums[i + 1]) i++;
// start用于记录当前左右节点的根节点
if (temp < nums[i])
{
nums[start] = nums[i];
start = i;
}
else break;
}
nums[start] = temp;
}
void HeapSort(vector<int>& nums, int len)
{
// 从堆的最后一个非叶子节点到根节点,开始构造大根堆
for (int i = len / 2 - 1; i >= 0; i--) HeapJust(nums, i, len - 1);
// 开始排序,每次将根节点与未排序的最后一个节点交换
int temp = 0;
for (int i = 0; i < len - 1; i++)
{
temp = nums[0];
nums[0] = nums[len - 1 - i];
nums[len - 1 - i] = temp;
HeapJust(nums, 0, len - 1 - i - 1);
}
}
八、基数排序
1.思路:是将整数按位数切割成不同的数字,然后按每个位数分别比较;

总结
以上我对常见的几种排序做了一个总结,并且把代码全部手动实现了一边,没有放main函数,方便之后每次面试之前复习;

1610

被折叠的 条评论
为什么被折叠?



