原地算法
⑴ 传动装置的总效率计算
总效率η=运输机传送带效率η1×运输机轴承效率η2×运输机与减速器间联轴器效率η3×减速器内3对滚动轴承效率η4×2对圆柱齿轮啮合传动效率η5×电动机与减速器间联轴器效率η6;
传动系统的组成和布置形式是随发动机的类型、安装位置,以及汽车用途的不同而变化的。例如,越野车多采用四轮驱动,则在它的传动系中就增加了分动器等总成。而对于前置前驱的车辆,它的传动系中就没有传动轴等装置。
(1)原地算法扩展阅读
汽车传动系的基本功能就是将发动机发出的动力传给驱动车轮。它的首要任务就是与汽车发动机协同工作,以保证汽车能在不同使用条件下正常行驶,并具有良好的动力性和燃油经济性,为此,汽车传动系都具备以下的功能:
减速和变速
我们知道,只有当作用在驱动轮上的牵引力足以克服外界对汽车的阻力时,汽车才能起步和正常行驶。由实验得知,即使汽车在平直得沥青路面上以低速匀速行驶,也需要克服数值约相当于1.5%汽车总重力得滚动阻力。
减速作用
为解决这些矛盾,必须使传动系具有减速增距作用(简称减速作用),亦即使驱动轮的转速降低为发动机转速的若干分之一,相应地驱动轮所得到的扭距则增大到发动机扭距的若干倍。
⑵ 原地哈希算法
原地哈希算法主要应用在范围为 [0, len(nums)] 的数组解法中,将数组元素本身作为 nums 的下标,即 nums[nums[i]] 。下面将结合leetcode 题目做一些总结。
题目描述:
题目分析:
首先想到的是利用哈希表来存储数组中的元素,然后从 1 开始遍历整数,不在哈希表中的第一个数字便是我们的解。我们知道了要从 1 开始遍历,那遍历到什么值为止呢?
设数组的长度为 n ,如果数组中的元素正好为 [1, …, n] ,则缺失的元素为 n+1 ;如果数组中有非 [1, …, n] 的数字呢?则它们占用的位置就是 [1, …, n] 中缺失的那些数字的位置。所以我们要找的数字一定是在 [1, …, n+1] 之间。所以我们需要遍历的最大值为 n+1 。
题目要求使用常数级别的额外空间,而我们使用哈希表来存储数字,空间复杂度为 O(n) ,不满足要求。基于此,我们尝试将原数组改造成哈希表(需要修改原数组,所以题目不能要求说不能修改原数组),这样不使用额外的空间。
我们对数组进行遍历,对于遍历到的值 num ,如果它的值在 [1, n] 之间,那么就将数组中索引为 num-1 的数字打上标记,在遍历结束后,如果所有位置都被打上了标记,则答案为 n+1 ,否则为最小的没有打上标记的位置加 1 。
由于我们只在意 [1, n] 中的数字,所以可以将其他数字做统一修改,比如改为 n+1 ,这样数组中的数字都为正整数,也方便了我们使用数组中的元素作为索引来用。
题目描述:
题目分析:
首先想到利用哈希表统计计数便可以得出答案,时间和空间复杂度为 O(n) 。时间复杂度上的优化很难想到了,但是空间复杂度还是可能优化的。
长度为 n 的数组 nums 包含的数字为 [1, n] ,那显然原地哈希算法可以派上用场。
我们对数组进行遍历,对于遍历到的值 num ,就将数组中索引为 num-1 的数字打上标记,如果遍历中发现某个数字已经被打上了标记,则表示这个数字为重复数字。遍历完成后,除了丢失的数字对应的位置上的元素没有标记,其余数字都被打上了标记。
题目描述:
题目分析:
很容易想到利用哈希表来求解,但是哈希表的空间复杂度为 O(n) ,不满足题目要求;基于上篇的位运算方法( https://www.jianshu.com/p/3b9a2a8a2e13 ),可以得到空间复杂度为 O(1) 的解。那么除了位运算,是否还有其他满足题目要求的解法呢?
既然数组中的数字都在 [1, n] 之间,那么原地哈希算法是否可以派上用场呢?
我们尝试将数组中的元素作为索引来遍历数组:
0 --> 1(nums[0]) --> 3(nums[1]) --> 2(nums[3]) --> 4(nums[2]) --> 2(nums[4])
我们发现重复数字 nums[3] 和 nums[4] 都指向了相同的数字 2 ,如果将上面的映射关系看做链表,那就是链表中出现了环: 2 --> 4 --> 2 ,且重复的数字刚好是环的入口节点的值。所以可以将题目转换成求链表环的入口节点( https://leetcode-cn.com/problems/linked-list-cycle-ii/ )
⑶ 一个数据结构的问题,单链表原地逆置,他的算法怎么写啊
Status reverse(LinkList &L) {
//L指向单链表的表头,算法将单链表逆转,L指向逆转后的表头。
if (L==NULL ) return OK;
else if (L->next ==NULL) return OK;
p=L; q = p->next;
L->next = NULL;
while (q!=NULL){
Lnode * tem = q ->next;
q ->next = p;
p = q;
q = temp;
}
L = p;
return OK;
}
你看怎么样?
⑷ 矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。
进阶:
一个直接的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
你能想出一个常数空间的解决方案吗?
咋一看,这个问题很简单:直接遍历整个矩阵,只要发现值为0的元素,就将其所在的行与列清零。不过这种方法有个缺陷:在读取被清零的行与列时,读到的尽是零,于是所在的行与所在的列都变成了0,很快,整个矩阵都变成了0.
避开这个缺陷的方法之一是新建一个矩阵标记零元素的位置。然后,在第二遍遍历矩阵的时候将0元素所在的行与列清零。这种做法的空间复杂度为O(MN)
真的需要占用O(MN)空间吗?不是的,既然打算将整行和整列清零,因此并不需要标记录它是cell[2][4](行2,列4),只需要知道行2有个元素为0,列4有个元素为0.不管怎样,整行和整列都要清零,又何必要记录零元素的确切位置?
常数空间的解决方案:
我们可以使用原矩阵的第一行和第一列来记录哪些行和列需要全部置为0。
首先看第一行和第一列有没有0,如果有,记录flag表示之后第一行或第一列需要全部置为0;
遍历除了第一行和第一列的所有元素,如果元素为0,则将其所在行与第一列交点的元素,其所在列与第一行交点的元素都置为0。
根据第一行和第一列中的0,将其他行列的元素置为0;
根据记录的flag将第一行和第一列置为0。
⑸ 什么是算法原地工作的含义
算法原地工作的含义是指不需要任何额外的辅助,算法所需要的辅助空
间不随着问题的规模而变化,是一个确定的值。
...
⑹ 请问求逆矩阵的原地求逆算法,哪本书上有详细的描述
用Gauss-Jordan法即可,
增广矩阵A|E
初等行变换,变成E|B
求出A的逆矩阵是B
⑺ 快速排序特点
快速排序(Quicksort)是对冒泡排序的一种改进,由东尼·霍尔在1960年提出。 快速排序是指通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序。整个排序过程可以递归进行,以此达到整个数据变成有序序列。
分类
排序算法
数据结构
不定
最坏空间复杂度
根据实现的方式不同而不同
快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。
步骤为:
从数列中挑出一个元素,称为“基准”(pivot),
重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
在简单的伪代码中,此算法可以被表示为:
function quicksort(q)
{
var list less, pivotList, greater
if length(q) ≤ 1
return q
else
{
select a pivot value pivot from q
for each x in q except the pivot element
{
if x<pivot then add x to less
if x ≥ pivot then add x to greater
}
add pivot to pivotList
return concatenate(quicksort(less), pivotList, quicksort(greater))
}
}
原地(in-place)分区的版本
上面简单版本的缺点是,它需要的额外存储空间,也就跟归并排序一样不好。额外需要的存储器空间配置,在实际上的实现,也会极度影响速度和缓存的性能。有一个比较复杂使用原地(in-place)分区算法的版本,且在好的基准选择上,平均可以达到空间的使用复杂度。
function partition(a, left, right, pivotIndex)
{
pivotValue = a[pivotIndex]
swap(a[pivotIndex], a[right]) // 把pivot移到结尾
storeIndex = left
for i from left to right-1
{
if a[i]<= pivotValue
{
swap(a[storeIndex], a[i])
storeIndex = storeIndex + 1
}
}
swap(a[right], a[storeIndex]) // 把pivot移到它最后的地方
return storeIndex
}
这是原地分区算法,它分区了标示为"左边(left)"和"右边(right)"的序列部分,借由移动小于的所有元素到子序列的开头,留下所有大于或等于的元素接在他们后面。在这个过程它也为基准元素找寻最后摆放的位置,也就是它回传的值。它暂时地把基准元素移到子序列的结尾,而不会被前述方式影响到。由于算法只使用交换,因此最后的数列与原先的数列拥有一样的元素。要注意的是,一个元素在到达它的最后位置前,可能会被交换很多次。
一旦我们有了这个分区算法,要写快速排列本身就很容易:
procere quicksort(a, left, right)
if right>left
select a pivot value a[pivotIndex]
pivotNewIndex := partition(a, left, right, pivotIndex)
quicksort(a, left, pivotNewIndex-1)
quicksort(a, pivotNewIndex+1, right)
这个版本经常会被使用在命令式语言中,像是C语言。
快速排序
快速排序是二叉查找树(二叉搜索树)的一个空间最优化版本。不是循序地把数据项插入到一个明确的树中,而是由快速排序组织这些数据项到一个由递归调用所隐含的树中。这两个算法完全地产生相同的比较次数,但是顺序不同。对于排序算法的稳定性指标,原地分区版本的快速排序算法是不稳定的。其他变种是可以通过牺牲性能和空间来维护稳定性的。
⑻ 为什么选C,我感觉3有问题啊,不是上界吧
(1)是不对的 《数据结构》严蔚敏版说:“若额外空间相对于输入数据量来说是常数”则称此算法为原地工作。
(2)可以算对,但是描述有个概念性问题:算法复杂度是没有常数的,即没有O(2n)这样的说法。你在执行每一条语句的时候比如加和乘,所消耗的时间都不一样,不要加常数加以限制。
(3)是正确的,比如下面算法
if(n>1000)
for(int i=0;i<n;i++)
a++;
else
a=0;
中不考虑else里的时间了,时间复杂度应该是O(n)
(4)对于第四项肯定是错误的。
所以选C (1)和(4)
(想选1 2 4)
⑼ 堆和堆排序
1,堆是一个完全二叉树;
完全二叉树要求除了最后一层,其他层的节点都是满的,最后一层的节点都靠左排列。
2,堆中每个节点都必须大于等于(或小于等于)其子树中每个节点的值。
堆中每个节点的值都大于等于(或者小于等于)其左右子节点的值。
3,对于每个节点的值都大于等于子树中每个节点值的堆,叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,叫“小顶堆”。
要实现一个堆,要先知道堆都支持哪些操作,已及如何存储一个堆。
1,如何存储一个堆:
完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。
2,往堆中插入一个元素
往堆中插入一个元素后,需要继续满足堆的两个特性
(1)如果把新插入的元素放到堆的最后,则不符合堆的特性了,于是需要进行调整,让其重新满足堆的特性,这个过程叫做 堆化(heapify)
(2)堆化实际上有两种,从下往上和从上往下
(3)从下往上的堆化方法:
堆化非常简单,就是顺着节点所在的路径,向上或者向下,对比,然后交换。
(1)从堆的定义的第二条中,任何节点的值都大于等于(或小于等于)子树节点的值,则堆顶元素存储的就是堆中数据的最大值或最小值。
(2)假设是大顶堆,堆堆顶元素就是最大的元素,但删除堆顶元素之后,就需要把第二大元素放到堆顶,那第二大元素肯定会出现在左右子节点中。然后在迭代地删除第二大节点,以此类推,直到叶子节点被删除。
但这种方式会使堆化出来的堆不满足完全二叉树的特性
(3)可以把最后一个节点放到堆顶,然后利用同样的父子节点对比方法,对于不满足父子节点大小关系的,互换两个节点,并且重复进行这个过程,直到父子节点之间满足大小关系为止,这是从上往下的堆化方法。
一个包含n个节点的完全二叉树,树的高度不会超过log2n。堆化的过程是顺着节点所在路径比较交换的,所以堆化的时间复杂度跟树的高度成正比,即O(log n)。插入数据和删除堆顶元素的主要逻辑就是堆化,所以往堆中插入一个元素和删除堆顶元素的时间复杂度都是O(log n)。
(1)排序方法有时间复杂度是O(n^2)的冒泡排序,插入排序,选择排序,有时间复杂度是O(nlogn)的归并排序,快速排序,线性排序。
(2)借助堆这种数据结构实现的排序算法就叫作堆排序,这种排序方法的时间复杂度非常稳定,是O(nlogn),并且它还是原地排序算法。
堆排序的过程大致分解为两大步骤:建堆和排序
(3)建堆:
1,首先将数组原地建成一个堆。“原地”:是指不借助另一个数组,就在原地数组上操作。
2,建堆有两种思路:
第一种:在堆中插入一个元素的思路。
尽管数组中包含n个数据,但是可以假设起初堆中只包含一个数据,就是下标为1的数据。然后,调用插入方法,将将下标从2到n的数据依次插入到堆中,这样就将包含n个数据的数组,组织成了堆
第二种:是从后往前处理数组,并且每个数据都是从上往下堆化。
第二种和第一种思路截然相反,第一种建堆思路的处理过程是从前往后处理数据,并且每个数据插入堆中时,都是从下往上堆化。
对下标从n/2开始到1的数据进行堆化,下标是n/2 + 1到n的节点,是叶子节点,不需堆化
3,建堆的时间复杂度
每个节点堆化的时间复杂度是O(logn),则n/2+1个节点堆化的总时间复杂度是O(n)。
①:因为叶子节点不需要堆化,所以需要堆化的节点从倒数第二层开始。每个节点堆化的过程中,需要比较和交换的节点个数,跟这个节点高度k成正比。
(4)排序:
建堆结束后,数组中的数据已是按照大顶堆的特性来组织的。数组中的第一个元素就是堆顶,也就是最大的元素。
将它和最后一个元素交换,最大元素就放到了下标为n的位置
这个过程有点类似“删除堆顶元素”的操作,当堆顶元素移除后,把下标为n的元素放到堆顶,然后在通过堆化的方法,将剩下的n-1个元素重新构建成堆。堆化完成之后,在取堆顶元素,放到下标是n-1的位置,一直重复这个过程,直到最后堆中只剩下标为1的一个元素,排序工作就完成了。
(5)时间,空间复杂度,以及稳定性分析
①:整个堆排序的过程,都只需要极个别临时存储空间,所以堆排序是原地排序算法。
②:堆排序包括建堆和排序两个操作,建堆过程的时间复杂度是O(n),排序过程的时间复杂度是O(nlogn),所以堆排序的时间复杂度是O(nlogn)
③:堆排序不是稳定的排序算法,可能改变值相等的数据原始相对顺序。