快速排序
据说,很多公司的面试环节都喜欢要求应聘者写出快速排序的代码;今天复习排序算法中快速排序的这个知识点,在此总结一下!
一,基本思想
实现快速排序算法的关键在于先在待排序的数组中选择一个数字M(有时,称这个数字为枢轴),然后把数组中的数字分成两部分,比M小的数字移动到数组的左边,比M大的数字移动到数组的右边;之后用递归的思路分别对左右两边进行排序。
根据这个思想,通常,在实现算法时先实现一趟快速排序的算法,然后实现一个递归函数对整个数组进行排序。
二,两种快速排序的代码
关于对快速排序的具体执行过程的分析的内容有很多文章和视频进行讲解,这里我直接上代码。
通常,对整个数组进行排序的递归函数的实现方式基本相同,这里我总结两中思想略有不同的一趟快速排序。
①选择第一个元素作为枢轴,从后向前找比枢轴小的元素移动到数组前端,从前向后找比枢轴大的元素移动到数组后端,最后空下一个位置刚好是枢轴的位置。
int partition(int data[], int start, int end)
{
data[0] = data[start];
while (start < end)
{
while (start<end && data[end]>=data[0])
--end;
data[start++] = data[end];
while (start < end && data[start] <= data[0])
++start;
data[end--] = data[start];
}
data[start] = data[0];
return start;
}
说明:本代码,是通过直接选择下标为待排序数组部分的第一个元素(其实并不是太好,下文说明原因)并存放在data[0]中进行操作的。(此处数组中下标为0的位置是在空着,待排序数是从下标为1处进行排序。)
当然,将待排序数组从下标为0处,通过定义一个变量保存枢轴元素也是一样的。
在调整过程中,设立了两个指针: start和end,它们的初值分别设为待排序部分的起始和结束位置。
之后逐渐减小 start,增加 end,并保证
data[start]≥枢轴,和 R[end]≤枢轴,否则进行记录的“交换”。
附上,全部代码
void QuickSort(int data[], int start, int end)
{
if (start < end)
{
int index = partition(data, start, end);
QuickSort(data, start, index - 1);
QuickSort(data, index + 1, end);
}
}
int main()
{
int data[] = {0,6,1,13,18,7,3,25,6,20,3,9,8,12,5};
cout << "待排序数组:" << endl;
for (int i : data)
cout << i << " ";
cout << endl;
cout << endl;
QuickSort(data, 1,14);
cout << endl;
cout << endl;
cout << "排序后数组:" << endl;
for (int i : data)
cout << i << " ";
cout << endl;
return 0;
}
运行结果:
②在start和end之间随机选择一个位置的数作为枢轴,并将枢轴放到数组待排序部分最后一个位置,从前向后遍历待排序的部分,将小于枢轴的元素移动到前端,大于枢轴的元素保持不动。
int partition(int data[], int start, int end)
{
if (data == nullptr || start < 0 || end < start)
throw new std::exception("Invalid input!");
//生成一个随机数
default_random_engine e;
uniform_int_distribution<int> u(start, end);
int index = u(e);
swap(data[index], data[end]);
int small = start - 1;//指向数组中比枢轴小的元素的最后一个
for (index = start; index < end; ++index)
{
if (data[index] < data[end])
{
++small;
if (small != index)
{
swap(data[index], data[small]);
}
}
}
++small;
swap(data[small], data[end]);
return small;
}
附上main函数代码(QuickSort函数同上):
int main()
{
int data[] = { 5,6,4,8,2,3,4,4,9,1,3,9,7,2,46,1,6,5 };
QuickSort(data, 0, 17);
for (int i : data)
cout << i<<endl;
}
此,只是从0位置开始排序。
三,性能分析
①快速排序的趟数取决于递归树的高度。
当每次划分定位一个元素以后,该元素的左右两侧子序列的长度相同,此时是最理想的情况。
▲②最好情况
当对n各元素进行排序时,对一个元素定位所需要时间为O(n);当每次划分定位一个元素以后,该元素的左右两侧子序列的长度相同时是最理想情况;此时时间复杂度为O(nlog2n)
▲③平均时间复杂度
O(nlog2n)
对于平均时间复杂度,快速排序是所有内排序方法中最好的一个。
▲④最坏情况
当待排序记录序列安关键字顺序有序时,快速排序的时间性能蜕化成O(n^2)。
所以,在前面算法中,直接选择第一个元素作为枢轴的方法是不太好的;通常,(1)我们可以选择第一个元素、最后一个元素和中间的一个元素进行比较,将大小居中的元素作为枢轴;(2)按照第二种算法的方法,进行随机选择一个数作为枢轴。
🔺⑤空间复杂度
快速排序是递归进行的,所以每一层递归都需要栈保存递归调用的指针和参数。因此,空间复杂度为O(log2n)。
⑥稳定性
不稳定,元素会进行交换,使相同元素的相对位置发生变换。
以上就是我对快速排序算法的一些总结,由于是复习知识点,一些内容,比如对排序的分析,没有在文章中体现,所以此文章希望能给有一定快速排序算法基础理解的朋友提供一些参考;对于初学快速排序的朋友,还请先阅读其他资料进行理解学习。
由于时间仓促,再加上本人的能力有限,文章难免有一些遗漏,希望读者包含和指正。