1 <?php 2 namespace Test; 3 4 use Iterator; 5 use ArrayAccess; 6 use Exception; 7 8 // 叶子结点或者连接结点的基类 9 class HuffmanBase 10 { 11 protected $weight; // 权重 12 protected $parent; 13 14 public function setParent($parent) 15 { 16 $this->parent = $parent; 17 } 18 19 public function getWeight() 20 { 21 return $this->weight; 22 } 23 24 public function getParent() 25 { 26 return $this->parent; 27 } 28 } 29 30 // 叶子结点 31 class HuffmanLeafNode extends HuffmanBase 32 { 33 protected $code; // 需要编码的字母 34 35 public function __construct($weight,$code,$parent = null) 36 { 37 if(!is_int($weight) || $weight <= 0 || is_null($code)) 38 { 39 throw new Exception('Param is error!' .__FILE__.__LINE__); 40 } 41 $this->weight = abs($weight); 42 $this->code = $code; 43 $this->parent = $parent; 44 } 45 46 public function getCode() 47 { 48 return $this->code; 49 } 50 } 51 52 // 连接结点 53 class HuffmanJoinNode extends HuffmanBase 54 { 55 protected $lChild; 56 protected $rChild; 57 58 public function __construct($weight = 0,$lChild = null,$rChild = null) 59 { 60 $this->weight = $weight; 61 $this->rChild = $rChild; 62 $this->lChild = $lChild; 63 } 64 65 public function setWeight($leftWeight,$rightWeight) 66 { 67 if(!is_int($this->rChild) || !is_int($this->lChild)) 68 { 69 throw new Exception("Please initialize the left child or the right child! "); 70 } 71 $this->weight = $leftWeight + $rightWeight; 72 } 73 74 public function setChild($child,$leftOrRight) 75 { 76 if('left' == $leftOrRight) 77 { 78 $this->lChild = $child; 79 } 80 elseif('right' == $leftOrRight) 81 { 82 $this->rChild = $child; 83 } 84 else 85 { 86 throw new Exception("Please input 'left' or 'right' to leftOrRight! "); 87 } 88 } 89 90 public function getChild($leftOrRight) 91 { 92 if('left' == $leftOrRight) 93 { 94 return $this->lChild; 95 } 96 elseif('right' == $leftOrRight) 97 { 98 return $this->rChild; 99 } 100 else 101 { 102 throw new Exception("Please input 'left' or 'right' to leftOrRight! "); 103 } 104 } 105 } 106 107 // 哈夫曼树 108 class HuffmanTree implements ArrayAccess,Iterator 109 { 110 protected $nodes = array(); 111 112 public function &getAllNodes() 113 { 114 return $this->nodes; 115 } 116 117 public function offsetExists($offset) 118 { 119 // TODO: Implement offsetExists() method. 120 return isset($this->nodes[$offset]); 121 } 122 123 public function offsetGet($offset) 124 { 125 // TODO: Implement offsetGet() method. 126 if(isset($this->nodes[$offset])) 127 { 128 return $this->nodes[$offset]; 129 } 130 else 131 { 132 return null; 133 } 134 } 135 136 public function offsetSet($offset,$value) 137 { 138 // TODO: Implement offsetSet() method. 139 if(!($value instanceof HuffmanBase)) 140 { 141 throw new Exception('Param is error!' .__FILE__.__LINE__); 142 } 143 144 if(is_null($offset)) 145 { 146 $this->nodes[] = $value; 147 } 148 else 149 { 150 $this->nodes[$offset] = $value; 151 } 152 } 153 154 public function offsetUnset($offset) 155 { 156 // TODO: Implement offsetUnset() method. 157 unset($this->nodes[$offset]); 158 } 159 160 public function current() 161 { 162 // TODO: Implement current() method. 163 return current($this->nodes); 164 } 165 166 public function key() 167 { 168 // TODO: Implement key() method. 169 return key($this->nodes); 170 } 171 172 public function next() 173 { 174 // TODO: Implement next() method. 175 next($this->nodes); 176 } 177 178 public function rewind() 179 { 180 // TODO: Implement rewind() method. 181 reset($this->nodes); 182 } 183 184 public function valid() 185 { 186 // TODO: Implement valid() method. 187 return $this->offsetExists(key($this->nodes)); 188 } 189 190 public function length() 191 { 192 return count($this->nodes); 193 } 194 } 195 196 // 从[$left,$right]区间选择parent=0并且weight最小的两个结点,其序号分别为$minNode1,$minNode2; 197 function selectTwoMinWeightNode(HuffmanTree &$huffmanTree,$left,$right,&$minNode1,&$minNode2) 198 { 199 $left = abs($left); 200 $right = abs($right); 201 202 if(!is_int($left) || !is_int($right) || $left == $right) 203 { 204 throw new Exception('Param is error!' .__FILE__.__LINE__); 205 } 206 207 if($left > $right) 208 { 209 $tmp = $left; 210 $left = $right; 211 $right = $tmp; 212 } 213 214 $nodes = $huffmanTree->getAllNodes(); 215 if(!isset($nodes[$right])) 216 { 217 throw new Exception('Over the array index!'.__FILE__.__LINE__); 218 } 219 220 $tmp = array(); 221 for($i = $left;$i <= $right; ++$i) 222 { 223 $huffmanNode = $huffmanTree[$i]; 224 if(!is_null($huffmanNode->getParent())) 225 { 226 continue; 227 } 228 $tmp[$i] = $huffmanNode->getWeight(); 229 } 230 231 if(count($tmp) <= 1) 232 { 233 throw new Exception('Not enough number!'.__FILE__.__LINE__); 234 } 235 asort($tmp,SORT_NUMERIC); 236 $t = array_keys($tmp); 237 $minNode1 = $t[0]; 238 $minNode2 = $t[1]; 239 } 240 241 // (编码 => 权重) 242 $nodes = array('A' => 3, 'B' => 4, 'C' => 7, 'D' => 10); 243 244 $huffmanTree = new HuffmanTree(); 245 246 // 初始化哈夫曼树的叶子结点 247 foreach($nodes as $code => $weight) 248 { 249 $huffmanNode = new HuffmanLeafNode($weight,$code); 250 $huffmanTree[] = $huffmanNode; 251 } 252 253 $leafCount = $huffmanTree->length(); // 叶子结点的数量(大于1的值) 254 $nodeCount = 2 * $leafCount -1 ; // 哈夫曼树结点的数量 255 256 // 初始化哈夫曼树的非叶子结点(如果编译器未优化,--$i应该是比$i++效率高点的) 257 for($i = $nodeCount - $leafCount;$i >= 1; --$i) 258 { 259 $huffmanNode = new HuffmanJoinNode(); 260 $huffmanTree[] = $huffmanNode; 261 } 262 263 // 建立哈夫曼树 264 for($i = $leafCount;$i < $nodeCount; ++$i) 265 { 266 selectTwoMinWeightNode($huffmanTree,0,$i-1,$minNode1,$minNode2); 267 $huffmanNode1 = $huffmanTree[$minNode1]; 268 $huffmanNode2 = $huffmanTree[$minNode2]; 269 $huffmanNode1->setParent($i); 270 $huffmanNode2->setParent($i); 271 $huffmanTree[$i]->setChild($minNode1,'left'); 272 $huffmanTree[$i]->setChild($minNode2,'right'); 273 $huffmanTree[$i]->setWeight($huffmanNode1->getWeight(),$huffmanNode2->getWeight()); 274 } 275 276 // 从叶子到根的遍历,得到字母的编码 277 $huffmanCode = array(); 278 for($i = 0;$i < $leafCount; ++$i) 279 { 280 $leafNode = $huffmanTree[$i]; 281 $code = $leafNode->getCode(); 282 $reverseCode = array(); 283 for($c = $i,$pi = $leafNode->getParent();!is_null($pi);$pi = $huffmanTree[$pi]->getParent()) 284 { 285 $huffmanNode = $huffmanTree[$pi]; 286 if($huffmanNode->getChild('left') === $c) 287 { 288 $reverseCode[] = 0; 289 } 290 elseif($huffmanNode->getChild('right') === $c) 291 { 292 $reverseCode[] = 1; 293 } 294 else 295 { 296 throw new Exception('Something error happened!' .__FILE__.__LINE__); 297 } 298 $c = $pi; 299 } 300 $huffmanCode[$code] = array_reverse($reverseCode); 301 } 302 303 foreach($huffmanCode as $key => $value) 304 { 305 $s = implode(',',$value); 306 echo $key. " : " .$s ." "; 307 }
运行结果:
运行环境:
ArrayAccess :(PHP 5 >= 5.0.0, PHP 7)