Masonry icon indicating copy to clipboard operation
Masonry copied to clipboard

查找两个view最近公共祖先那段逻辑算法复杂度是O(n²),可以优化

Open hopestar90 opened this issue 5 years ago • 8 comments

New Issue Checklist

Issue Info

如下代码:

  • (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view { MAS_VIEW *closestCommonSuperview = nil;

    MAS_VIEW *secondViewSuperview = view; while (!closestCommonSuperview && secondViewSuperview) { MAS_VIEW *firstViewSuperview = self; while (!closestCommonSuperview && firstViewSuperview) { if (secondViewSuperview == firstViewSuperview) { closestCommonSuperview = secondViewSuperview; } firstViewSuperview = firstViewSuperview.superview; } secondViewSuperview = secondViewSuperview.superview; } return closestCommonSuperview; }

上述代码意图为寻找两个View最近的公共父View,时间复杂度是O(n²),其实可以优化成O(n)。

将两个view作为两个链表的头结点,最顶层的superview作为尾结点,这样问题就转化为寻找两个链表的第一个相交节点的问题,从而变为一个复杂度为O(n)的问题。不知道这么考虑是否是正确的?

Issue Description

⚠️ Replace this with the description of your issue. ⚠️

hopestar90 avatar Jun 11 '20 10:06 hopestar90

有想法是好的,你可以把你的优化方案写出来,验证一下不就ok了吗

Jake-Chiu avatar Apr 20 '21 16:04 Jake-Chiu

这样?

- (__kindof MAS_VIEW *)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = self;
    MAS_VIEW *secondViewSuperview = view;

    while (closestCommonSuperview != secondViewSuperview) {
        closestCommonSuperview = closestCommonSuperview == nil ? view : closestCommonSuperview.superview;
        secondViewSuperview = secondViewSuperview == nil ? self : secondViewSuperview.superview;
    }

    return closestCommonSuperview;
}

cntrump avatar Jun 01 '21 14:06 cntrump

这么写感觉在某些case会陷入死循环,如果self的superview是nil,view也是nil的情况

hopestar90 avatar Jun 02 '21 06:06 hopestar90

这么写感觉在某些case会陷入死循环,如果self的superview是nil,view也是nil的情况

返回 nil。 理论上不会死循环,因为不会形成环。 这里有别人做的动画演示。 https://zhuanlan.zhihu.com/p/357611418

cntrump avatar Jun 02 '21 07:06 cntrump

我给一个写法~ 但可能没有那么优雅:

MAS_VIEW *firstView = self; MAS_VIEW *secondView = view; NSInteger firstLength = 0; NSInteger secondLength = 0; while (firstView) { firstLength++; firstView = firstView.superview; } while (secondView) { secondLength++; secondView = secondView.superview; } NSInteger diff = 0; if (firstLength > secondLength) { diff = firstLength - secondLength; firstView = self; secondView = view; } else { diff = secondLength - firstLength; firstView = view; secondView = self; }

while (diff > 0) {
    diff--;
    firstView = firstView.superview;
}
while (firstView && secondView && firstView != secondView) {
    firstView = firstView.superview;
    secondView = secondView.superview;
}
if (firstView != secondView) {
    return nil;
} else {
    return firstView;
}

hopestar90 avatar Jun 02 '21 07:06 hopestar90

这样?

- (__kindof MAS_VIEW *)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = self;
    MAS_VIEW *secondViewSuperview = view;

    while (closestCommonSuperview != secondViewSuperview) {
        closestCommonSuperview = closestCommonSuperview == nil ? view : closestCommonSuperview.superview;
        secondViewSuperview = secondViewSuperview == nil ? self : secondViewSuperview.superview;
    }

    return closestCommonSuperview;
}

刚又分析了下 看了下 这样确实可以返回正确的结果

hopestar90 avatar Jun 02 '21 07:06 hopestar90

楼上都搞的太复杂了,只需要几个简单的if-else就可以优化95%的遍历次数。 毕竟firstView与secondView,在大多数场景下都是父子关系 or 兄弟关系。

优化代码如下:


#pragma mark - heirachy

- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = nil;
    
    // 一般约束关联的两个view要么是父子关系,要么是兄弟关系,碰运气可以大大优化链表遍历的时间
    closestCommonSuperview = [self take_a_chance:view];
    if (closestCommonSuperview)
        return closestCommonSuperview;

    MAS_VIEW *secondViewSuperview = view;
    while (!closestCommonSuperview && secondViewSuperview) {
        MAS_VIEW *firstViewSuperview = self;
        while (!closestCommonSuperview && firstViewSuperview) {
            if (secondViewSuperview == firstViewSuperview) {
                closestCommonSuperview = secondViewSuperview;
            }
            firstViewSuperview = firstViewSuperview.superview;
        }
        secondViewSuperview = secondViewSuperview.superview;
    }
    return closestCommonSuperview;
}


/// 碰运气 快速找到common super view
- (MAS_VIEW *)take_a_chance:(MAS_VIEW *)secondView {
    if (!secondView) return nil;
    if (self == secondView) return self;
    
    if (secondView.superview == self.superview && self.superview) {
        return self.superview; // 兄弟关系
    } else if (self.superview == secondView) {
        return self.superview; // first 是 second 的子view
    } else if (self == secondView.superview) {
        return secondView.superview; // first 是 second 的父view
    }
    return nil;
}

优化结果如下:(某日活百万app启动,到首页不再layout)

优化前: 查找公共viev:323次 查找父view遍历:1656次

优化后: 查找公共view:10次 查找父view遍历:84次

OPTJoker avatar Dec 27 '23 10:12 OPTJoker

楼上都搞的太复杂了,只需要几个简单的if-else就可以优化95%的遍历次数。 毕竟firstView与secondView,在大多数场景下都是父子关系 or 兄弟关系。

优化代码如下:

#pragma mark - heirachy

- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = nil;
    
    // 一般约束关联的两个view要么是父子关系,要么是兄弟关系,碰运气可以大大优化链表遍历的时间
    closestCommonSuperview = [self take_a_chance:view];
    if (closestCommonSuperview)
        return closestCommonSuperview;

    MAS_VIEW *secondViewSuperview = view;
    while (!closestCommonSuperview && secondViewSuperview) {
        MAS_VIEW *firstViewSuperview = self;
        while (!closestCommonSuperview && firstViewSuperview) {
            if (secondViewSuperview == firstViewSuperview) {
                closestCommonSuperview = secondViewSuperview;
            }
            firstViewSuperview = firstViewSuperview.superview;
        }
        secondViewSuperview = secondViewSuperview.superview;
    }
    return closestCommonSuperview;
}


/// 碰运气 快速找到common super view
- (MAS_VIEW *)take_a_chance:(MAS_VIEW *)secondView {
    if (!secondView) return nil;
    if (self == secondView) return self;
    
    if (secondView.superview == self.superview && self.superview) {
        return self.superview; // 兄弟关系
    } else if (self.superview == secondView) {
        return self.superview; // first 是 second 的子view
    } else if (self == secondView.superview) {
        return secondView.superview; // first 是 second 的父view
    }
    return nil;
}

优化结果如下:(某日活百万app启动,到首页不再layout)

优化前: 查找公共viev:323次 查找父view遍历:1656次

优化后: 查找公共view:10次 查找父view遍历:84次

LGTM,搬走抄到我的 Fork 里

cntrump avatar Dec 27 '23 10:12 cntrump