前言
如果一个数组的左右区间都有序,我们可以使用一种方法(归并),使这个数组变得有序。
如下图:
过程也很简单,分别取左右区间中的最小元素,再把其中较小的元素放到临时数组中,例如第一次1和2被取出,1 被放到临时数组;第二次3和2被取出,2 被放到临时数组。重复此操作就能得到有序的临时数组,最后把临时数组拷贝到原数组中就好了。
这就是归并的思想,目前先依照上面过程写出归并方法的代码。注意不是归并排序的代码
#include <iostream>
using namespace std;
void mergeAdd0(int arr[], int left, int right,int *temp) //函数参数传入临时数组
{
int mid = (left + right) / 2 ; //区间的中间位置,[left,mid]为左区间,[mid+1,right]为右区间
int i = left; //指向左区间最小元素的位置
int j = mid + 1 ; //指向右区间最小元素的位置
int k = 0; //临时数组的下标
while (i <= mid && j <= right) //这个循环是取出元素的过程
{
if (arr[i] < arr[j])
{
temp[k++] = arr[i++];
}
else
{
temp[k++] = arr[j++];
}
}
//因为有一个区间的元素必定会先被取完,下面两个循环是将另一个区间的元素拿到临时数组
while (i <= mid)
{
temp[k++] = arr[i++];
}
while (j <= right)
{
temp[k++] = arr[j++];
}
//将temp数组拷贝到原数组
memcpy(arr + left, temp, sizeof(int) * (right - left + 1 ));
}
int main(void)
{
int arr[] = { 1 , 3, 5 ,7 ,2 ,4 ,6 ,8 };
int len = sizeof(arr) / sizeof(arr[0]);
int* temp = new int[len]; //和原数组大小一样的临时数组
mergeAdd0(arr, 0, len - 1,temp);
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
return 0;
}
看完了归并方法,有些人会问,你这个归并方法并不能适用于一般情况,一般数组都是无序的,哪里是你那样的数组:左半边有序,右半边也有序。
也是,下面请先看看归并排序的过程吧(也就是处理一般情况)。
具体步骤
接下来讲的就是排序本身的具体步骤了,对于下面待排序的数组
------170 189 187 186 169 173 162 170 168------
先以中间为界,均分为A、B两组(如果是奇数个,允许两组的个数相差一个)
A------170 189 187 186 169------
B------173 162 170 168------
如果A、B两组数据有序的话,那么就可以通过上面的归并方法让数组有序,可是此时两个数组都无序,此时可以使用分治法继续对A、B两组进行均分,以B组为例,可以分为B1、B2组如下:
B1------173 162------
B2------170 168------
均分后依旧无序,继续利用分治法细分,以B1组为例,可分为如下两组
B11------173------
B12------162------
数组细分到一个元素后,这时候就可以使用之前的归并法借助一个临时数组将B1组有序化!B2数组同理。
B1------162 173------
B2------168 170------
依次类推,B1、B2组有序后,可归并成有序的B组,A组同理。
A------169 170 186 187 189------
B------162 168 170 173------
最后将A、B两组通过归并法合并得到了有序的数组。
------162 168 169 170 170 173 186 187 189------
思路
上面这个过程,按我的理解是:首先归并方法可以使一个左区间有序、右区间有序的数组有序,那左区间怎么有序呢?
恭喜你,学会抢答了。那就是左区间的左区间有序并且左区间的右区间有序(使用归并方法),右区间同理。那左区间的左区间如何有序呢?......直到细分到区间内只有一个元素时,可以保证这个区间有序,从而得到一个个有序区间,最终使数组有序。
这个过程正如排序的名称归并,先递归,使大问题变成小问题,再合并,依次解决问题。
就如上过程结合归并方法可以得到以下代码:
//归并排序
void mergeSort(int arr[], int left, int right, int* temp)
{
if (left < right) //区间大于1个数,就要分而治之
{
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid+1, right, temp);
mergeAdd(arr, left, right, temp);
}
}
归并排序时间复杂度: n
全部代码以及测试图
#include <iostream>
#include <time.h>
using namespace std;
//归并方法
void mergeAdd(int arr[], int left, int right,int *temp)
{
int mid = (left + right) / 2 ; //区间的中间位置,[left,mid]为左区间,[mid+1,right]为右区间
int i = left; //指向左区间最小元素的位置
int j = mid + 1 ; //指向右区间最小元素的位置
int k = 0; //临时数组的下标
while (i <= mid && j <= right)
{
if (arr[i] < arr[j])
{
temp[k++] = arr[i++];
}
else
{
temp[k++] = arr[j++];
}
}
while (i <= mid)
{
temp[k++] = arr[i++];
}
while (j <= right)
{
temp[k++] = arr[j++];
}
//将temp数组拷贝到原数组
memcpy(arr + left, temp, sizeof(int) * (right - left + 1 ));
}
//归并排序
void mergeSort(int arr[], int left, int right, int* temp)
{
if (left < right) //区间大于1个数,就要分而治之
{
int mid = (left + right) / 2;
mergeSort(arr, left, mid, temp);
mergeSort(arr, mid+1, right, temp);
mergeAdd(arr, left, right, temp);
}
}
int main(void)
{
int arr[] = { 170 ,189,187 ,186 ,169 ,173 ,162 ,170 ,168 };
int len = sizeof(arr) / sizeof(arr[0]);
int* temp = new int[len]; //和原数组大小一样的临时数组
mergeSort(arr, 0, len - 1, temp);
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
return 0;
}