当前位置: 首页 > news >正文

基于树的存储数据结构demo

一.简介

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

二.准备代码

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

IMember 接口

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

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 nameexists 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方法

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 nameexists 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();
}

三.实现类

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;}@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(fullName, affiliation);}
}
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;}}// constructorpublic MemberBST() {System.out.println("Binary Search Tree");this.root = null;}@Overridepublic void clearDB() {root = null;}@Overridepublic boolean containsName(String name) {return recursionTree(root, name);}// if two string is same,compare() method will return 0private 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);}@Overridepublic 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 sequenceif (node.name.compareTo(name) == 0) {System.out.println("the Member name: " + node.member.getName());return node.member;}// recursionMember leftResult = recursionGetter(node.left, name, sequence);if (leftResult != null) {return leftResult;}return recursionGetter(node.right, name, sequence);}@Overridepublic 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;}@Overridepublic boolean isEmpty() {return root == null;}@Overridepublic 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 leftif (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 rightif (node.right == null) {node.right = new Node(member);System.out.println("Visited node: " + node.member.getName());} else {recursionAdder(node.right, member);}}}@Overridepublic 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 removeSystem.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 successornode.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;}@Overridepublic 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);}
}

包装类:用于值传递

package partA;public class MemberWrapper {Member member;
}

四.解释及测试类

APIs

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

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"

// constructorpublic 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()
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()
public boolean containsName(String name) {return recursionTree(root, name);}// if two string is same,compare() method will return 0private 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()
@Overridepublic 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 sequenceif (node.name.compareTo(name) == 0) {System.out.println("the Member name: " + node.member.getName());return node.member;}// recursionMember 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()
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.

isEmpty()
@Overridepublic 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 leftif (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 rightif (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 removeSystem.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 successornode.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.)

/*Insertion structure:Alice/    \Adam    Bob/   \Barbara  Charlie/     \null    null*/private MemberBST memberBST;private Member member1;private Member member2;private Member member3;private Member member4;@Beforepublic 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.

@Overridepublic 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);}@Overridepublic 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

相关文章:

  • Ubuntu系统版本查看办法
  • (Qt) 默认QtWidget应用包含什么?
  • 汽车工厂安灯系统能够快速知晓生产现场的状况
  • github下载代码
  • Docker 部署 Nginx 实现一个极简的 负载均衡
  • docker 笔记汇总
  • Java入门基础学习笔记36——面向对象基础
  • 思科模拟器--03.RIP协议路由--24.5.17
  • FOC之反park变化推导笔记
  • mysql 多表关联查询性能优化-同一sql不同的执行计划
  • 最近最少使用缓存
  • dify:开源 LLMOps平台。
  • Android Retrofit 封装模版
  • 物体检测算法-R-CNN,SSD,YOLO
  • 苍穹外卖①
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • Cookie 在前端中的实践
  • CSS盒模型深入
  • ERLANG 网工修炼笔记 ---- UDP
  • es6--symbol
  • github指令
  • Git学习与使用心得(1)—— 初始化
  • If…else
  • JSDuck 与 AngularJS 融合技巧
  • laravel with 查询列表限制条数
  • Redis 懒删除(lazy free)简史
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • 分布式事物理论与实践
  • 关于Flux,Vuex,Redux的思考
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 7行Python代码的人脸识别
  • #define 用法
  • #define、const、typedef的差别
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • (12)Hive调优——count distinct去重优化
  • (附源码)spring boot火车票售卖系统 毕业设计 211004
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (译)2019年前端性能优化清单 — 下篇
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转载)利用webkit抓取动态网页和链接
  • .NET Core WebAPI中封装Swagger配置
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .NET使用存储过程实现对数据库的增删改查
  • @RequestMapping-占位符映射
  • @SuppressLint(NewApi)和@TargetApi()的区别
  • @开发者,一文搞懂什么是 C# 计时器!
  • [.net] 如何在mail的加入正文显示图片
  • []我的函数库