XJTUSE 数据结构与算法第三次作业——任务1

任务1

  1. 题目

二叉检索树即 BST,是利用二叉树的非线性关系,结合数据之间的大小关系进行存储的一种用于检索数据的数据结构。一般情况下,对信息进行检索时,都需要指定检索关键码(Key),根据该关键字找到所需要的信息(比如学生信息里关键码是学号,而姓名等信息就是该关键码对应的信息),所以当提到检索时,都会有“键值对”这个概念,用(key,value)表示键值和对应信息的关系。

下面的二叉检索树的 ADT 在描述时,依然使用了模板类的方法,并且在这次描述中,使用了两个模板参数 K V(这表明 key value 可以为不同类型的数据)。在本次任务的测试文件中,key value 都是 String 类型,对模板参数运用有困难的同学,可以直接设定 key value 的类型为 String

在本次实验中,主要完成的任务是:

1、为指定的 BST ADT实现数据结构。

2、用给定的输入数据文件验证所实现的数据结构是否正确。

为了方便进行测试验证,对BST 的各种操作指定了相应的命令符号,具体的符号含义如下:

实验完成之后,必须通过实验中提供的测试用例。借助测试用例的运行结果,用来检查所撰写的代码功能是否正确。

测试用例中的每一行的内容都类似于上表中的每一行“命令行内容”列中所指示的内容。要求每执行一行,就调用 List 接口中的 showStructure 行为,用以验证该命令行的执行是否正确。

每行“命令行内容”都不是独立的,是针对同一个 List 类型的对象实例运行的结果。实验包里包括了个文件,一个是“list_testcase.txt”,其内包含了测试用例;另一个是“list_result.txt”,其内包含了对应测试用例的运行结果。

  1. 数据设计

这个BST的实现是一个通用的、基于泛型的二叉搜索树,支持任何实现了Comparable接口的键类型。递归是实现插入、删除、搜索和更新等操作的主要手段。

BSTNode类包括K key:键,用于比较节点的大小关系V value:值,存储在节点中的数据BSTNode<K, V> left:左子节点BSTNode<K, V> right:右子节点。

  1. static class BSTNode<K extends Comparable<K>, V> {
  2.         K key;
  3.         V value;
  4.         BSTNode<K, V> left;
  5.         BSTNode<K, V> right;
  6.         public BSTNode(K key, V value, BSTNode<K, V> left, BSTNode<K, V> right) {
  7.             this.key = key;
  8.             this.value = value;
  9.             this.left = left;
  10.             this.right = right;
  11.         }
  12.     }

 insert(K key, V value) 方法接受一个键值对作为输入,将其插入到二叉搜索树中。方法首先检查输入的键和值是否为 null,如果是则抛出异常。然后,调用 doInsert 方法来执行实际的插入操作,同时更新根节点。

doInsert 方法是一个私有的递归方法,它接受一个节点、一个键和一个值作为输入,并返回更新后的节点。在 doInsert(BSTNode<K, V> node, K key, V value) 方法中,首先检查节点是否为 null。如果节点为 null,说明当前位置是一个合适的插入点,于是创建一个新的节点,并将键值对存储在新节点中,然后返回新节点。如果节点不为 null,那么需要根据键的大小关系选择向左子树或右子树递归插入。如果键小于当前节点的键,则递归调用 doInsert 方法,传入当前节点的左子节点,键和值,并将返回的结果赋值给当前节点的左子节点。如果键大于当前节点的键,则递归调用 doInsert 方法,传入当前节点的右子节点,键和值,并将返回的结果赋值给当前节点的右子节点。如果键等于当前节点的键,则更新当前节点的值。

时间复杂度分析:对于 insert(K key, V value) 方法,除了调用 doInsert 方法外,其他操作都是常数时间操作,不会对时间复杂度产生影响。而 doInsert 方法是一个递归方法,其时间复杂度取决于树的高度。在平衡的情况下,二叉搜索树的高度可以近似为 log(n),其中 n 是树中节点的数量。因此,在平衡的情况下,insert 方法的时间复杂度为 O(log n)。然而,如果二叉搜索树不平衡,即树的高度接近于 n,那么插入操作的时间复杂度可能接近于 O(n)。

  1. @Override
  2.     public void insert(K key, V value) {
  3.         if (key == null) {
  4.             throw new NullPointerException("Key cannot be null");
  5.         }
  6.         if (value == null) {
  7.             throw new NullPointerException("Value cannot be null");
  8.         }
  9.         root = doInsert(root, key, value); // 更新根节点
  10.     }
  11.     private BSTNode<K, V> doInsert(BSTNode<K, V> node, K key, V value) {
  12.         if (node == null) {
  13.             return new BSTNode<>(key, value, nullnull); // 创建新节点并返回
  14.         }
  15.         if (key.compareTo(node.key) < 0) {
  16.             node.left = doInsert(node.left, key, value); // 递归插入左子树
  17.         } else if (key.compareTo(node.key) > 0) {
  18.             node.right = doInsert(node.right, key, value); // 递归插入右子树
  19.         } else {
  20.             node.value = value; // 更新节点值
  21.         }
  22.         return node;
  23.     }

remove(K key) 方法接受一个键作为输入,从二叉搜索树中删除对应的节点,并返回删除节点的值。方法首先将 removeValue 设置为 null,然后检查输入的键是否为 null,如果是则抛出异常。接着,调用 removeHelp 方法执行实际的删除操作,并更新根节点。最后,返回删除节点的值。

在 removeHelp(BSTNode<K, V> root, K key) 方法中,首先检查根节点是否为 null。如果根节点为 null,说明树为空,直接返回 null。接下来,根据键和根节点的键的大小关系,选择向左子树或右子树递归删除。如果键小于根节点的键,则递归调用 removeHelp 方法,并将返回的结果赋值给根节点的左子节点。如果键大于根节点的键,则递归调用 removeHelp 方法,并将返回的结果赋值给根节点的右子节点。如果键等于根节点的键,那么需要考虑不同情况下的节点删除操作:

如果根节点没有左子节点,将 removeValue 设置为根节点的值,然后将根节点指向其右子节点,完成删除操作。

如果根节点没有右子节点,将 removeValue 设置为根节点的值,然后将根节点指向其左子节点,完成删除操作。

如果根节点既有左子节点又有右子节点,需要找到右子树中的最小节点(即右子树中最左边的节点),将其键和值替换到根节点中,然后递归地删除右子树中的最小节点,并将返回的结果赋值给根节点的右子节点。

此外,还有两个辅助方法 getMinNode 和 removeMinNode。getMinNode 方法用于获取指定树中的最小节点,它通过递归地向左子树寻找最小节点,并返回结果。removeMinNode 方法用于删除指定树中的最小节点,它通过递归地删除左子树中的最小节点,并将返回的结果赋值给当前节点的左子节点。

时间复杂度分析:对于 remove(K key) 方法,除了调用 removeHelp 方法外,其他操作都是常数时间操作,不会对时间复杂度产生影响。而 removeHelp 方法是一个递归方法,其时间复杂度取决于树的高度。在平衡的情况下,二叉搜索树的高度可以近似为 log(n),其中 n 是树中节点的数量。因此,在平衡的情况下,删除操作的时间复杂度为 O(log n)。然而,如果二叉搜索树不平衡,即树的高度接近于 n,那么删除操作的时间复杂度可能接近于 O(n)。

  1. @Override
  2.     public V remove(K key) {
  3.         removeValue = null;
  4.         if (key == null) {
  5.             throw new NullPointerException("Key cannot be null");
  6.         }
  7.         root = removeHelp(root, key);
  8.         return removeValue;
  9.     }
  10.     private BSTNode<K, V> removeHelp(BSTNode<K, V> root, K key) {
  11.         if (root == null)
  12.             return null;
  13.         if (key.compareTo(root.key) < 0) {
  14.             root.left = removeHelp(root.left, key);
  15.         } else if (key.compareTo(root.key) > 0) {
  16.             root.right = removeHelp(root.right, key);
  17.         } else {
  18.             if (root.left == null) {
  19.                 removeValue = root.value;
  20.                 root = root.right;
  21.             } else if (root.right == null) {
  22.                 removeValue = root.value;
  23.                 root = root.left;
  24.             } else {
  25.                 BSTNode<K, V> minNode = getMinNode(root.right);
  26.                 removeValue = root.value;
  27.                 root.key = minNode.key;
  28.                 root.value = minNode.value;
  29.                 root.right = removeMinNode(root.right);
  30.             }
  31.         }
  32.         return root;
  33.     }
  34.     private BSTNode<K, V> getMinNode(BSTNode<K, V> root) {
  35.         if (root.left == null)
  36.             return root;
  37.         else
  38.             return getMinNode(root.left);
  39.     }
  40.     private BSTNode<K, V> removeMinNode(BSTNode<K, V> root) {
  41.         if (root.left == null) {
  42.             return root.right;
  43.         }
  44.         root.left = removeMinNode(root.left);
  45.         return root;
  46.     }

search(K key) 方法接受一个键作为输入,从二叉搜索树中查找对应的值,并返回该值。方法首先检查输入的键是否为 null,如果是则抛出异常。接下来,调用私有方法 search,并传入根节点和键进行递归查找。最后,返回查找结果。

在 search(BSTNode<K, V> root, K key) 方法中,首先检查根节点是否为 null。如果根节点为 null,说明树为空,直接返回 null。接下来,根据键和根节点的键的大小关系,选择向左子树或右子树递归查找。如果键小于根节点的键,则递归调用 search 方法,并传入根节点的左子节点和键进行查找。如果键大于根节点的键,则递归调用 search 方法,并传入根节点的右子节点和键进行查找。如果键等于根节点的键,则找到了对应的节点,直接返回该节点的值。

时间复杂度分析:对于 search(K key) 方法,除了调用 search 方法外,其他操作都是常数时间操作,不会对时间复杂度产生影响。而 search 方法是一个递归方法,其时间复杂度取决于树的高度。在平衡的情况下,二叉搜索树的高度可以近似为 log(n),其中 n 是树中节点的数量。因此,在平衡的情况下,搜索操作的时间复杂度为 O(log n)。然而,如果二叉搜索树不平衡,即树的高度接近于 n,那么搜索操作的时间复杂度可能接近于 O(n)。

  1. @Override
  2.     public V search(K key) {
  3.         if (key == null) {
  4.             throw new NullPointerException("Key cannot be null");
  5.         }
  6.         return search(root, key);
  7.     }
  8.     private V search(BSTNode<K, V> root, K key) {
  9.         if (root == null)
  10.             return null;
  11.         if (key.compareTo(root.key) < 0)
  12.             return search(root.left, key);//小于当前key值则往左子树查找
  13.         if (key.compareTo(root.key) > 0)
  14.             return search(root.right, key);//大于当前key值则往右子树查找
  15.         return root.value;//找到值
  16.     }

update(K key, V value) 方法接受一个键和一个值作为输入,用于更新二叉搜索树中指定键对应的节点的值,并返回一个布尔值表示更新是否成功。方法首先检查输入的键和值是否为 null,如果是则抛出异常。接下来,调用私有方法 update,并传入根节点、键和值进行递归更新。最后,返回更新结果。

在 update(BSTNode<K, V> root, K key, V value) 方法中,首先检查键是否等于根节点的键。如果相等,说明找到了要更新的节点,将节点的值更新为输入的值,并返回 true 表示更新成功。如果键小于根节点的键,则递归调用 update 方法,并传入根节点的左子节点、键和值进行更新。如果键大于根节点的键,则递归调用 update 方法,并传入根节点的右子节点、键和值进行更新。如果以上情况都不满足,说明树中不存在指定的键,返回 false 表示更新失败。

时间复杂度分析:对于 update(K key, V value) 方法,除了调用 update 方法外,其他操作都是常数时间操作,不会对时间复杂度产生影响。而 update 方法是一个递归方法,其时间复杂度取决于树的高度。在平衡的情况下,二叉搜索树的高度可以近似为 log(n),其中 n 是树中节点的数量。因此,在平衡的情况下,更新操作的时间复杂度为 O(log n)。然而,如果二叉搜索树不平衡,即树的高度接近于 n,那么更新操作的时间复杂度可能接近于 O(n)。

  1. @Override
  2.     public boolean update(K key, V value) {
  3.         if (key == null) {
  4.             throw new NullPointerException("Key cannot be null");
  5.         }
  6.         if (value == null) {
  7.             throw new NullPointerException("Value cannot be null");
  8.         }
  9.         return update(root, key, value);
  10.     }
  11.     public boolean update(BSTNode<K, V> root, K key, V value) {
  12.         if (key.compareTo(root.key) == 0) {
  13.             root.value = value;
  14.             return true;
  15.         }
  16.         if (key.compareTo(root.key) < 0)
  17.             return update(root.left, key, value);
  18.         if (key.compareTo(root.key) > 0)
  19.             return update(root.right, key, value);
  20.         return false;
  21.     }

isEmpty方法:判断树是否为空,即根节点是否为null。时间复杂度为O(1)。

clear方法:将根节点置为null,清空整个树。时间复杂度为O(1)。

  1. @Override
  2.     public boolean isEmpty() {
  3.         return root == null;
  4.     }
  5.     @Override
  6.     public void clear() {
  7.         root = null;
  8.     }

showStructure(PrintWriter pw) 方法用于将二叉搜索树的结构信息输出到给定的 PrintWriter。方法首先调用 countNodes 方法获取二叉搜索树的节点数量,然后调用 getHeight 方法获取二叉搜索树的高度。接下来,输出一些描述性信息,包括节点数量和树的高度。最后,通过调用 pw.flush() 来确保输出立即被写入到目标中。

countNodes(BSTNode<K, V> node) 方法用于递归计算以给定节点为根的二叉搜索树的节点数量。如果节点为 null,则返回 0。否则,递归调用 countNodes 方法计算左子树和右子树的节点数量,并将其加上当前节点的数量(1),然后返回结果。

getHeight(BSTNode<K, V> node) 方法用于递归计算以给定节点为根的二叉搜索树的高度。如果节点为 null,则返回 0。否则,递归调用 getHeight 方法计算左子树和右子树的高度,并取两者中的最大值,然后加上当前节点的高度(1),最后返回结果。

时间复杂度分析:countNodes 和 getHeight 方法都是递归方法,其时间复杂度取决于树的高度。在平衡的情况下,二叉搜索树的高度可以近似为 log(n),其中 n 是树中节点的数量。因此,这两个方法的时间复杂度为 O(log n)。需要注意的是,这里的时间复杂度是在遍历整个树的情况下。对于 showStructure 方法,除了调用 countNodes 和 getHeight 方法外,其他操作都是常数时间操作,不会对时间复杂度产生影响。

  1. @Override
  2.     public void showStructure(PrintWriter pw) {
  3.         int numberOfNode = countNodes(root);
  4.         int height = getHeight(root);
  5.         pw.println("-----------------------------");
  6.         pw.print("There are ");
  7.         pw.print(numberOfNode);
  8.         pw.println(" nodes in this BST.");
  9.         pw.print("The height of this BST is ");
  10.         pw.print(height);
  11.         pw.println(".");
  12.         pw.println("-----------------------------");
  13.         //使用 pw.flush() 是为了确保 showStructure 方法中的输出被立即写入到目标中,而不是等待缓冲区满或程序结束时才进行写入。
  14.         pw.flush();
  15.     }
  16.     private int countNodes(BSTNode<K, V> node) {
  17.         if (node == null) {
  18.             return 0;
  19.         }
  20.         int leftCount = countNodes(node.left);
  21.         int rightCount = countNodes(node.right);
  22.         return 1 + leftCount + rightCount;
  23.     }
  24.     private int getHeight(BSTNode<K, V> node) {
  25.         if (node == null) {
  26.             return 0;
  27.         }
  28.         int leftHeight = getHeight(node.left);
  29.         int rightHeight = getHeight(node.right);
  30.         return 1 + Math.max(leftHeight, rightHeight);
  31.     }

printInorder(PrintWriter pw) 方法用于将二叉搜索树按照中序遍历的顺序输出到给定的 PrintWriter。首先,方法检查传入的 PrintWriter 是否为 null,如果是,则抛出 NullPointerException。接下来,调用私有方法 inorderTraversal,并传入根节点和 PrintWriter 进行中序遍历输出。

inorderTraversal(BSTNode<K, V> node, PrintWriter pw) 方法是一个递归方法,用于实现中序遍历。如果当前节点不为 null,则先递归调用 inorderTraversal 方法遍历当前节点的左子树,然后将当前节点的键和值输出到 PrintWriter,最后再递归调用 inorderTraversal 方法遍历当前节点的右子树。通过这种方式,可以按照中序遍历的顺序输出二叉搜索树的节点。

时间复杂度分析:中序遍历的时间复杂度取决于树的节点数量。在最坏的情况下,即二叉搜索树是一个完全不平衡的链表时,需要遍历所有的节点,时间复杂度为 O(n)。在平衡的情况下,二叉搜索树的高度可以近似为 log(n),其中 n 是树中节点的数量。

  1. @Override
  2.     public void printInorder(PrintWriter pw) {
  3.         if (pw == null) {
  4.             throw new NullPointerException("PrintWriter不能为空");
  5.         }
  6.         inorderTraversal(root, pw);
  7.     }
  8.     private void inorderTraversal(BSTNode<K, V> node, PrintWriter pw) {
  9.         if (node != null) {
  10.             inorderTraversal(node.left, pw);
  11.             pw.println("[" + node.key + " --- <" + node.value + ">]");
  12.             inorderTraversal(node.right, pw);
  13.         }
  14.     }

  1. 算法设计

当实现了BST的数据结构后,剩下比较难办的就是怎么用StreamTokenizer实现命令行。

每当我们输入一个回车,程序都会相应的作出一系列动作,那么我们可以把根据行读入,根据输入的内容做出回应。

  1.         String line;
  2.         while ((line = br.readLine()) != null) {
  3.             executeCommand(bstTree, line);
  4.             writer.flush(); // 刷新PrintWriter的缓冲区
  5.         }

为了方便起见,我们把读取标记并进行相对应的动作用方法抽离出来:

  1.      private static <K extends Comparable<K>, V> void executeCommand(BSTTree<K, V> bstTree, String line) throws IOException {
  2.         StreamTokenizer st = new StreamTokenizer(new StringReader(line));
  3.         int token = st.nextToken();
  4.         while (token != StreamTokenizer.TT_EOF) {
  5.             char command = (char) st.ttype;
  6.             switch (command) {
  7.                 case '+' -> {
  8.                     ......
  9.                 }
  10.                 case '-' -> {
  11.                    ......
  12.                 }
  13.                 ......
  14.             }
  15.             token = st.nextToken();
  16.         }
  17.     }

当读取到+时,分别读取+后面的Key和Value并调用bsttree的insert方法。在bsttree插入对应的key和value。

  1.                 case '+' -> {
  2.                     //+( conflagration , "n.建筑物或森林大火" )
  3.                     st.nextToken();
  4.                     st.nextToken();
  5.                     K key = (K) st.sval;
  6.                     st.nextToken();
  7.                     st.nextToken();
  8.                     V value = (V) st.sval;
  9.                     bstTree.insert(key, value);
  10.                 }

当读取到-时,读取-后面的Key并调用bsttree的remove方法。在bsttree删除对应的key和value,并根据是否删除成功输出对应的语句。

  1.                 case '-' -> {
  2.                     st.nextToken();
  3.                     st.nextToken();
  4.                     K key = (K) st.sval;
  5.                     V value = bstTree.remove(key);
  6.                     if (value == null) {
  7.                         writer.println("remove unsuccess ---" + key);
  8.                     } else {
  9.                         writer.println("remove success ---" + key + " " + value);
  10.                     }
  11.                 }

当读取到?时,读取?后面的Key并调用bsttree的search方法。在bsttree查询对应的key和value,并根据是否查询成功输出对应的语句。

  1.                 case '?' -> {
  2.                     st.nextToken();
  3.                     st.nextToken();
  4.                     K key = (K) st.sval;
  5.                     V value = bstTree.search(key);
  6.                     if (value == null) {
  7.                         writer.println("search unsuccess ---" + key);
  8.                     } else {
  9.                         writer.println("search success ---" + key + " " + value);
  10.                     }
  11.                 }

当读取到=时,分别读取=后面的key和value并调用bsttree的update方法。在bsttree更新对应的key和value,并输出更新成功的语句。

  1.                 case '=' -> {
  2.                     st.nextToken();
  3.                     st.nextToken();
  4.                     K key = (K) st.sval;
  5.                     st.nextToken();
  6.                     st.nextToken();
  7.                     V value = (V) st.sval;
  8.                     bstTree.update(key, value);
  9.                     writer.println("update success ---" + key + " " + value);
  10.                 }

当读取到#就调用showStructure方法。

  1.                   case '#' ->bstTree.showStructure(writer);

最后,我们为了验证输出与标准答案是否一致,还需要一个验文本文件比较器。

  1. package Homework01;
  2. import java.io.BufferedReader;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. public class TextFileComparator {
  6.     public static boolean compareFiles(String filePath1, String filePath2) {
  7.         try (BufferedReader reader1 = new BufferedReader(new FileReader(filePath1));
  8.              BufferedReader reader2 = new BufferedReader(new FileReader(filePath2))) {
  9.             String line1, line2;
  10.             while ((line1 = reader1.readLine()) != null) {
  11.                 line2 = reader2.readLine();
  12.                 if (!line1.equals(line2)) {
  13.                     return false;
  14.                 }
  15.             }
  16.             // Check if file2 has additional lines
  17.             return reader2.readLine() == null;
  18.         } catch (IOException e) {
  19.             e.printStackTrace();
  20.             return false;
  21.         }
  22.     }
  23.     public static void main(String[] args) {
  24.         String file1 = "D:\\develop\\projects\\dataStructure\\homework03\\file\\homework3_result.txt";
  25.         String file2 = "D:\\develop\\projects\\dataStructure\\homework03\\file\\output.txt";
  26.         boolean areEqual = compareFiles(file1, file2);
  27.         if (areEqual) {
  28.             System.out.println("两个文件的内容相同");
  29.         } else {
  30.             System.out.println("两个文件的内容不同");
  31.         }
  32.     }
  33. }
  1. 运行结果展示

我们可以直接把输出打印在控制台,部分结果如下:

图1  测试结果图(部分)

用人眼无法看出来我们打印在控制台的和答案是否相同,所以我们将PrintWriter打印到一个文件output.txt中,再比较两个文件是否相同。为此写了一个类TextFileComparator。

  1. package Homework01;
  2. import java.io.BufferedReader;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. public class TextFileComparator {
  6.     public static boolean compareFiles(String filePath1, String filePath2) {
  7.         try (BufferedReader reader1 = new BufferedReader(new FileReader(filePath1));
  8.              BufferedReader reader2 = new BufferedReader(new FileReader(filePath2))) {
  9.             String line1, line2;
  10.             while ((line1 = reader1.readLine()) != null) {
  11.                 line2 = reader2.readLine();
  12.                 if (!line1.equals(line2)) {
  13.                     return false;
  14.                 }
  15.             }
  16.             // Check if file2 has additional lines
  17.             return reader2.readLine() == null;
  18.         } catch (IOException e) {
  19.             e.printStackTrace();
  20.             return false;
  21.         }
  22.     }
  23.     public static void main(String[] args) {
  24.         String file1 = "D:\\develop\\projects\\dataStructure\\homework03\\file\\homework3_result.txt";
  25.         String file2 = "D:\\develop\\projects\\dataStructure\\homework03\\file\\output.txt";
  26.         boolean areEqual = compareFiles(file1, file2);
  27.         if (areEqual) {
  28.             System.out.println("两个文件的内容相同");
  29.         } else {
  30.             System.out.println("两个文件的内容不同");
  31.         }
  32.     }
  33. }

结果如下:

图2  用TextFileComparator文件比较器对比结果图

  1. 总结和收获

通过这个实验,首先练习了BST这种数据结构,并复习了泛型的使用,学习了StreamTokenizer类的使用,学会了文件相关的方法和流。收获很大。

 

附录:

  1. 任务1
  1. BST的ADT:
  1. package Homework01;
  2. import java.io.PrintWriter;
  3. public interface BST <K extends Comparable<K>, V>{
  4.     /**
  5.      * @Precondition 二叉查找树未满。
  6.      * Key和value不能为null。
  7.      * @Postcondition 向二叉查找树中插入一个新元素(包括键和值)。
  8.      * 如果树中已经存在具有相同键值的元素,则使用newElement的value字段更新该元素的value字段。
  9.      * @return  none
  10.      */
  11.     public void insert(K key, V value);
  12.     /**
  13.      * @Precondition Key不为空。
  14.      * @Postcondition 从二叉查找树中删除具有相同键值的元素。
  15.      * 如果树中不存在具有相同键值的元素,则返回null。
  16.      * 否则,返回元素的value字段。
  17.      * @return value
  18.      */
  19.     public V remove(K key);
  20.     /**
  21.      * @Precondition Key不为空。
  22.      * @Postcondition 在二叉查找树中查找具有相同键值的元素。
  23.      * 如果找到该元素,则返回该元素的value字段。
  24.      * 否则,返回null。
  25.      * @return  value
  26.      */
  27.     public V search(K key);
  28.     /**
  29.      * @Precondition Key和value不能为null。
  30.      * @Postcondition 在二叉查找树中查找具有相同键值的元素。
  31.      * 如果没有找到该元素,则返回false。
  32.      * 否则,用参数的值更新元素的value字段,并返回true。
  33.      * @return  布尔值
  34.      */
  35.     public boolean update(K key,V value);
  36.     /**
  37.      * @Precondition none
  38.      * @Postcondition
  39.      * @return  布尔值
  40.      */
  41.     public boolean isEmpty();
  42.     /**
  43.      * @Precondition none
  44.      * @Postcondition 删除二叉查找树中的所有元素。
  45.      * @return  none
  46.      */
  47.     public void clear();
  48.     /**
  49.      * @Precondition PrintWriter不为null。
  50.      * @Postcondition 输出二叉查找树的相关信息。
  51.      * 这些信息包括二叉查找树的节点数和高度。
  52.      * 输出内容的格式应该是如图2所示(在下一页)。
  53.      * @return  none
  54.      */
  55.     public void showStructure(PrintWriter pw);
  56.     /**
  57.      * @Precondition PrintWriter不为null。
  58.      * @Postcondition 按发送顺序asending order输出二叉查找树中的所有节点。
  59.      * 每行输出一个元素。
  60.      * 每个元素的输出格式如下:
  61.      * [key --- <  value >]
  62.      * @return  none
  63.      */
  64.     public void printInorder(PrintWriter pw);
  65. }

  1. BSTtree:
  1. package Homework01;
  2. import java.io.PrintWriter;
  3. public class BSTTree<K extends Comparable<K>, Vimplements BST<KV> {
  4.     private V removeValue;
  5.     BSTNode<K, V> root;
  6.     static class BSTNode<K extends Comparable<K>, V> {
  7.         K key;
  8.         V value;
  9.         BSTNode<K, V> left;
  10.         BSTNode<K, V> right;
  11.         public BSTNode(K key, V value, BSTNode<K, V> left, BSTNode<K, V> right) {
  12.             this.key = key;
  13.             this.value = value;
  14.             this.left = left;
  15.             this.right = right;
  16.         }
  17.     }
  18.     @Override
  19.     public void insert(K key, V value) {
  20.         if (key == null) {
  21.             throw new NullPointerException("Key cannot be null");
  22.         }
  23.         if (value == null) {
  24.             throw new NullPointerException("Value cannot be null");
  25.         }
  26.         root = doInsert(root, key, value); // 更新根节点
  27.     }
  28.     private BSTNode<K, V> doInsert(BSTNode<K, V> node, K key, V value) {
  29.         if (node == null) {
  30.             return new BSTNode<>(key, value, nullnull); // 创建新节点并返回
  31.         }
  32.         if (key.compareTo(node.key) < 0) {
  33.             node.left = doInsert(node.left, key, value); // 递归插入左子树
  34.         } else if (key.compareTo(node.key) > 0) {
  35.             node.right = doInsert(node.right, key, value); // 递归插入右子树
  36.         } else {
  37.             node.value = value; // 更新节点值
  38.         }
  39.         return node;
  40.     }
  41.     @Override
  42.     public V remove(K key) {
  43.         removeValue = null;
  44.         if (key == null) {
  45.             throw new NullPointerException("Key cannot be null");
  46.         }
  47.         root = removeHelp(root, key);
  48.         return removeValue;
  49.     }
  50.     private BSTNode<K, V> removeHelp(BSTNode<K, V> root, K key) {
  51.         if (root == null)
  52.             return null;
  53.         if (key.compareTo(root.key) < 0) {
  54.             root.left = removeHelp(root.left, key);
  55.         } else if (key.compareTo(root.key) > 0) {
  56.             root.right = removeHelp(root.right, key);
  57.         } else {
  58.             if (root.left == null) {
  59.                 removeValue = root.value;
  60.                 root = root.right;
  61.             } else if (root.right == null) {
  62.                 removeValue = root.value;
  63.                 root = root.left;
  64.             } else {
  65.                 BSTNode<K, V> minNode = getMinNode(root.right);
  66.                 removeValue = root.value;
  67.                 root.key = minNode.key;
  68.                 root.value = minNode.value;
  69.                 root.right = removeMinNode(root.right);
  70.             }
  71.         }
  72.         return root;
  73.     }
  74.     private BSTNode<K, V> getMinNode(BSTNode<K, V> root) {
  75.         if (root.left == null)
  76.             return root;
  77.         else
  78.             return getMinNode(root.left);
  79.     }
  80.     private BSTNode<K, V> removeMinNode(BSTNode<K, V> root) {
  81.         if (root.left == null) {
  82.             return root.right;
  83.         }
  84.         root.left = removeMinNode(root.left);
  85.         return root;
  86.     }
  87.     @Override
  88.     public V search(K key) {
  89.         if (key == null) {
  90.             throw new NullPointerException("Key cannot be null");
  91.         }
  92.         return search(root, key);
  93.     }
  94.     private V search(BSTNode<K, V> root, K key) {
  95.         if (root == null)
  96.             return null;
  97.         if (key.compareTo(root.key) < 0)
  98.             return search(root.left, key);//小于当前key值则往左子树查找
  99.         if (key.compareTo(root.key) > 0)
  100.             return search(root.right, key);//大于当前key值则往右子树查找
  101.         return root.value;//找到值
  102.     }
  103.     @Override
  104.     public boolean update(K key, V value) {
  105.         if (key == null) {
  106.             throw new NullPointerException("Key cannot be null");
  107.         }
  108.         if (value == null) {
  109.             throw new NullPointerException("Value cannot be null");
  110.         }
  111.         return update(root, key, value);
  112.     }
  113.     public boolean update(BSTNode<K, V> root, K key, V value) {
  114.         if (key.compareTo(root.key) == 0) {
  115.             root.value = value;
  116.             return true;
  117.         }
  118.         if (key.compareTo(root.key) < 0)
  119.             return update(root.left, key, value);
  120.         if (key.compareTo(root.key) > 0)
  121.             return update(root.right, key, value);
  122.         return false;
  123.     }
  124.     @Override
  125.     public boolean isEmpty() {
  126.         return root == null;
  127.     }
  128.     @Override
  129.     public void clear() {
  130.         root = null;
  131.     }
  132.     @Override
  133.     public void showStructure(PrintWriter pw) {
  134.         int numberOfNode = countNodes(root);
  135.         int height = getHeight(root);
  136.         pw.println("-----------------------------");
  137.         pw.print("There are ");
  138.         pw.print(numberOfNode);
  139.         pw.println(" nodes in this BST.");
  140.         pw.print("The height of this BST is ");
  141.         pw.print(height);
  142.         pw.println(".");
  143.         pw.println("-----------------------------");
  144.         //使用 pw.flush() 是为了确保 showStructure 方法中的输出被立即写入到目标中,而不是等待缓冲区满或程序结束时才进行写入。
  145.         pw.flush();
  146.     }
  147.     private int countNodes(BSTNode<K, V> node) {
  148.         if (node == null) {
  149.             return 0;
  150.         }
  151.         int leftCount = countNodes(node.left);
  152.         int rightCount = countNodes(node.right);
  153.         return 1 + leftCount + rightCount;
  154.     }
  155.     private int getHeight(BSTNode<K, V> node) {
  156.         if (node == null) {
  157.             return 0;
  158.         }
  159.         int leftHeight = getHeight(node.left);
  160.         int rightHeight = getHeight(node.right);
  161.         return 1 + Math.max(leftHeight, rightHeight);
  162.     }
  163.     @Override
  164.     public void printInorder(PrintWriter pw) {
  165.         if (pw == null) {
  166.             throw new NullPointerException("PrintWriter不能为空");
  167.         }
  168.         inorderTraversal(root, pw);
  169.     }
  170.     private void inorderTraversal(BSTNode<K, V> node, PrintWriter pw) {
  171.         if (node != null) {
  172.             inorderTraversal(node.left, pw);
  173.             pw.println("[" + node.key + " --- <" + node.value + ">]");
  174.             inorderTraversal(node.right, pw);
  175.         }
  176.     }
  177. }

  1. SystemInTest:
  1. package Homework01;
  2. import java.io.*;
  3. public class SystemInTest {
  4.     private static PrintWriter writer;
  5.     public static void main(String[] args) throws IOException {
  6.         BSTTree<String, String> bstTree = new BSTTree<>();
  7.         InputStreamReader isr = new InputStreamReader(System.in);
  8.         BufferedReader br = new BufferedReader(isr);
  9.         writer = new PrintWriter(System.out); // 创建PrintWriter对象
  10.         String line;
  11.         while ((line = br.readLine()) != null) {
  12.             executeCommand(bstTree, line);
  13.             writer.flush(); // 刷新PrintWriter的缓冲区
  14.         }
  15.         writer.close(); // 在循环结束后关闭PrintWriter对象
  16.     }
  17.     //泛型方法
  18.     private static <K extends Comparable<K>, V> void executeCommand(BSTTree<K, V> bstTree, String line) throws IOException {
  19.         StreamTokenizer st = new StreamTokenizer(new StringReader(line));
  20.         int token = st.nextToken();
  21.         while (token != StreamTokenizer.TT_EOF) {
  22.             char command = (char) st.ttype;
  23.             switch (command) {
  24.                 case '+' -> {
  25.                     //+( conflagration , "n.建筑物或森林大火" )
  26.                     st.nextToken();
  27.                     st.nextToken();
  28.                     K key = (K) st.sval;
  29.                     st.nextToken();
  30.                     st.nextToken();
  31.                     V value = (V) st.sval;
  32.                     bstTree.insert(key, value);
  33.                 }
  34.                 case '-' -> {
  35.                     st.nextToken();
  36.                     st.nextToken();
  37.                     K key = (K) st.sval;
  38.                     V value = bstTree.remove(key);
  39.                     if (value == null) {
  40.                         writer.println("remove unsuccess ---" + key);
  41.                     } else {
  42.                         writer.println("remove success ---" + key + " " + value);
  43.                     }
  44.                 }
  45.                 case '?' -> {
  46.                     st.nextToken();
  47.                     st.nextToken();
  48.                     K key = (K) st.sval;
  49.                     V value = bstTree.search(key);
  50.                     if (value == null) {
  51.                         writer.println("search unsuccess ---" + key);
  52.                     } else {
  53.                         writer.println("search success ---" + key + " " + value);
  54.                     }
  55.                 }
  56.                 case '=' -> {
  57.                     st.nextToken();
  58.                     st.nextToken();
  59.                     K key = (K) st.sval;
  60.                     st.nextToken();
  61.                     st.nextToken();
  62.                     V value = (V) st.sval;
  63.                     bstTree.update(key, value);
  64.                     writer.println("update success ---" + key + " " + value);
  65.                 }
  66.                 case '#' ->
  67.                     bstTree.showStructure(writer);
  68.             }
  69.             token = st.nextToken();
  70.         }
  71.     }
  72. }

  1. FileTest:
  1. package Homework01;
  2. import java.io.*;
  3. public class FileTest {
  4.     private static PrintWriter writer;
  5.     public static void main(String[] args) throws IOException {
  6.         BSTTree<String, String> bstTree = new BSTTree<>();
  7.         FileReader fileReader = new FileReader("D:\\develop\\projects\\dataStructure\\homework03\\file\\homework3_testcases.txt");
  8.         BufferedReader br = new BufferedReader(fileReader);
  9.         writer = new PrintWriter(new FileWriter("D:\\develop\\projects\\dataStructure\\homework03\\file\\output.txt")); // 创建PrintWriter对象,并将输出定向到文件
  10.         String line;
  11.         while ((line = br.readLine()) != null) {
  12.             executeCommand(bstTree, line);
  13.             writer.flush(); // 刷新PrintWriter的缓冲区
  14.         }
  15.         writer.close(); // 在循环结束后关闭PrintWriter对象
  16.         br.close(); // 关闭输入流
  17.     }
  18.     //泛型方法
  19.     private static <K extends Comparable<K>, V> void executeCommand(BSTTree<K, V> bstTree, String line) throws IOException {
  20.         StreamTokenizer st = new StreamTokenizer(new StringReader(line));
  21.         int token = st.nextToken();
  22.         while (token != StreamTokenizer.TT_EOF) {
  23.             char command = (char) st.ttype;
  24.             switch (command) {
  25.                 case '+' -> {
  26.                     //+( conflagration , "n.建筑物或森林大火" )
  27.                     st.nextToken();
  28.                     st.nextToken();
  29.                     K key = (K) st.sval;
  30.                     st.nextToken();
  31.                     st.nextToken();
  32.                     V value = (V) st.sval;
  33.                     bstTree.insert(key, value);
  34.                 }
  35.                 case '-' -> {
  36.                     st.nextToken();
  37.                     st.nextToken();
  38.                     K key = (K) st.sval;
  39.                     V value = bstTree.remove(key);
  40.                     if (value == null) {
  41.                         writer.println("remove unsuccess ---" + key);
  42.                     } else {
  43.                         writer.println("remove success ---" + key + " " + value);
  44.                     }
  45.                 }
  46.                 case '?' -> {
  47.                     st.nextToken();
  48.                     st.nextToken();
  49.                     K key = (K) st.sval;
  50.                     V value = bstTree.search(key);
  51.                     if (value == null) {
  52.                         writer.println("search unsuccess ---" + key);
  53.                     } else {
  54.                         writer.println("search success ---" + key + " " + value);
  55.                     }
  56.                 }
  57.                 case '=' -> {
  58.                     st.nextToken();
  59.                     st.nextToken();
  60.                     K key = (K) st.sval;
  61.                     st.nextToken();
  62.                     st.nextToken();
  63.                     V value = (V) st.sval;
  64.                     bstTree.update(key, value);
  65.                     writer.println("update success ---" + key + " " + value);
  66.                 }
  67.                 case '#' ->
  68.                         bstTree.showStructure(writer);
  69.             }
  70.             token = st.nextToken();
  71.         }
  72.     }
  73. }

  1. TextFileComparator:
  1. package Homework01;
  2. import java.io.BufferedReader;
  3. import java.io.FileReader;
  4. import java.io.IOException;
  5. public class TextFileComparator {
  6.     public static boolean compareFiles(String filePath1, String filePath2) {
  7.         try (BufferedReader reader1 = new BufferedReader(new FileReader(filePath1));
  8.              BufferedReader reader2 = new BufferedReader(new FileReader(filePath2))) {
  9.             String line1, line2;
  10.             while ((line1 = reader1.readLine()) != null) {
  11.                 line2 = reader2.readLine();
  12.                 if (!line1.equals(line2)) {
  13.                     return false;
  14.                 }
  15.             }
  16.             // Check if file2 has additional lines
  17.             return reader2.readLine() == null;
  18.         } catch (IOException e) {
  19.             e.printStackTrace();
  20.             return false;
  21.         }
  22.     }
  23.     public static void main(String[] args) {
  24.         String file1 = "D:\\develop\\projects\\dataStructure\\homework03\\file\\homework3_result.txt";
  25.         String file2 = "D:\\develop\\projects\\dataStructure\\homework03\\file\\output.txt";
  26.         boolean areEqual = compareFiles(file1, file2);
  27.         if (areEqual) {
  28.             System.out.println("两个文件的内容相同");
  29.         } else {
  30.             System.out.println("两个文件的内容不同");
  31.         }
  32.     }
  33. }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值