因爲咱們後面講的一些結構有不少是樹結構實現的好比堆,而後基於堆能夠實現優先級隊列,有界優先級隊列等,因此咱們先講述樹結構,咱們可能常見到的是二叉樹,可是還有一些其餘的樹的概念:好比二叉搜索樹,AVL樹,紅黑樹,B樹,決策樹等。以便於在特定場景下使用。node
hit-test 邏輯:此方法經過hitTest:withEvent:從最後到第一個向其每一個子視圖發送消息來遍歷接收者的子樹,直到其中一個返回非nil值。ios
代碼:git
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
複製代碼
採用reverse pre-order depth-first traversal algorithm遍歷。首先訪問根節點,而後從較高到較低的索引遍歷其子樹,這樣作是爲了快速遍歷到咱們須要的節點,試想若是從低到高遍歷這個View,層級不少的狀況下豈不是要遍歷不少節點。以下圖所示:github
好比「View A.2」和「View B.1」都是重疊的。但因爲「View B」的子視圖索引高於「View A」,所以「View B」及其子視圖呈如今「View A」及其子視圖上方。所以,當用戶的手指在與「視圖A.2」重疊的區域中觸摸「視圖B.1」時,應經過命中測試返回「視圖B.1」。算法
一個小提示打印當前View下全部子View採用了遞歸遍歷並打印,此刻看出來算法的重要性了吧。數據庫
- (void)listSubviewsOfView:(UIView *)view {
NSArray *subviews = [view subviews];
if ([subviews count] == 0) return;
for (UIView *subview in subviews)
{
NSLog(@"%@", subview);
[self listSubviewsOfView:subview];
}
}
複製代碼
上圖後序遍歷獲得後綴表達式:( ((70 10 - )32 / ) (24 13 + ) X )數組
二叉樹的節點最大分支度是2,也說明每一個節點最多擁有2個子節點,範圍是[0-2]。bash
下面咱們推導下Max Nodes。上圖第三種狀況h = 3,Max Nodes = 1 +2 + 22+ 23 = 15,也就是Max Nodes = 1 +2 + 22+ 23 + ....+ 2h= ,也就是等比數列求和,以下圖:數據結構
等比數列求和能夠參考以下連接: zh.wikipedia.org/wiki/等比數列ide
反過來能夠很容易推導出Min Height (h = Log2(n+1)-1),Max Height(h = n-1)。
若是是full binary tree那麼節點數和樹Height的關係又是什麼呢? 推導過程能夠參考上面的步驟,Min Nodes(n = 2h+1),Max Nodes(2h+1-1),反過來能夠很容易推導出Min Height (h = Log2(n+1)-1),Max Height(h = )。
第i層至多擁有2i-1個節點,最少有1個節點。從下圖能夠很容易看出來,
度爲0的節點數n1和度爲2節點數n2的關係。n1 = n2 + 1。看下圖
二叉樹能夠被以廣度優先的順序做爲隱式數據結構存儲在數組中。注意的是若是這個二叉樹是complete binary tree,這些不會浪費空間,可是若是對於A degenerate (or pathological) tree這種高度很大的樹就很浪費空間,能夠參考後面根據這個存儲方式判斷這個樹是否是complete binary tree的介紹。這種存儲方法一般也用在binary heaps。
舉例:找E的父節點,E的索引是5,那麼Parent = i/2 = 5/2 = 2.5,向下取整就是2,對應的就是B。反之假如找A的左右孩子,A的索引是1,那麼左孩子索引就是2對應B,右孩子索引就是3對應C。
注意:Parent的索引若是有存在小數狀況是向下取整。
下面咱們看怎麼根據這個表示方法判斷是否是complete binary tree。
@interface DSTreeNode : NSObject
@property (nonatomic, strong) NSObject *object;
@property (nonatomic, strong) DSTreeNode *leftChild;
@property (nonatomic, strong) DSTreeNode *rightChild;
@property (nonatomic, strong) DSTreeNode *parent;
@property (nonatomic, assign) SEL compareSelector;
- (void)printDescription;
//是不是左仍是結點
- (BOOL)isLeftChildOfParent;
@end
複製代碼
這種存儲二叉樹方法浪費了很多內存,因爲那些節點的左右指針(爲null或者指向某些節點)。
經過上述四種方式遍歷二叉樹的每一個節點。
思路:通常咱們習慣 ,根節點-左節點-右節點,這樣的模型,咱們就把例如上圖A的左子樹當作一個塊,相似一個大節點用括號圈起來,一樣的右子樹也這樣作。而後每一個塊裏作前中後遍歷。
前序遍歷。A,(B,D,E),(C,F,G)。獲得結果是 A,B,D,E,C,F,G 。
中序遍歷。(D,B,E),A,(F,C,G)。獲得的結果是 D,B,E,A,F,C,G 。
後序遍歷。(D,E,B),(F,G,C),A。獲得的結果是 D,E,B,F,G,C,A 。
層級遍歷。 A,B,C,D,E,F,G 。
前序遍歷思路:每一個節點從左邊畫線一直到底部這個線,而後按照從左到右的順序讀取節點。 結果是:A,B,D,E,C,F,G 。
中序遍歷思路:每一個節點從中間畫線到底部這個線,而後按照從左到右的順序讀取節點。 結果是 D,B,E,A,F,C,G 。
後序遍歷思路:每一個節點從右邊畫線到底部這條線,而後從左到右的順序讀取節點。 結果是 D,E,B,F,G,C,A 。
前序遍歷思路:從每一個節點左邊畫出一個線,而後從根結點開始轉一圈,通過每一個節點和樹的分支,包裹這個樹。通過這些短線的順序就是結果。A,B,D,E,C,F,G 。
中序遍歷思路:從每一個節點底部邊畫出一個線,而後從根結點開始轉一圈,通過每一個節點和樹的分支,包裹這個樹。通過這些短線的順序就是結果。D,B,E,A,F,C,G 。
後序遍歷思路:從每一個節點右邊畫出一個線,而後從根結點開始轉一圈,通過每一個節點和樹的分支,包裹這個樹。通過這些短線的順序就是結果。D,E,B,F,G,C,A 。
這節主要介紹二叉樹的代碼實現,咱們講述Linked Representation的實現,主要包含下面幾個操做。
從上圖能夠看出,每一個節點除了自己之外,還得有一個父子以及左右孩子節點信息。所以須要一個節點類。主要代碼實現以下:
@interface DSTreeNode : NSObject
@property (nonatomic, strong) NSObject *object;
@property (nonatomic, strong) DSTreeNode *leftChild;
@property (nonatomic, strong) DSTreeNode *rightChild;
@property (nonatomic, strong) DSTreeNode *parent;
@property (nonatomic, assign) SEL compareSelector;
- (void)printDescription;
//是不是左仍是結點
- (BOOL)isLeftChildOfParent;
@end
複製代碼
對於二叉樹的建立咱們初始化一個根節點的方式建立,以下代碼實現:
- (instancetype)initWithObject:(NSObject *)object
{
if (self = [super init]) {
_root = [[DSTreeNode alloc] init];
self.root.object = object;
}
return self;
}
複製代碼
以插入節點的方式構建整個二叉樹以下代碼:
//插入結點
- (BOOL)insertNode:(NSObject *)node parent:(NSObject *)parent isLeftChild:(BOOL)value
{
DSTreeNode *treeNode = [[DSTreeNode alloc] init];
treeNode.object = node;
DSTreeNode *parentNode = [self find:parent];
//1
if (value == true && parentNode.leftChild == nil) {
//2
treeNode.parent = parentNode;
//3
parentNode.leftChild = treeNode;
}
//4
else if (parentNode.rightChild == nil) {
treeNode.parent = parentNode;
parentNode.rightChild = treeNode;
}
//5
else {
NSAssert(parentNode.leftChild != nil || parentNode.rightChild != nil, @"Can't insert into parent node!");
return false;
}
return true;
}
複製代碼
代碼解釋:
查找某個節點
- (DSTreeNode *)find:(NSObject *)object
{
//1
DSQueue*queue = [[DSQueue alloc] init];
[queue enqueue:self.root];
DSTreeNode *node;
//2
while (![queue isEmpty]) {
node = [queue dequeue];
if ([node.object isEqualTo:object]) {
return node;
}
if (node.leftChild) {
[queue enqueue:node.leftChild];
}
if (node.rightChild) {
[queue enqueue:node.rightChild];
}
}
return nil;
}
複製代碼
層級遍歷的思路和上述查找的思路相似。前中後序遍歷的思路利用遞歸的思路實現,而後按照以前介紹二叉樹遍歷算法的思路就能夠實現了。前序遍歷的代碼以下:
//若是當前根結點存在則前序遍歷這個樹
- (void)preOrderTraversal
{
if (self.root) {
[DSBinaryTree preOrderTraversalRecursive:self.root];
}
}
//遞歸的遍歷並打印樹 順序是根 左 右
+ (void)preOrderTraversalRecursive:(DSTreeNode *)node
{
if (node) {
NSLog(@"%@",node.object);
[DSBinaryTree preOrderTraversalRecursive:node.leftChild];
[DSBinaryTree preOrderTraversalRecursive:node.rightChild];
}
}
複製代碼
Given a binary tree, return all root-to-leaf paths.
For example, given the following binary tree:
1
/ \
2 3
\
5
All root-to-leaf paths are:
["1->2->5", "1->3"]
複製代碼
給咱們一個二叉樹,讓咱們返回全部根到葉節點的路徑。咱們能夠採用遞歸的思路,不停的DFS到葉結點,若是遇到葉結點的時候,那麼此時一條完整的路徑已經造成,咱們加上當前的葉結點後變成的完整路徑放到數組中。
須要注意的是對空節點的判斷,以及遞歸函數回溯時候對一些對象的影響。
- (void)printPathsRecurTreeNode:(DSTreeNode *)treeNode path:(NSString *)path results:(NSMutableArray <NSString *>*)results
{
//1
if (treeNode == nil) {
return;
}
//2
if (treeNode.leftChild == nil && treeNode.rightChild == nil)
{
NSString *resultsStr = [NSString stringWithFormat:@"%@%@",path,treeNode.object];
[results addObject:resultsStr];
}
else
{
//3
if (treeNode.leftChild != nil)
{
NSString *resultsStr = [NSString stringWithFormat:@"%@%@",path,[NSString stringWithFormat:@"%@->",treeNode.object]];
[self printPathsRecurTreeNode:treeNode.leftChild path:resultsStr results:results];
}
//4
if (treeNode.rightChild != nil )
{
NSString *resultsStr = [NSString stringWithFormat:@"%@%@",path,[NSString stringWithFormat:@"%@->",treeNode.object]];
[self printPathsRecurTreeNode:treeNode.rightChild path:resultsStr results:results];
}
}
}
複製代碼