1、排序

1.1 冒泡排序 Bubble Sort

原理:冒泡排序大概的意思是依次比较相邻的两个数,然后根据大小做出排序,直至最后两位数。由于在排序过程中总是小数往前放,大数往后放,相当于气泡往上升,所以称作冒泡排序。代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function BubbleSort($arr) {
// 判断参数是否是数组
if (!is_array($arr) || empty($arr)) {
return $arr;
}
// 确定要冒泡的轮数
$len = count($arr);
for ($i = 0; $i < $len - 1; $i++) {
// 每轮比较,都要去掉之前确定好顺序的数。第一轮去掉0个,第二轮去掉1个。。。
for($j= 0; $j < $len - $i - 1; $j++) {
// 两数相比,如果$arr[$j] 大于 $arr[$j+1] -- 交换位置,往后移动
if ($arr[$j] > $arr[j+1]) {
$temp = $arr[$j + 1];
$arr[$j + 1] = $arr[$j];
$arr[$j] = $temp;
}
}
}
return $arr;
}

1.2 选择排序 Selection sort

原理:首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function selectSort($arr) {
if(!is_array($arr) || empty($arr)) {
return $arr;
}

$len = count($arr);
// $i 当前最小值的位置,需要参与比较的元素
for ($i=0; $i < $len - 1; $i++) {
// 先假设最小的值的位置
$p = $i;

for ($j=$i + 1; $j < $len; $j++) {
// $arr[$p]是当前已知的最小值
// 比较,发现更小的,记录下最小值的位置;并且在下次比较时,应该采用已知的最小值进行比较。
$p = ($arr[$p] <= $arr[$j]) ? $p:$j;
}

// 已经确定了最小值,保存到$p中
// 如果发现 最小值的位置和当前假设的位置$i不同,则互换位置
if ($p != $i) {
$tmp = $arr[$p];
$arr[$p] = $arr[$i];
$arr[$i] = $tmp;
}
}
return $arr;
}

1.3 插入排序 Insert Sort

原理:
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
步骤:
1 从第一个元素开始,该元素可以认为已经被排序
2 取出下一个元素,在已经排序的元素序列中从后向前扫描
3 如果该元素(已排序)大于新元素,将该元素移到下一位置
4 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5 将新元素插入到该位置中
6 重复步骤2
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

function insertSort($arr) {
if(!is_array($arr) || empty($arr)) {
return $arr;
}

$len = count($arr);
// 从1开始,第一个元素不需要比较
for ($i = 1; $i < $len; $i++) {
// 获得当前需要比较的元素值
$tmp = $arr[$i];
// 内层循环,比较并插入
// 从$i的前一位开始往前比较
for ($j = $i - 1; $j >= 0; $j--) {
// $arr[$i] 需要插入的元素
// $arr[$j] 需要比较的元素
if ($tmp < $arr[$j]) {
// 发现插入的元素要小,交换位置
$arr[$j + 1] = $arr[$j];
$arr[$j] = $tmp;
} else {
break;
}
}
}
return $arr;
}

1.4 归并排序

原理:
利用递归,先拆分、后合并、再排序。
步骤:
1 均分数列为两个子数列
2 递归重复上一步骤,直到子数列只有一个元素
3 父数列合并两个子数列并排序,递归返回数列
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function mergeSort($arr) {
if(!is_array($arr) || empty($arr)) {
return $arr;
}

$len = count($arr);

// 递归结束条件
if ($len <= 1) {
return $arr;
}
$mid = intval($len / 2); // 取数组中间
$left = array_slice($arr, 0, $mid); // 拆分数组0-mid这部分给左边
$right = array_slice($arr, $mid); // 拆分数组mid-末尾给右边

$left = mergeSort($left); // 左边拆分完后开始递归合并往上走
$right = mergeSort($right); // 右边拆分完毕后开始递归往上走

return merge($left, $right); // 合并两个数组,继续递归
}

function merge($arrA, $arrB) {
$arrC = [];
while(count($arrA) && count($arrB)) {
$arrC[] = $arrA[0] < $arrB[0] ? array_shift($arrA) : array_shift($arrB);
}

return array_merge($arrC, $arrA, $arrB);
}

图示:


1.5 快速排序 Quick Sort

快速排序是由东尼·霍尔所发展的一种排序算法。
原理:
在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来,且在大部分真实世界的数据,可以决定设计的选择,减少所需时间的二次方项之可能性。
步骤:
1 从数列中挑出一个元素,称为 “基准”(pivot)
2 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
3 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function quickSort($arr) {
if (!is_array($arr)) {
return $arr;
}

$len = count($arr);

if ($len <= 1) {
return $arr;
}

$left = [];
$right = [];

for ($i = 1; $i < $len; $i++) {
if ($arr[$i] < $arr[0]) {
$left[] = $arr[$i];
} else {
$right[] = $arr[$i];
}
}
$left = quickSort($left);
$right = quickSort($right);
return array_merge($left, [$arr[0]], $right);
}

1.6 希尔排序 Shell Sort

希尔排序属于插入类排序,是将整个有序序列分割成若干个小的子序列分别进行插入排序。
排序过程:先取一个正整数d1<n,把所有序号相隔d1的数组元素放一组,组内进行直接插入排序,然后取d2<d1,重复上述分组和排序操作,直至d1=1,即所有记录放进一个组中排序为止。(将每间隔一定步距的数取出来进行比较,比如gap=5,就是把第1个、第6个、第11个…数取出来进行插入排序)
优点:
当n值很大时,数据项每一趟排序需要移动的个数很少,但数据项的距离很长;当n值减小时,每一趟需要移动的数据增多,此时已经接近于它们排序后的最终位置。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

function shellSort($arr)
{
$count = count($arr);
$inc = $count;
do {
$inc = ceil($inc / 2);
for ($i = $inc; $i < $count; $i++) {
$temp = $arr[$i]; // 设置哨兵
// 需将$temp插入有序增量子表
for ($j = $i - $inc; $j >= 0 && $arr[$j + $inc] < $arr[$j]; $j -= $inc) {
$arr[$j + $inc] = $arr[$j]; // 记录后移
}
$arr[$j + $inc] = $temp;
}
} while ($inc > 1);
return $arr;
}

2 搜索

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

  1. 将数组的第一个位置设置为下边界(0)。
  2. 将数组最后一个元素所在的位置设置为上边界(数组的长度减 1)。
  3. 若下边界等于或小于上边界,则做如下操作:
    a. 将中点设置为floor((上边界+下边界)/2)。
    b. 如果中点的元素小于查询的值,则将下边界设置为中点元素所在下标加1。
    c. 如果中点的元素大于查询的值,则将上边界设置为中点元素所在下标减1。
    d. 否则中点元素即为要查找的数据,可以进行返回。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

function binarySearch($arr, $res) {
$right = count($arr);
$left = 0;

while($left <= $right) {
$middle = floor(($right + $left) / 2);

if (!isset($arr[$middle])) {
return -1;
}

if ($arr[$middle] == $res) {
return $middle;
}

if ($arr[$middle] < $res) {
$left = $middle + 1;
} else {
$right = $middle - 1;
}
}
return -1;
}

// 第二种实现
function binarySearch2($arr, $search) {
if (empty($arr)) {
return -1;
}

$low = 0;
$high = count($arr) - 1;

while ($low <= $high) {
// 这样的写法没有溢出的风险
$mid = intval(($high - $low) / 2) + $low;
$val = $arr[$mid];

if ($search == $val) {
return $mid;
} elseif ($search < $val) {
$high = $mid - 1;
} else {
$low = $mid + 1;
}
}
return -1;
}