首先,斐波那契堆有什麼優點?插入更快,給元素賦值更快(o(1));
斐波那契堆是可合併堆,那就滿足下列幾個方法
MAKE-HEAP(): o1
INSERT(H,x): o1
MINIMUN(H): o1
EXTRACT-MIN(H): o(lgn)
UNION(H1,H2): o1
除此之外還有
DECREASE-KEY(H,x,k): o1
DELETE(H,x): o(lgn)
我們依次說:
MAKE-HEAP():這個很簡單,就是建個節點,然後沒了
INSERT(H,x):這個也很簡單,就是插在H的鏈表裏
MINIMUN(H):就是返回H.min
EXTRACT-MIN(H):把min移除,這之後就是把堆重新建好
UNION(H1,H2):合併堆就是把list連起來
DECREASE-KEY(H,x,k):把某個值減小,再判斷是不是小於父節點需要剪除,然後放到主鏈表上
DELETE(H,x):設置DECREASE-KEY(H,x,-1)+EXTRACT-MIN(H)
斐波那契堆的特性,正因爲這些特性保證了複雜度小:
1)重新建堆的時候,鏈表上的節點的度(degree,即子節點個數)不能有相同的,這樣就保證總結點不會很多
2)當一個節點有兩個子節點被剪除後,它也要被剪除,這樣就保證每棵樹都基本平衡,不會出現變成鏈
第四節講的理論,看得頭大。
實戰,用斐波那契堆的MAKE-HEAP、INSERT、EXTRACT-MIN、EXTRACT-MIN實現LeetCode1383題(效果比我想象的好,速度超過90%的用戶,關聯:LeetCode1383. 最大的團隊表現值(PriorityQueue的使用)
long NN = 1000000;
public int maxPerformance(int n, int[] speed, int[] efficiency, int k) {
if (k == 0) {
return 0;
}
long ans = 0;
if (k == 1) {
for (int i = 0; i < n; i++) {
ans = Math.max(ans, ((long) speed[i]) * efficiency[i]);
}
return (int) (ans % 1000000007);
}
Long[] effSpee = new Long[n];
for (int i = 0; i < n; i++) {
effSpee[i] = efficiency[i] * NN + speed[i];
}
Arrays.sort(effSpee, new Comparator<Long>() {
@Override
public int compare(Long o1, Long o2) {
return o2.compareTo(o1);
}
});
int nspeed = (int) (effSpee[0] % NN);
Node min = new Node(nspeed);
min.left = min;
min.right = min;
//建堆
H h = new H();
h.min = min;
long allNum = nspeed;
ans = (effSpee[0] / NN) * allNum;
for (int i = 1; i < n; i++) {
nspeed = (int) (effSpee[i] % NN);
long v = (effSpee[i] / NN);
if (i >= k) {
if (nspeed <= h.min.val) {
continue;
} else {
allNum -= h.min.val;
//移除最小
h.rewmovMin();
}
}
allNum += nspeed;
//插入
h.add(new Node(nspeed));
ans = Math.max(ans, v * allNum);
}
return (int) (ans % 1000000007);
}
Node[] nodes = new Node[20];
class H {
Node min;
public void rewmovMin() {
Node node = null;
//移除min,把min的child加入到鏈表中
if (min.left == min) {
node = min.child;
} else {
node = min.left;
node.right = min.right;
node.right.left = node;
if (min.degree > 0) {
node.right.left = min.child.left;
min.child.left.right = node.right;
node.right = min.child;
min.child.left = node;
}
}
//上面的操作,就是把可能是最小的值都放在最上層的鏈表裏
Node nNext = node.right;
min = node;
//循環遍歷鏈表
while (nNext != node) {
if (nNext.val < min.val) {
min = nNext;
}
Node thisNode = nNext;
nNext = thisNode.right;
build(thisNode);
}
build(node);
//以上操作,保證鏈表沒有相同degree的樹結構
for (int i = 0; i < nodes.length; i++) {
//清空
nodes[i] = null;
}
}
private void build(Node thisNode) {
//同等degree合併
while (nodes[thisNode.degree] != null) {
Node addNode = nodes[thisNode.degree];
nodes[thisNode.degree] = null;
if (addNode.val <= thisNode.val) {
//小的做父節點
remove(thisNode);
if (addNode.child == null) {
thisNode.left = thisNode;
thisNode.right = thisNode;
addNode.child = thisNode;
} else {
addRight(addNode.child, thisNode);
}
thisNode = addNode;
} else {
remove(addNode);
if (thisNode.child == null) {
addNode.left = addNode;
addNode.right = addNode;
thisNode.child = addNode;
} else {
addRight(thisNode.child, addNode);
}
}
thisNode.degree++;
}
nodes[thisNode.degree] = thisNode;
}
//把node加到N的左邊
private void addRight(Node N, Node node) {
node.right = N.right;
N.right.left = node;
node.left = N;
N.right = node;
}
//把node從鏈中刪除
private void remove(Node node) {
node.left.right = node.right;
node.right.left = node.left;
}
//添加
public void add(Node node) {
addRight(min, node);
if (node.val < min.val) {
min = node;
}
}
}
class Node {
int degree = 0;
int val;
Node child;
Node left;
Node right;
public Node(int nspeed) {
val = nspeed;
}
//四個操作用不到parent
}