归并排序算法

前言

排序算法有很多,冒泡排序,选择排序,插入排序,希尔排序,快速排序,等等.归并排序算是一种排序速度很快的算法.但是因为它额外占用内存空间,通常被人们诟病.

我在单片机开发中,通常遇不到排序的问题,但是整理资料的时候,发觉归并算法或许具有更深刻的意义.在网上一番搜索,并未得到关于归并算法的有用的信息,决定自己研究一下,于是,有了这篇归并排序算法的学习.

归并排序算法,实际上是两个算法,归并+排序.下面我会从归并,排序两个角度来讨论这一算法.

经历了一番摔打后,博主我认怂了.

凡事,先易后难.

否则,哎,一言难尽啊,自己被困难的"怪物"摔打的鼻青脸肿.

也算是一次成长.这种成长,对于年轻人很有必要.如果你还没有意识到,或者说,你还想挑战天有多高,地有多厚;想探究宇宙之奥秘,我不会拦你,我会赠予你八个字:

"知难而退,先易后难"

下面是正题.

1.排序算法

排序,是指做个比较,它需要至少两个对象,还要有大小之分,依据大小,定一个序出来.

序,这里就选个递增序,即小的在前,大的在后.符号定义 :

a<b<c<d....

两个对象,这里就选两个数组,方便引出后面的归并排序.

int left[5]
int right[5]

排序思路:

1.left[index_left]与right[index_right]比较
2.将比较后的结果放在新的数组中temp[10].

while(left_index < left_len && right_index < right_len)
{
    if (left[left_index] < right[right_index])
    {
        temp[temp_index++] = left[left_index++];
        //对于第left_index而言,已经被安排了,我们可以pass掉,即安排下一个.
        //而第right_index的数是比较大的,我们还没有安排好,需要再进行比对.
    }
    else
    {
        temp[temp_index] = right[right_index++];
    }
}
//上面的比对结果是,一定有一个比对完了, 且余下的数一定是大于已经排好的序列.
//于是,只需要把剩下的拼接即可.
while(left_index < left_len)
{
    temp[temp_index++] = left[left_index++];
}
while(right_index < right_len)
{
    temp[temp_index++] = right[right_index++];
}

上面是博主修改后的代码,其实博主第一次写的时候是这个样子,有bug,也给放出来,以供大家拷打.

while((left_index < left_len) && (right_index < right_len))
{
    if (left[left_index] < right[right_index])//左边的数 < 右边的数
    {
        //存放左右之中较小的数
        temp[temp_index] = left[left_index];
        temp_index++;//存放左右之中较大的数
        temp[temp_index] = right[right_index];
    }
    else
    {
        temp[temp_index] = right[left_index];
        temp_index++;//存放左右之中较大的数
        temp[temp_index] = left[left_index];
    }
    //进行下一轮,左右数的比对
    left_index++;
    right_index++;
}

好了,解释一下第二个代码的问题.

//这种比对存在问题.即必须要求一边的数列恰巧小于另一边数列的一个index位置.
//如:left:0,2,4,6,8;right:1,3,5,7,9,11,这样的数列才能被正确排序
//如:left:0,4,8,12,14;right:1,3,5,7,9,11;这样的数列则不能正确排序.
//修改算法,我们需要一个数列中的每一项与另一个数列的每一项做比较来确认它在两个数列中的位置

害,懒得写了,直接把代码里的注释copy过来吧..


2.归并算法

归并,似乎蕴含着递归与合并之意.先讨论合并吧..

2.1.合并算法

合并就需要有多个对象存在,把它们合并在一起,像是

1+1+1+1+1=5

五个1的合并就成了5.但是,合并的方式存在很多种,这就是困扰博主的地方.归并排序使用的是一种二分法逆向的"二合"法.
所以,还是得弄明白二分法的规则,也可以叫分组算法.

//数组长为30
int arraylen = 30;
//分组算法,按偶数分组,按奇数分组.----不好,还得考虑奇偶性
int left[?];
int right[?];
//left,right的长度呢?
//偶数
int left_len = arraylen/2;
int right_len = arraylen/2;
//奇数
int left_len = arraylen/2;
int right_len = arraylen/2+1;
//分组算法,左,右组/上,下组/前,后组...----好,不考虑奇偶性
int left[?];
int right[?];
//left,right的长度呢?
int left_len = arraylen/2;
int right_len = arraylen-left_len;
///我只考虑到上面这部分
//下面这部分,是参考菜鸟教程想出来的.
//更一般的呢?如果了有分组长度,我就可以不依赖arraylen了!
int seg_length = 15;
//left,right的长度呢?
int left_len = seg_length;
int right_len = seg_length+seg_length;

好了有了分组算法,那就来写二合算法吧.

2.2."二合"算法

"二合"算法的基本思想

1.确认两个分组的长度
2.合并两个分组....

int seg_length = 1;//从长度为1的分组开始合并
while(seg_length < arraylen)//结束条件是合并后的数据分组长度超过了数据总长度
{
    int seg_index = 0;//从第0个分组开始
    while(seg_index <= arraylen/(seg_length*2))//退出条件是,合并完所有分组了.
    {
        seg_index+=2//两个分组合并!
    }
    seg_length += seg_length//两个分组长度合并!
}

3.合并排序算法的实现

int arraylen = 30
int a[30] = {/*写入自己的随机数吧*/};
int *b = a;
int c[30];
//数据总长度:arraylen
//数据分组的长度:seg_length
//数据分组的索引:seg_start ---- 参考了菜鸟教程
int seg_length = 1;//从1个长度的数据分组开始合并
while(seg_length < arraylen)//结束条件是合并后的数据分组长度超过了数据总长度
{
    //我们还需要遍历m+1个数据片段(分组),(m+1)*seg_length<= arraylen,但是这样判断总会少一节
    //for(int seg_index = 0; seg_index*seg_length<arraylen; seg_index++)
    //于是可以用总长/分组长得出分组数,再将分组数进行两两归并&排序
    int seg_start = 0;
    int c_index = 0;
    //每次合并两个数据分组
    for (seg_start = 0; seg_start < arraylen; seg_start+=seg_length*2)
    {
        //本轮次的start位置
        int index_seg_start =  seg_start;
        int index_seg_mid = seg_start + seg_length;
        int index_seg_end = seg_start + seg_length + seg_length;
        //不能超过数组的最大索引位置
        index_seg_mid = index_seg_mid < arraylen ? index_seg_mid : arraylen;
        index_seg_end = index_seg_end < arraylen ? index_seg_end : arraylen;
        //左右比对需要两个索引
        left_index = index_seg_start;
        right_index = index_seg_mid;

        //剩下的就是比对了
        while ((left_index < index_seg_mid) && (right_index < index_seg_end))
        {
            if (b[left_index] < b[right_index])
            {
                c[c_index++] = b[left_index++];
                //对于第left_index而言,已经被安排了,我们可以pass掉,即安排下一个.
                //而第right_index的数是比较大的,我们还没有安排好,需要再进行比对.
            }
            else
            {
                c[c_index++] = b[right_index++];
            }
        }
        //上面的比对结果是,一定有一个比对完了, 且余下的数一定是大于已经排好的序列.
        //于是,只需要把剩下的拼接即可.
        while (left_index < index_seg_mid)
        {
            c[c_index++] = b[left_index++];
        }
        while (right_index < index_seg_end)
        {
            c[c_index++] = b[right_index++];
        }

        seg_start += seg_length + seg_length;//合并两个数组
    }
    //c指向的数组是经过排序的,b是未排序的,我们需要交换一下,以便下一轮排序.
    int  *temp;
    temp = b;
    b = c;
    c = temp;
    //将b设置为排好序的
    seg_length += seg_length;//数组的合并长度翻倍,这里就体现了"二合"
    //不要纠结是+上自己,还是*2.
}
//排序结束后,还得确认排好的序在不在a中.
//b!=a时,排好的序在b中存放着
if(b != a)
{
    for(int i = 0; i<arraylen; i++)
    {
        c[i] = b[i];//需要把b中的数搬运到a(c)中,然后交换c=b,释放c.
    }
    c = b;
}
//此时,a中的数据就排好了.
for(int i=0; i<arraylen; i++)
{
    printf("%d\t", a[i]);
}
printf("\n");

上面这个也是修改版本的,原版本是用seg_index,而不是seg_start.不过分组的索引和分组的起始位置是可以相互转化的.这里就没有必要纠结.

这里贴上最后a,b,c三个指针之间的关系图,方便理解这个交换过程.
我觉得,理解的关键点是,无论b,c如何交换,它们都只存在两种状态.----a和b指向同一个数组;a和c指向同一个数组.
img

是不是比菜鸟教程的版本要长很多,其实里面有很多注释,这些注释,都是算法里的坑,或者是关键点.

3.总结

如果要是用归并算法,还是上网百度一个吧,我没有再此提供api.

生活中,很多事物都是需要分类的,而分类的过程会产生大量的类....如何把这些类给进行再次分类(我理解为归并)是很麻烦,也痛苦的过程.

比如写的笔记过一段时间就需要分类整理,比较好的做法是为每一篇文章,进行分类,即贴标签,然后,对这些标签进行汇总,汇总是很麻烦的,因为没有一个统一的标准.于是,我想到了归并算法,希望从归并算法中获取些许灵感.结果,也是异想天开了,显然归并算法并不能满足我的需求.

人与计算机的区别也就在于这里吧,人可以分出许多复杂,重叠的类,即一个事物有多个标签...这种重叠就像是量子叠加态...

对于量子叠加态的归并算法目前还没有,或者说人本身就是一个量子叠加态的归并算法.我不是一次这么认为了----人就是个机器....

好了,既然知道了归并排序算法是不能满足我的需求.那我只好去学习量子算法了,希望可以从量子归并排序算法中找到自己的答案,那时,也许机器真的具有智能也说不定.

参考

归并排序算法--菜鸟教程

posted @ 2023-07-08 19:23  当最后一片树叶落下  阅读(12)  评论(0编辑  收藏  举报