基于树的存储数据结构demo

一.简介

由于之前博主尝试Java重构redis,在redis中的的字典数据结构底层也是采用数组实现,字典中存在两个hash表,一个是用于存储数据,另一个被用于rehash扩容为前者两倍。但是我注意到了在redis的数据结构中,并没有像Java集合类中的hashmap一样的树化机制,即当链表数大于8,且通过线性搜索法查找后续hash槽,发现占用了64个了,就会将拉链上的链表转化为红黑树,涉及到自平衡等等操作。是比较麻烦的,于是博主尝试使用二叉搜索树来实现一个基础的树的存储数据的数据结构。

二.准备代码

先定义两个接口,里面是我们准备实现的一些必须的方法,

IMember 接口

这个接口的实现类是该存储数据结构中的每个对象。

java 复制代码
package partA;

/**
 *
 * an objects of a class implementing this interface holds a
 * database of member information

 * DO NOT CHANGE THIS INTERFACE
 * You must create a class that implements this interface
 *
 */

public interface IMemberDB {

    /**
     * Empties the database.
     * @pre true
     */
    public void clearDB();

    /**
     * Determines whether a member's name exists as a key inside the database
     * @pre name is not null and not empty string
     * @param name the member name (key) to locate
     * @return true if the name exists as a key in the database
     */
    public boolean containsName(String name);

    /**
     * Returns a Member object mapped to the supplied name.
     * @pre name not null and not empty string
     * @param name The Member name (key) to locate
     * @return the Member object mapped to the key name if the name
    exists as key in the database, otherwise null
     */
    public Member get(String name);

    /**
     * Returns the number of members in the database
     * @pre true
     * @return number of members in the database.
     */
    public int size();

    /**
     * Determines if the database is empty or not.
     * @pre true
     * @return true iff the database is empty
     */
    public boolean isEmpty();

    /**
     * Inserts a Member object into the database, with the key of the supplied
     * member's name.
     * Note: If the name already exists as a key, then the original entry
     * is overwritten.
     * This method must return the previous associated value
     * if one exists, otherwise null
     *
     * @pre member not null and member name not empty string
     */
    public Member put(Member member);

    /**
     * Removes and returns a member from the database, with the key
     * the supplied name.
     * @param name The name (key) to remove.
     * @pre name not null and name not empty string
     * @return the removed member object mapped to the name, or null if
     * the name does not exist.
     */
    public Member remove(String name);

    /**
     * Prints the names and affiliations of all the members in the database in
     * alphabetic order.
     * @pre true
     */
    public void displayDB();
}

IMemberDB 接口

该接口就是定义一些 crud方法

java 复制代码
package partA;

/**
 *
 * an objects of a class implementing this interface holds a
 * database of member information

 * DO NOT CHANGE THIS INTERFACE
 * You must create a class that implements this interface
 *
 */

public interface IMemberDB {

    /**
     * Empties the database.
     * @pre true
     */
    public void clearDB();

    /**
     * Determines whether a member's name exists as a key inside the database
     * @pre name is not null and not empty string
     * @param name the member name (key) to locate
     * @return true if the name exists as a key in the database
     */
    public boolean containsName(String name);

    /**
     * Returns a Member object mapped to the supplied name.
     * @pre name not null and not empty string
     * @param name The Member name (key) to locate
     * @return the Member object mapped to the key name if the name
    exists as key in the database, otherwise null
     */
    public Member get(String name);

    /**
     * Returns the number of members in the database
     * @pre true
     * @return number of members in the database.
     */
    public int size();

    /**
     * Determines if the database is empty or not.
     * @pre true
     * @return true iff the database is empty
     */
    public boolean isEmpty();

    /**
     * Inserts a Member object into the database, with the key of the supplied
     * member's name.
     * Note: If the name already exists as a key, then the original entry
     * is overwritten.
     * This method must return the previous associated value
     * if one exists, otherwise null
     *
     * @pre member not null and member name not empty string
     */
    public Member put(Member member);

    /**
     * Removes and returns a member from the database, with the key
     * the supplied name.
     * @param name The name (key) to remove.
     * @pre name not null and name not empty string
     * @return the removed member object mapped to the name, or null if
     * the name does not exist.
     */
    public Member remove(String name);

    /**
     * Prints the names and affiliations of all the members in the database in
     * alphabetic order.
     * @pre true
     */
    public void displayDB();
}

三.实现类

java 复制代码
package partA;

import java.util.Objects;

public class Member implements IMember{
    String fullName;
    String affiliation;

    public Member(String name, String affiliation){
        this.fullName = name;
        this.affiliation = affiliation;
    }
    public String getName() {
        return fullName;
    }
    public String getAffiliation() {
        return affiliation;
    }

    public void setAffiliation(String affiliation) {
        this.affiliation = affiliation;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Member member = (Member) o;
        return Objects.equals(fullName, member.fullName) && Objects.equals(affiliation, member.affiliation);
    }

    @Override
    public int hashCode() {
        return Objects.hash(fullName, affiliation);
    }
}
java 复制代码
package partA;

import java.util.ArrayList;
import java.util.List;
public class MemberBST implements IMemberDB{

    private Node root;

    private static class Node {
        Member member;
        String name;
        Node left, right;

        Node(Member member) {
            this.member = member;
            this.name = member.getName();
            left = right = null;
        }
    }

    // constructor
    public MemberBST() {
        System.out.println("Binary Search Tree");
        this.root = null;
    }

    @Override
    public void clearDB() {
        root = null;
    }

    @Override
    public boolean containsName(String name) {
        return recursionTree(root, name);
    }

    // if two string is same,compare() method will return 0
    private boolean recursionTree(Node node, String name) {
        if (node == null) return false;
        if (node.name.compareTo(name) == 0) return true;

        return recursionTree(node.left, name) || recursionTree(node.right, name);
    }

    @Override
    public Member get(String name) {
        List<String> sequence = new ArrayList<>();
        Member result = recursionGetter(root, name, sequence);
        if (result != null) {
            System.out.println("the sequence of nodes of the tree visited: " + sequence);
        }
        return result;
    }

    private Member recursionGetter(Node node, String name, List<String> sequence) {
        if (node == null){
            return null;
        }
        sequence.add(node.member.getName()); // Add the node to the sequence

        if (node.name.compareTo(name) == 0) {
            System.out.println("the Member name: " + node.member.getName());
            return node.member;
        }

        // recursion
        Member leftResult = recursionGetter(node.left, name, sequence);
        if (leftResult != null) {
            return leftResult;
        }
        return recursionGetter(node.right, name, sequence);
    }



    @Override
    public int size() {
        int cnt = 0;
        return calculator(root, cnt);
    }

    private int calculator(Node node, int cnt) {
        if (node == null) return cnt;
        cnt++;

        cnt = calculator(node.left, cnt);
        cnt = calculator(node.right, cnt);

        return cnt;
    }

    @Override
    public boolean isEmpty() {
        return root == null;
    }

    @Override
    public Member put(Member member) {
        if ((member == null) || member.getName().isEmpty()) {
            throw new IllegalArgumentException("member and " +
                    "member's name can not be null");
        }
        // exist?
        Member meb = get(member.getName());
        if (meb != null) {
            meb.setAffiliation(member.affiliation);
            System.out.println("the Member name: " + meb.getName());
            return meb;
        }
        if (isEmpty()) {
            root = new Node(member);
            System.out.println("Visited node: " + root.member.getName());
        } else {
            recursionAdder(root, member);
        }
        return member;
    }

    private void recursionAdder(Node node, Member member) {
        int gap = member.getName().compareTo(node.name);
        if (gap < 0) {
            // The new member's name comes before the current node's name, go left
            if (node.left == null) {
                node.left = new Node(member);
                System.out.println("Visited node: " + node.member.getName());
            } else {
                recursionAdder(node.left, member);
            }
        } else if (gap > 0) {
            // The new member's name comes after the current node's name, go right
            if (node.right == null) {
                node.right = new Node(member);
                System.out.println("Visited node: " + node.member.getName());
            } else {
                recursionAdder(node.right, member);
            }
        }
    }

    @Override
    public Member remove(String name) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name cannot be null or an empty string.");
        }
        MemberWrapper removedMemberWrapper = new MemberWrapper();
        root = removeRecursive(root, name, removedMemberWrapper);
        System.out.println("Removed member: " + removedMemberWrapper.member.getName());
        return removedMemberWrapper.member;
    }

    private Node removeRecursive(Node node, String name, MemberWrapper removedMemberWrapper) {
        if (node == null) {
            return null;
        }

        int cmp = name.compareTo(node.name);
        if (cmp < 0) {
            System.out.println("Visited node: " + node.member.getName());
            node.left = removeRecursive(node.left, name, removedMemberWrapper);
        } else if (cmp > 0) {
            System.out.println("Visited node: " + node.member.getName());
            node.right = removeRecursive(node.right, name, removedMemberWrapper);
        } else {
            // Found the node to remove
            System.out.println("Removing node: " + node.member.getName());
            removedMemberWrapper.member = node.member;
            if (node.left == null) {
                return node.right;
            } else if (node.right == null) {
                return node.left;
            }

            // Node has two children, find the inorder successor
            node.member = findMin(node.right).member;
            node.name = findMin(node.right).name;
            node.right = removeRecursive(node.right, node.name, null);
        }
        return node;
    }

    private Node findMin(Node node) {
        while (node.left != null) {
            System.out.println("Visited node: " + node.member.getName());
            node = node.left;
        }
        return node;
    }


    @Override
    public void displayDB() {
        disRecDB(root);
    }

    private void disRecDB(Node node) {
        if (node == null) return;
        String name = node.name;
        String affiliation = node.member.affiliation;
        System.out.println("fullName:\t" + name + "\t" + affiliation);
        disRecDB(node.left);
        disRecDB(node.right);
    }
}

包装类:用于值传递

java 复制代码
package partA;

public class MemberWrapper {
    Member member;
}

四.解释及测试类

APIs

First we need to build the inner class of the node and define the root node.

java 复制代码
private Node root;

private static class Node {
    Member member;
    String name;
    Node left, right;

    Node(Member member) {
        this.member = member;
        this.name = member.getName();
        left = right = null;
    }
}

And then in the constructor, it says "Binary Search Tree"

java 复制代码
// constructor

public MemberBST() {

    System.out.println("Binary Search Tree");

    this.root = null;

}

Next we will implement all the apis defined in the MemberBST class inheritance interface:

  1. clearDB()
java 复制代码
public void clearDB() {

    root = null;

}

Emptying a tree simply requires setting its root node to 0, and the jvm's gc automatically frees up memory.

  1. containsName()
java 复制代码
public boolean containsName(String name) {

    return recursionTree(root, name);

}



// if two string is same,compare() method will return 0

private boolean recursionTree(Node node, String name) {

    if (node == null) return false;

    if (node.name.compareTo(name) == 0) return true;



    return recursionTree(node.left, name) || recursionTree(node.right, name);

}

This is done using a simple depth-first traversal in the middle order traversal.

  1. get()
java 复制代码
@Override

public Member get(String name) {

    List<String> sequence = new ArrayList<>();

    Member result = recursionGetter(root, name, sequence);

    if (result != null) {

        System.out.println("the sequence of nodes of the tree visited: " + sequence);

    }

    return result;

}



private Member recursionGetter(Node node, String name, List<String> sequence) {

    if (node == null){

        return null;

    }

    sequence.add(node.member.getName()); // Add the node to the sequence



    if (node.name.compareTo(name) == 0) {

        System.out.println("the Member name: " + node.member.getName());

        return node.member;

    }



    // recursion

    Member leftResult = recursionGetter(node.left, name, sequence);

    if (leftResult != null) {

        return leftResult;

    }

    return recursionGetter(node.right, name, sequence);

}

Since I need to return the order of access in task3, I create a variable-length array to store the locations of the accessed nodes and add them to the dynamic array whenever a node is accessed.

We also use recursion to implement the depth-first algorithm's mid-order pass to get the member of the specified name. Comparing two strings we use the compareTo() method, which converts the string to a char array and then determines whether the two strings are equal by adding the asc code. If 0 is equal, 1 means that the former is greater, and 2 means that the latter is greater.

  1. size()
java 复制代码
public int size() {

    int cnt = 0;

    return calculator(root, cnt);

}



private int calculator(Node node, int cnt) {

    if (node == null) return cnt;

    cnt++;



    cnt = calculator(node.left, cnt);

    cnt = calculator(node.right, cnt);



    return cnt;

}

dfs is also implemented using recursion. By defining a cnt counter to record the number of nodes and objects.

java 复制代码
isEmpty()
@Override

public boolean isEmpty() {

    return root == null;

}
Very simple no introduction.



put()
public Member put(Member member) {

    if ((member == null) || member.getName().isEmpty()) {

        throw new IllegalArgumentException("member and " +

                "member's name can not be null");

    }

    // exist?

    Member meb = get(member.getName());

    if (meb != null) {

        meb.setAffiliation(member.affiliation);

        System.out.println("the Member name: " + meb.getName());

        return meb;

    }

    if (isEmpty()) {

        root = new Node(member);

        System.out.println("Visited node: " + root.member.getName());

    } else {

        recursionAdder(root, member);

    }

    return member;

}



private void recursionAdder(Node node, Member member) {

    int gap = member.getName().compareTo(node.name);

    if (gap < 0) {

        // The new member's name comes before the current node's name, go left

        if (node.left == null) {

            node.left = new Node(member);

            System.out.println("Visited node: " + node.member.getName());

        } else {

            recursionAdder(node.left, member);

        }

    } else if (gap > 0) {

        // The new member's name comes after the current node's name, go right

        if (node.right == null) {

            node.right = new Node(member);

            System.out.println("Visited node: " + node.member.getName());

        } else {

            recursionAdder(node.right, member);

        }

    }

}
First we need to determine if a member of that name exists in the tree. Now add setAffiliation() to the Member class. Because we need to modify the operation, we need to get the member object so we can use the GET method here. If it exists, call setAffiliation() and modify the Affiliation corresponding to name.

If it is empty, create a new node to store the new member.

If it does not exist, we call the auxiliary method recursionAdder() to determine whether a member is placed in the left or right subtree by compartTo's gap.



desplyDB()
public void displayDB() {

    disRecDB(root);

}



private void disRecDB(Node node) {

    if (node == null) return;

    String name = node.name;

    String affiliation = node.member.affiliation;

    System.out.println("fullName:\t" + name + "\t" + affiliation);

    disRecDB(node.left);

    disRecDB(node.right);

}
Recursive depth traversal, just print.



remove()
public Member remove(String name) {

    if (name == null || name.isEmpty()) {

        throw new IllegalArgumentException("Name cannot be null or an empty string.");

    }

    MemberWrapper removedMemberWrapper = new MemberWrapper();

    root = removeRecursive(root, name, removedMemberWrapper);

    System.out.println("Removed member: " + removedMemberWrapper.member.getName());

    return removedMemberWrapper.member;

}



private Node removeRecursive(Node node, String name, MemberWrapper removedMemberWrapper) {

    if (node == null) {

        return null;

    }



    int cmp = name.compareTo(node.name);

    if (cmp < 0) {

        System.out.println("Visited node: " + node.member.getName());

        node.left = removeRecursive(node.left, name, removedMemberWrapper);

    } else if (cmp > 0) {

        System.out.println("Visited node: " + node.member.getName());

        node.right = removeRecursive(node.right, name, removedMemberWrapper);

    } else {

        // Found the node to remove

        System.out.println("Removing node: " + node.member.getName());

        removedMemberWrapper.member = node.member;

        if (node.left == null) {

            return node.right;

        } else if (node.right == null) {

            return node.left;

        }



        // Node has two children, find the inorder successor

        node.member = findMin(node.right).member;

        node.name = findMin(node.right).name;

        node.right = removeRecursive(node.right, node.name, null);

    }

    return node;

}



private Node findMin(Node node) {

    while (node.left != null) {

        System.out.println("Visited node: " + node.member.getName());

        node = node.left;

    }

    return node;

}

To implement value passing, we first create a wrapper class to hold the deleted member:

复制代码
public class MemberWrapper {

    Member member;

}

The member with the specified name is then recursively found and deleted. If the current node is empty, it indicates that the subtree has been traversed and the target member is not found, and null is returned. If the node has no right subtree, simply return its left subtree as the new subtree (or null if there is no left subtree). Compare the name of the current node member with the name of the target, and decide whether the left subtree or the right subtree recursion based on the comparison results. If the node has no left subtree, simply return its right subtree as the new subtree (or null if there is no right subtree), for a node with two children, find the smallest node in the right subtree (via the findMin method), replace the current node with this node, and then recursively remove the smallest node.

Testcode

Download Junit for unit testing using Maven.

(In Java, we generally do not use assertion types directly in our code, we use more if conditions, and assertion types are required to be used in test classes as much as possible.)

java 复制代码
/*

  Insertion structure:

    Alice

   /    \

Adam    Bob

       /   \

   Barbara  Charlie

           /     \

         null    null



   */

  private MemberBST memberBST;

  private Member member1;

  private Member member2;

  private Member member3;

  private Member member4;



  @Before

  public void setUp() {

      memberBST = new MemberBST();

      member1 = new Member("Alice", "Company A");

      member2 = new Member("Bob", "Company B");

      member3 = new Member("Charlie", "Company C");

      member4 = new Member("Adam", "Company D");



      memberBST.put(member1);

      memberBST.put(member2);

      memberBST.put(member3);

      memberBST.put(member4);

  }

First, insert the test data. See comments for the structure.

java 复制代码
@Override

public boolean equals(Object o) {

    if (this == o) return true;

    if (o == null || getClass() != o.getClass()) return false;

    Member member = (Member) o;

    return Objects.equals(fullName, member.fullName) && Objects.equals(affiliation, member.affiliation);

}



@Override

public int hashCode() {

    return Objects.hash(fullName, affiliation);

}

Note that we need to override the equals and hashcode methods in the Member class because we need a time value comparison rather than a memory address comparison.

The unit test code is as follows:

Then we can allow the main use case to be given in actual code:

Display:

Apparently it's allowed.

Analyse

  1. put
    1. Average case: O(log n), because each insertion attempts to place a new node in the proper place in the tree so that the tree is balanced.
    2. Best case: O(1), when the tree is empty or a new node is always inserted as the last node (this can happen with ordered inserts, but is uncommon).
    3. Worst case: O(n), if the tree is extremely unbalanced (degenerates into a linked list), each insertion may require traversing the entire tree.
  2. get & containsName
    1. Average case: O(log n), similar to insertion because the search follows a path from root to leaf.
    2. Best case: O(1), if the target node happens to be the root node.
    3. Worst case: O(n), when the tree degenerates into a linked list.
  3. Remove
    1. Average case: O(log n), the node needs to be found, and then three cases (no child, one child, two children) are processed, most of the operations are concentrated in a small part of the tree.
    2. Best case: O(1), delete the leaf node.
    3. Worst case: O(n), also when the tree is reduced to a linked list, it takes linear time to find the node.
  4. size & displayDB

The recursive implementation results in a time complexity of O(n) because each node needs to be traversed

相关推荐
代码雕刻家14 分钟前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
2402_8575893618 分钟前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰42 分钟前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没1 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
Kalika0-02 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
编程、小哥哥2 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
代码雕刻家2 小时前
课设实验-数据结构-单链表-文教文化用品品牌
c语言·开发语言·数据结构
IT学长编程3 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码3 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端