剑指offer之归并排序

举报
chenyu 发表于 2021/07/27 00:48:04 2021/07/27
【摘要】 1 问题 是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。归并排序是一种稳定的排序方法           &n...

1 问题

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

 

 

 

 

 

 

2 分析过程


  
  1.       1 4 3 2 6 8 7 5
  2.    1 4 3 2      6 8 7 5
  3. 1 4    3 2      6 8     7 5   
  4. 1 4    2 3      6 8     5 7
  5.    1 2 3 4      5 6 7 8 
  6.       1 2 3 4 5 6 7 8 

这里最关键的就是我们需要分析比如我们分治后变成了1 、4 和 2 、3这2部分数据,我们现在需要对这4个数排序,如果我们直接在这个数组里面操作下标对比,感觉分析起来很复杂,那我们可以借助辅助数组来分析,这个辅助数组的大小也是4,然后分别在2个数组1、4里面搞一个首指针,在2、3里面搞一个首指针,然后分别进行对比,然后小的数据放入辅助数组,哪个首指针插入辅助数组我么就向后移动,指导右一个手指针移动到尾巴,我们就结束比较,然后我们把右一个数组里面没有到尾巴的首指针再次移到尾巴,赋值给辅助数组就可以,然后我们辅助数组是排序好的元素,我们再把辅助元素里面的数据赋值给原数组。


  
  1.   1、           4                      2、       3
  2. i = start                           j = mid+1    end

对比数据时候循环终止条件


  
  1. while (i != mid + 1 && j != end + 1)
  2. {
  3. }

 

 

 

 

 

 

3 代码实现


  
  1. #include <stdio.h>
  2. void merge(int* source, int* temp, int start, int mid, int end)
  3. {
  4. if (source == NULL || temp == NULL)
  5. {
  6. printf("merge source or temp is NULL\n");
  7. return;
  8. }
  9. int i = start, j = mid + 1, k = start;
  10. while (i != mid + 1 && j != end + 1)
  11. {
  12. if (source[i] > source[j])
  13. temp[k++] = source[j++];
  14. else
  15. temp[k++] = source[i++];
  16. }
  17. while (i != mid + 1)
  18. temp[k++] = source[i++];
  19. while (j != end + 1)
  20. temp[k++] = source[j++];
  21. for(int h = start; h <= end; ++h)
  22. {
  23. source[h] = temp[h];
  24. }
  25. }
  26. void mergeSort(int* source, int* temp, int start, int end)
  27. {
  28. if (source == NULL || temp == NULL)
  29. {
  30. printf("mergeSort source or temp is NULL\n");
  31. return;
  32. }
  33. if (start < end)
  34. {
  35. int mid = start + (end - start) / 2;
  36. mergeSort(source, temp, start, mid);
  37. mergeSort(source, temp, mid + 1, end);
  38. merge(source, temp, start, mid, end);
  39. }
  40. }
  41. int main(void) {
  42. int source[] = {2, 3, 1, 5, 4, 9, 8, 6, 7};
  43. int length = sizeof(source) / sizeof(int);
  44. int temp[9];
  45. mergeSort(source, temp, 0, length - 1);
  46. for (int i = 0; i < length; ++i)
  47. {
  48. printf("%d\t", temp[i]);
  49. }
  50. return 0;
  51. }

 

 

 

 

 

4 运行结果

1	2	3	4	5	6	7	8	9	
 

 

 

 

 

5 总结

归并排序,我们需要对数组里面的几个子数组元素进行对比然后移动下标操作,感觉非常复杂,这个时候我们应该借助辅助数组来实现,不就是对比2个数组里面的数据吗?我们把辅助数组的大小设置2个数组元素大小之和,然后搞2个首指针,对比,然后哪个数据小,就插入到辅助数组,然后移动相应的指针就行,然后有一个数组里面的数据肯定都会插入到辅助数组,我们再把另外一个数组里面剩余的元素插入辅助数组,辅助数组就排序好了,然后我们再把辅助数组辅助给原数组就ok了。

1423
 

辅助数组里面的值变化


  
  1. 1    *      *      *
  2. 1    2     *      *
  3. 1    2     3     *
  4. 1    2     3     4

归并排序用到了辅助数组和2首指针思想,等辅助数组排序好了再赋值给原数组,打死也不要忘记。

 

 

这个问题的本质我们需要知道两个排序的数组,如果能移动里面的数据,确保两个数组的数据依次是都是排序的,比如我们数组如下

int source[] = {2, 6, 1, 4, 5, 9, 3, 7, 8};
 

 现在我们把这个数组里面的部分原始分割成2部分,列如第一个元素2和第二个元素6是一个子数组,第三个元素1和第四个元素4是一个子数组,每个子数组排序都好了,我们现在需要把这个2个子数组里面的数据进行排序,也就是2个子数组的起始下标是0~1  2~3排序好了后把原数组变成

1	2	4	6	5	9	3	7	8
 

我们实现标准的通用代码如下


  
  1. #include <stdio.h>
  2. void printDatas(int* datas, int len)
  3. {
  4. for (int i = 0; i < len; ++i)
  5. {
  6. printf("%d\t", datas[i]);
  7. }
  8. printf("\n");
  9. }
  10. void sort(int* datas, int start1, int end1, int start2, int end2)
  11. {
  12. if (datas == NULL)
  13. {
  14. printf("datas is NULL\n");
  15. return;
  16. }
  17. if (start1 > end1 || start2 > end2)
  18. {
  19. printf("start1 > end1 || start2 > end2\n");
  20. return;
  21. }
  22. int length = end1 - start1 + end2 - start2 + 2;
  23. int copy[length];
  24. int i = start1, j = end1, k = start2, h = end2, m = 0, n = 0;
  25. //用2个指针把指向的值进行对比,然后向右移动,这里需要要求2个数组都是排序好的,
  26. while (i != j + 1 && k != h + 1)
  27. {
  28. if (datas[i] > datas[k])
  29. {
  30. copy[m++] = datas[k++];
  31. }
  32. else
  33. {
  34. copy[m++] = datas[i++];
  35. }
  36. }
  37. //把剩余的一个数组里面的值赋值给我们的copy数组
  38. while (i != j + 1)
  39. copy[m++] = datas[i++];
  40. while (k != h + 1)
  41. copy[m++] = datas[k++];
  42. //把copy数组再赋值给原数组
  43. printDatas(copy, length);
  44. i = start1;
  45. k = start2;
  46. for (; n <= end1 - start1; ++n)
  47. {
  48. datas[i++] = copy[n];
  49. }
  50. for (; n < length; ++n)
  51. {
  52. datas[k++] = copy[n];
  53. }
  54. }
  55. int main(void) {
  56. int source[] = {2, 6, 1, 4, 5, 9, 3, 7, 8};
  57. int length = sizeof(source) / sizeof(int) ;
  58. printDatas(source, length);
  59. int temp[9];
  60. sort(source, 0, 1, 2, 3);
  61. printDatas(source, length);
  62. return 0;
  63. }

运行结果如下

1	2	4	6	5	9	3	7	8
 

现在如果我的2个子数组的起始下标不是0~1和2~3,是0~1和6~8,我们把上面的函数

	sort(source, 0, 1, 6, 8);
 

我们再看运行结果

2	3	1	4	5	9	6	7	8
 

注意我们这这个sort函数(void sort(int* datas, int start1, int end1, int start2, int end2)),不满足两个子数组数据有交叉的情况,但是对于两个数组的长度没有限制(在合法情况),而且这个两个数组可以不连续, 原始的5个数组是1 2 3 7 8 现在变成了2 3 6 7 8,说明没毛病

 

然后我们归并排序里面,只不过我们的end1就是mid值,然后start2的值是mid + 1的值,两个子数组是连续的,然后长度也是一致,属于上面的特殊情况。

 

归并排序是稳定排序算法,适合子数组序列排好序。

文章来源: chenyu.blog.csdn.net,作者:chen.yu,版权归原作者所有,如需转载,请联系作者。

原文链接:chenyu.blog.csdn.net/article/details/102752448

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。