算法入门经典 第五章

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Cloud_yan/article/details/102668801

5.1 几个简单的题目(有些思想)

致你:
代码只有自己真正写过一遍,才能更好的深入理解它。你看会了,终究是看会了。你会写么?在此过程中出现了什么问题?你在哪里又犯下了什么过错?

WERTYU

我:对于这道题,你如果让我做。我会把各个字符对应的下一个字符以一匹配。然后比较字符相等,逐个比较转换输出。

刘:给出的思路,将对应关系的字符都存入一个数组。想要输出,则进行加减运算。

TeX括号

刘:判断左右括号,利用标志进行转换。每次转为时:

q=!q;

这样的标志改变的思路。

周期串

我:把这个字符串分成若干组,组划分从仅1组到n组,以第一组为源头,分别比较各组元素是否全与第一组元素相同,若相同,则把最小即为当前划分组。比较完从1组到n组后,输出最小的那个。
刘:从小到大枚举各个周期。若一个周期满足,则立刻输出该周期,该周期绝对是最小周期。

#include<stdio.h>
int main()
{
	char s[80];
	scanf("%s", s);
	int i = 1;
	int j = 1;
	int n=strlen(s);
	for (int i = 1; i <= n / 2; i++) 
	{
		if (n % i == 0)
		{
			int ok = 1;
			for (int j = i; j < n; j++) //从i之后开始,也就是把i之前的子串作为底板
			{
				if (s[j] != s[j % i]) 	//之后的子串一一与底板匹配,若有一个不同则break
				{
					ok = 0;
					break;
				}
			}
			if (ok)	//只要一出现第一个子串匹配。那么就立刻输出。并跳出
			{
				printf("%d", i);
				break;
			}
		}
	}
	return 0;
}

取最大,最小之类的:那么就是从左,从右开始,并且只要遇到的第一个,就直接跳出。那么即为最大或最小

高精度运算

(用数组来存储整数,模拟手算进行四则运算)

小学生运算

对于int,上限是20亿,可以保存所有的9位数

阶乘的精确值

利用数组来存储大数据。
f[0]保存个位
f[1]保存十位
即,逆序保存(因为如果按照从高到低的顺序存储,那么一旦进位的话,就要移动整个数组了)
并且,在输出的时候,要忽略前导0

#include<stdio.h>
#include<string.h>
#define MAXN 3000

int f[MAXN];
int main()
{
	int i, j, n;
	scanf("%d", &n);
	memset(f, 0, sizeof(f));
	f[0] = 1;
	for (i = 2; i <=n; i++)
	{
		int c = 0;
		for (j = 0; j < MAXN; j++)
		{
			/*不能
			f[j] = (f[j] * i + c)%10;	如果改变了f[j]那么下一个c就改变了。不是想要的了
			c = (f[j] * i + c) / 10;
			*/
			int s = f[j] * i + c;
			f[j] = s % 10;
			c = s / 10;
		}
	}

	for (j = MAXN-1; j >= 0; j--)	//跳过前导0的思想,只要遇到第一个非0数,就跳出
		if (f[j])
			break;
	for (i = j; i >= 0; i--)
		printf("%d", f[i]);
	printf("\n");
	return 0;

}

跳过前导0的思想,我很喜欢

5.2 高精度运算类bign和重载bign的运算符

//在使用string的时候,要加#include<string> 和 using namespace std;
#include<string>
#include<iostream>
#include<cmath>
#include<algorithm>

using namespace std;

const int maxn = 1000;
struct bign	//c++中不需要typedef就可以直接用结构体名定义变量,而且还提供"自动初始化"的功能
{
	//要表示1234,则len=4,s[0]=4,s[1]=3,s[2]=2,s[3]=1;
	int len, s[maxn];	//len表示位数,s数组是具体的各个数字
	bign()	//构造函数,作用是进行初始化
	{
		memset(s, 0, sizeof(s));
		len = 1;
	}

	/*
	可以用x="123456"给x赋值了
	会将字符串转化为"逆序数组+长度"的内部表示方法
	*/
	bign operator = (const char* num)	
	{
		len = strlen(num);
		for (int i = 0; i < len; i++)
			s[i] = num[len - i - 1] - '0';
		return *this;
	}
	//这样做之后,可以支持 x=1234 这样的赋值方式
	bign operator=(int num)
	{
		char s[maxn];
		sprintf(s, "%d", num);
		*this = s;
		return *this;
	}

	/*
	下面两个函数定义完成后,可以支持bign x=100这样的初始化。
	*/
	bign(int num) 
	{
		*this = num;
	}

	bign(const char* num)
	{
		*this = num;
	}

	//把bign类型的变量变为字符串
	//str()就是bign类型变量的成员函数
	string str() const	//const表明x.str()不会改变x	
	{
		string res = "";
		for (int i = 0; i < len; i++)
		{
			res = (char)(s[i] + '0') + res;
		}
		if (res == "")
			res = "0";
		return res;
	}
	/*
	成员函数的定义和普通函数类似,但成员函数可以直接使用结构体中的域
	str()函数可以直接用到len和s
	对成员函数来说,this是指向当前对象的指针
	*/

	/*重定义 + 运算符*/
	bign operator + (const bign& b)const
	{
		bign c;
		c.len = 0;
		for (int i = 0, g = 0; g || i < max(len, b.len); i++)	//只要任何一个数还有数字,加法就要继续进行
		{
			int x = g;
			if (i < len)
				x += s[i];
			if (i < b.len)
				x += b.s[i];
			c.s[c.len++] = x % 10;
			g = x / 10;	//处理进位
		}
	}

	/*重定义 += 运算符*/
	bign operator +=(const bign& b)
	{
		*this = *this + b;
		return *this;
	}

	/*重定义 - 运算符*/
	bign operator - (const bign& b)const
	{
		
	}
	/*重定义 -= 运算符*/
	bign operator -= (const bign& b)
	{
		*this = *this - b;
		return *this;
	}
	/*重定义 * 运算符*/
	/*重定义 *= 运算符*/
	bign operator *=(const bign& b)
	{
		*this = *this * b;
		return *this;
	}
	/*重定义 / 运算符*/
	/*重定义 /= 运算符*/
	bign operator /=(const bign& b)
	{
		*this = *this / b;
		return *this;
	}
	/*重定义 % 运算符*/
	/*重定义 %= 运算符*/
	bign operator %=(const bign& b)
	{
		*this = *this % b;
		return *this;
	}
	
	/*重定义 < 运算符*/
	bool operator < (const bign& b)const
	{
		if (len != b.len)	//一开始就比较两个bign的位数,如果不相等则直接返回
			return len < b.len;
		for (int i = len - 1; i >= 0; i--)	//否则比较两个数组的逆序的字典顺序,这样做的前提是两者都没有前导0
		{
			if (s[i] != b.s[i])
				return s[i] < b.s[i];
		}
		return false;
	}

	/*利用 < 运算符定义其他的运算符*/
	bool operator >(const bign&b)const	//重定义 > 运算符
	{
		return b < *this;
	}
	bool operator <=(const bign& b)const	//重定义 <= 运算符
	{
		return !(b < *this);
	}
	bool operator >=(const bign& b)const	//重定义 >= 运算符
	{
		return !(*this < b);
	}
	bool operator !=(const bign& b)const	//重定义 != 运算符
	{
		return b < *this || *this < b;
	}	
	bool operator ==(const bign& b)const	//重定义 == 运算符
	{
		return !(b < *this) && !(*this < b);
	}
};

//重定义运算符>>和<<  让输入输出流直接支持我们的bign结构体
istream& operator >> (istream& in, bign& x)
{
	string s;
	in >> s;
	x = s.c_str();
	return in;
}

ostream& operator << (ostream& out, const bign& x)
{
	out << x.str();
	return out;
}

对于bign的综合应用
利用了c++中类的思想,来修改运算
采用数组的形式存储大数,并按照手算的思想重载运算符

5.3 排序与检索

6174问题
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>

int get_next(int num)
{
	char s[10];
	sprintf(s, "%d", num);	//把数据存储到字符数组中
	int len = strlen(s);
	for (int i = 0; i < len; i++)	//把大的数字排到后面
	{
		for (int j = i + 1; j < len; j++)
		{
			if (s[i] > s[j])
			{
				char temp = s[i];
				s[i] = s[j];
				s[j] = temp;
			}
		}
	}
	char s1[10];
	for (int i = len - 1; i >= 0; i--)	//逆序存储
	{
		s1[len - i -1] = s[i];
	}
		
	int a,b;
	sscanf(s1, "%d", &a);	//将数字串输入到数字变量中
	sscanf(s, "%d", &b);
	return a - b;
}
int a[1005];
int main()
{
	int n;
	scanf("%d", &n);
	int flag = 0;
	printf("%d", n);
	a[0] = n;
	for (int i = 1; i < 1000; i++)
	{
		printf("->");
		a[i]=get_next(a[i-1]);
		printf("%d", a[i]);
		for (int j = 0; j < i; j++)
		{
			if (a[i] == a[j])
			{
				flag = 1;
				break;
			}
		}
		if (flag == 1)
			break;
	}
	return 0;
}

总结:
sprintf(s,"%d",n):把n值按照整数的形式存储到字符数组中
sscanf(s,"%d",&n):把字符数组中的数据按照整数的形式存储到n中
冒泡排序:

for (int i = 0; i < len; i++)	//把大的数字排到后面
{
	for (int j = i + 1; j < len; j++)
	{
		if (s[i] > s[j])
		{
			char temp = s[i];
			s[i] = s[j];
			s[j] = temp;
		}
	}
}
字母重排
#define _CRT_SECURE_NO_WARNINGS
/*
算法思想:
判断两个单词是否可以通过重排得到?
ANS:把各个字母排序,然后直接比较。在读入时就把每个单词按照字母排好序,就不必每次重排了
必须把能重排的单词保存下来再排序?
ANS:没必要,只要在读入字典之后把所有单词排序,就可以每遇到一个满足条件的单词直接输出就好了
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int n;
char word[2000][10];
char sorted[2000][10];
//比较两个字母的大小
int cmp_char(const void* _a, const void* _b)
{
	char* a = (char*)_a;
	char* b = (char*)_b;
	return *a - *b;
}
//比较两个字符串的大小
int cmp_string(const void* _a, const void* _b)
{
	char* a = (char*)_a;
	char* b = (char*)_b;
	return strcmp(a, b);
}
int main()
{
	n = 0;
	for (;;)	//一直输入,直到 "******"
	{
		scanf("%s", word[n]);
		if (word[n][0] == '*')
			break;
		n++;	//n存储了有多少个单词
	}
	qsort(word, n, sizeof(word[0]), cmp_string);	//sizeof(word[0])表示的是数组的大小,而不是word[0]实际存储了多少字符
	for (int i = 0; i < n; i++)
	{
		strcpy(sorted[i], word[i]);	//把word[i]的内容复制到sorted[i]中
		qsort(sorted[i], strlen(sorted[i]), sizeof(char), cmp_char);	//对sorted[i]进行排序,以不改变word[i]的内容
	}
	char s[10];
	while (scanf("%s", s) == 1)
	{
		qsort(s, strlen(s), sizeof(char), cmp_char);	//对输入的字母序列排序
		int found = 0;
		for (int i = 0; i < n; i++)	//从第1个单词到最后一个,进行一一比较
		{
			if (strcmp(sorted[i], s) == 0)	//如果找到了
			{
				found = 1;
				printf("%s ", word[i]);
			}
		}	
		if (!found)	//没有找到
			printf(":(");
		printf("\n");
	}
	return 0;
}

算法思想:
判断两个单词是否可以通过重排得到?
ANS:把各个字母排序,然后直接比较。在读入时就把每个单词按照字母排好序,就不必每次重排了
必须把能重排的单词保存下来再排序?
ANS:没必要,只要在读入字典之后把所有单词排序,就可以每遇到一个满足条件的单词直接输出就好了


qsort()的用法:
qsort()是在stdlib.h中的
使用库函数排序的代码量并不比冒泡排序少,但速度更快


qsort()声明如下:

void qsort(void * base,size_t nmemb,size_t size ,int(*compar)(const void *,const void *));

base,要排序的数组
nmemb,数组中元素的数目
size,每个数组元素占用的内存空间,可使用sizeof函数获得
compar,指向函数的指针也即函数指针。这个函数用来比较两个数组元素,第一个参数大于,等于,小于第二个参数时,分别显示正值,零,负值。


qsort用起来是真的爽。要注意qsort的第二个参数与第三个参数是不一样的。第二个参数代表的是你要排的长度,而第三个参数代表的是你要排的每个元素的空间大小。如果错了,那么排序不成功。

四个基本函数,处理字符串的。
strcmp
strcat
strlen
strcpy

5.4数学基础

注意数学思维的思考了

如果判断两个浮点数a和b是否相等时,应尽量判断fabs(a-b)是否小于一个事先给定的eps,如1e-9
如果有可能,请尽量避免浮点运算

判断三角形内的树的数目:

/*
计算三角形(x0,y0)-(x1,y1)-(x2,y2)的有向面积的两倍
有向面积
P0 P1 P2 的三个顶点按逆时针排列,则有向面积为正
如果是顺时针排列,则为负
三点共线,有向面积为0
*/
double area2(double x0, double y0, double x1, double y1, double x2, double y2)
{
	return x0 * y1 + x2 * y0 + x1 * y2 - x2 * y1 - x0 * y2 - x1 * y0;
}
/*
判断O在三角形内部或边界上当且仅当:
SABC==SOAB+SOBC+SOCA
*/

分割土地:
V-E+F=2
V是顶点数,所有线段的端点数加上交点数
E是边数
F是面数(包括外面那个无穷大的面)

double V,E;
	int sum1=0;
	int sum2 = 0;
	for (int i = 0; i <= n - 2; i++)
	{
		sum1 += i * (n - 2 - i);
		sum2 += (i * (n - 2 - i) + 1);
	}
	V = n + (n * sum1) / 4.0;
	E = n + (n * sum2) / 2.0;

错误

答案错:WA(Wrong Answer)
输出格式错:PE(Presentation Error)
超时:TLE(Time Limit Exceeded)
运行错:RE(Runtime Error)

在运行时,除了程序自身异常退出(如除0,栈溢出,非法访问内存,断言为假,main函数返回非0值)外,还可能是因为超过了评测系统的资源约束(如内存限制,最大输出限制)而被强制中止执行。

猜你喜欢

转载自blog.csdn.net/Cloud_yan/article/details/102668801