快速排序(详细)
记录一下自己学习算法的过程包括学习中遇到的问题与困难。
什么是快速排序?
先通过一个具体的例子来理解:
下面是一个数组一共是个数给这十个数进行排序
6 | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 |
---|
①确定基准数
(什么是基准数呢?)
你可以理解为一个标杆(因为通常涉及到排序就要涉及到比较大小,比较就需要知道跟谁比而这这个基准数就是 你要与之比较的)
基准数 可以是这里面的任何数但方便编程方便理解就将第一个数先设为基准数
一开始这个6就是基准数。然后我们将这个基准数给它移动到某一个位置而且要求基准数左侧都要小于 基准数右侧都要大于 基准数。
6 | 1 | 2 | 7 | 9 | 3 | 4 | 5 | 10 | 8 |
---|
注:黄色部分为基准数。
②探测 与 交换
从序列两端开始“探测”首先要先从右往左寻找一个小于6(基准数)的数。
再从左往右寻找一个大于6(基准数)的数,然后将这个两个数进行交换。
注:顺序很重要假设基准数为最左侧一定要先从最右侧开始探测,这个问题后面再解释
用两个变量i,j分别表示序列的最左边和最右边(借用书中非常形象的描述方法)“哨兵i”与“哨兵j”,“哨兵j” 从最右侧开始探测(记住“哨兵j”它要寻找的是一个小于基准数的值)“哨兵j” 找到了 5然后停下,“哨兵i ”开始探测(记住“哨兵i”它要寻找的是一个大于基准数的值)“哨兵i”找到了7然后停下 将两名哨兵所找的数进行交换。
第一次探测 交换后的结果:
6 | 1 | 2 | 5 | 9 | 3 | 4 | 7 | 10 | 8 |
---|
两名哨兵再次分别出发,“哨兵j”找到了4 “哨兵i”
找到了9 将两个数进行交换。
第二次探测 交换后的结果:
6 | 1 | 2 | 5 | 4 | 3 | 9 | 7 | 10 | 8 |
---|
两名哨兵第三次出发,这里要注意下一“哨兵j”找到了3
“哨兵i”从左往右依次探测在3这个位置与“哨兵j”相遇了
将3与基准数进行交换。结果如下:
3 | 1 | 2 | 5 | 4 | 6 | 9 | 7 | 10 | 8 |
---|
这里最开始的基准数 6 完成了它使命。这里实现了6的左侧都小于6右侧都大于6。
③分组
观察这个序列就可以分为两个序列了
3 | 1 | 2 | 5 | 4 |
---|
与
9 | 7 | 10 | 8 |
---|
接下来便是依次处理这两个序列,重复方法②
3 | 1 | 2 | 5 | 4 |
---|
以最左侧3为基准数然后第一次 探测与交换
2 | 1 | 3 | 5 | 4 |
---|
基准数3 也光荣完成使命。又可以分成两个序列。
2 | 1 |
---|
与
5 | 4 |
---|
依次处理这两个序列后得到序列:
1 | 2 | 3 | 4 | 5 | 6 | 9 | 7 | 10 | 8 |
---|
然后处理
9 | 7 | 10 | 8 |
---|
这里有个小点可能会出错:哨兵是从两端开始探测的也就是说
这里的8会直接被“哨兵j”探测到然后直接与“哨兵i”所探测到的10
进行交换
9 | 7 | 8 | 10 |
---|
然后重复直到基准数9归位
8 | 7 | 9 | 10 |
---|
基准数9完成任务。
再次分组
8 | 7 |
---|
与
10 |
---|
最终结束了排序
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|
!
是不是会感觉很麻烦,其实是我本人太啰嗦我恨不得将整个过程都给罗列出来,重要的是先理解这个思路然后使劲去理解源代码。
上代码:
#include<stdio.h>
int a[101],n;
void quicksort(int left,int right)//定义一个自定义函数 快速排序
{
int i,j,t,temp;//temp中存放的就是基准数 ,t就是交换时临时存放的一个变量
if(left>right)
return;//跳出 返回值为void的函数详细看补充
i=left;
j=right;
temp=a[left];
while(i!=j)
{
while(a[j]>=temp&&i<j)//这里顺序很重要详细看补充,
// 还有这里为什么是 >= 我们“哨兵j”从左往右探寻找的是要小于基准数的值,为了执行满足循环条件找到哪个值在数组中所在位置所以条件要写成>=
j--;
while(a[i]<=temp&&i<j)
i++;
if(i<j)//当哨兵i与哨兵j 没有相遇
{
//这里就是算法中的交换
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
a[left]=a[i];//当哨兵i与哨兵j 相遇时交换也就是说基准数已经归位
a[i]=temp;
quicksort(left,i-1);//函数的递归,也就是相当于第一次 基准数就为后分组然后再重复执行
quicksort(i+1,right);//分组后的另一半。
return;
}
int main()
{
int i,j;
scanf("%d",&n);//读入有多少个原素
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);//将需要排序的序列读入 这里记得要加空格 方便 看排序结果
}
quicksort(1,n);//函数的调用,1与n分别为left与right
for(j=1;j<=n;j++)
{
printf("%d ",a[j]);//这里记得要加空格 方便 看排序结果
}
return 0;
}
补充问题
return;
这里的return;是不带返回值的return语句
它可以中断中断返回值为void类型的函数的执行。
void quicksort(int left,int right)//定义一个自定义函数 快速排序
{
int i,j,t,temp;//temp中存放的就是基准数 ,t就是交换时临时存放的一个变量
if(left>right)
return;//跳出 返回值为void的函数详细看补充
i=left;
j=right;
temp=a[left];
.......................
补充问题
为什么要当基准数在最左侧时要先让“哨兵j”去探测再让“哨兵i”去探测
可以试着假设一下先从左侧开始探测
2 | 1 | 4 | 8 |
---|
探测交换后的结果
4 | 1 | 2 | 8 |
---|
这次基准数2并没有完成它的使命,而且基准数2也并没有归位。
继续:将4设置为基准数
8 | 1 | 2 | 4 |
---|
越来越离谱
但如果你并不是以最左侧为基准数的话结果就另当别论了具体结果具体分析。
最后还想讲的是
如果原理可以理解但是代码不理解,不妨可以直接一个数一个数代入写出每一步骤的值,相信你一定会明白的,我就是一步一步代入的。
记录自己学习的过程。
一定会好的。
需要《啊哈!算法》pdf版可以私聊,