天天看点

绳索数据结构(字符串快速拼接)

原文地址:Ropes Data Structure (Fast String Concatenation)

字符串连接是一种十分常见的操作。当字符串以传统的方式(例如字符数组)存储的时候,连接操作需要花费 O(n) 的时间(在这里n是元字符串的长度)。

我们可以利用绳索数据结构减少拼接花费的时间。

绳索数据结构

绳索是一个二叉树,这个二叉树的节点包含除叶子节点以外,节点左边字符的个数。叶子节点包含的是字符串断开的子字符串(子字符串的长度是由用户自己确定的)。

如下图所示:

绳索数据结构(字符串快速拼接)

上图说明了字符串在内存中的存储方式。每个叶子节点都包含了原字符串的子字符串,其他所有的节点包含了这个节点左边节点的字符数目。将字符的数目保存到左边的背后思想是查找第i个位置的字符所需要的成本最小化。

优点

  1. 绳索可以很明显地降低拼接的成本。
  2. 与数组不一样,绳索不需要大块连续的内存。
  3. 绳索不需要额外 O(n) 的空间做插入、删除、查询等操作。
  4. 如果要恢复最后一次拼接,那么可以在 O(1) 内移除树的根节点。

缺点

  1. 源码的复杂度增加了。
  2. 很容易出现bug。
  3. 存储父节点需要额外的空间。
  4. 访问第i个字符的时间增加了。

现在我们看一种情况,它可以解释为什么绳索法适用于大型字符串数组。

已知两个字符串

a[]

b[]

,在

c[]

中拼接它们。

例子:

Input  : a[] = "This is ", b[] = "an apple"
Output : "This is an apple"

Input  : a[] = "This is ", b[] = "geeksforgeeks"
Output : "This is geeksforgeeks"
           

方法一(普通法)

我们用字符串

c[]

存储拼接后的结果。我们首先遍历

a[]

然后将

a[]

中的所有字符全部拷贝到

c[]

中,然后再把

b[]

全部拷到

c[]

中。

// Simple C++ program to concatenate two strings
#include <iostream>
using namespace std;

// Function that concatenates strings a[0..n1-1] 
// and b[0..n2-1] and stores the result in c[]
void concatenate(char a[], char b[], char c[],
                              int n1, int n2)
{
    // Copy characters of A[] to C[]
    int i;
    for (i=; i<n1; i++)
        c[i] = a[i];

    // Copy characters of B[]
    for (int j=; j<n2; j++)
        c[i++] = b[j];

    c[i] = '\0';
}


// Driver code
int main()
{
    char a[] =  "Hi This is geeksforgeeks. ";
    int n1 = sizeof(a)/sizeof(a[]);

    char b[] =  "You are welcome here.";
    int n2 = sizeof(b)/sizeof(b[]);

    // Concatenate a[] and b[] and store result
    // in c[]
    char c[n1 + n2 - ];
    concatenate(a, b, c, n1, n2);
    for (int i=; i<n1+n2-; i++)
        cout << c[i];

    return ;
}
           

输出:

This is geeksforgeeks
           

时间复杂度: O(n)

下面我们用绳索来解决这个问题。

方法二(绳索法)

绳索法可以在常量时间内将两个字符串拼接在一起。

  1. 创建一个根节点(这个节点存储要拼接的新字符串的根节点)。
  2. 标记这个节点的左孩子,这个字符串的根节点出现在首位。
  3. 标记这个节点的右孩子,这个字符串的根节点出现在第二位。

就是这样的,因为这个方法只要求建立新节点,所以时间复杂度是 O(1) 。

考虑下图:

绳索数据结构(字符串快速拼接)
// C++ program to concatenate two strings using
// rope data structure.
#include <bits/stdc++.h>
using namespace std;

// Maximum no. of characters to be put in leaf nodes
const int LEAF_LEN = ;

// Rope structure
class Rope
{
public:
    Rope *left, *right, *parent;
    char *str;
    int lCount;
};

// Function that creates a Rope structure.
// node --> Reference to pointer of current root node
//   l  --> Left index of current substring (initially 0)
//   r  --> Left index of current substring (initially n-1)
//   par --> Parent of current node (Initially NULL)
void createRopeStructure(Rope *&node, Rope *par,
                         char a[], int l, int r)
{
    Rope *tmp = new Rope();
    tmp->left = tmp->right = NULL;

    // We put half nodes in left subtree
    tmp->lCount = (r-l)/;
    tmp->parent = par;

    // If string length is more
    if ((r-l) > LEAF_LEN)
    {
        tmp->str = NULL;
        node = tmp;
        int m = (l + r)/;
        createRopeStructure(node->left, node, a, l, m);
        createRopeStructure(node->right, node, a, m+, r);
    }
    else
    {
        node = tmp;
        int j = ;
        tmp->str = new char[LEAF_LEN];
        for (int i=l; i<=r; i++)
            tmp->str[j++] = a[i];
    }
}

// Function that prints the string (leaf nodes)
void printstring(Rope *r)
{
    if (r==NULL)
        return;
    if (r->left==NULL && r->right==NULL)
        cout << r->str;
    printstring(r->left);
    printstring(r->right);
}

// Function that efficiently concatenates two strings
// with roots root1 and root2 respectively. n1 is size of
// string represented by root1.
// root3 is going to store root of concatenated Rope.
void concatenate(Rope *&root3, Rope *root1, Rope *root2, int n1)
{
    // Create a new Rope node, and make root1 
    // and root2 as children of tmp.
    Rope *tmp = new Rope();
    tmp->parent = NULL;
    tmp->left = root1;
    tmp->right = root2;
    root1->parent = root2->parent = tmp;
    tmp->lCount = n1;

    // Make string of tmp empty and update 
    // reference r
    tmp->str = NULL;
    root3 = tmp;
}

// Driver code
int main()
{
    // Create a Rope tree for first string
    Rope *root1 = NULL;
    char a[] =  "Hi This is geeksforgeeks. ";
    int n1 = sizeof(a)/sizeof(a[]);
    createRopeStructure(root1, NULL, a, , n1-);

    // Create a Rope tree for second string
    Rope *root2 = NULL;
    char b[] =  "You are welcome here.";
    int n2 = sizeof(b)/sizeof(b[]);
    createRopeStructure(root2, NULL, b, , n2-);

    // Concatenate the two strings in root3.
    Rope *root3 = NULL;
    concatenate(root3, root1, root2, n1);

    // Print the new concatenated string
    printstring(root3);
    cout << endl;
    return ;
}
           

输出:

Hi This is geeksforgeeks. You are welcome here.
           

继续阅读