关闭

算法学习 - 快速排序相关

标签: 快速排序算法
368人阅读 评论(0) 收藏 举报
分类:


1. 划分

快速排序中最重要的步骤就是划分,即选择一个元素(很多地方称为枢纽元素),将序列划分成两部分,比枢纽元素大的放在枢纽元素的右边,比枢纽元素小的放在枢纽元素的左边。

划分函数的思想:

1. 选择一个枢纽元素(以选择序列的第一个元素为例),暂存到临时变量中

2. 从序列的右边向左查找,找到一个比枢纽元素小的值。若找到,则将这个值填入到枢纽元素的位置

3. 从序列的左边向右查找,找到一个比枢纽元素大的值。若找到,则将这个值填入到step 2中那个比枢纽元素小的元素的位置。

4. 重复step2, step3,直到向右查找的“指针”(索引)和向左查找的“指针”(索引)碰到一起。表明一次划分就完成了。

按照上述思想实现代码如下:

int partition(int *arr, int l, int r)
{
	int i = l, j = r;
	int key = arr[l];									//枢纽元素
	while (i < j)
	{
		while (i < j && arr[j] >= key) j--;				//从右往左找到一个比枢纽小的
		if (i < j)
		{
			arr[i] = arr[j];
			i++;
		}
		while (i < j && arr[i] < key) i++;				//从右往左找到一个比枢纽大的
		if (i < j)
		{
			arr[j] = arr[i];
			j--;
		}
	}
	arr[i] = key;
	return i;
}


2. 递归快排

递归的方式是最常见的方式,因为递归的写法代码简短。

算法思想:对元素进行划分,划分一次之后分别对两个子序列进行划分。直到子序列的元素个数变成1为止。

void QuickSort(int *arr, int l, int r)
{
	if (l < r)
	{
		int par = partition(arr, l, r);
		QuickSort(arr, l, par - 1);
		QuickSort(arr, par + 1, r);
	}
}


3. 使用栈作为辅助空间的快排

递归的过程其实就可以理解为一个栈,因为递归是依靠函数调用压栈的方式实现的。所以我们可以手动将中间过程保留,来进行快排。

算法思想:

1. 对序列进行一次划分,划分后的两个子序列头尾都压入栈中。

2. 在栈非空的情况下,从栈中取出两个元素(子序列的头尾位置)。对子序列进行划分。

3. 同样,对子序列进行划分的时候也将划分后的序列头尾压入栈中。

4. 重复step2 step3,知道栈为空为止。

void QuickSort2(int *arr, int l, int r)
{
	stack<int> tmp;								//临时栈
	if (l < r)									//
	{
		int par = partition(arr, l, r);			//

		if (l < par - 1)						//
		{
			tmp.push(l);						//
			tmp.push(par - 1);
		}
		if (par + 1 < r)
		{
			tmp.push(par + 1);
			tmp.push(r);
		}

		while (!tmp.empty())
		{
			r = tmp.top(); tmp.pop();
			l = tmp.top(); tmp.pop();

			par = partition(arr, l, r);

			if (l < par - 1)
			{
				tmp.push(l); tmp.push(par - 1);
			}

			if (par + 1 < r)
			{
				tmp.push(par + 1); tmp.push(r);
			}
		}
	}
}


4. 使用队列作为辅助空间的快排

这个实现其实和用栈作为辅助空间的思想是相同的,无非就是将存储中间结果的辅助空间换成了队列。

void QuickSort3(int *arr, int l, int r)
{
	queue<int> tmp;								//临时队列
	if (l < r)									
	{
		int mid = partition(arr, l, r);			//一次划分

		if (l < mid - 1)						//划分中间结果入队列
		{
			tmp.push(l);						
			tmp.push(mid - 1);
		}
		if (mid + 1 < r)
		{
			tmp.push(mid + 1);
			tmp.push(r);
		}

		while (!tmp.empty())
		{
			l = tmp.front(); 
			tmp.pop();
			r = tmp.front();
			tmp.pop();

			mid = partition(arr, l, r);

			if (l < mid - 1)
			{
				tmp.push(l); tmp.push(mid - 1);
			}

			if (mid + 1 < r)
			{
				tmp.push(mid + 1);
				tmp.push(r);
			}
		}
	}
}


5. 快排什么时候适合使用,什么时候不适合使用?

快排是基于划分的,最好的情况就是每次划分恰好都是在序列的中间,这样算法的复杂度就是O(n*lgn)。所以说枢纽元素的选择会直接影像到排序的效率。

随机序列快排:枢纽元素的选择也将是随机的,这时候的划分不会导致出现最差的结果。

已序序列快排:假设采用枢纽元素总是选择第一个元素的方式,很明显,每次划分都只能排序一个元素,这时候排序的效率就是最差的(也就是严重的不平衡树)。此时效率为O(n^2)。

那么对于这种已序的序列什么排序比较适合呢?插入排序!!对于插入排序,若序列是已序的表明序列不需要作很多的移动,此时效率为O(n)。


这也就可以联系到STL中sort函数的实现:

STL中的sort(),在数据量大时,采用quicksort,分段递归排序;一旦分段后的数量小于某个门限值,改用Insertion sort,避免quicksort深度递归带来的过大的额外负担,如果递归层次过深,还会改用heapsort。


1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:197188次
    • 积分:3238
    • 等级:
    • 排名:第11671名
    • 原创:108篇
    • 转载:10篇
    • 译文:0篇
    • 评论:99条
    博客专栏
    最新评论