快速排序运用
题目:在数组中找到第k大的元素
样例:给出数组 [9,3,2,4,8]
,第三大的元素是 4
给出数组 [1,2,3,4,5]
,第一大的元素是 5
,第二大的元素是 4
,第三大的元素是 3
,以此类推
要求:要求时间复杂度为O(n),空间复杂度为O(1)
一、冒泡排序(平均时间复杂度为 )
(1)C++程序:
class Solution {
public:
int kthLargestElement(int n, vector<int> &nums)
{
int i,j,temp;
int sum=nums.size();
for(i=0;i<sum-1;i++)
{
for(j=i;j>=0;j--)
{
if(nums[j]<nums[j+1])
{
temp=nums[j];
nums[j]=nums[j+1];
nums[j+1]=temp;
}
}
}
return nums[n-1];
}
};
(2)python程序:
class Solution:
def kthLargestElement(self, n, nums):
for i in range(len(nums)-1):
for j in range(i,-1,-1):
if nums[j]<nums[j+1]:
nums[j],nums[j+1]=nums[j+1],nums[j]
return nums[n-1]
二、快速排序
快速排序是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
基本方法:
- 选取一个关键字(key)作为枢轴,一般取整组记录的第一个数/最后一个,这里采用选取序列最后一个数为枢轴。
- 设置两个变量left = 0;right = N - 1;
- 从left一直向后走,直到找到一个大于key的值,right从后至前,直至找到一个小于key的值,然后交换这两个数。
- 重复第三步,一直往后找,直到left和right相遇,这时将key放置left的位置即可。
当left >= right时,一趟快速排序就完成了,这时将Key和array[left]的值进行一次交换。 一次快排的结果:4 1 3 0 2 5 9 8 6 7
快速排序的时间复杂度:O(nlogn)~O(n^2) 若初始记录序列按关键字有序或基本有序,快速排序将蜕化为冒泡排序,其时间复杂度为O(n2)。
C++程序:
1. 取数组的第一个数作为关键字key(这种情况时间复杂度不符合要求):
class Solution {
public:
int kthLargestElement(int n, vector<int> &nums) {
// write your code here
quick_select(nums,0,nums.size()-1);
return nums[nums.size()-n];
}
private:
void quick_select(vector<int> &nums,int left,int right )
{
if(left>=right)
{
return;
}
int key=nums[left];
int i=left,j=right,temp;
while(i<j)
{
while(i<j&&nums[j]>=key)
{
j--;
}
while(i<j&&nums[i]<=key)
{
i++;
}
if(i<j)
{
temp=nums[j];
nums[j]=nums[i];
nums[i]=temp;
}
}
nums[left]=nums[i];
nums[i]=key;
quick_select(nums,left,i-1);
quick_select(nums,i+1,right);
}
};
2. 取数组中间数作为关键字key (可有效抵抗时间复杂度最坏的情况):
class Solution {
public:
int kthLargestElement(int n, vector<int> &nums)
{
quick_select(nums,0,nums.size()-1);
return nums[nums.size()-n];
}
private:
void quick_select(vector<int> &nums, int left, int right)
{
if(left>=right)
{
return;
}
int key=nums[(left+right)/2];
int i=left,j=right,temp;
while(i<=j)
{
while(i<=j&&nums[j]>key)
{
j--;
}
while(i<=j&&nums[i]<key)
{
i++;
}
if(i<=j)
{
temp=nums[j];
nums[j]=nums[i];
nums[i]=temp;
if(i<right) i++;
if(j>left) j--;
}
}
quick_select(nums,left,i-1);
quick_select(nums,i,right);
}
};
3. 三数取中:
虽然随机选取基元时,减少了出现不好分割的几率,但是最坏情况下还是O(n^2),要缓解这种情况,就引入了三数取中选取基元。最佳的划分是将待排序的序列分成等长的子序列,最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为基元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基元。
举例:待排序序列为:8 1 4 9 6 3 5 2 7 0
左边为:8,右边为0,中间为6.
我们这里取三个数排序后,中间那个数作为枢轴,则枢轴为6
注意:在选取中轴值时,可以从由左中右三个中选取扩大到五个元素中或者更多元素中选取,一般的,会有(2t+1)平均分区法(median-of-(2t+1),三平均分区法英文为median-of-three)。
对待排序序列中low、mid、high三个位置上数据进行排序,取他们中间的那个数据作为枢轴,并用0下标元素存储枢轴。
即:采用三数取中,并用0下标元素存储枢轴。
class Solution {
public:
int kthLargestElement(int n, vector<int> &nums)
{
quick_select(nums,0,nums.size()-1);
return nums[nums.size()-n];
}
private:
void swap(int &a,int &b)
{
int temp;
temp=a;
a=b;
b=temp;
}
int SelectPivotMedianOfThree(vector<int> &arr,int low,int high)
{
int mid=low+((high-low)>>1);//计算数组中间的元素的下标
//使用三数取中法选择枢轴
if(arr[mid]>arr[high])//目标:arr[mid]<=arr[high]
{
swap(arr[mid],arr[high]);
}
if(arr[low]>arr[high])//目标:arr[low]<=arr[high]
{
swap(arr[low],arr[high]);
}
if(arr[mid]>arr[low])
{
swap(arr[mid],arr[low]);
}
return arr[low];
}
void quick_select(vector<int> &nums, int left, int right)
{
if(left>=right)
{
return;
}
int key=SelectPivotMedianOfThree(nums,left,right);
int i=left,j=right;
while(i<j)
{
while(i<j&&nums[j]>=key)
{
j--;
}
while(i<j&&nums[i]<=key)
{
i++;
}
if(i<j)
{
swap(nums[i],nums[j]);
}
}
nums[left]=nums[i];
nums[i]=key;
quick_select(nums,left,i-1);
quick_select(nums,i+1,right);
}
};
使用三数取中选择枢轴优势还是很明显的,但是还是处理不了多数元素重复的数组。
三、QuickSelect
Quick select算法通常用来在未排序的数组中寻找第k小/第k大的元素。其方法类似于Quick sort。Quick select算法因其高效和良好的average case时间复杂度而被广为应用。Quick select的average case时间复杂度为O(n),然而其worst case时间复杂度为O(n^2)。总体而言,Quick select采用和Quick sort类似的步骤。首先选定一个pivot,然后根据每个数字与该pivot的大小关系将整个数组分为两部分。那么与Quick sort不同的是,Quick select只考虑所寻找的目标所在的那一部分子数组,而非像Quick sort一样分别再对两边进行分割。正是因为如此,Quick select将平均时间复杂度从O(nlogn)降到了O(n)。
Python程序:
class Solution:
def kthLargestElement(self,n,nums):
return self.quick_select(nums,0,len(nums)-1,len(nums)-n)
def quick_select(self,nums,left,right,k):
if left==right:
return nums[k]
key=nums[(left+right)//2]
i,j=left,right
while i<=j:
while i<=j and nums[j]>key:
j-=1
while i<=j and nums[i]<key:
i+=1
if i<=j:
nums[i],nums[j]=nums[j],nums[i]
i+=1
j-=1
if k<=right:
self.quick_select(nums,left,i-1,k)
if k>=left:
self.quick_select(nums,i,right,k)
return nums[k]