之前面试的时候,遇到了许多关于基本算法的题目,对于其代码的编写以及特殊情况下的优化及改进等,及算法相关的学习有了更多的感悟与领会,与大家分享一下,此处以快排讲解为例;
快排的核心思想很简单:就是先选择一个杠杆元素(一般选第一个数),对剩下元素遍历,大的放右边,小的放左边;然后对于两边元素再次用以上方法进行递归处理;
基本的代码实现在我的以前博客(排序二)中已经介绍过了,此处借鉴一个简洁代码:
// v为pivot,初始存储在arr[0]的位置
int j = 0; // 循环过程保持 arr[0...j] < v ; arr[j+1...i) > v
for( int i = l + 1 ; i <= n ; i ++ )
if( arr[i] < v )
swap( arr[++j] , arr[i] );
swap( arr[l] , arr[j]); // 此时,j指向pivot的正确位置
实例:
4 8 3 0 5 2 7
4 j=0
4 8
4 3 8 j=1
4 3 0 8 j=2
4 3 0 8 5
4 3 0 2 8 5 j=3
4 3 0 2 8 5 7
2 3 0 4 8 5 7
我们知道对于快速排序算法,当元素排序是随机无序的时候,也就是当每递归完一次的时候,其元素大约成双等分时其效率是最高的,类似于二叉排序树,时间复杂度为O(n*lg n);然而大多数情况下事与愿违,当元素接近有序或者拥有大量重复元素时其效率将大大降低为O(n^2),所以对于此种情况我们需要考虑在内,提出以下解决办法:
(1)如果我们一开始不知道数组是否基本有序,那么对于杠杆元素的选择上,我们可以实现随机选择一个元素作为pivot,这样同样可以降低有序的概率从而整体提高排序效率;
(2)如果我们事先知道数组含有大量重复元素,则对于上面算法还可以再次改进,称为三路快排:小于的为一路,等于的为一路,大于的为一路,算法如下:
// v为pivot,初始存储在arr[0]的位置
int lt = 0; // 循环过程中保持 arr[1...lt] < v
int i = 1; // 循环过程中保持 arr[lt+1...i) == v
int gt = n + 1; // 循环过程中保持 arr[gt...n] > v
while( i < gt ){
if( arr[i] < v ){
swap( arr[i++], arr[lt+1]); lt ++;
} else if( arr[i] > v ){
swap( arr[i], arr[gt-1]); gt --;
} else // arr[i] == v
i ++;
}
swap( arr[l] , arr[lt] );
// 此时 arr[lt...gt-1]部分为数组中元素等于v的部分
// 之后只需要递归地对arr[l...lt-1]和arr[gt...r]两部分进行三路快排即可
基于比较的排序算法,其时间复杂度至少为O(nlogn).
扩展面试题:有一个数组,其中的元素取值只有可能是0,1,2。为这样一个数组排序。
/// 解法一:直接使用排序接口,时间复杂度O(nlogn)
class Solution {
public:
void sortColors(vector<int>& nums) {
sort( nums.begin(), nums.end() );
return;
} };
// 解法二:基于计数排序的解法,时间复杂度O(n),需要两遍遍历 ,
//第一次统计元素个数,第二次放回原数组
class Solution {
public:
void sortColors(vector<int>& nums) {
int count[3] = {0}; // count[i]表示元素i的个数
for( int i = 0 ; i < nums.size() ; i ++ ){
assert( nums[i] >= 0 && nums[i] <= 2 );
count[ nums[i] ] += 1;
}
int index = 0;
for( int i = 0 ; i < count[0] ; i ++ ) // 安置count[0]个0
nums[index++] = 0;
for( int i = 0 ; i < count[1] ; i ++ ) // 安置count[1]个1
nums[index++] = 1;
for( int i = 0 ; i < count[2] ; i ++ ) // 安置count[2]个2
nums[index++] = 2; return;
} };
// 解法三:基于三路快排的partition的解法,时间复杂度O(n),只需要一边遍历
class Solution {
public:
void sortColors(vector<int>& nums) {
int i = 0; // nums[0..<i) == 0
int j = 0; // nums[i..<j) == 1
int k = nums.size(); // nums[k..<n) == 2
while( j < k ){
if( nums[j] == 1 ) j++;
else if( nums[j] == 0 ) swap( nums[i++] , nums[j++] );
else{ // nums[j] == 2
assert( nums[j] == 2 );
swap( nums[j] , nums[k-1] ); k --; }
}
return;
}
};