数据结构--经典排序之快速排序(超详细!!)

快速排序

快速排序(Quick Sort)是一种高效的排序算法,由英国计算机科学家霍尔(C.A.R. Hoare)在1960年提出。它的基本思想是,通过一次排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

算法步骤
选择基准值(pivot):
从数列中挑出一个元素,称为“基准”(pivot)。通常选择第一个元素、最后一个元素、中间元素或者随机选择一个元素作为基准值。
分区(Partition)操作:
重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区操作(Partition operation)。注意,在分区操作中,并不要求将小于基准的元素按照某种顺序排列,同样也不要求将大于基准的元素按照某种顺序排列。
递归排序子序列
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
算法示例
假设有一个无序序列如下:[6, 2, 8, 5, 1, 4, 9, 3, 7]。

选择第一个元素6作为基准值。
进行分区操作,将小于6的元素放在左边,大于6的元素放在右边。分区后的序列可能如下:
[3, 2, 1, 4, 5, 6, 9, 8, 7]。
对基准值左边和右边的子序列分别进行快速排序。比如先对左边的子序列[3, 2, 1, 4, 5]进行快速排序,选择3为基准值,分区后得到[2, 1, 3, 4, 5],然后再对左右两边的子序列进行排序,直到整个序列有序。同样,对右边的子序列也进行类似的排序操作。
算法性能
时间复杂度:快速排序的平均时间复杂度为O(n log n),最坏情况下的时间复杂度为O(n^2)(当输入序列已经有序或逆序时)。但通过随机化选择基准值,可以期望得到O(n log n)的平均性能。
空间复杂度:快速排序的空间复杂度主要取决于递归调用的深度。平均情况下,递归树的深度为O(log n),因此空间复杂度为O(log n)。但在最坏情况下,递归树的深度可能达到O(n),导致空间复杂度为O(n)。
优化技巧
随机化基准值选择:为了避免最坏情况的发生,可以在每次分区前随机选择一个元素作为基准值。
三数取中法:在选择基准值时,可以考虑选择头、中、尾三个元素中的中位数作为基准值,以提高排序的稳定性。
插入排序处理小数组:对于非常小的数组(如长度小于10),插入排序可能比快速排序更快。因此,在递归到很小数组时,可以切换到插入排序。
迭代法实现:虽然快速排序通常使用递归实现,但也可以通过迭代法(使用栈)来实现,以减少递归调用的开销。

代码实现

// 快速排序函数,传入一个整数数组a,以及要排序部分的左右边界left和right  
void QuickSort1(int* a, int left, int right) {  
    // 如果左边界大于等于右边界,说明排序区间无效或只有一个元素,直接返回  
	if (left >= right) {  
		return;  
	}  
  
 // 初始化变量,begin和end用于记录整个排序区间的边界,因为下次用的时候left和right的值会被改变
    //keyi用于记录基准元素的位置  
	int begin = left, end = right;  
	int keyi = left;  
  
    // 开始进行分区操作,将数组分为小于基准和大于基准的两部分  
	while (left < right) {  
        // 从右向左查找,找到第一个小于基准的元素  
		while (left < right && a[right] >= a[keyi]) {  
			right--;  
		}  
        // 从左向右查找,找到第一个大于基准的元素  
		while (left < right && a[left] < a[keyi]) {  
			left++;  
		}  
        // 交换找到的两个元素,使得小于基准的元素在基准的左边,大于基准的元素在基准的右边  
		Swap(&a[right], &a[left]);  
	}  
    // 将基准元素交换到它应该在的位置,此时基准左边的元素都小于基准,右边的元素都大于基准  
	Swap(&a[left], &a[keyi]);  
    // 更新基准元素的位置  
	keyi = left; // 注意不要忘了这一步,因为基准元素的位置可能已经变化了  
  
    // 递归地对基准左边的子数组进行快速排序  
	QuickSort(a, begin, keyi - 1);  
    // 递归地对基准右边的子数组进行快速排序  
	QuickSort(a, keyi + 1, end);  
}

这段代码实现的是快速排序算法的一个变种,其核心思想是通过分区操作将数组划分为两个子数组,然后递归地对这两个子数组进行排序。这个特定的实现采用了一种“单边循环法”来进行分区。
大概是以下两个步骤,建议自己先在草稿纸上演示一遍
在这里插入图片描述

void QuickSort2(int* a, int left, int right) {  
    // 如果左边界大于或等于右边界,说明区间无效或已排序完成,直接返回。  
    if (left >= right) {  
        return;  
    }  
      
    // keyi是基准元素的索引,初始化为左边界。  
    int keyi = left;  
    // prev指向小于基准元素的最后一个位置,初始化为左边界。  
    int prev = left;  
    // cur用于遍历数组,初始化为左边界的下一个位置。  
    int cur = left + 1;  
  
    // 当cur没有超过右边界时,执行循环。  
    while (cur <= right) {  
        // 如果当前元素小于基准元素,并且prev不等于cur(避免自己与自己交换),  
        // 则交换prev和cur位置的元素,并将prev向前移动一位。  
        if (a[cur] < a[keyi] && ++prev != cur) {  
            Swap(&a[cur], &a[prev]); // 这里假设Swap函数能够正确交换两个元素的值。  
        }  
        // cur向后移动一位,继续遍历数组。  
        cur++;  
    }  
      
    // 将基准元素交换到prev指向的位置,此时prev左边的所有元素都小于基准元素,  
    // prev右边的所有元素都大于或等于基准元素。  
    Swap(&a[prev], &a[keyi]);  
    keyi = prev; // 更新keyi为基准元素的新位置。  
  
    // 递归地对基准元素左边的子数组进行快速排序。  
    QuickSort2(a, left, keyi - 1);  
    // 递归地对基准元素右边的子数组进行快速排序。  
    QuickSort2(a, keyi + 1, right);  
}

测试用例

#include<stdio.h>

void PrintArray(int* a, int n)//打印
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

//快速排序
void QuickSort1(int* a, int left, int right) {
	if (left >= right) {
		return;
	}

	int begin = left, end = right;
	int keyi = left;
	while (left < right) {
		while (left < right && a[right] >= a[keyi]) {
			right--;
		}
		while (left < right && a[left] < a[keyi]) {
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = left;//注意不要忘了

	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

void QuickSort2(int* a, int left, int right) {

	if (left >= right) {
		return;
	}
	int keyi = left;
	int prev = left;
	int cur = left + 1;

	while (cur <= right) {
		if (a[cur] < a[keyi]&&++prev!=cur) {
			 
			Swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	QuickSort2(a, left, keyi - 1);
	QuickSort2(a, keyi + 1, right);
}
 
void TestSort()
{
	int a[] = { 6, 3, 9, 1, 5, 8, 2, 4, 7};
	PrintArray(a, sizeof(a) / sizeof(int));//计算数组元素个数并打印

	QuickSort2(a, sizeof(a) / sizeof(int));

	PrintArray(a, sizeof(a) / sizeof(int));
}


int main() {

	TestSort();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值