【问题标题】:Algorithm to partition the array using the pivot element使用枢轴元素对数组进行分区的算法
【发布时间】:2015-06-26 07:19:41
【问题描述】:

我试图从一些 java 编程书籍中解决以下编程练习

Write 方法使用第一个元素(称为枢轴)对数组进行分区。分区后,列表中的元素重新排列,使得枢轴之前的所有元素都小于或等于枢轴,枢轴之后的元素都大于枢轴。该方法返回新列表中枢轴所在的索引。例如,假设列表是 {5, 2, 9, 3, 6, 8}。分区后,列表变为{3, 2, 5, 9, 6, 8}。以最多进行array.length 比较的方式实现该方法。

我已经实现了解决方案,但它需要的远不止array.length 比较。

这本书本身有解决方案,但不幸的是它完全是错误的(不使用某些输入)。我已经看到了this 类似问题的答案,并且理解了快速排序算法的“征服”部分,但是在这个算法中,值使用中间值进行分区,但在我的情况下,需要使用第一个数组值作为枢轴。

【问题讨论】:

  • 你有什么问题?
  • 不,我的意思是这是分区的一般形式,它只需要 O(n) 次比较,因为你会将枢轴与所有其他元素进行比较。
  • 你必须向我们展示你在代码中的最佳尝试,并准确解释你得到了什么意想不到的结果。
  • 这是来自快速排序的分区。您可以在wikipedia's quick sort article 中找到它的伪代码

标签: java arrays algorithm


【解决方案1】:

这是来自链接答案(改编自 source here)的枢纽例程。

    int split(int a[], int lo, int hi) {
        // pivot element x starting at lo; better strategies exist
        int x=a[lo]; 

        //  partition
        int i=lo, j=hi;
        while (i<=j) {
            while (a[i]<x) i++;
            while (a[j]>x) j--;
            if (i<=j) swap(a[i++], a[j--]);
        }

        // return new position of pivot
        return i;
    }

该算法中元素间比较的次数要么是n,要么是n+1;因为在每次主循环迭代中,ij 以恰好 c 个单位靠得更近,其中 c 是在每个内部循环中执行的比较次数while 循环。看看那些内部循环 - 当它们返回 true 时,ij 移动了 1 个单位。如果它们返回 false,那么在主循环结束时,ij 将因为交换而靠近 2 个单位。

这个 split() 可读且简短,但它也有一个非常糟糕的最坏情况(即,枢轴在任一端结束;请点击第一个链接查看它的结果)。如果数组已经向前或向后排序,就会发生这种情况,这实际上非常频繁。这就是为什么其他枢轴位置更好的原因:如果您选择 x=a[lo+hi/2],最坏的情况将不太常见。更好的是do like Java,并花一些时间寻找一个好的支点来避开最坏的情况。如果您点击 Java 链接,您将看到一个更复杂的数据透视例程,它可以避免在有许多重复元素时做额外的工作。

【讨论】:

  • 你有一个问题:例如,数组:{2,3,4,7,1,8,9},pivot 为 2,j 将向左移动直到达到 1。但我将始终为 2,因为此条件永远不会成立:while (a[i]
  • 有了那个数组,i和j本来就是06,当达到if时,就是04;因为0 &lt;= 4,这些索引将被交换,产生{1,3,4,7,2,8,9}。下一次循环时,i 和 j 将从13 开始,并以i=1 结束循环,不执行任何交换。正如预期的那样,子数组{1} 中的所有元素都小于子数组{3,4,7,2,8,9} 中的元素。问题出在哪里?
【解决方案2】:

似乎算法(取自“算法简介第三版”)可以实现如下(C++)在Java的泛型中应该是类似的:

template <typename T> void swap_in_place(T* arr, int a, int b)
{
    T tmp = arr[a];
    arr[a] = arr[b];
    arr[b] = tmp;
}

template <typename T> int partition(T* arr, int l, int r)
{
      T pivot = arr[r];
      int i = l-1;
      int j;
      for(j=l; j < r; j++) {
         if (arr[j] < pivot /* or cmp callback */) {
               // preincrement is needed to move the element
             swap_in_place<T>(arr, ++i, j);
          }
       }
       // reposition the pivot
       swap_in_place(arr, ++i, j);
       return i;
    }
template <typename T> void qsort(T* arr, int l, int r) 
{
    if ( l < r ) {
       T x = partition<T>(arr, l, r);
       qsort(arr, l, x-1);
       qsort(arr, x+1, r);
     }
 }

但是,它是一个简单的伪代码实现,我不知道它是否是最好的选择。也许 (l+r)/2 会更合适。

【讨论】:

    【解决方案3】:

    非常简单的双端队列解决方案:

    int [] arr = {3, 2, 5, 9, 6, 8};
    
    Deque<Integer> q = new LinkedBlockingDeque<Integer>();
    
    for (int t = 0; t < arr.length; t++) {
        if (t == 0) {
            q.add(arr[t]);
            continue;
        }
    
        if (arr[t] <= arr[0])
            q.addFirst(arr[t]);
        else
            q.addLast(arr[t]);
    }
    
    for (int t:q) {
        System.out.println(t);
    }
    

    输出是:

    2
    3
    5 <-- pivot
    9
    6
    8
    

    【讨论】:

    • 这不是一个好主意——快速排序可以高效地实现为就地排序算法,不需要额外的内存。您正在使用复杂的数据结构(LinkedBlockingDeque 的每个元素大于 2 个整数)。
    • @tucuxi 我同意这不是最佳解决方案,但由于其简单的性质,我发布了此答案。但是,我并不完全同意您的第二个说法,尽管它可以忽略不计,但 Quicksort 确实需要额外的内存量 O(logn)
    • @lan2thedv O(logn) 额外内存与您的 O(n logn) 额外内存建议相比微不足道。如果我们假设 int 是指针的大小,并且您的队列每个元素使用 2 个指针,那么您的实现将占用 3*n 额外空间 - 并且将具有可怕的内存位置来引导。如果因为简单而要使用额外的内存,至少使用普通数组。
    【解决方案4】:

    我在基于 Pivot 的分区上制作的视频我解释了两种分区方法。

    https://www.youtube.com/watch?v=356Bffvh1dA

    并基于您的(另一种)方法

    https://www.youtube.com/watch?v=Hs29iYlY6Q4

    对于代码。这是我为枢轴作为第一个元素而编写的代码,它需要 O(n) 次比较。

    void quicksort(int a[],int l,int n)
    {   
        int j,temp;
        if(l+1 < n)
        {
            int p=l;
            j=l+1;
            for(int i=l+1;i<n;++i)
            {
                if(a[i]<a[p])
                {
                    temp=a[i];
                    a[i]=a[j];
                    a[j]=temp;
                    j++;
                }
    
            }   
            temp=a[j-1];
            a[j-1]=a[p];
            a[p]=temp;
            quicksort(a,l,j);
            quicksort(a,j,n);
        }
    }
    

    【讨论】:

    • 请再次检查您的代码。递归调用quicksort(a,0,j); 似乎不正确。顺便说一句,这 不是 partition 方法...
    • 如果a[0] 已经是数组中的最小项,您的代码如何工作?
    • 是的,它是我在这里编写的整个快速排序代码。如果您忽略递归调用,那么它基本上会根据第一个元素对数组进行分区。
    • 然后它将 a[0] 与所有元素 j=1 进行比较并保持为 1。最后 a[0] 和 a[0] 将互换,这意味着什么都不会改变,并且将对 0-1 和 1-n 进行递归调用。作为 0+1!
    • ...然后它将递归调用quicksort(a,0,some_new_j),尽管事实上第 0 项 属于 数组的其余部分!
    【解决方案5】:
    下面的分区函数工作如下: last 变量指向数组中的最后一个元素,该元素没有
    与枢轴元素进行比较并且可以交换。 如果直接靠近枢轴元素的元素小于枢轴 元素。他们被交换了。 否则,如果枢轴元素小于下一个元素,则将下一个
    元素与索引为最后一个变量的元素交换。
    static int partition(int[] a){
        int pivot = a[0];    
        int temp, index = 0;
        int last = a.length -1;
    
        for(int i = 1; i < a.length; i++){
    
            //If pivot > current element, swap elements
            if( a[i] <= pivot){
                temp = a[i];
                a[i] = pivot;
                a[i-1] = temp;             
                index = i;
            } 
            //If pivot < current elmt, swap current elmt and last > index of pivot
            else if( a[i] > pivot && last > i){
                temp = a[i];
                a[i] = a[last];
                a[last] = temp;
                last -= 1;
                i--;
            }
            else
                break;
        }
        return index;
    }
    

    【讨论】:

      猜你喜欢
      • 2018-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-26
      • 1970-01-01
      相关资源
      最近更新 更多