Friday, November 9, 2007

PHP Quicksort

Quicksort is a well-known sorting algorithm developed by C. A. R. Hoare that, on average, makes Θ(nlogn) (big O notation) comparisons to sort n items. However, in the worst case, it makes Θ(n2) comparisons. Typically, quicksort is significantly faster in practice than other Θ(nlogn) algorithms, because its inner loop can be efficiently implemented on most architectures, and in most real-world data it is possible to make design choices which minimize the possibility of requiring quadratic time.

Quicksort is a comparison sort and, in efficient implementations, is not a stable sort.


PHP implementation:


// Recursive version:
function quickSortRecursive( $arr, $left = 0 , $right = NULL )
{
// when the call is recursive we need to change
//the array passed to the function yearlier
static $array = array();
if( $right == NULL )
$array = $arr;

if( $right == NULL )
$right = count($array)-1;//last element of the array

$i = $left;
$j = $right;

$tmp = $array[(int)( ($left+$right)/2 )];

// partion the array in two parts.
// left from $tmp are with smaller values,
// right from $tmp are with bigger ones
do
{
while( $array[$i] < $tmp )
$i++;

while( $tmp < $array[$j] )
$j--;

// swap elements from the two sides
if( $i <= $j )
{
$w = $array[$i];
$array[$i] = $array[$j];
$array[$j] = $w;

$i++;
$j--;
}
}while( $i <= $j );

// devide left side if it is longer the 1 element
if( $left < $j )
quickSortRecursive(NULL, $left, $j);

// the same with the right side
if( $i < $right )
quickSortRecursive(NULL, $i, $right);

// when all partitions have one element
// the array is sorted

return $array;
}




// Non recursive version:
function quickSort( $array )
{
$cur = 1;
$stack[1]['l'] = 0;
$stack[1]['r'] = count($array)-1;

do
{
$l = $stack[$cur]['l'];
$r = $stack[$cur]['r'];
$cur--;

do
{
$i = $l;
$j = $r;
$tmp = $array[(int)( ($l+$r)/2 )];

// partion the array in two parts.
// left from $tmp are with smaller values,
// right from $tmp are with bigger ones
do
{
while( $array[$i] < $tmp )
$i++;

while( $tmp < $array[$j] )
$j--;

// swap elements from the two sides
if( $i <= $j )
{
$w = $array[$i];
$array[$i] = $array[$j];
$array[$j] = $w;

$i++;
$j--;
}

}while( $i <= $j );


if( $i < $r )
{
$cur++;
$stack[$cur]['l'] = $i;
$stack[$cur]['r'] = $r;
}
$r = $j;

}while( $l < $r );

}while( $cur != 0 );

return $array;
}


6 comments:

LPIC.lt said...

thanx

Yannick Tessier said...

Hi there... Thanks for sharing those functions. In testing your implementation of the Recursive function, I found that it fails to sort correctly on larger arrays containing multiple repetitive numbers, i.e. array(4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1);

When running the recursive function against this array I get several numbers that land in the wrong position. I am working on figuring out why. Thought I would point it out for the time being.

Yannick Tessier said...

Oops - my array got chopped, so I am reposting it with carriage returns.

array(4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,
3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,
7,1,6,8,3,9,1,4,3,5,2,9,7,1,6,8,3,9,1,4,3,5,2,9,7,1,
6,8,3,9,1);

Rajaku said...

I had no problem, the non recursive seems to work flawlessly. Did you ever find your problem?? If so can you post the solution or even an example of the fail. I was using arrays with a random length between 10 and 100, and values between 0 and PHP_INT_MAX.

Rajaku said...

I created an associate non-recursive version which is almost as fast as the original and still faster than the recursive version. I have shared it below. Thanks for the inspiration.

function QuickSortAssociateNonRecursive($array) {
$keys = array_keys( $array );
$values = array_values( $array );
$stackleft[$cur = 1] = 0;
$stackright[$cur] = count($array) - 1;
do {
$l = $stackleft[$cur];
$r = $stackright[$cur--];
do {
$tmp = $values[(int) ( (($i = $l) + ($j = $r)) / 2 )];
do {
while ($values[$i] < $tmp)
$i++;
while ($tmp < $values[$j])
$j--;
if ($i <= $j) {
$ki = $keys[$i];
// $kj = $keys[$j];
$values[$i] = $array[$keys[$i] = $keys[$j]];
$values[$j] = $array[$keys[$j] = $ki];
// $w = $values[$i];
// $values[$i] = $values[$j];
// $values[$j] = $w;
// $keys[$i] = $kj;
// $keys[$j] = $ki;
$i++;
$j--;
}
} while ($i <= $j);
if ($i < $r) {
$stackleft[++$cur] = $i;
$stackright[$cur] = $r;
}
} while ($l < ($r = $j));
} while ($cur);
return array_combine($keys, $values);
}

brairia said...

Hi
What happens when executing 'quickSortRecursive($arr,,3), and php tries to assign $tmp using $array, does $array have elements?

Thanks in advance, Manuel Lorenzo