【问题标题】:Writing merge sort in PHP在 PHP 中编写归并排序
【发布时间】:2012-02-22 18:45:43
【问题描述】:

我尝试在 PHP 中编写一个涉及一个小数组的基本合并排序,但问题是执行大约需要一分钟左右,然后返回:

致命错误:允许的内存大小为 536870912 字节已用尽(已尝试 在第 39 行的 /Users/web/www/merge.php 中分配 35 个字节)

有没有人知道代码可能出错的地方(如果有的话)?我已经盯着这个看了好一个小时了。

<?php

$array = array(8,1,2,5,6,7);
print_array($array);
merge_sort($array);
print_array($array);

function merge_sort(&$list){
    if( count($list) <= 1 ){
        return $list;
    }

    $left =  array();
    $right = array();

    $middle = (int) ( count($list)/2 );

    // Make left
    for( $i=0; $i < $middle; $i++ ){
        $left[] = $list[$i];
    }

    // Make right
    for( $i = $middle; $i < count($list); $i++ ){
        $right[] = $list[$i];
    }

    // Merge sort left & right
    merge_sort($left);
    merge_sort($right);

    // Merge left & right
    return merge($left, $right);
}

function merge(&$left, &$right){
    $result = array();

    while(count($left) > 0 || count(right) > 0){
        if(count($left) > 0 && count(right) > 0){
            if($left[0] <= $right[0]){
                $result[] = array_shift($left);
            } else {
                $result[] = array_shift($right);
            }
        } elseif (count($left) > 0){
            $result[] = array_shift($left);
        } elseif (count($right) > 0){
            $result[] = array_shift($right);
        }
    }

    print_array($result);exit;

    return $result;
}

function print_array($array){
    echo "<pre>";
    print_r($array);
    echo "<br/>";
    echo "</pre>";
}

?>

【问题讨论】:

  • 虽然这不是您的问题,但请注意 PHP 的最大递归限制为 100。当给定足够大的数组时,您最终可能会达到此限制。
  • 查看此网站以获得更多帮助:php.net/manual/en/array.sorting.php
  • 我不熟悉算法,但我建议在代码的各个点做一些回显/退出,看看你是否正确地得到中间步骤?
  • 最好包含一个指向您正在尝试做的事情的链接:en.wikipedia.org/wiki/Merge_sort。我个人会使用 php 的本机(用 C 编写)排序算法——我认为 php 代码的效率会低于 php 的本机快速排序方法。为什么快速排序会比合并排序更好的解释(除了明显的原生 vs PHP 代码可以在这里找到:stackoverflow.com/questions/680541/quick-sort-vs-merge-sort
  • @Arend 我创建合并排序纯粹是为了理解它并练习实现它。

标签: php sorting mergesort


【解决方案1】:

在您的merge 函数中,您调用right 而不是$right 的计数。 PHP 假定这是一个字符串常量(至少在 5.3.9 中),并且当它被转换为一个总是有一个元素的数组时。所以count(right) 总是一个,你永远不会退出第一个合并。

【讨论】:

  • 很抱歉,但这并不好用。最后的exit 给你留下了最小尺寸的数组,2 项。如果你忽略它,它只会把所有的数组都扔给你。需要在其中某处合并数组。
【解决方案2】:

我一直在寻找 PHP 中优化的 Mergesort 算法。答案中有 5 种算法,所以我测试了这些算法,我的也测试了。使用 PHP 7.2.7,这些是时代:

对 1000 个随机数进行排序:

对 10 个随机数进行排序:

因此,尽管我鼓励谁阅读它以使其更快(那是我一直在寻找的,我相信可以做到),但我也让您实现,因为似乎比其他答案更快:

//This function needs start and end limits
function mergeSortRec(&$a,$start,$end){
  if($start<$end){
    $center=($start+$end)>>1; //Binary right shift is like divide by 2
    mergeSortRec($a, $start, $center);
    mergeSortRec($a, $center+1, $end);
    //Mixing the 2 halfs
    $aux=array();
    $left=$start; $right=$center;
    //Main loop
    while($left<$center && $right<=$end){
      if($a[$left]<$a[$right]){
        $aux[]=$a[$left++];
      }else{
        $aux[]=$a[$right++];
      }
    }
    //Copy the rest of the first half
    while($left<$center) $aux[]=$a[$left++];
    //Copy the rest of the second half
    while($right<=$end) $aux[]=$a[$right++];
    //Copy the aux array to the main array
    foreach($aux as $v) $a[$start++]=$v;
  }
}
//This is the function easier to call
function mergeSort(&$a) {
  mergeSortRec($a,0,count($a)-1);
}

如果您发布了新答案,请让我发表评论以对其进行测试并添加。


编辑:我做了一些新的优化,针对那些寻求更好实现的人。

【讨论】:

  • 最后一行应该是foreach ($aux as $v) $a[$start++] = $v;
  • 您可能还想测试$center = ($start + $end) &gt;&gt; 1;$center = (int)(($start + $end) / 2); 是否比$center = floor(($start + $end) / 2); 更有效
  • 另一个想法:你可以使用一个单独的函数mergeSort_rec,没有默认参数值,也没有测试$end=$end??count($a)-1;,并从mergeSort()调用它。考虑排除 $end 会产生更简洁的代码。
  • 谢谢@chqrlie。你是对的,我应该在这里应用一些优化,而不是等待其他答案。二进制移位是一个很好的移位,它使算法的运行速度提高了 10%(没有尝试 int 转换)。将函数拆分为递归和非递归调用,使其运行速度甚至快了 5%。并且避免了 mix() 调用,另外 5%。所以我用这些更改更新了代码。
  • 也许我们可以稍后添加一些不太明显的 tweeks 并保持更新。我记得有一个,例如,在 2 个数组之间交替并避免创建和销毁 aux 数组,但这一切都需要时间。让我们稍后等待新的鼓励。目前,谢谢你的;)
【解决方案3】:

看看这个,算法已经实现了,使用array_push和array splice而不是array_shift。

http://www.codecodex.com/wiki/Merge_sort#PHP

【讨论】:

    【解决方案4】:

    我是这样实现归并排序的

    function mergeSort($Array)
    {
        $len = count($Array);
        if($len==1){
            return $Array;
        }
        $mid = (int)$len / 2;
        $left = mergeSort(array_slice($Array, 0, $mid));
        $right = mergeSort(array_slice($Array, $mid));
        return merge($left, $right);
    }
    
    function merge($left, $right)
    {
    
    
        $combined = [];
        $totalLeft = count($left);
        $totalRight = count($right);
        $rightIndex = $leftIndex=0;
        while ($leftIndex < $totalLeft && $rightIndex < $totalRight) {
            if ($left[$leftIndex] > $right[$rightIndex]) {
                $combined[]=$right[$rightIndex];
                $rightIndex++;
            }else {
                $combined[] =$left[$leftIndex];
                $leftIndex++;
            }
        }
        while($leftIndex<$totalLeft){
            $combined[]=$left[$leftIndex];
            $leftIndex++;
        }
        while ($rightIndex<$totalRight){
            $combined[] =$right[$rightIndex];
            $rightIndex++;
        }
        return $combined;
    }
    

    【讨论】:

      【解决方案5】:

      这是 PHP 中实现合并排序的类 -

                  <?php
                  class mergeSort{
                      public $arr;
                      public function __construct($arr){
                          $this->arr = $arr;
                      }
      
                      public function mSort($l,$r){
                          if($l===null || $r===null){ 
                              return false;
                          }
                          if ($l < $r)
                          {
                              // Same as ($l+$r)/2, but avoids overflow for large $l and $r
                              $m = $l+floor(($r-$l)/2);
      
                              // Sort first and second halves
                              $this->mSort($l, $m);
                              $this->mSort($m+1, $r);
      
                              $this->merge($l, $m, $r);
                          }
                      }
      
                      // Merges two subarrays of $this->arr[]. First subarray is $this->arr[$l..$m]. Second subarray is $this->arr[$m+1..$r]
                      public function merge($l, $m, $r)
                      {
                          if($l===null || $m===null || $r===null){    
                              return false;
                          }
      
                          $n1 = $m - $l + 1;
                          $n2 =  $r - $m;
      
                          /* create temp arrays */
                          $L=array();
                          $R=array();
      
                          /* Copy data to temp arrays $L[] and $R[] */
                          for ($i = 0; $i < $n1; $i++)
                              $L[$i] = $this->arr[$l + $i];
      
                          for ($j = 0; $j < $n2; $j++)
                              $R[$j] = $this->arr[$m + 1+ $j];
      
                          /* Merge the temp arrays back into $this->arr[$l..$r]*/
                          $i = 0; // Initial index of first subarray
                          $j = 0; // Initial index of second subarray
                          $k = $l; // Initial index of merged subarray
                          while ($i < $n1 && $j < $n2)
                          {
                              if($L[$i] <= $R[$j])
                              {
                                  $this->arr[$k] = $L[$i];
                                  $i++;
                              }
                              else
                              {
                                  $this->arr[$k] = $R[$j];
                                  $j++;
                              }
                              $k++;
                          }
      
                          /* Copy the remaining elements of $L[], if there are any */
                          while($i < $n1)
                          {
                              $this->arr[$k] = $L[$i];
                              $i++;
                              $k++;
                          }
      
                          /* Copy the remaining elements of $R[], if there are any */
                          while($j < $n2)
                          {
                              $this->arr[$k] = $R[$j];
                              $j++;
                              $k++;
                          }
                      }
                  }
      
                  $arr = array(38, 27, 43, 5, 9, 91, 12);
                  $obj = new mergeSort($arr);
                  $obj->mSort(0,6);
                  print_r($obj->arr);
                  ?>
      

      【讨论】:

        【解决方案6】:

        试试这个方法。不要移动它,而是切片。

        另外,对于 merge 函数的 for in while 循环,您需要改为与 &amp;&amp; 进行比较 ||

        function mergeSort($array)
        {
            if(count($array) == 1 )
            {
                return $array;
            }
        
            $mid = count($array) / 2;
            $left = array_slice($array, 0, $mid);
            $right = array_slice($array, $mid);
            $left = mergeSort($left);
            $right = mergeSort($right);
        
            return merge($left, $right);
        }
        
        
        function merge($left, $right)
        {
            $res = array();
        
            while (count($left) > 0 && count($right) > 0)
            {
                if($left[0] > $right[0])
                {
                    $res[] = $right[0];
                    $right = array_slice($right , 1);
                }
                else
                {
                    $res[] = $left[0];
                    $left = array_slice($left, 1);
                }
            }
        
            while (count($left) > 0)
            {
                $res[] = $left[0];
                $left = array_slice($left, 1);
            }
        
            while (count($right) > 0)
            {
                $res[] = $right[0];
                $right = array_slice($right, 1);
            }
        
            return $res;
        }
        

        【讨论】:

        • 如果数组的大小是奇数怎么办?
        【解决方案7】:

        您的合并排序通过引用接受列表

        function merge_sort(&$list)
        

        因此,您需要为其分配新的合并和排序列表。所以不是

        return merge($left, $right);
        

        $list = $this->merge($left, $right);
        

        应该这样做,只需删除出口并修复计数变量

        【讨论】:

          【解决方案8】:

          PHP 中的合并排序

          <?php 
          class Solution 
          {
              function mergeSort(&$arr)
              {
                  if(count($arr) > 1) {
                      $mid = floor(count($arr)/2);
                      
                      $left = array_slice($arr, 0, $mid);
                      $right = array_slice($arr, $mid);
          
                      $this->mergeSort($left);
                      $this->mergeSort($right);
          
                      // Merge the results.
                      $i = $j = $k = 0;
                      while(($i < count($left)) && ($j < count($right))) {
                          if($left[$i] < $right[$j]) {
                              $arr[$k] = $left[$i];
                              $i++;
                          } else {
                              $arr[$k] = $right[$j];
                              $j++;
                          }
                          $k++;
                      }
          
                      while($i < count($left)) {
                          $arr[$k] = $left[$i];
                          $i++;
                          $k++;
                      }
          
                      while($j < count($right)) {
                          $arr[$k] = $right[$j];
                          $j++;
                          $k++;
                      }
                  }
              }
          }
          
          $s = new Solution();
          $tmp = [12, 7, 11, 13, 5, 6, 7];
          $s->mergeSort($tmp);
          print_r($tmp);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2013-02-02
            • 1970-01-01
            • 1970-01-01
            • 2015-06-10
            • 1970-01-01
            • 2014-02-02
            • 2014-08-12
            相关资源
            最近更新 更多