分治思想01_排序算法_归并_各种写法和思路(待续)

Posted kid-yln

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分治思想01_排序算法_归并_各种写法和思路(待续)相关的知识,希望对你有一定的参考价值。

摸鱼了一个星期没更,现在补回来。上星期基本上就是邻接ddl(周一),套板子或者用现成sort修修补补过的,

今天是周六,是时候检验一下是不是真的完全会了,顺便解决一些当时没有想通的问题

先说归并排序

归并的思路,我觉得应该是很容易理解的,这里就不赘述,唯一难的就是把这个—再归并再排序—的模板自己写出来,

网上有很多模板,比较著名的是《算法导论》上的那个借助两个媒介数组实现的,以及借助一个媒介数组实现的,

停!这里其实就是第一个分歧了,似乎为了方便,大部分板子都是有辅助空间的,但是实际上还有一个原地存储不使用额外辅助空间的办法,只是写法会有所不同

  • 对于递归的思路来说,整个过程主要是一个自顶向下,再自底向上的过程

    分解成,递归分割到底,然后归并返回,返回时对上一个归并子区间两两排序合并,

  • 对于更简洁的过程,其实只有自底向上的过程,就是所谓的非递归实现

    不用考虑分割,直接实现各个部分的自底向上排序,

https://blog.csdn.net/acingdreamer/article/details/53925072?utm_source=blogxgwz8(比较详细的解释)

的,自然合并排序的思路,https://blog.csdn.net/m0_37787222/article/details/79856252

OK。这里是第二个分歧点,下面来讨论最核心的部分,合并两个有序区间该怎么操作,

对于 两个数组,1 2 4 和 3 4 6,固定一个,比如前面那个,然后一对一,从小到大,a1,b1,大就往后找,小就安插前面,

比如现在b1放在a2前面,然后接着b2,从a2开始找,

从1到n来比,小的放进媒介数组tmp,大的继续和剩下的比,比完之后,会有多余的,多余的补充到后面,

再把已经操作过的tmp中的元素再放回目标移动数组a,

这时就体现媒介数组的好处,如果原地存储需要整体移动,很麻烦,当然用stack或者set  insert可能稍微好一点,

附上本人的代码,重点是第十九行

技术图片
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 typedef long long ll;
 7 const int Max=1e6+2;
 8 int a[Max];
 9 int t[Max];
10 
11 void m1(int a[],int from,int to) {
12     if(from==to)return;
13     //int mid=(from+to)>>1;
14     int mid=(from+to)/2;
15     //int mid=f+(f-t)/2
16 
17     //先分割,
18     m1(a,from,mid);
19     m1(a,mid+1,to);
20 
21     //分割得到的是区间脚标f,t,mid的状态,最底为f=mid,t=mid+1的极限状态
22 
23     //再合并,放入中间数组保存
24 
25     int x=from;
26     int y=mid+1;
27     int g=1;
28     while(x<=mid||y<=to) {
29         /*这个边界是错误的,因为y>to之后继续在第二个if进行比较
30         if(y>to||a[x]<=a[y]) {//
31             t[g++]=a[x++];
32         }
33         if(x>mid||a[x]>a[y]) {
34             t[g++]=a[y++];
35         }
36         */
37 
38         if(y>to||(x<=mid&&y<=to&&a[x]<=a[y])) {
39             t[g++]=a[x++];
40         }
41         if(x>mid||(x<=mid&&y<=to&&a[x]>a[y])) {
42             t[g++]=a[y++];
43         }
44 
45     }
46     //多余的不能忽略
47     g--;
48     x=from;
49     for(int i=1; i<=g; i++)a[x++]=t[i];
50 }
51 
52 /*
53 
54 5
55 5 1 3 2 4
56 6
57 5 3 2 2 1 6
58 7
59 1 2 5 8 2 4 7
60 8
61 1 5 6 3 13 5 2 9
62 
63 */
64 
65 
66 
67 int main () {
68     int n;
69     while(~scanf("%d",&n)) {
70         for(int i=1; i<=n; i++)scanf("%d",&a[i]);
71 
72         m1(a,1,n);
73         //for(int i=1; i<=n; i++)printf("%d ",a[i]);
74         puts("");
75         //m2();
76         //    m3();
77 
78     }
79 
80 
81     return 0;
82 }
View Code

 

下面说说非递归实现, 说白了就是直接自底向上划分区间

重点是如何用循环划分区间后提取边界脚标,那比较简单的想法就是把比较的两个区间的长度作为变量,每次从1开始,

1v1的比较,得到区间长度最大为2最小为1的有序区间,

然后2v2,得到长度最大为4最小为2的有序区间,

然后4v4,得到长度最大为8最小为4的有序区间,

。。。以此类推

难点在于有些区间长度直接加不够,需要另外讨论,关键点在于

merge操作限定了mid必须小于to,不能等于,因此也不能等于n,因此上限是mid小于n

以下为非递归代码,m2+merge实现

技术图片
  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 using namespace std;
  6 typedef long long ll;
  7 const int Max=1e6+2;
  8 int a[Max];
  9 int t[Max];
 10 
 11 void m1(int a[],int from,int to) {
 12     if(from==to)return;
 13     //int mid=(from+to)>>1;
 14     int mid=(from+to)/2;
 15     //int mid=f+(f-t)/2
 16 
 17     //先分割,
 18     m1(a,from,mid);
 19     m1(a,mid+1,to);
 20 
 21     //分割得到的是区间脚标f,t,mid的状态,最底为f=mid,t=mid+1的极限状态
 22 
 23     //再合并,放入中间数组保存
 24 
 25     int x=from;
 26     int y=mid+1;
 27     int g=1;
 28     while(x<=mid||y<=to) {
 29         /*这个边界是错误的,因为y>to之后继续在第二个if进行比较
 30         if(y>to||a[x]<=a[y]) {//
 31             t[g++]=a[x++];
 32         }
 33         if(x>mid||a[x]>a[y]) {
 34             t[g++]=a[y++];
 35         }
 36         */
 37 
 38         if(y>to||(x<=mid&&y<=to&&a[x]<=a[y])) {
 39             t[g++]=a[x++];
 40         }
 41         if(x>mid||(x<=mid&&y<=to&&a[x]>a[y])) {
 42             t[g++]=a[y++];
 43         }
 44 
 45     }
 46     //多余的不能忽略
 47     g--;
 48     x=from;
 49     for(int i=1; i<=g; i++)a[x++]=t[i];
 50 }
 51 //非递归实现
 52 void merge(int from,int mid,int to) {
 53     int x=from,y=mid+1;
 54     int g=1;
 55     while(x<=mid||y<=to) {
 56         if(y>to||(x<=mid&&y<=to&&a[x]<=a[y])) {
 57             t[g++]=a[x++];
 58         }
 59         if(x>mid||(x<=mid&&y<=to&&a[x]>a[y])) {
 60             t[g++]=a[y++];
 61         }
 62     }
 63     g--;
 64     x=from;
 65     for(int i=1; i<=g; i++)a[x++]=t[i];
 66 }
 67 /*
 68 
 69 5
 70 5 1 3 2 4
 71 6
 72 5 3 2 2 1 6
 73 7
 74 1 2 5 8 2 4 7
 75 8
 76 1 5 6 3 13 5 2 9
 77 
 78 */
 79 
 80 
 81 
 82 
 83 
 84 void m2(int n) {
 85     //以区间长度为变量,是活动的
 86     //区间长度从1-2-4-直到n
 87 
 88     for(int i=2; i<n; i*=2) {
 89         int j=1;
 90         while(j<=n) {
 91             int from=j;
 92             int mid=j+i-1;
 93             if(j+i*2-1<=n) {
 94                 int to=j+i*2-1;//记得-1
 95                 merge(from,mid,to);
 96             } else if(i+j-1<n) {
 97                 int to=n;
 98                 merge(from,mid,to);
 99             } else if(i+j-1>=n) {
100                 break;
101             }
102             j+=(i*2);
103             //    j+=(i*2+1);想太多搞错边界,
104             printf("i == %d
",i);
105 
106             for(int i=1; i<=n; i++)printf("%d ",a[i]);
107             puts("");
108 
109         }
110     }
111 }
112 
113 
114 
115 int main () {
116     int n;
117     while(~scanf("%d",&n)) {
118         for(int i=1; i<=n; i++)scanf("%d",&a[i]);
119 
120         m2(n);
121         //for(int i=1; i<=n; i++)printf("%d ",a[i]);
122         puts("");
123         //m2();
124         //    m3();
125 
126     }
127 
128 
129     return 0;
130 }
View Code

 

以上是关于分治思想01_排序算法_归并_各种写法和思路(待续)的主要内容,如果未能解决你的问题,请参考以下文章

算法_快速排序

算法_归并排序

算法设计与分析期中考试复习:代码和经典题目 分治二分动态规划(未完待续)

python代码实现归并排序(Merge Sort )

逆序对的两种求法

算法排序02——归并排序介绍及其在分治算法思想上与快排的区别(含归并代码)