快速排序算法的基本原理为通过一次排序将要排序的数据分割成独立的两部分,将序列分为两部分的中间数作为基准(par),基准左边的数都要比基准右边的数要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
例如:8,15,26,18,7,66,44
分析:依靠基准将序列分割为两部分,那么基准如何找到呢??????
方法有三种:
具体步骤:
一次取基准(par)过程:设置一个临时变量temp,并以第一个数作为temp的值,设置两个指针low和high。low指向序列的起始位置,high指向序列的最后一个元素。首先我们用high指向的元素和temp进行比较,如果大于,指针回退,直到high指向的元素值小于temp,然后让low指向的值赋值为high的值。这时让low指向的元素和temp比较,如果low指向的元素小于temp,指针前进,直到low指向的元素值大于temp。这时让high指向的值赋值为low指向的值,接下来对high重复上述操作,直到high和low相遇。此时的位置就是基准的位置par,最后此位置赋值temp。(具体可依据下图进行分析)
说明:
然后对左右两部分子序列继续进行此步骤。(如果子序列为单个数时,说明自身有序,不进行此过程)
通过上面的具体分析,以下是其代码实现:
int Partion(int *arr,int low,int high) //一次找基准过程
{
int temp = arr[low];
while(low < high) //当low = high时退出循环,此时的位置为基准位置
{
while(low < high && arr[high] > temp)
{
high--;
}
if(low >= high)
{
break;
}
else
{
arr[low] = arr[high];
}
while(low < high && arr[low] < temp)
{
low++;
}
if(low >= high)
{
break;
}
else
{
arr[high] = arr[low];
}
}
arr[low] = temp;
return low;
}
回想该排序算法的基本原理,当第一次找到基准后,基准左边的都比基准小,基准右边的都比基准大。然后对其两边的子序列再进行找基准的操作。我们发现这其实是一个递归的过程,其递归结束条件就是当找完基准后,其左边子序列或者右边子序列只有一个元素时,便为自身有序,整体而言即为有序。但然除了采用递归的方式,还可以通过非递归的方式进行解决。下面给出两种方法的具体代码实现过程。
void Quick(int *arr,int start,int end)
{
int par = Partion(arr,start,end); //一次找基准
if(par > start+1)
{
Quick(arr,start,par - 1);
}
if(par < end - 1)
{
Quick(arr,par+1,end);
}
}
void Quick_Sort(int *arr,int len) //len为数组的长度
{
Quick(arr,0,len-1);
}
进行完一次排序后,将左右子序列的第一个和最后一个元素放在数组中,然后读取元素,重新进行排序。(如果子序列为单个数时,说明自身有序,不进行此过程)
一次入栈过程:
void Quick_Sort(int *arr,int len)
{
int tmpSize = (int)log((double)len)/log((double)2);
int *stack = (int *)malloc(sizeof(int)*tmpSize*2); //malloc开辟动态空间
assert(stack != NULL); //断言
int top = 0; //数组的下标
int low = 0;
int high = len - 1;
int par = Partion(arr,low,high); //第一次找基准
if(par > low + 1)
{
stack[top++] = low;
stack[top++] = par - 1;
}
if(par < high-1)
{
stack[top++] = par + 1;
stack[top++] = high;
}
while(top > 0) //栈不为空
{
high = stack[--top];
low = stack[--top];
par = Partion(arr,low,high);
if(par > low+1)
{
stack[top++] = low;
stack[top++] = par - 1;
}
if(par < high-1)
{
stack[top++] = par + 1;
stack[top++] = high;
}
}
free(stack); //释放空间
stack = NULL;
}
接下面对另外两种求得基准的方式进行说明:
(1)随机选取基准法(本质上实际就是选取序列中的任意一个数为temp的值):
void Swap(int *arr,int low,int high)
{
int temp;
temp = arr[low];
arr[low] = arr[high];
arr[high] = temp;
}
void Rand(int *arr,int low int high)
{
srand((unsigned int)time(NULL)); //随机种子
Swap(arr,low,rand() % (high-low)+low); //取low ~ high之间的任意数字和low发生交换
}
(2)三分取中法(实质上就是取起始位置,中间位置,末尾位置所指元素中间大的值为temp,如本例中上述三个位置的值为8、18、44,那么temp的值就取18)
void Mid_of_Three(int *arr,int low,int high)
{
int mid = (high - low)/2+low ;
if(arr[mid] > arr[low]) //arr[mid] <= arr[low]
{
Swap(arr,mid,low);
}
if(arr[low] > arr[high]) //arr[low] <= arr[high]
{
Swap(arr,low,high);
}
if(arr[mid] > arr[high]) //arr[mid] <= arr[high]
{
Swap(arr,mid,high);
}
}
关于快速排序还有两种优化方式,下面进行介绍。
一、如果待排序序列,在排序过程当中数据量变很小时,用直接插入排序
void Insert_sort(int *arr,int low,int high)
{
for(int i = low+1;i <= high;i++)
{
int temp = arr[i];
int j = 0;
for(j = i-1;j >= low;j--)
{
if(arr[j] > temp)
{
arr[j+1] = arr[j];
}
else
{
break;
}
}
arr[j+1] = temp;
}
}
void Quick(int *arr,int low,int high)
{
if((high - low)+1 < 100)
{
Insert_sort(arr,low,high);
return;
}
}
void Quick_Sort(int *arr,int len)
{
Quick(arr,0,len-1);
}
二、聚集相同元素基准法
void FocusNumPar(int *arr,int low,int par,int high,int *left,int *right)
{
if(low < high)
{
int parLeft = par-1;
for(int i = par-1;i >= low;i--)
{
if(arr[i] == arr[par])
{
if(i != parLeft)
{
Swap(arr,i,parLeft);
parLeft--;
}
else
{
parLeft--;
}
}
}
*left = parLeft;
int parRight = par+1;
for(int i = par+1;i <= high;i++)
{
if(arr[i] == arr[par])
{
if(i != parRight)
{
Swap(arr,i,parRight);
parRight++;
}
else
{
parRight++;
}
}
}
*right = parRight;
}
}
void Quick(int *arr, int low, int high)
{
int par = Partion(arr,low,high);
int left = 0;
int right = 0;
FocusNumPar(arr,low,par,high,&left,&right);
if(left >= low+1) //说明左边有两个数据以上
{
Quick(arr,low,left);
}
if(right <= high-1)
{
Quick(arr,right,high);
}
}
void Quick_Sort(int *arr,int len)
{
Quick(arr,0,len-1);
}
关于快速排序的三种方法及两种优化,读者若未能深入理解,可借鉴此博文https://blog.csdn.net/insistgogo/article/details/7785038
快速排序总体来说较为复杂,因此也深受面试官青睐,因此在学习过程中,必须熟悉其基本原理,并且要求能很快的写出相关代码。该排序算法在每次取基准之后,如果基准两边的子序列长度较为平均的话其时间复杂度为O(nlog 2n);如果序列自身有序,temp值为最大或最小,导致全部值都在temp的一侧,此时快速排序成为冒泡排序,其时间复杂度为O(n^2)。其空间复杂度为O(log2n),是一种不稳定的算法。