新闻动态

行业新闻企业新闻安博电竞

[数据结构基础]排序算法第四弹 -- 归并排序和计数排序 8Wib8l7b

安博电竞

目录

一. 归并排序

1.1 归并排序的数据算法数排实现思想

1.2 归并排序的递归实现

1.2.1 归并排序递归实现的思想

1.2.2 归并排序递归实现的代码

1.3 归并排序的非递归实现

1.3.1 归并排序非递归实现的思想

1.3.2 归并排序非递归实现的代码

1.4 归并排序的时间复杂度分析

二. 计数排序

2.1 计数排序的思想

2.2 计数排序函数代码

2.3 计数排序的时间复杂度、空间复杂度及适用情况分析


一. 归并排序

1.1 归并排序的结构基础实现思想

归并排序采用分治的思想实现,对于具有n个数据的待排序数组,先将其前半部分和后半部分都排列为有序,然后将前半部分和后半部分视为不同的两个有序序列,将这两个有序序列合并,得到的新的有序序列就是原序列排序后的结果 。图1.1展示了归并排序的排序排序实现过程 。

图1.1 归并排序的第弹实现思想

由图1.1,为了保证前半部分和后半部分有序,在将数组拆分为两部分后继续拆分,直到每组数据中仅有一个数据,单个数据可视为有序序列。完成整体拆分后,即拆到每组只有一个数据,将数据合并为每组两个数据,每组的归并两个数据有序,之后继续执行合并操作,直到数组中所有数据有序。

1.2 归并排序的和计递归实现

1.2.1 归并排序递归实现的思想

1.2.2 归并排序递归实现的归并代码

void _MergeSort(int* a, int* tmp, int left, int right){//如果区间左值大于等于区间右值,停止拆分if (left >= right){return;}int mid = (left + right) / 2;_MergeSort(a, tmp, left, mid);  //左区间归并排序_MergeSort(a, tmp, mid + 1, right);  //右区间归并排序//区间[left,mid] [mid+1,right]中的数据均有序//合并两组数据成新的有序序列int begin1 = left, end1 = mid;  //左区间的起始下标和结束下标int begin2 = mid + 1, end2 = right;  //右区间的起始下标和结束下标//排升序int index = begin1;  //控制tmp的下标//先将[left,right]区间的数据按顺序排序存入tmp的[left,right],然后拷贝会原数组awhile (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}//将tmp中的数据拷贝回afor (int i = left; i <= right; ++i){a[i] = tmp[i];}}void MergeSort(int* a, int n)  //递归实现归并排序函数{assert(a);//开辟临时空间存储用于临时存储部分排序后的数据int* tmp = (int*)malloc(n * sizeof(int)); if (NULL == tmp){printf("malloc failn");exit(-1);}_MergeSort(a, tmp, 0, n - 1);free(tmp);tmp = NULL;}

1.3 归并排序的非递归实现

1.3.1 归并排序非递归实现的思想

归并排序的非递归实现思想与递归类似,都是先将待排序数组中的有数据分组,先将数组中的每个数据单独视为一组,从左侧第一组数据开始,将每两个数据合并为新的组,然后继续按组合并数据,直到完成排序 。

与递归不同的和计是,非递归引入变量gap来控制两组待合并的有序序列首元素的下标之差 。如图1.2所示,取begin1和end1分别为左侧有序序列起始下标和结束下标,取begin2和end2位右侧有序序列的数据算法数排起始下标和结束下标,程序中用循环参数i来控制左侧有序序列起始下标,因此:左侧有序序列下标范围[i, i+gap-1],即[begin1,end1],右侧有序序列下标范围是[i+gap,i+2*gap-1],即[begin2,end2]  。将左右两边的结构基础有序序列合并,使区间[begin1,end2]之间的数据有序。

更新gap的排序排序值依次为2 、4 、8 、....,重复执行上段叙述的操作,直到gap

图1.2 冒泡排序的非递归实现流程

图1.2展示的数组有n=8个数据,满足n=2^i,因此,begin1、end1、begin2、end2全部没有发生越界,但是,如果待排序数据个数不满足n=2^i,那么end1 、begin2 、end2则有可能发生越界,begin1一定不会发生越界  。越界可分三种情况进行讨论:

  1. end1、begin2、end2均越界。
  2. end1不越界,begin2和end2越界 。
  3. end1和begin2不越界,end2越界。

图1.3展示了两种越界情况,当对7个数据进行排序时,gap=1时存在end1不越界,begin2和end2越界的情况,gap=2时存在end1和end2均越界,end2越界的情况 。

图1.3 越界情况

对于越界的处理可以分为两种情况讨论:

处理完越界的情况,即可执行两有序序列的合并操作,直到排序完成。

1.3.2 归并排序非递归实现的代码

//归并排序非递归实现void MergeSortNonR(int* a, int n){assert(a);int* tmp = (int*)malloc(n * sizeof(int));  //临时存储排序好的数据if (NULL == tmp){printf("malloc failn");exit(-1);}int gap = 1; //左右两侧的有序序列下标差while (gap < n){int i = 0;  //循环参数for (i = 0; i < n; i += 2 * gap){//划分左右两侧区间的起始下标和终止下标//左侧:[begin1,end1],右侧:[begin2,end2]int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//当end1越界或begin2越界时,[begin2,end2]不存在,无需从tmp中拷贝数据//此时终止循环即可if (end1 >= n || begin2 >= n){break;}//仅有end2越界,end1和begin2不越界,[begin2,end2]存在//此时修改end2的值,使其不越界即可if (end2 >= n){end2 = n - 1;}//将[begin1,end1] [begin2,end2]两有序数据整合为一个有序序列int index = begin1;while (begin1 <= end1 && begin2 <= end2){if (a[begin2] < a[begin1]){tmp[index++] = a[begin2++];}else{tmp[index++] = a[begin1++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}//将tmp中区间[begin1,end2]之间的数据拷贝回原数组a的对应区间int j = i;for (j = i; j <= end2; ++j){a[j] = tmp[j];}}gap *= 2;  //更新gap}free(tmp);tmp = NULL;}

1.4 归并排序的时间复杂度分析

假设待排序的数据个数为N,观察图1.1可得,总共需要被拆分为logN层,拆分完成后逐层进行合并,每层合并需要遍历全部待排序数据,即每层要遍历数据N次,因此,整个合并过程要执行的操作次数可以近似认为是NlogN,拆分过程执行的操作相对于合并过程可以忽略不计 。因此,归并排序的时间复杂度为:O(NlogN) 。

二. 计数排序

2.1 计数排序的思想

计数排序采用的是映射的思想,假设一组待排序数组中数据的最小值为min,最大值为max,开辟一块计数数组空间*count,其中count的空间大小应能存储range=max-min+1个数据,count[0]的值为最小值min出现的次数,count[1]的值为次小值出现的次数,...,count[range]为最大值max出现的次数 。根据count中每个数据的值对数据进行排序 。

如图2.1所示,对数组a[] = {-2,0,0,0,2,2,3}进行排序,其中min=-2,max=3,因此count应该能容纳range = 3-(-2)+1=6个数据 。数组a中,-2对应count下标为0的位置,-2出现一次,因此count[0]=1,-1对应下标为1的位置,-1出现0次,因此count[1]=0 。count[6]={1,0,3,0,2,1}。

图2.1 计数排序中的映射关系

2.2 计数排序函数代码

//计数排序函数void CountSort(int* a, int n){assert(a);int min = a[0];int max = a[0];   //数组a中最大值和最小值//获取数组中的最大值和最小值int i = 0;for (i = 1; i < n; ++i){if (a[i] > max){max = a[i];}if (a[i] < min){min = a[i];}}int range = max - min + 1;  //数据的范围int* count = (int*)malloc(range * sizeof(int));  //计数数组if (NULL == count){perror("malloc");exit(-1);}memset(count, 0, range * sizeof(int));  //计数数组所有元素初始化为0for (i = 0; i < n; ++i){count[a[i] - min]++;}//将count中的计数情况拷贝回原数组aint index = 0;for (i = 0; i < range; ++i){while (count[i]--){a[index++] = i + min;}}free(count);count = NULL;}

2.3 计数排序的时间复杂度、空间复杂度及适用情况分析

假设要对N个数据进行排序,计数排序首先要遍历一遍待排序数据获取计数数组count,遍历待排序数据的时间复杂度为O(N),生成count后,要再遍历一遍count以获取排序后的序列,count中含有的数据个数为range=max-min+1,遍历count数组的时间复杂度为O(range),因此:计数排序的时间复杂度为O(max(N,range)),空间复杂度为O(range) 。

对于数据范围range较小,即max-min较小的一组数据,计数排序是八大排序算法中唯一能做到时间复杂度为O(N)的排序算法 。但对于range远大于N的一组数据,采用计数排序不仅效率低,且需要消耗大量的内存空间 。

综上,得出结论:

chatgpt免费软件,chatgpt api 免费接口,chatgpt 聊天机器人教程,chatgpt 指令大全,chatgpt app

[数据结构基础]排序算法第四弹 -- 归并排序和计数排序

黑帽SEO泛目录程序与ChatGPT:各自的优势与挑战

黑帽SEO泛目录程序和ChatGPT是两种在网络领域中引人注目的工具,它们旨在为用户提供更好的体验和服务。然而,这两种工具之间存在着显著的区别。本文将探讨黑帽SEO泛目录程序和ChatGPT各自的优势与挑战 。

黑帽SEO泛目录程序是一种以强制性链路注入方式,违反搜索引擎优化原则来提高网站排名的工具。尽管在一些旧版搜索引擎中可能有效,但随着搜索引擎算法的升级 ,黑帽SEO泛目录程序已经变得不再可行。一方面,这种方法可能会导致网站被降权或封禁,从而影响网站的可信度和可持续发展 。另一方面 ,黑帽SEO泛目录程序的操作需要技术专业知识,对于普通网站主来说相对困难。

相比之下 ,ChatGPT是一种利用人工智能技术的自动化对话生成工具 。它使用大规模的语料库进行训练,并能够生成与人类对话类似的回应。ChatGPT具有广泛的应用领域 ,可以用于客户服务、内容创作等。然而 ,ChatGPT的训练数据决定了其生成回答的准确性和合理性 。因此 ,在特定领域的问题上 ,ChatGPT可能会出现错误或不准确的答案 。

黑帽SEO泛目录程序和ChatGPT各有优劣 。黑帽SEO泛目录程序在现代搜索引擎环境下已经不再实用  ,而ChatGPT则为用户提供了一个自动对话生成工具,具有广阔的应用前景。然而 ,在使用ChatGPT时,我们需要理解其局限性 ,并确保其合理使用 ,以避免误导用户和滥用风险。