Passed
Push — master ( 7d902f...49eb62 )
by Bingo
02:07
created

FibonacciHeap   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 450
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 170
c 1
b 0
f 1
dl 0
loc 450
rs 8.5599
wmc 48

17 Methods

Rating   Name   Duplication   Size   Complexity  
A link() 0 26 2
B consolidate() 0 51 8
A size() 0 3 1
A findMin() 0 6 2
A __construct() 0 6 1
A addToRootList() 0 17 3
A isEmpty() 0 3 1
A cascadingCut() 0 9 3
A forceDecreaseKeyToMinimum() 0 9 2
A cut() 0 18 3
B decreaseKey() 0 21 7
A clear() 0 6 1
A getOther() 0 3 1
A deleteMin() 0 57 5
A insert() 0 10 2
A setOther() 0 3 1
A meld() 0 35 5

How to fix   Complexity   

Complex Class

Complex classes like FibonacciHeap often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FibonacciHeap, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace heap\tree;
4
5
use Exception;
6
use InvalidArgumentException;
7
use heap\MergeableAddressableHeapInterface;
8
use heap\AddressableHeapHandleInterface;
9
10
/**
11
 * Class FibonacciHeap
12
 *
13
 * @package heap\tree
14
 */
15
class FibonacciHeap implements MergeableAddressableHeapInterface
16
{
17
    /**
18
     * The heap unique hash
19
     *
20
     * @var string
21
     */
22
    private $hash;
23
24
    /**
25
     * Heap node with the minimal key
26
     *
27
     * @var FibonacciHeapNode
28
     */
29
    private $minRoot = null;
30
    
31
    /**
32
     * Number of roots in the heap
33
     *
34
     * @var int
35
     */
36
    private $roots = 0;
37
38
    /**
39
     * Size of the heap
40
     *
41
     * @var int
42
     */
43
    private $size = 0;
44
45
    /**
46
     * Auxiliary array for consolidation
47
     */
48
    private $aux = [];
49
50
    /**
51
     * Reference to current or some other heap in case of melding
52
     *
53
     * @var FibonacciHeap
54
     */
55
    private $other;
56
57
    /**
58
     * Construct a new Fibonacci heap
59
     */
60
    public function __construct()
61
    {
62
        $this->minRoot = null;
63
        $this->roots = 0;
64
        $this->other = $this;
65
        $this->hash = uniqid('', true);
66
    }
67
68
    /**
69
     * Insert a new node to the heap
70
     *
71
     * @param int $key - node key
72
     * @param null|mixed $value - node value
73
     *
74
     * @return AddressableHeapHandleInterface
75
     *
76
     * @throws Exception
77
     */
78
    public function insert(int $key, $value = null): AddressableHeapHandleInterface
79
    {
80
        if ($this->other != $this) {
81
            throw new Exception("A heap cannot be used after a meld");
82
        }
83
84
        $node = new FibonacciHeapNode($this, $key, $value);
85
        $this->addToRootList($node);
86
        $this->size += 1;
87
        return $node;
88
    }
89
90
    /**
91
     * Find heap node with the minimal key
92
     *
93
     * @return AddressableHeapHandleInterface
94
     *
95
     * @throws Exception
96
     */
97
    public function findMin(): AddressableHeapHandleInterface
98
    {
99
        if ($this->size == 0) {
100
            throw new Exception("No such element!");
101
        }
102
        return $this->minRoot;
103
    }
104
105
    /**
106
     * Get an element with the minimum key.
107
     *
108
     * @return AddressableHeapHandleInterface
109
     */
110
    public function deleteMin(): AddressableHeapHandleInterface
111
    {
112
        if ($this->size == 0) {
113
            throw new Exception("No such element!");
114
        }
115
        $z = $this->minRoot;
116
117
        // move z children into root list
118
        $x = $z->child;
119
        $i = 0;
120
        while (!is_null($x)) {
121
            $nextX = ($x->next == $x) ? null : $x->next;
122
123
            // clear parent
124
            $x->parent = null;
125
126
            // remove from child list
127
            $x->prev->next = $x->next;
128
            $x->next->prev = $x->prev;
129
130
            // add to root list
131
            $x->next = $this->minRoot->next;
132
            // probably need to reset
133
            $x->prev = $this->minRoot;
134
            $this->minRoot->next = $x;
135
            $x->next->prev = $x;
136
            $this->roots += 1;
137
138
            // advance
139
            $x = $nextX;
140
            $i += 1;
141
        }
142
        $z->degree = 0;
143
        $z->child = null;
144
145
        // remove z from root list
146
        $z->prev->next = $z->next;
147
        $z->next->prev = $z->prev;
148
        $this->roots -= 1;
149
150
        // decrease size
151
        $this->size -= 1;
152
153
        // update minimum root
154
        
155
        if ($z == $z->next) {
156
            $this->minRoot = null;
157
        } else {
158
            $this->minRoot = $z->next;
159
            $this->consolidate();
160
        }
161
162
        // clear other fields
163
        $z->next = null;
164
        $z->prev = null;
165
166
        return $z;
167
    }
168
169
    /**
170
     * Check if the heap is empty.
171
     *
172
     * @return bool
173
     */
174
    public function isEmpty(): bool
175
    {
176
        return $this->size == 0;
177
    }
178
179
    /**
180
     * Get the number of elements in the heap.
181
     *
182
     * @return int
183
     */
184
    public function size(): int
185
    {
186
        return $this->size;
187
    }
188
189
    /**
190
     * Get the other heap referenced in the current heap
191
     *
192
     * @return FibonacciHeap
193
     */
194
    public function getOther(): FibonacciHeap
195
    {
196
        return $this->other;
197
    }
198
199
    /**
200
     * Reference the other heap in the current heap
201
     *
202
     * @param FibonacciHeap $other - the other heap
203
     */
204
    public function setOther(FibonacciHeap $other): void
205
    {
206
        $this->other = $other;
207
    }
208
209
    /**
210
     * Clear all the elements in the heap
211
     */
212
    public function clear(): void
213
    {
214
        $this->minRoot = null;
215
        $this->roots = 0;
216
        $this->other = $this;
217
        $this->size = 0;
218
    }
219
220
    /**
221
     * Meld other heap to the current heap
222
     *
223
     * @param MergeableAddressableHeapInterface $other - the heap to be melded
224
     */
225
    public function meld(MergeableAddressableHeapInterface $other): void
226
    {
227
        if ($other->other != $other) {
0 ignored issues
show
Bug introduced by
Accessing other on the interface heap\MergeableAddressableHeapInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
228
            throw new Exception("A heap cannot be used after a meld.");
229
        }
230
231
        if ($this->size == 0) {
232
            // copy the other
233
            $this->minRoot = $other->minRoot;
0 ignored issues
show
Bug introduced by
Accessing minRoot on the interface heap\MergeableAddressableHeapInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
234
        } elseif ($other->size != 0) {
0 ignored issues
show
Bug introduced by
Accessing size on the interface heap\MergeableAddressableHeapInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
235
            // concatenate root lists
236
            $h11 = $this->minRoot;
237
            $h12 = $h11->next;
238
            $h21 = $other->minRoot;
239
            $h22 = $h21->next;
240
            $h11->next = $h22;
241
            $h22->prev = $h11;
242
            $h21->next = $h12;
243
            $h12->prev = $h21;
244
245
            // find new minimum
246
            if ($other->minRoot->key < $this->minRoot->key) {
247
                $this->minRoot = $other->minRoot;
248
            }
249
        }
250
        $this->roots += $other->roots;
0 ignored issues
show
Bug introduced by
Accessing roots on the interface heap\MergeableAddressableHeapInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
251
        $this->size += $other->size;
252
253
        // clear other
254
        $other->size = 0;
255
        $other->minRoot = null;
256
        $other->roots = 0;
257
258
        // take ownership
259
        $other->other = $this;
260
    }
261
262
    /**
263
     * Decrease the node key
264
     *
265
     * @param FibonacciHeapNode $node - the node
266
     * @param int $newKey - the node new key
267
     */
268
    private function decreaseKey(FibonacciHeapNode $node, int $newKey): void
0 ignored issues
show
Unused Code introduced by
The method decreaseKey() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
269
    {
270
        if ($newKey > $node->key) {
271
            throw new Exception("Keys can only be decreased!");
272
        } elseif ($node->key != $newKey) {
273
            $node->key = $newKey;
274
            
275
            if (is_null($node->next)) {
276
                throw new Exception("Invalid handle!");
277
            }
278
279
            // if not root and heap order violation
280
            $parent = $node->parent;
281
            if (!is_null($parent) && $node->key < $parent->key) {
282
                $this->cut($node, $parent);
283
                $this->cascadingCut($parent);
284
            }
285
286
            // update minimum root
287
            if ($node->key < $this->minRoot->key) {
288
                $this->minRoot = $node;
289
            }
290
        }
291
    }
292
293
    /*
294
     * Decrease the key of a node to the minimum
295
     *
296
     * @param FibonacciHeapNode $node - the node
297
     */
298
    public function forceDecreaseKeyToMinimum(FibonacciHeapNode $node): void
299
    {
300
        // if not root
301
        $parent = $node->parent;
302
        if (!is_null($parent)) {
303
            $this->cut($node, $parent);
304
            $this->cascadingCut($parent);
305
        }
306
        $this->minRoot = $node;
307
    }
308
309
    /*
310
     * Consolidate: Make sure each root tree has a distinct degree.
311
     */
312
    private function consolidate(): void
313
    {
314
        $maxDegree = -1;
315
316
        // for each node in root list
317
        $numRoots = $this->roots;
318
        $x = $this->minRoot;
319
        while ($numRoots > 0) {
320
            $nextX = $x->next;
321
            $deg = $x->degree;
322
323
            while (true) {
324
                if (!isset($this->aux[$deg])) {
325
                    break;
326
                }
327
                $y = $this->aux[$deg];
328
329
                // make sure x's key is smaller
330
                if ($y->key < $x->key) {
331
                    $tmp = $x;
332
                    $x = $y;
333
                    $y = $tmp;
334
                }
335
336
                // make y a child of x
337
                $this->link($y, $x);
338
339
                $this->aux[$deg] = null;
340
                $deg += 1;
341
            }
342
343
            // store result
344
            $this->aux[$deg] = $x;
345
346
            // keep track of max degree
347
            if ($deg > $maxDegree) {
348
                $maxDegree = $deg;
349
            }
350
351
            // advance
352
            $x = $nextX;
353
            $numRoots -= 1;
354
        }
355
356
        // recreate root list and find minimum root
357
        $this->minRoot = null;
358
        $this->roots = 0;
359
        for ($i = 0; $i <= $maxDegree; $i += 1) {
360
            if (!is_null($this->aux[$i])) {
361
                $this->addToRootList($this->aux[$i]);
362
                $this->aux[$i] = null;
363
            }
364
        }
365
    }
366
367
    /*
368
     * Remove node "first" from the root list and make it a child of "second". Degree of "second"
369
     * increases by 1 and "first" is unmarked if marked.
370
     */
371
    private function link(FibonacciHeapNode $first, FibonacciHeapNode $second): void
372
    {
373
        // remove from root list
374
        $first->prev->next = $first->next;
375
        $first->next->prev = $first->prev;
376
377
        // one less root
378
        $this->roots -= 1;
379
380
        // clear if marked
381
        $first->mark = false;
382
383
        // hang as second's child
384
        $second->degree += 1;
385
        $first->parent = $second;
386
387
        $child = $second->child;
388
        if (is_null($child)) {
389
            $second->child = $first;
390
            $first->next = $first;
391
            $first->prev = $first;
392
        } else {
393
            $first->prev = $child;
394
            $first->next = $child->next;
395
            $child->next = $first;
396
            $first->next->prev = $first;
397
        }
398
    }
399
400
    /*
401
     * Cut the link between node and its parent making node a root.
402
     *
403
     * @param FibonacciHeapNode $node - the node
404
     * @param FibonacciHeapNode $parent - the root node
405
     */
406
    private function cut(FibonacciHeapNode $node, FibonacciHeapNode $parent): void
407
    {
408
        // remove x from child list of y
409
        $node->prev->next = $node->next;
410
        $node->next->prev = $node->prev;
411
        $parent->degree -= 1;
412
        if ($parent->degree == 0) {
413
            $parent->child = null;
414
        } elseif ($parent->child == $node) {
415
            $parent->child = $node->next;
416
        }
417
418
        // add x to the root list
419
        $node->parent = null;
420
        $this->addToRootList($node);
421
422
        // clear if marked
423
        $node->mark = false;
424
    }
425
426
    /*
427
     * Cascading cut until a root or an unmarked node is found.
428
     *
429
     * @param FibonacciHeapNode $node - the starting node
430
     */
431
    private function cascadingCut(FibonacciHeapNode $node): void
432
    {
433
        while (!is_null(($parent = $node->parent))) {
434
            if (!$parent->mark) {
435
                $parent->mark = true;
436
                break;
437
            }
438
            $this->cut($node, $parent);
439
            $node = $parent;
440
        }
441
    }
442
443
    /**
444
     * Add the specified node to heap root list
445
     *
446
     * @param FibonacciHeapNode $node - the node to be added
447
     */
448
    private function addToRootList(FibonacciHeapNode $node): void
449
    {
450
        if (is_null($this->minRoot)) {
451
            $node->next = $node;
452
            $node->prev = $node;
453
            $this->minRoot = $node;
454
            $this->roots = 1;
455
        } else {
456
            $node->next = $this->minRoot->next;
457
            $node->prev = $this->minRoot;
458
            $this->minRoot->next->prev = $node;
459
            $this->minRoot->next = $node;
460
461
            if ($node->key < $this->minRoot->key) {
462
                $this->minRoot = $node;
463
            }
464
            $this->roots += 1;
465
        }
466
    }
467
}
468