[闭目洞察算法系列之一]快速排序

这是第一篇关于算法的博客, 我本人对算法没什么深刻见解, 此处只是对别人博客的再整理, 用自己理解的方式进行表述一遍, 一方面加深印象, 另一方面做知识沉淀,供他日食用。 废话到此为止, 下面是正题

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


快速排序思想核心分为两部分:

        一是分治, 即选择一个值X作为标杆, 然后对每个子数组进行大致排序, 使得该标杆X左边的值都小于或等于X,, 右边的值都大于或等于X

       注意, 左边的值只需小于X即可, 至于这些值彼此之间不一定是有序的。例如(2, 3, 6, 489, 77, 34)就是正确分治后的结果

        二是递归, 递归的目的是对分治的数组再此进行分治, 需要分别对标杆X左右两边的集合分别进行递归,从而使得每一个子的集合均满足左边小于右边, 直至该集合只有一个 

        (分治函数一般命名为partition, 其中的标杆X一般命名为pivot(轴心))


上述两个阶段中, 难点在于分治阶段, 所以我们接下来着重分析一下分治过程, 举个实际的例子说明问题,借鉴其他博客, 我们可以把该过程想象成一个挖坑填坑的过程。

1. 首先我们准备一组数据 array[ ], 选择第一个元素11作为pivot值(标杆),本轮分治想要达到的效果是11位于中间某个位置, 左边的值都小于11, 右边的都大于11


0

1

2

 3


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


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


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


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


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


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


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);
	}
}

点赞