笔记19* (C语言进阶 指针练习)

 

目录

注:

 添加

例1:杨氏矩阵

例2:字符串左旋(来源:CSDN博主 v_JULY_v)

解法一

解法二(三步反转法)

例3:字符串旋转结果

解法一(穷举)

解法二(使用库函数)


注:

 本笔记参考B站up鹏哥C语言的视频


 添加

  • 创建的变量可以通过free释放内存 - 动态内存开辟

申请 - malloc

使用

释放 - free(变量不会被主动置为空指针,需要手动修改,如:

int* p = malloc(40);
//使用p指向的40个字节的空间
free(P);

注:free命令执行结束后,p依旧指向本来的地址。free命令不会把这个指针置为NULL。需要加上:

p = NULL;
  • 在定义函数指针时,对应函数的函数名可省略。

例1:杨氏矩阵

题目内容:有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。

要求:时间复杂度小于O(N)。
ps:① O(N) - 查找一个元素的次数和数字元素的个数(N)是有关系的。
       ② O(1) - 此时查找次数是3、5、7……等等与N无关的常数

一个标准的杨氏矩阵,如:

1  2  3  4

2  3  4  5

4  5  6  7

不符合要求的算法(遍历数组的方法)

N - 查找的次数最坏的情况下是N次(乃至2N、3N……)

int main()
{
	int arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	//查找一个数字,比如:7
	int i = 0;
	int j = 0;
	for ( i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)\
		{
			if (arr[i][j] = 7)
			{}
		}
	}
	return 0;
}

符合要求的算法(这种查找的方式可以更快的找到目标)

如:利用数组右上角的数字(或者左下角的数字)进行比较。

  1.  从第一行的最大值开始查找,比较3与7的大小,发现3<7,所以可以肯定7不在第一行。去掉第一行,向下查找。
  2. 同样的方法可以去掉第二行。
  3. 在第三行发现9>7,开始在这一行查找,并且可以去掉9所在的这一列。
  4. 发现8>7,去掉8所在的这一列。
  5. 找到7。

举例:寻找7

int find_num(int arr[3][3], int r, int c, int k)
{
	int x = 0;
	int y = c - 1;

	while (x < r && y >= 0)
	{
		if (arr[x][y] < k)
		{
			x++;//去掉行
		}
		else if (arr[x][y] > k)
		{
			y--;//去掉列
		}
		else
		{
			return 1;//找到了
		}
	}
	return 0;//找不到
}
int main()
{
	int arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	int k = 7;//目标

	//规定:如果找到,返回1;找不到,返回0。
	int ret = find_num(arr, 3, 3, k);
	if (ret == 1)
		printf("找到了\n");
	else
		printf("找不到\n");
	return 0;
}

注:上述代码难以和后续功能合并,以下提供优化的代码(返回型参数的运用)。

int find_num(int arr[3][3], int* px, int* py, int k)
{
	int x = 0;
	int y = *py - 1;

	while (x < *px && y >= 0)
	{
		if (arr[x][y] < k)
		{
			x++;
		}
		else if (arr[x][y] > k)
		{
			y--;
		}
		else
		{
			*px = x;
			*py = y;
			return 1;
		}
	}
	return 0;
}
int main()
{
	int arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	int k = 7;//目标
	int x = 3;//行
	int y = 3;//列

	//如果找到,返回1;找不到,返回0。
	int ret = find_num(arr, &x, &y, k);
	if (ret == 1)
	{
		printf("找到了\n");
		printf("下标是%d %d\n", x, y);
	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

实际上是把原本传过去的行(x)和列(y)改为了行的地址(*px)和列的地址(*py)。

两个指针在这里面的两个主要作用:

  1. 传入参数
  2. 带回值

注意:形参是实参的临时拷贝。

例2:字符串左旋(来源:CSDN博主 v_JULY_v)

v_JULY_v的博客主页

题目内容:首先一个函数,可以左旋字符串中的k个字符。

例如:

ABCD左旋一个字符得到BCDA
ABCD左旋两个字符得到CDAB

注意:

char* p = "ABCDEF";//这种写法是错误的,"ABCDEF"是常量字符串,地址无法改变

解法一

 思路: 

int main()
{
	char arr[10] = "ABCDEF";//目标字符串
	int k = 2;//旋转次数
	str_le_rot(arr, k);//实现左旋
	printf("%s\n", arr);//打印

	return 0;
}
ABCDEF

    1.创建一个新的空间,拿取首字符放入;

BCDEF
A

    2.剩余字符串向左移动一个单位;

BCDEF

    3.将首字符放入末尾位置;

BCDEFA

    4.再拿取首字符,循环步骤……

代码:

#include<stdio.h>

void str_le_rot(char* str, int k)
{
	int i = 0;
	int len = strlen(str);
	for (i = 0; i < k; i++)
	{
		//每次左旋一个字符
		char tmp = *str;

		//后面的n - 1个字符往前移动
		int j = 0;
		for (j = 0; j < len - 1; j++)
		{
			*(str + j) = *(str + j + 1);
		}

		//tmp内存储的字符放在最后
		*(str + len - 1) = tmp;
	}
}

int main()
{
	char arr[10] = "ABCDEF";
	int k = 2;
	str_le_rot(arr, k);
	printf("%s\n", arr);

	return 0;
}

解法二(三步反转法)

思路:

int main()
{
	char arr[10] = "ABCDEF";
	int k = 10;
	str_le_rot(arr, k);
	printf("%s\n", arr);

	return 0;
}

    1.将字符串分为两部分

A        BC        D        E        F

    2.将两部分分别逆序

B        AF        E        D        C

    3.整体进行逆序

C        D        E        FA        B

代码:

#include<stdio.h>
#include<assert.h>

void reverse(char* left, char* right)
{
	assert(left);
	assert(right);
	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}

void str_le_rot(char* str, int k)
{
        assert(str);
   
	int len = strlen(str);
	reverse(str, str + k - 1);//逆序左边
	reverse(str + k, str + len - 1);//逆序右边
	reverse(str, str + len - 1);//逆序整体
}

int main()
{
	char arr[10] = "ABCDEF";
	int k = 2;
	str_le_rot(arr, k);
	printf("%s\n", arr);

	return 0;
}

注:这串代码当k>数组长度时,会由于 str + k 的相关元素越界而出现问题。

例3:字符串旋转结果

题目内容:写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。

例如:

给定 s1 = AABCD 和 s2 = BCDAA ,返回1;
给定 s1 = abcd 和 s2 = ACBD ,返回0。

解法一(穷举)

思路:

int main()
{
	char arr_1[] = "AABCD";
	char arr_2[] = "BCDAA";

	return 0;
}

通过不断左旋字符串arr_1(旋转一个字符,旋转两个字符……),于字符串arr_2进行比较,得出结论。

代码:

#include<stdio.h>
#include<string.h>

is_str_le_rot(char* str1, char* str2)
{
	int i = 0;
	int len = strlen(str1);
	for (i = 0; i < len; i++)
	{
		//每次左旋一个字符
		char tmp = *str1;

		//后面的n - 1个字符往前移动
		int j = 0;
		for (j = 0; j < len - 1; j++)
		{
			*(str1 + j) = *(str1 + j + 1);
		}

		//tmp内存储的字符放在最后
		*(str1 + len - 1) = tmp;

		//比较str1和str2
		if (strcmp(str1, str2) == 0)
		{
			return 1;
		}
	}
	return 0;
}

int main()
{
	char arr_1[] = "AABCD";
	char arr_2[] = "BCDA";
	int ret = is_str_le_rot(arr_1, arr_2);

	if (ret == 1)
	{
		printf("yes\n");
	}
	else
	{
		printf("no\n");
	}
	return 0;
}

解法二(使用库函数)

思路:

对于字符串1"AABCD"

可以创建对应字符串2"AABCDAABCD",通过字符串2就可以找到所有字符串1的旋转结果。

知识点:

strcat函数 - 在字符串后面添加一个字符串

 

使用例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[20] = "Hello";
	strcat(arr, " World");
	printf("%s\n", arr);
	return 0;
}

打印结果:

注意:

strcat(str1, str1);

这种写法是不成立的。需要使用 strncat函数 。

对比strcat函数,strncat函数多了一个参数。

使用例:

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[20] = "Hello";
	strncat(arr, arr, 5);
	printf("%s\n", arr);
	return 0;
}

打印结果:


strstr函数 - 用来检测一个字符串是不是另一个字符串的子串

这个函数,如果发现一个字符串是另一个字符串的子串,就会返回这个子串所在的位置(返回地址);如果找不到,就会返回一个空指针(NULL)。

例子:

代码:

#include<stdio.h>
#include<string.h>

is_str_le_rot(char* str1, char* str2)
{
        //长度不相等,字符串不是由旋转得到的
	if (strlen(str1) != strlen(str2))
	{
		return 0;
	}

	//1.在字符串str1的后面追加一个str1
	//追加结果是:AABCDAABCD
	int len = strlen(str1);
	strncat(str1, str1, len);

	//2.判断str2是否为str1的字串
	char* ret = strstr(str1, str2);
        
        return ret != NULL;
}


int main()
{
	char arr_1[20] = "AABCD";
	char arr_2[] = "BCDA";
	int ret = is_str_le_rot(arr_1, arr_2);

	if (ret == 1)
	{
		printf("Yes\n");
	}
	else
	{
		printf("No\n");
	}
	return 0;
}

其中 return ret != NULL 等价于

if (ret == NULL)
{
	return 0;
}
else
{
	return 1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值