忙里偷闲,突然对排序算法产生了兴趣,只会冒泡排序和选择排序这两种本科学到的排序方法的我,来探索一波新世界!希望从此告别O(n²)
。
1、原理
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小。然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
2、实现
快速排序是分治思想的一个重要体现。
划分
要达到其中一部分的所有数据都比另外一部分的所有数据都要小,一个最简单的方案是:选择一个数字,接下来把数组中的其余数字分成两部分,比选择的数字小的放在数组的左边,大的放在右边
分治递归
依次对每一边递归调用,这样可以使每一边都变得有序。
终止条件
快速排序的一次划分算法可以使用双指针进行两头交替搜索,直到两头重合;如果不使用两根指针,则“两头重合”需要所需条件是:划分到当前区间段只有1个数字。
3、复杂度分析
时间复杂度
首先,划分算法需要O(n)的时间复杂度。因此
最好的情况:每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(n)
x O(log2n)
= O(nlog2n)
。
最坏的情况:每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O(n)
x O(n)
= O(n²)
。
然而,总体来说平均时间复杂度为O(nlogn)
空间复杂度
快速排序只需要一个元素的辅助空间(PS:后面我的代码中因习惯问题,使用了两个元素辅助空间。如需要变为1个,则将for循环中的i改为index即可。但用i相对更好理解);
快速排序需要一个栈空间来实现递归:
最好的情况下,即快速排序的每一趟排序都将元素序列均匀地分割成长度相近的两个子表,所需栈的最大深度为log2(n+1)
;但最坏的情况下,栈的最大深度为n
。
总体而言,快速排序的平均空间复杂度为O(log2n)
。
4、代码
给出一段自己写出来的代码,并附上注释,自认为没有任何问题,可以直接复制后在编辑器上粘贴即可运行的那种哦:
#include<iostream>
using namespace std;
/*Swap函数:交换两个数字*/
void Swap(int& a, int& b)//使用&来做到深入骨髓的替换,可以尝试一下不用引用号(滑稽脸)
{
int temp = a;
a = b;
b = temp;
}
/*Partition函数:在数组中选择一个数字,比其小的放在左边,比其大的放在右边*/
int Partition(int data[], int length, int start, int end)//start是起始位置下标,end是终止位置后面的坐标,length = data.size()只是判断时调用
{
if (data == nullptr || length <= 0 || start < 0 || end >= length)//判断异常情况
{
throw new std::exception("Invalid Parameters");
}
int index = (start + end) / 2;//这个可以是任意值,只要在start和end之间
Swap(data[index], data[end]);===在数组中选择的那个数字暂存在最后一位
int small = start - 1;//small是选出的那个数字,排序后应该所在的位置下标
for (int i = start; i < end; ++i)//进行遍历:small的递增找到排序后应该所在的位置下标,Swap将比选出的值小的数放在前面
{
if (data[i] < data[end])
{
++small;
if (small != i)
{
Swap(data[i], data[small]);//把小值放在“排序后应该所在的位置下标”的前面
}
}
}
small++;
Swap(data[small], data[end]);===暂存在最后一位的那个数字换到应在的位置
return small;
}
/*快排:对选出数字左右两边递归Partition即可完成*/
void QuickSort(int data[], int length, int start, int end)
{
if (start == end)//终止条件:只剩一个数字
return;
int index = Partition(data, length, start, end);
if (index > start)
QuickSort(data, length, start, index - 1);//递归:找到小数,放在选出的数字之前
if (index < end)
QuickSort(data, length, index + 1, end);//递归:找到大数,放在选出的数字之后
}
int main()
{
int data[10] = { 5,0,1,2,6,4,7,9,3,8 };//测试数据可以随意设置
int length = 10;//对于长度为n的数组,只需把start设为0,end设为n-1,调用QuickSort即可
int start = 0;
int end = 9;
QuickSort(data, length, start, end);
return 0;
}