剑指Offer读书笔记(3)

排序查找与算法优化
本文介绍了排序和查找算法的基本概念,包括快速排序的实现原理及其在查找特定元素的应用,同时对比了递归与循环的不同特性,并探讨了位运算的具体应用场景。

2.4.1 排序和查找

查找相对简单,查找分为顺序查找、二分查找、哈希表查找和二叉排序树查找

PS:哈希表最主要的优点是我们利用它能够在O(1)的时间度查找某一元素,是效率最高的查找方式。但缺点在于需要额外的空间来实现哈希表。

排序比较复杂,排序分为插入排序,哈希排序,冒泡排序,选择排序,堆排序,归并排序,快速排序

PS:比较排序的优缺点应从额外空间消耗,平均时间复杂度和最差时间复杂度等方面比较。

 

快速排序:实现快速排序的关键在于先在数组中选择一个数字,接下来把数组中的数字分为两部分,比选择的数字小的数字移到数组的左边,比选择的数字大的数字移到数组的右边。

 具体实现如下:

int Partition(int data[], int length, int start, int end)
{
	if (data != NULL || length <= 0 || start < 0 || end >= length)
		throw new std::exception("Invalid Parameters");
	int index = RandomInRange(start, end);//用来生成一个在start和end之间的随机数
	Swap(&data[index], &data[end]);		//交换两个数字

	int small = start - 1;
	for (index = start; index < end; ++index)
	{
		if (data[index] < data[end])
		{
			++small;
			if (small != index)
				Swap(&data[index], &data[small]);
		}
	}
	++small;
	Swap(&data[small], &data[end]);

	return small;
}
之后便可以根据递归的思路来实现对每次选中的数字的左右两边进行排序。

void QuickSort(int data[], int length, int start, int end)
{
	if (start == end)
		return;

	int index = Partition(data, length, start, end);
	if (index > start)
		QuickSort(data, length, start, index - 1);
	if (index < end)
		QuickSort(data, length, index + 1, end);
}
 在前面的代码中,函数Partition除了可以用在快速排序中还可以用来实现在长度为n的数组中查找第k大的数字

排序在实际问题中的应用:对某公司的员工的年龄排序,员工数量在几万名,要求时间复杂度为O(n)。

在实现之前需要认识到需要排序的数字是在一个较小的范围内,位放方便实现可以考虑利用辅助内存。

void SoftAges(int ages[], int length)
{
	if (ages == NULL || length <= 0)
		return;

	const int OldestAge = 99;
	int timesOfAge[OldestAge + 1];

	for (int i = 0; i <= OldestAge; ++i)//统计每个年龄出现的次数
		timesOfAge[i] = 0;
	for (int i = 0; i < length; ++i)
	{
		int age = ages[i];
		if (age<0 || age>OldestAge)
			throw new std::exception("age out of range.");

		++timesOfAge[age];
	}

	int index = 0;
	for (int i = 0; i <= OldestAge; ++i)
	{
		for (int j = 0; j < timesOfAge[i]; ++j)
		{
			ages[index] = i;//某个年龄出现了几次,就重置几次该年龄,相当于给数组ages排序
			++index;
		}
	}
}
该方法利用长度为100 的数组作为辅助空间换来了O(n)的时间效率。


查找的实际用例:旋转数组的最小数,例如:{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。

该题的直观思路不难,从头到尾遍历一遍数组,找出最小数,时间复杂度为O(n),但通过观察可以看出,数组在旋转后可以别分为两部分,并且都是有序的,前半部分的数值整体上都比后半部分的大,以此可以进行优化,找出比时间复杂度O(n)更优的解。

int minArray(int* a, int length)
{
	if (a == NULL || length <= 0)
	{
		throw new std::exception("Invalid parameters.");
	}

	int index1 = 0;
	int index2 = length - 1;
	int indexMid = index1;
	while (a[index1] >= a[index2])
	{
		if (index2 - index1 == 1)
		{
			return a[index2];
		}
		indexMid = (index1 + index2) / 2;
		if (a[index1] <= a[indexMid])
		{
			index1 = indexMid;
		}
		else if (a[index2] >= a[indexMid])
		{
			index2 = indexMid;
		}
	}
	return a[indexMid];
}
上面的代码存在着很大的缺陷,例如当输入特殊的数组,数组内存在大量相等且连序的数值时,如{1,1,1,0,1,1}按照上面的解法便会出现指针更改移动的问题,是指针的指向不在明确,从而导致导致程序崩溃,此时要找除最小值便只能顺序遍历数组。

int MinArray(int* a, int length)
{
	if (a == NULL || length <= 0)
	{
		throw new std::exception("Invalid parameters.");
	}

	int index1 = 0;
	int index2 = length - 1;
	int indexMid = index1;
	while (a[index1] >= a[index2])
	{
		if (index2 - index1 == 1)
		{
			indexMid = index2;
			break;
		}

		indexMid = (index1 + index2) / 2;
		if (a[index1] == a[index2] && a[index1] == a[indexMid])
		{
			return MinInOrder(a, index1, index2);
		}
		if (a[index1] <= a[indexMid])
		{
			index1 = indexMid;
		}
		else if (a[index2] >= a[indexMid])
		{
			index2 = indexMid;
		}
	}
	return a[indexMid];
}
int MinInOrder(int* a, int index1, int index2)		//当数组中年出现大量连续且相等的数值时的处理
{
	int result = a[index1];
	for (int i = 0; i <= index2; i++)
	{
		if (result > a[i])
			return result;
	}
	return result;
}

2.4.2递归和循环

递归是在一个函数的内部调用这个函数的本身。

循环是通过设置计算的初始值及终止条件,在一个范围内重复运算。

用递归实现的算法通常会比循环简洁,但相应的,递归的每次调用都会在时间和空间的消耗比较大。每次递归都需要在内存栈中分配空间以保存参数、返回地址和临时变量,而且往栈内压入数据和弹出数据都需要时间。

递归计算的时间复杂度是以n的指数递增的。

递归的本质是把一个问题分解成两个或多个小问题。

斐波那契数列的递归和非递归解法

long long Fibonacci(size_t n)
{
	if (n <= 0)
		return 0;

	if (n == 1)
		return 1;

	return Fibonacci(n - 1) + Fibonacci(n - 2);
}
递归实现的代码简洁,但仔细考虑递归的每个过程会发现对大量相同数据的存储和操作时递归的最大问题,当N值过大时这些消耗便不能被忽视。

long long Fibonacci(size_t n)
{
	int result[2] = { 0, 1 };
	if (n < 2)
	{
		return result[n];
	}

	long long first = 0;
	long long second = 1;
	long long fibN = 0;
	for (size_t i = 0; i <= n; i++)
	{
		fibN = first + second;

		first = second;
		second = fibN;
	}
	return fibN;
}

2.4.3位运算

位运算是把数字用二进制表示后,对每一位上的0或1的运算。

位与(&)运算:0&0=0;0&1=0;1&0=0;1&1=0

位或(|)运算:0|0=0;0|1=1;1|0=1;1|1=1

位异或(^)运算:0^0=0;1^0=1;0^1=1;1^1=1

位左移(<<):左移n位时,最左边的位将被丢弃,同时在最右边补上n位0。

位右移(>>):右移n位时,当数字是一个无符号数时,则用0填补最左边n位;如果数字是有符号数,则用数字的符号位填补最左边的n位。

联系题:求二进制中1的个数

//可能引起死循环的算法,原因:没有考虑到n 为负数的情况,N为负数时向右移位高位补1而不是0
//不能用除2来代替N向右移位,因为除法效率很低
int Num(int n)
{
	int count = 0;
	while (n)
	{
		if (n & 1)
			count++;

		n = n >> 1;
	}
	return n;
}
//常规解法
int Num(int n)
{
	int count = 0;
	size_t flag = 1;
	while (flag)
	{
		if (n&flag)
			count++;

		flag = flag << 1;
	}
	return count;
}
//最优解
//思想:把一个数减去1,再和原来的数相&,会把该数二进制的最右边一个1变为0;依次循环比较高效
int Num(int n)
{
	int count = 0;

	while (n)
	{
		++count;

		n = (n - 1)&n;
	}
	return count;
}

内容概要:本文设计了一种基于PLC的全自动洗衣机控制系统内容概要:本文设计了一种,采用三菱FX基于PLC的全自动洗衣机控制系统,采用3U-32MT型PLC作为三菱FX3U核心控制器,替代传统继-32MT电器控制方式,提升了型PLC作为系统的稳定性与自动化核心控制器,替代水平。系统具备传统继电器控制方式高/低水,实现洗衣机工作位选择、柔和过程的自动化控制/标准洗衣模式切换。系统具备高、暂停加衣、低水位选择、手动脱水及和柔和、标准两种蜂鸣提示等功能洗衣模式,支持,通过GX Works2软件编写梯形图程序,实现进洗衣过程中暂停添加水、洗涤、排水衣物,并增加了手动脱水功能和、脱水等工序蜂鸣器提示的自动循环控制功能,提升了使用的,并引入MCGS组便捷性与灵活性态软件实现人机交互界面监控。控制系统通过GX。硬件设计包括 Works2软件进行主电路、PLC接梯形图编程线与关键元,完成了启动、进水器件选型,软件、正反转洗涤部分完成I/O分配、排水、脱、逻辑流程规划水等工序的逻辑及各功能模块梯设计,并实现了大形图编程。循环与小循环的嵌; 适合人群:自动化套控制流程。此外、电气工程及相关,还利用MCGS组态软件构建专业本科学生,具备PL了人机交互C基础知识和梯界面,实现对洗衣机形图编程能力的运行状态的监控与操作。整体设计涵盖了初级工程技术人员。硬件选型、; 使用场景及目标:I/O分配、电路接线、程序逻辑设计及组①掌握PLC在态监控等多个方面家电自动化控制中的应用方法;②学习,体现了PLC在工业自动化控制中的高效全自动洗衣机控制系统的性与可靠性。;软硬件设计流程 适合人群:电气;③实践工程、自动化及相关MCGS组态软件与PLC的专业的本科生、初级通信与联调工程技术人员以及从事;④完成PLC控制系统开发毕业设计或工业的学习者;具备控制类项目开发参考一定PLC基础知识。; 阅读和梯形图建议:建议结合三菱编程能力的人员GX Works2仿真更为适宜。; 使用场景及目标:①应用于环境与MCGS组态平台进行程序高校毕业设计或调试与运行验证课程项目,帮助学生掌握PLC控制系统的设计,重点关注I/O分配逻辑、梯形图与实现方法;②为工业自动化领域互锁机制及循环控制结构的设计中类似家电控制系统的开发提供参考方案;③思路,深入理解PL通过实际案例理解C在实际工程项目PLC在电机中的应用全过程。控制、时间循环、互锁保护、手动干预等方面的应用逻辑。; 阅读建议:建议结合三菱GX Works2编程软件和MCGS组态软件同步实践,重点理解梯形图程序中各环节的时序逻辑与互锁机制,关注I/O分配与硬件接线的对应关系,并尝试在仿真环境中调试程序以加深对全自动洗衣机控制流程的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值