After reading this article, I am no longer afraid of interview questions about trees

Basic knowledge is like the foundation of a building, it determines the height of our technology

In the interview, there are many questions about the tree, for example, what is the traversal order of the pre-in-post-order of the tree? Difficulties will make you write algorithm questions about trees, or some knowledge of trees will also be involved in the Java back-end interview, such as HashMapwhy the linked list generated by hash collision in China should be converted into a red-black tree under certain conditions ? , why use a red-black tree instead of a B+ tree? Why use B+ tree instead of other trees for the storage of indexes in Mysql and so on. In fact, we all use these things in the daily development process. In fact, every programmer is not willing to work every day CRUD, so these data structures are actually internal skills. We have learned how to analyze problems in daily work, what sets to choose, and how to sort them. , we will have more options in these questions.

what is a tree

A tree is an abstract data type (ADT) or a data structure that implements this abstract data type and is used to simulate a collection of data with a tree-like structure. It consists of n (n>0) finite nodes to form a set with hierarchical relationship. It's called a "tree" because it looks like an upside-down tree, which means it has the roots up and the leaves down.

There is no intuitive diagram for a complete definition. Let's take a look at the diagram first (here borrows the diagram of Mr. Wang Zheng, the author of data structure and algorithm, and sees the same style of diagram below).

We can see that the tree here is very similar to the tree in our real life, where each element is called a node, and the relationship connected by a straight line is called a parent-child relationship. So what can be called a tree?

  • Each node has only a limited number of children or no children
  • A node without a parent is called a root node
  • Every non-root node has one and only one parent node
  • Except for the root node, each child node can be divided into multiple disjoint subtrees
  • There are no loops in the tree

There are many classifications of trees, but we often use binary trees ( a tree with at most two subtrees per node is called a binary tree ). The following figure is actually a binary tree.

There are also classifications in the binary tree, in which ② in the above figure is a full binary tree ( except for leaf nodes, each node has two left and right child nodes ), the above figure ③ is a complete binary tree ( the number of nodes in other layers has reached the maximum value. , all the last nodes are arranged closely from left to right, such a binary tree is called a complete binary tree ), where the key points are arranged from left to right. So why is there a complete binary tree? Two storage methods of trees are involved here. One intuitive feeling is direct linked list storage. The Node node sets two elements of left and right child nodes. code show as below.

class TreeNode <T>{
    public T data;
    public TreeNode leftNode;
    public TreeNode rightNode;

    TreeNode(T data){
        this.data = data;
    }
}

Another way is to store based on array order, we put the tree from the root node into the index of the array subscript 1, and then put it from left to right.

serial number left node position right node position
1 2 3
2 4 5
3 6 7
…… …… ……
n 2n 2n-1

Here, if the root node is placed at index 0, then the left node is 2n+1, and the right node is 2n+2

According to the above table, we should be able to get that for each node n, its left node position is 2n, and its right node position is 2n+1. This way we can store the complete binary tree in the structure of the array. If the non-complete binary tree is also stored in an array, there will be many holes in the middle of the array, resulting in a waste of memory.

The advantage of using an array to store data is that there is no need to store pointers to left and right child nodes like a linked list, which saves space.

traversal of binary tree

We briefly understood the definition of tree above. Next, we will learn about binary tree traversal (pre-order traversal, in-order traversal, post-order traversal), which is also a point that is often asked in interviews.

  • Preorder traversal means that for any node in the tree, print the node first, then print its left subtree, and finally print its right subtree
  • In-order traversal means that for any node in the tree, first print its left subtree, then print itself, and finally print its right subtree
  • Post-order traversal means that for any node in the tree, first print its left subtree, then its right subtree, and finally print the node itself

Let's express it more intuitively in code.

/**
* @Description: 前序遍历
* @Param: [treeNode]
* @return: void
* @Author: hu_pf
* @Date: 2019/11/26
*/
private static void frontOrder(TreeNode<String> treeNode){
    if (treeNode == null){
        return;
    }
    System.out.printf(treeNode.data);
    frontOrder(treeNode.leftNode);
    frontOrder(treeNode.rightNode);
}

/**
* @Description: 中序遍历
* @Param: [treeNode]
* @return: void
* @Author: hu_pf
* @Date: 2019/11/26
*/
private static void middleOrder(TreeNode<String> treeNode){
    if (treeNode == null){
        return;
    }
    middleOrder(treeNode.leftNode);
    System.out.printf(treeNode.data);
    middleOrder(treeNode.rightNode);
}

/**
* @Description: 后序遍历
* @Param: [treeNode]
* @return: void
* @Author: hu_pf
* @Date: 2019/11/26
*/
private static void afterOrder(TreeNode<String> treeNode){
    if (treeNode == null){
        return;
    }
    afterOrder(treeNode.leftNode);
    afterOrder(treeNode.rightNode);
    System.out.printf(treeNode.data);
}

binary search tree

In a binary search tree, the value of each node is greater than the value of the left subtree node and less than the value of the right subtree node.

Binary search tree is a dynamic support for data addition, deletion, modification and search, and it is still naturally ordered. As long as we pass the in-order traversal, the data obtained is ordered data.

Insertion into binary search tree

We only need to start from the root node and compare the relationship between the data to be inserted and the node in turn. If the inserted data is larger than the current node, and the right node has no data, put it on the right node. If the right node has data, then traverse its right subtree again. The process is similar if the inserted data is smaller than the current data.

Expressed in code as follows.

private void insertTree(int data){
    if (this.rootNode == null){
        rootNode = new TreeNode(data);
        return;
    }
    TreeNode<Integer> p = rootNode;
    while (p!=null){
        Integer pData = p.data;
        if (data>=pData){
            if (p.rightNode == null){
                p.rightNode = new TreeNode(data);
                break;
            }
            p = p.rightNode;
        }else {
            if (p.leftNode == null){
                p.leftNode = new TreeNode(data);
                break;
            }
            p = p.leftNode;
        }
    }
}

Binary search tree search

The search of the binary tree is relatively simple. Compare the data to be found with the root node. If the searched data is smaller than it, search in the left subtree, and if the searched data is larger than it, search in the right subtree.

private TreeNode findTreeNode(int data){

    if (this.rootNode == null){
        return null;
    }

    TreeNode<Integer> p = rootNode;
    while (p != null){
        if (p.data == data) return p;
        if (data >= p.data) p = p.rightNode;
        else p = p.leftNode;
    }
    return null;
}

Deletion of a binary search tree

The deletion operation of a binary search tree is more complicated, and three situations need to be considered.

  • The deleted node has no child nodes: just delete it directly
  • The deleted node has only one node: the reference of the parent node can be replaced by its child node
  • The deleted node has two nodes: then we need to think about it, left child node data < parent node data < right child node data, if we delete the parent node at this time, then we need to find a node from the left node or the right node Move over to occupy this position, and keep the same size relationship after the move, so which one to move? Move the node with the largest left child, or the node with the smallest right child . Here we all need to savor it carefully. Why move like this.

To find the node with the smallest right child node, just find the right child node without the left node, which means that the node is the smallest. On the contrary, if you want to find the node with the largest left child node, just find the left child node without the right node. Node is the largest.

Next we look at the deleted code

private void deleteTreeNode(int data){

    if (this.rootNode == null ) return;
    
    TreeNode<Integer> treeNode = rootNode;
    TreeNode<Integer> treeNodeParent = null;
    while (treeNode != null){
        if (treeNode.data == data) break;
        treeNodeParent = treeNode;
        if (data >= treeNode.data) treeNode = treeNode.rightNode;
        else treeNode = treeNode.leftNode;
    }

    // 没有找到节点
    if (treeNode == null) return;

    TreeNode<Integer> childNode = null;
    // 1. 删除节点没有子节点
    if (treeNode.leftNode == null && treeNode.rightNode == null){
        childNode = null;
        if (treeNodeParent.leftNode == treeNode) treeNodeParent.leftNode = childNode;
        else treeNodeParent.rightNode = childNode;
        return;
    }

    // 2. 删除节点只有一个节点
    if ((treeNode.leftNode !=null && treeNode.rightNode==null)||(treeNode.leftNode ==null && treeNode.rightNode!=null)){
        // 如果此节点是左节点
        if (treeNode.leftNode !=null)  childNode = treeNode.leftNode;
        // 如果此节点是右节点
        else childNode = treeNode.rightNode;
        if (treeNodeParent.leftNode == treeNode) treeNodeParent.leftNode = childNode;
        else treeNodeParent.rightNode = childNode;
        return;
    }


    // 3. 删除的节点有两个子节点都有,这里我们演示的是找到右子节点最小的节点
    if (treeNode.leftNode !=null && treeNode.rightNode!=null){
        TreeNode<Integer> minNode = treeNode.rightNode;
        TreeNode<Integer> minNodeParent = treeNode;
        while (minNode.leftNode!=null){
            minNodeParent = minNode;
            minNode = minNode.leftNode;
        }
        treeNode.data = minNode.data;
        if (minNodeParent.rightNode != minNode) minNodeParent.leftNode = minNode.rightNode;
        else minNodeParent.rightNode = minNode.rightNode;
    }
}

The deleted code is more complicated, and there is a simple way to do it here, which is to mark it for deletion. In this way, there is no need to move the data, and you can judge whether to delete it when inserting the data. But it will take up more memory.

Balanced binary tree - red-black tree

A balanced binary tree has the following properties: it is an empty tree or the absolute value of the height difference between its left and right subtrees does not exceed 1, and both left and right subtrees are a balanced binary tree.

What we talked about above is to search for a binary tree, then the search for a binary tree may become a linked list in some extreme cases, so the efficiency of the search will become lower, and the balanced binary tree is to solve this problem. The tree looks relatively uniform, with roughly the same number of left and right child nodes. This will not turn into a linked list in extreme cases. Then usually the balanced binary tree we commonly use is the red- black tree .

I will not explain the code implemented by the red-black tree here, because it is too troublesome. I will briefly talk about its application scenario in the Java backend. The structure of Java is HashMapactually an array plus a linked list structure. If there are too many linked lists in a slot, it will also affect the performance. Therefore, when the length of the linked list is 8, it will be automatically converted to a red-black tree to increase query performance. Next, I will tell you about the points that red-black trees are often asked in interviews based on my own interview.

  • HashMapWhen will the linked list be converted to a red-black tree? The case where the length of the linked list is 8
  • Under what circumstances will the red-black tree be converted to a linked list? When the node of the red-black tree is 6
  • Why is a linked list converted to a red-black tree? Answer the linked list query performance is not good
  • Why not use another tree? The key point is to balance the tree, just answer the advantages of the red-black tree
  • Why not use a B+tree? Key points, disk, memory. Red-black trees are mostly used for internal sorting, that is, they are all placed in memory. When the B+ tree is mostly used in external memory, the B+ tree is also called a disk-friendly data structure

Under normal circumstances, the interviewer will not let you write red-black trees or the like during the interview. I probably encountered the above few in the interview. It is enough to answer the advantages and application scenarios of red-black trees.

Sorting of trees

It refers to a sorting algorithm designed using the data structure of the heap. A heap is a structure that approximates a complete binary tree and satisfies the property of stacking at the same time: that is, the key value or index of a child node is always less than (or greater than) its parent node.

In fact, some sorting algorithms are often encountered in the interview, such as heap sorting or some applications of heap sorting, such as the previous TopN data. In fact, the structure of the heap is also a tree. A heap is a complete binary tree with the following properties: the value of each node is greater than or equal to the value of its left and right child nodes, which is called a big top heap; or the value of each node is less than or equal to the value of its left and right child nodes value, called the small top heap.

The 1st and 2nd are the big top heap, the 3rd is the small top heap, and the 4th is not the heap. Since the heap is essentially a complete binary tree, then we can use arrays to store heap data. Then if the current node is n, its left child node is 2n, and its right child node is 2n+1.


class Heap{
    // 存放堆中的数据,用数组来装
    private int [] a;

    // 堆中能存放的最大数
    private int n;

    // 堆中已经存放的数量
    private int count;

    public Heap(int capacity){
        a = new int [capacity + 1];
        this.n = capacity;
        count = 0;
    }
}

How to insert data in heap

Next, how do we dynamically insert data into the heap?

For example, we insert element 22 in the heap in the above figure, so what should we do to keep it still a heap? At this time, the big top heap we demonstrated in the figure above, then we must ensure the conceptual requirements of the big top heap, and the value of each node is greater than or equal to the value of its left and right child nodes. Then we put it into the last position of the array, and then compare it with its parent node (n/2 is his parent node), if it is greater than its parent node, swap the position with the parent node, and continue with its parent node The comparison stops until it is less than its parent. The code is implemented as follows.

public void insert(int data){
    if (count >= n) return;

    count++;
    // 把数据放到数组中
    a[count] = data;

    int index = count;
    // 开始进行比较,先判断跳出条件
    // 1. 首先能想到的是插入的数据满足了大(小)顶堆的数据要求
    // 2. 加上极值条件,及是一颗空树情况
    while (index/2>0 && a[index]>a[index/2]){
        swap(a,index,index/2);
        index = index/2;
    }
}

private void swap(int [] a, int i , int j){
    int swap = a[i];
    a[i] = a[j];
    a[j] = swap;
}

How to remove the top element of the stack

We reach the maximum or minimum value until the root node value of the top heap size. Then if the top element of the heap is deleted, then the largest or smallest one from the left and right child nodes should be selected and put into the root node, and so on. Then if we move according to what we just analyzed, it may generate holes in the array.

We can remove the root node, then move the last value of the array to the root node, and then compare and change positions in turn, so that there will be no array holes.

code show as below

public void removeMax(){

    if (count == 0) return;

    // 将最后的元素移动到堆顶
    a[1] = a[count];

    count--;

    heapify(a,count,1);
}

private void heapify(int [] a,int n,int i){
    // 定义什么时候结束,当前节点与其左右子节点进行比对,如果当前节点是最大的则跳出循环(代表当前节点已经是最大的)
    while (true){
        int maxIndex = i;
        // 找到三个节点中最大节点的索引值
        if (2*i<= n && a[i]<a[2*i]) maxIndex = 2*i; // 判断当前节点,是否小于左节点
        if (2*i+1<= n && a[maxIndex]<a[2*i+1]) maxIndex = 2*i+1;// 判断最大节点是否小于右节点
        // 如果当前节点已经是最大节点就停止交换并停止循环
        if (maxIndex == i )break;
        // 找到中最大值的位置,并交换位置
        swap(a,i,maxIndex);
        i = maxIndex;
    }
}

heap sort

How do we use the heap to sort an unordered array passed in? So since the heap is used for sorting, our first step must be to turn this array into a heap structure first.

build heap

How to turn an unordered array into a heap structure? The first one we can easily think of is to call our insert method above in sequence, but the memory used will be doubled, that is, we will create a new memory to store the heap. So what if we want to turn the original array into a heap without adding new memory?

The array here is not a heap, so how to turn it into a heap? Here we use the method of building the heap from the bottom up. What does it mean? In fact, it is the idea of ​​recursion. We first build the heap below, and then pass it up in turn. We first heap the tree labeled 4, then the one labeled 3, then the one labeled 2, and then the one labeled 1. proceed in sequence. By the end we have a heap. The circles in the figure below represent the extent of its heaped tree. code show as below

public void buildHeap(int[] a,int n){
    for (int i =n/2;i>=1;i--){
        heapify(a,n,i);
    }
}
    
private void heapify(int [] a,int n,int i){
// 定义什么时候结束,当前节点与其左右子节点进行比对,如果当前节点是最大的则跳出循环(代表当前节点已经是最大的)
while (true){
    int maxIndex = i;
    // 找到三个节点中最大节点的索引值
    if (2*i<= n && a[i]<a[2*i]) maxIndex = 2*i; // 判断当前节点,是否小于左节点
    if (2*i+1<= n && a[maxIndex]<a[2*i+1]) maxIndex = 2*i+1;// 判断最大节点是否小于右节点
    // 如果当前节点已经是最大节点就停止交换并停止循环
    if (maxIndex == i )break;
    // 找到中最大值的位置,并交换位置
    swap(a,i,maxIndex);
    i = maxIndex;
}
}

sort

Sorting is simple, because the root node of the heap is the largest or smallest element, so the first one we can still think of is to directly take out the value of the root node and put it in another array, and then delete the top element of the heap , then remove the top element of the heap, then remove it, and so on. This has a disadvantage of occupying memory, because we redefine an array, so is there a way to directly sort in the original array without occupying memory?

The second way is not to use another array, how to do it? The advantage of this process is similar to deleting the top element of the heap. The difference is that we directly discard the top element of the heap to delete the top element of the heap. For sorting, we need to exchange the top element and the last element of the heap. Heap, then swap the top element of the heap with the previous element of the last element, then heap again, and so on.

How to find the top N data

I believe everyone uses Weibo, so how are the top ten hot searches displayed in real time? In fact, the data used to find the top N are basically heaps. We can build a small top heap with a size of N. If it is static data, then take elements from the array and the top elements of the small top heap in turn. In contrast, if it is larger than the top element of the heap, then discard the top element of the heap, then put the elements in the array into the top of the heap, and then heap. In order, the final result is the data of TopN. If it is dynamic data, it is the same as static data, and it is also constantly compared. Finally, if you want data with a large TopN, you can directly return this heap.

Next, I will use the animation to demonstrate how to obtain the TopN data.

The array is: 1 2 3 9 6 5 4 3 2 10 15 Find Top5

Code address of this article

refer to

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324173426&siteId=291194637