本人的数学功底不是很好,如果有说得不好的地方请大佬指正。
这里需要已经对这些排序算法理解了,才能听得懂我讲得复杂度问题.我不会再介绍他的具体动作.
时间复杂度:
最内层循环的语句执行的次数。
冒泡排序:
- 最优状态:
已经是我们所需的序列,外层for循环 第一轮,利用flag,因为已经是目标序列,所以第一轮内层for循环执行了一轮后,无任何元素交换,因此通过flag直接中断,跳出最外层循环,此时我们只执行了n-1次内层的for循环。
结论:最优状态时冒泡排序的时间复杂度为O(n)。
如果我们没有利用flag,那么最优状态会退化成最差状态 O(n^2),使用了flag可以优化冒泡排序。
- 最坏状态:
逆序状态:每次进行比较都要进行交换,因此里面的循环必须走完,flag永远无法满足,所以外层循环也是走完n-1次,因此受到外层循环的影响,内层循环次是一个以a1=1,d=1,an=n-1的等差数列和n*(n+1)/2,此时我们就得出n*(n+1)/2是内层循环的次数,所以内层循环里的语句需要执行n*(n+1)/2次数,然后我们取数量级 O(n^2);
//冒泡排序
void bubble_sort(int length) {
for (int i = 1; i < length; i++)
{
bool flag = 1;
for (int j = 1; j <= length - i; j++)
{
if (a[j] < a[j + 1])
{
swap(a[j], a[j + 1]);
flag = 0;
}
}
if (flag) break;
}
}
简单选择排序
每次从剩余的集合中选一个最大的,也就是说每次选择要遍历一遍剩余的集合.
所以遍历的次数是一个以a1=1,d=1,an=n-1的等差数列和n*(n+1)/2,此时我们就得出n*(n+1)/2是内层循环的次数,所以内层循环里的语句需要执行n*(n+1)/2次数,然后我们取数量级 O(n^2),这一点与冒泡排序的最差情况的计算方法类似.
简单选择排序无最优最差,或者说两个都是一样的,那么平均时间复杂度也是一样的.
//选择排序
void select_sort(int length) {
for (int i = 1; i < length; i++) {
a[0] = a[i];
int minarr = i;
for (int j = i; j <= length; j++)
{
if (a[j] < a[0])
{
minarr = j;
a[0] = a[j];
}
}
swap(a[i], a[minarr]);
}
}
直接插入排序
最优状态:
已经是我们所需的序列,那么我们从2-n,每一次进入第一层循环我们都不会满足内层循环的条件,因为假设我们已经是1 2 3 4 5
我们找到2这个元素准备往左边的有序序列中插入,因为已经按照我们的序列拍好了,所以我们不需要移动位置.在外层的最后一句中插入.因此我们只用了外层循环的n-2次遍历了一遍,所以我们的时间复杂度是O(n).
最坏情况:
逆序.每次都要移动到有序的集合中的最前面,也就是每次都遍历了一遍有序集合.外层循环的i影响了内层循环的次数,使得内层循环的次数也成了一个等差数列,可以看成以a1=1,d=1,an=n-1的等差数列和n*(n+1)/2,此时我们就得出n*(n+1)/2是内层循环的次数,所以内层循环里的语句需要执行n*(n+1)/2次数,然后我们取数量级 O(n^2),这一点与冒泡排序的最差情况的计算方法类似.
void my_inset_sort(int *a, int n) {
int j;
for (int i = 2; i <=n; i++) {
a[0] = a[i];
for (j = i; a[j-1] <a[0];j--) {
a[j] = a[j - 1];
}
a[j] = a[0];
}
}
快速排序
最坏情况:
退化成冒泡的最坏情况O(n^2).
这里默认我们的pivot是从最左边的元素开始获取,我们每次的pivot定位后都是在最右边,这样的话我们的区间长度每次-1,所以也是一个以a1=1,d=1,an=n-1的等差数列和n*(n+1)/2,此时我们就得出n*(n+1)/2是内层循环的次数,所以内层循环里的语句需要执行n*(n+1)/2次数,然后我们取数量级 O(n^2).
最优情况:
每次pivot结束后都能正好在中间,左右两区间的大小相等,那么我们就以2的倍数减少我们的分的区间次数。2^k=n , k=log2底n 也就是log2底n次分区间,每次分区间我们还要相当于对整个区间的长度n遍历一遍,两者的积即为所求,
nlog2底n
//pivot
int portion(int l, int h) {
a[0] = a[l];
while (l < h)
{
while (l < h && a[h] >= a[0]) h--;
a[l] = a[h];
while (l < h && a[l] <= a[0]) l++;
a[h] = a[l];
}
a[h] = a[0];
return h;
}
//快排
void quick_sort(int l, int length) {
if (l < length) {
int pivot = portion(l, length);
quick_sort(l, pivot - 1);
quick_sort(pivot + 1, length);
}
}
归并排序
归并排序与快速排序的时间复杂度的计算方法差不多。
归并排序是先分区间后操作,因此不会受到类似于快排的pivot的影响.
直接以2倍的速度分区间,也就是形成一个2^k=n , k=log2底n 也就是log2底n次分区间,每次分区间我们还要相当于对整个区间的长度n遍历一遍,两者的积即为所求,
nlog2底n
//归并操作
void my_merge(int l, int h, int length) {
int* b = (int*)malloc(sizeof(int) * length);
for (int i = 1; i <= length; i++)
{
b[i] = a[i];
}
int m = (l + h) / 2;
int i, j, k;
for (i = l, j = m + 1, k = l; i <= m && j <= h; k++)
{
if (b[i] < b[j]) {
a[k] = b[i++];
}
else {
a[k] = b[j++];
}
}
while (i <= m)
{
a[k++] = b[i++];
}
while (j <= h)
{
a[k] = b[j++];
}
}
//归并排序
void merge_sort(int l, int h, int length) {
if (l < h) {
int m = (l + h) / 2;
merge_sort(l, m, length);
merge_sort(m + 1, h, length);
my_merge(l, h, length);
}
}
堆排序
建堆过程是O(n),这里知道就好
循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 log2底n
(n-1)*log2底n -> nlog2底n
因为O(nlog2底n) >O(n)
我们取最大的O(nlog2底n)
//调整堆
void adjust_heap(int key, int length) {
a[0] = a[key];
for (int i = key * 2; i <= length; i *= 2)
{
if (i < length && a[i] < a[i + 1])
{
i++;
}
if (a[0] >= a[i])
{
break;
}
else {
a[key] = a[i];
key = i;
}
}
a[key] = a[0];
}
//建堆
void build_heap(int length) {
for (int i = length / 2; i > 0; i--)
{
adjust_heap(i, length);
}
}
//堆排序
void heap_sort(int length) {
build_heap(length);
for (int i = length; i > 0; i--)
{
swap(a[1], a[i]);
adjust_heap(1, i - 1);
}
}
求点赞👍👍👍
原创不易,点赞容易。
您的鼓励就是我的最大动力!!!。
本篇博客到此结束,谢谢大家观看。