數據結構與算法-學習筆記(17)

沒有父節點的節點叫作根節點。沒有子節點的節點叫作葉子節點或葉節點。node

關於樹還有三個類似概念:高度(Height)、深度(Depth)、層(Level)。數組

二叉樹

如何表示或者存儲一顆二叉樹

  1. 基於指針的二叉鏈式存儲

只須要知道根節點,就知道了整棵樹。

  1. 基於數組的順序存儲:更適合徹底二叉樹(浪費最少空間)

二叉樹的遍歷

三種方法:前序遍歷、中序遍歷、後序遍歷(前中後指的是根節點的位置)。遍歷是一個遞歸的過程。bash

遞歸公式:post

前序遍歷的遞推公式:
preOrder(r) = print r->preOrder(r->left)->preOrder(r->right)

中序遍歷的遞推公式:
inOrder(r) = inOrder(r->left)->print r->inOrder(r->right)

後序遍歷的遞推公式:
postOrder(r) = postOrder(r->left)->postOrder(r->right)->print r

// 出口就是r=nil
複製代碼
typedef struct TreeNode {
    int data;
    struct TreeNode *left;
    struct TreeNode *right;
} Node;

void preOrder(Node *root) {
    if (!root) return;
    printf("%d",root->data);
    preOrder(root->left);
    preOrder(root->right);
}

void inOrder(Node *root) {
    if (!root) return;
    inOrder(root->left);
    printf("%d", root->data);
    inOrder(root->right);
}

void postOrder(Node *root) {
    if (!root) return;
    postOrder(root->left);
    postOrder(root->right);
    printf("%d",root->data);
}
複製代碼

遍歷的時間複雜度:每一個節點最多被訪問兩次,因此遍歷的時間複雜度跟節點個數n成正比,所以O(n);ui

思考題

Q: 給定一組數據,能夠構建出多少種不一樣的二叉樹?spa

A:徹底二叉樹能夠存儲爲一個數組,則樹的排序也就是數組中n個數據的排序:n!3d

二叉查找樹

二叉查找樹支持快速查找、插入、刪除數據。指針

二叉查找樹的要求:在樹中的任意一個節點,其左子樹中的每一個節點值,都要小於這個節點的值,而右子樹中節點值都大於這個節點的值。所以不能有重複的數據。(左子樹<父節點<右節點)code

查找

Node* search(Node *root, int data) {
    if (!root) return nil;
    if (data == root->data) {
        return root;
    } else if (data > root->data) {
        return search(root->right, data);
    } else {
        return search(root->left, data);
    }
    
}

Node * search2(Node *root, int data) {
    while (root != nil) {
        if (data == root->data) {
            return root;
        } else if (data > root->data) {
            root = root->right;
        } else {
            root = root->left;
        }
    }
    
    return nil;
}
複製代碼

插入

相似查找。把新插入的數據放在葉子節點上,這樣既不影響二叉查找樹的特性,又不須要破壞本來樹的結構。cdn

void insert(Node *root, int data) {
    if (!root) {
        // 注意結構體指針的賦值 Node *node = {data, nil, nil}不行的,此時node是個指針,卻把結構體值類型數據賦值給它了(int *a = 2❎)
        /* 平時
         ① NSObject *obj = [[NSObject alloc] init];
         obj 它也是一個指向NSObject類型對象數據的指針,[[NSObject alloc] init]返回的也是一個指針,而不是實際的對象的值
         ② char *b = "String";  "String"字符串常量,它返回給b的也是地址。
         ③ obj = nil;讓obj指針變量置空,也就是讓它指向空,nil是空指針的意思,不是讓obj所指向的數據置空
         */
        Node node = {data, nil, nil};
        root = &node;
    }
    while (root != nil) {
        if (data > root->data) {
            if (!root->right) {
                Node node = {data, nil, nil};
                root->right = &node;
                return;
            } else {
                root = root->right;
            }
        } else {
            if (!root->left) {
                Node node = {data, nil, nil};
                root->left = &node;
                return;
            } else {
                root = root->left;
            }
        }
    }
}
複製代碼

刪除

分三種狀況:

  1. 要刪除的節點沒有子節點,即葉子節點;
  2. 要刪除的節點只有一個子節點;
  3. 要刪除的節點有兩個子節點。

int delete(Node *root, int data) {
    if (!root) return -1;
    
    Node *preNode = nil;
    BOOL find = NO;
    while (root != nil) {
        if (root->data == data) {
            find = YES;
            break;
        }
        preNode = root;
        if (root->data > data) {
            root = root->left;
        } else {
            root = root->right;
        }
    }
    if (!find) return -1;
    
    if (!root->left && !root->right) {
        if (preNode->left == root) preNode->left = nil;
        else preNode->right = nil;
        return 1;
    }
    
    if (!root->left || !root->right) {
        Node *child = nil;
        if (!root->left) {
            child = root->right;
        } else {
            child = root->left;
        }
        if (preNode->left == root) preNode->left = child;
        else preNode->right = child;
        
        return 1;
    }
    
    Node *min = root->right;
    Node *preM = root;
    while (!min) {
        preM = min;
        min = min->left;
    }
    root->data = min->data;
    if (preM != root) {
        if (min->right) preM->left = min->right;
        else preM->left = nil;
    } else {
        preM->right = nil;
    }
    
    return 1;
    
}
複製代碼

其餘操做

能夠快速排序:

中序遍歷二叉查找樹,能夠輸出有序數據列,O(n)。

支持重複數據的二叉查找樹

實際開發中每一個節點的數據能夠是一個更大的結構,如包含不少字段的對象,咱們利用某個字段做爲key來構建樹。

這樣若是存在相同鍵值的對象,怎麼解決:

  1. 每一個節點不止能夠存儲一個數據,能夠存儲鏈表、數組等結構,key相同的數據都存儲在同一節點。
typedef struct TreeNode {
    int key;
    struct TreeNode *left;
    struct TreeNode *right;
    
    NSMutableArray *data;
} Node;
複製代碼
  1. 每一個節點仍存儲一個數據,遇到相同節點,就當作大於當前節點,繼續向右走直到走到葉子節點,插入。(同理當作小於,向左走也能夠)這樣查詢和刪除就須要一直向下走直到走到葉子節點,把全部相同的找出、刪除。

時間複雜度

相同一組數據二叉查找樹的排列方式會有不少種:

對於極度不平衡的樹,他們已經接近鏈表了,查找插入刪除O(n)。

對於徹底二叉樹,時間複雜度和樹的層數L成正比。除了底層,每層包含的節點數2^(L-1)。

// 假設最大層數爲K
n >= 1+2+4+8+...+2^(K-2)+1
n <= 1+2+4+8+...+2^(K-2)+2^(K-1)
複製代碼

藉助等比數列求和公式,算出K的範圍[log2(n+1), log2n+1],也就得出時間複雜度O(logn)

注:等比數列(每一項與它前一項的比都是同一個常數,也叫公比 q)

思考題

求樹的高度?

用遞歸的思想:height = Max(Height(left), Height(right))+1;

相關文章
相關標籤/搜索