目录
1、插入排序
插入排序思想:
插入排序的思想是将待排序的数据按照要求依次插入到一个有序序列中
插入排序步骤
以升序为例:
- 从第一个元素开始,第一个数据默认是有序的
- 获取下一个元素tmp,将已排好序的元素,从后往前依次遍历比较,直到有元素cur小于tmp或遍历完毕
- 将tmp放置cur后面,若没有元素小于tmp,将tmp放置第一个
- 重复2,3步骤,直至排序完毕
代码:
void InserSort(int* a, int n)
{
int left = 0;
int right = 0;
while (right < n - 1)
{
int end = right;
int tmp = a[end + 1];
while (end >= 0 && tmp < a[end])
{
a[end + 1] = a[end];
end--;
}
a[end+1] = tmp;
right++;
}
}
时间复杂度最慢为O(N*N),此时数据为逆序或接近逆序
时间复杂度最快为O(N),此时数据为有序或接近有序
空间复杂度为O(1)
稳定性:稳定
总结:数据越有序,插入排序越快
2、希尔排序
希尔排序又称缩小增量排序,是插入排序更高效的版本。
希尔排序思想:
借助数据越有序,插入排序速度越快的特点,将数据按一定距离分成若干份进行预排序,将数据整理成接近有序的状态
希尔排序步骤
以升序为例:
- 选取gap,一般gap=n/3+1
- 将距离为gap的所有元素进行插入排序
- gap/3+1,继续重复步骤2,直到gap=1
- gap=1时,最后一次插入排序后排序完毕
代码
void ShellSort(int* a, int n)
{
int gap = n / 3 + 1;
int left = 0;
int right = 0;
while (1)
{
while (right < n - gap)
{
int end = right;
int tmp = a[end + gap];
while (end >= 0 && tmp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
a[end + gap] = tmp;
right++;
}
right = 0;
if (gap == 1)
{
break;
}
gap = gap / 3 + 1;
}
}
希尔排序的时间复杂度很难算,时间复杂度平均:O(N^1.3)
空间复杂度:O(1)
稳定性:不稳定
3、选择排序
选择排序思想
每次从待排序元素中找到最大值或最小值放置序列起始位置,直至排序完毕
选择排序步骤
以升序为例:
- 遍历数据,找出最小值
- 将最小值和第一个待排序交换位置
- 重复以上步骤直至排序完成
代码
void SelectSort(int* a,int n)
{
int i = 0;
while (i < n-1)
{
int j = i+1;
int tmp = i;
while (j < n)
{
if (a[j] < a[tmp])
{
tmp = j;
}
j++;
}
swap(&a[tmp], &a[i]);
i++;
}
}
时间复杂度最快和最慢都O(N*N)
空间复杂度为O(1)
稳定性:不稳定,因为在第2个步骤会将打乱
4、堆排序
对堆不了解的可以先看我的往期文章
堆排序思想
在一个堆中,堆顶的元素一定是最大的或者最小的。依次取出堆顶的元素按顺序排放就排好序了
堆排序步骤
以升序为例:
- 将待排序元素建成堆
- 将堆顶的元素和堆中最后一个元素交换
- 将最后的元素从堆中剔除(不要覆盖)
- 向下调整重新建堆
- 重复2、3、4步骤,堆内没有元素后排序完毕
代码
//向下调整
void AdjustDwon(int* a, int n, int root)
{
int child = root * 2 + 1;
if (child + 1 < n && a[child + 1] > a[child])
{
child++;
}
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
child++;
}
if (a[child] > a[root])
{
swap(&a[root], &a[child]);
root = child;
child = child * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int* a, int n)
{
int parent = (n - 2) / 2;
int size = n - 1;
while (parent >= 0)
{
AdjustDwon(a, n, parent);
parent--;
}
while (size > 0)
{
swap(&a[0], &a[size]);
size--;
AdjustDwon(a, size+1, 0);
}
}
时间复杂度为O(N*)
空间复杂度:O(1)
稳定性:不稳定
注意:排升序要建大堆,排降序建小堆。
5、冒泡排序
冒泡排序思想
从头开始一次比较两个相邻的元素,如果他们的顺序错误就把他们交换过来
冒泡排序步骤
以升序为例:
- 遍历一遍元素,每次都和后一个元素比较。大的放在后一个
- 重复步骤1即可排序完成
代码
//冒泡排序
void BubbleSort(int* a, int n)
{
int i = n;
int j = 0;
while (i > 1)
{
j = 0;
while (j < i - 1)
{
if (a[j] > a[j + 1])
{
swap(&a[j], &a[j + 1]);
}
j++;
}
i--;
}
}
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定
6、快速排序
快速排序思想
选取一个key,小于key的放左边,大于key的放右边,中间的值就是key的位置。将左右两边重复该步骤直到所有key都在正确的位置上
快速排序步骤
以升序为例:
- 选取序列中第一个元素作为key值
- 从序列最后一个元素right开始和key比较,小于key时停下
- 从序列第一个元素left开始和key比较,大于key时停下
- 交换两个数字
- 重复步骤2、3、4,当letf等于right时,将key和liet交换。key的位置找到了
- 将序列以key为分割线,分成左右两个子序列
- 重复以上所有步骤,直至序列有序
代码
void QuickSort(int* a, int left, int right)
{
if (left >= right)
{
return;
}
int begin = left;
int end = right;
int key = left;
while (begin < end)
{
while (begin < end && a[end] >= a[key])
{
end--;
}
while (begin < end && a[begin] <= a[key])
{
begin++;
}
swap(&a[begin], &a[end]);
}
swap(&a[begin], &a[key]);
key = begin;
QuickSort(a, left, key - 1);
QuickSort(a, key + 1, right);
}
时间复杂度为O(N*)
空间复杂度为O(logN)
稳定性:不稳定
上面的代码是hoare版本的快速排序,除此之外还有挖坑法,双指针法。处理一些极端情况的三数取中,三路并排,将小区间优化成插入排序省去很多递归,或是用栈或队列写成非递归。这里不展开讲了,后面会专门写一篇文章。
7、归并排序
归并排序思想
归并排序的基本思想是分治,先使子序列有序,再使子序列段间有序
归并排序步骤
以升序为例:
- 将整个待排序序列划分成多个不可再分的子序列,每个子序列中仅有 1 个元素
- 将子序列以两个为一组进行合并得到一个有序序列。子序列合并的排序方法为每次比较子序列中第一个元素
- 重复步骤2后可以得到一个有序序列
代码
void _MergeSort(int* a, int* tmp, int left, int right)
{
if (left >= right)
{
return;
}
int mid = (right - left) / 2 + left;
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
_MergeSort(a, tmp, left, mid);
_MergeSort(a, tmp, mid+1, right);
int i = begin1;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[i] = a[begin1];
begin1++;
i++;
}
else
{
tmp[i] = a[begin2];
begin2++;
i++;
}
}
while (begin1 <= end1)
{
tmp[i] = a[begin1];
begin1++;
i++;
}
while (begin2 <= end2)
{
tmp[i] = a[begin2];
begin2++;
i++;
}
memcpy(a+left, tmp+left, (right - left + 1) * sizeof(int));
}
void MergeSort(int* a, int n)
{
int* tmp = (int*)malloc(sizeof(int) * n);
_MergeSort(a, tmp, 0, n - 1);
free(tmp);
tmp = NULL;
}
时间复杂度为O(N*)
空间复杂度为O(N)
稳定性:稳定
虽然归并排序是空间复杂度为O(N),但归并排序是上面唯一的一个外排序