二叉树的代码定义和基本遍历算法

二叉树

顺序存储

从根结点开始,自上至下,自左至右从 1 开始编号,编号 k 存放在数组下标 k-1 的位置。

需要注意的是:即便是从数组下标0开始存储,也是满足完全二叉树的性质的(王道中描述片面),只不过从编号到下标需要先按性质再-1,从下标到编号需要先+1再按性质。当然,编程的时候还是从 1 开始比较好,减少编译后的代码量。

Ps:对于高度为h的完全二叉树,叶结点可以在第h-1和h层,故可分别就该点讨论最大/小总结点数。

链式存储

结构体定义

typedef struct btNode{ // binary tree node
	int val;
    struct btNode *lchild;
    struct btNode *rchild;
}btNode;

// 一个更简明的声明
typedef struct node btNode;
struct node{
	int val;
    btNode *lchild;
    btNode *rchild;
};

基本操作

btNode *createBTree(void)
{
	btNode *r = (btNode *)malloc(sizeof(btNode));
    r->val = 0;
    r->lchild = NULL;
    r->rchild = NULL;
    return r;
}

先序遍历(根左右)

递归
int* preorder(struct TreeNode* root, int* returnSize, int *ret);

int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
    int *ret = (int *)malloc(sizeof(int) * 100);
    *returnSize = 0;

    return preorder(root, returnSize, ret);
}

int* preorder(struct TreeNode* root, int* returnSize, int *ret)
{
    if (root){
        ret[*returnSize] = root->val;
        (*returnSize)++;
        preorder(root->left, returnSize, ret);
        preorder(root->right, returnSize, ret);
    }
    return ret;
}


非递归

思路:以递归思路做锚点,为了描述方便,分为队列q和栈s两种数据结构

  • 若 p 非空,则入队列记录数据,然后入栈保存当前结点信息
    • 令 p = p->left,重新循环
  • 若 p 为空,则按照递归思路,需要获得 p 的父结点后访问 p->right
    • p = pop(s), p = p->right
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */

#define MAXSIZE 100

typedef struct Stack Stack;

struct Stack{
    int top;
    struct TreeNode *array[MAXSIZE];
};

void push(Stack *s, struct TreeNode *node);
struct TreeNode *pop(Stack *s);

int* preorderTraversal(struct TreeNode* root, int* returnSize){
    Stack *s = (Stack *)malloc(sizeof(Stack));
    s->top = -1;

    struct TreeNode *p = root;
    int *ret = (int *)malloc(sizeof(int) * 100);
    int i = 0;

    while(s->top != -1 || p){
        if (p){
            ret[i++] = p->val;
            push(s, p);
            p = p->left;
        }else{
            p = pop(s);
            p = p->right;
        }
    }
    *returnSize = i;
    return ret;
}

void push(Stack *s, struct TreeNode *node){
    s->array[++s->top] = node;
}

struct TreeNode *pop(Stack *s){
    return s->array[s->top--];
}

随笔:关于栈和递归

栈可以模拟递归,所以有了递归代码就可以写非递归代码。

很早之前有个粗略的想法:若只有一个递归调用,那么在调用递归之前都可以看成循环,直到满足终止条件,之后的代码按顺序执行即可。(当时并没有理解两个递归在一起的情况,或者说有点畏难情绪,就没有深入思考)

综合一下现在的想法,关于连续的两个不返回参数的简单递归调用(中间没有代码):

循环判定的条件有两个,只要满足一个就循环:一是非终止条件,另一个是信息栈非空。这应该是很自然的,如果栈空,则代表此时位于最开始的递归,如果此时终止条件被满足,那么就可以结束。栈不空,代表可以跳回上次递归,没有满足终止条件,代表可以到下一次递归。

当不满足终止条件时,将当前所用的信息 m 压栈,根据第一个递归的传参(不妨称为next1(m)),令 m = next1(m),然后进入下一个循环,如果此时满足终止条件,那么令 m = pop(s),m = next2(m)。因为当前是不满足终止条件的,这代表此次调用结束,需要回到上次调用,但是当前的 m 并非上次调用的信息,所以需要出栈。

如果两个调用附近有代码,可以根据以下原则插入:

  • 两个调用前:在 m = next1(m) 之前
  • 两个调用中:在 m = pop(s) 后,m = next2(m) 前
  • 两个调用后:放在循环后(待验证)

层次遍历

#define MAXSIZE 100

typedef struct Queue Queue;
struct Queue{
    int front;
    int rear;
    int capacity;
    struct TreeNode *array[MAXSIZE];
};

Queue *createQueue(int queueSize);
int isEmpty(Queue *q);
int enQueue(Queue *q, struct TreeNode *node);
int deQueue(Queue *q, struct TreeNode **node);

void levelOrder(struct TreeNode* root){
    struct TreeNode *p;
    Queue *q = createQueue(MAXSIZE);
    enQueue(q, root);
    
    while(!isEmpty(q)){
        deQueue(q, &p);
        /* 这里插入访问操作 */
        if (p->left)
            enQueue(q, p->left);
        if (p->right)
            enQueue(q, p->right);
    }
}

/* 创建空队列 */
Queue *createQueue(int queueSize){
    Queue *q = (Queue *)malloc(sizeof(Queue));
    q->capacity = queueSize;
    q->front = 0;
    q->rear = 0;
    return q;
}

/* 判空 */
int isEmpty(Queue *q){
    return q->front == q->rear;
}

/* 入队 */
int enQueue(Queue *q, struct TreeNode *node){
    if ((q->rear+1) % q->capacity == q->front)
        return 1;
    q->array[q->rear] = node;
    q->rear = (q->rear+1) % q->capacity;
    return 0;
}

/* 出队 */
int deQueue(Queue *q, struct TreeNode **node){
    if (q->front == q->rear)
        return 1;
    *node = q->array[q->front];
    q->front = (q->front+1) % q->capacity;
    return 0;
}

构造二叉树

已知先序序列 Pre 和中序序列 In

初始思路:

  1. 看 Pre[0] 在 In[] 中的左右序列,优先处理左序列,因为两个序列中,左的相对位置都是在右之前。
    • 若左序列非空,那么 Pre[1] 就是根结点的左孩子,
    • 若左序列为空,右序列非空,那么 Pre[1] 就是根结点的右孩子
    • 否则孩子结点为 NULL
TreeNode *preInCreat(int *preorder, int preorderSize, int *inorder, int inorderSize)
{
	int *root = (TreeNode *)malloc(sizeof(TreeNode));
    root->val = preorder[0];
    
    if (preorderSize > 1)
}

二叉排序树(二叉查找树)

就是在中序遍历的情况下,结点的val升序排列,按递归定义描述就是,左子树所有的值 < 根结点的值 < 右子树所有的值。

一个序列符合二叉排序树的查找路径:第 i 项之后的所有项满足相同的大/小于关系。

插入
  1. 递归代码
struct TreeNode* insertIntoBST(struct TreeNode* root, int val){
    if (root == NULL){
        root = (struct TreeNode *)malloc(sizeof(struct TreeNode));
        root->val = val;
        root->left = NULL;
        root->right = NULL;
    }
    if (val < root->val)
        root->left = insertIntoBST(root->left, val);
    else if (val > root->val)
        root->right = insertIntoBST(root->right, val);
    // else: 相等不需要插入

    return root;
}
  1. 非递归代码
struct TreeNode* insertIntoBST(struct TreeNode* root, int val){
    struct TreeNode *p, *q, *node; // q 在循环后指向 p 的父节点
    
    node = (struct TreeNode *)malloc(sizeof(struct TreeNode));
    node->val = val;
    node->left = node->right = NULL;
    if (!root){
        root = node;
    }else{
        p = root;
        while (p){
            q = p;
            if (val < p->val)
                p = p->left;
            else if (val > p->val)
                p = p->right;
            else
                break;
        }
        if (val < q->val)
            q->left = node;
        else if (val > q->val)
            q->right = node;
    }
    return root;
}

删除

删除当前结点分为三种情况:

  1. 当前结点为叶子结点:直接删除
  2. 当前结点度为1,删除该结点,并使指针指向其孩子结点
  3. 当前结点度为2,令当前结点的值为直接前驱的值(左子树中的最右结点:左子树中最大的值)或直接后继的值(右子树的最左结点:右子树中最小的值),然后删除直接前驱/后继对应的结点,注意,若该结点上还有孩子结点,需要令父节点指向它。
// TODO: 优化
int findRMinDel(struct TreeNode *root); // 返回右子树最左的值,并删除对应结点

struct TreeNode* deleteNode(struct TreeNode* root, int key){
    if (!root){
        return root;
    }
    if (key < root->val){
        root->left = deleteNode(root->left, key);
    }else if(key > root->val){
        root->right = deleteNode(root->right, key);
    }else{
        if (!root->left && !root->right){
            free(root);
            return NULL;
        }else if(root->left && root->right){
            root->val = findRMinDel(root);
        }else{
            return (root->left != NULL) ? root->left : root->right;
        }
    }
    return root;
}

int findRMinDel(struct TreeNode *root){
    struct TreeNode *p, *q; // p初始指向右子树,后续指向子树中的最小结点,q始终为p的父结点
    int min;

    q = root;
    p = root->right;
    min = p->val;
    if (!p->left){ // 若无左子树,则当前结点即为最小
        q->right = p->right;
        free(p);
    }else{
        while (p->left){
            q = p;
            p = p->left;
            min = p->val;
        }
        q->left = p->right;
        free(p);
    }
    return min;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hoper.J

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值