快速排序
快速排序是一种交换类排序,它是对冒泡排序的一种改进,基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
快速排序最好情况下的时间复杂度为O(nlogn),待排序列越接近无序,本算法的效率越高。最坏情况下的时间复杂度为O(n2),待排序列越接近有序,本算法的效率越低。平均时间复杂度为O(nlogn)。就平均时间而言,快速排序是所有排序算法中最好的。空间复杂度为O(logn),因为需要栈空间。
快速排序的主要思想有分治与递归。
下面是一个标准的快速排序算法C代码:
void swap(int a[], int i, int j)
{
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
int partition(int a[], int low, int high)
{
int pivot = a[low];
while (low < high) {
while (low < high && a[high] >= pivot)
high--;
swap(a, low, high);
while (low < high && a[low] <= pivot)
low++;
swap(a, low, high);
}
return low;
}
void quick_sort(int a[], int low, int high)
{
int pivot;
if (low < high) {
pivot = partition(a, low, high);
quick_sort(a, low, pivot - 1);
quick_sort(a, pivot + 1, high);
}
}
- 后++改为前++,因为前++比后++效率上高那么一点,所以可以优化一丢丢;
- 我们可以注意到partition函数中调用swap函数的部分,当low=high时,仍然要进行交换,那么加一个if判断?no,这个部分频繁地调用swap函数,因为调用函数意味着推栈和出栈,性能损失极大,我们注意到partition实际是将一个“枢纽”元素想办法放到中间,使得左边的比它小,右边的比它大,那么我们可以考虑将枢纽元素保存下来,用赋值替代交换;
- 排序划分后在各个子部分分别递归再进行排序,递归层数越多性能影响越大,所以可以考虑使用迭代替换部分递归(尾递归);
- 快速排序通常会选第一个作为枢纽元素,当这个元素比较大或者比较小时会很大地影响性能,所以可以选取左端、中间和右端的三个数,取它们的中间值作为枢纽,这样可以极大概率地避免上述情况。
优化后的快排如下:
void swap(int a[], int i, int j)
{
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
int partition(int a[], int low, int high)
{
int mid = low+(high-low)/2;
if(a[low]>a[high]) swap(a, low, high);
if(a[mid]>a[high]) swap(a, mid, high);
if(a[mid]>a[low]) swap(a, low, mid);
int pivot = a[low];
int tmp = pivot;
while (low < high) {
while (low < high && a[high] >= pivot)
--high;
a[low] = a[high];
while (low < high && a[low] <= pivot)
++low;
a[high] = a[low];
}
a[low] = tmp;
return low;
}
void quick_sort(int a[], int low, int high)
{
int pivot;
while (low < high) {
pivot = partition(a, low, high);
quick_sort(a, low, pivot - 1);
low = pivot + 1;
}
}
总之记住能量守恒定律,要么牺牲时间换空间,要么牺牲空间换时间,在现在计算机存储能力普遍提高的情况下,通常人们优化的方向都是后者。