c++反转字符,算法优化与实现

c++反转字符
这是我在leetcode上看见的题目
尝试了几种写法,耗时均不同,这里先说一下第一种算法“交换法”
交换法反转字符就是从尾部一直向前挪移:
如:
abcd = dbca
这是第一次交换,第二次交换就是:
dcba
可以看到每次交换都是首位交换,以此向中心扩展,通俗易懂的说就是两者之间向中心点靠拢,每次交换会交换两个字符,所以交换时间是:o(s\2)
四个字符两次就可以完成反转
实现:
声明一个函数,这里leetcode使用的是vector容器,我们也一样.
void reverseString(std::vector& s) {
}
第二步编写一个循环:
注意循环条件是字符大小/2,因为上面说过每次交换是两个字符
for(int i = 0;i<(s.size()/2);++i){

}
这里我的写法为了省掉内存开辟空间以及内存交互的方式,使用加减法来保存要交换的字符,防止被覆盖,因为字符也是ascii码
s[i] += s[(s.size()-i)-1];
这里说一下这段代码
s[I]是指向从0开始的字符下标,比如abcd那么现在下标就是a
s[(s.size()-i)-1]
这段代码是算出字符尾部的坐标,每次-i是为了下一次的移动,比如现在是d,那么下次循环i是1,就是b与c的交换了.
最后的-1是为了跳过\0,c/c++里对字符结尾的定义是\0,不能把\0放到首位,如果\0在首位c/c++会认为这就是这段字符的结束.
这里+=是为了存储第一位的值,比如a的ascii是97,d的ascii码是100,那么相加就是197
此时s[i]=197相当于保存了两个字符
s[(s.size()-i)-1] = s[i] - s[(s.size()-i)-1];
这段代码就是把末尾的值减去s[i],也就是197-d的ascii码就等于了a的ascii码,这样尾部就有了a
同时还需要把首部的值在减去末尾的值,因为末尾的值刚好是a,那么197-a的ascii码就等于了d
s[i] -= s[(s.size()-i)-1];
完整代码:
void reverseString(std::vector& s) {
for(int i = 0;i<(s.size()/2);++i){
s[i]+=s[(s.size()-i)-1];
s[(s.size()-i)-1] = s[i] - s[(s.size()-i)-1];
s[i] -= s[(s.size()-i)-1];
}
}

c++反转字符,算法优化与实现_第1张图片

可惜力扣那边测试,我们的算法还是比较慢的,这里就要想想别的办法,因为我们上面的算法造成了一部分的计算,还有内存数据交换,还是比较耗时的
c++内置了一种函数move,这个函数的用法是直接将一个值变成左值,也就是立即数的方式,直接跟着指令集去运算,那么就可以直接不用内存的方式去做这些工作了
比如:
int a = 10;
Int b = 0;
b = a;
如果想要让b=a,cpu需要到a的内存里取数据,复制一份数据然后再到b的内存地址里,把数据写入
这个过程比较繁琐,但是move不一样,这个函数可以把a里的值直接变成左值返回
b = std::move(a)
则变成了:b = 10;
如果不实用move则会翻译成:b = &a;
这里不用指令集的方式表示,这样表示更加的直观,
汇编的方式就是:
move方式:
mov byte ptr ds:[b],10
非move方式:
mov ax, byte ptr ds:[a] //取出a的值
mov byte ptr ds:[b],ax //放入b中
这里汇编是大致写了一下,详细可以去返汇编看一下
我们修改一下代码加上move试试:
for(int i = 0;i<(s.size()/2);++i){
s[i]+=std::move(s[(s.size()-i)-1]);
s[(s.size()-i)-1] = std::move(s[i] - s[(s.size()-i)-1]);
s[i] -= std::move(s[(s.size()-i)-1]);
}
看下力扣测试:
c++反转字符,算法优化与实现_第2张图片
速度一下快了很多,也证实了我们上面说的.有时候算法固然重要,但是编译优化也很重要,很多硬件加速就是这样的方法,使用一些特定的指令集.
上面这套算法因为有了move的方式,可以省略内存访问,这里我们就完全没必要在进行运算了,直接用一个内存变量来存储临时的值:
char _c = s[i];
s[i] = std::move(s[(s.size()-i)-1]);
s[(s.size()-i)-1] = std::move(_c);
因为不用计算,也不用去取地址,看一下力扣的测试:
c++反转字符,算法优化与实现_第3张图片
如果你想更懒一点,你可以使用swap函数直接实现,不过这样就丢失了这道题的意义了.
for(int i = 0;i<(s.size()/2);++i){
std::swap(s[i],s[(s.size()-i)-1]);
}
Move的方式就是我在查阅c++的代码时找到的加速方法,因为我发现c++提供的一些函数,或是c,提供的一些基础函数总是比我们自己写的要快,我一开始以为是他们的算法比较好,后来我去查阅了一些glibc的代码和c++的一些实现,发现内部内嵌汇编极多,都是使用汇编优化,还有一些编译器指令优化比如move,虽然说可以调整优化编译器等级,但是这些细节往往是最重要的.
Swap函数的实现:
template inline
void _Swap(_Ty& _Left, _Ty& _Right)
{ _Ty _Tmp = std::move(_Left);
_Left = std::move(_Right);
_Right = std::move(_Tmp);
}

注意c++的实现里有一个inline声明,这是一个类似c语言的宏展开的声明
比如你的函数add
前面加上inline声明以后,你在别处调用的时候,会直接变成实现代码,省去了函数调用的栈保存地址跳转等等开销. 这是c++特有的关键字

你可能感兴趣的:(笔记)