划分算法

来源:互联网 发布:淘宝裹胸 编辑:程序博客网 时间:2024/05/17 06:18
        划分是快速排序的一个根本机制,在介绍快速排序之前,先了解一下划分。
       划分数据就是把数据分为两组,使所有关键字大于特定值的数据项在一组,使所有关键字小于特定值的数据项在另一组。
       很容易想象划分数据的情况。比如可以将支援记录分为两组:家住办公地点15公里以内的雇员和住在15公里以外的雇员。或者学校管理者想要把学生分成年级平均成绩高于60分和低于60分的两组,一次来判定哪些学生应该在系主任掌握的名单里,等等。
       在完成了划分之后,数据还不能称为有序:这只是把数据简单的分成了两组。但是,数据还是比没有划分之前更接近有序了。注意划分是不稳定的。这也就是说,每一组中的数据项 并不是按照它原来的顺序排列的。事实上,划分往往会颠倒组中一些数据的顺序。
       划分的Java程序:
      
public int pattitionIt(int left, int right, long pivot)
       {
             int leftPtr=left -1;                                 //right of first elem
             int rightPtr=right+1;                           //left of pivot
             while(true)
             {
                   while(leftPtr<right &&                 //find bigger item
                              theArray[++leftPtr]<pivot)  // theArray是需要进行划分的数组
                          ;                                                //(nop)
                   while(rightPtr>left &&               //find smaller item
                                theArray[--rightPtr]>pivot)
                            ;                                             //(nop)
                   if(leftPtr>=rightPtr)                      //if pointers cross,
                         break;                                    //partition done
                   else
                         swap(leftPtr,rightPtr);         //swap elements
             }                                                         //end while(true)
             return ledtPtr;
       }                                                               // end partitionIt()

       public void swap(int dex1, int dex2)    //swap two elements
       {
                long temp;
                temp=theArray[dex1];
                theArray[dex1]=theArray[dex2];
                theArray[dex1]=temp;
       }

         划分的过程不一定要把数组分成大小小相同的两半;这取决于枢纽以及数据的关键字的值。有可能一组中的数据项个数多于另一组中的数据项个数。

划分算法:
      
划分算法由两个指针开始工作,两个指针分别指向数组的两头(此指针非彼指针)。在昨天的指针,leftPtr,向右推移,而在右边的指针,rightPtr,向左推移。
       实际上,leftPtr初始化时是在第一个数据项的左一位,rightPtr是在最后一个数据项的右边一位,这是因为在它们工作之前,它们都要分别的加一和减一。
       停止和交换
       当leftPtr遇到比枢纽小的数据项时,它继续右移,因为这个数据项的位置已经处在数组的正确一边了。但是,当遇到比枢纽大的数据项时,它就停下来。类似的,当rightPtr遇到大于枢纽的数据项时,它继续左移,但是当发现比枢纽小的数据项时,它也挺下来,两个内层的while循环,第一个应用与leftPtr,第二个应用于rightPtr,控制这个扫描过程。因为指针退出了while循环,所以它停止移动。下面是一段扫描不在适当位置上的数据项的简化代码:
       while(theArray[++leftPtr]<pivot)  //find bigger item
          ;
       while(theArray[--right>piovt])      //find smaller item
          ;
       swap(leftPtr,rightPtr);
       第一个while 循环在发现比枢纽大的数据项时退出;第二个循环在发现比枢纽小的数据项时退出,当这两个循环都退出之后,leftPtr和rightPtr都指着在数组错误一方位置上的数据项,所以交换这两个数据项。
       交换之后,继续移动两个指针,当指向的数据项在数组的错误一方时,再次停止,然后交换数据项。所有的这些操作都包含在一个外部循环中。当两个指针最终相遇的时候,划分过程结束,并且退出这个外层while循环。
       处理异常数据
      
如果肯定数组最右端的数据项小于枢纽,并且数组最左端的数据项大于枢纽,那么前面看到的简化的while循环会很好的执行。遗憾的是,可能用算法来划分的那些数据并没有排列得这么好。
       例如,如果所有的的数据项都小于枢纽,leftPtr变量将会遍历整个数组,徒劳地寻找大于枢纽的数据项,然后跑出数组的最右端,产生数组越界的异常。当所有的数据都大于枢纽的时候,类似的糟糕结果也会发生在rightPtr上。
       为了避免这些问题,必须在while循环中增加数组边界的检测:在第一个循环中增加leftPtr<right,第二个循环中增加rightPtr>left。
       精巧的代码
       这个while循环中的代码相当精巧。举例来说,想要从内部循环条件中出去加1操作符,并且用这个加1操作符代替空操作指令语句。例如,可以把如下的代码:
       while(leftPtr<right &&                 //find bigger item
                              theArray[++leftPtr]<pivot)  // theArray是需要进行划分的数组
                          ;  
       改为
       while(leftPtr<right &&                 //find bigger item
                              theArray[leftPtr]<pivot)  // theArray是需要进行划分的数组
                        ++ leftPtr ;
        对于另一个内部while循环的改变是相似的。这些改变使指针的初始值分别设为left和right成为可能,这比设为left-1和right+1要更为清晰。
        但是,这些改变导致只有在满足条件的情况下指针才会加1。而算法要求指针在任何情况下都必须移动,所以需要在外层的while循环中增加两个附加的语句强迫指针变换。空操作指令是最有效的解决办法。
        相等的关键字
      
下面是要在partitionIt()方法中做的另一个细微的改变。如果想要对所有与枢纽相等的数据项运行partitionIt()方法,将发现每一次比较都会产生一次交换。交换关键字相等的数据项看起来是浪费时间的。while循环中对枢纽和数组数据项进行比较的<和>操作符产生了这种额外的交换。然而,假设使用<=和>=操作符,这确实防止了相等数据项的交换,但是这也使得在算法结束时leftPtr和rightPtr停在了数组的两端。
       划分算法的效率:
        划分算法的执行时间为O(N)。两个指针开始时分别在数组的两端,然后以或大或小的恒定速度相向移动,停止移动并且在移动的过程中交换。当两个指针相遇时,划分完成。如果要划分两倍树目的数据项,指针以同样的速率移动,但是需要比较和交换两倍数目的数据项,因此这个过程耗时也是两倍。从而,运行时间和N成正比。
        更为特别的,每一次划分都有N+1或者N+2次的比较。每个数据项都由这个或那个指针参与比较,这产生了N次比较,但是在指针发现它们已经彼此“越过”之前,他们已经移动过头了,所以在划分完成之前多了一次或者两次额外的比较,比较的次数不取决于数据是如何排列的(除了在扫描结束时的一次或者两次额外比较的不确定性)。
        但是,交换的次数确实是取决于数据是如何排列的。如果数据是逆序排列的,并且取得枢纽把数据项分为两半,那么每一对值都需要交换,也就是N/2次交换。
        对于任意的数据,在一次划分中交换的次数将小于N/2,即使一半的数据项小于枢纽,一半的数据项大于枢纽。这是因为总会有一些数据项在正确的位置上。如果枢纽比大多数的竖条都大(或者小),会有更少的交换次数,这是因为只有很少的大于(或者小于)枢纽的数据项需要交换。对于任意的数据,在平均情况下,大约执行数据项最大数目的一般的交换操作。
        尽管交换的次数少于比较的次数,但它们都是和N成正比的。         
原创粉丝点击