【算法笔记】- 树和二叉树

《算法笔记》- 第9章整理


| 二叉树

注意区分二叉树和度为2的树的区别。二叉树是严格区分左右子树的。

(一)存储结构与基本操作
//定义
struct node {
    typename data;
    node* lchild;
    node* rchild;
};

//生成一个新节点
node* newNode(int data) { //data为结点权值
    node* Node = new node;
    Node->data = data;
    Node->lchild = Node->rchild = NULL;
    return Node;
}

//查找 & 修改
void search(node* root, int x, int newData) {

    if(root == NULL) return;
    if(root->data == x) root->data = newData; //找到,修改

    search(root->lchild, x, newData);
    search(root->rchild, x, newData);
}

//插入
void insert(node* &root, int x) { //注根节点用引用
    if(root == NULL) { //空树,查找失败,插入位置
        root = newNode(x);
        return;
    }
    //递归插入
    if(左子树) insert(root->lchild, x);
    else    insert(root->rchild, x);
}

//构造二叉树
node* create(int data[], int n) {
    node* root = NULL;
    for(int i = 0; i < n; i++) //逐个插入
        insert(root, data[i]);
    return root;
}

附注1:判断是够需要加引用

如果函数需要新建结点,则加;若只是遍历或修改已有结点的呢绒,则不加;

(二)完全二叉树

数组存储:

  • 1、根节点必须在1号位 (0号位左孩子错误);
  • 2、对于完全二叉树中的任一结点(编号X),其左孩子编号一定是2X,右孩子编号一定是2X+1
  • 3、数组中元素存放的位置正好是层序遍历系列;
  • 4、判断某个结点是够为叶节点:若其左孩子结点编号2X > n(结点总数) 即为叶节点
(三)遍历
  • 1、注意层序遍历时队列中的元素是node*而非node,因为队列中保存的知识原元素的一个副本(修改无法成功)
  • 2、要求计算每个结点所处层次时,在二叉树结构体中添加一个结构体变量即可
(四)二叉树静态存储

申请一个大小为结点上限个数的node型数组即可!

struct node {
    int data;
    int lchild;
    int rchild;
}Node[maxn];  //结点数组

//新节点
int index = 0;
int newNode(int data) { //静态指定即可
    Node[index].data = data;
    Node[index].lchild = -1; //以-1或maxn表示空
    Node[index].rchild = -1;
    return index;
}

//查找 & 修改
void search(int root, int x, int newData) {
    if(root == -1) return;
    if(x == Node[root].data) //找到, 修改
        Node[root].data = newData; 
    //递归查找 
    search(Node[root].lchild, x, newData);
    search(Node[root].rchild, x, newData);
}

//插入
void insert(int &root, int x) { //记得加引用
    if(root == -1) { //空树,查找失败,插入位置
        root = newNode(x);
        return
    }
    //递归
    if(左子树) insert(Node[root].lchild, x);
    else   insert(Node[root].rchild, x);
}

//二叉树建立
int create(int data[], int n) {
    int root = -1;
    for(int i = 0; i < n; i++)
        insert(root, data[i]);
    return root;
}

| 树

既可以用静态存储,也可以用指针操作;更推荐静态存储方法

(一)静态存储
struct node{
    int data;
    int layer; //记录层号
    vector<int> child; //存储子节点的下标【直接用数组只能开到maxn大小,必然空间溢出】
}Node[maxn];

//新建结点 (同二叉树)
int index = 0;
int newNode(int data) {
    Node[index].child.clear(); //清空子节点
    return index++;
}

//先根遍历
void pre_order(int root) {
    printf("%d ", Node[root].child[i]);//此处访问

    for(int i = 0; i < Node[root].child.size(); i++)//访问子节点
        pre_order(Node[root].child[i]);
}

//层序遍历 【加入对称号的求解:结构体node中增加layer变量】
void layer_order(int root) {

    queue<int> Q; //存放数组下标
    Q.push(root);
    Node[root].layer = 0; //根节点层号为0

    while(!Q.empty()) {
        int front = Q.front(); //获取队首元素
        Q.pop();
        printf("%d", Node[front].data);  //此处访问
        //子节点入队列
        for(int i = 0; i < Node[front].child.size(); i++) {
            int child = Node[front].child[i];
            Node[child].layer = Node[front].layer + 1; //更新子节点层号
            Q.push(child);
        }
    }
}

| 二叉查找树(BST)

注:对BST进行中序遍历,遍历结果是有序的。

//定义(与一般二叉树一致)
struct node{
    int data;
    node* lchild;
    node* rchild;
};

//生成新结点(与一般二叉树一致)
node* newNode(int data) {
    node* root = new node;
    root->data = data;
    root->lchild = root->rchild = NULL;
    return root;
}

//查找
void search(node* root, int x) {
    if(root == NULL) return; //查找失败 
    if(x == root->data) printf("%d", root->data); //查找成功
    else if(x < root->data) 
        search(root->lchild, x); //左子树搜索
    else   
        search(root->rchild, x); //右子树搜索
}

//插入 【就是在查找失败处插入即可】
void insert(node* &root, int x) {  //记得加引用
    if(root == NULL) { //查找失败,插入
        root = newNode(x);
        return; 
    }
    if(x == root->data) printf("%d", root->data); //查找成功
    else if(x < root->data) 
        insert(root->lchild, x); //左子树搜索
    else   
        insert(root->rchild, x); //右子树搜索
}

//构造BST (与一般二叉树完全一样)
node* create(int data[], int n) {
    node* root = NULL;
    for(int i = 0; i < n; i++) //逐个插入
        insert(root, data[i]);
    return root;
}

//寻找最大(最小)结点
node* findMax(node* root) { //用于寻找前驱
    while(root->rchild) 
        root = root->rchild;  //不断向右,直到没有右孩子
    return root; 
}
node* findMin(node* root) { //用于寻找后继
    while(root->lchild) 
        root = root->lchild; //不断向左,直到没有左孩子
    return root;
}

//删除以root为根结点的树中权值为x的结点
//递归实现【当然也可以优化为非递归】
void delete_node(node* &root, int x) { //记得加引用
    if(root == NULL) return; //不存在权值为x的结点
    if(x == root->data) { //找到欲删除结点

        if(root->lchild == NULL && root->rchild) //叶节点
            root = NULL;
        else if(root->lchild){ //左子树非空
            node* pre = findMax(root->lchild); //找root前驱pre
            root->data = pre->data;     //用前驱覆盖root
            delete_node(root->lchild, pre->data); //在左子树中删除pre(递归)
        } else { //右子树非空
            node* next = findMin(root->rchild);
            root->data = next->data;
            delete_node(root->rchild, next->data);
        }

    } else if(x < root->data)
        delete_node(root->lchild, x);
    else
        delete_node(root->rchild, x);

}

优化删除操作 – 非递归

  • 思路:
    1、(前提)在结点定义中额外记录每个结点父结点地址
    2、在找到欲删除结点root的后继结点next后,假设next的父结点是S,显然结点nextS的左孩子(因为findMin()),那么由于next一定没有左子树,便可直接把next的右子树代替为next成为S的左子树;
    3、(前驱同理)在找到欲删除结点root的前驱结点pre后,假设pre的父结点是S,显然preS的右孩子(因为findMax()),那么由于pre一定没有右子树,便可直接把pre的左子树代替pre成为S的右子树

  • 代码实现
    只需在原有基础上改变两句,但是每个结点的父结点信息需要在建树时赋值。

//删除以root为根结点的树中权值为x的结点
void delete_node(node* &root, int x) { //记得加引用
    if(root == NULL) return; //不存在权值为x的结点
    if(x == root->data) { //找到欲删除结点

        if(root->lchild == NULL && root->rchild) //叶节点
            root = NULL;
        else if(root->lchild){ //左子树非空
            node* pre = findMax(root->lchild); //找root前驱pre
            root->data = pre->data;     //用前驱覆盖root
            pre->farther->rchild = pre->lchild; //优化递归
        } else { //右子树非空
            node* next = findMin(root->rchild);
            root->data = next->data;
            next->farther->lchild = next->rchild; //优化递归
        }

    } else if(x < root->data)
        delete_node(root->lchild, x);
    else
        delete_node(root->rchild, x);

}

| 平衡二叉树(AVL)

(一)、定义

1、AVL树是一棵BST
2、对AVL树的每一个结点,其左子树与右子树高度之差的绝对值不超过1
其中,平衡因子 = 左子树高度 - 右子树高数

AVL树作为BST树的一种,当每次删除或插入结点后,调整树的结构,使树的高度始终保持在O(logn)的级别,这样查询操作的时间复杂度仍然是O(logn)

//定义(增加树高)
struct node{
    int data, height; //权值,树高
    node* lchild;
    node* rchild;
};

//生成新节点
node* new_node(int data) {
    node* root = new node;
    root->data = data;
    root->height = 1; //树高初始为1
    root->lchild = root->rchild = NULL;
    return root;
}

//获取树高
int get_height(node* root){
    if(root == NULL) return 0; //空树
    return root->height;
}

//计算结点root的平衡因子
int get_balance_factor(node* root) {
    return get_height(root->lchild) - get_height(root->rchild); //左高 - 右高
}

//更新结点root的高度
void update_height(node* root){ //左高与右高的较大者 + 1
    root->height = max(get_height(root->lchild), get_height(root->rchild)) + 1;
}
(二)基本操作(查找、插入、建立)
//删除操作太过复杂,只讨论查找、插入、建立!

//查找(AVL就是BST,与BST完全相同)
void search(node* root, int x) {
    if(root == NULL) return; //查找失败 
    if(x == root->data) printf("%d", root->data); //查找成功
    else if(x < root->data) 
        search(root->lchild, x); //左子树搜索
    else   
        search(root->rchild, x); //右子树搜索
}

//左旋
void L(node* &root) { //记得加引用
    node* temp = root->rchild;
    root->rchild = temp->lchild;
    temp->lchild = root;
    update_height(root);  //两句更新顺序不能反
    update_height(temp);
    root = temp;
}

//右旋(与左旋为互逆操作)
void R(node* &root) { //记得加引用
    node* temp = root->lchild;
    root->lchild = temp->rchild;
    temp->rchild = root;
    update_height(root); //更新顺序不能反
    update_height(temp);
    root = temp;
}

//插入 【就是在查找失败处插入即可】(在BST的基础上加入调整)
//先插入,等返回后,判断结点是否失衡,若有则根据失衡类型进行旋转
//整个过程呈现为从下到上的回溯
void insert(node* &root, int x) {  //记得加引用
    //查找失败,插入(判断失衡和旋转调整在上一层函数)
    if(root == NULL) { 
        root = new_node(x);
        return;
    }
    //插入左侧
    if(x < root->data) {

        insert(root->lchild, x); //先插入;等返回后,先更新高度,再检查

        update_height(root);
        //检查是否有失衡结点
        if(get_balance_factor(root) == 2) { //插入左侧只可能 左高 > 右高
            if(get_balance_factor(root->lchild) == 1) { //LL型
                R(root); //右旋即可
            } else if(get_balance_factor(root->lchild) == -1) { //LR型
                L(root->lchild); //先左旋后右旋
                R(root);
            }
        }
    }
    //插入右侧
    else {

        insert(root->rchild, x); //先插入等返回后检查

        update_height(root);
        //检查是否有失衡结点
        if(get_balance_factor(root) == -2) { //插入左侧只可能 左高 < 右高
            if(get_balance_factor(root->rchild) == -1){ //RR型
                L(root); //左旋
            } else if(get_balance_factor(root->rchild) == -1) { //RL型
                R(root->rchild); //先右旋在左旋
                L(root);
            }
        }
    }//else

}//insert

//构造AVL (与一般二叉树完全一样)
node* create(int data[], int n) {
    node* root = NULL;
    for(int i = 0; i < n; i++)
        insert(root, data[i]);

    return root;
}


| 并查集


|


| 哈夫曼树


*
*
*
* 整理自《算法笔记》!!


猜你喜欢

转载自blog.csdn.net/qq_26398495/article/details/81667195