欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 旅游 > 排序(1)

排序(1)

2024/10/22 22:37:36 来源:https://blog.csdn.net/2302_82004664/article/details/143167771  浏览:    关键词:排序(1)

在这里插入图片描述

文章目录

  • 1.排序的概念
  • 2. 插入排序
    • 2.1 基本思想
    • 2.2 插入排序的实现
    • 2.3 优缺点分析
  • 3. 希尔排序
    • 3.1 基本思想和排序的实现
    • 3.2 gap的值
    • 3.3 优缺点分析

1.排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不断地在内外存之间移动数据的排序。

常用的排序如下:

image-20241022154446529

2. 插入排序

2.1 基本思想

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列 。

先来看一下图解:

插入排序

其实就是将选中的数,将其插入到其中,让其前面有序

和之前冒泡排序的区别:

冒泡

2.2 插入排序的实现

单趟排序实现:

int end;
int tmp = a[end + 1];
while (end >= 0)
{if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}
}
a[end + 1] = tmp;

再将趟数补全,注意不要访问越界

void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++) //为了放置后面end+1访问越界{int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}else{break;}}a[end + 1] = tmp;}
}

image-20241022161931087

2.3 优缺点分析

时间复杂度为:O(N^2)

逆序的时候时间最长

顺序的时候为时间最短 此时复杂度为O(N)

这里我们写一段测试时间的代码:

void TestOP()
{srand(time(0));const int N = 1000000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);int* a3 = (int*)malloc(sizeof(int) * N);int* a4 = (int*)malloc(sizeof(int) * N);int* a5 = (int*)malloc(sizeof(int) * N);int* a6 = (int*)malloc(sizeof(int) * N);int* a7 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand() + i;a2[i] = a1[i];a3[i] = a1[i];a4[i] = a1[i];a5[i] = a1[i];a6[i] = a1[i];a7[i] = a1[i];}int begin1 = clock();//InsertSort(a1, N);int end1 = clock();int begin2 = clock();ShellSort(a2, N);int end2 = clock();int begin3 = clock();//SelectSort(a3, N);int end3 = clock();int begin4 = clock();//HeapSort(a4, N);int end4 = clock();int begin5 = clock();//QuickSort(a5, 0, N - 1);int end5 = clock();int begin6 = clock();//MergeSort(a6, N);int end6 = clock();int begin7 = clock();//BubbleSort(a7, N);int end7 = clock();printf("InsertSort:%d\n", end1 - begin1);printf("ShellSort:%d\n", end2 - begin2);printf("SelectSort:%d\n", end3 - begin3);printf("HeapSort:%d\n", end4 - begin4);printf("QuickSort:%d\n", end5 - begin5);printf("MergeSort:%d\n", end6 - begin6);printf("BubbleSort:%d\n", end7 - begin7);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);free(a7);
}

再将冒泡排序和堆排序的代码拿过来进行对比

//堆排序
void AdjustDown(int* a, int n, int parent)
{// 先假设左孩子小int child = parent * 2 + 1;while (child < n)  // child >= n说明孩子不存在,调整到叶子了{// 找出小的那个孩子if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{// 向下调整建堆 O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}// O(N*logN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;}
}void BubbleSort(int* a, int n)
{for (int j = 0; j < n; j++){// 单趟int flag = 0;for (int i = 1; i < n - j; i++){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);flag = 1;}}if (flag == 0){break;}}
}

image-20241022170300798

我们进行对比这三个排序发现:

相比于堆排序,插入排序和堆排序还是相对来说比较慢的,冒泡排序耗时就比较长了

3. 希尔排序

3.1 基本思想和排序的实现

插入排序在逆序的时候效率就下来了,那如何能解决这个问题呢:

  1. 预排序(让数组接近有序)
  2. 插入排序

预排序就是将数组分成多组,然后将组内优先排完

image-20241022175840967

我们先将红色的组实现:

  1. 先写交换逻辑
int gap = 3;//间隔3个数为一组int end = 0;
int tmp = a[end + gap];//这一组下一个数据的位置
while (end >= 0)
{if (tmp < a[end]){a[end + gap] = a[end];end = end - gap;}else{break;}
}
a[end + gap] = tmp;
  1. 再将红色的逻辑补全
int gap = 3;//间隔3个数为一组for (int i = 0; i < n; i += gap)
{int end = i;int tmp = a[end + gap];//这一组下一个数据的位置while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end = end - gap;}else{break;}}a[end + gap] = tmp;
}

但是这里会存在越界,当i在n到n-2的位置时,对tmp进行赋值的时候会+3,导致数组访问越界

改成n - gap就行了

int gap = 3;//间隔3个数为一组for (int i = 0; i < n - gap; i += gap)
{int end = i;int tmp = a[end + gap];//这一组下一个数据的位置while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end = end - gap;}else{break;}}a[end + gap] = tmp;
}

其实这里也能发现:如果gap为1的时候就是我们之前写的插入排序

那么怎么排三组呢?

其实再套个循环就可以解决了:

for (int i = 0; i < gap; i++)
{for (int i = 0; i < n - gap; i += gap){int end = i;int tmp = a[end + gap];//这一组下一个数据的位置while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end = end - gap;}else{break;}}a[end + gap] = tmp;}
}

当然也可以多组并着走,进而减少一个循环(但是效率上并没有任何区别):

int gap = 3;
for (int i = 0; i < n - gap; i++)
{int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = tmp;
}

3.2 gap的值

  • gap的值越大,可以越快跳到后面,小的数可以越快调到前面,越不接近有序
  • gap的值越小,跳的越慢,越接近有序,当gap==1时相当于排序就有序了

那gap究竟该给多少呢?

一般来说让gap走多组预排序:

int gap = n;
while (gap > 1)
{gap = gap / 3 + 1;for (int i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end >= 0){if (tmp < a[end]){a[end + gap] = a[end];end -= gap;}elsebreak;}a[end + gap] = tmp;}
}

gap = gap / 3 + 1

这样保证最后一次时gap的值直接就是1,就直接有序了

image-20241022193109169

3.3 优缺点分析

我们先进行速度的比较,可以发现

image-20241022193323176

希尔排序的时间复杂度接近于 O(N^1.3)

这个时间复杂度比较难计算,感兴趣的可以看一下下面这篇文章:

希尔排序复杂度详细分析(翻译来源:Hans Werner Lang教授)_希尔排序时间复杂度-CSDN博客

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com