Data Structures and Algorithms Chapter 6 [Tree]

Related definitions of tree

Depth (height) of the tree: the maximum level at which leaf nodes are located in the tree

Forest: m m mA collection of mutually disjoint trees

Binary tree

A binary tree is either an empty tree or consists of a root node plus two non-intersecting binary trees called the left subtree and the right subtree.

nature

  • Forked Tree, Part i i iThere are many on the top 2 i − 1 2^{i-1} 2i1's result ( i ≥ 1 i≥1i1这个性质比较好理解,因为第i层是由i-1层引出的,再以此递归到根结点就很好理解
  • depth k k The binary tree of k contains at most 2 k − 1 2^k-1 2k1 End point ( k ≥ 1 k≥1 k1这个也很好理解,先把树想象成一棵满二叉树,因为根结点那层只有一个结点,所以减一即可
  • For any binary tree, if it contains n 0 n_0 n0leaf nodes, n 2 n_2 n2 Preference 2 2 2, then there must be a relationship: n 0 = n 2 + 1 n_0 = n_2+1 n0=n2+1
  • The depth of a complete binary tree with n nodes is l o g 2 n + 1 log_2n+1 log2n+1 用第2条性质很好理解
  • containing n n For a complete binary tree with n nodes, we can number each node in order from top to bottom and from left to right, from 1 1 1 to n n n. For any number i i i point:
    1. Result i = 1 i=1 i=1 , then the node is the root node of the entire binary tree, and it has no parent node; otherwise, its parent node number is ⌊ i / 2 ⌋ \lfloor i/2 \rfloor i/2
    2. Result 2 i > n 2i>n 2i>n,な么编名为 i i The node of i has no left child. Otherwise, the number of its left child is 2 i 2i 2i
    3. Result 2 i + 1 > n 2i+1>n 2i+1>n,な么编名为 i i The node of i has no right child, otherwise, its right child number is 2 i + 1 2i+1 2i+1

Complete binary tree:

The complete binary tree is a special kind of binary tree. It differs from the ordinary binary tree in its number of levels, and for the i i i layer, if the nodes of this layer are not filled, all its nodes must be concentrated on the left contiguous position. That is to say, in the complete binary tree, except for the last layer, the number of nodes in other layers has reached the maximum value, and the nodes in the last layer are concentrated in the leftmost positions of the layer.

storage structure

sequential storage

#define MAX_TREE_SIZE 100      
typedef TElemType SqBiTree[MAX_TREE_SIZE];   
// 0号单元存储根结点

SqBiTree bt;

Binary linked list

// 定义二叉树结点和指向二叉树结点的指针类型
typedef struct BiTNode {
    
    
    TElemType data;             // 数据域
    struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;

Schematic diagram

lchild data rchild

Three-pronged linked list

// 定义三叉链表结点和指向三叉链表结点的指针类型
typedef struct TriTNode {
    
    
    TElemType data;             // 数据域
    struct TriTNode *lchild, *rchild; // 左右孩子指针
    struct TriTNode *parent;    // 双亲指针
} TriTNode, *TriTree;

Root node p a r e n t parent parent为NULL

parent lchild data rchild

Binary tree traversal

  • preorder traversal根左右
  • inorder traversal左根右
  • Postorder traversal左右根

The preorder traversal algorithm is relatively simple, so I won’t go into it.

In-order traversal:

Status InOrderTraverse(BiTree T, Status (*Visit)(TElemType e)) {
    
    
    InitStack(S);
    Push(S, T); // 根指针进栈
    while (!StackEmpty(S)) {
    
    
        while (GetTop(S, p) && p) {
    
    
            Push(S, p->lchild); // 向左走到尽头
        }
        Pop(S, p); // 空指针退栈
        if (!StackEmpty(S)) {
    
     // 访问结点,向右一步
            Pop(S, p); 
            if (!Visit(p->data)) {
    
    
                return ERROR;
            }
            Push(S, p->rchild);
        }
    }
    return OK;
}
//采用非递归的方式,中序遍历以T为根指针的二叉树
Status InOrderTraverse(BiTree T, Status (*Visit)(TElemType e)){
    
    
    SqStack S;  //定义一个栈 S 来存储节点。
    InitStack(&S);  //初始化栈
    
    BiTree p = T;  //p为遍历指针,初始时指向根结点
    
    while (p || !StackEmpty(S)){
    
       //p非空或者栈不为空
        if (p){
    
       //如果p非空
            Push(&S, p);   //节点入栈
            p = p->lchild;   //遍历左子树
        }
        else{
    
      //如果p为空
            Pop(&S, &p);   //取出栈顶元素
            if (!Visit(p->data)) return ERROR;  //访问栈顶元素
            p = p->rchild;   //遍历右子树
        }   
    }
    return OK;
}

Create a binary tree

//按照前序遍历方式,创建一棵二叉树,并返回 OK
Status CreateBiTree(BiTree &T) {
    
    
    char ch;  //定义一个字符类型的变量 ch 用于输入二叉树结点的值
    scanf("%c", &ch);  //从用户输入中读取下一个字符,即当前结点的值
    
    if (ch == ' ') T = NULL;  //如果输入的是空格,则表示该结点为空结点
    else {
    
       //否则,生成一个新的二叉树结点,并赋值为 ch
        if (!(T = (BiTNode *)malloc(sizeof(BiTNode))))
            exit(OVERFLOW);  //若生成结点失败,则退出程序
        T->data = ch;  //为当前结点赋值
        CreateBiTree(T->lchild);  //递归调用 CreateBiTree 函数构造左子树
        CreateBiTree(T->rchild);  //递归调用 CreateBiTree 函数构造右子树
    }
    return OK;  //返回 OK 表示创建成功
}

Construct a binary tree from pre-order and in-order traversal:

Insert image description here

Constructed from post-order and in-order:

Insert image description here

clue binary tree

The clue binary tree is a data structure that adds clue information based on the binary tree. In addition to the pointers pointing to the left and right subtrees, each node of the clue binary tree also has two special pointers, called the predecessor clue pointer and the successor clue pointer, which are used to record the location of the node in Predecessor and successor nodes in in-order traversal.

In a clued binary tree, if a node does not have a left subtree, point its left subtree pointer to the node's predecessor node in inorder traversal; if a node does not have a right subtree, point its right subtree to The pointer points to the node's successor in inorder traversal.当然并非一定中序遍历,只是书上的内容多是中序遍历

The threading process is the process of converting a binary tree into a threaded binary tree. There are two main methods of clueing:

  • In-order traversal threading: For a given binary tree, in the node sequence obtained by in-order traversal, each node has a unique predecessor and successor. By modifying the pointers in the binary tree to make it a linear structure while retaining the original in-order traversal sequence, the clue binary tree of the binary tree can be obtained.
  • Pre-order traversal threaded: similar to in-order traversal, except that the order of traversal is different.

The main advantage of the clued binary tree is that it can speed up the traversal operation of the binary tree and save storage space at the same time. The disadvantage is that the threading process requires additional time overhead and increases the complexity of the code.

typedef enum {
    
     Link, Thread } PointerThr;  // Link==0:指针,Thread==1:线索

typedef struct BiThrNode {
    
    
    TElemType         data;      // 结点数据
    struct BiThrNode *lchild;    // 左子树指针
    struct BiThrNode *rchild;    // 右子树指针
    PointerThr        LTag;      // 左标志
    PointerThr        RTag;      // 右标志
} BiThrNode, *BiThrTree;

Traversal algorithm

void InOrderTraverse_Thr(BiThrTree T, void (*Visit)(TElemType e)) {
    
    
    BiThrTree p = T->lchild;   // p指向根结点的左子树
    while (p != T) {
    
               // 空树或遍历结束时,p==T
        while (p->LTag == Link) p = p->lchild;  // 如果左标志为指针,则p进至其左子树的最左下结点
        if (!Visit(p->data)) return;            // 访问该结点,如果访问失败则返回错误
        while (p->RTag == Thread && p->rchild != T) {
    
    
            p = p->rchild;  Visit(p->data);      // 循环访问后继结点,直到遇到右标志为指针的结点
        }
        p = p->rchild;          // 如果右标志为指针,则p进至其右子树的根结点
    }
}

Take a closer look. I didn’t understand it when I first saw it.

Create a clue list

Status InOrderThreading(BiThrTree &Thrt, BiThrTree T) {
    
    
    if (!T) {
    
      // 如果二叉树为空,则创建头结点并将左右指针都指向自身
        if (!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) {
    
    
            exit(OVERFLOW);
        }
        Thrt->LTag = Link;
        Thrt->RTag = Thread;
        Thrt->lchild = Thrt;
        Thrt->rchild = Thrt;
        return OK;
    }

    // 否则,创建头结点,并将左指针指向根节点,右指针指向尾节点
    if (!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) {
    
    
        exit(OVERFLOW);
    }
    Thrt->LTag = Link;
    Thrt->RTag = Thread;
    Thrt->rchild = Thrt;

    pre = Thrt;  // 初始化前驱节点,方便遍历时标记前驱线索

    Thrt->lchild = T;  // 左指针指向根节点
    InThreading(T);  // 中序遍历二叉树并进行线索化

    pre->rchild = Thrt;  // 将最后一个节点的右子树指针指向头结点
    pre->RTag = Thread;
    Thrt->rchild = pre;  // 将头结点的右指针指向最后一个节点

    return OK;
}

You don’t need to read the code part in detail, the main thing isThe left subtree of the head node is the root node, the right node points to the tail node, and the right subtree of the last node points to the head node Dot

First determine whether the input binary tree is empty. If it is empty, create a head node and point both left and right pointers to itself. Otherwise, wecreate a head node, and point its left pointer to the root node and its right pointer to the tail. Node.

Next, we initialize the predecessor node pre and point it to the head node so that the predecessor clues can be easily marked when traversing the binary tree. Then, we point the left pointer of the head node to the root node and call the function InThreading to perform in-order threading on the binary tree.

After the InThreading function returns, we need to find the last node and point its right subtree pointer to the head node. We use the variable pre to save the last node currently traversed, and update it continuously in the loop until the last node is traversed. Finally, we point the right subtree pointer of the last node to the head node, and point the right pointer of the head node to the last node.

forest and trees

Notation

parent representation
Insert image description here

Child linked list representation
Insert image description here
Compared with parent representation, there is one more domain space for storing children

Binary linked list (child-sibling) representation of a tree
Insert image description here

Left child - right brother

Conversion between binary tree and forest

Insert image description here

Traverse

Tree traversal:

Insert image description here
Note post-root traversal - similar to post-order traversal
Level traversal - BFS

Forest traversal:
Insert image description here

  1. If the forest is not empty, it can be traversed according to the following rules: (pre-order traversal)
    (1) Visit the root node of the first tree in the forest; a>
    (2) Pre-order traversal of the sub-tree forest of the first tree in the forest;
    (3) Pre-order traversal of the forest (except the first tree) The rest of the trees make up the forest.

  2. If the forest is not empty, it can be traversed according to the following rules: (mid-order traversal)
    (1) Mid-order traversal of the sub-tree forest of the first tree in the forest;
    (2) Visit the root node of the first tree in the forest;
    (3) In-order traversal of the forest (except the first tree) The rest of the trees make up the forest.

Insert image description here
但是树的后根遍历不是对应中序遍历
Post-root traversal:
Insert image description here

tree storage structure

typedef struct CSNode {
    
    
    Elem data;
    struct CSNode *left, *right;  // 左儿子和右兄弟指针
} CSNode, *CSTree;

Left child - right brother

Common application algorithms

Find the depth of the tree

int TreeDepth(CSTree T) {
    
    
    if (!T) {
    
    
        return 0;
    } else {
    
    
        int h1 = TreeDepth(T->left);     // 递归求左子树深度
        int h2 = TreeDepth(T->right);    // 递归求右子树深度
        return (h1 > h2 ? h1 : h2) + 1;  // 返回深度较大的子树深度加1
    }
} // TreeDepth

Find the path from the root to all leaves

void AllPath(Bitree T, Stack &S) {
    
    
    if (T) {
    
    
        Push(S, T->data);
        if (!T->left && !T->right) {
    
    
            PrintStack(S);
        } else {
    
    
            AllPath(T->left, S);
            AllPath(T->right, S);
        }
        Pop(S);
    }
} // AllPath

Huffman

The path length of the node:

Insert image description here

The path length of the tree: the sum of the path lengths from the root of the tree to each node

The weighted path length of a node: the product of the path length from the node to the root of the tree and the weight on the node.

The weighted path length of the tree: the sum of the weighted path lengths of all leaf nodes in the tree. Denoted as: WPL(T) = ∑wklk ( for all leaf nodes).

An algorithm for constructing optimal binary trees-huffman algorithm

The implementation process of Huffman algorithm is as follows:

  1. Calculate the frequency of each character appearing in the text;
  2. Construct a binary tree using all characters and their frequencies as leaf nodes, where the weight value of a node is the frequency of the character represented by the node;
  3. For this binary tree, select the two nodes with the smallest weights as the left and right subtrees, add their weights and use them as the weight of the new node;
  4. Use this new node as the root node of the subtree and insert it into the binary tree at the location of the original two selected nodes;
  5. Repeat steps 3 and 4 until the entire tree becomes a node, which is the root node of the Huffman tree;
  6. For each character, starting from the root node, if the character is in the left subtree, output 0, otherwise output 1, until the corresponding leaf node is found;
  7. Concatenating the encoding strings of all characters is the compressed data.

huffman structure

typedef struct Node {
    
    
    int data;           // 节点存储的值
    unsigned int freq;  // 节点的频率(权重)
    struct Node *left;  // 左子节点
    struct Node *right; // 右子节点
} HTNode, *HuffmanTree;

huffman coding

The coding part can be solved with better understood recursion

void generateHuffmanCodes(HuffmanNode* root, string code) {
    
    
	if (!root) return;
	if (root->left == nullptr && root->right == nullptr) codes[root->ch] = code;
	generateHuffmanCodes(root->left, code + "0"); 
	generateHuffmanCodes(root->right, code + "1"); 
}

Use min-heap to build huffman tree (better to understand)

Node* buildHuffmanTree(int data[], unsigned int freq[], int size) {
    
    
    Node *left, *right, *top;
    // 创建一个用于存储节点的最小堆
    MinHeap* minHeap = createMinHeap(size);
    // 将所有数据和频率构建为节点,并插入到最小堆中
    for (int i = 0; i < size; ++i) {
    
    
        insertMinHeap(minHeap, createNode(data[i], freq[i]));
    }
    // 从最小堆中取出两个具有最小频率的节点,并构建Huffman树
    while (!isSizeOne(minHeap)) {
    
    
        // 从最小堆中取出频率最小的两个节点
        left = extractMin(minHeap);
        right = extractMin(minHeap);
        // 创建一个新的节点作为它们的父节点,并设置频率为左右子节点的频率之和
        top = createNode('$', left->freq + right->freq);
        // 将左右子节点分别作为新节点的左右子节点
        top->left = left;
        top->right = right;
        // 将新节点插入到最小堆中
        insertMinHeap(minHeap, top);
    }
    // Huffman树的根节点即为最后剩下的节点
    return extractMin(minHeap);
}

exercise

Build clue tree

Insert image description here
Insert image description here
Caution G G G 目前驱し N U L L NULL NULL (back order)

Determine the tree based on in-order traversal and post-order traversal

Insert image description here
Insert image description here
If you have time, you can write this question. I wrote it wrong the first time I wrote it.

resemblance

If two binary trees are known B 1 B1 B1 B 2 B2 B2 Everyone is empty, or everyone is empty and B 1 B1 B1 Mito left, right child Sowa B 2 B2 If the left and right subtrees of B2 are similar respectively, it is called a binary tree B 1 B1 < /span>B1 B 2 B2 B2 is similar. Try writing an algorithm to determine whether two given binary trees are similar.
Insert image description here
It’s relatively simple, but you might as well take a look

Stack implements post-order traversal

Write a non-recursive algorithm for post-order traversal (hint: in order to distinguish the different return points of two pushes on the stack during post-order traversal, a flag needs to be pushed onto the stack at the same time when the pointer is pushed onto the stack).

Insert image description here
look carefully

Level traversal

Write an algorithm that traverses the Eryou tree in hierarchical order (from left to right at the same level).

Insert image description here

Determine complete binary tree

bool full(tree t) {
    
    
    init_queue(q);
    int flag = 0;
    enqueue(q, t);
    while (!queue_empty(q)) {
    
    
        dequeue(q.p);
        if (!p) {
    
    
            flag = 1;
        } else if (flag) {
    
    
            return 0;
        } else {
    
    
            enqueue(q, p->lchild);
            enqueue(q, p->rchild);
        }
    }
    return 1;
}

Find the depth of the child-brother chain

typedef struct TreeNode * Tree;
int depth(Tree root) {
    
    
    if (!root) {
    
    
        return 0; // 如果节点为空,深度为 0
    }
    int maxDepth = 0;
    for (Tree p = root->firstChild; p; p = p->rightSibling) {
    
    
        int d = depth(p); // 递归计算子节点的深度
        if (d > maxDepth) {
    
    
            maxDepth = d; // 记录最大深度
        }
    }
    return maxDepth + 1; // 当前节点的深度等于最深子节点的深度加上 1
}

Tree p = root->firstChild; p; p = p->rightSibling
You can take a good look at this piece of code

typedef struct TreeNode* tree;
int depth(tree root) {
    
    
     if (!root) {
    
    
         return 0;
     } else {
    
    
         return max(depth(root->firstChild) + 1, depth(root->rightSibling));
     }
}

I wrote it later, and it feels more concise.

Construct a binary tree (binary linked list) based on the preorder sequence and the inorder sequence

Tree buildTree(int preorder[], int inorder[], int n) {
    
    
    if (n == 0) {
    
    
        return NULL; // 当序列为空时,返回空指针
    }
    // 在前序序列中找到根节点
    int root_val = preorder[0];
    int i;
    for (i = 0; i < n; i++) {
    
    
        if (inorder[i] == root_val) {
    
    
            break;
        }
    }
    // 根据中序序列划分左右子树
    int left_size = i;
    int right_size = n - i - 1;
    // 递归构建左右子树
    Tree root = (Tree) malloc(sizeof(struct TreeNode));
    root->val = root_val;
    root->left = buildTree(preorder + 1, inorder, left_size);
    root->right = buildTree(preorder + 1 + left_size, inorder + 1 + left_size, right_size);
    return root;
}

It can be solved by recursion

Guess you like

Origin blog.csdn.net/qq_61786525/article/details/131070520