一. 算法的衡量标准
1.1 时间复杂度
在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间。
这是一个关于代表算法输入值的字符串的长度的函数。
时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。
1.2 空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度,记做S(n)=O(f(n))。
比如直接插入排序的时间复杂度是O(n^2),空间复杂度是O(1) 。
而一般的递归算法就要有O(n)的空间复杂度了,因为每次递归都要存储返回信息
1.3 举例理解时间复杂度
一个有序数组A,另一个无序数组B,请打印B中的所有不在A中的数。A数组长度为N,B数组长度为M。
三种流程,三种时间复杂度的表达式
算法流程1:对于数组B中的每一个数,都在A中通过遍历的方式找一下。
O(M*N)
算法流程2:对于数组B中的每一个数,都在A中通过二分的方法找一下。
O(M*log2N)
算法流程3:先把数组B排序,然后用类似外排的方式打印所有不在A中出现的数。
O(M*log2M)+O(N+M)
分析:排除算法1。
如果A数组个数比较少,相对来说数组B个数比较多,算法2较好。
如果A数组个数比较多,相对来说数组B个数比较少,算法3较好。
二. 对数器
- 有一个你想要测的方法A
- 实现一个绝对正确但是复杂度不好的方法B
- 实现一个随机样本产生器
- 实现比对的方法
- 把方法A和方法B比对很多次来验证方法A是否正确
- 如果有一个样本使得比对出错,打印样本分析是哪个方法出错
- 当样本数量很多时比对测试依然正确,可以确定方法A已经正确
三. 冒泡排序
时间复杂度O(N2),额外空间复杂度O(1)
// 冒泡排序 先减后加是冒泡、冒泡加中有if
protected function bubbleSort(&$data)
{
$dataNum = count($data);
if ($dataNum < 2) {
return $data;
}
for ($j=$dataNum-1; $j > 0 ; $j--) {
for ($i=0; $i < $j; $i++) {
if ($data[$i] > $data[$i+1]) {
$this->swap($data, $i, $i+1);
}
}
}
}
四. 选择排序
时间复杂度O(N2),额外空间复杂度O(1)
// 选择排序 选择排序有加加、三元变量和if
protected function selectionSort(Array $data)
{
$dataNum = count($data);
for ($j=0; $j < $dataNum - 1; $j++) {
$minIndex = $j;
for ($i=$j+1; $i < $dataNum; $i++) {
$minIndex = $data[$i] < $data[$minIndex] ? $i : $minIndex ;
}
if ($minIndex != $j) {
$tmp = $data[$minIndex];
$data[$minIndex] = $data[$j];
$data[$j] = $tmp;
}
}
return $data;
}
五. 插入排序
时间复杂度O(N2),额外空间复杂度O(1)
// 插入排序 先加后减是插入、插入减中有&
protected function insertionSort(&$data)
{
$dataNum = count($data);
for ($j=1; $j < $dataNum; $j++) {
for ($i=$j ; $i > 0 && $data[$i] < $data[$i-1]; $i--) {
$tmp = $data[$i];
$data[$i] = $data[$i-1];
$data[$i-1] = $tmp;
}
}
}
六. 递归行为及其时间复杂度的估算
递归函数就是系统帮你压栈,系统会按照代码的执行数序,把现场信息存储到进程控制块中。
估计递归时间复杂度的通式
master公式的使用:原始样本量为N的时间复杂度 = 子问题的样本量 + 除去子问题的样本量。
T(N) = a*T(N/b) + O(Nd)
条件 | 时间复杂度 |
---|---|
logba > d | O(Nlog~b~a) |
logba = d | O(Nd * logN) |
logba < d | O(Nd) |
七. 归并排序
时间复杂度O(N*logN),额外空间复杂度O(N)
// 归并排序
protected function mergeSort(&$data)
{
$start = 0;
$end = count($data) - 1;
$this->mSort($data, $start, $end);
}
protected function mSort(Array &$data, $start, $end)
{
if ($start < $end) {
//$mid = $start + ($end - $start) >> 1;
$mid = floor(($start + $end) / 2);
$this->mSort($data, $start, $mid);
$this->mSort($data, $mid + 1, $end);
$this->merge($data, $start, $mid, $end);
}
}
protected function merge(&$data, $start, $mid, $end)
{
$p = $start;
$j = $mid + 1;
$i = $start;
$tmpData = [];
while ($p != $mid + 1 && $j != $end + 1) {
$tmpData[$i++] = $data[$p] <= $data[$j] ? $data[$p++] : $data[$j++];
}
while ($p != $mid + 1) {
$tmpData[$i++] = $data[$p++];
}
while ($j != $end + 1) {
$tmpData[$i++] = $data[$j++];
}
for ($i=$start; $i <= $end; $i++) {
$data[$i] = $tmpData[$i];
}
}
八. 小和问题和逆序对问题
8.1 小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:
[1, 3, 4, 2, 5]
1左边比1小的数,没有
3左边比3小的数,1
4左边比4小的数, 1、3
2左边比2小的数,1
5左边比5小的数,1、3、4、2
所有小和为1+1+3+1+1+3+4+2=16
8.2逆序对问题
<?php
namespace App\Services;
// 小和和逆序对问题
class SmallSumService
{
public function mergeSortSum(&$data){
if(count($data) < 2){
return 0;
}
// 排序并返回最小和
return $this->mSort($data, 0, count($data) - 1);
}
public function mSort(&$data, $start, $end)
{
if ($start == $end) {
return 0;
}
$mid = (int)floor(($start + $end) / 2);
// 左边的最小和+右边的最小和+最后排序好的最小和就是最后的结果
return $this->mSort($data, $start, $mid) + $this->mSort($data, $mid + 1, $end) + $this->merge($data, $start, $mid, $end);
}
public function merge(&$data, $start, $mid, $end)
{
$l = $start;
$r = $mid + 1;
$i = $start;
$result = 0;
$tmpData = [];
while ($l != $mid + 1 && $r != $end + 1) {
// 如果左边小于右边,那就有($r - $l + 1)个$data[$l]元素的和是最小和
// 如果大于右边,返回0
$result += $data[$l] < $data[$r] ? $l * ($end - $r + 1) : 0;
// sum += $data[$l] > $data[$r] ? ($mid - $l + 1) : 0; //求逆序对
$tmpData[$i++] = $data[$l] < $data[$r] ? $data[$l++] : $data[$r++];
}
while ($l != $mid + 1) {
$tmpData[$i++] = $data[$l++];
}
while ($r != $end+ 1) {
$tmpData[$i++] = $data[$r++];
}
for ($i = $start; $i < $end; $i++) {
$data[$i] = $tmpData[$i];
}
return $result;
}
}