字符串逆序(一)

一、普通的字符串逆序
例如,给定一个字符串 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 + 2)]; // 包含空字符
    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 0;
}

注意点:
<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 0;
}

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 + 1, right - 1); // 此处一定要带上return。
}

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

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

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

    return 0;
}

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

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

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

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

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

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

    return 0;
}

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, 0, (int)strlen(str) - 1);

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

    return 0;
}

(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 0;
}

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 0;
}

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

#include <iostream>

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

    char *p = q - 1;

    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 0;
}

小记:
交换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个临时变量过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值