PHP 排序算法

1.1冒泡排序(BubbleSort)

原理

比较相邻的元素。如果第一个比第二个大,就交换他们两个。
对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
针对所有的元素重复以上的步骤,除了最后一个。 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

<?php
/**
 * 冒泡排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(n^2)
 * 最优时间复杂度-----------O(n)
 * 平均时间复杂度-----------O(n^2)
 * 空间复杂度---------------O(1)
 * 稳定性------------------稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
 
function BubbleSort($arr)
{
	$length = count($arr);
	for ($i = 0; $i < $length - 1; $i++) { 			// 每次最大元素移到数组最后
		for ($j = 0; $j < $length - 1 - $i; $j++) {     // 内部循环次数=数组长度-已经排序好的个数-1
			if ($arr[$j] > $arr[$j + 1]) {	        // 比较相邻两个元素,较大的后移
				$t = $arr[$j];
				$arr[$j] = $arr[$j + 1];
				$arr[$j + 1] = $t; 
			}
		}
	}
	return $arr;
}
 
print_r(BubbleSort($arr));

1.2鸡尾酒排序(CocktailSort)

鸡尾酒排序也就是定向冒泡排序, 鸡尾酒搅拌排序, 搅拌排序 (也可以视作选择排序的一种变形), 涟漪排序, 来回排序 or 快乐小时排序, 是冒泡排序的一种变形。此演算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序。

原理:数组中的数字本是无规律的排放,先找到最小的数字,把他放到第一位,然后找到最大的数字放到最后一位。然后再找到第二小的数字放到第二位,再找到第二大的数字放到倒数第二位。以此类推,直到完成排序。

<?php
/**
 * 鸡尾酒排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(n^2)
 * 最优时间复杂度-----------O(n)
 * 平均时间复杂度-----------O(n^2)
 * 空间复杂度--------------O(1)
 * 稳定性-----------------稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
 
function CocktailSort($arr)
{
	$left = 0;                                                // 定义左边界
	$right = count($arr) - 1;                                // 定义右边界
	while ($left < $right) {
		for ($i = $left; $i < $right; $i++) {            // 从左向右,将最大元素放在后面
			if ($arr[$i] > $arr[$i + 1]) {
				$t = $arr[$i];
				$arr[$i] = $arr[$i + 1];
				$arr[$i + 1] = $t;
			}
		}
		$right--;                                        // 右边界缩小
		for ($i = $right; $i > $left; $i--) {            // 从右向左,将最小元素放在前面
			if ($arr[$i-1] > $arr[$i]) {
				$t = $arr[$i -1];
				$arr[$i - 1] = $arr[$i];
				$arr[$i] = $t;
			}
		}
		$left++;                                        // 左边界增大
	}
	return $arr;
}



print_r(CocktailSort($arr));

2.选择排序(SelectionSort)

原理:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完.。

<?php
/**
 * 选择排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(n^2)
 * 最优时间复杂度-----------O(n^2)
 * 平均时间复杂度-----------O(n^2)
 * 空间复杂度--------------O(1)
 * 稳定性-----------------不稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
 
function SelectionSort($arr)
{
	$length = count($arr);
	for ($i = 0; $i < $length - 1; $i++) {			// $i为已经排序序列的末尾下标
		$min = $i;				    // 暂存未排列序列的最小值下标
		for ($j = $i + 1; $j < $length; $j++) {		// 遍历未排列序列
			if ($arr[$j] < $arr[$min]) {		// 找出排列序列最小值,下标赋给$min
				$min = $j;
			}
		}
		if ($min != $i) {			    // 如果找到最小值,放到已排列序列末尾
			$t = $arr[$min];
			$arr[$min] = $arr[$i];
			$arr[$i] = $t;
		}
	}
	return $arr;
}
 
print_r(SelectionSort($arr));

3.1插入排序——直接插入排序(StraightInsertionSort)

原理

从第一个元素开始,该元素可以认为已经被排序 取出下一个元素,在已经排序的元素序列中从后向前扫描
如果该元素(已排序)大于新元素,将该元素移到下一位置 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置 将新元素插入到该位置后
重复步骤2~5

<?php
/**
 * 直接插入排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(n^2):输入序列是降序排列的
 * 最优时间复杂度-----------O(n):输入序列是升序排列的
 * 平均时间复杂度-----------O(n^2)
 * 空间复杂度--------------O(1)
 * 稳定性-----------------稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
 
function StraightInsertionSort($arr)
{						     // 数组第一个元素默认已被排序
	for ($i = 1; $i < count($arr); $i++) {	      // 未排序序列从数组第二个元素开始
		$get = $arr[$i];		       // 获取未排列序列第一个元素为要比较的元素
		$j = $i - 1;				// 指针指向已排序序列最后一个元素
		while ($j >= 0 && $arr[$j] > $get) {  // 当指针指向的元素比要比较的元素大
			$arr[$j + 1] = $arr[$j];	// 则将指针指向的元素向后移动一位
			$j--;				// 指针前移
		}
		$arr[$j + 1] = $get;			// 直到指针指向的元素与要比较元素小或者相等,则要比较元素插入到指针指向元素后
	}
	return $arr;
}
 
print_r(StraightInsertionSort($arr));

3.2插入排序——二分查找排序(BinarySearchSort)

原理:首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

<?php
/**
 * 二分查找排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(n^2)
 * 最优时间复杂度-----------O(nlogn)
 * 平均时间复杂度-----------O(n^2)
 * 空间复杂度--------------O(1)
 * 稳定性-----------------稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
 
function BinarySearchSort($arr)
{							    // 数组第一个元素默认已被排序
	for ($i = 1; $i < count($arr); $i++) {		        // 未排列序列从数组第二个元素开始
		$get = $arr[$i];			       // 获取未排列序列第一个元素为要比较元素
		$left = 0;				     // 定义已排列序列左边界
		$right = $i -1;					// 右边界
		while ($left <= $right) {					
			$mid = floor(($left + $right) / 2);	// 定义中间数,将已排列序列一分为二
			if ($arr[$mid] > $get) {		// 如果中间数大于比较元素,则比较元素属于前半段
				$right = $mid -1;		// 右边界左移
			} else {				// 否则比较元素属于后半段
				$left = $mid + 1;		// 左边界右移
			}
		}
		for ($j = $i -1; $j >= $left; $j--) {	       // 最后的$left就是待插入位置
			$arr[$j + 1] = $arr[$j];		// 将待插入位置的右边整体向右移动一个单位
		}		
		$arr[$left] = $get;				// 将比较元素插入该空位
	}
	return $arr;
}
 
print_r(BinarySearchSort($arr));

3.3插入排序——希尔排序(ShellSort)

希尔排序,也叫递减增量排序,是插入排序的一种更高效的改进版本。希尔排序是不稳定的排序算法。

原理:希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。

增量序列:由Knuth提出,递归表达式为:h = 3 * h + 1;减小增量:h = (h - 1) / 3;此公式产生的逆置序列为:…, 314, 121, 40, 13, 4, 1。

<?php
/**
 * 希尔排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(n^2)
 * 最优时间复杂度-----------O(n^1.3)
 * 平均时间复杂度-----------O(nlogn)
 * 空间复杂度--------------O(1)
 * 稳定性-----------------不稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
 
function ShellSort($arr)
{												
	$length = count($arr);
	$h = 0;							
	while ($h <= $length) {					// 定义增量序列
		$h = 3 * $h + 1;
	}
	while ($h >= 1) {
		for ($i = $h; $i < $length; $i++) {			
			$j = $i - $h;
			$get = $arr[$i];			// 暂存要比较的元素
			while ($j >= 0 && $arr[$j] > $get) {	// 组内判断,若组内元素大于该元素
				$arr[$j + $h] = $arr[$j];	// 则两者位置互换
				$j = $j - $h;			// 指针继续向前移动增量的距离,继续比较
			}
			$arr[$j + $h] = $get;
		}
		$h = ($h - 1) / 3;				// 递减增量
	}
	return $arr;
}
 
print_r(ShellSort($arr));

4.归并排序(MergeSort)

原理:归并排序的实现分为递归实现与非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。

归并排序算法主要依赖归并(Merge)操作。归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:

申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置 重复步骤3直到某一指针到达序列尾
将另一序列剩下的所有元素直接复制到合并序列尾

<?php
/**
 * 归并排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(nlogn)
 * 最优时间复杂度-----------O(nlogn)
 * 平均时间复杂度-----------O(nlogn)
 * 空间复杂度--------------O(n)
 * 稳定性-----------------稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
$arr2 = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
$length = count($arr);
$length2 = count($arr2);
 
// 递归实现
function MergeSortRecursion(&$arr, $left, $right)
{	
	if ($left == $right) {
		return;
	}
	$mid = floor(($left + $right) / 2);
	MergeSortRecursion($arr, $left, $mid);
	MergeSortRecursion($arr, $mid + 1, $right);
	Merge($arr, $left, $mid, $right);
}
 
// 迭代实现
function MergeSortIteration(&$arr, $length)
{	
	// 分为两个子数组,前一个为[$left, ... , $mid], 后一个为[mid + 1, ... , $right]
	for ($i = 1; $i < $length; $i *= 2) {		// 子数组大小,初始值为1,每轮翻倍
		$left = 0;
		while ($left + $i < $length) {		// 当后一个子数组存在时,需要归并
			$mid = $left + $i - 1;
			$right = $mid + $i < $length ? $mid + $i : $length - 1;	// 当后一个子数组长度不够时,用右边界
			Merge($arr, $left, $mid, $right);	
			$left = $right + 1;					// 前一个子数组左边界向右移动
		}
	}
}
 
// 合并两个已经排列好的序列
function Merge(&$arr, $left, $mid, $right)
{
	$length = $right - $left + 1;	// 待排序的数组长度
	$temp = [];			// 暂存排序好的元素
	$index = 0;
	$i = $left;			// 前一个数组的起始元素
	$j = $mid + 1;			// 后一个数组的起始元素
	while ($i <= $mid && $j <= $right) {
		$temp[$index++] = $arr[$i] <= $arr[$j] ? $arr[$i++] : $arr[$j++];	// 比较两个指针所指向的元素,选择小的元素放入暂存数组,并将改指针后移
	}
	// 将剩余未比较的元素放入暂存数组末尾
	while ($i <= $mid) {
		$temp[$index++] = $arr[$i++];
	}
	while ($j <= $right) {
		$temp[$index++] = $arr[$j++];
	}
	// 更新数组的排序
	for ($k = 0; $k < $length; $k++) {
		$arr[$left++] = $temp[$k];
	}
}
 
MergeSortRecursion($arr, 0, $length - 1);
echo "递归实现:";
print_r($arr);
 
MergeSortIteration($arr2, $length2);
echo "迭代实现:";
print_r($arr2);

5.堆排序(HeapSort)

原理:

由输入的无序数组构造一个最大堆,作为初始的无序区 把堆顶元素(最大值)和堆尾元素互换 把堆(无序区)的尺寸缩小1,并调用heapify(A,
0)从新的堆顶元素开始进行堆调整 重复步骤2,直到堆的尺寸为1

<?php
/**
 * 堆排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(nlogn)
 * 最优时间复杂度-----------O(nlogn)
 * 平均时间复杂度-----------O(nlogn)
 * 空间复杂度--------------O(1)
 * 稳定性-----------------不稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
$len = count($arr);
 
function Swap(&$arr, $i, $j)
{												
	$temp = $arr[$i];
	$arr[$i] = $arr[$j];
	$arr[$j] = $temp;
}
// 从$arr[$i]开始向下进行堆调整
function Heapify(&$arr, $i, $heap_size)
{
	$left_child = 2 * $i + 1;	// 左孩子索引
	$right_child = 2 * $i + 2;	// 右孩子索引
	$max = $i;					// 选出当前结点与其左右孩子死难者之中最大值
	if ($left_child < $heap_size && $arr[$left_child] > $arr[$max]) {
		$max = $left_child;
	}
	if ($right_child < $heap_size && $arr[$right_child] > $arr[$max]) {
		$max = $right_child;
	}
	if ($max != $i) {
		Swap($arr, $i, $max);	// 当前结点与最大值进行交换
		Heapify($arr, $max, $heap_size);	// 递归,继续调整
	}
 
}
// 建堆,时间复杂度O(n)
function BuildHeap(&$arr, $len)
{
	$heap_size = $len;
	for ($i = floor($heap_size / 2) - 1; $i >= 0; $i--) {	// 从每一个非叶子结点开始向下进行堆调整
		Heapify($arr, $i, $heap_size);
	}
	return $heap_size;
}
// 堆排序
function HeapSort($arr, $len)
{
	$heap_size = BuildHeap($arr, $len);		// 建立最大堆(无序区)
 
	while ($heap_size > 1) {				// 堆(无序区)元素大于1,未完成排序
		Swap($arr, 0, --$heap_size);		// 将堆定元素沉到堆底(成为有序区),堆(无序区)-1
		Heapify($arr, 0, $heap_size);		// 从新的栈顶元素开始向下调整
	}
	return $arr;
}
 
print_r(HeapSort($arr, $len));

6.快速排序(QuickSort)

快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。

原理:

从序列中挑出一个元素,作为"基准"(pivot)
把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作
对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了

<?php
/**
 * 快速排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------每次选取的基准都是最大(或最小)的元素,导致每次只划分出了一个分区,需要进行n-1次划分才能结束递归,时间复杂度为O(n^2)
 * 最优时间复杂度-----------每次选取的基准都是中位数,这样每次都均匀的划分出两个分区,只需要logn次划分就能结束递归,时间复杂度为O(nlogn)
 * 平均时间复杂度-----------O(nlogn)
 * 空间复杂度--------------O(logn)
 * 稳定性-----------------不稳定
 */
 
$arr = [1, 3, 34, 2, 32, 2, 78, -43, 53, -35, 0];
$len = count($arr);
 
function Swap(&$arr, $i, $j)
{												
	$temp = $arr[$i];
	$arr[$i] = $arr[$j];
	$arr[$j] = $temp;
}
// 划分
function Partition(&$arr, $left, $right)
{
	$pivot = $arr[$right];					// 每次都选择最后一个元素作为基准
	$tail = $left - 1;						// tail为小于基准的子数组最后一个元素的索引
	for ($i = $left; $i < $right; $i++) {	// 遍历基准以外的其他元素
		if ($arr[$i] <= $pivot) {			// 把小与等于基准的元素放到前一个子数组的末尾
			$tail++;
			if ($tail != $i) {
				Swap($arr, $tail, $i);
			}
		}
	}
	Swap($arr, $tail + 1, $right);			// 最后把基准放到前一个子数组的后边,剩下的子数组即是大于基准的子数组
	return $tail + 1;
}
 
function QuickSort(&$arr, $left, $right)
{
	if ($left >= $right)
		return;
	$pivot_index = Partition($arr, $left, $right);	// 基准的索引
	QuickSort($arr, $left, $pivot_index - 1);
	QuickSort($arr, $pivot_index + 1, $right);
 
	return $arr;
}
print_r(QuickSort($arr, 0, $len - 1));

7.计数排序(CountingSort)

计数排序适合排序一定范围内的数,例如0-99之间的排序

原理:

统计数组A中每个值A[i]出现的次数,存入C[A[i]]
从前向后,使数组C中的每个值等于其与前一项相加,这样数组C[A[i]]就变成了代表数组A中小于等于A[i]的元素个数
反向填充目标数组B:将数组元素A[i]放在数组B的第C[A[i]]个位置(下标为C[A[i]] - 1),每放一个元素就将C[A[i]]递减

<?php
/**
 * 计数排序
 * 数据结构----------------数组
 * 最差时间复杂度-----------O(n + k)
 * 最优时间复杂度-----------O(n + k)
 * 平均时间复杂度-----------O(n + k)
 * 空间复杂度--------------O(n + k)
 * 稳定性-----------------稳定
 */
 
$arrA = [1, 3, 34, 2, 32, 2, 78, 43, 53, 35, 0, 88, 75, 31, 5, 17];
$len = count($arrA);
 
CONST K = 100;	// 基数为100,排序0-99内的整数
$arrB = [];		// 暂存中间数据
$arrC = [];
 
function CountingSort(&$arrA, $len)
{
	for ($i = 0; $i < K; $i++) {	// 初始化B、C数组
		$arrB[$i] = 0;
		$arrC[$i] = 0;
	}
	for ($i = 0; $i < $len; $i++) {	// 使C[$i]保存着等于$i元素的个数
		$arrC[$arrA[$i]]++;
	}
	for ($i = 1; $i < K; $i++) {	// 使C[$i]保存着小于等于$i的个数
		$arrC[$i] = $arrC[$i] + $arrC[$i - 1];
	}
	for ($i = $len - 1; $i >= 0; $i--) {		// 从后向前扫描保证排序稳定性
		// 把每个元素A[$i]放到它在输出数组B中的正确位置上
		// 当遇到重复元素时,会被放到当前的前一个位置上保证稳定性
		$arrB[--$arrC[$arrA[$i]]] = $arrA[$i];	
	}
	for ($i = 0; $i < $len; $i++) {	// 把B中的数据拷贝回A
		$arrA[$i] = $arrB[$i];
	}
	return $arrA;
}
 
print_r(CountingSort($arrA, $len));

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

微信扫一扫

微信扫一扫

微信扫一扫,分享到朋友圈

PHP  排序算法