快速排序详解---C语言实现
作者:互联网
5 快速排序
核心思想
:初始时选择一个标志位(一般是第一个),让其余各位的值与该标志位的值相比较,刚开始从数组尾部开始选择小于该标志位的值放于前面,然后再从前面选择大于该标志位的值放于后面,如此循环,最终可以将数组以该标志位分为两部分,而后在对前后两部分循环操作。
实现过程(改进前)
:快速排序通俗点讲就是挖坑填补法,取一个值来将数组分成两部分,左侧都比所取标志的值小,右侧都比所取的标志值大,具体看下;
初始时,取数组的第一个元素为坑(分组标志),设置一个变量标记它,有int nValue = arr[0]
;
将arr[0]
标记后,则arr[0]
的位置空了出来,由于我们排序是准备从小到大
排序,即想所有相对小的在前面,所有相对大的在后面;因此我们就利用这个思想,初始时arr[0]
空着,因此我们从数组最后一个位置,由后向前
找一个比标志值nValue
小的数将arr[0]
处的位置填上,填上之后,数组后面位置又空出来一个位置,然后我们从数组前面找一个大于标志值nValue
得数放于数值后面的空位,我们可以看出此时我们需要两个变量来标记数组前面后后面的位置,重复此操作,当前面标记等于后面标记时,说明第一次分组已经完成,而且凉后变量相等的位置就是nValue
的位置,插入nValue
,由此数组将被分成两部分,之后再对前后两个部分进行重复操作,详细过程见下图:
1
选取标志变量nValue = arr[0]
,front
标记前面位置,back
标记后面位置,由于arr[0]
为选取的标志,故front
从第二个位置开始,如下所示:
初始时,arr[0]
位置空着,因此从back
位置由后向前
查找比nValue
小的数放前面,6 < 8
,即6
放于front
位置,front
后移如下图所示:
back
处的元素已经放于前面,因此back处此时的位置空出来,从前面front
处往后查找比nValue
值大的元素放于back
位置,然后back
往前移动一个位置,由于front
处的值为3
,3<8
,因此front
后移,·9
大于8
,将9
插入back
位置,如下所示:
将9
插入back
处位置后,9
的位置空了出来,因此从back
位置由后往前
找比nValue
值小的放于front
处,20>8
,back
往前移动,5<8
,插入front
位置,front
后移,如下图所示:
back
处的值插入到了前面,back
处空了出来,因此从front
位置往后查找比nValue
大的值放于back
位置,15>8
,直接插入,back
前移,如下图所示:
15
值插入了后面,因此front
处的位置空了出来,由后往前
查找比nValue
值小的放于front
位置,1<8
,直接插入,然后front
后移,如下如所示:
1
插入了前面的位置,因此1
处的位置空了出来,从front
处查找比nValue
值大的元素放于back
位置,然后back
前移,由于29>8
,直接插入,如下图所示:
从上图,我们可以看出,最终front
和back
指向了同一个地方,即循环终止条件,也就是nValue
所要插入的位置,将nValue
值插入,如下图所示:
总结
:从上述过程,可以看出,当遍历一遍数组后,原本无序的数组被依据nValue
大小分成了两部分,左侧的值都是小于nValue
,右侧的值都是大于nValue
,另外,当front = back
时循环终止,且此位置就是nValue
值要插入的位置。另外,我们对数组进行遍历一次就将数组分成了两部分,那么我们接下来对这两部分执行相同的操作,我们很容易想到可以使用递归实现,拆分数组如下图所示:
代码实现
#include <stdio.h>
void QuickSort(int arr[],int nBegin,int nEnd)
{
if(arr == NULL || nBegin >= nEnd) return;
int front = nBegin;
int back = nEnd;
printf("nBegin = %d nEnd = %d\n",nBegin,nEnd);
//选择标志
int nValue = arr[nBegin];
while(front != back)
{
//由后往前查找小于nValue的值插入
while(arr[back] > nValue && back > front)
{
back--;
}
if(front < back)
{
arr[front] = arr[back];
front++;
}
else
break;
//由前往后查找大于nValue的值插入
while(arr[front] < nValue && front < back)
{
front++;
}
if(front < back)
{
arr[back] = arr[front];
back--;
}
else
break;
}
//插入标志值
arr[front] = nValue;
//左侧数组
QuickSort(arr,nBegin,back-1);
//右侧数组
QuickSort(arr,front+1,nEnd);
}
void Print(int arr[],int nLen)
{
if(nLen <= 0) return;
for(int i = 0;i < nLen;i++)
{
printf("%d\t",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {1,78,2,54,56,2,78,12,4555,8};
QuickSort(arr,0,sizeof(arr)/sizeof(arr[0])-1);
Print(arr,sizeof(arr)/sizeof(arr[0]));
return 0;
}
不过,从上面数组可以看出,当选取的标志元素为数组中最小的时,并不能有效的将数组分割,因此对于快速排序标志值得选取决定了排序效率的高低,标志值得选取这里不再详述,感兴趣的可以自己搜索一下如何选取快速排序的标志值。
上面是改进前的快速排序实现,由前后两个变量来标记位置,下面我们来看一下只使用一个标记变量如何来完成快速排序,具体实现如下。
改进后的核心思想
:不从两个方向移动,只从最前面移动,此时标志比较的值基本选最后一个,从前移动时,设置一个标记位来记录小于标志值得最近的一个位置,如果值大于标志位,不更新位置,继续移动,如果值小于标志位,则交换此元素值与标志位的后一个元素值,更新标志位为后一个元素位置,如此循环。
默认选取标志位数组的最后一个元素
:int nValue = arr[nLen-1];(nLen 表示数组的长度)
实现过程
:取初始标记标量的下标为 int nFlag = -1(选取-1的目的是防止数组第一个元素大于nValue时不发生交换,因为每次交换时都是标志后面的值交换)
,见下图:
如上图所示,nFlag = -1, nValue = 6
,nFlag
之后元素arr[0]的值8 > 6
,不发生交换,i++
,向后移动一位,使得arr[1]
与nValue
比较,由于3 < 6
,即将arr[1]
与nFlag
之后的一个元素交换,然后更新标志位为nFlag++
,i++
,(注:只有发生交换才更新nFlag的位置)
如下图所示:
如上所示,此时i =2
,由于arr[2] = 9
,并且9 > 6
,因此,i++
,此时i = 3
,arr[3] = 15
,并且15 > 6
,重复上述操作继续移动,直到i = 5
,时,此时arr[5]=1
,1 < 6
,因此将arr[5]
与nFlag
后面一个位置的元素交换,交换后更新nFlag
,并且移动arr[5]
,如下图所示:
如上图所示,此时i = 6
,并且arr[6] = 5
,5 < 6
,故交换移动,重复上述操作,如下图所示:
如上图所示,此时i = 7
,并且arr[7] = 20
,20 > 6
,因此不发生交换,然后继续移动arr[i]
,此时arr[i]
,已经移动到数组尾部,因此最后将数组最后的值与nFlag
后面的值交换,如下图所示:
后续操作为重复上述操作…
代码如下
#include <stdio.h>
void Print(int arr[],int nLen);
void Swap(int *a,int *b)
{
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
void QuickSort(int arr[],int nBegin,int nEnd)
{
if(nBegin >= nEnd) return;
//定义比较的标准值
int nValue = arr[nEnd];
int nFlag = nBegin-1;//下标
//分组
int i;
for(i = nBegin;i < nEnd;i++)
{
//查找第一个小于nValue的
while(arr[i] > nValue && i < nEnd)
{
i++;//arr移动
}
if(i == nEnd)
break;
//交换
if(arr[nFlag+1] != arr[i])
Swap(&arr[nFlag+1],&arr[i]);
nFlag++;
}
//数组末尾交换
if(arr[nFlag+1] != arr[i])
Swap(&arr[nFlag+1],&arr[i]);
//Print(arr,nEnd+1);
//左
QuickSort(arr,nBegin,nFlag);
//右
QuickSort(arr,nFlag+2,nEnd);
}
void Print(int arr[],int nLen)
{
if(nLen <= 0) return;
for(int i = 0;i < nLen;i++)
{
printf("%d\t",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {1,78,2,54,56,2,78,12,4555,8};
QuickSort(arr,0,sizeof(arr)/sizeof(arr[0])-1);
Print(arr,sizeof(arr)/sizeof(arr[0]));
return 0;
}
最好时间复杂度
:O(nlogn)
最坏时间复杂度
:O(n²)
平均时间复杂度
:O( nlogn)
空间复杂度
:O(logn)~O(n)
稳定性
:不稳定
适用场合
:数组量大且较无序
标签:arr,nValue,int,back,C语言,---,详解,数组,front 来源: https://blog.csdn.net/qq_43450920/article/details/118074429