树与二叉树

一.二叉树的实现

首先,是二叉树节点的结构定义:

typedef struct Node {
    int key;
    int ltag, rtag; // 1 : thread, 0 : edge
    struct Node *lchild, *rchild;
} Node;

它包括节点存储的值,指向左右子树的指针。

二叉树相关结构操作:

封装一个树节点:

Node *getNewNode(int key) {
    Node *p = (Node *)malloc(sizeof(Node));
    p->key = key;
    p->ltag = p->rtag = 0;
    p->lchild = p->rchild = NULL;
    return p;
}

清除树结构:

void clear(Node *root) {
    if (root == NULL) return ;
    if (root->ltag == 0) clear(root->lchild);
    if (root->rtag == 0) clear(root->rchild);
    free(root);
    return ;
}

树节点的插入:

Node *insert(Node *root, int key) {
    if (root == NULL) return getNewNode(key);
    if (rand() % 2) root->lchild = insert(root->lchild, key);
    else root->rchild = insert(root->rchild, key);
    return root;
}

二.二叉树的遍历

前序遍历:

void pre_order(Node *root) {
    if (root == NULL) return ;
    printf("%d ", root->key);
    if (root->ltag == 0) pre_order(root->lchild);
    if (root->rtag == 0) pre_order(root->rchild);
    return ;
}

中序遍历:

void in_order(Node *root) {
    if (root == NULL) return ;
    if (root->ltag == 0) in_order(root->lchild);
    printf("%d ", root->key);
    if (root->rtag == 0) in_order(root->rchild);
    return ;
}

后序遍历:

void post_order(Node *root) {
    if (root == NULL) return ;
    if (root->ltag == 0) post_order(root->lchild);
    if (root->rtag == 0) post_order(root->rchild);
    printf("%d ", root->key);
    return ;
}

线索化:将二叉树变为链表结构来进行遍历,具体步骤是将叶子结点的左指针指向该节点的在指定遍历方式下的前驱,右指针指向后继。

这里展示在中序遍历的线索化:

Node *pre_node = NULL, *inorder_root = NULL;
void __build_inorder_thread(Node *root) {
    if (root == NULL) return ;
    if (root->ltag == 0) __build_inorder_thread(root->lchild);
    if (inorder_root == NULL) inorder_root = root;
    if (root->lchild == NULL) {
        root->lchild = pre_node;
        root->ltag = 1;
    }
    if (pre_node && pre_node->rchild == NULL) {
        pre_node->rchild = root;
        pre_node->rtag = 1;
    }
    pre_node = root;
    if (root->rtag == 0) __build_inorder_thread(root->rchild);
    return ;
}
 
void build_inorder_thread(Node *root) {
    __build_inorder_thread(root);
    pre_node->rchild = NULL;
    pre_node->rtag = 1;
    return ;
}

其中rtag和ltag代表该节点所连边为树的边还是线索化时所连的边。

在实现线索化后,其遍历就可这样实现:

Node *getNext(Node *root) {
    if (root->rtag == 1) return root->rchild;
    root = root->rchild;
    while (root->ltag == 0 && root->lchild) root = root->lchild;
    return root;
}
 
int main() {
    srand(time(0));
    Node *root = NULL;
    #define MAX_NODE 10
    for (int i = 0; i < MAX_NODE; i++) {
        root = insert(root, rand() % 100);
    }
    pre_node = inorder_root = NULL;
    build_inorder_thread(root);
 
    pre_order(root); printf("\n");
    in_order(root);   printf("\n");
    post_order(root); printf("\n");
 
    // like linklist
    Node *node = inorder_root;
    while (node) {
        printf("%d ", node->key);
        node = getNext(node);
    }
    printf("\n");
    clear(root);
    return 0;
}

三.二叉树的序列化

关于序列化的概念这里不加解释,直接来看序列化的相关实现。

首先就是将树序列化:

char str[1000];
int len = 0;
void serialize(node *root){
    if(root == NULL) return ;
    len += sprintf(str + len, "%d", root -> key);

    if(root -> left == NULL && root -> right == NULL) return ;
    len += sprintf(str + len, "(");
    serialize(root -> left);
    if(root -> right) {
        len += sprintf(str + len, ",");
        serialize(root -> right);
    }
    len += sprintf(str + len, ")");
    return ;
}
void __serialize(node *root){
    memset(str, 0, sizeof(str));
    len = 0;
    serialize(root);
    return ;
}

整体递归实现即可。

接着是对树的序列进行去序列化:

node *de_serialize(char *str, int len){
    node **stack = (node**)malloc(sizeof(node*) * 10);
    int top = -1, scode = 0, flag = 0;
    node *p = NULL, *root = NULL;
    for(int i = 0; str[i]; i++){
        switch(scode){
            case 0:{
                if(str[i] >= '0' && str[i] <= '9') scode = 1;
                else if(str[i] == '(') scode = 2;
                else if(str[i] == ',') scode = 3;
                else scode = 4;
                i--;
            } break;
            case 1:{
                int num = 0; 
                while(str[i] >= '0' && str[i] <= '9') {
                    num = num * 10 + (str[i] - '0');
                    i++;
                }
                p = get_new_node(num);
                if(top >= 0 && flag == 0) stack[top] -> left = p;
                if(top >= 0 && flag == 1) stack[top] -> right = p;
                i--;
                scode = 0;
            } break;
            case 2:{
                stack[++top] = p;
                flag = 0;
                scode = 0;
            } break;
            case 3:{ 
                flag = 1; 
                scode = 0;
            } break;
            case 4:{
                root = stack[top];
                top--;
                scode = 0;
            } break;
        }
    }
    return root;
}

可以看出去序列化的问题,是一个完全包含问题,所以用栈实现即可。

四.哈夫曼编码

对于这种最优编码方式,可以通过构造哈夫曼树的方法实现:

先构造哈夫曼树:

int find_min_node(node **arr, int n){
    int index = 0;
    for(int j = 1; j <= n; j++){
        if(arr[index] -> f > arr[j] -> f) index = j;
    }
    return index;
}
node *build_haffmantree(node **arr, int n){
    for(int i = 1; i < n; i++){
        //找两最小值,并放于数组末两位
        int index1 = find_min_node(arr, n - i);
        swap(arr, index1, n - i);
        int index2 = find_min_node(arr, n - i - 1);
        swap(arr, index2, n - i - 1);
        
        //合并两节点,并将新节点放于倒数第二位
        node *p = get_new_node(arr[n - i] -> f + arr[n - i - 1] -> f, 0);
        p -> left = arr[n - i - 1];
        p -> right = arr[n - i];
        arr[n - i - 1] = p;
    }
    return arr[0];
}

在通过哈夫曼树读取每个字符的编码:

void haffman_code(node *root, char str[], int k){
    str[k] = 0;
    if(root -> left == NULL && root -> right == NULL) {
        printf("%c : %s\n", root -> a, str);
        return ;
    }

    str[k] = '0';
    haffman_code(root -> left, str, k + 1);
    str[k] = '1';
    haffman_code(root -> right, str, k + 1);
    return ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值