快速排序也是有缺点的,它的缺点就是,数据越有序,快速排序的效率就越差。
比如当我们拿到这样的一组关键字序列
[1 2 3 4 5 6 7 8]
根据上一篇快速排序的思想,还是会先将1当关键字然后将序列划分成两部分,然后接着排序接着划分。。。如下图所示:
这样下来,就进行了很多没有必要的递归,递归的层次就会变得非常多。基于这样的情况我们给出优化方案
优化一:随机取基准点法
这个方法优化最坏的是情况,也就是数据最有序的情况。这样的话我们就不取第一个数为基准点,而是随机取基准点,这样的话就大大提高了算法的效率。
void Swap(int arr[],int first, int sencond)
{
int tmp;
tmp = arr[frist];
arr[frist] = arr[sencond];
arr[sencond] = tmp;
}
int parition(int arr[],int start,int end)//找到基准点后的基准点下标
{
int boundkey = arr[start];
while(start < end)
{
while(start < end && arr[end])
end--;
arr[start] = arr[end];
while(start < end && arr[start] <= boundkey)
start++;
arr[end] = arr[start]
}
arr[start] = boundkey;
return start;
}
void Quick(int arr[],int startindex,int endindex)
{
int k;
if(startindex < endindex)
{
swap(arr.startindex,rand()%(endindex - startindex + 1) + startindex)//改变随机值。随机在这个数据里取一个元素与其实位置交换
k = parition(arr,startindex,endindex);
Quick(arr,startindex,k - 1);
Quick(k + 1,endindex);
}
}
void QuickSort(int arr[],int len)
{
Quick(arr, 0, len-1);
}
优化二:三数取中法(第一个数据,中间数据,最后一个数据)
数据本身是随机的,当数据是随机的时候,随机取基准点的优化效率并不大。这个方法可以优化快排最坏的情况,普通情况也可以稍作优化。
int parition(int arr[],int start,int end)//找到基准点后的基准点下标
{
int boundkey = arr[start];
while(start < end)
{
while(start < end && arr[end])
end--;
arr[start] = arr[end];
while(start < end && arr[start] <= boundkey)
start++;
arr[end] = arr[start]
}
arr[start] = boundkey;
return start;
}
void getMiddleMaxNum(int arr[], int start, int mid, int end)
{
if(arr[mid] > arr[end])
{
Swap(arr, mid, end);//mid和right中较大的数据
}
if(arr[start] < arr[end])//end中间大
{
Swap(arr,start,end);
}
if(arr[start] > arr[mid])//mid中间大
{
Swap(arr.start,mid);
}
}
void Quick(int arr[],int startindex,int endindex)
{
int k;
if(startindex < endindex)
{
//swap(arr.startindex,rand()%(endindex - startindex + 1) + startindex)//改变随机值。随机在这个数据里取一个元素与其实位置交换
int midindex = (endindex + startindex) / 2;
getMiddleMaxNum(arr, startindex,midindex,endindex);
k = parition(arr,startindex,endindex);
Quick(arr,startindex,k - 1);
Quick(k + 1,endindex);
}
}
void QuickSort(int arr[],int len)
{
Quick(arr, 0, len-1);
}
优化三:数据量小
这种情况下就没有必要进行快速排序了,这个时候我们可以进行插入排序来提高s算法的效率
//核心代码
if(startindex < endindex0)
{
if((endindex - startindex ) < 20)
{
InsertSort(arr,startindex,endindex);
}
}
具体实现
int parition(int arr[],int start,int end)//找到基准点后的基准点下标
{
int boundkey = arr[start];
while(start < end)
{
while(start < end && arr[end])
end--;
arr[start] = arr[end];
while(start < end && arr[start] <= boundkey)
start++;
arr[end] = arr[start]
}
arr[start] = boundkey;
return start;
}
void InsertSort(int arr[],int start,int end)
{
int i = start + 1;
int j = i - 1;
int tmp;
for(i;i <= end;i++)
{
tmp = arr[i];
for(j = i - 1;j >= start && arr[j] > tmp;j--)
{
arr[j + 1] = arr[j];
}
arr[j + 1] = tmp;
}
}
void Quick(int arr[],int startindex,int endindex)
{
int k;
if(startindex < endindex)
{
if((endindex - startindex ) < 20)
{
InsertSort(arr,startindex,endindex);
return;
}
//swap(arr.startindex,rand()%(endindex - startindex + 1) + startindex)//改变随机值。随机在这个数据里取一个元素与其实位置交换
int midindex = (endindex + startindex) / 2;
getMiddleMaxNum(arr, startindex,midindex,endindex);
k = parition(arr,startindex,endindex);
Quick(arr,startindex,k - 1);
Quick(k + 1,endindex);
}
}
优化四:聚集优化
针对于重复率高,数据值少的情况。比如这样的待排序序列:
[19 18 19 20 20 20 19 19 19 20]
这样的话如果我们不做聚集优化的话,它会将每个数字都当成基准点,会提高递归的次数。如果我们将所有重复的数字只当一次基准点,这样就大大减少了递归的次数,提高了算法的效率。
方法是:找到基准点,在它左边的区间,把和基准点相同的数据向右侧移动和基准点聚集。在它右边的区间,把和基准点相同的数据向左侧移动和基准点聚集。
void Gather(int arr[],int s,int e,int k,int newleft,int newright)
{
int newleft = k - 1;
int newright = k + 1;
// 寻找并聚集基准左边与基准相同的元素
for (int i = k - 1; i >= start; i--)
{
if (arr[i] == arr[k])
{
if (i != newleft)
{
int tmp = arr[newleft];
arr[newleft] = arr[i];
arr[i] = tmp;
newleft--;
}
else
{
newleft--;
}
}
}
// 寻找并聚集基准右边与基准相同的元素
for (int j = k + 1; j <= end; j++)
{
if (arr[j] == arr[k])
{
if (j != newright)
{
// 依次遍历比较,相同就交换位置
int tmp = arr[newright];
arr[newright] = arr[j];
arr[j] = tmp;
newright++;
}
else
{
newright++;
}
}
}
}
void Quick(int arr[],int startindex,int endindex)
{
int k;
if(startindex < endindex)
{
if((endindex - startindex ) < 20)
{
InsertSort(arr,startindex,endindex);
return;
}
int midindex = (endindex + startindex) / 2;
getMiddleMaxNum(arr, startindex,midindex,endindex);
int left;
int right;
Gather(arr,startindex,endindex,k,&left,&right)
k = parition(arr,startindex,endindex);
Quick(arr,startindex,k - 1);
Quick(k + 1,endindex);
}
}
优化五:非递归优化
将递归处理的划分的区间采用非递归的方法。
int parition(int arr[],int start,int end)//找到基准点后的基准点下标
{
int boundkey = arr[start];
while(start < end)
{
while(start < end && arr[end])
end--;
arr[start] = arr[end];
while(start < end && arr[start] <= boundkey)
start++;
arr[end] = arr[start]
}
arr[start] = boundkey;
return start;
}
void Quick(int arr[], int startindex,int endindex)
{
int * st = (int *)malloc(sizeof(int)*(endindex - startindex + 1))
int top = 0;//栈顶指针
int left = startindex;
int right = endindex;
st[top++] = left;
st[top++] = right;
while(top != 0)
{
right = st[top - 1]; top--;
left = st[top - 1]; top--;
k = parition(arr,left,right);
if(k + 1 < right)//右区间至少有两个元素
{
st[top++] = k + 1;
st[top++] = right;
}
if(left < k - 1)//右区间至少有两个元素
{
st[top++] = left;
st[top++] = k - 1;
}
}
free(st);
}