实现一个函数,可以左旋字符串中的i个字符
宇宙免责声明:以下的方法命名纯属瞎编
例:将“abcdef”左旋2个字符,得到“cdefab”
int main()
{
char str[] = "abcdef";
printf("%s\n", str_loop(str, 2));
return 0;
}
实现方式具体分为两种
1 交换法
即通过交换,使后面的字符向前移动,前面的字符向后移动,每个步骤都是如此,直到最前面的字符移动到最后面。
我们定义一个char类型的指针 left ,让 left 与 left + 1 内的值交换,这就达成了一次交换,“abcdef” 就变为了 “bacdef” ,接着让 left++ 使其指向第二个字符,此时 left 依旧指向准备下一次交换循环,下一次交换依旧是将 left 指向的值与 left + 1指向的值交换
char c = *left;
*left = *(left + 1);
*(left + 1) = c;
left++;
用循环将其括起来,直到判断 left + 1 处的字符为 '\0' ,判断字符串已经结束,不能再向后交换了,本次循环结束(别忘了 left 的定义,left 因为始终指向要后移的字符,所以只需要在 while 循环外定义一次就行)
char* left = str;
while (*(left + 1) != '\0')
{
char c = *left;
*left = *(left + 1);
*(left + 1) = c;
left++;
}
此时字符串 “abcdef” 已经完成左旋一次,变为了 “bcdefa”
当我们需要左旋两次时,在外部再加一次计数循环即可,顺带也可以把交换这个步骤封装成一个函数
void char_exchange(char* a, char* b) //交换函数
{
char c = *a;
*a = *b;
*b = c;
}
char* str_loop_1(char* str, int i) //(字符串地址,左旋次数)
{
int n = 0;
char* ret = str; //返回值是字符串首地址,先记录好,这里不记录也行
for (n = 0; n < i; n++) //可以直接返回str
{
char* left = str; //定义 left 指针
while (*(left + 1) != '\0') //left 指向 '\0' 证明单次左旋完成,不再循环
{
char_exchange(left, left + 1); //交换
left++; //left 记得指向下一个字符
}
}
return ret; //返回字符串地址,方便调用
}
当然这个函数还是有一定优化空间的
每次让首字符与后一个字符交换似乎就有些费时间了,我们可以这样想,将首字符看做 left 部分,将剩余部分看做 right 部分,事实上我们只需要将 left 部分与 right 部分交换即可
我们只需要先将首字符'a',提取出来存放到临时变量 left 里,将指针 right 指向剩余部分的首字符'b'
char left = *str;
char* right = str + 1;
再将 right 中的值赋值给上一个字符的空间(即赋值给 right - 1 指向的空间),这样我们就由原来的 “abcdef” 变为了 “bbcdef”,'a'存进了临时变量 left 中
*(right - 1) = *right;
right++;
只要在 right 左移的部分外面再加一层计数循环,就可以让 right 的剩余部分全部左移完毕,此时字符串中为“bcdeff”
char left = *str;
char* right = str + 1;
while (*right != '\0')
{
*(right - 1) = *right;
right++;
}
*(right - 1) = left;
最后只需要将存进了临时变量的 'a' 赋值给字符串的除了 '\0' 外的最后一个空间即可,此时字符串完成单次左旋,变为 “bcdefa”
最后只需要如法炮制在后面加一个计数循环就可以实现左旋多次了
char* str_loop_2(char* str, int i)
{
char* ret = str; //返回值为字符串首地址
int n = 0;
for (n = 0; n < i; n++)
{
char left = *str; //首字符赋值给临时变量
char* right = str + 1; //right 指向首字符后面的字符
while (*right != '\0')
{
*(right - 1) = *right; //除了首字符外剩下的字符向前移动
right++;
}
*(right - 1) = left; //最后把挪给临时变量的首字符赋值回给末字符
}
return ret; //返回值返回字符串首元素地址
}
2 逆序法
这个方法非常巧妙
简单来说,如果我们需要左旋2个字符,就会将最左边的两个字符移向右边,而剩下的只剩下右边的几个字符,将其分为左右两个区域
将左区字符逆序得到“bacdef”
再将右区字符逆序得到“bafedc”
最后将整个字符串逆序得到“cdefab”
逆序只需要最左与最右交换,左指针右移一位,右指针左移一位,如此往复,直到左指针大于右指针就停下(因为左指针不应该大于右指针对吧)
我们把逆序写成函数,完整代码就如下
void reverse(char* left, char* right)
{
for(; right > left; right--, left++)
{
char tmp = *left;
*left = *right;
*right = tmp;
}
}
char* str_loop_3(char* str, int i)
{
int sz = (int)strlen(str);
i %= sz; //防止超范围导致野指针
char* ret = str;
reverse(str, str + i - 1); //左
reverse(str + i, str + sz - 1); //右
reverse(str, str + sz - 1); //整个
return ret;
}
你说巧不巧(doge)
3 优化
我们发现,对“abcdef”左旋2次和对其左旋8次得到的结果相同
我们就可以靠输入的左旋次数模上 len (字符串长度)得到实际计算机需要执行左旋的次数
int sz = (int)strlen(str);
i %= sz;
例如对 “abcdef” 左旋8次,计算过后实际就只需要左旋2次即可,结果相同,都是“cdefab”
所以我们可以在以上的交换法里用上这种优化方法,极大地提高代码运行效率!
而在逆序法里这个优化应该是必须的,否则就会导致野指针!
4 所有代码
这里把所有的代码贴上,方便查看
void char_exchange(char* a, char* b) //交换函数
{
char c = *a;
*a = *b;
*b = c;
}
char* str_loop_1(char* str, int i) //(字符串地址,左旋次数)
{
int n = 0;
char* ret = str; //返回值是字符串首地址,先记录好,这里不记录也行
for (n = 0; n < i; n++) //可以直接返回str
{
char* left = str; //定义 left 指针
while (*(left + 1) != '\0') //left 指向 '\0' 证明单次左旋完成,不再循环
{
char_exchange(left, left + 1); //交换
left++; //left 记得指向下一个字符
}
}
return ret; //返回字符串地址,方便调用
}
char* str_loop_2(char* str, int i)
{
char* ret = str; //返回值为字符串首地址
int n = 0;
for (n = 0; n < i; n++)
{
char left = *str; //首字符赋值给临时变量
char* right = str + 1; //right 指向首字符后面的字符
while (*right != '\0')
{
*(right - 1) = *right; //除了首字符外剩下的字符向前移动
right++;
}
*(right - 1) = left; //最后把挪给临时变量的首字符赋值回给末字符
}
return ret; //返回值返回字符串首元素地址
}
void reverse(char* left, char* right)
{
for(; right > left; right--, left++)
{
char tmp = *left;
*left = *right;
*right = tmp;
}
}
char* str_loop_3(char* str, int i)
{
int sz = (int)strlen(str);
i %= sz; //防止超范围导致野指针
char* ret = str;
reverse(str, str + i - 1); //左
reverse(str + i, str + sz - 1); //右
reverse(str, str + sz - 1); //整个
return ret;
}
感谢点开并认真看到这里!!