This commit is contained in:
hairrrrr
2020-03-22 21:04:21 +08:00
parent 17d5701ecb
commit 34194cbc1a
8 changed files with 497 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
#define _CRT_SECURE_NO_WARNINGS 1
// atoi 实现
// ElementType atoi( const char *str );
//转译 str 所指的字节字符串中的整数值。
//舍弃任何空白符,直至找到首个非空白符,然后接收尽可能多的字符以组成合法的整数表示,并转换之为整数值。合法的整数值含下列部分:
//(可选) 正或负号
//数位
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
typedef long long ElementType;
ElementType MyAtoi(const char* str);
ElementType StrToNum(int numbers[], int size);
int main(void) {
char str[MAXSIZE];
ElementType NumInStr;
scanf("%s", str);
NumInStr = MyAtoi(str);
printf("%lld\n", NumInStr);// 转换成大的类型需要改变 printf 的转换说明
return 0;
}
ElementType MyAtoi(const char* str) {
ElementType ret = 0;
int isFirstNumber = 1;
int flg = 1;
int numbers[MAXSIZE];
int size = 0;
while (*str != '\0') {
if (*str >= '0' && *str <= '9') {
// 判断这个数是否为第一个出现的数字,如果是,我们要判断前一个字符是否是 - 号
if (isFirstNumber) {
if (*(str - 1) == '-')
flg = -1;
isFirstNumber = 0;
}
// 直接将字符转化为数字放入数组内,数组放置的是目标数字的每一位(不带权重)。
numbers[size++] = *str - '0';// 数字对应的 ACSII 码值减去 0对应的 ASCII 值就是实际上数组表示的值
}
++str;
}
ret = StrToNum(numbers, size);
return (flg * ret);
}
ElementType StrToNum(int numbers[], int size) {
ElementType ret = 0;
ElementType weight = 1;
int i;
for (i = size - 1; i >= 0; i--) {
ret += numbers[i] * weight;
weight *= 10;
}
return ret;
}

View File

@@ -0,0 +1,82 @@
#define _CRT_SECURE_NO_WARNINGS 1
// 这是 atoi 实现的另一个版本
// 上一个版本其实和库里面的 atoi 实现有的地方不是一致的
// 比如库函数 atoi 处理字符串 "abc123", 返回的结果是 0 ,而上一个我实现的 MyAtoi 会返回 123
// "+-123", 返回的结果是 0 而MyAtoi 会返回 -123
// 其实我觉得我都 atoi 函数更强大哈哈
// 但是我们也应该规范一点,毕竟是库函数
#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>
int MyAtoi_2(const char* str) {
int flg = 1;
int ret = 0;
if (str == NULL || str == '\0') {
return 0;
}
// 判断是否为空白字符(空格,换行,制表,翻页 ...
while (isspace(*str)) {
str++;
}
// 判断是否为正负号
// 用库函数 atoi 试一下就知道 "+-123" 这种字符串是无法转成数字的,所以这是一种要处理的情况
// 如果符号位的下一个不是数字,则返回 0
if (*str == '+' || *str == '-' ) {
if (*(str + 1) == NULL) {
return 0;
}
if (!isdigit(*(str + 1))) {
return 0;
}
if (*str == '-') {
flg = -1;
}
str++;
}
// 处理数字字符
while (*str != '\0') {
if (isdigit(*str)) {
// 判断一下会不会超过 int 的范围
if (((unsigned long)ret * 10 + (*str - '0')) > INT_MAX) {
return INT_MAX;
}
ret = ret * 10 + (*str - '0');
// 在 MyAtoi 中,我选择用数组存储每一位,然后从后向前依次乘以每一位的权重
// 但是这样直接加一个数之前先乘以 10 的做法更为简洁
}
else {
return 0;
}
str++;
}
return ret * flg;
}
int main(void) {
char str[100];
int ret;
scanf("%s", str);
ret = MyAtoi_2(str);
printf("%d\n", ret);
return 0;
}

View File

@@ -0,0 +1,70 @@
#define _CRT_SECURE_NO_WARNINGS 1
// strncat 实现
#include<stdio.h>
#include<assert.h>
char* MyStrncat(char* dest, const char* src, int count);
int main(void) {
char dest[100];
char src[50];
int count = 0;
printf("Enter string dest: ");
scanf("%s", dest);
printf("Enter string src: ");
scanf("%s", src);
printf("Enter number of character(s) copy from src to dest: ");
scanf("%d", &count);
MyStrncat(dest, src, count);
printf("%s\n", dest);
return 0;
}
// 其实我觉得这取实现程序不如不写。这种写法你上网一查都是一大片一大片的,
// 乍一看感觉是对的,但是自己想一想,这是很危险的写法。当让你也可以归结于
// C语言本身的语法不好但是其实是我们的水品还没到罢了我们想不到更好的写法
// 我们根本没学精 C但是这糟糕的函数是 C 的创始人写出来的啊!似乎可以为我们找一些接口。
// 现在我们来说一下这样的写法有什么漏洞,首先,我们看一看 cppreference 上给的定义:
//后附来自 src 所指向的字符数组的至多 count 个字符,到 dest 所指向的空终止字节字符串的末尾,
//若找到空字符则停止。字符 src[0] 替换位于 dest 末尾的空终止符。
//始终后附终止空字符到末尾(故函数可写入的最大字节数是 count + 1 )。
//若目标数组没有对于 dest 和 src 的首 count 个字符加上终止空字符的足够空间,
//则行为未定义。若源与目标对象重叠,则行为未定义。若 dest 不是指向空终止字节字符串的指针,
//或 src 不是指向字符数组的指针,则行为未定义。
// 现在我来说一下我认为这个程序可能存在的问题:
// 1.dest 函数本身加上 count 个字符是否会造成数组越界?
// 2.dest 函数有没有 '\0' 我没有检测。如果没有,则很有可能造成越界,并且将造成非法内存访问和改写。
// 3.如果 src 没有 '\0' 而 count 又大于 src字符数组有意义内容的大小或者 src字符数组本身的大小
// 这时也会造成非法内存访问。
// 4.src 是不是指向字符数组的指针?
// 如果还有问题欢迎补充如果你有更好的解法C语言欢迎告诉我我会加上你的真知灼见私信我即可
char* MyStrncat(char* dest, const char* src, int count) {
assert(dest && src);
char* NewStr = dest;
// 循环结束 NewStr 指向 dest 字符串 '\0' 所在位置
while (*NewStr != '\0') {
++NewStr;
}
while (count-- && (*NewStr++ = *src++));
*NewStr = '\0';
return dest;
}

View File

@@ -0,0 +1,52 @@
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
char* MyStrncpy(char* dest, char* src, int count);
int main(void) {
char dest[100];
char src[50];
int count = 0;
printf("Enter string dest: ");
scanf("%s", dest);
printf("Enter string src: ");
scanf("%s", src);
printf("Enter number of character(s) copy from src to dest: ");
scanf("%d", &count);
MyStrncpy(dest, src, count);
printf("%s\n", dest);
return 0;
}
// 这个函数同样是考虑不周全的:
// 1. count 如果大于 字符数组dest 怎么办?
// 2. count 大于 src 而 src 没有空字符怎么办?
char* MyStrncpy(char* dest,const char* src, int count) {
assert(dest && src);
char* begin = dest;
while (count-- && (*begin++ = *src++));
// count 如果小于 src 的长度,那么得到的字符串是不含空字符的。
// 加上 \0 是保险的。
if (count + 1 == 0) {
*begin = '\0';
}
else {
while (count--) {
*begin++ = *src++;
}
}
return dest;
}

View File

@@ -0,0 +1,77 @@
#define _CRT_SECURE_NO_WARNINGS 1
// 找单身狗问题:
// 1. 现有一个数组,只有一个元素只出现了一次,其他元素都出现了两次,请找出这个元素。
// 2. 如果数组中有两个元素只出现了一次,其他都出现了两次,请找出这两个元素。
// 这两个问题都应用到了按位异或
// 如果两个相同的数按位异或结果为零
// 我们在前面的 两数平均值求法 中用异或来求出两数 二进制位 中 不同的位 表示的数的 和
#include<stdio.h>
int FindOneSingleDog(int arr[], int size) {
int Dog = arr[0];
for (int i = 1; i < size; i++) {
Dog = Dog ^ arr[i];
}
return Dog;
}
void FindTwoSingleDog(int arr[], int size, int* Dog1, int* Dog2) {
int rst = arr[0];
int leftPos = 0;
for (int i = 1; i < size; i++) {
rst = rst ^ arr[i];
}
//此时 rst 等价于 Dog1 ^ Dog2, 这个结果的比特位一定有一个是 1
// 找到第一个是 1 的比特位
for (leftPos = 0; leftPos < 32; leftPos++) {
if ((rst & (1 << leftPos)) == 1) {
break;
}
}
// 将数组元素分成两部分,一部分 leftPos 位为 1另一部分不为 1
// 那么 Dog1 和 Dog2 就在不同的组内了。再次按位异或,结果就是 Dog1 和 Dog2 的值
*Dog1 = 0;
*Dog2 = 0;
for (int i = 0; i < size; i++) {
// leftPos 为 1 的组
if ((arr[i] & (1 << leftPos)) == 1) {
*Dog1 = *Dog1 ^ arr[i];
}
else {
*Dog2 = *Dog2 ^ arr[i];
}
}
}
int main(void) {
int arr1[] = { 1, 2, 3, 4, 1, 2, 3 };
int size1 = sizeof(arr1) / sizeof(arr1[0]);
int Dog;
int arr2[] = { 1, 1, 2, 2, 3, 4 };
int size2 = sizeof(arr2) / sizeof(arr2[0]);
int Dog1, Dog2;
Dog = FindOneSingleDog(arr1, size1);
FindTwoSingleDog(arr2, size2, &Dog1, &Dog2);
return 0;
}

View File

@@ -0,0 +1,51 @@
#define _CRT_SECURE_NO_WARNINGS 1
// 今天在听课的时候听到一个老师讲 平均数求法,一时之间我竟无从下手,其实这个问题很早之前我们老师就说过了。
// 虽然这种题确实是一种考验计算机专业的人的专业度的问题,但是大多老师讲完之后恐怕根本也不会去给你布置
// 练习来强化你的记忆,所以你可能会很快忘记这种特殊的解法(也许是我太卑微了,大家都过目不忘??)。
//所以,大多时候,讲到这个的老师无非是为了炫技,让你觉得他很专业。
// 但是我忘记了是真的,我没能想出来也是真的,我真菜啊,所以,我决定动手写一下
#include<stdio.h>
int main(void) {
int a = 10;
int b = 11;
double aver;
int Aver;
// 方法1直接求和除以2 这种方法的问题是:如果 a + b 会超过 int 最大能表示的范围,那结果就不是你想要的了
// 方法2
// 确定两个的大小
int max = a > b ? a : b;
int min = a < b ? a : b;
// 平均值等于:大 - 后面这一坨 或者 小 + 后面这一坨
aver = max - (max - min) / 2.0;
// 比较僵硬的是,整数的平均值要用浮点数来表示,不然可能会丧失精度
printf("%.2f\n", aver);
// 方法3这个方法可以说就是炫技的地方所在了但是位运算确实效率是比较高的。
Aver = (a & b) + ((a ^ b) >> 1);
printf("%d\n", Aver);
// a & b 得到的是 ab 相同二进制位所表示的数
// 比如 2 和 2 相同,那他们的平均值就是 2
// a ^ b 表示 a 异或 b得到的是 ab 不同二进制位的和表示的数,除以 2就是这部分两数的平均值。
// 其实这个思想挺像 方法2 的思想,就将数分为两部分,依靠 2 进制的性质进行计算。
// 位运算会向下取整,如果要求平均值有小数部分,那这个方法也就僵硬了
// 参考博客https://blog.csdn.net/weixin_43214609/article/details/83547313?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task
return 0;
}

View File

@@ -0,0 +1,85 @@
#define _CRT_SECURE_NO_WARNINGS 1
// 字符串的左旋问题包含两点:
// 1. 左旋字符串
// 2. 判断字符串是否是另一个字符串左旋得到的
// 这次我们一起处理这两个问题。
#include<stdio.h>
#include<string.h>
// 让字符串左旋,我们可以先实现一个函数左旋一个字符,然后用另一个函数去调用这个函数,实现旋转多个字符
void LeftRotateOne(char str[], int size) {
char tmp;
int i;
if (str == NULL)
return;
if (size == 1)
return;
// 先备份字符串第一个元素
tmp = str[0];
// 将字符串从第二个元素开始依次向前移动一位
for (i = 1; i < size; i++) {
str[i - 1] = str[i];
}
str[size - 1] = tmp;
}
// 这样的算法,如果 size 比较大,效率是很低的,因为数组每次移动复杂度都是 O(n)
void LeftRotateN(char str[], int size, int n) {
if (str == NULL)
return;
// 求模是因为:比如 “ABCD” 左旋 4 次 等于没左旋
for (int i = 0; i < n % size; i++) {
LeftRotateOne(str, size);
}
}
// 判断 str1 str2 是否互为左旋
int LeftRotateCmp(char str1[], char str2[]) {
int len1 = strlen(str1);
int len2 = strlen(str2);
if (str1 == NULL || str2 == NULL)
return -1;
if (len1 != len2)
return 0;
// 将 str1 用 for 循环左旋 len1 次,每次左旋后与 str2 比较
// 其实最多只用循环 len1 - 1 次即可,但是如果 写成 i < len1 - 1
// 最后依次旋转的结果不会被比较!!而且即使多旋转了一次,相当于判断结束将 str1 归位了,一举两得!
for (int i = 0; i < len1; i++) {
if (strcmp(str1, str2) == 0) {
return 1;
}
LeftRotateOne(str1, len1);
}
return 0;
}
int main(void) {
char str[] = "ABCD";
char str1[] = "DABC";
//LeftRotateOne(str, strlen(str));
//LeftRotateN(str, strlen(str), 4);
int a = LeftRotateCmp(str, str1);
printf("%d", a);
return 0;
}

View File

@@ -0,0 +1,3 @@
## 字符串左旋
这个问题我们在【习题一】也就是 上一级 【01】目录中讨论过此次给出另一个中解法