返回一个在两个给定字符串之间进行排序的新字符串

时间:2016-08-12 17:20:52

标签: string algorithm sorting

给出两个字符串a和b,其中a按字典顺序排列< b,我想返回一个字符串c,使得< c<湾用例是在由这些键排序的数据库中插入节点。您可以根据需要指定a,b和c的格式,只要可以在插入时生成初始值和新值。

这是否有实用的算法?

5 个答案:

答案 0 :(得分:7)

最小化字符串长度

如果要将字符串长度保持在最小值,可以在左右字符串之间创建一个按字典顺序排列的字符串,以便有空间插入其他字符串,并且只有在绝对必要时才创建更长的字符串。

我将假设一个字母[a-z],以及一个字典顺序,其中空白空间出现在' a'之外,例如" AB"来自" abc"。

基本案例

首先复制字符串开头的字符,直到遇到第一个区别,可能是两个不同的字符,或左字符串的结尾:

abcde ~ abchi  ->  abc  +  d ~ h  
abc   ~ abchi  ->  abc  +  _ ~ h  

然后通过在左字符(或字母表的开头)和右字符之间追加字母表中间的字符来创建新字符串:

abcde ~ abchi  ->  abc  +  d ~ h  ->  abcf  
abc   ~ abchi  ->  abc  +  _ ~ h  ->  abcd  

连续字符

如果两个不同的字符按字典顺序连续,首先复制左边的字符,然后将字符添加到左边的下一个字符和字母的末尾之间:

abhs ~ abit  ->  ab  +  h ~ i  ->  abh  +  s ~ _  ->  abhw
abh  ~ abit  ->  ab  +  h ~ i  ->  abh  +  _ ~ _  ->  abhn

如果左侧字符串中的下一个字符是一个或多个z,则复制它们并将字符追加到第一个非z字符和字母结尾之间:

abhz   ~ abit  ->  ab  +  h ~ i  ->  abh  +  z ~ _  ->  abhz  +  _ ~ _  ->  abhzn  
abhzs  ~ abit  ->  ab  +  h ~ i  ->  abh  +  z ~ _  ->  abhz  +  s ~ _  ->  abhzw  
abhzz  ~ abit  ->  ab  +  h ~ i  ->  abh  +  z ~ _  ->  ... ->  abhzz  +  _ ~ _  ->  abhzzn

正确的字符是a或b

你永远不应该通过附加' a'来创建一个字符串。到左边的字符串,因为这将创建两个按字典顺序排列的连续字符串,其间不能添加其他字符串。解决方案是始终在字母表的开头和右边的字符串中的下一个字符之间追加一个附加字符:

abc  ~ abcah   ->  abc  +  _ ~ a  ->  abca  +  _ ~ h  ->  abcad  
abc  ~ abcab   ->  abc  +  _ ~ a  ->  abca  +  _ ~ b  ->  abcaa  +  _ ~ _  ->  abcaan  
abc  ~ abcaah  ->  abc  +  _ ~ a  ->  abca  +  _ ~ a  ->  abcaa  +  _ ~ h  ->  abcaad  
abc  ~ abcb    ->  abc  +  _ ~ b  ->  abca  +  _ ~ _  ->  abcan

代码示例

以下是演示该方法的代码段。它有点繁琐,因为JavaScript,但实际上并不复杂。要生成第一个字符串,请使用两个空字符串调用该函数;这将生成字符串" n"。要在最左边或最右边的字符串之前插入字符串,请使用该字符串和空字符串调用该函数。



function midString(prev, next) {
    var p, n, pos, str;
    for (pos = 0; p == n; pos++) {               // find leftmost non-matching character
        p = pos < prev.length ? prev.charCodeAt(pos) : 96;
        n = pos < next.length ? next.charCodeAt(pos) : 123;
    }
    str = prev.slice(0, pos - 1);                // copy identical part of string
    if (p == 96) {                               // prev string equals beginning of next
        while (n == 97) {                        // next character is 'a'
            n = pos < next.length ? next.charCodeAt(pos++) : 123;  // get char from next
            str += 'a';                          // insert an 'a' to match the 'a'
        }
        if (n == 98) {                           // next character is 'b'
            str += 'a';                          // insert an 'a' to match the 'b'
            n = 123;                             // set to end of alphabet
        }
    }
    else if (p + 1 == n) {                       // found consecutive characters
        str += String.fromCharCode(p);           // insert character from prev
        n = 123;                                 // set to end of alphabet
        while ((p = pos < prev.length ? prev.charCodeAt(pos++) : 96) == 122) {  // p='z'
            str += 'z';                          // insert 'z' to match 'z'
        }
    }
    return str + String.fromCharCode(Math.ceil((p + n) / 2)); // append middle character
}

var strings = ["", ""];
while (strings.length < 100) {
    var rnd = Math.floor(Math.random() * (strings.length - 1));
    strings.splice(rnd + 1, 0, midString(strings[rnd], strings[rnd + 1]));
    document.write(strings + "<br>");
}
&#13;
&#13;
&#13;

下面简单地转换为C.使用空的以空字符结尾的字符串调用函数以生成第一个字符串,或者在最左边或最右边的字符串之后插入。字符串缓冲区buf应足够大,以容纳一个额外的字符。

int midstring(const char *prev, const char *next, char *buf) {
    char p = 0, n = 0;
    int len = 0;
    while (p == n) {                                           // copy identical part
        p = prev[len] ? prev[len] : 'a' - 1;
        n = next[len] ? next[len] : 'z' + 1;
        if (p == n) buf[len++] = p;
    }
    if (p == 'a' - 1) {                                        // end of left string
        while (n == 'a') {                                     // handle a's
            buf[len++] = 'a';
            n = next[len] ? next[len] : 'z' + 1;
        }
        if (n == 'b') {                                        // handle b
            buf[len++] = 'a';
            n = 'z' + 1;
        }
    }
    else if (p + 1 == n) {                                     // consecutive characters
        n = 'z' + 1;
        buf[len++] = p;
        while ((p = prev[len] ? prev[len] : 'a' - 1) == 'z') { // handle z's
            buf[len++] = 'z';
        }
    }
    buf[len++] = n - (n - p) / 2;                              // append middle character
    buf[len] = '\0';
    return len;
}

平均字符串长度

最好的情况是以随机顺序插入元素。实际上,当以伪随机顺序生成65,536个字符串时,平均字符串长度约为4.74个字符(理论最小值,在移动到较长字符串之前使用每个组合,将为3.71)。

最糟糕的情况是按顺序插入元素,并始终生成新的最右边或最左边的字符串;这将导致反复出现的模式:

n, u, x, z, zn, zu, zx, zz, zzn, zzu, zzx, zzz, zzzn, zzzu, zzzx, zzzz...  
n, g, d, b, an, ag, ad, ab, aan, aag, aad, aab, aaan, aaag, aaad, aaab...  

在每第四个字符串后添加一个额外的字符。

如果您有要为其生成密钥的现有有序列表,请使用如下所示的算法生成按字典顺序排列的按键,然后使用上述算法在插入新元素时生成新密钥。

代码检查需要多少个字符,最低有效数字需要多少个不同的字符,然后在字母表中的两个选项之间切换以获得正确数量的键。例如。具有两个字符的键可以具有676个不同的值,因此如果您要求1600个键,则每个双字符组合需要1.37个额外的键,因此在每个双字符键之后另外一个键(&#39; n&#39;)或附加两个(&#39; j&#39; r&#39;)字符,即:aan ab abj abr ac acn ad adn ae aej aer af afn ...(跳过最初的&#39; aa&#39;)。

&#13;
&#13;
function seqString(num) {
    var chars = Math.floor(Math.log(num) / Math.log(26)) + 1;
    var prev = Math.pow(26, chars - 1);
    var ratio = chars > 1 ? (num + 1 - prev) / prev : num;
    var part = Math.floor(ratio);
    var alpha = [partialAlphabet(part), partialAlphabet(part + 1)];
    var leap_step = ratio % 1, leap_total = 0.5;
    var first = true;
    var strings = [];
    generateStrings(chars - 1, "");
    return strings;

    function generateStrings(full, str) {
        if (full) {
            for (var i = 0; i < 26; i++) {
                generateStrings(full - 1, str + String.fromCharCode(97 + i));
            }
        }
        else {
            if (!first) strings.push(stripTrailingAs(str));
            else first = false;
            var leap = Math.floor(leap_total += leap_step);
            leap_total %= 1;
            for (var i = 0; i < part + leap; i++) {
                strings.push(str + alpha[leap][i]);
            }
        }
    }
    function stripTrailingAs(str) {
        var last = str.length - 1;
        while (str.charAt(last) == 'a') --last;
        return str.slice(0, last + 1);
    }
    function partialAlphabet(num) {
        var magic = [0, 4096, 65792, 528416, 1081872, 2167048, 2376776, 4756004,
                     4794660, 5411476, 9775442, 11097386, 11184810, 22369621];
        var bits = num < 13 ? magic[num] : 33554431 - magic[25 - num];
        var chars = [];
        for (var i = 1; i < 26; i++, bits >>= 1) {
            if (bits & 1) chars.push(String.fromCharCode(97 + i));
        }
        return chars;
    }

}
document.write(seqString(1600).join(' '));
&#13;
&#13;
&#13;

答案 1 :(得分:2)

这是实现这一目标的一种非常简单的方法,可能远非最佳(取决于您所称的最佳方式)。

我只使用ab。我想你可以推广使用更多的字母。

两个简单的观察结果:

  1. 在另一个字符串后创建的新字符串很简单:只需附加一个或多个字母。例如,abba&lt; abbab
  2. 如果xx结尾,则始终可以保证在 之前创建一个新字符串<{em>}。现在,将b替换为b并附加一个或多个字母。例如,a&gt; abbab
  3. 该算法现在非常简单。以abbaaba作为哨兵开始。在两个现有密钥bx之间插入新密钥:

    • 如果yx的前缀:新密钥为y,结尾y替换为b
    • 如果ab不是x的前缀:新密钥为y并附加x

    示例运行:

    b

答案 2 :(得分:1)

以下是与PL / pgSQL直接在我的PostgreSQL数据库中实现的m69答案的等效功能:

create or replace function app_public.test() returns text[] as $$
declare
  v_strings text[];
  v_rnd int;
begin
  v_strings := array_append(v_strings, app_public.mid_string('', ''));

  FOR counter IN 1..100 LOOP
    v_strings := v_strings || app_public.mid_string(v_strings[counter], '');
  END LOOP;
  return v_strings;
end;
$$ language plpgsql strict volatile;

经过此功能测试:

"strings": [
  "n",
  "u",
  "x",
  "z",
  "zn",
  "zu",
  "zx",
  "zz",
  "zzn",
  "zzu",
  "zzx",
  "zzz",
  "zzzn",
  "zzzu",
  "zzzx",
  "zzzz",
  "...etc...",
  "zzzzzzzzzzzzzzzzzzzzzzzzn",
  "zzzzzzzzzzzzzzzzzzzzzzzzu",
  "zzzzzzzzzzzzzzzzzzzzzzzzx",
  "zzzzzzzzzzzzzzzzzzzzzzzzz",
  "zzzzzzzzzzzzzzzzzzzzzzzzzn"
]

这将导致:

{{1}}

答案 3 :(得分:0)

@m69 答案的简化/修改。

假设字符串不以“零”结尾(这通常是必需的,因为在 s 和 s 的一些零扩展之间只有有限数量的字符串),这些字符串与 [0, 1)。所以我将讨论十进制,但同样的原则适用于任意字母表。

我们可以对左边的字符串进行零扩展(0.123 = 0.123000...)和对右边的字符串进行 9 扩展(0.123 = 0.122999...),这自然导致

// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0
template <typename Str, typename Digit>
Str midpoint(const Str left, const Str right, Digit zero, Digit nine) {
  Str mid;
  for (auto i = left.size() - left.size();; ++i) {
    Digit l = i < left.size() ? left[i] : zero;
    Digit r = i < right.size() ? right[i] : nine;
    if (i == right.size() - 1) --r;
    // This is mid += (l + r + 1)/2
    // without needing Digit to be wider than nine.
    r -= l;
    mid += l + r/2 + (r&1);
    if (mid.back() != l) break;
  }
  return mid;
}

答案 4 :(得分:0)

以防万一有人需要它。这是 Kotlin 中的相同算法。它有效,但可能可以用更好的方式编写。

    fun midString(prev: String?, next: String?): String {
        val localPrev = prev ?: ""
        val localNext = next ?: ""
    
        var p: Int
        var n: Int
        var str: String
    
        // Find leftmost non-matching character
        var pos = 0
        do {
            p = if (pos < localPrev.length) localPrev[pos].toInt() else 96
            n = if (pos < localNext.length) localNext[pos].toInt() else 123
            pos++
        } while (p == n)
    
        str = localPrev.substring(0, pos - 1)           // Copy identical part of string
        if (p == 96) {                                  // Prev string equals beginning of next
            while (n == 97) {                           // Next character is 'a'
                n = if (pos < localNext.length) localNext[pos++].toInt() else 123 // Get char from next
                str += 'a'                              // Insert an 'a' to match the 'a'
            }
            if (n == 98) {                              // Next character is 'b'
                str += 'a'                              // Insert an 'a' to match the 'b'
                n = 123                                 // Set to end of alphabet
            }
        }
        else if (p + 1 == n) {                          // Found consecutive characters
            str += p.toChar()                           // Insert character from prev
            n = 123                                     // Set to end of alphabet
    
            p = if (pos < localPrev.length) localPrev[pos++].toInt() else 96
            while (p == 122) { // p='z'
                str += 'z'                              // Insert 'z' to match 'z'
                p = if (pos < localPrev.length) localPrev[pos++].toInt() else 96
            }
        }
        return str + ceil((p + n) / 2.0).toChar()   // Append middle character
    }