当前位置: 首页 > news >正文

东莞便宜做网站中国企业网址大全

东莞便宜做网站,中国企业网址大全,计算机网络规划与设计,河南省建设厅一.排序的概念和其应用 1.1排序的概念 排序#xff1a;排列或排序是将一组数据按照一定的规则或顺序重新组织的过程#xff0c;数据既可以被组织成递增顺序#xff08;升序#xff09;#xff0c;或者递减顺序#xff08;降序#xff09;。稳定性#xff1a;假定在待…一.排序的概念和其应用 1.1排序的概念 排序排列或排序是将一组数据按照一定的规则或顺序重新组织的过程数据既可以被组织成递增顺序升序或者递减顺序降序。稳定性假定在待排序的数据序列中存在多个相同的数据若经过排序这些数据的相对次序保持不变即在原序列中r[i]r[j]且r[i]在r[j]之前而在排序后的序列中r[i]仍在r[j]之前则称这种排 序算法是稳定的否则称为不稳定的。内部排序待排序的数据全都存放在内存中。外部排序外排的数据太多内存无法同时存储这么多的数据需要不停从外存中提取数据到内存中进行排序。 1.2排序的应用 排序与我们的生活息息相关当我们想买一部性价比较高的手机时我们打开购物网站可以根据综合排序来找到最近热门的手机型号。 当然我们还可以根据销量、评论数等等来进行选择这里面就是排序的过程。该网站获取所有手机的销量然后排序再展示给使用者。这就是排序在生活中的作用。 还比如我们到了一个地方旅游不知道吃什么我们就可以根据大众点评上面的餐饮排序来选择。  所以排序是我们日常生活中不可或缺的。而在数据结构与算法中排序大致分为插入排序、选择排序、交换排序和归并排序。而这几种下面又具体分化出不同的排序方法。  下面我们来了解一下这几种常见的排序方法。 二.冒泡排序 冒泡排序是我们刚开始借助C语言所学的一种排序方式其排序的基本思路是遍历待排序的数据两两相邻的数据之间进行比较与要求的排列顺序进行对比如果顺序不正确就要进行交换。直到将所有的数据都交换至有序为止。动图如下 假设我们要排升序那我们就从头开始进行比较如果前一个大于后一个就交换这样就可以将该数据中最大的一个数据放到最后一个位置这是一次排序然后我们要从头开始继续比较将次大的数放到倒数第二个的位置上。重复该操作我们就可以将该数据变为有序。 //交换 void Swap(int* a, int* b) {int tmp *a;*a *b;*b tmp; }//冒泡排序 void BubbleSort(int* a, int n) {//打印为排序的数据Print(a, n);int j 0;for (j 0; j n; j){//一趟交换int i 0;int flag 0;//标记判断一趟中是否发生交换for (i 1; i n - j; i){if (a[i - 1] a[i]){flag 1;Swap(a[i - 1], a[i]);//交换}}//序列已经有序不必在进行排序if (flag 0){break;}}//打印排序好的数据Print(a, n); } 我们根据冒泡排序的执行逻辑可以推出其时间复杂度为O(N^2).冒泡排序的一些细节  三.插入排序 插入排序是一种非常清晰明了的一种排序方式通过构建有序序列对于未排序数据在已排序序列中从后向前扫描找到相应位置并插入。我们所熟知的扑克牌中就暗藏我们的插入排序。我们先将某一张牌看作有序然后将后面无序的牌与前面的牌进行比较然后插入到合适的位置。 动图演示 插入排序中的直接插入排序就很好的利用了这一算法逻辑实现了插入排序。 3.1直接插入排序 直接插入排序的逻辑就是插入排序的逻辑我们根据动图分析我们先将第一个数3看作为有序然后将后面的数与其进行比较如果小于则3往后挪将后面的数插到前面否则就不动此时这两个数就有序了。然后插入第三个数据插入的地方一定是大于等于前一个数并且小于等于后一个数。  直接插入排序的特性总结 元素集合越接近有序直接插入排序算法的时间效率越高时间复杂度O(N^2)空间复杂度O(1)它是一种稳定的排序算法稳定性稳定 然而我们现在只是完成了单次的交换那么怎样实现插入排序的全过程呢 我们知道了end是有序队列的最后一个的下标所以我们就可以控制end来实现插入排序的实现。我们使end从0开始到n-1n表示数据个数就可以实现对n个数据的排序。 //插入排序 void InsertSort(int* a, int n) {int i 0;for (i 0; i n - 1; i){int end i;int tmp a[end 1];//待插入的数while (end 0){if (a[end] tmp){a[end 1] a[end];//将大数往后放end--;}else{break;}}a[end 1] tmp;} } 我们i从0~n-2就可以对n个数进行排序。为什么in-1呢因为i end而待排序的数的下标是end1如果in的话i最大就是n-1end1 n而n不是数组的下标就越界了。我们通过i的位置就可以一直往后找到待排序的数。当i n-2的时候此时就只剩一个数没有被排了。 3.2希尔排序  希尔排序利用的基本方法也是插入排序但是希尔排序在排序前会先进行预排序使序列更接近有序。然后再进行一次插入排序使序列有序。 希尔排序法又称缩小增量法。希尔排序法的基本思想是先选定一个整数gap把待排序文件中所有记录分成gap组所有距离为gap的记录分在同一组内并对每一组内的记录进行排序。然后取重复上述分组和排序的工 作。当到达1时所有记录在统一组内排好序。 什么意思呢我们借助图来进行分析。  知道了思路那如何实现希尔排序呢 我们进行一次预排序是不能使队列更接近有序的所以我们需要进行多组预排序使我们的数据更可能的接近有序。 那么再次进行预排序的时候gap还等于3有用么 当然没有用了因为我们根据gap等于3已经对其进行了预排序其分为3组的情况已经有序了。所以我们要改变gap的值后再进行预排序。 那么现在gap的值是应该变大还是变小呢 答案是变小因为我们在预排序之后还要进行一次插入排序而我们可以观察到当gap等于1时希尔排序和插入排序是一样的。 这样不仅可以解决要进行多次预排序的问题还可以解决最后一次插入排序的问题。 那么gap要怎么取呢gap的取法有很多种没有一个固定的要求但是目前大多数都已gap/31的方式来取gap。这样可以保证最后一次gap一定等于1. // 希尔排序 void ShellSort(int* a, int n) {int gap n;while (gap 1){gap gap / 3 1;int i 0;for (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;}else{break;}}a[end gap] tmp;}} } 3.2.1希尔排序的时间复杂度 希尔排序的时间复杂度非常难计算因为gap取的不同算出的时间复杂度也不同。但是目前都比较认可的时间复杂度为O(N^1.3)。 我们从图中可分析出gap的变化会导致交换次数的变化而gap的取法又是多种多样的所以希尔排序的时间复杂度非常难计算。 四.选择排序 选择排序也是我们刚开始学习C语言就接触到的一种排序方法。其排序思想是遍历待排数据从中选出最小的与第一个数据进行交换然后重新遍历数据找到次小的数据放到第二个数据的位置。直到所有数据有序。我们从分析中可以得知其时间复杂度为O(N^2). 直接选择排序的特性总结 直接选择排序思考非常好理解但是效率不是很好。实际中很少使用时间复杂度O(N^2)空间复杂度O(1)稳定性不稳定  //选择排序 void SelectSort(int* a, int n) {int i 0;for (i 0; i n; i){int mini i;int j 0;for (j i; j n; j){if (a[mini] a[j]){mini j;}}Swap(a[i], a[mini]);} } 我们在遍历的时候记录下当前数据中最小数据的下标如果遇到更小的就更新下标遍历完之后与第一个数据交换。此时我们第一个数据就是最小的数据我们直接从第二个开始遍历如果从第一个开始的话结果会错误。以此类推第三次从第三个开始遍历。 这种选择排序的效率太低了实践中没有什么意义。所以我们可以对其进行优化。 我们可以从左右两边同时开始遍历左边找最小的放右边找最大的放。 //优化选择排序 void OptimizeSelectSort(int* a, int n) {int begin 0;int end n - 1;while (begin end){int mini begin;//记录最小数据的下标int maxi end;//记录最大数据的下标int j 0;for (j begin; j end; j){if (a[mini] a[j]){mini j;}if (a[maxi] a[j]){maxi j;}}if (mini maxi n - 1){Swap(a[mini], a[maxi]);}else if (maxi begin){Swap(a[end], a[maxi]);Swap(a[begin], a[mini]);}else if (mini end){Swap(a[begin], a[mini]);Swap(a[end], a[maxi]);}begin;end--;} } 五.堆排序 堆排序是数据结构堆的一种应用我之前已经介绍过了大家可以看这篇文章来了解堆排序。小伙饿坏了赶紧点了一份爱学的二叉树——堆狼吞虎咽学完很过瘾-CSDN博客 六.快速排序  6.1hoare快排 hoare快排的基本思想是选左边的数为关键字key然后R先从右边开始遍历序列找到第一个比key小的数就停下然后L开始从左边开始遍历找到第一个比key大的数停下来然后交换R和L的值然后R继续找找到后L找然后交换当R与L相遇的时候与key交换。这就完成了一次排序使key换到了自己有序的位置上左边的数都小于key右边的数都大于key。然后key会将区间分成左右两个区间然后分别对这两个区间采用相同的操作就可以使数据有序。 下面我们来实现一下单趟排序的逻辑 首先选取区间左端点为keyL从左端点开始R从右端点开始 然后R先向左走找到第一个比key小的数然后停下来  然后L开始向右走找到第一个比key大的数停下来  然后交换L和R对应位置上的数据  此时L和R还没有相遇R继续向左走找比key小的数据  找到后L向右走找比key大的数  L和R还没有相遇交换L和R对应的值  然后重复刚才找小和找大的过程 判断有没有相遇没有相遇L再向右走找大 当L和R相遇之后交换key位置的数据和L、R位置上的数据  然后原来key对应的数6此刻就已经有序了因为它的左边都是小于它的数右边都是大于它的数 。接着想要让该序列有序的话只需要让6的左右区间分别有序就可以了。而且让其有序的方法和上述方法一样所以我们这里需要用到递归的思想。 然后下面的左右区间也会使一个数有序然后又将区间分成左右两个然后再利用递归使其有序。  理解了思想我们先来实现快排单趟如何实现也就是如果先使一个数据有序 我们单趟完成之后只需要采用递归的思想对左右两个区间分别进行该操作即可。 但是我们递归就必须得有递归地结束条件。每完成一次就会有一个数有序分出来左右区间那么当分出来的区间只有一个数时或者区间不存在时还需要排序么 所以递归地结束条件就是当区间只有一个数或者区间不存在。 //hoare快排 void hoareSort(int* a, int left, int right) {//区间只有一个数或者没有数时不需要排序if (left right){return;}int begin left;int end right;int keyi left;while (begin end){//end先往左走找小while (a[end] a[keyi] begin end){end--;}//begin往右走找大while (a[begin] a[keyi] begin end){begin;}//begin和end在一个位置时交换没必要if (begin ! end){//交换end和begin处的数据Swap(a[begin], a[end]);}}//此时begin和end相遇交换其和keyi的值Swap(a[begin], a[keyi]);keyi begin;//[left,keyi-1] keyi [keyi1,right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi 1, right); } 这就是hoare快排的基本形式但是该代码有一定的缺陷。可能会造成栈溢出的风险。我们分析排序过程之后可以发现其递归图就好像二叉树的样子每一个区间就类似于二叉树的节点其高度也就是logN但是当待排序的序列是有序的时候即最小的数据一直在左边的话那么就会出现下面这种情况 每次都将最左边的排好将区间分为左边和右边每次相当于左区间为空右区间比原区间少一个数所以现在的高度就成了N^2这个量级。  这样递归的深度就增加了可能会造成栈溢出的封信。 那么有什么办法可以避免该问题么 采用三数取中法 我们可以在进行排序之前先取出区间左右端点的值和区间中点的值然后取三个数中中间的数作为key这样就可以避免key是序列中最小值的情况了。 //三数取中法 int GetMidData(int* a,int left,int right) {int begin left;int end right;int mid (left right) / 2;if (a[begin] a[mid]){if (a[mid] a[end]){//begin mid endreturn mid;}else{//mid是最小的返回end和begin中较小的那个return a[begin] a[end] ? end : begin;}}else//begin mid{if (a[mid] a[end]){//begin mid endreturn mid;}else{//mid是最大的返回begin和end中较大的那个return a[begin] a[end] ? begin : end;}} } 我们将第二大的数的下标返回然后在快排函数中接收该下标与区间左端点值进行交换使左端点left对应的值为第二大的值。  那么快排的时间复杂度是多少呢 最坏的情况就是数据已经有序的情况下递归深度增加其时间复杂度为O(N^2)。但是我们采用了三数取中的方式对快排进行了优化就不可能出现递归太深的情况往往都是哪种类似二叉树的情况它的高度为logN每一层都要遍历区间所以是N所以它的时间复杂度为O(N*logN)。 到了这里我们还可以对快排在进行一次优化——小区间优化。  我们刚才介绍快排展开就像是一棵二叉树一样而对于二叉树来说其最后一层上的节点数就占了总结点数的一半。那对于快排来说最后一层上的区间数就是总区间数的一半而每一个区间都要进行递归所以快排最后一层的递归次数占了整个排序过程中递归次数的一半。 而对于后面几层的区间来说这些已经是被分的很小的区间了。所以我们这里提出一个小区间优化对区间元素个数小于10个的直接对其使用插入排序这样就可以减少快排的大部分递归次数。 hoare快排最后一个问题就是当L和R相遇之后key与其交换相遇处的值是否一定小于key值答案是是 我们在这里先给出结论区间左端点为keyR起始为右端点L为左端点R先走找小L后走找大当其相遇的时候相遇位置的值一定小于key位置的值。 在走的过程中结束的情况有两种 L遇见RL遇见R说明R已经停下来了此时该L走了那么它们相遇的位置就是R停下的位置R是找小说明其遇到了比key小的数才停了下来。此时相遇值小于key。R遇见LR遇见L说明已经完成了一次交换该R先走走到了L之前停的位置又因为上一次L已经和R发生了交换此时L位置上的数一定小于key所以该相遇处的值还是小于key。 当然key也可以取右端点那就是L先走R再走。对于上面的情况如果想要排降序那就R找大L找下。 6.2挖坑法 hoare快排是最先提出来的快排方式但后来还出现来好几种实现快排的方式但其本质上都是一样。这里我们了解一下挖坑法实现快排。 这里依旧选区间左端点为key我们记录其值然后该位置的数就可以被覆盖把这记作一个坑。然后R从右端点开始找小找到之后将数据填入坑中然后在该位置形成一个新的坑。然后L开始找大找到之后填入坑中再形成新的坑。当L与R相遇时将key的值填入坑中就完成了一次单趟快排。然后跟hoare一样再进行递归实现其他区间排序。 下面给出单趟排序的过程图 先记录区间左端点的值然后在该位置形成坑  R开始找小找到之后将值填入坑中然后形成新的坑 L开始找大找到之后填入坑中形成新坑  然后重复上面的过程直到L和R相遇 R走 L走 R走  L走 但是L走的过程中与R相遇此时将key填入坑中。这样6就已经有序了然后我们在对6左右两个区间分别采用递归的方式使序列有序。 //挖坑法 int DigHoleSort(int* a, int left, int right) {//三数取中法int mid GetMidData(a, left, right);//将中间数的值与left的值进行交换left的值就一定不是最小的了Swap(a[mid], a[left]);int begin left;int end right;int key a[left];//关键字R和L比较的标准int hole left;//在挖坑法中hole就是坑位的下标while (begin end){while (a[end] key begin end){end--;}//找到小于key的数之后填入坑中a[hole] a[end];//更新坑位hole end;while (a[begin] key begin end){begin;}//找到大于key的数之后填入坑中a[hole] a[begin];//更新坑位hole begin;}//相遇后将key填入坑中a[hole] key;//hole下标对应的数据就已经有序了return hole;//返回的是分左右区间的依据 }我们对快排进行了拆分将单趟的排序分割出来使快排函数内部显得更为简单一点。我们只需要调用单趟排序即可。下面是快排的逻辑。 //快排 void QuickSort(int* a, int left, int right)//left为起始下标right为终止下标 {//区间只有一个数或者没有数时不需要排序if (left right){return;}//小区间优化if ((right - left 1) 10){InsertSort(aleft, right - left 1);}else {//int keyi hoareSort(a, left, right);//hoare快排int keyi DigHoleSort(a, left, right);//挖坑法//一个单趟走完此时keyi处的数据已经有序将还需排序的数据分成了两个区间再对下面区间数据采用相同逻辑进行排序//[left,keyi-1] keyi [keyi1,right]QuickSort(a, left, keyi - 1);QuickSort(a, keyi 1, right);} } 我们根据上面两端代码的逻辑可以清楚的理解挖坑法为什么要将最后一个坑位返回给快排函数因为单趟之后最后一个坑的数已经有序了所以要借助来分割区间。 6.3前后指针法 前后指针法也是一种递归方式的快排与hoare和挖坑有些许不同。 该方法就是key指向左端点值prev也指向左端点cur指向prev的下一个位置然后cur开始向后遍历遇到比key小的数后prev往前走一步然后交换cur和prev然后cur继续走当cur指向序列之外时此时prev指向的数据就已经有序了返回该值作为区分区间的依据。然后在进行递归排序。 下面给出一个单趟交换的过程 //前后指针法 int PrevCurSort(int* a, int left, int right) {//三数取中法int mid GetMidData(a, left, right);//将中间数的值与left的值进行交换left的值就一定不是最小的了Swap(a[mid], a[left]);int prev left;int cur prev 1;int keyi left;while (cur right){ //cur小于key时prev向后走一步然后交换if (a[cur] a[keyi] prev ! cur){Swap(a[cur], a[prev]);}cur;}Swap(a[prev], a[keyi]);keyi prev;return keyi; } 6.4非递归实现快排 6.4.1栈实现快排 我们在上面介绍了三种借助递归实现快排的方法。我们在这里了解一下如何用非递归实现快排。 我们仔细观察上面递归实现快排的过程中每一次不同的地方在于函数的区间不同所以如果想用非递归实现快排我们只需要将每一个需要排序的区间记录下来。然后根据区间调用单趟快排即可。 在这里我们可以借助我们先前了解过的数据结构栈来实现。不了解栈的可以看这篇文章重生之我在学数据结构——栈-CSDN博客。 栈遵循的是后进先出的逻辑所以如果我们想要实现跟上面递归一样的逻辑时我们可以先储存右区间在储存左区间。在储存区间时先插入区间右端点再插入区间左端点。当我们要排序的时候先取出区间然后进行单趟排序然后将其产生的左右区间再次插入到栈中。当栈为空时此时序列已经有序。 在这里我们采用上面的哪一个单趟都是可以的。 这就是借助栈来实现快排的思路。接下来我们实现代码  //非递归快排 void QuickSortNonR(int* a, int left, int right) {//创建栈Stack s { 0 };StackInit(s);//先将区间端点压入栈中//先压右端点后压左端点StackPush(s, right);StackPush(s, left);while (!StackEmpty(s)){int begin GetStackTop(s);StackPop(s);int end GetStackTop(s);StackPop(s);int keyi PrevCurSort(a, begin, end);//右区间if (end keyi 1){StackPush(s, end);StackPush(s, keyi 1);}//左区间if (begin keyi - 1){StackPush(s, keyi - 1);StackPush(s, begin);}}StackDestory(s); } 我们再将区间压入栈时要进行判断如果区间只有一个数那就没有必要入栈去走单趟了。区间不存在时也不能入栈。 6.4.2队列实现快排 我们也可以借助队列来实现快排。基本思路是相同的储存区间即可。但是因为队列遵循的是先进先出所以在存储区间的时候有所不同。我们要先入左区间再入右区间入区间端点时先入左区间再入右区间。 //借助队列实现非递归快排 void QuickSortNonRByQueue(int* a, int left, int right) {//创建队列Queue q { 0 };QueueInit(q);QueuePush(q, left);QueuePush(q, right);while (!QueueEmpty(q)){//获取区间左右端点int begin GetQueueFront(q);QueuePop(q);int end GetQueueFront(q);QueuePop(q);int keyi PrevCurSort(a, begin, end);//先入左区间if (begin keyi - 1){//先入左端点QueuePush(q, begin);QueuePush(q, keyi - 1);}if (end keyi 1){QueuePush(q, keyi 1);QueuePush(q, end);}}QueueDestroy(q); } 七.归并排序 7.1递归实现归并 归并排序也是一种常用的排序方法其基本逻辑是将一段区间分为左右区间如果左右区间有序那就进行归并。归并的操作就是同时遍历左右区间进行比较将小的数拷贝到一个新数组中然后拷贝完后在将数据拷贝到原数组这样就实现了排序的功能。 所以我们的问题就转变成了使区间分成的左右区间有序那么如何使分成的左右区间有序呢 我们可以多次进行分割当区间只有一个数据时或者区间不存在时那就说明此时这个区间就已经有序了然后进行归并操作这时候两个数据就有序了然后继续归并直到将所有数据归并完。我们可以看到这里面用到了递归地思想。 我们给出下面一组数据进行归并排序的过程分析 假设下面就是待排序的数据 为了使这组数据有序那就得将他分成左右两个区间让左右区间分别有序然后进行归并操作  但是这两个区间并不有序那我们就得继续分割直到其区间中的数据有序  我们将其分为一个区间只有一个数据时此时每一个区间就都是有序的了然后我们在进行归并操作。使左右区间的上一级区间有序。 然后再继续归并使其上一层区间有序  在执行同样的归并操作  当其没有上一层区间时就说明数据已经有序了。  void _MergeSort(int* a, int* tmp, int left, int right) {if (left right){return;}//分割区间//[left,mid][mid1,right]int mid (left right) / 2;_MergeSort(a, tmp, left, mid);_MergeSort(a, tmp, mid 1, right);//归并int i left;//左区间int begin1 left;int end1 mid;//右区间int begin2 mid 1;int end2 right;while (begin1 end1 begin2 end2){if (a[begin1] a[begin2]){tmp[i] a[begin1];}else{tmp[i] a[begin2];}}//一个区间结束放完了还有一个区间没完while (begin1 end1){tmp[i] a[begin1];}while (begin2 end2){tmp[i] a[begin2];}memcpy(a left, tmp left, sizeof(int) * (end2 - left 1)); }//归并排序——递归 void MergeSort(int* a, int n) {int* tmp (int*)malloc(sizeof(int) * n);if (tmp NULL){perror(MergeSort()::malloc());return;}_MergeSort(a,tmp,0,n-1);free(tmp);tmp NULL; } 那么归并排序的时间复杂度是多少呢 归并排序最后会将数据分割成一个一个的区间其分割的过程是一种折半分割类似于二叉树所以其高度为logN然后其每一层都要进行归并就要对每一个数据进行比较访问到每一个数据其消耗是N所以其时间复杂度为O(N*logN)  7.2非递归实现归并 我们也可以使用非递归来实现归并排序。但是我们不再借助栈来实现我们直接通过循环来控制每一次归并的区间使其每进行一次归并就使区间有序。 我们同样对相同的数据进行操作下面进行过程的分析 我们不再像上面那种分割区间将区间分成只有一个数据时然后在进行归并。我们这里直接进行归并但是归并的要求是左右区间是有序的而在当前情况下只有一个数据是一个区间时才是有序的所以我们可以进行11归并即每组只有一个值相邻两组进行归并。 我们看到此时就产生了有序的区间了而区间的值也有1变为2所以我们这里来在进行22归并使相邻的区间合并成一个有序区间  此时上面相邻的左右区间就合并成了一个新的有序然后又作为左区间和右区间进行44归并 当只剩一个区间时就说明已经排序完成了。 我们看到我们再归并的过程中控制了每一次归并数据的数据个数由一个变成2个、4个这就像是我们递归的逆过程。我们用一个gap变量来表示当前归并的每组数据的数据个数。 //归并排序——非递归 void MergeSortNonR(int* a, int n) {int* tmp (int*)malloc(sizeof(int) * n);if (tmp NULL){perror(MergeSort()::malloc());return;}int gap 1;while (gap n){int i 0;for (i 0; i n; i 2*gap){int begin1 i;int end1 i gap - 1;int begin2 i gap;int end2 i 2 * gap - 1;while (begin1 end1 begin2 end2){if (a[begin1] a[begin2]){tmp[i] a[begin1];}else{tmp[i] a[begin2];}}//一个区间结束放完了还有一个区间没完while (begin1 end1){tmp[i] a[begin1];}while (begin2 end2){tmp[i] a[begin2];}//将数据复制到原数组中memcpy(a i, tmp i, sizeof(int) * (end2 - i));}gap * 2;}free(tmp);tmp NULL; } 代码写完了但是我们写的是有问题的我们的i是每次归并的开始位置只有每一次归并结束后才改变但是我们再归并操作中用它做了导致它进行了自增对后面其他组的递归造成影响。 所以我们利用j来作为下标使用防止意外改变了i。 还有一个问题就是拷贝数据的时候我们拷贝的数据个数是右区间的右端点-左区间的左端再加上1因为下标是个数-1所以求个数时要加上1.  修改完成后我们看结果  但是当我们的数据不是2的倍数时就会产生错误会发生越界访问。因为我们的下标都是按照2的倍数计算的当数据不是时就会导致下标大于最后一个数据的下标导致越界访问。 我们来打印一下每一次归并的区间来判断那个端点越界了 我们只有10个数据当下标超过9时就说明越界了。而根据我们分出的区间我们可以判断出是谁越界了。  这样分类是因为当end1和begin2都越界时它们就不再需要进行归并了说明此时只有一组数据两组才能归并所以我们将这两种归为一类。 第二类就是end2越界说明此时begin2没有越界是有两组数据的所以还需要进行归并此时我们就要对end2进行修改。  再进行测试 我们已经解决了非递归中出现的问题代码已经完全  //归并排序——非递归 void MergeSortNonR(int* a, int n) {int* tmp (int*)malloc(sizeof(int) * n);if (tmp NULL){perror(MergeSort()::malloc());return;}int gap 1;while (gap n){int i 0;for (i 0; i n; i 2*gap){int begin1 i;int end1 i gap - 1;int begin2 i gap;int end2 i 2 * gap - 1;//printf([%d,%d] [%d,%d], begin1, end1, begin2, end2);//begin2或者end1越界都不需要归并直接跳出循环if (begin2 n){break;}//只有end2越界修改end2的下标使其指向数据的最后一个元素的下标if (end2 n){end2 n - 1;}int j begin1;while (begin1 end1 begin2 end2){if (a[begin1] a[begin2]){tmp[j] a[begin1];}else{tmp[j] a[begin2];}}//一个区间结束放完了还有一个区间没完while (begin1 end1){tmp[j] a[begin1];}while (begin2 end2){tmp[j] a[begin2];}//将数据复制到原数组中memcpy(a i, tmp i, sizeof(int) * (end2 - i 1));}gap * 2;}free(tmp);tmp NULL; } 八.计数排序 计数排序是一种不需要进行比较就可以实现排序的一种的算法有点像哈希表。其具体操作步骤为1.统计相同数据出现的个数2.根据个数将对应数据放到原数组中。 下面借助一组数据来分析计数排序的执行过程 假设有一组数据都属于0~10这个范围我们来对其进行排序 第一步就是统计数据中每一种元素出现的数据个数记录到一个count数组中 count数组中每一个数据都初始化为0 我们进行统计之后count数组就发生了变化 然后我们根据count数组中每一个下标对应元素的个数开始打印下标。  0下标对应的元素是0那就不打印01下标对应的元素是3那就打印3次1一次类推在遍历count数组时将数据输出到原数组此时数据就变得有序了。 这到底是怎么实现的呢  其实我们是将待排序的数作为了count数组的下标了而count数组的下标本来就是有序的。所以我们只需要知道每一个下标出现了几次然后拷贝到原数组就行了。 那该方法遇到数据不是0~10的怎么办呢比如数据是100~109呢 其实我们还是可以做到一一对应的关系。我们计算出数据所在的区间大小range来判断我们需要开多大的空间然后我们将原数组中每一个数据减去最小值再作为count数组的下标来进行统计。  假如我们的数据是上面的数据难道我们要开一个空间为110的count数组来存储嘛  如果这样的话我们前面的空间都被浪费掉了因为那里并没有值所以肯定不可以这样来申请空间。 我们用每一个值减去最小值来看一下 当前去最小值之后是不是就可以和下面的下标对应起来 0就相当于是1001相当于是101。这样就避免了多开空间造成空间浪费的问题。而我们这个逻辑转化为代码就是 我们接下来实现代码  //计数排序 void CountSort(int* a, int n) {//找出最小值和最大值求出数据的范围int i 0;int min a[0];int max a[0];for (i 0; i n; i){if (min a[i]){min a[i];}if (max a[i]){max a[i];}}int range max - min 1;//创建count数组int* count (int*)calloc(range, sizeof(int));if (count NULL){perror(CountSort()::calloc());return;}//统计for (i 0; i n; i){count[a[i] - min];}//将数据拷贝回原数组i 0;int j 0;for (j0; jrange; j){while (count[j]--){a[i] j min;}}九.排序的稳定性 排序的稳定性就是说对两个相同的数据进行排序之后它们两个的相对位置不发生变化就说明该排序是稳定的否则就是不稳定的。我们对上面介绍过的排序进行稳定性分析。 冒泡排序是稳定的冒泡排序是相邻之间进行交换如果两个数据相等时就不再交换了所以其不会改变两个相等元素的相对位置。 插入排序是稳定的如果该元素小于前面的元素才会向前插入如果与前面元素相等就会插入到该元素的后面所以相对位置也不会改变。 希尔排序是不稳定的因为希尔排序要进行多组的预排序有可能相等的元素不在同一组可能会造成数据位置的交换。 选择排序是不稳定的选择排序会遍历数组选出最小的放到第一个位置但是如果是下面这种情况就会交换数据位置 虽然两个1的相对位置没变但是6的相对位置变了 堆排序是不稳定的如果数组建堆之后的跟和左右儿子位置的值相等此时将根换到最后一个位置时相对位置发生了改变。 快速排序是不稳定的快速排序中会跳跃交换这种就很有可能造成相对位置的变化 归并排序是稳定的如果归并时两组的数据相同就会先归并第一个在归并第二个但是他们的相对位置却没有变化。
http://www.dnsts.com.cn/news/30085.html

相关文章:

  • 网站标题写什么作用是什么意思网站设计的基本流程是什么
  • 网站维护费用明细可以左右滑动的网站
  • seo怎样才能优化网站青岛百度网站排名优化
  • 网站建设售价多少钱珠宝首饰网站模板
  • 黑龙江生产建设兵团网站高校网站群建设的公司有哪些
  • 东营seo整站优化电子商务网站开发与建设
  • 越秀区营销型网站建设小程序开发工具下载
  • 深圳市工商注册信息查询网站wordpress 站内链接
  • 网站么做淘宝客赚佣金网站建设模板网站
  • 网上做牙刷上什么网站最新新闻热点国家大事
  • 设计案例的网站鲜花店网站建设
  • 好创意网站有哪些方面网站建设证有
  • 杭州做网站的网络公司有哪些网站建设和网站优化哪个更重要
  • 公司网站升级改版方案网站开发建设总结
  • 免费申请自己的网站湛江网站建设公司
  • 网站设计任务深圳网域官网
  • 兼容性视图中显示所有网站浏览器代理怎么弄
  • 宁波网站建设培训唐山百度网站建设
  • 网站怎样绑定域名访问乐清房产在线网
  • 网站泛目录怎么做天津网站建设揭秘
  • 安徽网站建设哪家好郑州seo技术博客
  • ai设计网站24小时在线观看视频直播
  • 南京建设局的网站可以做推广的平台
  • 怎样可以做网站手机网站开发工具
  • 域名备案关闭网站seo是什么服务
  • 东莞哪家网站建设高端大气网页
  • 网站建设安全性天猫商城网官网
  • 景区微网站建设费用本地主机 搭建网站
  • 网站开发要学些什么线上营销培训
  • 营销类网站建设需要注意的问题网站模板的使用