3

两周前,我完成了一个展开树的实现,它允许基本功能,如插入、删除、查找键和获取三者的一系列元素的总和。您可以在此处找到此实现作为此问题的参考或出于好奇。

作为一项额外的任务(它是可选的,它是过期的,我解决这个问题不是为了一个成绩,而是因为我相信它是一个有用的数据结构不容易“谷歌关于它”),我被要求实现一个 Rope 数据结构来操作字符串,因此如果字符串是“warlock”并且给定的键是 0 2 2,那么字符串将是“lowarck”(0 2 是子字符串“war”,“lock”是删除“war”后剩下的内容,然后插入它在第二个字符之后变成“lo”+“war”+“ck”)。这只是一个查询,但对于 300k 字符长的字符串,它最多可以查询 100k,所以一个简单的解决方案是行不通的。

我的第一个疑问是初始化树(对于那些已经阅读要点的人,我将使用 Node 而不是 Vertex,以便大多数人易于理解)。

这是 Node 类和 NodePair 类:

class Node {
        char key;
        int size;
        Node left;
        Node right;
        Node parent;

        Node(char key, int size, Node left, Node right, Node parent) {
            this.key = key;
            this.size = size;
            this.left = left;
            this.right = right;
            this.parent = parent;
        }
    }

class NodePair {
        Node left;
        Node right;
        NodePair() {
        }
        NodePair(Node left, Node right) {
            this.left = left;
            this.right = right;
        }
    }

之后,我以这种方式创建树:

StringBuffer sb = new StringBuffer(br.readLine());
Node left=null;
for (int i=0;i<sb.length();i++){
    root=new Vertex(sb.charAt(i),  i+1, left, null, null);
    if (i!=sb.length()-1){
        left=root;
    }
}

这会创建一个非常不平衡的树,其中字符串的第一个字符(作为 node.key)具有 node.size 1 并且是最左边的孩子;字符串的最后一个字符是 size=sb.length() 的根。

我不完全确定这是否正确初始化。我用键和大小做了一个中序遍历打印,我得到了整个字符串的大小顺序,这是我所期望的。

之后,我修改了 Update 方法:

void update(Node v) {
        if (v == null) return;
        v.sum = v.key + (v.left != null ? v.left.sum : 0) + (v.right != null ? v.right.sum : 0);
        if (v.left != null) {
            v.left.parent = v;
        }
        if (v.right != null) {
            v.right.parent = v;
        }
    }

为此:(基于 CLRS 第 14.1 章)

void update(Node v) {
    if (v == null) return;
    v.size = 1 + (v.left != null ? v.left.size : 0) + (v.right != null ? v.right.size : 0);
    if (v.left != null) {
        v.left.parent = v;
    }
    if (v.right != null) {
        v.right.parent = v;
    }
}

然后是find方法,来自原文:

NodePair find(Node root, int key) {
        Node v = root;
        Node last = root;
        Node next = null;
        while (v != null) {
            if (v.key >= key && (next == null || v.key < next.key)) {
                next = v;
            }
            last = v;
            if (v.key == key) {
                break;
            }
            if (v.key < key) {
                v = v.right;
            } else {
                v = v.left;
            }
        }
        root = splay(last);
        return new NodePair(next, root);
    }

为此:(基于 CLRS 第 14.1 章的 Order Statistics-SELECT)

Node find(Node r, int k){
  int s = r.left.size + 1;
  if (k==s) return r;
  else if (k < s) {
     return find(r.left,k);
  }
  return find(r.right,k-s);
}

但是,此时我已经遇到了问题,因为如您所见,原始 find 返回一个 NodePair,而此方法返回一个 Node。根据讲师对 NodePair 的解释是:

返回结果和新根的对。如果找到,result 是指向具有给定键的节点的指针。否则,result 是指向具有最小较大键的节点的指针(顺序中的下一个值)。如果键大于树中的所有键,则结果为空。

这使我的拆分方法变得复杂,因为它使用 Find 方法来查找要拆分的节点。除此之外,我在这个 find 方法中获得了 NullPointerException,从其他学生那里我了解到,为了避免其他错误,我们应该使用非递归版本,所以基本上我需要实现 OS-Select 的非递归版本,它返回一个NodePair 作为以前的 find 方法或修改我的 split 方法是:

NodePair split(Node root, int key) {
    NodePair result = new NodePair();
    NodePair findAndRoot = find(root, key);
    root = findAndRoot.right;
    result.right = findAndRoot.left;
    if (result.right == null) {
        result.left = root;
        return result;
    }
    result.right = splay(result.right);
    result.left = result.right.left;
    result.right.left = null;
    if (result.left != null) {
        result.left.parent = null;
    }
    update(result.left);
    update(result.right);
    return result;
}

如您所见,find 方法被分配给 NodePair findAndRoot。我相信除了将 OS-Select 转换为非递归之外,我的主要问题是要了解 NodePair 使用之前的 find 方法和 split 方法的方式

最后,这是我接收树和键并操作它们的方法的实现:

 Node stringManip(Node v, int i, int j, int k){
    Node left;
    Node right;
    NodePair middleRight =split(v,j+1);
    left=middleRight.left;
    right=middleRight.right;
    NodePair leftMiddle = split(left,i);
    Node start = leftMiddle.left;
    Node substr = leftMiddle.right;
    Node tmp = merge(start, right);
    NodePair pairString = split(tmp,k+1);
    Vertex fLeft =pairString.left;
    Vertex fRight = pairString.right;
    root = merge(merge(fLeft,substr),fRight);
    root = splay(root);
    update(root);
    return root;
}

正如您必须从我的代码中意识到的那样,我是一个只有 5 个月开始学习编程的初学者,我选择了 Java,所以从我收集到的信息中,我了解到这种类型的数据结构更多地处于中间状态 -专家级别(我已经很惊讶我能够实现以前的 splay tree。所以请在你的回答中考虑我的初学者级别。

PD:这是非递归 OS-Select 的伪代码版本,仍然有 NullPointerException..

OS-SELECT(x, i)
    while x != null
        r <- size[left[x]]+1
        if i = r
            then return x
        elseif i < r
            x = left[x]
        else
            x = right[x]
            i = i - r
4

0 回答 0