这是第一篇关于算法的博客, 我本人对算法没什么深刻见解, 此处只是对别人博客的再整理, 用自己理解的方式进行表述一遍, 一方面加深印象, 另一方面做知识沉淀,供他日食用。 废话到此为止, 下面是正题
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
快速排序思想核心分为两部分:
一是分治, 即选择一个值X作为标杆, 然后对每个子数组进行大致排序, 使得该标杆X左边的值都小于或等于X,, 右边的值都大于或等于X
注意, 左边的值只需小于X即可, 至于这些值彼此之间不一定是有序的。例如(2, 3, 6, 4,8,9, 77, 34)就是正确分治后的结果
二是递归, 递归的目的是对分治的数组再此进行分治, 需要分别对标杆X左右两边的集合分别进行递归,从而使得每一个子的集合均满足左边小于右边, 直至该集合只有一个
(分治函数一般命名为partition, 其中的标杆X一般命名为pivot(轴心))
上述两个阶段中, 难点在于分治阶段, 所以我们接下来着重分析一下分治过程, 举个实际的例子说明问题,借鉴其他博客, 我们可以把该过程想象成一个挖坑填坑的过程。
1. 首先我们准备一组数据 array[ ], 选择第一个元素11作为pivot值(标杆),本轮分治想要达到的效果是11位于中间某个位置, 左边的值都小于11, 右边的都大于11
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
11 | 22 | 32 | 4 | 2 | 66 | 10 | 55 | 69 |
2. 我们需要几个变量来辅助, int left = start(此处为0), int right = end(此处为8), int pivot = start, int pivot_value = array[pivot], 所谓挖坑是指将该位置的值保存起来, 然后将新找到满足条件的值(v1)填到该位置, 从而在v1原来的位置上形成了一个新坑, 等待新值填入,如此循环下去,直至结束
那么, 首先我们把11存放到pivot_value中了, 此时在array[0]处形成了一个坑。想要完成分治过程,我们肯定是要遍历比较数组中的每一个元素的, 具体顺序是, 先由后向前, 再由前向后与pivot_value比较,然后再次交替重复该过程,向中间逼近,直至left 和right抵达相同位置(pos),此处就是pivot_value最终填充的位置, 而该pos就是要返回的位置,供递归使用
下面我们结合实例走一遍,
a. 由后向前: 期初hight = 8, pivot_value = 11,我们现在要找一个比11 小的值填到这个array[0]位置,11 比 69小, hight–, 11 比55小, hight–, 10比11小,满足填坑条件, 把10填到array[0]处,并且low++, low =1, 从而在array[6]处产生一个新坑(红色为待填的坑, 蓝色为填完的坑)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 22 | 32 | 4 | 2 | 66 | 10 | 55 | 69 |
b. 由前向后, 期初low = 0, 现在low = 1,我们现在要找比pivot_value大的值填到新坑array[6]中,22大于11, 满足填坑条件,填进去,形成新坑array[1], high–, high = 5
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 22 | 32 | 4 | 2 | 66 | 22 | 55 | 69 |
c. 现在又回到由后向前的环节,同样的, high = 5, 然后与pivot_value比较, 66 大于11, high–, 2小于11, 满足填坑条件,填到array[1]中,形成新坑array[4], low ++后等于2
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 2 | 32 | 4 | 2 | 66 | 22 | 55 | 69 |
d. 现在是由前向后, low = 2, 32 大于11, 再此填坑, 形成新坑array[2], high– 后等于3
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 2 | 32 | 4 | 32 | 66 | 22 | 55 | 69 |
e. 现在由后向前,high = 3, 4小与11, 满足条件, 挖出来, 填到array[2]中, 形成新坑, low++后等于3
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 2 | 4 | 4 | 32 | 66 | 22 | 55 | 69 |
f. 此时, 由前向后,low = 3, 注意,此时low和high值相同,那么,也就是说所有的元素已经全部遍历了,循环结束,此时位置3就是最终pivot的位置,将pivot_value填进去即可
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 2 | 4 | 11 | 32 | 66 | 22 | 55 | 69 |
至此,本轮分治结束,返回位置3
我们可以看到分治后的结果, 所有11左边的数字都小于11, 右边的都大于11
对挖坑填数进行总结
1.low = left; high = right; 将基准数挖出形成第一个坑array[low]。
2.high–由后向前找比它小的数,找到后挖出此数填前一个坑array[low]中。
3.high++由前向后找比它大的数,找到后也挖出此数填到前一个坑array[high]中。
4.再重复执行2,3二步,直到low==high,将基准数填入array[low]中。
实现代码如下:
分治代码:
int partition(int *array, int start, int end)
{
if (array == nullptr || start < 0 || end < start)
{
return -1;
}
int pivot = start;
int pivot_value = array[pivot];
int low = start;
int high = end;
while(low < high)
{
while(low < high && array[high] >= pivot_value)
{
high--;
}
if (low < high)
{
array[low++] = array[high];
}
while (low < high && array[low] <= pivot_value)
{
low++;
}
if (low < high)
{
array[high--] = array[low];
}
}
array[low] = pivot_value;
return low;
}
递归代码:
void quickSort(int *array, int start, int end)
{
if (start < end)
{
int pivot = partition(array, start, end);
quickSort(array, start, pivot -1);
quickSort(array, pivot + 1, end);
}
}