数据结构与算法_归并排序_1

目录:

0.提出

1. 基本思想

2. 执行流程

附录:


0.提出

1945年由约翰·冯·诺伊曼(John von Neumann)首次提出。

1. 基本思想

归并排序是用分治思想,分治模式在每一层递归上有三个步骤:

分解(Divide):将n个元素分成个含n/2个元素的子序列。

解决(Conquer):用合并排序法对两个子序列递归的排序。

合并(Combine):合并两个已排序的子序列已得到排序结果。

 

2. 执行流程

1.不断地将当前序列平均分割成2个子序列

直到不能再分割(序列中只剩1个元素)

2.不断地将2个子序列合并成一个有序序列

直到最终只剩下1个有序序列

 

此算法浅显易懂,以一个数组为例,不断的将一个数组进行分割,直至不可分。此时再对其进行归并,排序完成。

以下列数据为例,26,5,77,1,61,11,59,15,48,19

1 把数组进行分割直至不可分。该数组有10个元素,我们不断的从中间切割。

2 再重新进行归并,如26和5进行一次比较,小的5放前面(升序)

  至此,不断重复操作2,最后排序就成功了。

 

3. 代码如何实现?

【C++】

在实现代码之前,我们需要一个重要的问题,怎么切割成两个子序列呢。

我们应该从中间切。此外,因为是不断的从中间切割,你联想到了什么?没错,递归调用!那这个递归调用的边界退出条件是什么呢?是子序列不可分割!也就是当你切到只剩下1个元素的时候,就退出。

那现在开始动手吧,先定义一个MergeSort函数,那传的参数要有哪些呢?首先,一个数组和数组长度这是必须的。此外,因为是递归调用,所以是不是每次都要传首元素的下表和尾元素的下标呢?是的,没错,于是很轻松的写下了下面这些代码。

void MergeSort(int* array, int length) {

         int* tmp = new int[length]();  // 申请堆空间内存

         MergeSort(array, tmp, 0, length-1);

         delete[] tmp;  // 销毁临时数组内存

}

void MergeSort(int* array, int* tmp, int begin, int end) {

         // 左闭右闭 的设计模式

         // [0,mid] [mid+1,end]

         if (end - begin < 1) { // 边界条件

                 return;

         }

         // 递归分解再合并

         int mid = (begin + end) >> 1;   // 右移位运算符 相比 除法运算符,运行效率更高

         MergeSort(array, tmp, begin, mid);

         MergeSort(array, tmp, mid + 1, end);

         Merge(array, tmp, begin, mid, end);  // 合并,看后面

         copy(array, tmp, begin, end); // 拷贝tmp数组的元素到array,拷贝范围是begin到end

}

在这里,我申请了一个临时数组来存放新排序,之后再把排好序的数组拷贝到原先的数组,再把临时数组销毁掉。此外,这里还写到左闭右闭的设计模式,大家也许会有些困惑,请看下去。

至此,分割的代码就写完了,那么如何合并呢?

/**定义三个指针

* i 指向 原数组 左半部分 初始位置

* j 指向 原数组 右半部分 初始位置

* k 指向 新数组的 初始位置

*/

void Merge(int* array, int* tmp, int begin, int mid ,int end) {

         int i = begin, j = mid + 1, k = begin;

         // 不断的把小的放入新数组,如若左半或者右半完成,就退出循环

         while (i <= mid && j <= end) {

                 if (array[i] <= array[j]) { // 右半部比较大

                          tmp[k++] = array[i++];  // 小的放新数组,并且指针右移

                 }

                 else {

                          tmp[k++] = array[j++];

                 }

         }

         // 能来到这里表示此时已经有一边(左半OR右半)先挪动完成了

         if (i > mid) { // 如果i>mid,表明左半部分已经搬完,现在只要搬右半的

                 for (int q = j; q <= end; q++) {

                          tmp[k++] = array[q];

                 }

         }

         else {

                 for (int q = i; q <= mid; q++) {

                          tmp[k++] = array[q];

                 }

                

         }

}

没错,如上左闭右闭的设计模式,可以规范我们对边界上元素的处理逻辑,从0到mid,和mid+1到end,鲜明的区分了左右两个部分。

 

此外,附上另外一种归并排序的解法,这种就不用把排序好的元素放在新数组里面了,直接在原来的数组上进行排序,但是要先备份左半的元素出来。但是这次我们采用了左闭右开的设计模式。[0,mid), [mid,end)

这样子,就不用判断mid是不是要+1了。

void MergeSort(int* array, int end) {

         int* list = new int[end / 2];   // 备用的数组,大小是数组长度一般

         MergeSort(array, list, 0, end); 

         delete[] list;

}



void MergeSort(int* array, int* dest, int begin, int end) {

         if (end - begin < 2) return; 

// 小于2个元素 退出,这里之所以是<2而不是<1,是因为end在数组最后位元素的后一位,采取的是左闭右开的设计,即array[10],数组index从0-9,但是传了index = 10

         int mid = (begin + end) >> 1;  // >>1运算符表示除2

         MergeSort(array, dest, begin, mid);

         MergeSort(array, dest, mid, end);

         Merge(array, dest, begin, mid, end);

}



void Merge(int* array, int* dest, int begin, int mid, int end) {

         int li = 0, le = mid - begin;  //li为左半的首位元素,le为左半的最后一个元素与左半开始之间元素的个数

         int ri = mid, re = end;  //ri为右半的首位元素,re为右半的最后一个元素

         int ai = begin;   // ai指向原数组开始位置的序号

         for (int i = li; i < le; i++) {

                 dest[i] = array[begin + i];  // 备份左边数组

         }

         while (li < le) {  // 如果左边的还没搬完

                 if (ri < re && cmp(array[ri], dest[li]) < 0) {  // 左边 > 右边

                          array[ai++] = array[ri++];  // 右边的元素放到左边数组去

                 }   // 能来到下边,表示两种情况

                 else {  // 1. 左边 < 右边 

                              // 2.右边已经搬完

                          array[ai++] = dest[li++];  // 把备份的左边元素放入原数组处

                 }

         }

}

附录:

void copy(int* a, int* b, int left, int right) {

         for (int i = left; i <= right; i++)

                 a[i] = b[i];

}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值