排序算法分类
比较类排序:
- 交换排序:冒泡排序,快速排序
- 插入排序:简单插入排序,希尔排序
- 选择排序:简单选择排序,堆排序
- 归并排序:二路归并排序,多路归并排序
非比较类排序:
- 计数排序
- 桶排序
- 基数排序
算法复杂度
排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 |
插入排序 | O(![]() | O(1) | 稳定 |
希尔排序 | O(![]() | O(1) | 不稳定 |
选择排序 | O(![]() | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
冒泡排序 | O(![]() | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | 不稳定 |
归并排序 | O(nlogn) | O(n) | 稳定 |
计数排序 | O(n+k) | O(n+k) | 稳定 |
桶排序 | O(n+k) | O(n+k) | 稳定 |
基数排序 | O(n*k) | O(n+k) | 稳定 |
冒泡排序
算法步骤:
- 从头开始比较相邻的元素。如果第一个元素比第二个元素大,交换他们两个的位置。
- 再依次比较后面的相邻元素,从开头第一对到结尾最后一对。完成一轮比较后,结尾最后一个元素一定是当前所有元素中的最大值。
- 从头开始重复以上步骤,因为最后一个元素位置已经固定,所以每次对比的元素都比上一次少一个,直到没有元素需要进行比较。
代码:
void BubbleSort(vector<int> &v)
{
for (int i = 0; i < v.size(); i++)
{
for (int j = 1; j < v.size() - i; j++)
{
if (v[j] < v[j - 1])
swap(v[j], v[j - 1]);
}
}
}
选择排序
算法步骤:
- 从头到尾遍历整个数组,找出最大或最小的元素,放到数组的末尾或开头。
- 遍历剩余未排序元素中,找出最大或最小的元素,放到数组末尾或开头。
- 重复以上操作,直到所有元素排序完毕。
代码:
void SelectionSort(vector<int> &v)
{
int minindex;
for (int i = 0; i < v.size(); i++)
{
minindex = i;
for (int j = i + 1; j < v.size(); j++)
{
if (v[j] < v[minindex])
minindex = j;
}
if (minindex != i)
swap(v[i], v[minindex]);
}
}
插入排序
算法步骤:
- 将待排数组中第一个元素当作有序序列,第二个到最后看作未排序序列。
- 依次扫描未排序序列,将扫描到的每个元素插入前面的有序序列中。如果待插入的元素与前面有序序列中的一个元素相等,则将其插入相等元素的后面。
代码:
void InsertSort(vector<int> &v)
{
for (int i = 1; i < v.size(); i++)
{
for (int j = i; j > 0; j--)
{
if (v[j] < v[j - 1])
swap(v[j], v[j - 1]);
}
}
}
希尔排序
希尔排序也称递减增量排序,是插入排序的一种改进版本。
希尔排序的基本思想:先将整个待排序的序列分成若干子序列分别进行插入排序,待整个序列中基本有序时,再对整体进行插入排序
算法步骤:
- 选择一个增量序列t1,t2,...,tk,其中ti>tj,tk=1。
- 按增量序列个数k,对序列进行k次排序。
- 每趟排序根据对应的增量ti,将待排序列分为若干个长度为m的子序列,分别对其进行直接插入排序,直到增量为1,完成对整个序列的排序。
代码:
增量序列t1,t2,...,tk,满足t2=t1/3。
void ShellSort(vector<int> &v)
{
int gap = 1;
while (gap < v.size())
gap = gap * 3;
while (gap > 0)
{
for (int i = 0; i < gap; i++)
{
for (int j = i + gap; j < v.size(); j += gap)
{
if (v[j] < v[j - gap])
{
int temp = v[j];
int k = j - gap;
while (k >= 0 && v[k] > temp)
{
v[k + gap] = v[k];
k -= gap;
}
v[k + gap] = temp;
}
}
}
gap /= 3;
}
}
归并排序
归并排序是采用分治法的一个典型应用。有两种实现方法:自上而下的递归和自下而上的迭代。
算法步骤:
- 申请空间,使其为大小为两个排序序列之和,用来存放合并之后的序列。
- 设定两个指针,最初位置分别指向两个已经排序序列的起始位置。
- 比较两个指针所指向的元素,选择较小的元素放入合并空间,并移动指针到下一个位置。
- 重复步骤3直到其中一个指针到达序列末尾。
- 将另一序列剩下的元素复制到合并序列末尾。
void MergeArray(vector<int> &array, int start, int end, int mid, int temp[]) {
int i = start;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= end) {
if (array[i] < array[j]) {
temp[k++] = array[i++];
}
else {
temp[k++] = array[j++];
}
}
while (i <= mid) {
temp[k++] = array[i++];
}
while (j <= end) {
temp[k++] = array[j++];
}
for (int i = 0; i < k; i++) {
array[start + i] = temp[i];
}
}
void MergeSort(vector<int> &array, int start, int end, int temp[]) {
if (start < end) {
int mid = (start + end) / 2;
MergeSort(array, start, mid, temp);
MergeSort(array, mid + 1, end, temp);
MergeArray(array, start, end, mid, temp);
}
}
void MergeSort(vector<int> &array) {
int len = array.size();
int start = 0;
int end = len - 1;
int *temp = new int[len];
MergeSort(array, start, end, temp);
}
快速排序
快速排序使用分治法策略把一个串行分为两个子串行。
算法步骤:
- 从数列中挑出一个元素,称为基准。
- 重新排序数列,所有比基准值小的元素放在基准值前面,比基准值大的元素放在基准值后面。
- 递归地把基准值元素左边和右边的子树列进行排序。
代码:
void(vector<int> &nums, int left, int right)
{
if (left >= right)
return;
int i = left;
int j = right;
int base = nums[left];
while (i < j)
{
while (nums[j] >= base && i < j)
--j;
while (nums[i] <= base && i < j)
++i;
if (i < j)
swap(nums[i], nums[j]);
}
swap(nums[i], nums[left]);
quickSort(nums, left, i - 1);
quickSort(nums, i + 1, right);
}
堆排序
算法步骤:
- 构建一个大顶堆或小顶堆。
- 将堆首与堆尾互换,将堆尾输出,若为大顶堆,则每次输出的为最大值,反之为最小值。
- 重复步骤1和2,直到堆的尺寸为1。
代码:
void HeapAdjust(vector<int> &array, int length, int k)
{
int tmp = array[k];
int i = 2 * k + 1;
while (i < length) {
if (i + 1 < length && array[i] > array[i + 1])
++i;
if (tmp < array[i])
break;
array[k] = array[i];
k = i; //继续查找
i = 2 * k + 1;
}
array[k] = tmp;
}
void HeapSort(vector<int> &array)
{
int length = array.size();
for (int i = length / 2 - 1; i >= 0; --i) {
HeapAdjust(array, length, i);
}
for (int i = length - 1; i >= 0; --i) {
swap(array[0], array[i]);
HeapAdjust(array, i, 0);
}
}
计数排序
计数排序核心在于将输入的数值转化为键存储在额外开辟的数组空间中,作为一种线性时间复杂度的排序,要求输入的数据必须是有确定范围的整数。
代码:
void CountingSort(vector<int> &array)
{
int maxval = array[0];
for (auto i : array)
{
if (maxval < i)
maxval = i;
}
vector<int> range(maxval + 1, 0);
for (auto i : array)
range[i]++;
for (int i = 1; i < range.size(); i++)
range[i] += range[i - 1];
int length = array.size();
vector<int> temp(length, 0);
for (int i = length - 1;i >= 0; --i)
{
temp[range[array[i]] - 1] = array[i];
range[array[i]]--;
}
array = temp;
}
桶排序
使用一种映射函数将N个数据分配到K个有序桶中,并对每个桶中的元素进行排序。
基数排序:
将整数按位分割成不同的数字,然后依次根据每个位数分别进行比较。比如先根据个位的顺序排好序之后再将排序后的数组按十位的顺序继续排序,直到不存在更高位。