一、排序
接着造火箭,面试对排序一般是不会放过的,而在所有的排序中,快排勿庸置疑是问的最多的。而在实际应用中,这个排序确实也是在实际工程中用的最多的之一。快速排序是什么呢?快速排序就是通过排序数据进行比较和交换位置来实现数据排序的目的。快速排序分为经典快速排序和随机快速排序。前者是指定固定的基准数据(最左或者最右即第一个或最后一个),随机快速排序是随机指定任一一个。
快速排序的时间复杂度为O(nlogn),最坏情况下,即分治后一侧为1,另外一侧为n-1,时间复杂度为O(n^2),空间复杂度为O(nlogn),最坏情况下为O(n),属于不稳定、复杂排序。也就是说,在最坏的情况下,快速排序基本退化成为了冒泡排序。
二、快速排序的原理
快速排序的基本思想是分治法在排序中的一种应用方式。它通过随机定位排序元素中任一元素(经典方法取第一或最后,随机是用随机数随机定位),小于其的数据元素放到左侧,不小于(大于或等于)其的数据元素放到右侧,将排序数据分成两部分。然后继续通过上述方法分别对左右两部分进行操作,直到排序完成。
看上去有一点点复杂,看下面的图,就明白了:
基础排序数据:
循环到最后,左右指针相遇,再把Base(key)放到最后一个位置。然后再重新对两侧进行上述的操作,直到排序完成。
快速排序其实有几种方式,大家在网上遇到资料时要弄清楚:
1、指针数据的交换法:即左右数据同时向内侧挤压,比较大小,然后二者交换。最后在每一次递归调用后再进行一个low 和right的交换。
2、网上说的“挖坑”法。类似于传统的教学方法。
3、前后指针法:两个指针一前(pre)一后(cur),和KEY比较,大于KEY时,前指针停留,后指针继续,当cur再次小于KEY时,交换。
三、应用和优化
下面就给出指针数据交换的方法,看下面应用的代码:
递归方式:
#include <iostream>
void SortQuick(int arr[], int low, int high) {
if (high <= low)
{
return;
}
int left = low;
int right = high;
int key = arr[low];
while (true)
{
//自左向右比较KEY,定位大于KEY的索引
while (arr[++left] < key)
{
if (left == high) {
break;
}
}
//自右向左查找小于KEY的索引
while (arr[--right] >= key)
{
if (right == low) {
break;
}
}
if (left >= right)
{
break;
}
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
int temp = arr[low];
arr[low] = arr[right];
arr[right] = temp;
//递归调用
SortQuick(arr, low, right - 1);
SortQuick(arr, right + 1, high);
}
int main()
{
int a[] = { 58, 98, 69, 52, 73, 88, 96, 83, 28 };
int h = sizeof(a) / sizeof(a[0]) ;
SortQuick(a, 0, h);
for (int i = 0; i < h-1; i++)
{
std::cout << a[i] << "-> ";
}
return 0;
}
非递归方式:
int Split(int arr[], int low, int high) {
int base = arr[low];
while (low < high) {
while (low < high && arr[high] >= base)
{
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= base)
{
low++;
}
arr[high] = arr[low];
}
arr[low] = base;
return low;
}
void SortQuick2(int arr[], int low, int high)
{
//用其它数据结构也可以,只要注意数据的顺序即可。
std::stack<int> cur_s;
if (low < high) {
int mid = Split(arr, low, high);
if (low < mid - 1)
{
cur_s.push(low);
cur_s.push(mid - 1);
}
if (mid + 1 < high)
{
cur_s.push(mid + 1);
cur_s.push(high);
}
while (!cur_s.empty()) {
int l = cur_s.top();
cur_s.pop();
int r = cur_s.top();
cur_s.pop();
mid = Split(arr, l, r);
if (l < mid - 1)
{
cur_s.push(l);
cur_s.push(mid - 1);
}
if (mid + 1 < r)
{
cur_s.push(mid + 1);
cur_s.push(r);
}
}
}
}
使用递归实现的话,在解释和理解快速排序时,可能更方便一些,但在一些实际的场景中,因为对时间空间等的要求,可能会使用非递归的方式,这个就看实际应用的具体场景了。
快速排序的优化:
在快速排序中,其实主要是防止最坏的情况发生,最好的情况当然是接近二分,所以这就是优化的一个方向,可以找出一个中位数来做为排序的基数。这个方法就比较多了,这里不做过多的说明。
四、总结
快速排序在正常情况下,排序数量越大越有优势,这和其使用分治的思想是有关的。快排对内存的需求比较大,效率相对比较高。但是在分治法不理想的情况(最坏情况下),时间复杂度会大幅增加。在一些框架中的排序算法中,快速排序一般是比较普遍的,所以还是掌握快速排序为好。