基础算法学习(一)__几种排序:选择、插入、冒泡和快排

     团队新成员要开始算法培训了,去年这个时候我也是在学数据结构,也了解了一些算法。但是那个时候学数据结构和算法就是打打酱油而已,掌握的不好,这次正好趁这个机会跟着新人一起学学。不求自己能有多大的突破,一步一步坚实地走下去就够。

    今天的任务就是几种排序方法:选择、插入、冒泡和快排,学习的教材是Matrix67的blog上介绍的从零开始学算法:十种排序算法介绍(上)和《算法导论》。

    先开始选择排序

    对n个数进行选择排序,先另开一个空间用于存放排好的数sort,再开一个temp,用于存放当前最小值。考虑最坏的情况,这n个数是从大到小排列的,而我们需要从小到大的顺序。选择第一个数时,先看着n个数里面的第一个,这个数我们称之为n_1。n_1是当前最大的数,它与剩下n-1个数进行比较,第一次比较n_1和n_2大,n_2小,则将n_2赋给temp。之后,再将n_2与n_3比较,n_3小,则将n_3赋给temp。一直到temp里存放的是n_n-1时,再比较n_n-1和n_n,此时仍是n_n小,则将n_n赋给temp。此时已进行n-1次比较,n-1次赋值。这时,再将temp的值赋给我们sort的第一个。第一轮的排序结束,n-1次比较,n次赋值。同理,在第i轮排序时,n-i次比较,n-i+1次赋值。一共我们会进行n轮比较,则总的比较次数n(n-1)/2,赋值次数n(n+1)/2。

   插入排序:

    同样的,我们假设最坏的情况,n个数从大到小排好的,我们需要从小到大的排序。第i轮时,这n个数的情况是 n_i-1…n_1  n_i    n_1… n_n ,前i-1个数是按照从小到大。此时,我们看n_i,将n_i与前面的i个数比较,最坏的情况,n_i比前面的i-1个数都要小。此时,我们比较了i-1次,还需要将n_i放在最首位。步骤是:n_i赋给temp,前i-1个数往后移(i-1次赋值),再将temp里面的值赋到首位,一共有i+1次赋值。即是说,第i轮排序时,我们比较了i-1次,复制了i+1次。所以,总共的,我们比较了n(n-1)/2次,赋值了 n(n+3)/2次。

    冒泡排序:

    依然是最坏的情况,n个数从大到小排好的,我们需要从小到大的排序。第一个数n_1首先与n_2比较,n-1大,我们进行交换,比较一次,赋值三次。此轮,我们比较了n-1次,赋值3(n-1)次。在第i轮说,最后的i-1个数已经按照从小到大顺序排列,因为我们每轮都是将最大的一个排在后面。因此这轮我们只需比较前n-i+1个数找出最大的,并将其放在n-i+1的位置。此轮需比较n-i次,赋值3(n-i)次。总共我们需要比较n(n-1)/2次,赋值 3n(n-1)/2。

    以上三种排序方法的时间复杂度都是O(n^2)。

    快速排序:

    快速排序的学习先是看的Matrix67的blog,看得我有点困惑--!于是转向了算法导论,只是看了算法导论中关于快速排序的内容,就感受到了算法导论的强大..严格的公式推理与证明,有理有据,很有说服力。下面写一下自己学习快速排序的收获。

    快速排序的思路不难,运用了递归的思想。假设我们要对数组A[p…r]排序,就将它划分为A[p…q-1]和A[q+1…r]两个数组,使得A[p…q-1]里面的元素都小于A[q],A[q+1…r]里面的元素都大于A[q]。下标q在这个划分过程中被计算出,也就排在所有比他小的元素的后面。之后再进行递归调用快速排序,对子数组A[p…q-1]和A[q+1…r]排序。

      划分的过程参考算法导论的写法。PARTITION过程,它对子数组A[p…r]进行就地重排:

     PARTITION(A,p,r)

1    x←A[r]

2    i←p-1

3    for j←p to r-1

4        if(A[j]<=x)

5             i←i+1;   

6             A[i]↔A[j];//保证i指向的是比A[x]小的元素的尾

7    A[i+1]↔x           //将x移到比他大和比他小的元素中间

8    return i+1;        //返回此次排序后q的位置

     以上结论的证明这里不写,算法导论上有很详细的过程。

  •      快速排序的复杂度也是一个很有意思的东西。我们考虑两种情况,最坏和平均。
  •      最坏的情况下,我们每次选择的主元(pivot element,被比较的数),都是当前要比较序列里最大或小的数。那么这种情况下的一共要比较的次数就为1+2+3+…+n-1,也就是n(n-1)/2,和上面三种排序方法在时间复杂度上就没啥区别了。但是一般来说,我们的RP也不会老那么低,总是遇到那么多些纠结的情况,平均情况下的时间复杂度也就也必要算一算了。
  •     在算平均情况下的时间复杂度前,我们有必要先明确一点:快排中,我们比较的次数肯定比交换的次数多,因此这里仅讨论比较的次数。那么在什么情况下两个数会进行比较呢?为了从数学的角度去求解这个问题,我们先定义:Z(ij)为A[i]到A[j]之间所有的元素(包括A[i],A[j]),P(ij)为A[i]和A[j]会进行比较的概率。从上面的比较过程我们可以看出,如果A[i]A[j]进行比较过,那么Z(ij)之间的关键字要么是A[i],要么是A[j]。如果这个关键字x不是A[i]或者A[j],那么A[i]A[j]肯定在将这个x作为pivot element的比较中分开了,他们之间就不会再有机会进行互相比较了。举个实例来说,1到10这10个数,我们第一次将6作为pivot element,划分出的两个子数组为{1,2,3,4,5}和{7,8,9,10}。那么4和8就不会再有机会比较了,因为他们已经分别位于两个子数组中了。但是1和6比较过,6和7也比较过。那么Z(ij)之间的关键字是A[i]或A[j]的概率是什么呢?
  •      P[ij]=P{Z(ij)之间的关键字是A[i]或A[j]}
               =P{Z(ij)之间的关键字是A[i]}+P{Z(ij)之间的关键字A[j]}
  •            =2/(j-i+1)

     那么,整个排序过程的比较次数

E=  \sum_{i=1}^{n-1}{} \sum_{j=i+1}^{n}{2/(j-i+1)}

     这个和式的求解此处不再解释。最终的结果就是快排的时间复杂度O(nlgn)。

以上就是我学习的第一课…学完了就去复习下C语言吧,发现自己C语言都忘光了,当初学的时候就没学好未命名

posted @ 2009-11-30 21:30  唐颖  阅读(602)  评论(0编辑  收藏  举报