Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
hopestar90 opened this issue Jun 11, 2020 · 8 comments

Comments

@hopestar90
Copy link

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. ⚠️

@Jake-Chiu
Copy link

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

@cntrump
Copy link
Contributor

cntrump commented Jun 1, 2021

这样?

- (__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
Copy link
Author

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

@cntrump
Copy link
Contributor

cntrump commented Jun 2, 2021

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

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

@hopestar90
Copy link
Author

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

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
Copy link
Author

这样?

- (__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;
}

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

@OPTJoker
Copy link

楼上都搞的太复杂了,只需要几个简单的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次

@cntrump
Copy link
Contributor

cntrump commented Dec 27, 2023

楼上都搞的太复杂了,只需要几个简单的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 里

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants