C基礎 - 終結 Size Balanced Tree

来源:https://www.cnblogs.com/life2refuel/archive/2018/08/12/9464074.html
-Advertisement-
Play Games

Size Balanced Tree 挺有意思的, 適合新手練習 ...


引言 - 初識 Size Balanced Tree

  最近在抽細碎的時間看和學習 random 的 randnet 小型網路庫.

  iamrandom/randnet - https://github.com/iamrandom/randnet (1)

  瞭解到 陳啟峰 2006-12-29 高中的時候寫的論文 Size Balanced Tree(一種變種的 AVI 樹) 感覺好精彩.

  就著手翻譯成實戰代碼.

  陳啟峰 Size Balanced Tree - https://files.cnblogs.com/files/life2refuel/%E9%99%88%E5%90%AF%E5%B3%B0%E3%80%8ASize-Balanced-Tree%E3%80%8B.pdf (2)

     翻譯過程中前驅和後繼參照了下麵同行的代碼

   二叉查找樹的前驅後繼 - http://www.cnblogs.com/Renyi-Fan/p/8252227.html (3)

: )  - , -

  推薦朋友學習的時候, 可以先看 (2) 和 (3) . 其中 (3) 最簡單, 看完之後應該收穫滿滿.

對於 (2) 還是深深佩服的, 畢竟一樣大時候, 才剛脫離趣味玩泥巴. 大佬就可以證明演算法的完備性.  如果你看到

(2) 證明部分,  這裡不妨補充一些關於 f[h] 求證過程.

其實也就是高中二階等比數列求證.

  那後面就開始扯了正題了  : ) 如何用 C 實現上面論文思路, 用於實戰開發中.. 我這裡拋磚砸高鐵.  -:

 

前言 - 結構介面先行

  先看設計的數據結構 

#ifndef _H_STREE
#define _H_STREE

typedef unsigned long long stree_key_t;

typedef union {
    void * ptr;
    double number;
    signed long long i;
    unsigned long long u;
} stree_value_t;

struct stree {
    struct stree * left;    // 左子樹
    struct stree * right;   // 右子樹
    unsigned size;          // 樹節點個數

    stree_key_t key;        // tree node key
    stree_value_t value;    // tree node value
};

typedef struct stree * stree_t;

inline unsigned stree_size(const struct stree * const node) {
    return node ? node->size : 0;
}

#endif//_H_STREE

Size Balanced Tree 中多了一個特色節點 unsigned size; 用戶記錄當前樹上節點個數.  相比 red black tree

後者多記錄了 父親節點和紅黑性質. 每種平衡搜索樹, 都有自己的特殊欄位. 看的出後續核心操作都是

圍繞 unsigend size; 欄位.

(對於 key 和 value 設計持開放態度. 其中 key 是必須的, 不過這裡直接 unsigned long long 走起了)

首先看看最好理解的旋轉部分(出道就是巔峰), 的 rotate 旋轉操作

// stree_left_rotate - size tree left rotate
static void stree_left_rotate(stree_t * pode) {
    stree_t node = *pode;
    stree_t right = node->right;

    node->right = right->left;
    right->left = node;
    right->size = node->size;
    node->size = stree_size(node->left) + stree_size(node->right) + 1;

    *pode = right;
}

// stree_right_rotate - size tree right rotate
//
//           (y)   left rotate      (x)
//          /   \  ------------>   /   \
//        (x)   [C]              [A]   (y)
//       /   \     <------------      /   \
//      [A]  [B]    right rotate    [B]   [C]
//
static void stree_right_rotate(stree_t * pode) {
    stree_t node = *pode;
    stree_t left = node->left;

    node->left = left->right;
    left->right = node;
    left->size = node->size;
    node->size = stree_size(node->left) + stree_size(node->right) + 1;

    *pode = left;
}

強烈好好看看練習練習, 否則後面更加不懂.

  : ) 好想說, 後面不用看了, 複製我的代碼. 如果用的時候用吧, 否則就別再見了.

 

正文 - 有時候要認命

當前核心是翻譯論文, 並且終結 Size Balanced Tree 各種花式的代碼.

細節部分看論文, 看作者原文思路.這裡只是貼代碼, 帶你感受一下一個上的了臺面的 Size Balanced Tree 實現  ~

stree.h

#ifndef _H_STREE
#define _H_STREE

typedef unsigned long long stree_key_t;

typedef union {
    void * ptr;
    double number;
    signed long long i;
    unsigned long long u;
} stree_value_t;

struct stree {
    struct stree * left;    // 左子樹
    struct stree * right;   // 右子樹
    unsigned size;          // 樹節點個數

    stree_key_t key;        // tree node key
    stree_value_t value;    // tree node value
};

typedef struct stree * stree_t;

inline unsigned stree_size(const struct stree * const node) {
    return node ? node->size : 0;
}

//
// stree_delete - Size Balanced Tree delete destroy 
// poot     : 指向 tree 對象指針
// return   : void
//
extern void stree_delete(stree_t * poot);

//
// stree_insert - size tree insert node
// poot     : 指向 tree 對象指針
// key      : 插入 node key
// value    : 插入 node value
// return   : void
//
extern void stree_insert(stree_t * poot, stree_key_t key, stree_value_t value);

//
// stree_remove - size tree remove node
// poot     : 指向 tree 對象指針
// key      : 查找 node key
// return   : void
//
extern void stree_remove(stree_t * poot, stree_key_t key);

//
// stree_find - 尋找到指定的節點
// root     : tree 對象
// key      : 查找 node key
// return   : 查找 tree node, NULL 表示沒有
//
extern stree_t stree_find(stree_t root, stree_key_t key);

//
// stree_rank - 返回 key 在 root 樹中排名, 也就是比 key 小的那顆樹的大小上加 1
// root     : tree 對象
// key      : 查找 node key
// return   : 排名
// 
extern unsigned stree_rank(stree_t root, stree_key_t key);

//
// stree_select - root 根節點樹種排名為 k 的節點
// root     : tree 對象
// k        : 排名 [1, stree_size(root)]
// return   : 返回查詢到節點
//
extern stree_t stree_select(stree_t root, unsigned k);

//
// stree_pred - 查找 root 前驅節點, 比 key 小的最大的數
// root     : tree 對象
// key      : 查找 node key
// return   : 返回查詢到節點
//
extern stree_t stree_pred(stree_t root, stree_key_t key);

//
// stree_succ - 查找 root 後繼節點, 比 key 大的最小的數
// root     : tree 對象
// key      : 查找 node key
// return   : 返回查詢到節點
//
extern stree_t stree_succ(stree_t root, stree_key_t key);

#endif//_H_STREE

  上面 delete 和 remove 單詞和論文中比對有些不一樣. 因為在 C / C++ 中 delete 語義是銷毀. 

所以採用 remove 去除的節點的語義單詞. 應該更加貼合實際開發的代碼函數定義. 

stree.c

#include "stree.h"
#include <stdlib.h>
#include <stdbool.h>

static void stree_free(stree_t root) {
    if (root->left)
        stree_free(root->left);
    if (root->right)
        stree_free(root->right);
    free(root);
}

//
// stree_delete - Size Balanced Tree delete destroy 
// poot     : 指向 tree 對象指針
// return   : void
//
inline void 
stree_delete(stree_t * poot) {
    if (!poot || !*poot)
        return;
    stree_free(*poot);
    *poot = NULL;
}

// stree_left_rotate - size tree left rotate
static void stree_left_rotate(stree_t * pode) {
    stree_t node = *pode;
    stree_t right = node->right;

    node->right = right->left;
    right->left = node;
    right->size = node->size;
    node->size = stree_size(node->left) + stree_size(node->right) + 1;

    *pode = right;
}

// stree_right_rotate - size tree right rotate
//
//           (y)   left rotate      (x)
//          /   \  ------------>   /   \
//        (x)   [C]              [A]   (y)
//       /   \     <------------      /   \
//      [A]  [B]    right rotate    [B]   [C]
//
static void stree_right_rotate(stree_t * pode) {
    stree_t node = *pode;
    stree_t left = node->left;

    node->left = left->right;
    left->right = node;
    left->size = node->size;
    node->size = stree_size(node->left) + stree_size(node->right) + 1;

    *pode = left;
}

// stree_maintain - Size Balanced Tree Maintain
static void stree_maintain(stree_t * pode, bool flag) {
    unsigned size;
    stree_t node = *pode;
    if (!node) return;

    if (!flag) {
        if (!node->left) return;

        size = stree_size(node->right);
        if (size < stree_size(node->left->left))
            stree_right_rotate(pode);
        else if (size < stree_size(node->left->right)) {
            stree_left_rotate(&node->left);
            stree_right_rotate(pode);
        } else return;

        stree_maintain(&node->left, false);
    } else {
        if (!node->right) return;

        size = stree_size(node->left);
        if (size < stree_size(node->right->right))
            stree_left_rotate(pode);
        else if (size < stree_size(node->right->left)) {
            stree_right_rotate(&node->right);
            stree_left_rotate(pode);
        } else return;

        stree_maintain(&node->right, true);
    }

    stree_maintain(pode, false);
    stree_maintain(pode, true);
}

static void stree_insert_node(stree_t * poot, stree_t node) {
    bool flag;
    stree_t root = *poot;
    if (!root) {
        (*poot = node)->size = 1;
        return;
    }

    ++root->size; // 插入到非空樹, 節點數加 1
    if ((flag = node->key < root->key))
        stree_insert_node(&root->left, node);
    else
        stree_insert_node(&root->right, node);
    stree_maintain(poot, !flag);
}

//
// stree_insert - size tree insert node
// poot     : 指向 tree 對象指針
// key      : 插入 node key
// value    : 插入 node value
// return   : void
//
inline void 
stree_insert(stree_t * poot, stree_key_t key, stree_value_t value) {
    stree_t node = calloc(1, sizeof(struct stree));
    node->key = key;
    node->value = value;
    stree_insert_node(poot, node);
}

static stree_t stree_remove_node(stree_t * pode, const stree_key_t key, bool find) {
    stree_t record;
    stree_t node = *pode;
    if (!node) return NULL;

    if (find && !node->right) {
        //  the right end node of right child tree replace the delete node
        *pode = node->left;
        return node;
    }

    if (!find && key == node->key) {
        if (node->size == 1) {
            // leaf node, need delete
            *pode = NULL;
            return node;
        }
        if (node->size == 2) {
            // single branch node, need child node replace delete node
            record = node->left ? node->left : node->right;
            node->left = node->right = NULL;
        } else {
            // max key node of left tree replace pnode
            record = stree_remove_node(&node->left, key, true);
        }
        if (record) {
            node->key = record->key;
            node->value = record->value;
        }
        --node->size;
        stree_maintain(pode, true);
    } else if (!find && key < node->key) {
        record = stree_remove_node(&node->left, key, false);
        if (record)
            --node->size;
    } else {
        record = stree_remove_node(&node->right, key, find);
        if (record)
            --node->size;
    }

    stree_maintain(pode, !find && key < node->key);
    return record;
}

//
// stree_remove - size tree remove node
// poot     : 指向 tree 對象指針
// key      : 查找 node key
// return   : void
//
inline void 
stree_remove(stree_t * poot, stree_key_t key) {
    stree_t node;
    if (!poot) return;
    node = stree_remove_node(poot, key, false);
    if (node) free(node);
}

//
// stree_find - 尋找到指定的節點
// root     : tree 對象
// key      : 查找 node key
// return   : 查找 tree node, NULL 表示沒有
//
stree_t 
stree_find(stree_t root, stree_key_t key) {
    while (root) {
        if (key < root->key)
            root = root->left;
        else if (root->key < key)
            root = root->right;
        else break;
    }
    return root;
}

//
// stree_rank - 返回 key 在 root 樹中排名, 也就是比 key 小的那顆樹的大小上加 1
// root     : tree 對象
// key      : 查找 node key
// return   : 排名
// 
unsigned 
stree_rank(stree_t root, stree_key_t key) {
    int k = 0;
    while (root) {
        if (key < root->key)
            root = root->left;
        else if (root->key < key) {
            k += stree_size(root->left) + 1;
            root = root->right;
        } else {
            k += stree_size(root->left);
            break;
        }
    }
    return k;
}

//
// stree_select - root 根節點樹種排名為 k 的節點
// root     : tree 對象
// k        : 排名 [1, stree_size(root)]
// return   : 返回查詢到節點
//
stree_t 
stree_select(stree_t root, unsigned k) {
    while (root) {
        unsigned size = stree_size(root->left);
        if (k < size)
            root = root->left;
        else if (size < k) {
            root = root->right;
            k -= size + 1;
        } else break;
    }
    return root;
}

/*
 前驅節點
    1. 若一個節點有左子樹,那麼該節點的前驅節點是其左子樹中 key 值最大的節點
    2. 若一個節點沒有左子樹,那麼判斷該節點和其父節點的關係
        2.1 若該節點是其父節點的右孩子,那麼該節點的前驅節點即為其父節點。
        2.2 若該節點是其父節點的左孩子,那麼需要沿著其父親節點一直向樹的頂端尋找,
            直到找到一個節點P,P節點是其父節點Q的右孩子,那麼Q就是該節點的後繼節點

 */

static stree_t stree_get_right(stree_t root) {
    if (root) {
        while (root->right)
            root = root->right;
    }
    return root;
}

static stree_t stree_get_right_p(stree_t root, stree_key_t key, stree_t * pq, stree_t * pr) {
    while (root) {
        if (key == root->key)
            return root;

        *pq = root;
        if (key < root->key)
            root = root->left;
        else {
            *pr = root; // 出現右拐點, 父節點 Q 的右孩子
            root = root->right;
        }
    }
    return root;
}

//
// stree_pred - 查找 root 前驅節點, 比 key 小的最大的數
// root     : tree 對象
// key      : 查找 node key
// return   : 返回查詢到節點
//
stree_t 
stree_pred(stree_t root, stree_key_t key) {
    if (root) {
        stree_t q = NULL, r = NULL;
        stree_t p = stree_get_right_p(root, key, &q, &r);
        if (!p)
            return NULL;
        // 有左子樹
        if (p->left)
            return stree_get_right(p->right);

        // 沒有前驅節點的情況
        if ((!p) || (p && !r))
            return NULL;

        // 沒有左子樹, 其父節點的右節點
        if (p == q->right)
            return q;
        // 沒有左子樹, 是其父節點的左節點
        return r;
    }
    return root;
}

/*
 後繼節點
    1. 若一個節點有右子樹,那麼該節點的後繼節點是其右子樹中 key 值最小的節點
    2. 若一個節點沒有右子樹,那麼判斷該節點和其父節點的關係 
        2.1 若該節點是其父節點的左孩子,那麼該節點的後繼結點即為其父節點 
        2.2 若該節點是其父節點的右孩子,那麼需要沿著其父親節點一直向樹的頂端尋找,
            直到找到一個節點P,P節點是其父節點Q的左孩子,那麼Q就是該節點的後繼節點
 */

static stree_t stree_get_left(stree_t root) {
    if (root) {
        while (root->left)
            root = root->left;
    }
    return root;
}

static stree_t stree_get_left_p(stree_t root, stree_key_t key, stree_t * pq, stree_t * pr) {
    while (root) {
        if (root->key == key)
            return root;

        *pq = root; // 設置父親節點
        if (root->key < key)
            root = root->right;
        else {
            *pr = root; // 出現左拐點, 父節點 Q 的左孩子
            root = root->left;
        }
    }
    return root;
}

//
// stree_succ - 查找 root 後繼節點, 比 key 大的最小的數
// root     : tree 對象
// key      : 查找 node key
// return   : 返回查詢到節點
//
stree_t 
stree_succ(stree_t root, stree_key_t key) {
    if (root) {
        stree_t q = NULL, r = NULL;
        stree_t p = stree_get_left_p(root, key, &q, &r);
        if (!p)
            return NULL;
        // 有右子樹
        if (p->right)
            return stree_get_right(p->right);

        // 沒有前驅節點的情況
        if ((!p) || (p && !r))
            return NULL;

        // 沒有右子樹, 其父節點的左節點
        if (p == q->left)
            return q;
        // 沒有右子樹, 是其父節點的右節點
        return r;
    }
    return root;
}

   這裡也簡單寫個測試例子, 保證核心邏輯插入和查找還有銷毀可以用. 

main.c

#include <stdio.h>
#include <stdlib.h>

#include "stree.h"

//
// Size Balanced Tree
//
int main(int argc, char * argv[]) {
    stree_t root = NULL;

    // 插入數據
    for (int i = 0; i < 9; i++)
        stree_insert(&root, i, (stree_value_t) { .i = i });
    
    int key = 6;
    stree_t node = stree_find(root, key);
    if (NULL == node) {
        fprintf(stderr, "stree_find is error key = %d\n", key);
        exit(EXIT_FAILURE);
    }
    printf("key = %d, node = %lld\n", key, node->value.i);
    
    stree_remove(&root, key);
    node = stree_find(root, key);
    if (node) {
        fprintf(stderr, "stree_find is error key = %d\n", key);
        exit(EXIT_FAILURE);
    }
    printf("stree_remove is success key = %d\n", key);

    stree_delete(&root);
    return 0;
}

論文 + C 代碼互相配合, 希望能對準備嘗試的人有些作用.  長舒一口氣.

拋開圖, 面試最難不過二叉樹 ~,~

開發中 tree set hash list vector 非常常見, 其中 tree 常被 hash 和 set 搶掉運用場景.  例如 id 管理.

但二叉樹是批量搜索業務的基石 (別說跳錶). 不管如何你都得嘗試躍遷過 ~-~

 

後記 - 開心就好

  錯誤是難免的, 歡迎交流指正, 互相提升 ~

  負け犬達のレクイエム - http://music.163.com/m/song?id=29751658&userid=16529894

 

: )

 故事的開頭,總是驚鴻一瞥,一眼千年,故事的結尾,總是瀟灑轉身,漸行漸遠

-:

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 說概率前複習下歷史函數create_rand_list() #創建一個含有指定數量元素的listsum_fun() #累加len_fun() #統計個數multiply_fun() #累乘sum_mean_fun() #算數平均數sum_mean_rate() #算數平均數計算回報median_fu ...
  • 一、為什麼要用synchronized關鍵字 首先多線程中多個線程運行面臨共用數據同步的問題。 多線程正常使用共用數據時需要經過以下步驟: 1.線程A從共用數據區中複製出數據副本,然後處理。 2.線程A將處理好的數據副本寫入共用數據區。 3.線程B從共用數據區中複製出數據副本。 如此迴圈,直到線程結 ...
  • 最近維護一個項目,裡面用到ClientDataSet,由於之前接觸ClientDataSet比較少,所以這個星期補了一下關於ClientDataSet的知識,併在此記錄下我所瞭解到的並應用到實際項目中的ClientDataSet的知識。 項目新需求:1.從別的資料庫導入物料資料,並允許操作員做修改後 ...
  • 在最近的秋招中,阿裡和多益網路都問到了這個問題,雖然很簡單,但是我還是想總結一下,感興趣的可以看一下我的 "個人博客網站(Spring+MyBatis+redis+nginx+mysql)" (適合菜鳥),最近會抽空把最近面試遇到的問題總結一下。 本文針對問題:深克隆和淺克隆的區別和實現方式?(阿裡 ...
  • 1、數據結構 從不同的角度,可以分為邏輯結構和物理結構 邏輯結構:是數據元素之間的相互關係 集合結構 線性結構 樹形結構 圖形結構 物理結構:數據的邏輯結構在電腦的存儲形式 順序存儲結構:數據間的邏輯關係和物理關係一致 鏈式存儲結構 2、演算法時間複雜度 時間複雜度T(n)=O(f(n));f(n) ...
  • 由於需要在公司的內網進行神經網路建模試驗(https://www.cnblogs.com/NosenLiu/articles/9463886.html),為了更方便的在內網環境下快速的查閱資料,構建深度學習模型,我決定使用爬蟲來對深度學習框架keras的使用手冊進行爬取。 keras中文文檔的地址是 ...
  • 編程零基礎如何學習Python 如果你是零基礎,註意是零基礎,想入門編程的話,我推薦你學Python。雖然國內基本上是以C語言作為入門教學,但在麻省理工等國外大學都是以Python作為編程入門教學的。 那麼如何學習Python呢? 第一步:先把刀磨好 俗話說得好,磨刀不誤砍柴工,這個你不得不信,反正 ...
  • 在具備了volatile、CAS和模板方法設計模式的知識之後,我們可以來深入學習下AbstractQueuedSynchronizer(AQS),本文主要想從AQS的產生背景、設計和結構、源代碼實現及AQS應用這4個方面來學習下AQS,文章耗時一個月,所以篇幅有點長,需要一點耐心。 1、AQS產生背 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...