归并排序
归并排序是采用的分治法的一个非常典型的应用,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
既然是排序,通俗来讲就是把数字序列变为有序状态,但是传统的排序方式,像冒泡排序啊,插入排序啊什么的,耗时太高,即使是快速排序,最坏的情况时间复杂度也到了O(n^2),而归并排序,则比快排最好的情况O(n*log n)要好。
比如我有两个有序数列,我怎么把他们合并起来并且排序好呢,直接合并再排序太麻烦了,我们可以这样;
例如:
arr11 3 5 7 9
arr22 4 6 8 10
两个有序数列,我们可以新建一个数组(qrr),然后我们设两个指针分别指向两个数组的开头,通过比较大小把小的放到qrr里面,(当然如果想从大到小也行) ,上面两个数组 其中1 < 2 ,把1放到qrr里面之后我们可以让arr1的指针后移,指向3 然后再比较3 和 2 的大小 ,这时2 < 3, arr2的指针后移,不断进行下去,然后你就会发现qrr 1 2 3 4 5 6 7 8 9
arr1 已经走完了 , arr2还有剩余,这时我们只需要把arr2剩余的添加的qrr里面就行了。
这样我们就实现了合并 + 排序, 时间复杂的为O(n)
但是为什么说用到了分治的思想呢,我们知道,分治就是把一件大的事情,不断分解成许多小问题来解决,(其实类似递归对吧) ,如果合并这两个序列是无序的呢? 事情就复杂了。这是分治就排上用场了,算法算法自然可以有很多变化,前面我们说了合并两个有序数列,这是一种应用,从归并排序分离出来的一些而已,如果我们要对一个数列排序的话,运用归并的思想应该怎么做呢?
不妨这样想,我们可以把数列分成两个数列,但又有问题了,分成的两个数列仍然不是有序的怎么办,是不是想到了什么… 递归对吧,虽然分成的两个数列都不是有序的,但我们可以继续分下去,一直分到最后每个区间只剩一个元素,这样我们就可以认为他是有序的,(一个元素肯定是有序的)
例如:
2 1 3 5 0 7 6 9
对应下标
0 1 2 3 4 5 6 7
这样一个无序数列,我们可以把他分成两半,
左边: 2 1 3 5 右边: 0 7 6 9
然后不是有序的没关系 我们递归下去,继续分它,
一直分到每个区间都是一个元素的时候
2 1 3 5 0 7 6 9
然后我们用刚开始的办法合并,
2 1 合并成一个区间
3 5 合并成一个区间
0 7合并成一个区间
6 9合并成一个区间
然后4个区间合并成两个
两个最后合并成一个
这样我们就完成了排序
int arr[100];
int qrr[100];
void memeryarray(int first, int mid, int end,)//分别对应开始,中间,末尾
{
int i=first, j=mid+1, k=0; //对应下标
while(i <= mid && j <= end) //分成的两个区间
{
if(arr[i] > arr[j]) //如果左边的区间下标为i的值 > 右边区间下标为j的值
{
qrr[k++]=arr[j++]; //放到新的数组里面
}
else //如果左边的区间下标为i的值 < 右边区间下标为j的值
qrr[k++]=arr[i++];
}
while(i <= mid) qrr[k++]=arr[i++];
while(j <= end) qrr[k++]=arr[j++]; //这两句分别代表某一个区间的值用完了,剩下的直接排在后面
for(i = 0; i < k; ++i)
arr[first+i] = qrr[i]; //最后区间合并完再放回去
}
void mergesort(int first, int end) //数组的开头和最后
{
if(first < end) //区分到最后区间内只剩一个值的话 就停止递归
{
int mid=(first + end)/2; //分成两个区间
mergesort(first,mid); //左边
mergesort(mid+1,end); //右边
memeryarray(first,mid,end); //区间合并
}
}
至此我们的归并排序就完成了。