前言
关于快速排序和归并排序的思路,讲得好的那可多了,但是在我自己的学习过程中遇到的问题并不是弄不清思路,而是一敲代码就瞬间不会,看别人写好的吧,还该死的看不懂【哭泣】
所以!!出现了这篇保姆级讲解(妈妈再也不怕我看不懂了嘿嘿)
快速排序
思路
虽说思路讲得好的大有人在,但大家的思路还是有一点点小不同的。所以呢,思路这玩意儿该提还是要提一下的。
- 大致思路如下
-
在每一段小数组中,设最左边的数为基数(pivot)。令j从右往左遍历,找到第一个小于基数的数,停下。再令i从左遍历,找到第一个大于基数的数,停下。(注意一定要j先开始遍历哦)交换i和j指向的数,继续向中间遍历、寻找、交换,直到i和j相遇。
-
交换相遇处的数和最左边(基点),以基点为界,把左右分成两个小数组(不包括基点哦),再次传入函数,遍历、寻找、交换(递归思想就是这个啦)。直到小数组只剩下一个数,结束递归。
-
然后!数组就很神奇地,有序了!!
(其实我理解的快速排序,就是每一次函数的调用,都为该轮pivot找到正确位置的过程)
- 关于形参和实参问题,因为传入函数的是数组名(也就是数组首地址),所以在对数组进行改动的时候是直接在原数组地址上进行的,不用担心修改的时候,改的是形参而不是实参了。
代码部分
void quick_sort(int* a, int left, int right) {
int i = left, j = right;
//left和right其实就是框定了本轮要在原数组上改动的范围
int pivot = *(a + left); //基点
int t = 0; //t用于交换数
if (left >= right) return;//递归退出的条件,即当本轮要修改的只剩下一个数的时候退出
while (i < j) {
while (a[j] >= pivot && i<j) j--;
while (a[i] <= pivot && i<j) i++;
//到这个地方,有两种可能性
//第一种,i和j都找到了要交换的数(即i<j),那么交换i和j指向的数
//第二种,i和j在找到数之前相遇了,则要将pivot换到中间。这个时候,pivot的位置就是最后有序时,pivot这个数应该在的位置(即为pivot找到正确位置)
if (i < j) {
//如果是因为两个都找到才退出循环,那么交换两个数据
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
//把pivot移到正确位置
a[left] = a[i];
a[i] = pivot;
//把pivot两边的数,再次传入函数进行排序(不含pivot哦)
quick_sort(a, left, i - 1);
quick_sort(a, i + 1, right);
}
归并排序
思路
是一个结合了递归和分治思想的玩意儿。通过递归把一个待排序的数组分成只有一个数字的小数组,再通过类似合并有序数列的方法,一层一层的结合,最后合成一个有序数组。
在整个过程中,实际上并没有新数组的形成,所谓“分”“合成”,都是通过传递和改变left和right两个变量做到的,和快速排序类似,left和right都是框定了一个对原数组进行改动的范围。
- 先递归,一层一层地分成若干个小数组,递归的结束条件是小数组里只有一个元素。
- 然后将前一层的两个有序数组合并,存放至临时数组中,待两个有序数组合并完成,将其按顺序复制到原数组的对应位置,这样原数组的对应位置上的元素就有序了。
- 通过每一次调用修改原数组不同范围上的数,范围逐步扩大,最后整个数组有序。
(如果觉得我思路讲不明白,可以移步这个视频:排序算法:归并排序【图解+代码】)
代码部分
void merge_sort(int a[],int left,int right){
//这个函数其实就是一个中转站
//在分的时候,接收上一层的数,对半分了往下传
//在合并过程,接收排序完成的信息然后关闭本层
//至于原数组怎么修改的,就是merge函数建立临时数组,排好序后,直接将排好序的数覆盖到原数组对应位置上
if(left<right){//如果只剩一个数,就不用再分,也不用接收,直接把分好的信息往回传
int mid = (left + right) / 2;//为了砍半做标记
//这两个是分的过程
merge_sort(a,left, mid);
merge_sort(a, mid + 1, right);
//接收下层治完成的信息,关闭本层
merge(a, left, right, mid);
}
}
void merge(int a[],int left,int right,int mid) {
//left 和 right是左右边界,指在原数组的left到right位进行排序覆盖
//mid是两个待排序数组的分界线,从left和mid右边第一个开始合并有序数组
int s[100];//s是临时数组
int i = left, j = mid + 1;//i,j分别指向代插数组的第一个
int sor = left;//sor是指向临时数组待放入的位置
while (i <= mid && j <= right) {
if (a[i] < a[j]) {
s[sor++] = a[i++];
}
else {
s[sor++] = a[j++];
}
}
while (i <= mid) s[sor++] = a[i++];
while (j <= right) s[sor++] = a[j++];
//将合并完成的临时数组里的数,复制到原数组里面
sor = left;
while (sor <= right) {
a[sor] = s[sor];
sor++;
}
}