面試中,經常遇到的一個簡單演算法題:查詢兩個單連結串列的公共節點
最近在讀react原始碼的時候發現一個react樹中對該演算法的運用(見getLowestCommonAncestor函式),在此做簡單的記錄。
git地址
getParent
在react樹中獲取當前例項節點的父節點例項
//HostComponent元件對應的DOM,比如App的tag=3, 表示為類元件,其child為tag=5對應div元素。
function getParent(inst) {
do {
inst = inst.return;
// TODO: If this is a HostRoot we might want to bail out.
// That is depending on if we want nested subtrees (layers) to bubble
// events to their parent. We could also go through parentNode on the
// host node but that wouldn't work for React Native and doesn't let us
// do the portal feature.
} while (inst && inst.tag !== HostComponent);
if (inst) {
return inst;
}
return null;
}
getLowestCommonAncestor
獲取節點A與B的最近的公共祖先節點
演算法題:找到兩個連結串列的公共節點
export function getLowestCommonAncestor(instA, instB) {
//獲取子節點A在樹中的深度
let depthA = 0;
for (let tempA = instA; tempA; tempA = getParent(tempA)) {
depthA++;
}
//獲取子節點B在樹中的深度
let depthB = 0;
for (let tempB = instB; tempB; tempB = getParent(tempB)) {
depthB++;
}
// If A is deeper, crawl up.
// 如果A的高度高,那麼A節點先往上走depthA - depthB個節點,最後同時走,直到父節點是同一個
while (depthA - depthB > 0) {
instA = getParent(instA);
depthA--;
}
// 如果B的高度高,那麼B節點先往上走depthB - depthB個節點,最後同時走,直到父節點是同一個
// If B is deeper, crawl up.
while (depthB - depthA > 0) {
instB = getParent(instB);
depthB--;
}
// Walk in lockstep until we find a match.
// 現在,指標所處的位置的高度一致,可以同時往上查詢,直到找到公共的節點
let depth = depthA;
while (depth--) {
if (instA === instB || instA === instB.alternate) {
return instA;
}
instA = getParent(instA);
instB = getParent(instB);
}
return null;
}
isAncestor
判斷A節點是否是B節點的祖先節點
export function isAncestor(instA, instB) {
while (instB) {
if (instA === instB || instA === instB.alternate) {
return true;
}
instB = getParent(instB);
}
return false;
}
getParentInstance
對getParent的export封裝:
export function getParentInstance(inst) {
return getParent(inst);
}
traverseTwoPhase
對inst及其以上的樹執行冒泡捕獲的操作,執行fn。類似事件的冒泡捕獲
export function traverseTwoPhase(inst, fn, arg) {
const path = [];
//將inst的父節點入棧,陣列最後的為最遠的祖先
while (inst) {
path.push(inst);
inst = getParent(inst);
}
let i;
//從最遠的祖先開始向inst節點捕獲執行fn
for (i = path.length; i-- > 0; ) {
fn(path[i], 'captured', arg);
}
//從inst節點開始向最遠的祖先節點冒泡執行fn
for (i = 0; i < path.length; i++) {
fn(path[i], 'bubbled', arg);
}
}
traverseEnterLeave
當關注點從from節點移出然後移入to節點的時候,在from執行執行類似移入移出的操作,from節點
export function traverseEnterLeave(from, to, fn, argFrom, argTo) {
const common = from && to ? getLowestCommonAncestor(from, to) : null;
const pathFrom = [];
while (true) {
if (!from) {
break;
}
if (from === common) {
break;
}
const alternate = from.alternate;
if (alternate !== null && alternate === common) {
break;
}
pathFrom.push(from);
from = getParent(from);
}
const pathTo = [];
while (true) {
if (!to) {
break;
}
if (to === common) {
break;
}
const alternate = to.alternate;
if (alternate !== null && alternate === common) {
break;
}
pathTo.push(to);
to = getParent(to);
}
//以上程式碼將from節點到from與to節點的最近公共祖先節點(不包括公共祖先節點)push到pathFrom陣列
//以上程式碼將to節點到from與to節點的最近公共祖先節點(不包括公共祖先節點)push到pathTo陣列
// 以下程式碼用於對pathFrom冒泡,執行fn
for (let i = 0; i < pathFrom.length; i++) {
fn(pathFrom[i], 'bubbled', argFrom);
}
// 以下程式碼用於對pathTo捕獲,執行fn
for (let i = pathTo.length; i-- > 0; ) {
fn(pathTo[i], 'captured', argTo);
}
}