├── README.md ├── SUMMARY.md ├── graph ├── README.md ├── create.md ├── graph.php ├── search.md └── shortpath.md ├── linear ├── README.md ├── compare.md ├── doublelinked.md ├── linked.md ├── queue.md ├── seq.md └── stack.md ├── sort ├── README.md ├── bubble.md ├── compare.md ├── heap.md ├── insert.md ├── merge.md ├── quick.md ├── radix.md ├── select.md ├── shell.md └── sort.php ├── spl └── README.md └── tree ├── README.md ├── heap.md ├── heap.php ├── hoffman.php ├── huffman.md ├── invert.md ├── search.md └── traversal.md /README.md: -------------------------------------------------------------------------------- 1 | # 数据结构(PHP描述) 2 | 3 | 数据结构本身和编程语言无关,市面上所见到的数据结构的书籍一般是以C语言进行描述。搜索了一番并没有找到以PHP描述的数据结构的书籍,考虑到PHP是第二好的编程语言,所以我准备用PHP描述一遍基本的数据结构,也算是加深自己对数据结构的理解吧。 4 | 5 | 6 | [github仓库地址](https://github.com/jiangshanmeta/datastructure_php) 7 | 8 | [电子书在线地址](https://jiangshanmeta.gitbooks.io/datastructure_php/content/) -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | * [前言](README.md) 3 | * [线性结构](linear/README.md) 4 | * [顺序存储方式](linear/seq.md) 5 | * [链式存储方式](linear/linked.md) 6 | * [顺序存储和链式存储的比较](linear/compare.md) 7 | * [双向链表](linear/doublelinked.md) 8 | * [栈](linear/stack.md) 9 | * [队](linear/queue.md) 10 | * [树](tree/README.md) 11 | * [树的遍历](tree/traversal.md) 12 | * [反转二叉树](tree/invert.md) 13 | * [二叉搜索树](tree/search.md) 14 | * [堆](tree/heap.md) 15 | * [霍夫曼树](tree/huffman.md) 16 | * [图](graph/README.md) 17 | * [图的表示](graph/create.md) 18 | * [图的遍历](graph/search.md) 19 | * [最短路径](graph/shortpath.md) 20 | * [SPL提供的数据结构](spl/README.md) 21 | * [排序](sort/README.md) 22 | * [选择排序](sort/select.md) 23 | * [冒泡排序](sort/bubble.md) 24 | * [插入排序](sort/insert.md) 25 | * [希尔排序](sort/shell.md) 26 | * [堆排序](sort/heap.md) 27 | * [归并排序](sort/merge.md) 28 | * [快速排序](sort/quick.md) 29 | * [基数排序](sort/radix.md) 30 | * [排序算法比较](sort/compare.me) -------------------------------------------------------------------------------- /graph/README.md: -------------------------------------------------------------------------------- 1 | 在线性结构中,节点之间的关系是一对一,在树中,节点之间的关系是一对多,在图中,节点之间的关系是多对多。所以,在树中包含了线性关系,在图中,既包含了树又包含了线性结构。 2 | 3 | 我们采用了一个节点类```Node_graph```表示图中的节点。 4 | 5 | ```php 6 | class Graph_node{ 7 | protected $index; 8 | protected $data; 9 | function __construct($data=NULL){ 10 | $this->set_data($data); 11 | } 12 | 13 | function get_index(){ 14 | return $this->index; 15 | } 16 | function set_index($index){ 17 | $this->index = $index; 18 | } 19 | function get_data(){ 20 | return $this->data; 21 | } 22 | function set_data($data){ 23 | $this->data = $data; 24 | } 25 | } 26 | ``` 27 | 28 | 可以在[github查看最终的实现代码](https://github.com/jiangshanmeta/datastructure_php/blob/master/graph/graph.php) -------------------------------------------------------------------------------- /graph/create.md: -------------------------------------------------------------------------------- 1 | 首先要了解的概念是有向图和无向图。有向图的节点之间的边是有方向的,类似于向量,无向图节点之间的边没有方向,可以理解为有一条从A指向B的边,同时也有一条从B到A的边。 2 | 3 | 然后我们要解决的是如何表示图,这里的重点是如何表示多对多的关系。 4 | 5 | ```php 6 | class Graph{ 7 | protected $_nodes = []; 8 | protected $_edges = []; 9 | function __construct(){ 10 | 11 | } 12 | 13 | function insert($node){ 14 | if(!($node instanceof Graph_node)){ 15 | $node = new Graph_node($node); 16 | } 17 | $index = $this->length(); 18 | $node->set_index($index); 19 | $this->insert_indexed_node($node); 20 | } 21 | 22 | function insert_indexed_node($node){ 23 | $index = $node->get_index(); 24 | $this->_nodes[$index] = $node; 25 | } 26 | 27 | function length(){ 28 | return count($this->nodes); 29 | } 30 | 31 | function set_edge($from,$to,$weight=1){ 32 | if(!isset($this->_edges[$from])){ 33 | $this->_edges[$from] = []; 34 | } 35 | $this->_edges[$from][$to] = $weight; 36 | } 37 | 38 | function get_node_by_index($index){ 39 | return $this->_nodes[$index]; 40 | } 41 | 42 | } 43 | ``` 44 | 45 | 我们用一个```Graph```类表示图,其中```$nodes```用来存储节点,用```$_edges```表示边。```$_edges```的键为节点的索引,值是一个array的形式,这个array的键表示相邻节点的索引,array的值表示边的权重。上面的```set_edge```方法就是建立边的方法。 46 | 47 | 对于有向图来说,上面的```set_edge```方法已经足够了,对于无向图来说,当建立一条从A到B的边,我们同时需要建立从B到A的边。 48 | 49 | 50 | ```php 51 | class Graph_undirected extends Graph{ 52 | function __construct(){ 53 | parent::__construct(); 54 | } 55 | 56 | function set_edge($from,$to,$weight=1){ 57 | parent::set_edge($from,$to,$weight); 58 | parent::set_edge($to,$from,$weight); 59 | } 60 | } 61 | ``` 62 | 63 | 对于图的基本表示其实有很多方法,这里只是根据自己的理解对应的一种实现。 -------------------------------------------------------------------------------- /graph/graph.php: -------------------------------------------------------------------------------- 1 | set_data($data); 7 | } 8 | 9 | function get_index(){ 10 | return $this->index; 11 | } 12 | function set_index($index){ 13 | $this->index = $index; 14 | } 15 | function get_data(){ 16 | return $this->data; 17 | } 18 | function set_data($data){ 19 | $this->data = $data; 20 | } 21 | } 22 | 23 | class Graph{ 24 | protected $_nodes = []; 25 | protected $_edges = []; 26 | function __construct(){ 27 | 28 | } 29 | 30 | function insert($node){ 31 | if(!($node instanceof Graph_node)){ 32 | $node = new Graph_node($node); 33 | } 34 | $index = $this->length(); 35 | $node->set_index($index); 36 | $this->insert_indexed_node($node); 37 | } 38 | 39 | function length(){ 40 | return count($this->_nodes); 41 | } 42 | 43 | function insert_indexed_node($node){ 44 | $index = $node->get_index(); 45 | $this->_nodes[$index] = $node; 46 | } 47 | 48 | function set_edge($from,$to,$weight){ 49 | if(!isset($this->_edges[$from])){ 50 | $this->_edges[$from] = []; 51 | } 52 | $this->_edges[$from][$to] = $weight; 53 | } 54 | 55 | function get_node_by_index($index){ 56 | return $this->_nodes[$index]; 57 | } 58 | 59 | protected function _visit_stack($fn,&$stack,&$visitedHash){ 60 | while(!empty($stack)){ 61 | $node = array_pop($stack); 62 | $index = $node->get_index(); 63 | if(is_callable($fn,true)){ 64 | $fn($node->get_data()); 65 | } 66 | if(isset($this->_edges[$index])){ 67 | foreach ($this->_edges[$index] as $key => $value) { 68 | if(!isset($visitedHash[$key])){ 69 | $visitedHash[$key] = $key; 70 | array_push($stack,$this->get_node_by_index($key)); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | function dfs($fn,$node_index=0){ 78 | $stack = []; 79 | $visitedHash = []; 80 | $node = $this->get_node_by_index($node_index); 81 | $visitedHash[$node_index] = $node_index; 82 | array_push($stack,$node); 83 | $this->_visit_stack($fn,$stack,$visitedHash); 84 | 85 | // 有的节点没有和初始节点直接或间接相连 86 | $graph_length = $this->length(); 87 | while(count($visitedHash)<$graph_length){ 88 | for($i=0;$i<$graph_length;$i++){ 89 | if(isset($visitedHash[$i])){ 90 | continue; 91 | } 92 | $visitedHash[$i] = $i; 93 | array_push($stack,$this->get_node_by_index($i)); 94 | $this->_visit_stack($fn,$stack,$visitedHash); 95 | } 96 | } 97 | 98 | } 99 | 100 | protected function _visit_queue($fn,&$queue,&$visitedHash){ 101 | while(!empty($queue)){ 102 | $node = array_shift($queue); 103 | $index = $node->get_index(); 104 | if(is_callable($fn,true)){ 105 | $fn($node->get_data()); 106 | } 107 | if(isset($this->_edges[$index])){ 108 | foreach ($this->_edges[$index] as $key => $value) { 109 | if(!isset($visitedHash[$key])){ 110 | $visitedHash[$key] = $key; 111 | array_push($queue,$this->get_node_by_index($key)); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | 118 | function bfs($fn,$node_index=0){ 119 | $queue = []; 120 | $visitedHash = []; 121 | $node = $this->get_node_by_index($node_index); 122 | $visitedHash[$node_index] = $node_index; 123 | array_push($queue,$node); 124 | $this->_visit_stack($fn,$queue,$visitedHash); 125 | 126 | // 有的节点没有和初始节点直接或间接相连 127 | $graph_length = $this->length(); 128 | while(count($visitedHash)<$graph_length){ 129 | for($i=0;$i<$graph_length;$i++){ 130 | if(isset($visitedHash[$i])){ 131 | continue; 132 | } 133 | $visitedHash[$i] = $i; 134 | array_push($queue,$this->get_node_by_index($i)); 135 | $this->_visit_queue($fn,$queue,$visitedHash); 136 | } 137 | } 138 | } 139 | 140 | function shortpath_unweighted($from=0){ 141 | $dist = []; 142 | $path = []; 143 | $length = $this->length(); 144 | for($i=0;$i<$length;$i++){ 145 | $dist[$i] = -1; 146 | $path[$i] = -1; 147 | } 148 | $dist[$from] = 0; 149 | 150 | $queue = []; 151 | $node = $this->get_node_by_index($from); 152 | array_push($queue,$node); 153 | while (!empty($queue)) { 154 | $node = array_shift($queue); 155 | $index = $node->get_index(); 156 | if(!isset($this->_edges[$index])){ 157 | continue; 158 | } 159 | foreach ($this->_edges[$index] as $key => $value) { 160 | if($dist[$key]===-1){ 161 | $dist[$key] = $dist[$index] + 1; 162 | $path[$key] = $index; 163 | array_push($queue,$this->get_node_by_index($key)); 164 | } 165 | } 166 | } 167 | return [ 168 | 'dist'=>$dist, 169 | 'path'=>$path, 170 | ]; 171 | } 172 | 173 | function shortpath_weighted($from=0){ 174 | $dist = []; 175 | $path = []; 176 | $collected = []; 177 | $length = $this->length(); 178 | for($i=0;$i<$length;$i++){ 179 | if(isset($this->_edges[$from][$i])){ 180 | $dist[$i] = $this->_edges[$from][$i]; 181 | $path[$i] = $from; 182 | }else{ 183 | $dist[$i] = INF; 184 | $path[$i] = -1; 185 | } 186 | $collected[$i] = false; 187 | } 188 | $dist[$from] = 0; 189 | $collected[$from] = true; 190 | 191 | 192 | while(true){ 193 | $index = $this->_find_min_index_by_dist($dist,$collected); 194 | if($index===-1){ 195 | break; 196 | } 197 | $collected[$index] = true; 198 | if(!isset($this->_edges[$index])){ 199 | continue; 200 | } 201 | foreach ($this->_edges[$index] as $key => $value) { 202 | if($collected[$key]){ 203 | continue; 204 | } 205 | if($dist[$index]+$this->_edges[$index][$key]<$dist[$key]){ 206 | $dist[$key] = $dist[$index] + $this->_edges[$index][$key]; 207 | $path[$key] = $index; 208 | } 209 | 210 | } 211 | } 212 | 213 | return [ 214 | 'dist'=>$dist, 215 | 'path'=>$path, 216 | ]; 217 | } 218 | 219 | private function _find_min_index_by_dist(&$dist,&$collected){ 220 | $minIndex = -1; 221 | $minDist = INF; 222 | $length = $this->length(); 223 | for($i=0;$i<$length;$i++){ 224 | if(!$collected[$i]&&$dist[$i]<$minDist){ 225 | $minIndex = $i; 226 | $minDist = $dist[$i]; 227 | } 228 | } 229 | return $minIndex; 230 | } 231 | 232 | 233 | function shortpath_multisource(){ 234 | $dist = []; 235 | $path = []; 236 | $length = $this->length(); 237 | // 初始化距离,到自己距离为0,没直接相连距离为INF 238 | for($i=0;$i<$length;$i++){ 239 | $edges = isset($this->_edges[$i])?$this->_edges[$i]:[]; 240 | for($j=0;$j<$length;$j++){ 241 | if($i===$j){ 242 | $dist[$i][$j] = 0; 243 | }else if(isset($edges[$j])){ 244 | $dist[$i][$j] = $edges[$j]; 245 | }else{ 246 | $dist[$i][$j] = INF; 247 | } 248 | $path[$i][$j] = -1; 249 | } 250 | } 251 | 252 | for($k=0;$k<$length;$k++){ 253 | for($i=0;$i<$length;$i++){ 254 | for($j=0;$j<$length;$j++){ 255 | if($dist[$i][$k]+$dist[$k][$j]<$dist[$i][$j]){ 256 | $dist[$i][$j] = $dist[$i][$k] + $dist[$k][$j]; 257 | $path[$i][$j] = $k; 258 | } 259 | } 260 | } 261 | } 262 | 263 | return [ 264 | 'dist'=>$dist, 265 | 'path'=>$path 266 | ]; 267 | 268 | } 269 | 270 | 271 | 272 | 273 | } 274 | 275 | class Graph_undirected extends Graph{ 276 | function __construct(){ 277 | parent::__construct(); 278 | } 279 | 280 | function set_edge($from,$to,$weight){ 281 | parent::set_edge($from,$to,$weight); 282 | parent::set_edge($to,$from,$weight); 283 | } 284 | } 285 | 286 | class Graph_directed extends Graph{ 287 | function __construct(){ 288 | parent::__construct(); 289 | } 290 | 291 | 292 | } 293 | 294 | 295 | 296 | $graph = new Graph(); 297 | 298 | $graph->insert('index-0'); 299 | $graph->insert('index-1'); 300 | $graph->insert('index-2'); 301 | $graph->insert('index-3'); 302 | $graph->insert('index-4'); 303 | $graph->insert('index-5'); 304 | $graph->insert('index-6'); 305 | 306 | $graph->set_edge(0,1,2); 307 | $graph->set_edge(0,3,1); 308 | $graph->set_edge(1,3,3); 309 | $graph->set_edge(1,4,10); 310 | $graph->set_edge(3,4,2); 311 | $graph->set_edge(3,2,2); 312 | $graph->set_edge(2,0,4); 313 | 314 | $graph->set_edge(3,5,8); 315 | $graph->set_edge(2,5,5); 316 | $graph->set_edge(3,6,4); 317 | $graph->set_edge(4,6,6); 318 | $graph->set_edge(6,5,1); 319 | // $graph->insert('index-7'); 320 | 321 | 322 | 323 | 324 | 325 | // $graph->set_edge(0,2,9); 326 | // $graph->set_edge(2,5,9); 327 | // $graph->set_edge(1,3,9); 328 | // $graph->set_edge(0,5,9); 329 | // $graph->set_edge(5,1,8); 330 | 331 | // $graph->bfs(function($data){ 332 | // var_dump($data); 333 | // }); 334 | // $short = $graph->shortpath_unweighted(); 335 | // var_dump($short); 336 | 337 | $short = $graph->shortpath_multisource(); 338 | var_dump($short); 339 | 340 | // var_dump(INF); 341 | // var_dump(9999 -------------------------------------------------------------------------------- /graph/search.md: -------------------------------------------------------------------------------- 1 | 图的遍历有两种基本方式:深度优先搜索(Depth First Search)和广度优先搜索(Breadth First Search)。看着这两个词觉得挺陌生的,但是在树中我们学习了他们的特殊形式先序遍历和层序遍历。 2 | 3 | ```php 4 | protected function _visit_stack($fn,&$stack,&$visitedHash){ 5 | while(!empty($stack)){ 6 | $node = array_pop($stack); 7 | $index = $node->get_index(); 8 | if(is_callable($fn,true)){ 9 | $fn($node->get_data()); 10 | } 11 | if(isset($this->_edges[$index])){ 12 | foreach ($this->_edges[$index] as $key => $value) { 13 | if(!isset($visitedHash[$key])){ 14 | $visitedHash[$key] = $key; 15 | array_push($stack,$this->get_node_by_index($key)); 16 | } 17 | } 18 | } 19 | } 20 | } 21 | 22 | function dfs($fn,$node_index=0){ 23 | $stack = []; 24 | $visitedHash = []; 25 | $node = $this->get_node_by_index($node_index); 26 | $visitedHash[$node_index] = $node_index; 27 | array_push($stack,$node); 28 | $this->_visit_stack($fn,$stack,$visitedHash); 29 | 30 | // 有的节点没有和初始节点直接或间接相连 31 | $graph_length = $this->length(); 32 | while(count($visitedHash)<$graph_length){ 33 | for($i=0;$i<$graph_length;$i++){ 34 | if(isset($visitedHash[$i])){ 35 | continue; 36 | } 37 | $visitedHash[$i] = $i; 38 | array_push($stack,$this->get_node_by_index($i)); 39 | $this->_visit_stack($fn,$stack,$visitedHash); 40 | } 41 | } 42 | 43 | } 44 | ``` 45 | 46 | 类似于先序遍历,深度优先搜索需要借助于一个栈结构。简化起见我这里直接使用了一个array模拟了一个栈。与栈不同的是一个节点可能从不同路径上被访问到,所以才有了```$hash```这个变量存储哪些节点被访问过了或者加入了待访问序列。 47 | 48 | ```php 49 | protected function _visit_queue($fn,&$queue,&$visitedHash){ 50 | while(!empty($queue)){ 51 | $node = array_shift($queue); 52 | $index = $node->get_index(); 53 | if(is_callable($fn,true)){ 54 | $fn($node->get_data()); 55 | } 56 | if(isset($this->_edges[$index])){ 57 | foreach ($this->_edges[$index] as $key => $value) { 58 | if(!isset($visitedHash[$key])){ 59 | $visitedHash[$key] = $key; 60 | array_push($queue,$this->get_node_by_index($key)); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | function bfs($fn,$node_index=0){ 68 | $queue = []; 69 | $visitedHash = []; 70 | $node = $this->get_node_by_index($node_index); 71 | $visitedHash[$node_index] = $node_index; 72 | array_push($queue,$node); 73 | $this->_visit_stack($fn,$queue,$visitedHash); 74 | 75 | // 有的节点没有和初始节点直接或间接相连 76 | $graph_length = $this->length(); 77 | while(count($visitedHash)<$graph_length){ 78 | for($i=0;$i<$graph_length;$i++){ 79 | if(isset($visitedHash[$i])){ 80 | continue; 81 | } 82 | $visitedHash[$i] = $i; 83 | array_push($queue,$this->get_node_by_index($i)); 84 | $this->_visit_queue($fn,$queue,$visitedHash); 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | 图的广度优先搜索类似于树的层序遍历,需要借助于一个队结构。 -------------------------------------------------------------------------------- /graph/shortpath.md: -------------------------------------------------------------------------------- 1 | 图的最短路径问题是一类问题,其中最短既可以指经过的路径边最少,又可以指经过的路径权重和最小。对于经过的边最少这个含义,我们可以把它看成无权图的最短路径问题,也可以看成有权图的一种特殊情况,即所有边权重一致。 2 | 3 | ## 单源无权图最短路径 4 | 5 | 图的最短路径问题,最简单的一种情况就是无权图从一个节点出发,找到其它节点的最短路径。 6 | 7 | 它的基本思路是这样的:从起点开始,按照到起点的距离从小到大向外探索节点,新探索的节点到起点的距离为上一个节点到起点的距离+1。这里的按照到起点的距离和之前提到的广度优先搜索思路是一致的。 8 | 9 | 10 | ```php 11 | function shortpath_unweighted($from=0){ 12 | $dist = []; 13 | $path = []; 14 | $length = $this->length(); 15 | for($i=0;$i<$length;$i++){ 16 | $dist[$i] = -1; 17 | $path[$i] = -1; 18 | } 19 | $dist[$from] = 0; 20 | 21 | $queue = []; 22 | $node = $this->get_node_by_index($from); 23 | array_push($queue,$node); 24 | // 广度优先搜索 25 | while (!empty($queue)) { 26 | $node = array_shift($queue); 27 | $index = $node->get_index(); 28 | if(!isset($this->_edges[$index])){ 29 | continue; 30 | } 31 | foreach ($this->_edges[$index] as $key => $value) { 32 | // 新探索的节点到起点的距离为上一个节点```$index```到起点距离+1 33 | if($dist[$key]===-1){ 34 | $dist[$key] = $dist[$index] + 1; 35 | $path[$key] = $index; 36 | array_push($queue,$this->get_node_by_index($key)); 37 | } 38 | } 39 | } 40 | return [ 41 | 'dist'=>$dist, 42 | 'path'=>$path, 43 | ]; 44 | } 45 | ``` 46 | 47 | 这里有两个数组```$dist```好```$path```,前者存放到起点的距离,后者存放最短路径上前一个元素的索引。这里的```$dist```初始化为-1,也可以初始化为正无穷或者负无穷,总之能表示距离未被探索即可。 48 | 49 | 50 | ## 单源有权图最短路径 51 | 52 | 解决这个问题的算法有个名字是**Dijkstra**,它的思路是这样的:从未找到最短路径的节点中找到距离起点最近的节点,这样这个新节点的最短路径就确定了,然后通知这个节点的未找到最短路径的相邻节点更新到起点的距离。 53 | 54 | 55 | 56 | ```php 57 | private function _find_min_index_by_dist(&$dist,&$collected){ 58 | $minIndex = -1; 59 | $minDist = INF; 60 | $length = $this->length(); 61 | for($i=0;$i<$length;$i++){ 62 | if(!$collected[$i]&&$dist[$i]<$minDist){ 63 | $minIndex = $i; 64 | $minDist = $dist[$i]; 65 | } 66 | } 67 | return $minIndex; 68 | } 69 | function shortpath_weighted($from=0){ 70 | $dist = []; 71 | $path = []; 72 | $collected = []; 73 | $length = $this->length(); 74 | // 初始化,到起点没有直接边的距离为无穷 75 | for($i=0;$i<$length;$i++){ 76 | if(isset($this->_edges[$from][$i])){ 77 | $dist[$i] = $this->_edges[$from][$i]; 78 | $path[$i] = $from; 79 | }else{ 80 | $dist[$i] = INF; 81 | $path[$i] = -1; 82 | } 83 | $collected[$i] = false; 84 | } 85 | $dist[$from] = 0; 86 | $collected[$from] = true; 87 | 88 | 89 | while(true){ 90 | // 从未找到最短路径的节点中找出距离最小的 91 | $index = $this->_find_min_index_by_dist($dist,$collected); 92 | if($index===-1){ 93 | break; 94 | } 95 | // 标记该节点已找到最短路径 96 | $collected[$index] = true; 97 | if(!isset($this->_edges[$index])){ 98 | continue; 99 | } 100 | // 通知相邻节点路程更新 101 | foreach ($this->_edges[$index] as $key => $value) { 102 | if($collected[$key]){ 103 | continue; 104 | } 105 | if($dist[$index]+$this->_edges[$index][$key]<$dist[$key]){ 106 | $dist[$key] = $dist[$index] + $this->_edges[$index][$key]; 107 | $path[$key] = $index; 108 | } 109 | 110 | } 111 | } 112 | 113 | return [ 114 | 'dist'=>$dist, 115 | 'path'=>$path, 116 | ]; 117 | } 118 | ``` 119 | 120 | 最开始我就说,单源无权图的最短路径问题是单源有权图的最短路径问题的一种特殊情况,我们比较一下这两个实现:在无权图中出队操作相当于有权图中找距离最小的节点,无权图中距离+1操作(可以看做权重均为1)对应有权图中更新距离操作。 121 | 122 | 在我的这个实现中,对应算法复杂度为O(n^2),while循环执行N次,找最小距离节点用的是直接暴力遍历对应N,所以时间复杂度为O(n^2)。一个改进是找最小距离节点那一步使用最小堆。 123 | 124 | ## 多源有权图最短路径 125 | 126 | 有了单源有权图最短路径的解决方案,多源有权图最短路径似乎没什么难度,遍历一遍节点调用单源有权图解决方案就好了,这样时间复杂度为O(n^3)。 127 | 128 | 还有个改进方案被称为**Floyd**算法: 129 | 130 | ```php 131 | function shortpath_multisource(){ 132 | $dist = []; 133 | $path = []; 134 | $length = $this->length(); 135 | // 初始化距离,到自己距离为0,没直接相连距离为INF 136 | for($i=0;$i<$length;$i++){ 137 | $edges = isset($this->_edges[$i])?$this->_edges[$i]:[]; 138 | for($j=0;$j<$length;$j++){ 139 | if($i===$j){ 140 | $dist[$i][$j] = 0; 141 | }else if(isset($edges[$j])){ 142 | $dist[$i][$j] = $edges[$j]; 143 | }else{ 144 | $dist[$i][$j] = INF; 145 | } 146 | $path[$i][$j] = -1; 147 | } 148 | } 149 | 150 | for($k=0;$k<$length;$k++){ 151 | for($i=0;$i<$length;$i++){ 152 | for($j=0;$j<$length;$j++){ 153 | if($dist[$i][$k]+$dist[$k][$j]<$dist[$i][$j]){ 154 | $dist[$i][$j] = $dist[$i][$k] + $dist[$k][$j]; 155 | $path[$i][$j] = $k; 156 | } 157 | } 158 | } 159 | } 160 | 161 | return [ 162 | 'dist'=>$dist, 163 | 'path'=>$path 164 | ]; 165 | 166 | } 167 | ``` 168 | 169 | 可以看到这个算法时间复杂度也为O(n^3),毕竟三个for循环套在一起。虽然也为O(n^3),但比第一个方案优化一点。 -------------------------------------------------------------------------------- /linear/README.md: -------------------------------------------------------------------------------- 1 | 线性结构是最常用且最简单的一种数据结构。一个线性表是n个数据元素的有限序列。 2 | 3 | 在这里我们首先介绍线性结构的两种实现——顺序表示和链式表示。在此基础上介绍两种特殊的线性结构——栈和队。 -------------------------------------------------------------------------------- /linear/compare.md: -------------------------------------------------------------------------------- 1 | 之前已经介绍了线性表的顺序存储和链式存储的实现和操作。下面就这两种实现进行比较。 2 | 3 | 顺序表有以下优点:存储密度大,无需为表示元素之间的逻辑关系额外使用存储空间;根据序号查找元素很容易。 4 | 5 | 顺序表的缺点是插入和删除元素需要移动大量的操作,这一点我在实现的时候强调过。 6 | 7 | 链表优点是插入和删除操作只需要修改相关指针域,不需要移动元素。 8 | 9 | 链表的缺点是存储密度小,需要额外的字段表示元素间的关系;根据序号查找元素需要从头找起。 10 | 11 | -------------------------------------------------------------------------------- /linear/doublelinked.md: -------------------------------------------------------------------------------- 1 | 之前所讨论的链表是单向链表,只能从前向后寻找,无法从后向前查找节点,在这一节我们来解决这个问题。 2 | 3 | 首先要对节点进行扩展: 4 | 5 | ```php 6 | class Node_doublelinked extends Node_linked{ 7 | public $prev = NULL; 8 | function __construct($value=NULL){ 9 | parent::__construct($value); 10 | } 11 | } 12 | ``` 13 | 14 | 我们是基于之前的节点类```Node_linked```进行了改造,添加了一个```$prev```属性,这个属性指向了前一个节点。 15 | 16 | 随后我们实现双向链表类: 17 | 18 | ```php 19 | class Linear_doublelinked extends Linear_linked{ 20 | function __construct(){ 21 | $this->_head = new Node_doublelinked(); 22 | } 23 | 24 | function insert($value,$index){ 25 | $length = $this->length(); 26 | if($index<0 || $index>$length){ 27 | return false; 28 | } 29 | $node = new Node_doublelinked($value); 30 | $prev = $this->head(); 31 | for($i=0;$i<$index;$i++){ 32 | $prev = $prev->next; 33 | } 34 | $node->next = $prev->next; 35 | $node->prev = $prev; 36 | if($prev->next){ 37 | $prev->next->prev = $node; 38 | } 39 | $prev->next = $node; 40 | 41 | return true; 42 | } 43 | 44 | function delete($index){ 45 | $length = $this->length(); 46 | if($index<0 || $index>$length-1){ 47 | return NULL; 48 | } 49 | $prev = $this->head(); 50 | for($i=0;$i<$index;$i++){ 51 | $prev = $prev->next; 52 | } 53 | $next = $prev->next; 54 | $value = $next->data; 55 | if($next->next){ 56 | $next->next->prev = $prev; 57 | } 58 | $prev->next = $next->next; 59 | $next->next = NULL; 60 | $next->prev = NULL; 61 | unset($next); 62 | return $value; 63 | } 64 | } 65 | ``` 66 | 67 | 我这里只实现了双向链表的插入和删除操作,其他需要修改的操作,如```merge```请自行完成。在插入时,双向链表所需要改变的指针域更多,要考虑对```$prev```属性进行设置,注意这里的顺序。在删除时,也需要改变后继节点的```$prev```指向。 -------------------------------------------------------------------------------- /linear/linked.md: -------------------------------------------------------------------------------- 1 | 线性结构的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素。 2 | 3 | 在这里,我们用一个特殊的节点类表示存储元素。 4 | 5 | ```php 6 | class Node_linked{ 7 | public $data; 8 | public $next = NULL; 9 | public function __construct($value=NULL){ 10 | $this->data = $value; 11 | } 12 | 13 | public function update($newValue){ 14 | $this->data = $newValue; 15 | } 16 | } 17 | ``` 18 | 19 | 对于每一个节点,有一个特殊的数据域```$data```表示存储的数据,还有一个指针域```$next```指向下一节点。这个类仅仅用来表示最简单的单向链表的节点,只能依次向后寻找节点。双向链表的节点其实差不多,只是多了一个前一个节点的指针域。 20 | 21 | 为了操作方便,我们为链式存储添加一个头结点。 22 | 23 | ```php 24 | class Linear_linked{ 25 | protected $_head; 26 | function __construct(){ 27 | $this->_head = new Node_linked(); 28 | } 29 | 30 | function head(){ 31 | return $this->_head; 32 | } 33 | 34 | } 35 | ``` 36 | 37 | 链式存储的所需要实现的操作和顺序存储是一致的,我们首先看长度相关的几个方法。 38 | 39 | ```php 40 | function head(){ 41 | return $this->_head; 42 | } 43 | 44 | function length(){ 45 | $length = 0; 46 | $prev = $this->head(); 47 | while($prev->next){ 48 | $prev = $prev->next; 49 | $length++; 50 | } 51 | return $length; 52 | } 53 | 54 | function isEmpty(){ 55 | return $this->head()->next === NULL; 56 | } 57 | ``` 58 | 59 | 在这里不能直接调用count来获得线性表的长度了(其实对于PHP也是可以的,但是要实现某个特殊的接口,最终还是要自己实现),需要我们手动遍历来计数。遍历的终点就是指向下一个节点的指针域为空。 60 | 61 | 然后就是插入操作了 62 | 63 | ``` 64 | function insert($value,$index){ 65 | $length = $this->length(); 66 | if($index<0 || $index>$length){ 67 | return false; 68 | } 69 | $node = new Node_linked($value); 70 | $prev = $this->head(); 71 | for($i=0;$i<$index;$i++){ 72 | $prev = $prev->next; 73 | } 74 | $node->next = $prev->next; 75 | $prev->next = $node; 76 | 77 | return true; 78 | } 79 | ``` 80 | 81 | 与顺序存储相比,链式存储需要的操作少多了,他不需要把```$index```及以后的每一个元素都移动,在链式存储中只需要修改有限的几个指针域就可以了。相关的```push```操作和```unshift```操作的实现和顺序存储的是一致的,都是间接调用```insert```操作。 82 | 83 | 删除操作 84 | 85 | ```php 86 | function delete($index){ 87 | $length = $this->length(); 88 | if($index<0 || $index>$length-1){ 89 | return NULL; 90 | } 91 | $prev = $this->head(); 92 | for($i=0;$i<$index;$i++){ 93 | $prev = $prev->next; 94 | } 95 | $next = $prev->next; 96 | $value = $next->data; 97 | $prev->next = $next->next; 98 | $next->next = NULL; 99 | unset($next); 100 | return $value; 101 | } 102 | ``` 103 | 104 | 删除操作也是需要找到前一个节点,然后修改几个指针域即可,不需要顺序存储那样大量的挪动位置。 105 | 106 | 根据序号寻找元素就比较折腾了。 107 | 108 | ```php 109 | function findEleByIndex($index){ 110 | $length = $this->length(); 111 | if($index<0 || $index>$length-1){ 112 | return NULL; 113 | } 114 | $prev = $this->head(); 115 | for($i=0;$i<$index;$i++){ 116 | $prev = $prev->next; 117 | } 118 | 119 | return $prev->next->data; 120 | } 121 | ``` 122 | 123 | 需要我们手动计数寻找对应的节点,而根据元素寻找序号和顺序结构类似,都需要一个一个按顺序比对。 124 | 125 | ```php 126 | function findIndexByEle($value){ 127 | $length = $this->length(); 128 | $prev = $this->head(); 129 | for($i=0;$i<$length;$i++){ 130 | $prev = $prev->next; 131 | if($prev->data === $value){ 132 | return $i; 133 | } 134 | } 135 | return -1; 136 | } 137 | ``` 138 | 139 | 到这里我们会发现有相当一部分代码是重复的,我们需要一个根据索引找节点(上面的是找元素)的功能,请大家自行实现```findNodeByIndex```这个方法(为了使用方便,我们需要返回序号对应节点的前一个节点)。 140 | 141 | 142 | 至于merge功能,对于链式存储结构来说就更简单了,因为只需要首尾相连即可。 143 | 144 | ```php 145 | function tail(){ 146 | $prev = $this->head(); 147 | while($prev->next){ 148 | $prev = $prev->next; 149 | } 150 | return $prev; 151 | } 152 | function merge($linked){ 153 | if(!($linked instanceof Linear_linked)){ 154 | return false; 155 | } 156 | $this->tail()->next = $linked->head()->next; 157 | return true; 158 | } 159 | ``` 160 | 161 | 这样链式存储的线性表的基本操作就完成了。当前的代码清单如下: 162 | 163 | ```php 164 | class Node_linked{ 165 | public $data; 166 | public $next = NULL; 167 | public function __construct($value=NULL){ 168 | $this->data = $value; 169 | } 170 | 171 | public function update($newValue){ 172 | $this->data = $newValue; 173 | } 174 | } 175 | 176 | class Linear_linked{ 177 | protected $_head; 178 | function __construct(){ 179 | $this->_head = new Node_linked(); 180 | } 181 | 182 | function head(){ 183 | return $this->_head; 184 | } 185 | 186 | function tail(){ 187 | $prev = $this->head(); 188 | while($prev->next){ 189 | $prev = $prev->next; 190 | } 191 | return $prev; 192 | } 193 | 194 | function insert($value,$index){ 195 | $length = $this->length(); 196 | if($index<0 || $index>$length){ 197 | return false; 198 | } 199 | $node = new Node_linked($value); 200 | $prev = $this->head(); 201 | for($i=0;$i<$index;$i++){ 202 | $prev = $prev->next; 203 | } 204 | $node->next = $prev->next; 205 | $prev->next = $node; 206 | 207 | return true; 208 | } 209 | 210 | function push($value){ 211 | return $this->insert($value,$this->length()); 212 | } 213 | 214 | function unshift($value){ 215 | return $this->insert($value,0); 216 | } 217 | 218 | function delete($index){ 219 | $length = $this->length(); 220 | if($index<0 || $index>$length-1){ 221 | return NULL; 222 | } 223 | $prev = $this->head(); 224 | for($i=0;$i<$index;$i++){ 225 | $prev = $prev->next; 226 | } 227 | $next = $prev->next; 228 | $value = $next->data; 229 | $prev->next = $next->next; 230 | $next->next = NULL; 231 | unset($next); 232 | return $value; 233 | } 234 | 235 | function pop(){ 236 | return $this->delete($this->length()-1); 237 | } 238 | 239 | function shift(){ 240 | return $this->delete(0); 241 | } 242 | 243 | function length(){ 244 | $length = 0; 245 | $prev = $this->head(); 246 | while($prev->next){ 247 | $prev = $prev->next; 248 | $length++; 249 | } 250 | return $length; 251 | } 252 | 253 | function isEmpty(){ 254 | return $this->head()->next === NULL; 255 | } 256 | 257 | function clear(){ 258 | $this->head()->next = NULL; 259 | } 260 | 261 | function findEleByIndex($index){ 262 | $length = $this->length(); 263 | if($index<0 || $index>$length-1){ 264 | return NULL; 265 | } 266 | $prev = $this->head(); 267 | for($i=0;$i<$index;$i++){ 268 | $prev = $prev->next; 269 | } 270 | 271 | return $prev->next->data; 272 | } 273 | 274 | function findIndexByEle($value){ 275 | $length = $this->length(); 276 | $prev = $this->head(); 277 | for($i=0;$i<$length;$i++){ 278 | $prev = $prev->next; 279 | if($prev->data === $value){ 280 | return $i; 281 | } 282 | } 283 | return -1; 284 | } 285 | 286 | function update($newValue,$index){ 287 | $length = $this->length(); 288 | if($index<0 || $index>$length-1){ 289 | return false; 290 | } 291 | $prev = $this->head(); 292 | for($i=0;$i<$index;$i++){ 293 | $prev = $prev->next; 294 | } 295 | $prev->next->update($newValue); 296 | return true; 297 | } 298 | 299 | function merge($linked){ 300 | if(!($linked instanceof Linear_linked)){ 301 | return false; 302 | } 303 | $this->tail()->next = $linked->head()->next; 304 | return true; 305 | } 306 | 307 | // 返回序号所对应的节点的前一个节点 308 | function findNodeByIndex($index){ 309 | $length = $this->length(); 310 | if($index<0 || $index>$length-1){ 311 | return NULL; 312 | } 313 | $prev = $this->head(); 314 | for($i=0;$i<$index;$i++){ 315 | $prev = $prev->next; 316 | } 317 | return $prev; 318 | } 319 | 320 | 321 | } 322 | 323 | ``` -------------------------------------------------------------------------------- /linear/queue.md: -------------------------------------------------------------------------------- 1 | 队也是一种特殊的线性结构,它只能在一端插入,在另一端删除,与队相关的一个词是先进先出(First In First Out)。 2 | 3 | 我们依然可以把之前的顺序表和链表的实现拿过来说这是个队,毕竟常用的操作```push```和```shift```都实现了。 -------------------------------------------------------------------------------- /linear/seq.md: -------------------------------------------------------------------------------- 1 | 线性结构的顺序表示指的是用一组地址连续的存储单元依次存储线性结构的数据元素。在PHP中我们可以通过数组模拟这一特征。 2 | 3 | ```php 4 | class Linear_seq{ 5 | protected $_cache = []; 6 | function __construct(){ 7 | 8 | } 9 | 10 | } 11 | ``` 12 | 13 | 为了聚合相关操作,我使用一个*Linear_seq*类,其中的```$_cache```是顺序存储体。 14 | 15 | 定义了最基本的结构,我们开始实现操作。 16 | 17 | 第一个操作的获取线性结构的长度。 18 | 19 | ```php 20 | function length(){ 21 | return count($this->_cache); 22 | } 23 | ``` 24 | 25 | 第二个操作是线性结构是否为空 26 | 27 | ```php 28 | function isEmpty(){ 29 | return empty($this->_cache); 30 | } 31 | ``` 32 | 33 | 上面两个操作没什么可说的,第三个操作是向顺序结构中插入元素 34 | 35 | ```php 36 | function insert($value,$index){ 37 | $length = $this->length(); 38 | if($index<0 || $index>$length){ 39 | return false; 40 | } 41 | for($i=$length;$i>$index;$i--){ 42 | $this->_cache[$i] = $this->_cache[$i-1]; 43 | } 44 | $this->_cache[$index] = $value; 45 | return true; 46 | } 47 | ``` 48 | 49 | 插入操作第一个参数是要插入的节点,第二个参数是要插入的位置,这个位置是从零开始的。首先校验插入位置的合法性,如果位置不合法返回false表示插入失败。然后进行插入操作,这里首先要做的是把第```$index```的位置空出来,对应for循环,注意这里挪位置的顺序,空出来了位置,我们才能把新元素插进来。你可能会说这里直接用```array_splice```不就好了吗,何必这么折腾。虽然PHP为我们提供了强大的工具,但是一些基础的操作还是要会。插入成功后返回true表示成功。 50 | 51 | 与插入相关的另外两个操作是```push```和```unshift```,有过编程经验的对这两个操作应该不陌生。 52 | 53 | ```php 54 | function push($value){ 55 | return $this->insert($value,$this->length()); 56 | } 57 | 58 | function unshift($value){ 59 | return $this->insert($value,0); 60 | } 61 | ``` 62 | 63 | push就是插入到最后,unshift就是插入到最前面。 64 | 65 | 66 | 插入操作完成后,我们要做的是删除操作。 67 | 68 | ```php 69 | function delete($index){ 70 | $length = $this->length(); 71 | if($index<0 || $index>$length-1){ 72 | return NULL; 73 | } 74 | $val = $this->_cache[$index]; 75 | for($i=$index;$i<$length-1;$i++){ 76 | $this->_cache[$i] = $this->_cache[$i+1]; 77 | } 78 | unset($this->_cache[$length-1]); 79 | return $val; 80 | } 81 | ``` 82 | 83 | 删除操作传入的是要删除的序号,在判断了序号合法性之后,我们把该序号以后的元素向前挪,最后返回被删除的元素。注意这里挪动的顺序和上面插入时挪动的顺序的差异。 84 | 85 | 与删除有关的操作是```pop```和```shift``` 86 | 87 | ```php 88 | function pop(){ 89 | return $this->delete($this->length()-1); 90 | } 91 | 92 | function shift(){ 93 | return $this->delete(0); 94 | } 95 | ``` 96 | 97 | 可以看出,pop是删除最后一个元素,shift是删除第一个元素。 98 | 99 | 增删解决以后,我们要做的是查找。有两种查找类型,一种是根据序号找到元素,另一种根据元素找序号。 100 | 101 | ```php 102 | function findEleByIndex($index){ 103 | $length = $this->length(); 104 | if($index<0 || $index>$length-1){ 105 | return NULL; 106 | } 107 | return $this->_cache[$index]; 108 | } 109 | 110 | function findIndexByEle($value){ 111 | $length = $this->length(); 112 | for($i=0;$i<$length;$i++){ 113 | if($value === $this->_cache[$i]){ 114 | return $i; 115 | } 116 | } 117 | return -1; 118 | } 119 | ``` 120 | 121 | 对于顺序存储的线性结构来说,根据序号找元素很容易实现,不做过多说明,根据元素找序号需要遍历,找到第一个就返回序号,遍历完没找到元素则返回-1。 122 | 123 | 作为一名CURD工程师,还有一个改操作需要实现。 124 | 125 | ```php 126 | function update($newValue,$index){ 127 | $length = $this->length(); 128 | if($index<0 || $index>$length-1){ 129 | return false; 130 | } 131 | $this->_cache[$index] = $newValue; 132 | return true; 133 | } 134 | ``` 135 | 136 | 到这里一个顺序存储的线性结构的操作基本上就结束了,下面的操作是两个线性结构之间的操作。 137 | 138 | 第一个是将第二个线性结构合并到第一个线性结构中 139 | 140 | ```php 141 | function merge($seq){ 142 | if(!($seq instanceof Linear_seq)){ 143 | return false; 144 | } 145 | $length = $seq->length(); 146 | for($i=0;$i<$length;$i++){ 147 | $this->push($seq->findEleByIndex($i)); 148 | } 149 | return true; 150 | } 151 | ``` 152 | 153 | 算是上面操作的一个综合运用吧,思路就是把第二个线性结构每一个元素都加入到第一个线性结构中 154 | 155 | 第二个是求两个线性结构元素的并集。 156 | 157 | ```php 158 | static function union($seq1,$seq2){ 159 | $seq = new Linear_seq(); 160 | if(!($seq1 instanceof Linear_seq) || !($seq2 instanceof Linear_seq)){ 161 | return $seq; 162 | } 163 | $length1 = $seq1->length(); 164 | for($i=0;$i<$length1;$i++){ 165 | $seq->push($seq1->findEleByIndex($i)); 166 | } 167 | 168 | $length2 = $seq2->length(); 169 | for($i=0;$i<$length2;$i++){ 170 | $value = $seq2->findEleByIndex($i); 171 | if($seq->findIndexByEle($value)===-1){ 172 | $seq->push($value); 173 | } 174 | } 175 | 176 | return $seq; 177 | } 178 | ``` 179 | 180 | 并集操作返回的是一个新的线性结构,并集操作的思路也不难,首先是把第一个线性结构的元素复制,然后把第二个线性结构特有的元素添加进去。 181 | 182 | 183 | 184 | ```php 185 | static function intersection($seq1,$seq2){ 186 | $seq = new Linear_seq(); 187 | if(!($seq1 instanceof Linear_seq) || !($seq2 instanceof Linear_seq)){ 188 | return $seq; 189 | } 190 | 191 | $length = $seq1->length(); 192 | for($i=0;$i<$length;$i++){ 193 | $value = $seq1->findEleByIndex($i); 194 | if($seq2->findIndexByEle($value)!==-1){ 195 | $seq->push($value); 196 | } 197 | } 198 | return $seq; 199 | } 200 | ``` 201 | 202 | 有了并集操作的例子,交集操作实现起来也不复杂,不多说了。 203 | 204 | 205 | 到现在,我们有了如下代码: 206 | 207 | ```php 208 | class Linear_seq{ 209 | protected $_cache = []; 210 | function __construct(){ 211 | 212 | } 213 | 214 | function insert($value,$index){ 215 | $length = $this->length(); 216 | if($index<0 || $index>$length){ 217 | return false; 218 | } 219 | for($i=$length;$i>$index;$i--){ 220 | $this->_cache[$i] = $this->_cache[$i-1]; 221 | } 222 | $this->_cache[$index] = $value; 223 | return true; 224 | } 225 | 226 | function push($value){ 227 | return $this->insert($value,$this->length()); 228 | } 229 | 230 | function unshift($value){ 231 | return $this->insert($value,0); 232 | } 233 | 234 | function delete($index){ 235 | $length = $this->length(); 236 | if($index<0 || $index>$length-1){ 237 | return NULL; 238 | } 239 | $val = $this->_cache[$index]; 240 | for($i=$index;$i<$length-1;$i++){ 241 | $this->_cache[$i] = $this->_cache[$i+1]; 242 | } 243 | unset($this->_cache[$length-1]); 244 | return $val; 245 | } 246 | 247 | function pop(){ 248 | return $this->delete($this->length()-1); 249 | } 250 | 251 | function shift(){ 252 | return $this->delete(0); 253 | } 254 | 255 | 256 | function length(){ 257 | return count($this->_cache); 258 | } 259 | 260 | function clear(){ 261 | $this->_cache = []; 262 | } 263 | 264 | function isEmpty(){ 265 | return empty($this->_cache); 266 | } 267 | 268 | 269 | function findEleByIndex($index){ 270 | $length = $this->length(); 271 | if($index<0 || $index>$length-1){ 272 | return NULL; 273 | } 274 | return $this->_cache[$index]; 275 | } 276 | 277 | function findIndexByEle($value){ 278 | $length = $this->length(); 279 | for($i=0;$i<$length;$i++){ 280 | if($value === $this->_cache[$i]){ 281 | return $i; 282 | } 283 | } 284 | return -1; 285 | } 286 | 287 | function update($newValue,$index){ 288 | $length = $this->length(); 289 | if($index<0 || $index>$length-1){ 290 | return false; 291 | } 292 | $this->_cache[$index] = $newValue; 293 | return true; 294 | } 295 | 296 | function merge($seq){ 297 | if(!($seq instanceof Linear_seq)){ 298 | return false; 299 | } 300 | $length = $seq->length(); 301 | for($i=0;$i<$length;$i++){ 302 | $this->push($seq->findEleByIndex($i)); 303 | } 304 | return true; 305 | } 306 | 307 | 308 | // 求并集 309 | static function union($seq1,$seq2){ 310 | $seq = new Linear_seq(); 311 | if(!($seq1 instanceof Linear_seq) || !($seq2 instanceof Linear_seq)){ 312 | return $seq; 313 | } 314 | $length1 = $seq1->length(); 315 | for($i=0;$i<$length1;$i++){ 316 | $seq->push($seq1->findEleByIndex($i)); 317 | } 318 | 319 | $length2 = $seq2->length(); 320 | for($i=0;$i<$length2;$i++){ 321 | $value = $seq2->findEleByIndex($i); 322 | if($seq->findIndexByEle($value)===-1){ 323 | $seq->push($value); 324 | } 325 | } 326 | 327 | return $seq; 328 | } 329 | 330 | // 求交集 331 | static function intersection($seq1,$seq2){ 332 | $seq = new Linear_seq(); 333 | if(!($seq1 instanceof Linear_seq) || !($seq2 instanceof Linear_seq)){ 334 | return $seq; 335 | } 336 | 337 | $length = $seq1->length(); 338 | for($i=0;$i<$length;$i++){ 339 | $value = $seq1->findEleByIndex($i); 340 | if($seq2->findIndexByEle($value)!==-1){ 341 | $seq->push($value); 342 | } 343 | } 344 | return $seq; 345 | } 346 | 347 | 348 | } 349 | 350 | ``` -------------------------------------------------------------------------------- /linear/stack.md: -------------------------------------------------------------------------------- 1 | 栈其实大家并不陌生,毕竟有种编程方式叫做面向stackoverflow编程。 2 | 3 | 栈本身就是一种特殊的线性表,仅仅允许在栈顶进行操作,我们完全可以把之前实现的顺序表和链表拿过来,然后声称这是栈。毕竟,我们所需要的绝大部分操作已经有了,比如入栈```push```操作,出栈```pop```操作。可能我们还需要一个获得栈顶元素的操作```top```,但这个实现起来也只是调用了```length```和```findEleByIndex```这两个方法,所以也没什么特殊的。 4 | 5 | 和栈经常一起出现的词是后进先出(Last In First Out),也没什么难理解的,就这样。 -------------------------------------------------------------------------------- /sort/README.md: -------------------------------------------------------------------------------- 1 | 虽然在php中有各种各样的排序函数,但是掌握一些基本的排序算法也是需要的。这一章就用php实现一些基本的排序算法。 2 | 3 | 为了管理各种排序算法,我取了一个```Sort```类,具体的排序算法都以静态方法的形式组织。先约定排序结果是从小到大。 4 | 5 | 下面是一个在多个排序算法中用到的小函数,作用是数组中两元素交换 6 | 7 | ```php 8 | static private function _swap(&$arr,$index1,$index2){ 9 | if($index1!==$index2){ 10 | $temp = $arr[$index1]; 11 | $arr[$index1] = $arr[$index2]; 12 | $arr[$index2] = $temp; 13 | } 14 | } 15 | ``` 16 | 17 | 可以在[github查看最终的实现代码](https://github.com/jiangshanmeta/datastructure_php/blob/master/sort/sort.php) -------------------------------------------------------------------------------- /sort/bubble.md: -------------------------------------------------------------------------------- 1 | 冒泡排序的思路是这样的:从头到尾扫描待排序列,如果当前元素大于下一个元素,则两元素交换位置,每经过一次扫描最大元素移到待排序列最后。 2 | 3 | ```php 4 | static public function bubbleSort(&$arr){ 5 | if(!is_array($arr)){ 6 | return false; 7 | } 8 | $len = count($arr); 9 | // 每扫描一次,筛出最大的元素放到最后 10 | for($i=0;$i<$len;$i++){ 11 | $end = $len - $i; 12 | $flag = false; 13 | for($j=1;$j<$end;$j++){ 14 | if($arr[$j-1]>$arr[$j]){ 15 | self::_swap($arr,$j-1,$j); 16 | $flag = true; 17 | } 18 | } 19 | if(!$flag){ 20 | break; 21 | } 22 | } 23 | return true; 24 | } 25 | ``` 26 | 27 | 上面的实现中有个```$flag```,它的作用是表示是否已经排好序了,当还需要交换的时候说明没有排好序,一次扫描没有发生交换说明已经排好序了,这时候需要终止循环避免无用的扫描。 28 | 29 | 这个实现对应最好时间复杂度为O(n),对应最坏时间复杂度为O(n^2)。 -------------------------------------------------------------------------------- /sort/compare.md: -------------------------------------------------------------------------------- 1 | 有这么多排序算法,但是没有一种排序算法是在任何情况下都表现最好。 2 | 3 | * 堆排序、选择排序、冒泡排序实现都是在待排序列找最值,它们也适合寻找前几位最值的需求 4 | * 快速排序平均时间性能上最好,但是最坏情况下比不上堆排序和归并排序 5 | * 插入排序比较适合元素小的情况,通常会与其他排序结合使用 6 | * 基数排序适合元素多且基数少的情况 7 | 8 | 9 | 所以还是安心用系统提供的各种排序好了。 -------------------------------------------------------------------------------- /sort/heap.md: -------------------------------------------------------------------------------- 1 | 在选择排序中,我们为了从剩余元素中找到最小的,需要遍历剩余元素。想要改进选择排序,一个思路就是改进从剩余元素找到最小元素的方式。还记得树那一章提及的最小堆吗,最小堆为我们提供了一种快速寻找序列中最小元素的方法。 2 | 3 | 然而我们这里的实现用的是最大堆,这样就不需要额外的空间,直接在待排序列上修改就好了。 4 | 5 | 基本思路是这样的:首先把待排序列调整成为最大堆,然后从堆中移除最大元素,剩余元素调整成为最大堆,重复上述步骤。 6 | 7 | 8 | ```php 9 | static private function _parentIndex($num){ 10 | return ceil($num/2) - 1; 11 | } 12 | 13 | // 调整成最大堆 14 | static private function _perceDown(&$arr,$from,$to){ 15 | $temp = $arr[$from]; 16 | for($parent=$from;2*$parent+1<=$to;$parent=$child){ 17 | $child = 2*$parent + 1; 18 | if($child+1<=$to && $arr[$child+1]>$arr[$child]){ 19 | $child++; 20 | } 21 | if($arr[$child]>$temp){ 22 | $arr[$parent] = $arr[$child]; 23 | }else{ 24 | break; 25 | } 26 | } 27 | $arr[$parent] = $temp; 28 | } 29 | 30 | static public function heapSort(&$arr){ 31 | if(!is_array($arr)){ 32 | return false; 33 | } 34 | $len = count($arr); 35 | // 调整原始数据成为最大堆 36 | for($i=self::_parentIndex($len-1);$i>=0;$i--){ 37 | self::_perceDown($arr,$i,$len-1); 38 | } 39 | 40 | for($i=$len-1;$i>0;$i--){ 41 | // 未排序序列(最大堆)中选出最大的,放到最后 42 | self::_swap($arr,0,$i); 43 | 44 | // 重新调整为最大堆 45 | self::_perceDown($arr,0,$i-1); 46 | } 47 | return true; 48 | } 49 | ``` 50 | 51 | 堆排序的时间复杂度是O(nlogn),这种排序方式非常适合在一个待排序列中找前几个最大的元素。 -------------------------------------------------------------------------------- /sort/insert.md: -------------------------------------------------------------------------------- 1 | 插入排序类似于我们插入扑克牌的过程:拿到一张新牌,与手中的牌比较(倒序),如果被比较的牌比新牌大,被比较的牌向后移空出新牌的位置,否则终止比较,空出来的位置就是新牌应该插入的位置。 2 | 3 | ```php 4 | static public function insertSort(&$arr){ 5 | if(!is_array($arr)){ 6 | return false; 7 | } 8 | $len = count($arr); 9 | // 对应摸牌过程 10 | for($i=1;$i<$len;$i++){ 11 | $item = $arr[$i]; 12 | // 与手中已有的牌比较的过程 13 | for($j=$i;$j>0;$j--){ 14 | if($arr[$j-1]>$item){ 15 | $arr[$j] = $arr[$j-1]; 16 | }else{ 17 | break; 18 | } 19 | } 20 | $arr[$j] = $item; 21 | } 22 | return true; 23 | } 24 | ``` 25 | 26 | 上面是对应的实现,第一层循环是拿取一张新牌的过程(默认手中有张牌),第二层循环是新牌与手中的牌比较的过程。 27 | 28 | 这个算法的时间复杂度和冒泡排序是一样的,最好的情况下(顺序)是O(n),最坏的情况下(逆序)是O(n^2)。 -------------------------------------------------------------------------------- /sort/merge.md: -------------------------------------------------------------------------------- 1 | 归并排序核心思想是“分而治之”,它的思路是这样的:要实现一个序列的排序,先实现左半个序列的排序,再实现右半个序列的排序,最后把这两个排好序的半个序列合并在一起。 2 | 3 | ```php 4 | 5 | // 实现序列合并 6 | static private function _merge(&$arr,$start,$mid,$end){ 7 | if($mid<=$start || $end<=$mid){ 8 | return; 9 | } 10 | $temp = []; 11 | $len1 = $mid - $start; 12 | $len2 = $end - $mid; 13 | $index1 = 0; 14 | $index2 = 0; 15 | while ($index1<$len1 && $index2<$len2) { 16 | if($arr[$start+$index1]<=$arr[$mid+$index2]){ 17 | $temp[] = $arr[$start+$index1]; 18 | $index1++; 19 | 20 | }else{ 21 | $temp[] = $arr[$mid+$index2]; 22 | $index2++; 23 | } 24 | } 25 | while ($index1<$len1) { 26 | $temp[] = $arr[$start+$index1]; 27 | $index1++; 28 | } 29 | while ($index2<$len2) { 30 | $temp[] = $arr[$mid+$index2]; 31 | $index2++; 32 | } 33 | $len = count($temp); 34 | for($i=0;$i<$len;$i++){ 35 | $arr[$start+$i] = $temp[$i]; 36 | } 37 | 38 | } 39 | 40 | static public function mergeSort(&$arr,$from=0,$to=NULL){ 41 | if(!is_array($arr)){ 42 | return false; 43 | } 44 | if($to===NULL){ 45 | $to = count($arr) - 1; 46 | } 47 | $len = $to - $from; 48 | // 递归退出条件 49 | if($len<1){ 50 | return false; 51 | } 52 | $mid = floor(($to+$from)/2); 53 | self::mergeSort($arr,$from,$mid); 54 | self::mergeSort($arr,$mid+1,$to); 55 | 56 | self::_merge($arr,$from,$mid+1,$to+1); 57 | return true; 58 | } 59 | ``` 60 | 61 | 这种算法时间复杂度为O(nlogn)。 -------------------------------------------------------------------------------- /sort/quick.md: -------------------------------------------------------------------------------- 1 | 快速排序是经常听到的一种排序算法,它和归并排序类似,都是利用了“分而治之”的思路。 2 | 3 | 它的思路是这样的:在序列中找到一个元素(称之为主元),然后调整序列,使主元左侧的元素都比主元小、右侧的元素都比主元大,这样序列就被分成了两个小序列,然后递归地对这两个小序列进行快速排序。 4 | 5 | ```php 6 | static public function quickSort(&$arr,$from=0,$to=NULL){ 7 | if(!is_array($arr)){ 8 | return false; 9 | } 10 | if($to===NULL){ 11 | $to = count($arr) - 1; 12 | } 13 | if($to-$from<1){ 14 | return false; 15 | } 16 | $mid = floor(($from+$to)/2); 17 | // 下面三个if判断保证$from、$mid、$to对应元素递增 18 | if($arr[$from]>$arr[$mid]){ 19 | self::_swap($arr,$from,$mid); 20 | } 21 | if($arr[$from]>$arr[$to]){ 22 | self::_swap($arr,$from,$to); 23 | } 24 | if($arr[$mid]>$arr[$to]){ 25 | self::_swap($arr,$mid,$to); 26 | } 27 | // 主元就是$mid对应的元素 28 | 29 | // $to对应的元素比$mid大,把$mid对应元素交换到$to-1 30 | self::_swap($arr,$mid,$to-1); 31 | 32 | $pivot = $arr[$to-1]; 33 | $low = $from; 34 | $high = $to - 1; 35 | while(true){ 36 | while($low<$to-1&&$arr[++$low]<$pivot){ 37 | 38 | } 39 | while($high>$from&&$arr[--$high]>$pivot){ 40 | 41 | } 42 | if($low<$high){ 43 | self::_swap($arr,$low,$high); 44 | }else{ 45 | break; 46 | } 47 | } 48 | 49 | self::_swap($arr,$low,$to-1); 50 | // 到现在以$low为界已经分成了两部分,递归处理 51 | self::quickSort($arr,$from,$low-1); 52 | self::quickSort($arr,$low+1,$to); 53 | } 54 | ``` 55 | 56 | 一个要解决的问题是这个主元怎么确定。我上面给出的实现是找左中右三个位置对应元素的中位数。 57 | 58 | 快速排序对于数据量较大时速度比较快,数据量较小时可以结合其他排序方式,如插入排序。 59 | 60 | 快速排序的时间复杂度是O(nlogn)。 -------------------------------------------------------------------------------- /sort/radix.md: -------------------------------------------------------------------------------- 1 | 基数排序和之前提及的各种排序算法思路完全不同。之前排序算法的是通过关键字的比较和移动,而基数排序不需要记录关键字的比较,同时通过若干次分类和收集实现的。 2 | 3 | 我所介绍的是最低位优先基数排序。它的思路是这样的:从低位到高位,依次进行一次分配,每次分配按照元素低n位的值,每次分配的结果作为下一次分配的原始数据,最后把数据收集起来。 4 | 5 | 6 | ```php 7 | static private function _getDigit($num,$digit=1){ 8 | for($i=0;$i<$digit;$i++){ 9 | $rst = $num%10; 10 | $num = floor($num/10); 11 | } 12 | return $rst; 13 | } 14 | 15 | static private function _getNumLen($num){ 16 | return strlen((string)$num); 17 | } 18 | 19 | static private function _getBucket(){ 20 | return [ 21 | 0=>[], 22 | 1=>[], 23 | 2=>[], 24 | 3=>[], 25 | 4=>[], 26 | 5=>[], 27 | 6=>[], 28 | 7=>[], 29 | 8=>[], 30 | 9=>[], 31 | ]; 32 | } 33 | 34 | static public function LSDRadixSort(&$arr){ 35 | if(!is_array($arr)){ 36 | return false; 37 | } 38 | $maxLen = 1; 39 | $bucket = self::_getBucket(); 40 | for($i=0;$i<$maxLen;$i++){ 41 | if($i>0){ 42 | // 上一次的结果作为这次处理的依据 43 | $newBucket = self::_getBucket(); 44 | $bucketLen = count($bucket); 45 | for($j=0;$j<$bucketLen;$j++){ 46 | $bucketArr = $bucket[$j]; 47 | $bucketArrLen = count($bucketArr); 48 | for($k=0;$k<$bucketArrLen;$k++){ 49 | $item = $bucketArr[$k]; 50 | $bucketId = self::_getDigit($item,$i+1); 51 | $newBucket[$bucketId][] = $item; 52 | } 53 | } 54 | $bucket = $newBucket; 55 | }else{ 56 | // 第一次处理需要调整数据结构 57 | $arrLen = count($arr); 58 | for($j=0;$j<$arrLen;$j++){ 59 | $item = $arr[$j]; 60 | $itemLength = self::_getNumLen($item); 61 | if($itemLength>$maxLen){ 62 | $maxLen = $itemLength; 63 | } 64 | $bucketId = self::_getDigit($item,1); 65 | $bucket[$bucketId][] = $item; 66 | } 67 | } 68 | } 69 | 70 | // 最后的合并工作 71 | $bucketLen = count($bucket); 72 | $index = 0; 73 | for($i=0;$i<$bucketLen;$i++){ 74 | $bucketArr = $bucket[$i]; 75 | $bucketArrLen = count($bucketArr); 76 | for($j=0;$j<$bucketArrLen;$j++){ 77 | $arr[$index] = $bucketArr[$j]; 78 | $index++; 79 | } 80 | } 81 | return true; 82 | } 83 | ``` -------------------------------------------------------------------------------- /sort/select.md: -------------------------------------------------------------------------------- 1 | 选择排序是最容易理解的一个排序算法了,它的思路是这样的:从待排序列找到最小的,将最小值从待排序列移除,重复上述两步直到排好序。 2 | 3 | ```php 4 | static public function selectSort(&$arr){ 5 | if(!is_array($arr)){ 6 | return false; 7 | } 8 | $len = count($arr); 9 | for($i=0;$i<$len;$i++){ 10 | $item = $arr[$i]; 11 | $index = $i; 12 | for($j=$i+1;$j<$len;$j++){ 13 | if($arr[$j]<$item){ 14 | $index = $j; 15 | $item = $arr[$j]; 16 | } 17 | } 18 | self::_swap($arr,$i,$index); 19 | } 20 | return true; 21 | } 22 | ``` 23 | 24 | 这个算法时间复杂度为O(n^2)。 -------------------------------------------------------------------------------- /sort/shell.md: -------------------------------------------------------------------------------- 1 | 希尔排序是对插入排序的优化。它的基本思路是先做一些```$skip```间隔的插入排序,将带排序序列的有序性提高一些,最后再通过一个一间隔排序(即标准的插入排序)完成最终排序工作。 2 | 3 | 4 | ```php 5 | static public function shellSort(&$arr){ 6 | if(!is_array($arr)){ 7 | return false; 8 | } 9 | $len = count($arr); 10 | for($skip=floor($len/2);$skip>0;$skip=floor($skip/2)){ 11 | for($i=$skip;$i<$len;$i++){ 12 | $item = $arr[$i]; 13 | for($j=$i;$j>=$skip;$j-=$skip){ 14 | if($arr[$j-$skip]>$item){ 15 | $arr[$j] = $arr[$j-$skip]; 16 | }else{ 17 | break; 18 | } 19 | } 20 | $arr[$j] = $item; 21 | } 22 | } 23 | return true; 24 | } 25 | ``` 26 | 27 | 这类算法要求```$skip```递减,上面给出的实现```$skip```是通过折半向下取整得到。内部的两个循环对应的是```$skip```间隔的插入排序,最外层的循环控制```$skip```的生成及多次```$skip```间隔插入循环调用。 -------------------------------------------------------------------------------- /sort/sort.php: -------------------------------------------------------------------------------- 1 | $arr[$j]){ 40 | self::_swap($arr,$j-1,$j); 41 | $flag = true; 42 | } 43 | } 44 | if(!$flag){ 45 | break; 46 | } 47 | } 48 | return true; 49 | } 50 | 51 | static public function insertSort(&$arr){ 52 | if(!is_array($arr)){ 53 | return false; 54 | } 55 | $len = count($arr); 56 | for($i=1;$i<$len;$i++){ 57 | $item = $arr[$i]; 58 | for($j=$i;$j>0;$j--){ 59 | if($arr[$j-1]>$item){ 60 | $arr[$j] = $arr[$j-1]; 61 | }else{ 62 | break; 63 | } 64 | } 65 | $arr[$j] = $item; 66 | } 67 | return true; 68 | } 69 | 70 | static public function shellSort(&$arr){ 71 | if(!is_array($arr)){ 72 | return false; 73 | } 74 | $len = count($arr); 75 | for($skip=floor($len/2);$skip>0;$skip=floor($skip/2)){ 76 | for($i=$skip;$i<$len;$i++){ 77 | $item = $arr[$i]; 78 | for($j=$i;$j>=$skip;$j-=$skip){ 79 | if($arr[$j-$skip]>$item){ 80 | $arr[$j] = $arr[$j-$skip]; 81 | }else{ 82 | break; 83 | } 84 | } 85 | $arr[$j] = $item; 86 | } 87 | } 88 | return true; 89 | } 90 | 91 | static private function _parentIndex($num){ 92 | return ceil($num/2) - 1; 93 | } 94 | 95 | // 调整成最大堆 96 | static private function _perceDown(&$arr,$from,$to){ 97 | $temp = $arr[$from]; 98 | for($parent=$from;2*$parent+1<=$to;$parent=$child){ 99 | $child = 2*$parent + 1; 100 | if($child+1<=$to && $arr[$child+1]>$arr[$child]){ 101 | $child++; 102 | } 103 | if($arr[$child]>$temp){ 104 | $arr[$parent] = $arr[$child]; 105 | }else{ 106 | break; 107 | } 108 | } 109 | $arr[$parent] = $temp; 110 | } 111 | 112 | static public function heapSort(&$arr){ 113 | if(!is_array($arr)){ 114 | return false; 115 | } 116 | $len = count($arr); 117 | // 调整原始数据成为最大堆 118 | for($i=self::_parentIndex($len-1);$i>=0;$i--){ 119 | self::_perceDown($arr,$i,$len-1); 120 | } 121 | 122 | for($i=$len-1;$i>0;$i--){ 123 | // 未排序序列中选出最大的 124 | self::_swap($arr,0,$i); 125 | 126 | // 重新调整为最大堆 127 | self::_perceDown($arr,0,$i-1); 128 | } 129 | return true; 130 | } 131 | 132 | static private function _merge(&$arr,$start,$mid,$end){ 133 | if($mid<=$start || $end<=$mid){ 134 | return; 135 | } 136 | $temp = []; 137 | $len1 = $mid - $start; 138 | $len2 = $end - $mid; 139 | $index1 = 0; 140 | $index2 = 0; 141 | while ($index1<$len1 && $index2<$len2) { 142 | if($arr[$start+$index1]<=$arr[$mid+$index2]){ 143 | $temp[] = $arr[$start+$index1]; 144 | $index1++; 145 | 146 | }else{ 147 | $temp[] = $arr[$mid+$index2]; 148 | $index2++; 149 | } 150 | } 151 | while ($index1<$len1) { 152 | $temp[] = $arr[$start+$index1]; 153 | $index1++; 154 | } 155 | while ($index2<$len2) { 156 | $temp[] = $arr[$mid+$index2]; 157 | $index2++; 158 | } 159 | $len = count($temp); 160 | for($i=0;$i<$len;$i++){ 161 | $arr[$start+$i] = $temp[$i]; 162 | } 163 | 164 | } 165 | 166 | static public function mergeSort(&$arr,$from=0,$to=NULL){ 167 | if(!is_array($arr)){ 168 | return false; 169 | } 170 | if($to===NULL){ 171 | $to = count($arr) - 1; 172 | } 173 | $len = $to - $from; 174 | if($len<1){ 175 | return false; 176 | } 177 | $mid = floor(($to+$from)/2); 178 | self::mergeSort($arr,$from,$mid); 179 | self::mergeSort($arr,$mid+1,$to); 180 | 181 | self::_merge($arr,$from,$mid+1,$to+1); 182 | return true; 183 | } 184 | 185 | static public function quickSort(&$arr,$from=0,$to=NULL){ 186 | if(!is_array($arr)){ 187 | return false; 188 | } 189 | if($to===NULL){ 190 | $to = count($arr) - 1; 191 | } 192 | if($to-$from<1){ 193 | return false; 194 | } 195 | $mid = floor(($from+$to)/2); 196 | // 下面三个if判断保证$from、$mid、$to对应元素递增 197 | if($arr[$from]>$arr[$mid]){ 198 | self::_swap($arr,$from,$mid); 199 | } 200 | if($arr[$from]>$arr[$to]){ 201 | self::_swap($arr,$from,$to); 202 | } 203 | if($arr[$mid]>$arr[$to]){ 204 | self::_swap($arr,$mid,$to); 205 | } 206 | // $to对应的元素比$mid大,把$mid对应元素交换到$to-1 207 | self::_swap($arr,$mid,$to-1); 208 | 209 | $pivot = $arr[$to-1]; 210 | $low = $from; 211 | $high = $to - 1; 212 | while(true){ 213 | while($low<$to-1&&$arr[++$low]<$pivot){ 214 | 215 | } 216 | while($high>$from&&$arr[--$high]>$pivot){ 217 | 218 | } 219 | if($low<$high){ 220 | self::_swap($arr,$low,$high); 221 | }else{ 222 | break; 223 | } 224 | } 225 | 226 | self::_swap($arr,$low,$to-1); 227 | // 到现在以$low为界已经分成了两部分,递归处理 228 | self::quickSort($arr,$from,$low-1); 229 | self::quickSort($arr,$low+1,$to); 230 | } 231 | 232 | static private function _getDigit($num,$digit=1){ 233 | for($i=0;$i<$digit;$i++){ 234 | $rst = $num%10; 235 | $num = floor($num/10); 236 | } 237 | return $rst; 238 | } 239 | 240 | static private function _getNumLen($num){ 241 | return strlen((string)$num); 242 | } 243 | 244 | static private function _getBucket(){ 245 | return [ 246 | 0=>[], 247 | 1=>[], 248 | 2=>[], 249 | 3=>[], 250 | 4=>[], 251 | 5=>[], 252 | 6=>[], 253 | 7=>[], 254 | 8=>[], 255 | 9=>[], 256 | ]; 257 | } 258 | 259 | static public function LSDRadixSort(&$arr){ 260 | if(!is_array($arr)){ 261 | return false; 262 | } 263 | $maxLen = 1; 264 | $bucket = self::_getBucket(); 265 | for($i=0;$i<$maxLen;$i++){ 266 | if($i>0){ 267 | $newBucket = self::_getBucket(); 268 | $bucketLen = count($bucket); 269 | for($j=0;$j<$bucketLen;$j++){ 270 | $bucketArr = $bucket[$j]; 271 | $bucketArrLen = count($bucketArr); 272 | for($k=0;$k<$bucketArrLen;$k++){ 273 | $item = $bucketArr[$k]; 274 | $bucketId = self::_getDigit($item,$i+1); 275 | $newBucket[$bucketId][] = $item; 276 | } 277 | } 278 | $bucket = $newBucket; 279 | }else{ 280 | $arrLen = count($arr); 281 | for($j=0;$j<$arrLen;$j++){ 282 | $item = $arr[$j]; 283 | $itemLength = self::_getNumLen($item); 284 | if($itemLength>$maxLen){ 285 | $maxLen = $itemLength; 286 | } 287 | $bucketId = self::_getDigit($item,1); 288 | $bucket[$bucketId][] = $item; 289 | } 290 | } 291 | } 292 | $bucketLen = count($bucket); 293 | $index = 0; 294 | for($i=0;$i<$bucketLen;$i++){ 295 | $bucketArr = $bucket[$i]; 296 | $bucketArrLen = count($bucketArr); 297 | for($j=0;$j<$bucketArrLen;$j++){ 298 | $arr[$index] = $bucketArr[$j]; 299 | $index++; 300 | } 301 | } 302 | return true; 303 | } 304 | 305 | 306 | } 307 | ?> -------------------------------------------------------------------------------- /spl/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangshanmeta/datastructure_php/8c3fc2a932508ca08d65fab55a0bc2654e4f9911/spl/README.md -------------------------------------------------------------------------------- /tree/README.md: -------------------------------------------------------------------------------- 1 | 树是n个节点的有限集。在任意一棵非空树中:(1)有且仅有一个特定的称为根的节点;(2)当n>1时,其余节点可分为m个互不相交的有限集,其中每个有限集本身又是一颗树,称为根的子树。 2 | 3 | 从定义中我们可以看到树其实是一种递归的定义,和树有关的操作也大量和递归有关。 4 | 5 | 关于树的基本概念,比如**节点的度**、**树的度**、**节点层次**、**树的深度**、**树的路径长度**、**树的带权路径长度**,请自行查阅相关书籍。 6 | 7 | 8 | 我们接下来要讨论的主要是二叉树。二叉树是一种特定类型的树,每个节点至多有两颗子树(任意节点的度不大于2,树的度不大于2),且这两颗子树有左右之分。 9 | 10 | 关于二叉树有一些基本的性质。 11 | 12 | * 在二叉树的第i层至多有2^(i-1)个节点(i≥1) 13 | * 深度为k的二叉树至多有2^k -1 个节点(k≥1) 14 | * 对于任何一棵二叉树,如果其叶节点数为n0,度为2的节点数为n2,则 n0 = n2 + 1 15 | 16 | 为了在PHP中实现二叉树,我们先做些准备工作 17 | 18 | ```php 19 | class Node_tree{ 20 | public $leftNode = NULL; 21 | public $rightNode = NULL; 22 | public $parentNode = NULL; 23 | public $data; 24 | function __construct($value=NULL){ 25 | $this->update($value); 26 | } 27 | 28 | function update($newValue){ 29 | $this->data = $newValue; 30 | } 31 | 32 | protected function _formatNode($value){ 33 | if(!($value instanceof Node_tree)){ 34 | $value = new Node_tree($value); 35 | } 36 | $value->parentNode = $this; 37 | return $value; 38 | } 39 | 40 | function setLeft($value){ 41 | $node = $this->_formatNode($value); 42 | $this->leftNode = $node; 43 | } 44 | 45 | function setRight($value){ 46 | $value = $this->_formatNode($value); 47 | $this->rightNode = $value; 48 | } 49 | 50 | } 51 | ``` 52 | 53 | 这个```Node_tree```即用来表示一个节点,又用来表示树。我们约定,和节点有关的操作为实例方法,和树有关的操作为静态方法。这个节点有一个指向左子树的指针域```leftNode```和一个指向右子树的指针域```rightNode```,为了方便起见我还加了一个指向父节点的指针域```parentNode```。到这里你应该已经看出来的,我这里采用的是类似于单链表的链式表示。 -------------------------------------------------------------------------------- /tree/heap.md: -------------------------------------------------------------------------------- 1 | 在介绍堆的相关概念之前请自行学习满二叉树和完全二叉树的有关知识。 2 | 3 | 对于完全二叉树,如果从1按照从上到下从左到右的顺序进行编号,则节点的序号之间有如下特点: 4 | 5 | * 如果一个节点的序号为i,如果有左儿子,则左儿子序号为2i,如果有右儿子,则右儿子的序号为2i+1 6 | * 如果一个节点序号为i,如果有父节点,则父节点序号为floor(i/2),其中floor表示向下取整 7 | 8 | 以上两个结论非常重要,在以后的实现中会用到。 9 | 10 | 完全二叉树我们已经可以进行标号了,而且这些标号是连续的,从1开始的,在这种特殊的情况下我们就会考虑之前的链式结构是否可以换成顺序存储结构 。答案是肯定的,下面实现堆就用到了顺序存储结构(借助于一个数组)。 11 | 12 | 说了这么久,堆到底是什么。堆首先是完全二叉树,并且堆有两种,一种叫最小堆,一种叫最大堆。最小堆的特点是根元素对应数据最小,从上到下数据增大,最大堆与之相反。看到这你可能会想起优先队列,毕竟优先队列就是按升序或者降序排列的。以下的讲解均已最大堆为例。 13 | 14 | ```php 15 | public $compareFunc; 16 | public $elements = []; 17 | public $size = 0; 18 | public function __construct(){ 19 | $this->elements[0] = NULL; 20 | $this->compareFunc = function($data){ 21 | return $data; 22 | }; 23 | } 24 | ``` 25 | 26 | 堆中的元素存放在```$elements```数组中,并且从下标1开始。```$size```属性用来表示堆中的元素个数,```$compareFunc```是比较节点大小时产生比较值的函数。 27 | 28 | 和堆相关有三个基本操作:把一个数组调整成为一个堆、向堆中插入一个元素、删除堆顶元素。这三个操作所需要的一个元操作:如果左子树是堆、右子树也是堆,如何把根节点结合进去构成一个更大的堆。 29 | 30 | 31 | ```php 32 | static public function _perceDown(&$arr,$from,$to,$fn){ 33 | $temp = $fn($arr[$from]); 34 | $rootItem = $arr[$from]; 35 | for($parent=$from;2*$parent<=$to;$parent=$child){ 36 | $child = 2*$parent; 37 | // 和左右子树较大的比较 38 | if($child<$to&&$fn($arr[$child])<$fn($arr[$child+1]) ){ 39 | $child++; 40 | } 41 | if($temp<$fn($arr[$child]) ){ 42 | $arr[$parent] = $arr[$child]; 43 | }else{ 44 | break; 45 | } 46 | } 47 | if($parent===$from){ 48 | return false; 49 | }else{ 50 | $arr[$parent] = $rootItem; 51 | return true; 52 | } 53 | } 54 | ``` 55 | 56 | 上述实现中```$arr```对应堆中的```$elements```,```$from```对应根节点在数组中的下标,```$to```是左右子树中最后一个元素的下标,```$fn```对应堆中```$compareFunc```。这个实现思路是这样的:根节点和左右子树的根节点进行比较,如果根节点比左右子树根节点都大,那么我们就已经调整成最大堆了,否则根节点和左右子树根节点中较大的互换位置,剩下要做的就是把其中被移动的子树调整成最大堆。这里的返回值是用来表示是否被调整过(在插入操作中会用到这个结果)。 57 | 58 | 59 | 然后我们看一下如何将一个数组调整成为最大堆: 60 | 61 | ```php 62 | static public function arrayToMaxHeap($arr,$compareFunc=NULL){ 63 | $maxHeap = new MaxHeap(); 64 | if($compareFunc===NULL){ 65 | $maxHeap->compareFunc = function($data){ 66 | return $data; 67 | }; 68 | }else{ 69 | $maxHeap->compareFunc = $compareFunc; 70 | } 71 | if(!is_array($arr)){ 72 | return $maxHeap; 73 | } 74 | $maxHeap->size = count($arr); 75 | array_unshift($arr,NULL); 76 | if(!is_array($arr)){ 77 | return $maxHeap; 78 | } 79 | // 调整arr成为一个堆 80 | for($i=$maxHeap->size;$i>0;$i--){ 81 | $parentIndex = self::_parentIndex($i); 82 | if($parentIndex>0){ 83 | self::_perceDown($arr,$parentIndex,$maxHeap->size,$maxHeap->compareFunc); 84 | } 85 | } 86 | 87 | $maxHeap->elements = $arr; 88 | 89 | return $maxHeap; 90 | } 91 | ``` 92 | 93 | 这里多次调用了调整函数,从下到上逐渐调整,最大堆的规模逐渐变大。 94 | 95 | 96 | 然后要实现的是删除最大堆中的堆顶元素: 97 | 98 | ```php 99 | public function delete(){ 100 | if($this->isEmpty()){ 101 | return NULL; 102 | } 103 | $data = $this->elements[1]; 104 | // 调整剩余元素 105 | $this->elements[1] = $this->elements[$this->size]; 106 | $cls = get_class($this); 107 | $cls::_perceDown($this->elements,1,$this->size-1,$this->compareFunc); 108 | unset($this->elements[$this->size]); 109 | $this->size--; 110 | return $data; 111 | } 112 | ``` 113 | 114 | 在最大堆中堆顶元素是最大元素,找到它并没有什么难度,问题是删除这个元素之后剩余的元素如何调整成为新的最大堆。我们可以把最后一个元素移到堆顶,这样左子树是一个最大堆,右子树是最大堆,要结合根节点构成新的最大堆,这个问题我在最开始已经解决了。 115 | 116 | 117 | 最后一个问题是向最大堆中插入一个元素: 118 | 119 | ```php 120 | public function insert($data){ 121 | $this->size++; 122 | $this->elements[$this->size] = $data; 123 | for($i=$this->size;$i>0;$i=$parentIndex){ 124 | $parentIndex = self::_parentIndex($i); 125 | if($parentIndex>0){ 126 | $cls = get_class($this); 127 | $rst = $cls::_perceDown($this->elements,$parentIndex,$this->size,$this->compareFunc); 128 | // 已经调整成堆,无需进一步调整 129 | if(!$rst){ 130 | break; 131 | } 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | 插入时先把新节点放到最后,然后从下向上调整成堆,如果在调整过程中发现某一步没有移动元素,说明已经调整成堆无需进一步向上调整。 138 | 139 | [相关实现代码可以在github上看到](https://github.com/jiangshanmeta/datastructure_php/blob/master/tree/heap.php) -------------------------------------------------------------------------------- /tree/heap.php: -------------------------------------------------------------------------------- 1 | elements[0] = NULL; 13 | $this->compareFunc = function($data){ 14 | return $data; 15 | }; 16 | } 17 | public function isEmpty(){ 18 | return $this->size === 0; 19 | } 20 | 21 | public function insert($data){ 22 | $this->size++; 23 | $this->elements[$this->size] = $data; 24 | for($i=$this->size;$i>0;$i=$parentIndex){ 25 | $parentIndex = self::_parentIndex($i); 26 | if($parentIndex>0){ 27 | $cls = get_class($this); 28 | $rst = $cls::_perceDown($this->elements,$parentIndex,$this->size,$this->compareFunc); 29 | if(!$rst){ 30 | break; 31 | } 32 | } 33 | } 34 | } 35 | 36 | public function delete(){ 37 | if($this->isEmpty()){ 38 | return NULL; 39 | } 40 | $data = $this->elements[1]; 41 | // 调整剩余元素 42 | $this->elements[1] = $this->elements[$this->size]; 43 | $cls = get_class($this); 44 | $cls::_perceDown($this->elements,1,$this->size-1,$this->compareFunc); 45 | unset($this->elements[$this->size]); 46 | $this->size--; 47 | return $data; 48 | } 49 | 50 | 51 | } 52 | class MaxHeap extends Heap{ 53 | static public function _perceDown(&$arr,$from,$to,$fn){ 54 | $temp = $fn($arr[$from]); 55 | $rootItem = $arr[$from]; 56 | for($parent=$from;2*$parent<=$to;$parent=$child){ 57 | $child = 2*$parent; 58 | if($child<$to&&$fn($arr[$child])<$fn($arr[$child+1]) ){ 59 | $child++; 60 | } 61 | if($temp<$fn($arr[$child]) ){ 62 | $arr[$parent] = $arr[$child]; 63 | }else{ 64 | break; 65 | } 66 | } 67 | if($parent===$from){ 68 | return false; 69 | }else{ 70 | $arr[$parent] = $rootItem; 71 | return true; 72 | } 73 | 74 | } 75 | 76 | 77 | static public function arrayToMaxHeap($arr,$compareFunc=NULL){ 78 | $maxHeap = new MaxHeap(); 79 | if($compareFunc===NULL){ 80 | $maxHeap->compareFunc = function($data){ 81 | return $data; 82 | }; 83 | }else{ 84 | $maxHeap->compareFunc = $compareFunc; 85 | } 86 | if(!is_array($arr)){ 87 | return $maxHeap; 88 | } 89 | $maxHeap->size = count($arr); 90 | array_unshift($arr,NULL); 91 | if(!is_array($arr)){ 92 | return $maxHeap; 93 | } 94 | // 调整arr成为一个堆 95 | for($i=$maxHeap->size;$i>0;$i--){ 96 | $parentIndex = self::_parentIndex($i); 97 | if($parentIndex>0){ 98 | self::_perceDown($arr,$parentIndex,$maxHeap->size,$maxHeap->compareFunc); 99 | } 100 | } 101 | 102 | $maxHeap->elements = $arr; 103 | 104 | return $maxHeap; 105 | } 106 | function __construct(){ 107 | parent::__construct(); 108 | } 109 | 110 | } 111 | class MinHeap extends Heap{ 112 | // $from $to 均指在数组中的索引 113 | static public function _perceDown(&$arr,$from,$to,$fn){ 114 | $temp = $fn($arr[$from]); 115 | $rootItem = $arr[$from]; 116 | // var_dump($temp,$from,$to); 117 | for($parent=$from;2*$parent<=$to;$parent=$child){ 118 | $child = 2*$parent; 119 | if($child<$to&&$fn($arr[$child])>$fn($arr[$child+1]) ){ 120 | $child++; 121 | } 122 | if($temp>$fn($arr[$child]) ){ 123 | $arr[$parent] = $arr[$child]; 124 | }else{ 125 | break; 126 | } 127 | } 128 | if($parent===$from){ 129 | return false; 130 | }else{ 131 | $arr[$parent] = $rootItem; 132 | return true; 133 | } 134 | 135 | } 136 | 137 | static public function arrayToMinHeap($arr,$compareFunc=NULL){ 138 | $minHeap = new MinHeap(); 139 | if($compareFunc===NULL){ 140 | $minHeap->compareFunc = function($data){ 141 | return $data; 142 | }; 143 | }else{ 144 | $minHeap->compareFunc = $compareFunc; 145 | } 146 | if(!is_array($arr)){ 147 | return $minHeap; 148 | } 149 | $minHeap->size = count($arr); 150 | array_unshift($arr,NULL); 151 | 152 | 153 | // 调整arr成为一个堆 154 | for($i=$minHeap->size;$i>0;$i--){ 155 | $parentIndex = self::_parentIndex($i); 156 | if($parentIndex>0){ 157 | self::_perceDown($arr,$parentIndex,$minHeap->size,$minHeap->compareFunc); 158 | } 159 | } 160 | 161 | $minHeap->elements = $arr; 162 | 163 | return $minHeap; 164 | } 165 | 166 | function __construct(){ 167 | parent::__construct(); 168 | } 169 | 170 | 171 | } 172 | ?> -------------------------------------------------------------------------------- /tree/hoffman.php: -------------------------------------------------------------------------------- 1 | compareFunc = function($data){ 10 | return $data['weight']; 11 | }; 12 | foreach ($arr as $value) { 13 | $minHeap->insert($value); 14 | } 15 | $size = count($arr); 16 | for($i=1;$i<$size;$i++){ 17 | $node = []; 18 | $node['left'] = $minHeap->delete(); 19 | $node['right'] = $minHeap->delete(); 20 | $node['weight'] = $node['left']['weight'] + $node['right']['weight']; 21 | $minHeap->insert($node); 22 | } 23 | return $minHeap->delete(); 24 | } 25 | } 26 | ?> -------------------------------------------------------------------------------- /tree/huffman.md: -------------------------------------------------------------------------------- 1 | 霍夫曼树是一类特殊的二叉树,它的特征是带权路径长度最小(带权路径长度的概念请自行学习)。霍夫曼树又被称为最优二叉树。 2 | 3 | 我们这里实现霍夫曼树依赖于堆(具体来说是最小堆)。 4 | 5 | 霍夫曼树的构成按照以下方法: 6 | 7 | * 根据给定的n个权重构成含有n棵二叉树的集合,集合中的每个二叉树均只有一个根节点。 8 | * 从集合中选取两棵权重最低的二叉树作为左右子树构建一棵新的二叉树,新的二叉树权重为两颗子树根节点权重之和 9 | * 从集合中删除那两棵二叉树,并把新的二叉树加入到集合中 10 | * 重复第二步和第三步,直道集合中只含有一棵树,则该树为霍夫曼树。 11 | 12 | 其中那个二叉树的集合我们使用最小堆实现。 13 | 14 | 15 | ```php 16 | class Hoffmann_tree{ 17 | function __construct(){ 18 | 19 | } 20 | 21 | function genTree($arr){ 22 | $minHeap = new MinHeap(); 23 | $minHeap->compareFunc = function($data){ 24 | return $data['weight']; 25 | }; 26 | foreach ($arr as $value) { 27 | $minHeap->insert($value); 28 | } 29 | $size = count($arr); 30 | for($i=1;$i<$size;$i++){ 31 | $node = []; 32 | $node['left'] = $minHeap->delete(); 33 | $node['right'] = $minHeap->delete(); 34 | $node['weight'] = $node['left']['weight'] + $node['right']['weight']; 35 | $minHeap->insert($node); 36 | } 37 | return $minHeap->delete(); 38 | } 39 | } 40 | $huffmann = new Hoffmann_tree(); 41 | $data = [ 42 | ['data'=>'a','weight'=>7], 43 | ['data'=>'b','weight'=>5], 44 | ['data'=>'c','weight'=>2], 45 | ['data'=>'d','weight'=>4], 46 | ]; 47 | $rst = $huffmann->genTree($data); 48 | ``` 49 | 50 | 以上就是按照上面所说的步骤对应的一种实现。 51 | 52 | 霍夫曼树还有一些特征: 53 | 54 | * 霍夫曼树中没有度为1的节点 55 | * n个叶节点的霍夫曼树总共有2n-1个节点 56 | * 霍夫曼树左右节点交换后依然是霍夫曼树 57 | * 同一组权重可以对应多个霍夫曼树,但是其带权路径长度一致。 -------------------------------------------------------------------------------- /tree/invert.md: -------------------------------------------------------------------------------- 1 | 反转二叉树是这个圈子里一个著名的梗,所以我把这个操作单独拿出来。 2 | 3 | 所谓反转二叉树,就是把左右子树对换。我将用两种方式来实现,一种是递归的,另一种是非递归的。 4 | 5 | ```php 6 | static function invertBinaryTree1($tree){ 7 | if(!($tree instanceof Node_tree)){ 8 | return false; 9 | } 10 | 11 | $temp = $tree->leftNode; 12 | $tree->leftNode = $tree->rightNode; 13 | $tree->rightNode = $temp; 14 | self::invertBinaryTree($tree->leftNode); 15 | self::invertBinaryTree($tree->rightNode); 16 | return true; 17 | } 18 | ``` 19 | 20 | 上面是递归实现反转二叉树,我们反转的顺序是从上到下,不过通过调整代码顺序,我们可以轻松实现从下到上反转二叉树。 21 | 22 | ```php 23 | static function invertBinaryTree2($tree){ 24 | if(!($tree instanceof Node_tree)){ 25 | return false; 26 | } 27 | $queue = []; 28 | array_push($queue,$tree); 29 | while(!empty($queue)){ 30 | $node = array_shift($queue); 31 | if($node->leftNode){ 32 | array_push($queue,$node->leftNode); 33 | } 34 | if($node->rightNode){ 35 | array_push($queue,$node->rightNode); 36 | } 37 | 38 | $temp = $node->leftNode; 39 | $node->leftNode = $node->rightNode; 40 | $node->rightNode = $temp; 41 | } 42 | return true; 43 | } 44 | ``` 45 | 46 | 上面是非递归实现反转二叉树,可以看到和层序遍历类似,我使用了一个队结构。上面的实现反转是从上到下进行的。 47 | 48 | 到现在我们的代码清单如下: 49 | 50 | ```php 51 | class Node_tree{ 52 | public $leftNode = NULL; 53 | public $rightNode = NULL; 54 | public $parentNode = NULL; 55 | public $data; 56 | function __construct($value=NULL){ 57 | $this->update($value); 58 | } 59 | 60 | function update($newValue){ 61 | $this->data = $newValue; 62 | } 63 | 64 | protected function _formatNode($value){ 65 | if(!($value instanceof Node_tree)){ 66 | $value = new Node_tree($value); 67 | } 68 | $value->parentNode = $this; 69 | return $value; 70 | } 71 | 72 | function setLeft($value){ 73 | $node = $this->_formatNode($value); 74 | $this->leftNode = $node; 75 | } 76 | 77 | function setRight($value){ 78 | $value = $this->_formatNode($value); 79 | $this->rightNode = $value; 80 | } 81 | 82 | // 树的深度 83 | static function depth($tree){ 84 | $leftDepth = 0; 85 | $rightDepth = 0; 86 | if($tree->leftNode){ 87 | $leftDepth = self::depth($tree->leftNode); 88 | } 89 | if($tree->rightNode){ 90 | $rightDepth = self::depth($tree->rightNode); 91 | } 92 | return max($leftDepth,$rightDepth) + 1; 93 | } 94 | 95 | // 节点的层次 96 | function level(){ 97 | $count = 1; 98 | $parentNode = $this->parentNode; 99 | while($parentNode){ 100 | $parentNode = $parentNode->parentNode; 101 | $count++; 102 | } 103 | return $count; 104 | } 105 | 106 | // 节点数量 107 | static function length($tree){ 108 | $leftTreeLength = 0; 109 | $rightTreeLength = 0; 110 | if($tree->leftNode){ 111 | $leftTreeLength = self::length($tree->leftNode); 112 | } 113 | if($tree->rightNode){ 114 | $rightTreeLength = self::length($tree->rightNode); 115 | } 116 | return $leftTreeLength + $rightTreeLength + 1; 117 | } 118 | 119 | static function preOrderTraversal($tree,$fn){ 120 | if(!($tree instanceof Node_tree)){ 121 | return false; 122 | } 123 | if(is_callable($fn,true)){ 124 | $fn($tree->data); 125 | } 126 | self::preOrderTraversal($tree->leftNode,$fn); 127 | self::preOrderTraversal($tree->rightNode,$fn); 128 | } 129 | 130 | static function inOrderTraversal($tree,$fn){ 131 | if(!($tree instanceof Node_tree)){ 132 | return false; 133 | } 134 | 135 | self::inOrderTraversal($tree->leftNode,$fn); 136 | if(is_callable($fn,true)){ 137 | $fn($tree->data); 138 | } 139 | self::inOrderTraversal($tree->rightNode,$fn); 140 | } 141 | 142 | static function postOrderTraversal($tree,$fn){ 143 | if(!($tree instanceof Node_tree)){ 144 | return false; 145 | } 146 | 147 | self::postOrderTraversal($tree->leftNode,$fn); 148 | self::postOrderTraversal($tree->rightNode,$fn); 149 | if(is_callable($fn,true)){ 150 | $fn($tree->data); 151 | } 152 | } 153 | 154 | static function levelOrderTraversal($tree,$fn){ 155 | if(!($tree instanceof Node_tree)){ 156 | return false; 157 | } 158 | $queue = []; 159 | array_push($queue,$tree); 160 | while(!empty($queue)){ 161 | $node = array_shift($queue); 162 | if(is_callable($fn,true)){ 163 | $fn($node->data); 164 | } 165 | if($node->leftNode){ 166 | array_push($queue,$node->leftNode); 167 | } 168 | if($node->rightNode){ 169 | array_push($queue,$node->rightNode); 170 | } 171 | } 172 | return true; 173 | } 174 | 175 | static function invertBinaryTree1($tree){ 176 | if(!($tree instanceof Node_tree)){ 177 | return false; 178 | } 179 | 180 | $temp = $tree->leftNode; 181 | $tree->leftNode = $tree->rightNode; 182 | $tree->rightNode = $temp; 183 | self::invertBinaryTree($tree->leftNode); 184 | self::invertBinaryTree($tree->rightNode); 185 | return true; 186 | } 187 | 188 | static function invertBinaryTree2($tree){ 189 | if(!($tree instanceof Node_tree)){ 190 | return false; 191 | } 192 | $queue = []; 193 | array_push($queue,$tree); 194 | while(!empty($queue)){ 195 | $node = array_shift($queue); 196 | if($node->leftNode){ 197 | array_push($queue,$node->leftNode); 198 | } 199 | if($node->rightNode){ 200 | array_push($queue,$node->rightNode); 201 | } 202 | 203 | $temp = $node->leftNode; 204 | $node->leftNode = $node->rightNode; 205 | $node->rightNode = $temp; 206 | } 207 | return true; 208 | } 209 | 210 | 211 | } 212 | ``` -------------------------------------------------------------------------------- /tree/search.md: -------------------------------------------------------------------------------- 1 | 二叉搜索树是一种特殊的二叉树,它满足以下条件: 2 | 3 | * 非空左子树的所有值小于其根节点的值。 4 | * 非空右子树的所有值大于其根节点的值。 5 | * 左右子树均为二叉搜索树。 6 | 7 | 因为二叉搜索树是特殊的二叉树,代码实现上表现为继承,并且为了以后方便,我们在一般二叉树节点上添加三个方法: 8 | 9 | ```php 10 | function isLeaf(){ 11 | return (!$this->leftNode) && (!$this->rightNode); 12 | } 13 | 14 | function isHalf(){ 15 | return (!$this->isLeaf()) && (!$this->isFull()); 16 | } 17 | 18 | function isFull(){ 19 | return $this->leftNode && $this->rightNode; 20 | } 21 | ``` 22 | 23 | 这三个方法是用来反应节点子树数量的。 24 | 25 | 在正式书写代码之前我们先观察一下二叉树,我们可以发现,二叉搜索树中值最小的在二叉树的左下角,二叉搜索树值最大的在二叉树的右下角,据此我们可以写出二叉搜索树的查找最值节点的两个方法。 26 | 27 | ```php 28 | class Node_bst extends Node_tree{ 29 | function __construct($value=NULL){ 30 | parent::__construct($value); 31 | } 32 | 33 | static function findMax($bst){ 34 | if(!($bst instanceof Node_bst)){ 35 | return NULL; 36 | } 37 | while($bst->rightNode){ 38 | $bst = $bst->rightNode; 39 | } 40 | return $bst; 41 | } 42 | 43 | static function findMin($bst){ 44 | if(!($bst instanceof Node_bst)){ 45 | return NULL; 46 | } 47 | while($bst->leftNode){ 48 | $bst = $bst->leftNode; 49 | } 50 | return $bst; 51 | } 52 | 53 | } 54 | ``` 55 | 56 | 在二叉搜索树中找到值为X的节点,如果X小于根节点的值,那么如果二叉搜索树存在该值,那么一定在左子树中;如果X大于根节点的值,如果二叉搜索树存在该值,那么一定在右子树中。如果X等于根节点的值,还用说嘛。这就是在二叉搜索树中查找值为X的节点的思路。 57 | 58 | ```php 59 | static function find($bst,$data){ 60 | if(!($bst instanceof Node_bst)){ 61 | return NULL; 62 | } 63 | while($bst){ 64 | if($bst->data>$data){ 65 | $bst = $bst->leftNode; 66 | }else if($bst->data<$data){ 67 | $bst = $bst->rightNode; 68 | }else{ 69 | return $bst; 70 | } 71 | } 72 | return NULL; 73 | } 74 | ``` 75 | 76 | 到现在我们所讨论的操作都是对于一棵现成的二叉搜索树,那么二叉搜索树该如何构建呢? 77 | 78 | 在二叉搜索树中插入节点不能随便插入,毕竟根据定义节点所在的位置是有限制的。 79 | 80 | ```php 81 | static function insert($bst,$data){ 82 | if(!($data instanceof $bst)){ 83 | $data = new Node_bst($data); 84 | } 85 | 86 | if($data->data<$bst->data){ 87 | if(!$bst->leftNode){ 88 | $bst->setLeft($data); 89 | }else{ 90 | self::insert($bst->leftNode,$data); 91 | } 92 | }else if($data->data>$bst->data){ 93 | if(!$bst->rightNode){ 94 | $bst->setRight($data); 95 | }else{ 96 | self::insert($bst->rightNode,$data); 97 | } 98 | } 99 | 100 | } 101 | ``` 102 | 103 | 根据定义,如果新节点的值小于根节点,那么一定在左侧,如果没有左子树,直接作为左子树就好了,否则就需要把这个新节点插入到左子树这个二叉搜索树中。大于的情况类似,不做赘述。 104 | 105 | 106 | 关于二叉搜索树还有一个操作就是删除节点,我将从要被删除的节点的角度看这个问题。如果是叶节点,直接删掉该节点,删除父节点的引用就好了。如果只有一颗子树,父节点的相关引用指向这棵子树就可以了。最复杂的是有两棵子树的情况,我们可以找左子树的最大值节点,或者右子树的最小值节点代替该节点,这样在删除的同时能保证依然是二叉搜索树,并且改动最小。 107 | 108 | ```php 109 | // 删除节点 110 | static function delete($node){ 111 | $parentNode = $node->parentNode; 112 | if($node->isLeaf()){ 113 | if($parentNode){ 114 | if($parentNode->leftNode===$node){ 115 | $parentNode->leftNode = NULL; 116 | }else{ 117 | $parentNode->rightNode = NULL; 118 | } 119 | } 120 | $node->parentNode = NULL; 121 | }else if($node->isHalf()){ 122 | if($node->leftNode){ 123 | $node->leftNode->parentNode = $parentNode; 124 | if($parentNode){ 125 | if($parentNode->leftNode===$node){ 126 | $parentNode->leftNode = $node->leftNode; 127 | // $parentNode->setLeft($node->leftNode); 128 | }else{ 129 | $parentNode->rightNode = $node->leftNode; 130 | // $parentNode->setRight($node->rightNode); 131 | } 132 | } 133 | }else{ 134 | $node->rightNode->parentNode = $parentNode; 135 | if($parentNode){ 136 | if($parentNode->leftNode===$node){ 137 | $parentNode->leftNode = $node->rightNode; 138 | }else{ 139 | $parentNode->rightNode = $node->rightNode; 140 | } 141 | } 142 | } 143 | 144 | $node->parentNode = NULL; 145 | }else{ 146 | $rightMinNode = self::findMin($node->rightNode); 147 | $node->update($rightMinNode->data); 148 | self::delete($rightMinNode); 149 | } 150 | } 151 | ``` 152 | 153 | 关于二叉搜索树的代码清单如下: 154 | 155 | ```php 156 | class Node_bst extends Node_tree{ 157 | function __construct($value=NULL){ 158 | parent::__construct($value); 159 | } 160 | 161 | static function find($bst,$data){ 162 | if(!($bst instanceof Node_bst)){ 163 | return NULL; 164 | } 165 | while($bst){ 166 | if($bst->data>$data){ 167 | $bst = $bst->leftNode; 168 | }else if($bst->data<$data){ 169 | $bst = $bst->rightNode; 170 | }else{ 171 | return $bst; 172 | } 173 | } 174 | return NULL; 175 | } 176 | 177 | static function findMax($bst){ 178 | if(!($bst instanceof Node_bst)){ 179 | return NULL; 180 | } 181 | while($bst->rightNode){ 182 | $bst = $bst->rightNode; 183 | } 184 | return $bst; 185 | } 186 | 187 | static function findMin($bst){ 188 | if(!($bst instanceof Node_bst)){ 189 | return NULL; 190 | } 191 | while($bst->leftNode){ 192 | $bst = $bst->leftNode; 193 | } 194 | return $bst; 195 | } 196 | 197 | static function insert($bst,$data){ 198 | if(!($data instanceof $bst)){ 199 | $data = new Node_bst($data); 200 | } 201 | 202 | if($data->data<$bst->data){ 203 | if(!$bst->leftNode){ 204 | $bst->setLeft($data); 205 | }else{ 206 | self::insert($bst->leftNode,$data); 207 | } 208 | }else if($data->data>$bst->data){ 209 | if(!$bst->rightNode){ 210 | $bst->setRight($data); 211 | }else{ 212 | self::insert($bst->rightNode,$data); 213 | } 214 | } 215 | 216 | } 217 | 218 | // 删除节点 219 | static function delete($node){ 220 | $parentNode = $node->parentNode; 221 | if($node->isLeaf()){ 222 | if($parentNode){ 223 | if($parentNode->leftNode===$node){ 224 | $parentNode->leftNode = NULL; 225 | }else{ 226 | $parentNode->rightNode = NULL; 227 | } 228 | } 229 | $node->parentNode = NULL; 230 | }else if($node->isHalf()){ 231 | if($node->leftNode){ 232 | $node->leftNode->parentNode = $parentNode; 233 | if($parentNode){ 234 | if($parentNode->leftNode===$node){ 235 | $parentNode->leftNode = $node->leftNode; 236 | // $parentNode->setLeft($node->leftNode); 237 | }else{ 238 | $parentNode->rightNode = $node->leftNode; 239 | // $parentNode->setRight($node->rightNode); 240 | } 241 | } 242 | }else{ 243 | $node->rightNode->parentNode = $parentNode; 244 | if($parentNode){ 245 | if($parentNode->leftNode===$node){ 246 | $parentNode->leftNode = $node->rightNode; 247 | }else{ 248 | $parentNode->rightNode = $node->rightNode; 249 | } 250 | } 251 | } 252 | 253 | $node->parentNode = NULL; 254 | }else{ 255 | $rightMinNode = self::findMin($node->rightNode); 256 | $node->update($rightMinNode->data); 257 | self::delete($rightMinNode); 258 | } 259 | } 260 | 261 | } 262 | ``` -------------------------------------------------------------------------------- /tree/traversal.md: -------------------------------------------------------------------------------- 1 | 树的遍历包含先序遍历、中序遍历、后序遍历、层序遍历。 2 | 3 | 先解释一下前三种的先中后是什么意思,这个先中后指的是访问根节点的顺序。先序就是首先根节点,然后左子树,最后右子树;中序就是先左子树、然后根节点,最后右子树;后序遍历就是先左子树,然后右子树,最后根节点。注意这里默认左子树的顺序永远在右子树之前。 4 | 5 | 他们对应的实现代码如下: 6 | 7 | ```php 8 | static function preOrderTraversal($tree,$fn){ 9 | if(!($tree instanceof Node_tree)){ 10 | return false; 11 | } 12 | if(is_callable($fn,true)){ 13 | $fn($tree->data); 14 | } 15 | self::preOrderTraversal($tree->leftNode,$fn); 16 | self::preOrderTraversal($tree->rightNode,$fn); 17 | } 18 | 19 | static function inOrderTraversal($tree,$fn){ 20 | if(!($tree instanceof Node_tree)){ 21 | return false; 22 | } 23 | 24 | self::inOrderTraversal($tree->leftNode,$fn); 25 | if(is_callable($fn,true)){ 26 | $fn($tree->data); 27 | } 28 | self::inOrderTraversal($tree->rightNode,$fn); 29 | } 30 | 31 | 32 | static function postOrderTraversal($tree,$fn){ 33 | if(!($tree instanceof Node_tree)){ 34 | return false; 35 | } 36 | 37 | self::postOrderTraversal($tree->leftNode,$fn); 38 | self::postOrderTraversal($tree->rightNode,$fn); 39 | if(is_callable($fn,true)){ 40 | $fn($tree->data); 41 | } 42 | } 43 | ``` 44 | 45 | 可以很容易看出来,这里利用的递归的思想。 46 | 47 | 层序遍历和前三种遍历不太一致,前三种遍历都可以通过栈来进行模拟,而层序遍历需要依赖队。层序遍历是按照一层一层的顺序从上到下从左到右访问每个节点。 48 | 49 | ```php 50 | static function levelOrderTraversal($tree,$fn){ 51 | if(!($tree instanceof Node_tree)){ 52 | return false; 53 | } 54 | $queue = []; 55 | array_push($queue,$tree); 56 | while(!empty($queue)){ 57 | $node = array_shift($queue); 58 | if(is_callable($fn,true)){ 59 | $fn($node->data); 60 | } 61 | if($node->leftNode){ 62 | array_push($queue,$node->leftNode); 63 | } 64 | if($node->rightNode){ 65 | array_push($queue,$node->rightNode); 66 | } 67 | } 68 | return true; 69 | } 70 | ``` 71 | 72 | 这里没有使用之前实现的队,而是利用PHP的array模拟了一个队,本身思想是一致的。 73 | 74 | 遍历二叉树有哪些应用呢,我给出如下几个例子: 75 | 76 | ```php 77 | // 树的深度 78 | static function depth($tree){ 79 | $leftDepth = 0; 80 | $rightDepth = 0; 81 | if($tree->leftNode){ 82 | $leftDepth = self::depth($tree->leftNode); 83 | } 84 | if($tree->rightNode){ 85 | $rightDepth = self::depth($tree->rightNode); 86 | } 87 | return max($leftDepth,$rightDepth) + 1; 88 | } 89 | 90 | // 节点数量 91 | static function length($tree){ 92 | $leftTreeLength = 0; 93 | $rightTreeLength = 0; 94 | if($tree->leftNode){ 95 | $leftTreeLength = self::length($tree->leftNode); 96 | } 97 | if($tree->rightNode){ 98 | $rightTreeLength = self::length($tree->rightNode); 99 | } 100 | return $leftTreeLength + $rightTreeLength + 1; 101 | } 102 | ``` 103 | 104 | 我们可以求得树的深度(不知道这个概念的自己看书),我们也可以实现线性结构中的求节点数量操作。 --------------------------------------------------------------------------------