天天看点

字符串逆序(一)

一、普通的字符串逆序

例如,给定一个字符串 s,将 s 中的字符顺序颠倒过来,如 s = “abcd”,逆序后变成 “dcba”。可以采用多种方法对字符串进行逆序,以下将对其中的一些方法进行分析。

(1)直接分配一个与原字符串等长的字符数组,然后反向拷贝即可。

具体实现如下:

#include <iostream>

char *Reverse(char *s)
{
    char *q = s;
    while (*q)
        ++q;
    q--; // q指向字符串s的倒数第二个字符(即空字符前的那个字符)

    char *p = new char[sizeof(char)*(q - s + )]; // 包含空字符
    char *r = p; // r指向逆序后字符串的首部

    while (q >= s)
        *p++ = *q--;
    *p = '\0';

    return r;
}

int main(int argc, const char * argv[]) {

    char str[] = "I am a student";
    char *s = Reverse(str);

    printf("%s\n", s);

    return ;
}
           

注意点:

<1> new char[10]; 分配一块连续的内存,即一个数组,里面有10个元素。

<2> new char(10); 分配一块内存,有一个元素,该元素被初始化为10。

(2)原地逆序

原地逆序不允许额外分配空间,就是将字符串两边的字符逐个交换。例如,给定字符串 “abcd”,逆序的过程分别是交换字符a和d,交换字符b和c。如果最中间有两个字符(即需要反转的字符串长度为偶数),那就交换;如果最中间有一个字符(即需要反转的字符串长度为奇数),那就不需要碰它。还有就是最后一个用来标识字符串结尾的0x00字符不用动它。

实现方式有以下几种:

1)设置两个指针,分别指向字符串的头部和尾部,然后交换两个指针所指的字符,并向中间移动指针直到交叉。

具体实现如下:

#include <iostream>

char *Reverse(char *s) // 该函数应有返回值,或者建议使用引用型形参。
{
    char *p = s;
    char *q = s;

    while (*q)
        ++q;
    q--;
    while (q > p)
    {
        char temp = *q;
        *q-- = *p;
        *p++ = temp;
    }

    return s;
}

int main(int argc, const char * argv[]) {

    char str[] = "I am a student";
    char *s = Reverse(str);

    printf("%s\n", s);

    return ;
}
           

2)递归:思想很简单,每次交换首尾两个字符,中间部分则又变为和原来字符串同样的问题,因此可以通过递归的思想来解决这个问题;需要给定逆序的空间,调用方法Reverse( s, 0, strlen(s) - 1 ),对字符串 s 在区间 left 和 right 之间进行逆序。

具体实现如下:

#include <iostream>

char *Reverse(char *s, int left, int right)
{
    if (left >= right)
        return s;

    char temp = s[left];
    s[left] = s[right];
    s[right] = temp;

    return Reverse(s, left + , right - ); // 此处一定要带上return。
}

int main(int argc, const char * argv[]) {

    char str[] = "I am a student";
    char *s = Reverse(str, , (int)strlen(str)-); // 使用的是下标,不是长度,所以需要strlen(str)-1。

    printf("%s\n", s);

    return ;
}
           

注意:

该递归方法的缺陷:不能直接反转常量字符串,需要转换,而且需要额外分配空间,已经不满足原地逆序的要求。参考如下:

// 如果想用递归方法,反转字符串常量,需要做如下修改。
int main(int argc, const char * argv[]) {

    const char *str1 = "I am a student";
    int len = (int)strlen(str1); // 不包含空字符

    char str[len+];
    for (int j = ; j <= len; j++) { // 连同空字符一起拷贝过来
        str[j] = str1[j];
    }

    char *s = Reverse(str, , (int)strlen(str)-);

    printf("%s\n", s);

    return ;
}
           

3)非递归,同样指定逆序区间,和方法 1)没有本质区别,一个使用指针,一个使用下标。

具体实现如下:

#include <iostream>

char *Reverse(char *s, int left, int right)
{
    while (left < right)
    {
        char temp = s[left];
        s[left++] = s[right];
        s[right--] = temp;
    }

    return s;
}

int main(int argc, const char * argv[]) {

    char str[] = "I am a student";
    char *s = Reverse(str, , (int)strlen(str) - );

    printf("%s\n", s);

    return ;
}
           

(3)不允许使用临时变量的原地逆序

原地逆序虽然没有额外分配空间,但还是使用了临时变量,占用了额外的空间。如果不允许使用额外空间,主要有以下几种实现方法。

1)利用异或操作,实现两个变量的交换(按位异或)

#include <iostream>

char *Reverse(char *s)
{
    char *r = s;
    char *q = s;
    while (*q)
        ++q;
    q--; // q指向字符串s的倒数第二个字符(即空字符前的那个字符)

    while (q > r)
    {
        *q = *q ^ *r;
        *r = *q ^ *r;
        *q = *q ^ *r;
        q--;
        r++;
    }

    return s;
}

int main(int argc, const char * argv[]) {

    char str[] = "I am a student";
    char *s = Reverse(str);

    printf("%s\n", s);

    return ;
}
           

2)利用加减法,实现两个变量的交换

#include <iostream>

char *Reverse(char *s)
{
    char *r = s;
    char *q = s;
    while (*q)
        ++q;
    q--; // q指向字符串s的倒数第二个字符(即空字符前的那个字符)

    while (q > r)
    {
        *q = *q + *r;
        *r = *q - *r;
        *q = *q - *r;
        q--;
        r++;
    }

    return s;
}

int main(int argc, const char * argv[]) {

    char str[] = "I am a student";
    char *s = Reverse(str);

    printf("%s\n", s);

    return ;
}
           

3)使用字符串的结束字符 ‘\0’ 所在的位置作为交换空间,但这个有局限,只适合以 ‘\0’ 结尾的字符串,对于不支持这种字符串格式的语言,就不能使用了。

#include <iostream>

char *Reverse(char *s)
{
    char *r = s;
    char *q = s;
    while (*q)
        ++q;

    char *p = q - ;

    while (p > r)
    {
        *q = *p;
        *p-- = *r;
        *r++ = *q;
    }
    *q = '\0';

    return s;
}

int main(int argc, const char * argv[]) {

    char str[] = "I am a student";
    char *s = Reverse(str);

    printf("%s\n", s);

    return ;
}
           

小记:

交换2个数,常用的方法:

<1> 第一种方法,大家会借助第三个变量来实现:

如:tmp=A;A=B;B=tmp;

这种方法需要借助第三变量来实现;

<2> 第二种方法是利用加减法实现两个变量的交换,

如:A=A+B;B=A-B;A=A-B;

但是 如果 A+B 超出 A的范围,就会出错!极为不推荐 此方法

<3> 第三种方法是得用位异或运算来实现,也是效率最高的一种,在大量数据交换的时候,效率明显优于前两种方法,

如:A=A^B;B=A^B;A=A^B;

原理:利用一个数异或本身等于0和异或运算符合交换率。

异或运算的性质:

1.任意一个变量X与其自身进行异或运算,结果为0,即X^X=0

2.任意一个变量X与0进行异或运算,结果不变,即X^0=X

3.异或运算具有可结合性,即a^b^c=(a^b)^c=a^(b^c)

4.异或运算具有可交换性,即a^b=b^a

分析:

第一步: a = a ^ b;

完成后 a变量的结果为a ^ b

第二步: b = a ^ b;

此时赋值号右边的a保存的是a ^ b的值,那么将赋值号右边的a用a ^ b替换,

得到(a ^ b) ^ b=a ^ (b ^ b)=a ^0=a,

即经过第二步运算后b中的值为a,即b=a,将a换到了b里

第三步: a = a ^ b;

此时赋值号右边的a保存的仍然是a ^ b的值,不变,而赋值号右边的b已经是a 了,

将赋值号右边的a,b分别进行替换,

即此时赋值号右边a ^ b=(a ^ b)^ a=a ^ b^ a=a ^ a^ b=0^ b=b, 该值赋值给a,即a=b

即经过第三步运算后a中的值为b,即a=b,将b换到了a里

这样经过如上的三步骤,完成了交换两个变量a,b而无需借助第3个临时变量过程。

继续阅读