(1)挖坑填数(l标记头,h标记尾):(找基准(最中间的数))
从数列中取出一个基准(一般取第一个);h从后往前遍历,找到比基准小的数字,将此数字填入之前取基准的坑中(即l的位置);l再从头往后遍历,找到比基准大的数字,填入之前填基准挪走数字的坑中(即h的位置)。
重复上述操作,直到l,h相遇(注意:小心l,h越过去),相遇位置即基准要放入的位置。此刻基准左边都比基准小,基准右边都比基准大。(即基准已经到了最终该放的位置)
(2)分治法:(只要数据量 >= 2 ,就继续划分)
对之前基准所到位置的左边和右边分别重复(1)的操作。
2.代码实现:
static int Datum(int *arr,int low,int high)//一趟划分过程//返回基准下标
{
int index;
if(arr == NULL || low >= high) index = -1;
else
{
int tmp = arr[low];//基准
while(low < high)
{
//这里判断的原因是:怕low和high越过去
while((low < high) && tmp <= arr[high]) -- high;
if(low == high) break;//从while循环出来,两种情况:1.low == high 2.low < high
else
{
arr[low] = arr[high];//把小的换到前面来
}
while((low < high) && tmp >= arr[low]) ++low;
if(low == high) break;
else
{
arr[high] = arr[low];//不稳定,如果有重复的数字这里判断不出来
}
}
arr[low] = tmp;
index = low;
}
return index;
}
//时间递归深度 O(logn) //空间 O(logn)
static void Quick(int *arr,int low,int high)
{
int index = Datum(arr,low,high);
if(low+1 < index)//左边//保证至少还有两个数才再次调用快排
{
Quick(arr,low,index-1);
}
if(index < high-1)//右边
{
Quick(arr,index+1,high);
}
}
void Q_sort(int *arr,int n)
{
Quick(arr,0,n-1);//递归调用时,接口和实际调用有差距,重新实现一个子函数
}
3.时间复杂度:O(nlogn)
4.空间复杂度:O(logn)~O(n) 最差O(n)(基本有序的情况)
5.稳定性:不稳定
6.快排的时间复杂度的优化:
(1.最开始的时候找到之后交换,现在只是先挖坑再填数而不是交换,降低了多余或无用交换//
2.选取基准:(1)选取第一个(如果数组已经有序,分割不理想,退化成冒泡);(2)随机选取基准(相对安全,仍然会出现最坏的,整个数组数字全相等);(3)三数取中//
3.当待排序序列的长度分割到一定大小,使用插入排序(对于很小和部分有序的数组,快排不如插排好)//
4.在一次分割后,把key相等的元素聚集在一起,下次再分割,无需对key再分割(三数取中选择枢轴+插排+聚集相等元素的组合,效果好的出奇)//
5.优化递归(尾部的两次递归,可以对其尾递归优化,优点:减少递归的深度,O(n)->O(logn),提高性能)//
总结:这里效率最好的快排组合 是:三数取中+插排+聚集相等元素,它和STL中的Sort函数效率差不多)
(1)思想:(这里只做最优化的一种)三数取中+插排+聚集
//三数取中:要将待排序的序列划分成等长的子序列,基准得是中值,一般的做法是使用左端、右端和中心位置上的三个元素的中值作为基准
//插排:直接插入排序
//聚集:一次分割后,把key相等的元素聚在一起,继续下次分割时,不用再对与key相等的元素进行分割;两步:1.在划分过程中,把与key相等的元素放到数组的两端;2.划分结束后,把与key相等的元素移到基准周围
(2)代码实现:
//三数取中
int Datum(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])//目标:arr[low] >= arr[mid]
{
swap(arr[mid],arr[low]);
}
//此时,arr[mid] <= arr[low] <= arr[high]
return arr[low];
//low的位置上保存这三个位置中间的值
//分割时可以直接使用low位置的元素作为基准,而不用改变分割函数了
}
//直接插入
void Insert_sort(int *a, int low,int high)
{
int i, j, k;
for (i = low; i < high; i++)
{
//为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置
for (j = i - 1; j >= 0; j--)
if (a[j] < a[i])
break;
//如找到了一个合适的位置
if (j != i - 1)
{
//将比a[i]大的数据向后移
int temp = a[i];
for (k = i - 1; k > j; k--)
a[k + 1] = a[k];
//将a[i]放到正确位置上
a[k + 1] = temp;
}
}
}
//改进后的快排
void Q_sort1(int *arr,int low,int high)
{
int first = low;
int last = high;
int left = low;
int right = high;
int leftlen = 0;
int rightlen = 0;
if(high - low + 1 < 10)//数量过小就用直接插入
{
Insert_sort(arr,low,high);
return ;
}
//一次分割
int key = Datum(arr,low,high);
while(low < high)
{
while(high > low && arr[high] >= key)
{
if(arr[high] == key)//处理相等元素
{
swap(arr[right],arr[high]);
--right;
++rightlen;
}
--high;
}
arr[low] = arr[high];
while(high > low && arr[low] <= key)
{
if(arr[low] == key)
{
swap(arr[left],arr[low]);
++left;
++leftlen;
}
++low;
}
arr[high] = arr[low];
}
arr[low] = key;
//一次快排结束
//把与基准key相同的元素移到基准最终位置周围
int i = low - 1;
int j = first;
while(j < left && arr[i] != key)
{
swap(arr[i],arr[j]);
--i;
++j;
}
i = low + 1;
j = last;
while(j > right && arr[i] != key)
{
swap(arr[i],arr[j]);
++i;
--j;
}
Q_sort1(arr,first,low-1-leftlen);
Q_sort1(arr,low+1+rightlen,last);
}
//对外接口
void Q_sort1(int *arr,int n)
{
Q_sort1(arr,0,n-1);
}