【数据结构与算法初阶(c语言)】插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序-全梳理(万字详解,干货满满,建议三连收藏)

目录

1.排序的概念及其运用

1.1排序的概念

1.2排序运用

1.3常见的排序算法

2.插入排序 

2.1 原理演示:​编辑

2.2 算法实现 

2.3 算法的时间复杂度和空间复杂度分析 

 3.希尔排序

3.1算法思想

3.2原理演示

3.3代码实现

3.4希尔算法的时间复杂度

4.冒泡排序

4.1冒泡排序原理(图解)

4.2.冒泡排序实现

4.3冒泡排序封装为函数

4.4 冒泡排序代码效率改进。 

5.堆排序

5.1升序建大堆,降序建小堆

 5.2 建堆的时间复杂度

5.2.1 向下调整建堆

5.2.2向上调整建堆

5.3 排序实现

6.选择排序 

6.1基本思想:

6.2算法演示

6.3代码实现 

6.4时间复杂度分析

7.快速排序

7.1 hoare版本

7.1.1算法图解

7.1.2代码实现 

7.1.3 算法优化---三数取中

7.2挖坑法快排 

7.2.1算法图解

7.2.2 代码实现

7.3 前后指针法

 7.4快速排序进一步优化

7.5快速排序非递归写法

8.归并排序

9.计数排序

10.结语


深度优先遍历 dfs  : 先往深处走,走到没有了再往回,前序遍历 一般是递归

广度优先遍历  bfs  层序遍历  一般用队列 

1.排序的概念及其运用

1.1排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次 序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排 序算法是稳定的;否则称为不稳定的。
内部排序:数据元素全部放在内存中的排序。
外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2排序运用

1.3常见的排序算法

2.插入排序 

直接插入排序是一种简单的插入排序法

算法思想:

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

和我们玩扑克牌整理牌的时候是非常相像的

将我们的摸到的牌作为手里的牌的最后一张,从倒数第一张开始到第一张逐次比较,如果当前牌大于前一张就放置在当前位置,如果小于那就向前移动,最坏的情况就是每次摸到牌都要逐渐比较放到最前面。

2.1 原理演示:

2.2 算法实现 

由原理图我们知道这个算法的实现就是第二个先和1第一个比较,调整位置,第三个又和前两个进行比较调整位置。

第一次 调整前两个位置

第二次 调整前三个位置

第三次 调整前四个位置

。。。

n个元素,就调整n-1次

每一次调整,都是倒数一个与倒数第二个比较调整完,倒数第二个又和倒数第三个做比较,知道比较到倒数最后一个也就是最开始的一个。

void InserPort(int* a, int n)
{int i = 0;for (i = 0; i < n - 1; i++)//n个元素调整n-1次{int end = i;int tmp = a[end + 1];//最后一个元素值给tmpwhile (end >= 0){if (tmp < a[end]){//交换a[end + 1] = a[end];a[end] = tmp;}else{//不动break;}//继续比较调整前两个end--;}}
}

2.3 算法的时间复杂度和空间复杂度分析 

上述代码就是最坏的情况:

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

空间复杂度为O(1)

.元素集合越接近有序,直接插入排序算法的时间效率越高

 3.希尔排序

3.1算法思想

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个 组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当间距到达=1时,所有记录在统一组内排好序
通俗来讲:就是先进行与排序再直接插入排序
预排序的操作是将数据以同一个间隔的数据分为一组:使得更大的数更快的到一段,更小的数更快到一端。

3.2原理演示

3.3代码实现

ShellSort(int* a, int n)
{int i = 0;int gap = n;while (gap > 1){gap /= 2;for (i = 0; i < n - gap; i++){int end = i;int tmp = a[end + gap];while (end>gap){if (tmp < a[end]){a[end + gap] = a[end];a[end] = tmp;}else{break;}end -= gap;}}}
}

3.4希尔算法的时间复杂度

希尔排序的时间复杂度不好计算,因为 gap 的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定

《数据结构》--严蔚敏

 《数据结构-用面相对象方法与C++描述》--- 殷人昆

按照:

来计算

空间复杂度为O(1)

稳定性:不稳定

4.冒泡排序

 核心思想:两两相邻的元素相比较是一种交换排序

4.1冒泡排序原理(图解)

4.2.冒泡排序实现

通过图解原理我们可以发现规律(以10个元素排列举例)

1.每一趟比较完全完成就把这一趟数据的最大数放在最后,第一趟是10个数据中的最大值9在最后,第二趟是9个数据中的8在最后,那么我们10个元素就要循环9趟,那么n个数据,就要循环n - 1趟。

2.第一趟是10个数据,就有9对数据比较,那么第二趟,就有8对数据进行比较。如果一趟有n个数据就要比较n - 1对数据。

3.所以需要两层循环,外层控制趟数,内层控制每一趟要比较的次数。

看如下实现。

int main()
{int len[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){scanf("%d",&len[i]);}int n = sizeof(len) / sizeof(len[0]);//printf("%d", n);int x = 0;for (x = 0; x < n; x++){int y = 0;{for (y = 0; y < n-1 - x; y++){if (len[y] > len[y + 1]){int temp = 0;temp = len[y];len[y] = len[y + 1];len[y + 1] = temp;//交换}}}}int j = 0;for (j = 0; j < 10; j++){printf(" %d ", len[j]);}
}

运行结果如图,大家也可以用源码自己体验一下效果: 

 

4.3冒泡排序封装为函数

现在已经实现了冒泡排序,为了以后的方便,我们来试着将这个冒泡排序模块功能封装为一个函数。让我们一起来看看自定义为函数有那些坑。

   按照上述排序规则和封装函数的方法,我们将数组作为一个函数参数,传递给冒泡排序函数来执行冒泡排序,我们来看一下: 

void BBurd_sort(int arr[10])
{int n= sizeof(arr) / sizeof(arr[0]);int x = 0;for (x = 0; x < n; x++){int y = 0;{for (y = 0; y < n - 1 - x; y++){if (arr[y] > arr[y + 1]){int temp = 0;temp = arr[y];arr[y] = arr[y + 1];arr[y + 1] = temp;//交换}}}}}
int main()
{int arr[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){scanf("%d", &arr[i]);}//int sz = sizeof(arr) / sizeof(arr[0]);BBurd_sort(arr);int j = 0;for (j = 0; j < 10; j++){printf(" %d ", arr[j]);}return 0;
}

我们观察到没有发生排序,我们调试看一下:

问题所在:我们这里想要实现的功能是n 保存的是数组的大小,那么应该是10才对,为什么是2呢。

思考:sizeof(arr[0] 求的是一个整形数组元素的大小那么其大小就应该是4字节,

那么说明我们的sizeof(arr)就是8字节,那么只有可能我们这里的arr,并不是一整个数组,而是一个地址 (地址也就是指针,在32位操作系统下面大小是4个字节,64位操作系统下是8个字节),那就说明我们这里的arr实质上是一个指针,保存的是数组首元素的地址。

所以在函数里面是没有办法求出这个数组的大小,解决办法,把函数的大小作为一个函数的参数一起传递,看一下改进效果。

程序正常实现,附上源码。大家体验一下效果。 

void BBurd_sort(int arr[10], int n)
{int x = 0;for (x = 0; x < n; x++){int y = 0;{for (y = 0; y < n-1 - x; y++){if (arr[y] > arr[y + 1]){int temp = 0;temp = arr[y];arr[y] = arr[y + 1];arr[y + 1] = temp;//交换}}}}}
int main()
{int arr[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){scanf("%d", &arr[i]);}int sz = sizeof(arr) / sizeof(arr[0]);BBurd_sort(arr, sz);int j = 0;for (j = 0; j < 10; j++){printf(" %d ", arr[j]);}return 0;
}

4.4 冒泡排序代码效率改进。 

思考:

对于这样一个有序数列,我们如果执行的话,我们的自定义函数就还是会跑一遍,那么这样实际效率并不高。所以想能不能改进一下我我们的代码。

再次浏览原理图,我们不难发现这样一个规律,只要数据是乱序一趟下来就会有元素在发生交换。

比如:

9 1 2 3 4 5 6 7 8

这样的数据,第一趟会有元素交换,第二趟就没有。

那么我们可以这样,只要某一趟没有交换,说明此时我们的数列已经有序了就跳出

只要发生交换就继续。

那么看一下改进代码: 

void BBurd_sort(int arr[10], int n)
{int x = 0;for (x = 0; x < n; x++){int flag = 0;int y = 0;for (y = 0; y < n-1 - x; y++){if (arr[y] > arr[y + 1]){int temp = 0;temp = arr[y];arr[y] = arr[y + 1];arr[y + 1] = temp;//交换flag = 1;//发生了交换就改变flag}}//执行完一趟就判断一下有没有交换,有交换就继续,没有交换就直接退出,不用排序了if (flag == 0){break;}}}
int main()
{int arr[10] = { 0 };int i = 0;for (i = 0; i < 10; i++){scanf("%d", &arr[i]);}int sz = sizeof(arr) / sizeof(arr[0]);BBurd_sort(arr, sz);int j = 0;for (j = 0; j < 10; j++){printf(" %d ", arr[j]);}return 0;
}

5.堆排序

堆排序 (Heapsort) 是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
使用堆排序前首先要建立堆:

使用堆结构对一组数据进行排序,方便对数据进行处理。粗暴办法就是将原数组数据插入堆,再取堆数据覆盖,这种方法首先得有堆结构,其次插入数据就要额外开辟空间。

最好的方式就是直接将原数组或者原来的这组数据变成堆。将原数组直接看成一颗完全二叉树,一般都不是堆。那么就要将原数据之间调成堆----建堆

建堆不是插入数据,只是使用向上调整的思想。在原有数据上进行更改,调换。

5.1升序建大堆,降序建小堆

一般我们要利用堆结构将一组数据排成升序,就建立大堆

要利用堆结构将一组数据排成降序,就建立小堆。

void HeapSort(int* a, int n)
{//对数据进行建堆for (int i = 0; i < n; i++){AdjustUp(a, 1);}//堆排序---向下调整的思想int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);--end;//让n-1个数据调整成堆选出次小}}

 5.2 建堆的时间复杂度

5.2.1 向下调整建堆

讨论最坏的时间复杂度

将下图进行建立一个大堆

实现:

void AdjustDown(int* a, int parent, int n)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){child++;//找出左右孩子中大的一个1}if (a[child] >a[parent]){//交换Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
//堆排序//首先将数据建堆//升序建大堆,降序建小堆//采用向下调整建堆的方式时间复杂度较小int child = n - 1;int parent = (child - 1) / 2;while (parent >=0){AdjustDown(a, parent, n);parent--;}

假设有h层,这里测算的时间复杂度是最坏的情况,那么就相当于调整的是一个满二叉树的堆。

第h层  ,共有2^(h-1)个节点, 需要调整0次

第h-1层,共有2^(h-2)个节点 ,每个节点调整一次,调整:2^(h-2)*1次

第h-2层,共有2^(h-3)个节点,每个节点最坏向下调整两次,调整2^(h-3)*2次

        :

        :

        :

第3层,共有2^(2)个节点,每个节点向下调整h-3次,调整2^(2)*(h-3)次

第2层,共有2^(1)个节点,每个节点向下调整h-2次,调整2^(1)*(h-2)次

第1层,共有2^(0)个节点,每个节点向下调整h-1次,调整2^(0)*(h-1)次

时间复杂度为:

T(h) = 2^(0)*(h-1)+2^(1)*(h-2)+2^(2)*(h-3)+…………2^(h-3)*2+2^(h-2)*1①

2T(h) =  2^(1)*(h-1)+2^(2)*(h-2)+2^(3)*(h-3)+…………2^(h-2)*2+2^(h-1)*1

②-①得

T(h) = 2^(1)+2^(2)……+2^(h-2)+2^(h-1)+1-h

=2^0+2^(1)+2^(2)……+2^(h-2)+2^(h-1)-h

=2^h-1-h

由于h是树的层高,与节点个数的关系是:N = 2^h-1

h = log(n+1)

所以时间复杂度为:

O(N) = N-longN+1~O(N)

5.2.2向上调整建堆

假设有h层,这里测算的时间复杂度是最坏的情况,那么就相当于调整的是一个满二叉树的堆。

第2层,共有2^1个节点,每个节点向上调整1次

 第3层,共有2^2个节点,每个节点向上调整2次

        

        :

        :

        :

第h-2层,共有2^(h-3)个节点,每个节点向上调整h-3次

第h-1层,共有2^(h-2)个节点,每个节点向上调整h-2次

第h层  ,共有2^(h-1)个节点,每个节点向上调整h-1次

时间复杂度为:T(h) = 2^1*1+2^2*2+2^3*3……2^(h-3)*(h-3)+2^(h-2)*(h-2)+2^(h-1)*(h-1)

                       2 T(h) = 2^2*1+2^3*2+2^4*3……2^(h-2)*(h-3)+2^(h-1)*(h-2)+2^(h)*(h-1)

T(h) = -2-(2^2+2^3+2^4…………2^(h-1))+2^h(h-1)

= -(2^0+2^1+2^2…………2^(h-1))+2^h(h-1)+2^0

=2^h*(h-2)+2

由于h是树的层高,与节点个数的关系是:N = 2^h-1

h = log(n+1)

T(N) = (N+1)log(N+1)-2N-1

5.3 排序实现

排序和删除的原理是一样的,先找最大/最小然后次大/次小,每次选取数据后不会将后面数据覆盖上来,否则就会导致关系全乱,可能次大数据就要重新建堆,增加了工作量了。而是通过堆顶元素和最后一个数据交换位置过后,向下调整思想,将排除刚刚调整的尾部最大数据除外的剩下数据看成堆,循环排序。

最后发现:大堆这样处理的数据最大的数据在最后,最小的在最前,小堆相反。 

void AdjustDown(int* a, int parent, int n)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){child++;//找出左右孩子中大的一个1}if (a[child] >a[parent]){//交换Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
//堆排序void HeapSort(int* a, int n)
{//首先将数据建堆//升序建大堆,降序建小堆//采用向下调整建堆的方式时间复杂度较小int child = n - 1;int parent = (child - 1) / 2;while (parent >=0){AdjustDown(a, parent, n);parent--;}//排序int end = n - 1;while (end >= 0){Swap(&a[0], &a[end]);AdjustDown(a, 0, end);end--;}}

6.选择排序 

6.1基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。也可第一轮将最小的放在一端,将最大的放在一端,第二轮选出次大次小。

6.2算法演示

6.3代码实现 

//选择排序void SelectSort(int* a, int n)
{int begin = 0;int end = n - 1;while (begin <end){int min = begin;int max = begin;int i = 0;for (i = begin+1; i <= end; i++){if (a[min] > a[i]){min = i;}if (a[max] < a[i]){max = i;}}Swap(&a[begin], &a[min]);if (begin == max){max = min;}Swap(&a[end], &a[max]);end--;begin++;}}

6.4时间复杂度分析

. 时间复杂度: O(N^2)
如果有10个数据: 10 9 8 7 6 5 4 3 2 1
第一次:比较2(n-1次)
第二次比较:2(n-3)次
最后一次比较:1次
总共比较:n/2次,时间复杂度为:1/4n^2
3. 空间复杂度:O(1)
4. 稳定性:不稳定

7.快速排序

算法思想:

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

7.1 hoare版本

单趟:

7.1.1算法图解

 

 

为什么相遇的位置一定是比k值小的值是因为右边先走,那么每次左边停下来的值经过交换一定是比k值大,此时r继续走,找到比k值大的数才会停下来,所以如果右边先走,停下来的地方值一定小于k,如果左边先走,停下来的地方的值一定大于k。 

将上述过程递归就可以得到一个有序的序列:

 

7.1.2代码实现 

int Partsort(int* a, int left, int right)
{int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]){right--;//如果左右两边同时是key,此时如果不取等号就回陷入死循环//再次判断left<key是因为值都比k大或者都比k小的极端情况出现越界问题,因为外层循环只判断了一次,里面的--++的循环没有判断}while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);return left;
}//快速排序
void QuickSort(int* a, int left,int right)
{if (left >= right){//大于为空,等于只有一个值return;}int keyi = Partsort(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

7.1.3 算法优化---三数取中

为了保证我们快排的效率呢,我们试着调整一下我们选择key值的策略。

每次选取三个数中中间值的一个作为我们的key,并且将这个值换到数组的最左边:

 

int GetMid(int* a, int left, int right)
{int mid = (left + right) / 2;if (a[left] < a[mid]){if (a[mid] < a[right]){return mid;}else if (a[left] > a[right]){return left;}else{return right;}}else{if (a[mid > a[right]]){return mid;}else if (a[left] < a[right])//mid是最小的{return left;}else{return right;}}
}
int Partsort(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);int keyi = left;while (left < right){while (left < right && a[right] >= a[keyi]){right--;//如果左右两边同时是key,此时如果不取等号就回陷入死循环//再次判断left<key是因为值都比k大或者都比k小的极端情况出现越界问题,因为外层循环只判断了一次,里面的--++的循环没有判断}while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[left], &a[right]);}Swap(&a[left], &a[keyi]);return left;
}//快速排序
void QuickSort(int* a, int left,int right)
{if (left >= right){//大于为空,等于只有一个值return;}int keyi = Partsort(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);
}

这样我们的逆序排序这种对于快排最坏的情况就变成最好的情况,十万个数据1毫秒就可以排好。

7.2挖坑法快排 

7.2.1算法图解

7.2.2 代码实现

 


//挖坑法
int Partsort2(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);int ken = a[left];int hole = left;while (left < right){while (left < right && a[right] >= ken){right--;}a[hole] = a[right];hole = right;while (left < right && a[left] <= a[ken]){left++;}a[hole] = a[left];hole= left;}a[hole] = ken;return hole;}

7.3 前后指针法

逻辑:cur找比key值小的值,然后prev遇到比key值大的就停下来,直到cur遇到比key值小的值过后就交换。

prev:

在cur没有遇到比key值大的值的时候,prev紧紧的跟着cur

在cur遇到比key值大的值的时候,prev就停下来了,prev在比key大的一组值的前面。

//前后指针
int Partsort3(int* a, int left, int right)
{int mid = GetMid(a, left, right);Swap(&a[left], &a[mid]);int keyi = left;int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi]&&prev++ != cur)//防止开始位置自己跟自己交换{Swap(&a[prev++], &a[cur]);}cur++;}Swap(&a[prev], &a[keyi]);return prev;}

 7.4快速排序进一步优化

按照上面的方法实际主题来说,大思想就是递归,递归是有消耗的,而且,最后几层的递归往往是最大的,如果当递归区间的数值减少到一定程度,我们就不递归了,特别是到最后一两层的时候,那么排序的效率就会有提升。

//快速排序
void QuickSort(int* a, int left,int right)
{if (left >= right){//大于为空,等于只有一个值return;}if ((right - left + 1) > 10){int keyi = Partsort(a, left, right);QuickSort(a, left, keyi - 1);QuickSort(a, keyi + 1, right);}else{InserPort(a + left, right - left + 1);}
}

小区间优化,编译器对递归优化也比较好,小区间不在递归,降低递归次数

7.5快速排序非递归写法

使用栈保存每次划分的区间

void QuickSortNonR(int* a, int left, int right)
{
Stack st;
StackInit(&st);
StackPush(&st, left);
StackPush(&st, right);
while (StackEmpty(&st) != 0)
{right = StackTop(&st);StackPop(&st);left = StackTop(&st);StackPop(&st);if(right - left <= 1)continue;int div = PartSort1(a, left, right);// 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)StackPush(&st, div+1);StackPush(&st, right);StackPush(&st, left);StackPush(&st, div);
}StackDestroy(&s);
}
快速排序的特性总结:
1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定

 

8.归并排序

排序思想:

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 
public class Sort {public static void main(String[] args) {int[] a={10,4,6,3,8,2,5,7};//归并排序mergeSort(a,0,a.length-1);}private static void mergeSort(int[] a, int left, int right) {if(left<right){int middle = (left+right)/2;//对左边进行递归mergeSort(a, left, middle);//对右边进行递归mergeSort(a, middle+1, right);//合并merge(a,left,middle,right);}}private static void merge(int[] a, int left, int middle, int right) {int[] tmpArr = new int[a.length];int mid = middle+1; //右边的起始位置int tmp = left;int third = left;while(left<=middle && mid<=right){//从两个数组中选取较小的数放入中间数组if(a[left]<=a[mid]){tmpArr[third++] = a[left++];}else{tmpArr[third++] = a[mid++];}}//将剩余的部分放入中间数组while(left<=middle){tmpArr[third++] = a[left++];}while(mid<=right){tmpArr[third++] = a[mid++];}//将中间数组复制回原数组while(tmp<=right){a[tmp] = tmpArr[tmp++];}}
}

 

归并排序的特性总结:
1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(N)
4. 稳定性:稳定

9.计数排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中
算法演示:
代码:
//计数排序
void MergeSort(int* a, int n)
{int min = a[0], max = a[0];for (int i = 0; i < n; i++){if (a[i] < min){min = a[i];}if (a[i] > max){max = a[i];}}int range = max - min + 1;int* count = (int*)malloc(sizeof(int) * range);if (count == NULL){perror("malloc fail");return;}memset(count, 0, sizeof(int) * range);//统计数据出现的个数for (int i = 0; i < n; i++){count[a[i] - min]++;}//排序int j = 0;for (int i = 0; i < range; i++){while (count[i]--){a[j++] = i + min;}}
}

 

计数排序的特性总结:
1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
2. 时间复杂度:O(MAX(N,范围))
3. 空间复杂度:O(范围)
4. 稳定性:稳定

10.结语

以上就是本期所有内容,耗时半周,应该整理得比较全面,越往后面的排序代码实现就越难。知识含量蛮多,大家可以配合解释和原码运行理解。创作不易,大家如果觉得还可以的话,欢迎大家三连,有问题的地方欢迎大家指正,一起交流学习,一起成长,我是Nicn,正在c++方向前行的奋斗者,数据结构内容持续更新中,感谢大家的关注与喜欢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/785019.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

test01

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

Java的IDEA的工程管理

模块和包的图标&#xff1a; 举个例子&#xff1a; IDEA中创建包&#xff1a; 如图所示&#xff0c;com.LBJ的意思是在com包中创建子包LBJ 参见&#xff1a; IDEA中项目、模块和包的关系_idea中模块和项目-CSDN博客

网站秒收录的6个方法

网站怎么做到秒收录 网站的收录速度对于网站的曝光和流量至关重要。如果能够实现网站的秒收录&#xff0c;将大大提高网站的可见性和访问量。下面介绍几种方法&#xff0c;帮助您实现网站秒收录的目标。 1. 提交sitemap.xml文件 创建并提交sitemap.xml文件是实现网站快速收录…

Netty学习——源码篇9 Netty的Handler其他处理 备份

1 ChannelHandlerContext 每个ChannelHandler被添加到ChannelPipeline后&#xff0c;都会创建一个ChannelHandlerContext&#xff0c;并与ChannelHandler关联绑定。ChannelHandlerContext允许ChannelHandler与其他的ChannelHandler进行交互。ChannelHandlerContext不会改变添加…

U8二次开发-钉钉集成

钉钉开放平台作为企业沟通和协作的重要工具,其技术的每一次迭代都为企业带来了新的机遇和挑战。随着企业对于高效沟通和智能化管理的需求日益增长,钉钉平台的SDK更新显得尤为重要。把传统的U8与钉钉平台集成,可以有效的将业务功能和角色进行前移,打破应用系统二八原则,即8…

dailyneaty、希亦、鲸立婴儿洗衣机怎么样?三款卷王测评PK对决

曾经我还是一直选择手洗婴儿衣物&#xff0c;最终还是加入了买婴儿洗衣机的大军&#xff0c;一方面因为我懒&#xff0c;不想再继续手洗&#xff0c;另一方面是因为我看了科普才知道&#xff0c;当我们清洗衣物时&#xff0c;除了要洗掉衣物表面的污渍&#xff0c;更需要消除掉…

MATLAB——知识点备忘

最近在攻略ADC建模相关方面&#xff0c;由好多零碎的知识点&#xff0c;这里写个备忘录。 Matlab 判断一个数是否为整数 1. isinteger 函数 MATLAB中&#xff0c;可以使用 isinteger 函数来判断一个数是否为整数&#xff0c;例如&#xff1a;要判断x是否为整数可以采用以下代…

科研学习|论文解读——情感对感知偶然信息遭遇的影响研究(JASIST,2022)

原文题目 Investigating the impact of emotions on perceiving serendipitous information encountering 一、引言 serendipity一词最初是由霍勒斯沃波尔创造的&#xff0c;他将其定义为“通过意外和睿智发现你并不追求的事物”。信息研究中大多数现有的偶然性定义从几个角度看…

劳动力规划:对企业加速运营的未来展望

近年来&#xff0c;企业面临着过山车般的经济形势&#xff0c;面对消费水平的上涨、市场波动带来的担忧以及数字化的加速转型&#xff0c;许多企业虽然对未来仍秉持着谨慎乐观的态度&#xff0c;但也同时认为自身缺乏持续增长和成功转型的能力。为了让企业能够实现战略目标、应…

算法笔记之蓝桥杯pat系统备考(3)

算法笔记之蓝桥杯&pat系统备考&#xff08;2&#xff09; 多训练、多思考、多总结٩(๑•̀ω•́๑)۶ 八、深搜和广搜 8.1DFS dfs是一种枚举完所有完整路径以遍历所有情况的搜索方法&#xff0c;可以理解为每次都是一条路走到黑的犟种。 以老朋友斐波那契额数列为例&a…

Docker-compose管理工具的使用

华子目录 容器编排工具docker composecompose介绍compose使用的三个步骤docker-compose.yml文件案例compose具有管理应用程序整个生命周期的命令 docker compose安装安装条件在Linux系统上安装composedocker compose卸载 docker compose运用演示修改compose配置&#xff0c;添加…

【手册】——mq延迟队列

目录 一、背景介绍二、思路&方案三、过程1.项目为啥用延迟队列&#xff1f;2.项目为啥用三方延迟队列&#xff1f;3.项目中为啥用rabbitmq延迟队列&#xff1f;4.rabbitmq延迟队列的安装5.rabbitmq的延迟队列配置方式5.1.exchange配置5.2.queues配置5.3.exchange和queues的…

初识C++ · 入门(2)

目录 1 引用 1.1引用的概念 1.2 引用的特性 2 传值&#xff0c;传引用的效率 3 引用和指针的区别 4 内联函数 4.1 内联函数的定义 4. 2 内联函数的特性 5 关键字auto 5.1关于命名的思考 5.2 关于auto的发展 5.3 auto使用规则 6 范围for的使用 7 空指针 1 引用 …

win10如何开启麦克风权限,win10麦克风权限设置

手机下载软件后,总是会跳出各种权限需要,例如访问通讯录、读取位置信息、启动相机等等。电脑上的应用也有这些权限设置,比如说玩游戏、直播、或录制视频时,我们需要打开麦克风权限,否则无法进行交流和录音。但是,win10如何开启麦克风权限呢?针对这个问题,小编已整理了两…

《自动机理论、语言和计算导论》阅读笔记:p115-p138

《自动机理论、语言和计算导论》学习第 6 天&#xff0c;p115-p138 总结&#xff0c;总计 24 页。 一、技术总结 1.associativity and comutativity (1)commutativity(交换性): Commutativity is the property of an operator that says we can switch the order of its ope…

比KMP简单的Manacher

P3805 【模板】manacher - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) “没时间悼念KMP了&#xff0c;接下来上场的是Manacher&#xff01;” 什么是Manacher? 历史背景&#xff1a; 1975 年&#xff0c;一个叫 Manacher 的人发明了这个算法&#xff0c;所以叫Manacher 算…

财务管理系统的设计与实现|Springboot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目持续更新中..... 2024年计算机毕业论文&#xff08;设计&#xff09;学生选题参考合集推荐收藏&#xff08;包含Springboot、jsp、ssmvue等技术项目合集&#xff09; 目录 1. …

02-JDK新特性-接口新特性

接口新特性 接口组成更新概述 接口的组成 常量 public static final String ZERO "0";抽象方法 public abstract void dance();默认方法&#xff08;JAVA8新增&#xff09; public default void dance(){}静态方法&#xff08;JAVA8新增&#xff09; public stat…

leecode 331 |验证二叉树的前序序列化 | gdb 调试找bug

计算的本质是数据的计算 数据的计算需要采用格式化的存储&#xff0c; 规则的数据结果&#xff0c;可以快速的按照指定要求存储数据 这里就不得不说二叉树了&#xff0c;二叉树应用场景真的很多 本题讲的是&#xff0c;验证二叉树的前序序列化 换言之&#xff0c;不采用建立树的…

Logback日志框架常见配置

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl logback简介 Logback是一个高性能、功能强大的日志框架&#xff0c;专为Java应用程序设计。它由Log4j的创始人Ceki Glc创建&#xff0c;并被视为Log4j的继承者和改进版。Lo…