快速排序简单思路及c语言实现——超详细

快速排序简单思路及c语言实现

快速排序思路:

主体思想:

  • 分治思想,一分为二处理数据。
  • 函数递归调用

首先拿一个数组来举例子:
假设我们创建一个长度为10的int数组:

int num[10]={
     4,3,7,1,9,6,2,8,0,5};

如果要对这个数组中的数据进行从小到大的排序,那么我们用快速排序实现的思路如下:

首先,我们用两个变量分别来代表左右两个“哨兵”。这里我们假设创建两个int型的变量i和j,分别代表左哨兵和右哨兵。将他们的值分别初始化为数组最左边和最右边的索引位置。
这里数组最左边的位置为:0
这里数组最右边的位置为:9

int i=left,j=right;   //left和right分别代表最左边和最右边的位置。

接下来我们再来定义一个很重要的数值变量:基准数
我们默认基准数的数值为数组最左边也就是第一个数的值。

int temp=num[left] // "or:"    int temp=num[i]        "because Init int i==left"

现在我们要做的事情是把所有大于基准数的数值放在基准数右边,把所有小于基准数的数值放在基准数左边(但这些数在排序过程中不一定保持有序)

如何实现以上想法呢:

首先我们让右边的哨兵行动,也就是先对变量j进行操作(前提是基准数设置为最左边的数)
注意:这里一定要先对变量j进行操作,也就是先让右哨兵行动,否则可能会出现问题,这里具体会在后面进行分析。

右哨兵j的任务是从最右边开始找出所有比基准数小的数并交换到左边,因为我们想要实现的是,让右边只存在比基准数大的数。

不过在这个过程中不能让右哨兵和左哨兵相遇,否则立即停下。
那么这该如何实现呢:

while(i<j&&a[j]>temp)//不让j和i相遇并且去寻找比基准数temp小的数
{
     
 j--;
}
//跳出循环有两种可能:
//1.找到了比基准数temp小的数
//2.哨兵j和哨兵i相遇了
//对两种情况进行判断:

if(i<j)//如果j和i没有相遇,那么把j找到的这个比temp小的数放到左边哨兵i的位置上
{
     
   a[i]=a[j];
   i++;//同时左哨兵i原来位置上的数已经是j位置上的数了,所以让i哨兵向右走一步
}

在进行了第一次操作后,temp基准数位置上已经被j哨兵查找到的数覆盖和代替了,这时数组中没有了值为temp 的数值,不过在最后我们要将temp数还回去,称作归位。不过这是最后才进行的操作,先暂时跳过

在右哨兵进行完搜索操作后,我们要让左哨兵i进行类似的搜索操作,即就是:让哨兵i从最左边开始找出比基准数temp值大的数,并放到右边j的位置上,因为此时j位置上的数刚才已经被放到了原来i的位置上了,所以现在j位置上的数需要被重新覆盖。
那么代码和刚才右哨兵j 的操作大同小异:

while(i<j&&a[i]<temp)//这里省略注释,思路同上一段代码相似
{
     
i++;
}
if(i<j)
{
     
a[j]=a[i];
j--;
}

左右哨兵i,j的搜寻操作我们已经定义并实现了,那么这样的操作需要重复进行到什么时候结束呢?
答案是当i==j时结束,即当哨兵i和j相遇时搜寻结束,原因是i和j通过一次或多次搜索之后,i的左边已经不存在比temp大的数了,同时j的右边也不存在比temp小的数了。
换言之就是:此时i左边全是比temp小的数,右边全是比temp大的数了,那么此时基本上已经实现了我们想要的结果,于是这是该将基准数归为到i和j相遇的位置上了。

a[i]=temp;//将基准数归位,i也可以替换为j,因为此时i==j

那么通过刚才的逻辑,我们把上面两哨兵的搜索操作放入循环得到:

while(i<j)//将i和j的搜索操作放入判定条件为i和j不相遇的循环中
{
     
  while(i<j&&a[j]>temp)//不让j和i相遇并且去寻找比基准数temp小的数
  {
     
    j--;
  }
//跳出循环有两种可能:
//1.找到了比基准数temp小的数
//2.哨兵j和哨兵i相遇了
//对两种情况进行判断:

  if(i<j)//如果j和i没有相遇,那么把j找到的这个比temp小的数放到左边哨兵i的位置上
  {
     
    a[i]=a[j];
    i++;//同时左哨兵i原来位置上的数已经是j位置上的数了,所以让i哨兵向右走一步
  }

  while(i<j&&a[i]<temp)//这里省略注释,思路同上一段代码相似
  {
     
   i++;
  }


  if(i<j)
  {
     
   a[j]=a[i];
   j--;
  }
  
}//循环结束,i和j相遇

//将基准数归为:
a[i]=temp;//将基准数归为,i也可以替换为j,因为此时i==j

如此一来,一次分治的操作就完成了,我们成功得到了一边小于temp一边大于temp的数组
不过这时数据仍然不是有序排列的,所以我们要用递归的思想,继续分别对temp左右两边的数进行如上相同的重复操作,直到任意两个数都能保持左边小,右边大,如此则可以保证整个数组都有序排列。

那么我们需要把上面的代码稍微改造一下封装成一个函数并实现递归调用:

QuickSort(int num[],int left,int right)
{
     
if(left>=right)//如果左边比右边大,或者等于右边,则递归停止,并返回
   return;

int i=left,j=right;
int temp=a[left];

while(i<j)//将i和j的搜索操作放入判定条件为i和j不相遇的循环中
{
     
  while(i<j&&a[j]>temp)//不让j和i相遇并且去寻找比基准数temp小的数
  {
     
    j--;
  }
//跳出循环有两种可能:
//1.找到了比基准数temp小的数
//2.哨兵j和哨兵i相遇了
//对两种情况进行判断:

  if(i<j)//如果j和i没有相遇,那么把j找到的这个比temp小的数放到左边哨兵i的位置上
  {
     
    a[i]=a[j];
    i++;//同时左哨兵i原来位置上的数已经是j位置上的数了,所以让i哨兵向右走一步
  }

  while(i<j&&a[i]<temp)//这里省略注释,思路同上一段代码相似
  {
     
   i++;
  }


  if(i<j)
  {
     
   a[j]=a[i];
   j--;
  }
  
}
//循环结束,i和j相遇

//将基准数归为:
a[i]=temp;//将基准数归为,i也可以替换为j,因为此时i==j

//对左右两边的数进行递归,继续实现排序
QuickSort(num,left,i-1);
QuickSort(num,i+1,right);
}

//主函数调用快速排序:
void main()
{
     
int num[10]={
     4,3,7,1,9,6,2,8,0,5};
int i=0;
QuickSort(num,0,9);
for(i=0;i<=9;i++)
{
     
printf("%d",num[i]);
}

输出结果为:0123456789

最后回到前面提出的那个问题上来:为什么当基准数是左边第一个时,要先从最右边开始搜索?

原因是我们在进行第一次搜索的时候会牺牲一个数,有一个数会被覆盖,比如j先走,那么当j找到比temp小的数的时候,会把这个数覆盖到i的初始位置上,也就是把基准数位置上的数替换成找到的第一个数,那么因为被替换调的是基准数,所以即使它牺牲了,我们也在temp中备案了它的值,最后能将它归位。
而如果让i先去搜索,那么当i搜索到第一个比temp大的值的时候,同样也会用这个值把初始位置上的值替换掉。也就是把数组中最后一个数直接覆盖掉,那么这个数也牺牲了,而且我们并没有对它的值进行备案,所以最后没办法把这个一开始就被覆盖掉的值归位。

关于快速排序平均时间复杂度的分析

先给出快速排序平均时间复杂的结果:NlogN

由于快速排序使用的是分治思想,所以我们很容易地可以给出递归方程:
T(n)=T(k-1)+T(n-k)+p(n)
这里需要注意的是:这不同于一般的二分查找,因为我们无法确定分治的位置,在第1-n个数的位置都有可能出现,所以我们假设分治的位置左边右k-1个数,那么左边需要分治的规模就是k-1,而右边则是n-k,另外还需要记得加上问题拆分所导致的额外的线性时间复杂度p(n)。
那我们来考虑平均的情况,由于k的位置并不确定,并且等可能的出现在1-n内所以我门又可以将平均情况的T(n)写成:

T ( n ) = 1 n ∑ k = 1 n [ T ( k − 1 ) + T ( n − k ) ] + P ( n ) T(n)=\frac{1}{n} \sum_{k=1} ^n [T(k-1)+T(n-k)]+P(n) T(n)=n1k=1n[T(k1)+T(nk)]+P(n)
其实进行化简之后等于2倍将T(0)到T(n-1)求和的结果,即
T ( n ) = 2 n S ( n − 1 ) + P ( n ) S ( n − 1 ) = n 2 T ( n ) − c n 2 2 S ( n − 1 ) = T ( n − 1 ) + S ( n − 2 ) = n + 1 2 T ( n − 1 ) − c ( n − 1 ) 2 2 T(n)=\frac{2}{n}S(n-1)+P(n) \\ S(n-1)=\frac{n}{2}T(n)-c\frac{n^2}{2} \\\qquad\quad S(n-1)=T(n-1)+S(n-2) \\\qquad\qquad\qquad\quad\quad\qquad=\frac{n+1}{2}T(n-1)-c\frac{(n-1)^2}{2} T(n)=n2S(n1)+P(n)S(n1)=2nT(n)c2n2S(n1)=T(n1)+S(n2)=2n+1T(n1)c2(n1)2
联立上面两个式子解得:
T ( n ) = n + 1 n T ( n − 1 ) + c 2 n − 1 n T(n)=\frac{n+1}{n}T(n-1)+c\frac{2n-1}{n} T(n)=nn+1T(n1)+cn2n1

你可能感兴趣的:(笔记,快速排序)