当前位置:   article > 正文

常见排序宝典:帮助快速上手常见基础排序算法(下)

常见排序宝典:帮助快速上手常见基础排序算法(下)

目录

五、归并排序

1、算法步骤

 2、动图演示​编辑

 3、代码实现

六、堆排序

1、算法步骤

2、动图演示 

3、代码实现

七、快速排序

1、基本思想

2、动图演示

3、代码实现 

八、计数排序

1、算法步骤

 2、动图演示

​编辑

3、代码实现 


五、归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即显示每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:

  • 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
  • 自下而上的迭代;

和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 O(nlogn) 的时间复杂度。代价是需要额外的内存空间。

1、算法步骤

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;

  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;

  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;

  4. 重复步骤 3 直到某一指针达到序列尾;

  5. 将另一序列剩下的所有元素直接复制到合并序列尾。

 2、动图演示

 3、代码实现

(递归实现)

  1. void _Merge_sort(int* arr, int begin, int end, int* tmp)
  2. {
  3. if (begin == end)
  4. {
  5. return;
  6. }//从单个元素开始递归
  7. int mid = (begin + end) / 2;//划分为两个空间,等待回归
  8. _Merge_sort(arr, begin, mid, tmp);
  9. _Merge_sort(arr, mid + 1, end, tmp);
  10. //开始归并
  11. int begin1 = begin, end1 = mid;
  12. int begin2 = end1 + 1, end2 = end;
  13. int i = begin;//i来记录此次归并开始的位置
  14. while (begin1 <= end1 && begin2 <= end2)
  15. {
  16. if (arr[begin1] <= arr[begin2])
  17. {
  18. tmp[i++] = arr[begin1++];
  19. }
  20. else
  21. {
  22. tmp[i++] = arr[begin2++];
  23. }
  24. }
  25. //当两部分有一方出现end大于begin时,退出循环,此时另外一方可能出现数据没完全拷贝回去的情况
  26. while (begin1 <= end1)
  27. {
  28. tmp[i++] = arr[begin1++];
  29. }
  30. while (begin2 <= end2)
  31. {
  32. tmp[i++] = arr[begin2++];
  33. }
  34. //此时tmp中的数已经排好顺序,需要返回到原数组
  35. memcpy(arr + begin, tmp + begin, sizeof(int) * (end - begin + 1));
  36. }
  37. void Merge_sort(int* arr, int len)//由于我们打算用递归来写,所以最好将递归部分包装出去成为一个接口
  38. {
  39. //开辟一个同等大小的数组
  40. int* tmp = (int*)malloc(sizeof(int) * len);
  41. if (tmp == NULL)
  42. {
  43. perror("malloc fail!");
  44. exit(1);
  45. }
  46. _Merge_sort(arr, 0, len - 1, tmp);
  47. free(tmp);
  48. tmp = NULL;
  49. }

(迭代实现)

  1. void Merge_sort(int* arr, int len)//非递归思想
  2. {
  3. int* tmp = (int*)malloc(sizeof(int) * len);
  4. if (tmp == NULL)
  5. {
  6. perror("malloc fail!");
  7. exit(1);
  8. }
  9. int gap = 1;//gap代表是当前所分两组,每组的元数个数
  10. while (gap < len)
  11. {
  12. for (int i = 0; i < len; i += 2 * gap)
  13. {
  14. int begin1 = i, end1 = begin1 + gap - 1;
  15. int begin2 = begin1 + gap, end2 = begin2 + gap - 1;
  16. int j = i;
  17. if (end1 >= len || begin2 >= len)
  18. {
  19. break;//判断,小心此次循环出现数据越界
  20. }
  21. //还有一种end2越界但是begin2没有越界
  22. if (end2 >= len)
  23. {
  24. end2 = len - 1;
  25. }
  26. while (begin1 <= end1 && begin2 <= end2)
  27. {
  28. if (arr[begin1] <= arr[begin2])
  29. {
  30. tmp[j++] = arr[begin1++];
  31. }
  32. else
  33. {
  34. tmp[j++] = arr[begin2++];
  35. }
  36. }
  37. while (begin1 <= end1)
  38. {
  39. tmp[j++] = arr[begin1++];
  40. }
  41. while (begin2 <= end2)
  42. {
  43. tmp[j++] = arr[begin2++];
  44. }
  45. memcpy(arr + i, tmp + i, sizeof(int) * (end2 - i + 1));
  46. }
  47. gap *= 2;
  48. }
  49. free(tmp);
  50. tmp = NULL;
  51. }

六、堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:

  1. 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
  2. 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;

堆排序的平均时间复杂度为 Ο(nlogn)。

1、算法步骤

  1. 创建一个堆 H[0……n-1];

  2. 把堆首(最大值)和堆尾互换;

  3. 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;

  4. 重复步骤 2,直到堆的尺寸为 1。

2、动图演示 

3、代码实现

  1. void AdjustDown(int* arr, int len, int parent)
  2. {
  3. int child = parent * 2 + 1;
  4. while (child < len)
  5. {
  6. if (child + 1 < len && arr[child] < arr[child + 1])\
  7. {
  8. child += 1;
  9. }
  10. if (arr[parent] < arr[child])
  11. {
  12. Swap(arr[parent], arr[child]);
  13. parent = child;
  14. child = child * 2 + 1;
  15. }
  16. else
  17. {
  18. break;
  19. }
  20. }
  21. }
  22. void Heap_sort(int* arr, int len)
  23. {
  24. int size = len;
  25. while (size > 0)
  26. {
  27. for (int i = (size - 1 - 1) / 2; i >= 0; i--)
  28. {
  29. AdjustDown(arr, size, i);
  30. }//挑选最大的到堆顶
  31. Swap(arr[0], arr[--size]);
  32. }
  33. }

七、快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。

快速排序又是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。

1、基本思想

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割为两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

快速排序递归实现的主框架与二叉树前序遍历规则非常像,我们在写递归框架时可想想二叉树前序遍历的规则即可快速写出来,后序只需要分析如何按照基准值来对区间中数据进行划分的方式即可。

2、动图演示

(hoare版本)

(前后指针法) 

3、代码实现 

(hoare版本)

  1. void Swap(int& a, int& b)
  2. {
  3. int c = a;
  4. a = b;
  5. b = c;
  6. }
  7. //快速排序
  8. //hoare版本
  9. void Quick_sort(int* arr, int left, int right)
  10. {
  11. if (left > right)
  12. {
  13. return ;
  14. }
  15. int key = left;
  16. int begin = left, end = right;
  17. while (left < right)
  18. {
  19. while (left < right && arr[right] >= arr[key])//在循环过程中,容易出现分循环一直没找到小于的值,right一直减,让right<小于left的情况
  20. //所以安排循环条件内加入left<right。a[right]>=a[left]的等于是怕出现相同的值,导致循环断掉,出现卡死循环的情况
  21. {
  22. right--;
  23. }//直到找到一个值小于a[key]
  24. while (left<right && arr[left]>arr[key])
  25. {
  26. left++;
  27. }
  28. Swap(arr[right], arr[left]);
  29. }
  30. //退出循环时rightleft相遇
  31. Swap(arr[key], arr[left]);
  32. key = left;//交换之后更新key的值充当分割点坐标进行下面的递归
  33. Quick_sort(arr, begin, key - 1);
  34. Quick_sort(arr, key, end);
  35. }

(双指针)

  1. //双指针法
  2. void Quick_sort(int* arr, int left, int right)
  3. {
  4. if (right <= left)
  5. {
  6. return;
  7. }
  8. int prew = left, cur = left + 1;
  9. int keyi = left;
  10. while (cur <= right)
  11. {
  12. if (arr[cur] < arr[keyi] && ++prew != cur)
  13. {
  14. Swap(arr[cur], arr[prew]);
  15. }
  16. cur++;
  17. }
  18. Swap(arr[keyi], arr[prew]);
  19. keyi = prew;
  20. Quick_sort(arr, left, keyi - 1);
  21. Quick_sort(arr, keyi + 1, right);
  22. }

八、计数排序

计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

1. 计数排序的特征

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。

由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。例如:计数排序是用来排序0到100之间的数字的最好的算法,但是它不适合按字母顺序排序人名。但是,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

通俗地理解,例如有 10 个年龄不同的人,统计出有 8 个人的年龄比 A 小,那 A 的年龄就排在第 9 位,用这个方法可以得到其他每个人的位置,也就排好了序。当然,年龄有重复时需要特殊处理(保证稳定性),这就是为什么最后要反向填充目标数组,以及将每个数字的统计减去 1 的原因。

1、算法步骤

  • (1)找出待排序的数组中最大和最小的元素
  • (2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
  • (3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  • (4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

 2、动图演示

3、代码实现 

  1. void Count_sort(int* arr, int len)
  2. {
  3. int min = arr[0], max = arr[0];
  4. for (int i = 1; i < len; i++)
  5. {
  6. if (min > arr[i])
  7. {
  8. min = arr[i];
  9. }
  10. if (max < arr[i])
  11. {
  12. max = arr[i];
  13. }
  14. }
  15. int range = max - min + 1;
  16. int* tmp = (int*)calloc(range, sizeof(int));
  17. if (tmp == NULL)
  18. {
  19. perror("calloc fail!");
  20. exit(1);
  21. }
  22. for (int i = 0; i < len; i++)
  23. {
  24. tmp[arr[i] - min]++;
  25. }
  26. int j = 0;
  27. for (int i = 0; i < range; i++)
  28. {
  29. while (tmp[i]--)
  30. {
  31. arr[j++] = min + i;
  32. }
  33. }
  34. }

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/415576
推荐阅读
相关标签
  

闽ICP备14008679号