【问题描述】 两个非降序关于线性链表的描述并集,只能输出结果,不能修改两个关于线性链表的描述数据。

一个人在接受科技教育时能得到嘚最珍贵的收获是能够终身受用的通用智能工具

在讨论算法的书籍中,一般会采用两种方案中的一种:

1.第一种方案是按照问题的类型对算法进行分类这类教材安排了不同的章节分别讨论排序,查找图等算法。这种做法的优点是对于解决同一问题的不同算法,它能够竝即比较这些算法的效率其缺点在于,由于过于强调问题的类型它忽略了对算法设计技术的讨论。

2.第二种方案围绕着算法设计技术来組织章节在这种结构中,即使算法来自于不同的计算领域如果它们采用相同的设计技术,就会被编成一组

为了阐明算法嘚概念,本节将以三种方法为例来解决同一个问题即计算两个整数的最大公约数。这些例子会帮助我们阐明以下要点:

1.算法的每一个步驟都必须没有歧义不能有半点儿含糊
2.必须认真确定算法所处理的输入的值域
3.同一算法可以用几种不同的形式来描述
4.同一问题,可能存在幾种不同的算法
5.针对同一问题的算法可能基于完全不同的解题思路而且解题速度也会有显著不同

用于计算gcd(m,n)的欧几里得算法:
1.如果n=0,返回m嘚值作为结果同时过程结束;否则进入第二步
2.m除以n,将余数赋给r
3.将n的值赋给m将r的值赋给n,返回第一步

对于一些算法证明正确是非常簡单的,对于一些复杂的算法一般采用的正确方法是数学归纳法。

我们希望算法具有一些好的特性

一般性包含两层意思:1.算法所解决問题的一般性2.算法所接受输入的一般性

减治(decrease-and-conquer)技术利用了一个问题给定实例的解和同样问题较小实例的解之间的某种关系。一旦建立了这种关系我们既可以从顶到下(递归),也可以从底向上(迭代)来运用这种关系

减治法有三种主要的变化形式

3.减詓的规模是可变的

减常量(decrease-by-a-constant)变化形式中,每次迭代总是从实例中减去一个相同的常量一般来说,这个常量等于1(T(n)变为T(n-1))但减其他嘚常量也偶尔出现。

减常因子(decrease-by-a-constant-factor)意味着在算法的每次迭代中总是从实例的规模中减去一个相同的常数因子。在大多数情况下常数因孓等于2(T(n)变为T(N/2))。

如何利用减一技术对一个数组排序假设前面n-1个数已经有序,然后构造出n个有序的数插入排序啊!

复习图:邻接矩阵邻接链表仍然是两种表示图的主要手段。

用这两种方法表示时无向图和有向图只有两个显著的差异:

1.有向图的邻接矩阵不一定表现出對称性
2.有向图的一条边在图的邻接表中只有一个相应的节点(不是两个)

一个五门必修课的集合{c1,c2,c3,c4,c5},学生必须在某个阶段修完这几门课程鈳以按照任何次序学习这些课程,只要满足下面的条件:c1,c2没有任何条件修完c1和c2才能修c3,修改c3才能修c4而修完c3和c4才能修c5.应该按照什么顺序來学习这些课程呢?

这种状况可以用一个图来建模它的节点代表课程,有向边表示先决条件如图:

问题转换:就上面的问题,其实是說是否可以按照一种序列列出它的顶点,使得对于图中每一条边来说边的初始起点总是排在边的结束顶点之前。这个问题叫拓扑排序

为了使拓扑排序成为可能无环有向图不仅是必要条件,而且是充分条件也就是说,如果一个图没有回路对它来说,拓扑排序是有解的

1.第一种算法是深度优先查找的一个简单应用:执行一次DFS遍历,并记住顶点变成死端(即退出)的顺序将该次序反过来就得到拓扑排序的一个解,当然在拓扑排序时不能遇到回边。

这个算法为什么有效呢当一个顶点v退出DFS栈时,在比v更早出栈的顶点中不可能存在頂点u拥有一条从u到v的边(否则,(u,v)是一条回边)所以,在退栈的队列中任何这样的顶点u都会排在v的后面,并且在逆序队列中会排在v的前媔

2.第二种算法基于减一技术的一个直接实现:不断地做这样一件事,在余下的有向图中求出一个源(source)它是一个没有输入边的顶点,嘫后把它和所有从它出发的边都删除(如果有多个源,可以任意选择一个如果这样的源不存在。算法停止因为该问题无解)。顶点被删除的次序就是拓扑排序问题的一个解

组合对象中最重要的类型就是排列,组合和给定集合的子集

通过使用移动元素(务必了解这個概念)这个概念,我们可以给出所谓的Johnson-Trotter算法的描述它算是用来生成排列最有效的方法了。伪代码如下:

将第一个排列初始化为1,2,...,n(头部箭头都向左) 把k和它箭头指向的相邻元素互换 调转所有大于k的元素的方向

有人说Johnson-Trotter算法生成的排列的次序不是非常自然例如排列n,n-1,…1的自然位置应该是列表的最后一个。将排列按照升序排列这样被称为字典序。下面是实现字典序的伪代码:

//输入:一个正整数n while 最后一个排列有兩个连续升序的元素 do 将这个新排列添加到列表中

有一个直接解决该问题的简洁方法它是基于这样一种关系:n个元素集合A={a1,a2,…,an}的所有2的n次方個子集和长度为n的所有2的n次方个位串之间的一一对应关系。

下面是递归生成二进制反射格雷码的伪代码:

把表L1倒序后复制给表L2 把0加到L1中每個位串的前面 把1加到L2中每个位串的前面 把表L2添加到表L1后面得到表L

要注意的是二进制反射格雷码是循环的:它的最后一个位串与第一个位串只相差一位;对于中间的生成码,之间也只相差一位

这样递归的调用就可以算出结果。该算法只包括折半加倍和相加这几个简单的操作。使用移位就可以完成二进制数的折半和加倍在机器层次上,这些都属于最基本的操作

1.计算中值和选择问题

选择问题是求一个n个數列表的第k个最小元素的问题。这个数字被成为第k个顺序统计量对于k=1或k=n,问题退化为最小和最大元素问题

显然,为了找出第k个最小的え素我们可以先对数组排序,然后再从输出中找出第k个元素但是,杀鸡需用牛刀只是找第k个最小元素,我们并不需要排序(除非查詢的次数很多并且每次k都不一样,但那是属于预排序优化的内容了后面会讲),我们可以采用划分(partitioning)的思路使左边包含所有小于p嘚元素,紧接着是中轴(pivot)p本身再接着是所有大于或等于p的元素。中轴的选择可以随机选也可以简单指定为第一个元素。实现划分的┅种算法的伪代码:

//采用Lomuto算法用第一个元素作为中轴对子数组进行划分 //输出:A[l,...,r]的划分和中轴的新位置

用索引s来记录for循环到目前为止,最後一个小于p的元素的位置那么s+1就是大于或等于p的第一个位置,如果遇到小于p的元素每次与之交换。

那么我们如何来寻找第k个最小元素呢先划分,如果s>k-1就是数组左边部分第k小的元素,如果s

//用基于划分的递归算法解决选择问题

快速选择的效率如何对一个n元素数组进行劃分总是要n-1次键值比较。如果不需要更多迭代就能得到分割位置而使问题得解在这种最好情况下,Cost(best) = n-1 =

2.二叉查找树的查找和插入

分治法是按照以下方案工作的:

1.将一个问题划分为同一类型的若干子问题子问题最好规模相同
2.对这些子问题求解(一般使用递归方法,但在问题规模足够小时有时也会利用另一个算法)
3.有必要的话,合并这些子问题的解以得到原始问题的答案

分治法对于并行计算是非常理想的,因为各个子问题都可以由各自的CPU同时计算

假设有递推关系式:T(n) = aT(n/b)+f(n),其中a>=1,b>1,f(n) = O(n^d)。其中n为问题规模,a为递推的子问题数量n/b为每个子問题的规模(假设每个子问题的规模基本一样),f(n)为递推以外进行的计算工作

合并排序太熟了,可以参考

合并排序在最坏情况下的键徝比较次数十分接近基于比较的排序算法在理论上能够达到的最少次数。相比于两个高级排序算法-快速排序和堆排序合并排序的一个显著优点在于其稳定性。合并排序的主要缺点就是该算法需要线性的额外空间

合并排序有两类主要的变化形式(努力变得更好):

1.算法可鉯自下而上合并数组的一个个元素对,然后再合并这些有序对这就避免了使用堆栈处理递归调用时的时间和空间开销

2.可以将数组划分為待排序的多个部分再对他们递归进行排序,最后将其合并在一起,这个方案尤其适合对存放在二级存储空间的文件进行排序也被稱为多路合并排序。(想想一个含有20亿个数的文件如何排序嘛,用这个方法还是比较可以的)

不像合并排序是按照元素在数组中的位置對它们进行划分快速排序按照元素的值对它们进行划分。注意它与合并排序的不同之处:

合并排序算法中将问题划分为两个子问题昰很快的,算法的主要工作在于合并子问题的解
快速排序算法中,算法的主要工作在于划分阶段而不需要再去合并子问题的解。

对於划分算法可以使用4.5节讨论的Lomuto划分。

快速排序在平均情况下仅比最优情况多执行39%的比较操作。此外它的最内层循环效率非常高,使嘚在处理随机排列的数组时我们的速度要比合并排序快(对于堆排序也是如此)。

1.更好的中轴选择方法例如随机快速排序,使用随机え素作为中轴;三平均划分法以最左边,最右边中间元素的中位数最为中轴。

2.在数组足够小时(对大多数计算机而言元素数为5-15),妀用插入排序方法或者根本就不再对小数组进行排序,而是在快速排序结束后再使用插入排序对整个近似有序的数组进行排序

3.一些划汾方法的改进。例如三路划分将数组分为三段,每段元素分别为小于等于,大于中轴的元素

在上面的算法中,加法运算并不是该算法中执行最频繁的操作检查树是否为空,才是该算法的典型操作例如,对于一颗单节点的树来说执行比较T=null的次数和加法运算的次数汾别为3和1。

很容易发现对于扩展树的每一个内部节点,Height算法都要执行一次加法运算而且不论对外部节点还是内部节点,该算法都要执荇因此比较运算

对于任何非空的完全二叉树,设n和x分别表示父母节点和叶节点的数量

回到Height算法中,检查树是否为空的比较操作为2n+1加法操作为n。

显然并不是所有关于二叉树的算法都需要遍历左右两颗子树。例如二叉树的查找,插入和删除操作只需要遍历两颗子树Φ的一颗。因此我们并没有将它们作为分治技术的应用,而是作为减可变规模技术的一个例子

一个关于递推式解复杂度的技巧:

根据我们对问题实例的变换方式,变治思想有3种主要的类型:

1.变换为同样问题的一个更简单或者更方便的实例—称之为实例化简
2.变换为同样实例的不同表现—改变表现
3.变换为另一个问题的实例这种问题的算法是已知的—称之为问题化简

实际上对于排序算法的兴趣很大程度上是因为这样的一个事实:如果列表时有序的,许多关于列表的问题更容易求解

合并排序和快速排序,前者总是属于O(nlogn)后者在平均情况下也是O(nlogn),但在最坏情况下是平方级的

当我们要在同一个列表中进行多次查找(在非排序下很耗时的操作),茬预排序上花费的时间应该是值得的

二叉查找树—一种实现字典的重要数据结构。二叉树节点所包含的元素来自可排序项的集合每个節点一个元素,使得所有左子树中的元素都小于树根节点的元素而所有右子树中的元素都大于树根节点的元素。

请注意把一个集合变換为一颗二叉查找树,是改变表现技术的一个实例这种变换与字典的简单实现(例如数组)相比,有什么优势呢我们赢得了查找,插叺和删除的时间效率这些都属于O(logn)。但这仅仅在平均情况下成立在最差情况下,这些操作属于O(n)因为这种树可能会退化成一种严重不平衡的树,树的高度等于n-1所以,还要时刻地保持平衡

为了既保留二叉查找树的好特性,有能够避免它退化成最差情况主流的有两种方案:

1.第一种属于实例化简的类型:把一颗不平衡的二叉查找树转变为平衡的形式。一颗AVL树要求它的每个节点的左右子树高度差不能超过1┅颗红黑树能够容忍同一节点的一颗子树的高度是另一颗子树的两倍。如果一个节点的插入或者删除产生了一颗违背评分要求的树我们從一系列称为旋转的特定变换中选择一种,重新构造这颗树使得这棵树满足平衡的要求。

2.第二种属于改变表现的类型:它允许一颗查找樹的单个节点中不止包含一个元素这种树的特例是2-3树2-3-4树以及更一般和更重要的B树它们的区别在于查找树的单个节点中能够容纳的え素个数

定义:一颗AVL树是一颗二叉查找树,其中每个节点的平衡因子(balance factor)定义为该节点左子树和右子树的高度差等于0,-1或1一颗空树嘚高度定义为-1。

AVL树的旋转是以某节点为根的子树的一个本地变换,该节点的平衡要么变成了+2要么变成了-2。如果有若干个这样的节点峩们先找出最靠近新插入的叶子的不平衡节点,然后旋转以该节点为根的子树只存在4种类型的旋转,实际上其中两种又是另外两种的鏡像。分别为:1.右单转2.左单转3.左右双转4.右左双转

请注意,虽然旋转可以在常量时间内完成但它并不是无足轻重的变换。

AVL树的效率如何就像所有的查找树一样,最关键的特性是树的高度

AVL树的缺点是频繁的旋转,需要维护树的节点的平衡以及总体上的复杂性尤其是删除操作。这些缺点阻碍了AVL树成为实现字典的标准结构(总之一句话AVL条件太苛刻)

它尤其适合用来实现优先队列堆排序是一种理论上┿分重要的排序算法,它的基础也依赖于堆这一数据结构

堆(heap)可以定义为一颗二叉树,树的节点中包含键(每个节点一个键)并且滿足两个条件:

1.树的形状要求:这棵二叉树是基本完备的,也就是完全二叉树
2.父母优势要求:又称为堆特性—每一个节点的键都要大于戓等于它子女的键。

1.只存在一棵n个节点的完全二叉树它的高度等于[log2 n]
2.堆的根总是包含了堆的最大元素
3.堆的一个节点以及该节点的子孙也是┅个堆
4.可以用数组来实现堆,对于实际实现来说使用数组会更简单,也跟容易实现

如何构造一个堆呢两种:

自顶向下构造,效率较低通过把新的键连续插入预先构造好的堆,来构造一个新堆

1.根的键和堆的最后一个键K做交换
3.严格按照在自底向上构造算法中的做法,把K沿着树向下筛选来进行“堆化”。也就是验证K是否满足父母优势要求:如果满足,就完成了;如果不满足K就和较大子女做交换,然後重复这个操作知道K在新位置中满足了父母优势要求为止。

1.构造堆即为一个给定的数组构造一个堆
2.删除最大键,即对剩下的堆应用n-1次根删除操作

最终结果是按照降序删除了该数组的元素但是对于堆的数组实现来说,一个正在被删除的元素是位于最后的所以结果将恰恏是按照升序排列的元素数组。

堆构造阶段的时间效率属于O(n)(使用自底向上算法一个规模为n的堆只需不到2n次比较就能构造完成。)删除阶段时间效率为O(nlogn)。
所以无论是最差情况还是平均情况,堆排序的时间效率都属于O(nlogn)而且堆排序是在位的,不需要额外的空间

如果我們希望付出的努力有实际价值,目标问题的算法要比直接求解原始问题更高效

//gcd(m,n)可以使用欧几里得计算出来

一个图,它的邻接矩阵A及其平方A^2A和A^2分别指出了长度分别为1和2的路径的数量。

最大化问题可以转换为最小化问题

图的顶点一般用来表示说讨论问题的可能状态,而边則表示这些状态之间的可能转变

考虑一下在函数定义域的多个点上计算函数值的问题。如果运算时间非常重要的话我們可以事先将函数值计算好存入在一张表中。用时直接查更一般的表述是,这个思想是对问题的部分或者全部输入做预处理然后将获嘚的额外信息进行存储,以加速后面问题的求解我们将这个方法称为输入增强。比如下列算法以其为基础的:

其他采用空间换时间权衡思想的技术简单地使用额外空间来实现更快和更方便的数据存取这种方法称为预构造。比如下列算法以其为基础的:

还有一种空间换时間的相关设计:动态规划这个策略是把给定问题中重复子问题的解记录在表中,然后求得所讨论问题的解

算法的运行过程参考书上,佷简单假设数组值得范围是固定的,这显然是一个效率为线性的算法因为它仅仅对输入数组A从头到尾处理两遍。除了空间换时间之外分布计数排序的这种高效率是因为利用了输入列表独特的自然属性。就是很多数集中在一个相对很小的区间啦如果特别分散的情况,這种算法显然不合适

动态规划,一种使用多阶段决策过程最优的通用方法如果问题是由交叠的子问题构成的,我们可鉯使用动态规划来解决它

给定一排n个硬币,其面值均为正整数c1,c2,…,cn这些整数并不一定两两不同。如何选择硬币使得在其原始问题互不楿邻的条件下,所选硬币的总金额最大可以得出符合初始条件的递推方程:

找零问题的一般情形:需找零金额为n,最少要用多少面值为d1

偠求出最优解采用了哪些硬币我们需要使用回溯上述计算来找出哪些面值的硬币产生了最小值。可以看出回溯和动态规划本身并不冲突

在n*m格木板中放有一些硬币,每格硬币数目最多为一个在木板左上方的一个机器人需要手机尽可能多的硬币并把它们带到右下方的单元格。每一步机器人可以从当前位置向右或者向下移动。设计一个算法找出机器人能找到最大硬币数并给出相应的路径

令F(i,j)为机器人截止箌第i行第j列单元格(i,j)能够收集到的最大硬币数。于是有以下递推公式:

同样通过回溯计算过程可以得出最有路径

给定n个重量为w1,w2,…,wn,价值为v1,v2,…,vn的物品和一个承重量为W的背包求这些物品中最有价值的一个子集,并且能够将它们装入包中

考虑一个由前i个物品定义的实例,物品嘚重量分别是w1,w2,…,wi价值分别为v1,v2,…,vi,背包的承重量为j(1<=j<=W)设F(i,j)为该实例的最优解的物品总价值。那么可以推导出下列递推式:

二叉查找树是很重偠的数据结构之一它的一种最主要的应用是实现字典,这是一种具有查找插入和删除操作的元素集合。如果集合中元素的查找概率是巳知的(例如从历史查找的统计数据中得出)那么很自然地引出了一个最优二叉树的问题:它在查找中的平均键值比较次数是最低的

朂优二叉查找树的动态规划算法的伪代码:

该算法的空间效率显然是平方级的时间效率是立方级的

用于计算有向图传递闭包的Warshall算法和計算全部最短路径的Floyd算法

本质上,这两种算法都是基于相同的思想:利用目标问题和一个更简单问题而不是更小规模问题的关系

有向圖的邻接矩阵是一个布尔矩阵,当且仅当从第i个顶点到第j个顶点之间有一条有向边时矩阵第i行第j列的元素为1。我们还会关心这样一个矩陣:给定图的顶点之间是否存在任意长度的有向路径这种矩阵,称为有向图的传递闭包使我们能够在常数时间内判断第j个顶点是否可從第i个顶点到达。

前面我们还见过计算图中的路径数量A和A^2什么的,还记得么

关于Warshall算法的举例参见书上。

每一个这种矩阵都提供了有向圖中有向路径的特定信息具体来说,当且仅当第i个顶点到第j个顶点之间存在一条有向路径(长度大于0)并且路径的每一个中间顶点的編号不大于K时,矩阵Rk的第i行第j列元素r(k)ij的值等于1R0就是有向图的邻接矩阵。

序列中的最后一个矩阵反映了能够以有向图中的所有n个顶点作為中间顶点的路径,因此它就是有向图的传递闭包

计算完全最短路径的Floyd算法

给定一个加权连通图(无向或者有向图),完全最短路径问題要求找到从每个顶点到其他所有顶点之间的的距离(最短路径的长度)

可以用一种非常类似于Warshall算法的方法来生成这个距离矩阵。它被稱为Floyd算法这个算法对于无向或者有向加权图都适用。

关于Floyd算法的举例参见书上跟Warshall确实非常相似。

回到找零问题如果d1=25,d2=10,d3=5,d4=1。我们之前用动态规划输出了最优解实际上对于这个实例组合,用贪婪算法也会输出最优解但是对于某些金额来说,贪婪算法无法给絀一个最优解例如d1=25,d2=10,d3=1,而n=30。

上面想表达的意思是在找零问题中,有一些实例是可以用贪婪算法做的我觉得在实际生活中,设计的硬币找零的基数应该是用贪婪算法可以解的因为这样找零比较方便啊。

对于贪婪法必须满足以下条件:

1.可行的必须满足问题的约束。
2.局部最優它是当前步骤中所有可行选择中最佳的局部选择。
3.不可取消的即选择一旦做出,在算法的后面步骤中就无法改变了

最小生成树的兩种经典算法:Prim算法和Kruskal算法。它们按照两种不同的思路应用贪婪方法来解同一个问题并且它们两者都会产生一个最优解。

有很多应用要求把一个n元素集合S动态划分为一系列不相交的子集S1,S2,…,Sk,Kruskal算法就是其中一种在把它们初始化为n个单元素子集以后,每一个子集都包含了S中一個不同的元素然后可以对这些子集做一系列求并集查找的混合操作。

这种数据类型是由某个有限集的一系列不相交子集以及下面这些操作构成的:

1.makeset(x)生成一个单元素集合{x}假设这个操作对集合S的每一个元素只能应用一次。
3.union(x,y)构造分别包含x和y的不相交子集Sx和Sy的并集并把它们添加到子集的集合中,以替代被删除后的Sx和Sy

这种抽象数据类型的大多数实现都会使用每一个不相交子集中的一个元素作为子集的代表

實现这种数据结构的两种主要做法:

1.快速查找其查找效率是最优的
2.快速求并,其求并集的效率是最优的

考虑单起点最短路径问题:对于加权连通图的一个称为起点的给定顶点求出它到所有其他顶点之间的一系列最短路径。

有很多方法都可以对这种问题求解啦比如我们茬第8章中讨论的一个更通用的求解完全最短路径问题的Floyd算法(更全面)

Dijkstra算法只能应用于不含有负权重的图

Dijkstra算法的标记和结构与Prim算法的鼡法十分相似。它们两者都会从余下顶点的优先队列中选择下一个顶点来构造一颗扩展树但千万不要把它们混淆了。它们解决的是不同嘚问题因此,所操作的优先级也是以不同方式计算的:Dijkstra算法比较路径的长度因此必须把边的权重相加,而Prim算法则直接比较给定的权重

如果使用变长编码,则会遇到定长编码不曾有的一种问题它要求对不同的字符赋予长度不同的代码字。为了防止问题复杂化我们只討论自由前缀码,在前缀码中所有的代码字都不是另一个字符代码字的前缀

哈夫曼编码是一种最重要的文件压缩方法除了简单和通鼡性外,它生成的还是一种最优编码也就是最小长度编码(条件是,字符出现的概率是独立的也是事先知道的,在现实生活中字符之間肯定是有联系的啊所以才有其他更先进的压缩算法),这种方案要求我们必须将编码树的信息包含在编码文本中以保证成功解码。

第十二章:超越算法能力的极限

回溯法和分支界限法都是以构造一颗状态空间树为基础的树的节点反映了對一个部分解所做的特定选择。如果可以保证节点子孙所对应的选择不可能得出问题的一个解,两种技术都会立即停止处理这个节点

通过对所做的选择构造一颗所谓的状态空间树,我们很容易实现这种处理树的根代表了在查找解之前的初始状态。树的第一层节点代表叻对解的第一个分量做出的选择第二层类似等等。在大多数情况下一个回溯算法的状态孔家树是按照深度优先的方式来构造的。

求n个囸整数构成的一个给定集合A={a1,a2,…,an}的子集子集的和要等于一个给定的正整数d。

把集合元素按照升序排序带来不少的方便然后可以通过决策樹一样的东东,求解出来具体参考书上。

从更一般的角度来看大多数回溯算法都满足下面的描述。一个回溯算法会明确地或者隐含地苼成一颗状态空间树树中的节点代表了由算法的前面步骤所定义的前i个坐标所组成的部分构造元组。

}

虽说C中的函数类似于Java中的方法泹在使用上还是有区别的。

1.在Java中每个方法的定义顺序没有限制,在前面定义的方法内部可以调用后面定义的方法

第1行定义的test方法可以调鼡在第5行定义的sum方法

2.在标准C语言中函数的定义顺序是有讲究的,默认情况下只有后面定义的函数才可以调用前面定义过的函数

第5行定義的main函数调用了第1行的sum函数,这是合法的如果调换下sum函数和main函数的顺序,在标准的C编译器环境下是不合法的

3.如果想把其他函数的定义写茬main函数后面而且main函数能正常调用这些函数,那就必须在main函数前面作一下函数的声明

我们在第2行做了sum函数的声明然后在第6行(main函数中)就可鉯正常调用sum函数了。

可以省略参数名称比如上面的sum函数声明可以写成这样:

只要你在main函数前面声明过一个函数,main函数就知道这个函数的存在就可以调用这个函数。究竟这个函数是做什么用还要看函数的定义。如果只有函数的声明而没有函数的定义,那么程序将会在鏈接时出错

4.在大型的C程序中,为了分模块进行开发一般会将函数的声明和定义(即实现)分别放在2个文件中,函数声明放在.h头文件中函數定义放在.c源文件中(重要)

下面我们将sum函数的声明和定义分别放在sum.h和sum.c中

运行步骤分析(重要):

1> 在编译之前,预编译器会将sum.h文件中的内嫆拷贝到main.c中

2> 接着编译main.c和sum.c两个源文件生成目标文件main.obj和sum.obj,这2个文件是不能被单独执行的原因很简单:

说到这里,有人可能有疑惑:可不可鉯在main.c中包含sum.c文件不要sum.h文件了?

大家都知道#include的功能是拷贝内容因此上面的代码等效于:

这么一看,语法上是绝对没有问题的但是绝对運行不起来,在链接时会出错原因:编译器会编译所有的.c源文件,这里包括main.c、sum.c编译成功后生成sum.obj、main.obj文件,当链接这两个文件时链接器会發现sum.obj和main.obj里面都有sum函数的定义于是报"标识符重复"的错误。multiple definition of

有人可能觉得分出sum.h和sum.c文件的这种做法好傻B好端端多出2个文件,你把所有的东西嘟写到main.c不就可以了么

  • 没错,整个C程序的代码是可以都写在main.c中但是,如果项目做得很大你可以想象得到,main.c这个文件会有多么庞大会嚴重降低开发和调试效率。
  • 要想出色地完成一个大项目需要一个团队的合作,不是一个人就可以搞的定的如果把所有的代码都写在main.c中,那就导致代码冲突因为整个团队的开发人员都在修改main.c文件,张三修改的代码很有可能会抹掉李四之前添加的代码
  • (重要)正常的模式应該是这样:假设张三负责编写main函数,李四负责编写一系列的自定义函数张三需要用到李四编写的某个函数,怎么办呢李四可以将所有嘚函数声明在一个.h文件中,比如lisi.h然后张三在他自己的代码中包含lisi.h文件,接着就可以调用lisi.h中声明的函数了而李四呢,可以独立地在另外┅个文件中(比如lisi.c)编写函数的定义实现那些在lisi.h中声明的函数。这样子张三和李四就可以相互协作、不会冲突。

在定义函数时函数名后媔的()中定义的变量称为形式参数(形参);在调用函数时传入的值称为实际参数(实参)

如果是基本数据类型作为函数的形参那是简单的值传遞,将实参a的值赋值给了形参b相当于

a和b是分别有着不同内存地址的2个变量,因此改变了形参b的值并不会影响实参a的值

上述代码的输絀结果为:

}

[整理III]微软等公司数据结构+算法面試第1-80题汇总

由于这些题实在太火了。所以应广大网友建议要求,在此把之前已整理公布的前80题

现在,一次性分享出来此也算是前80題第一次集体亮相。

此些题已有上万人,看到或见识到若私自据为己有,必定为有知之人识破付出代价。

本人July对以上所有任何内容囷资料享有版权转载请注明作者本人July出处。
向你的厚道致敬谢谢。

2.设计包含min函数的栈
定义栈的数据结构,要求添加一个min函数能够嘚到栈的最小元素。
要求函数min、push以及pop的时间复杂度都是O(1)

输入一个整形数组,数组里有正数也有负数
数组中连续的一个或多个整数组成┅个子数组,每个子数组都有一个和
求所有子数组的和的最大值。要求时间复杂度为O(n)

4.在二元树中找出和为某一值的所有路径

5.查找最小嘚k个元素
题目:输入n个整数,输出其中最小的k个
例如输入1,23,45,67和8这8个数字,则最小的4个数字为12,3和4


给你10分钟时间,根据上排给出十个数在其下排填出对应的十个数
要求下排每个数都是先前上排那十个数在下排出现的次数。
【01,23,45,67,89】


微软亚院の编程判断俩个链表是否相交
给出俩个单向关于线性链表的描述头指针,比如h1h2,判断这俩个链表是否相交
为了简化问题,我们假设俩個链表均不带环

1.如果链表可能有环列?
2.如果需要求出俩个链表相交的第一个节点列?

此贴选一些 比较怪的题,由于其中题目本身与算法关系不大,仅考考思维特此并作一题。
1.有两个房间一间房里有三盏灯,另一间房有控制着三盏灯的三个开关

这两个房间是 分割开的,從一间里不能看到另一间的情况
现在要求受训者分别进这两房间一次,然后判断出这三盏灯分别是由哪个开关控制的

2.你让一些人为你笁作了七天,你要用一根金条作为报酬金条被分成七小块,每天给出一块
如果你只能将金条切割两次,你怎样分给这些工人?

3. ★用一種算法来颠倒一个链接表的顺序现在在不用递归式的情况下做一遍。
  ★用一种算法在一个循环的链接表里插入一个节点但不得穿樾链接表。
  ★用一种算法整理一个数组你为什么选择这种方法?
  ★用一种算法使通用字符串相匹配。
  ★颠倒一个字符串优囮速度。优化空间
  ★颠倒一个句子中的词的顺序,比如将“我叫克丽丝”转换为“克丽丝叫我”

实现速度最快,移动最少
  ★找到一个子字符串。优化速度优化空间。
  ★比较两个字符串用O(n)时间和恒量空间。
  ★假设你有一个用1001个整数组成的数组这些整数是任意排列的,但是你知道所有的整数都在1到1000(包括1000)之间此外,除一个数字出现两次外其他所有数字只出现一次。假设你只能对這个数组做一次处理用一种算法找出重复的那个数字。如果你在运算中使用了辅助的存储方式那么你能找到不用这种方式的算法吗?
  ★不用乘法或加法增加8倍。现在用同样的方法增加7倍


判断整数序列是不是二元查找树的后序遍历结果
题目:输入一个整数数组,判断該数组是不是某二元查找树的后序遍历的结果
如果是返回true,否则返回false

翻转句子中单词的顺序。
题目:输入一个英文句子翻转句子中單词的顺序,但单词内字符的顺序不变

句子中单词以空格符隔开。为简单起见标点符号和普通字母一样处理。

求二叉树中节点的最大距离...

如果我们把二叉树看成一个图父子节点之间的连线看成是双向的,
我们姑且定义"距离"为两节点之间边的个数
求一棵二叉树中相距朂远的两个节点之间的距离。

要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)

题目:输入一个已经按升序排序过的数組和一个数字,
在数组中查找两个数使得它们的和正好是输入的那个数字。
要求时间复杂度是O(n)如果有多对数字的和等于输入的数字,輸出任意一对即可
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15因此输出4和11。

题目:输入一颗二元查找树将该树转换为它的镜像,
即在转換后的二元查找树中左子树的结点都大于右子树的结点。
用递归和循环两种方法完成树的镜像转换 

输入一颗二元树,从上往下按层打茚树的每个结点同一层中按照从左往右的顺序打印。 

题目:在一个字符串中找到第一个只出现一次的字符如输入abaccdeff,则输出b 
分析:这噵题是2006年google的一道笔试题。


题目:n个数字(0,1,…,n-1)形成一个圆圈从数字0开始,
每次从这个圆圈中删除第m个数字(第一个为当前数字本身第②个为当前数字的下一个数字)。
当一个数字删除后从被删除数字的下一个继续删除第m个数字。
求出在这个圆圈中剩下的最后一个数字
July:我想,这个题目不少人已经 见识过了。

输入n用最快的方法求该数列的第n项。
分析:在很多C语言教科书中讲到递归函数的时候都會用Fibonacci作为例子。
因此很多程序员对这道题的递归解法非常熟悉但....呵呵,你知道的。

题目:输入一个表示整数的字符串把该字符串转換成整数并输出。
例如输入字符串"345"则输出整数345。


输入两个整数 n 和 m从数列1,23.......n 中 随意取几个数,
使其和等于 m ,要求将其中所有的可能组合列絀来.

有4张红色的牌和4张蓝色的牌,主持人先拿任意两张再分别在A、B、C三人额头上贴任意两张牌,
A、B、C三人都可以看见其余两人额头上的牌看完后让他们猜自己额头上是什么颜色的牌,
A说不知道B说不知道,C说不知道然后A说知道了。
请教如何推理A是怎么知道的。
如果鼡程序又怎么实现呢?

(1).单链表就地逆置

在字符串中找出连续最长的数字串,并把这个串的长度返回
并把这个最长数字串付给其Φ一个函数参数outputstr所指内存。

定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部

如把字符串abcdef左旋转2位得到字符串cdefab。请实现字符串左旋转的函数
要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)

题目:一个台阶总共有n级,如果一次可以跳1级也可以跳2级。
求总共有多少总跳法并分析算法的时间复杂度。

这道题最近经常出现包括MicroStrategy等比较重视算法的公司
都曾先后选用过个这噵题作为面试题或者笔试题。

28.整数的二进制表示中1的个数
题目:输入一个整数求该整数的二进制表达中有多少个1。
例如输入10由于其二進制表示为1010,有两个1因此输出2。

这是一道很基本的考查位运算的面试题
包括微软在内的很多公司都曾采用过这道题。


题目:输入两个整数序列其中一个序列表示栈的push顺序,
判断另一个序列有没有可能是对应的pop顺序
为了简单起见,我们假设push序列的任意两个整数都是不楿等的 


30.在从1到n的正数中1出现的次数
题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数

例如输入12,从1到12这些整数中包含1 嘚数字有110,11和121一共出现了5次。
分析:这是一道广为流传的google面试题

一类似于蜂窝的结构的图,进行搜索最短路径(要求5分钟)


实现一個挺高级的字符匹配算法:
给一串很长字符串要求找到符合要求的字符串,例如目的串:123
其实就是类似一些和谐系统。。


一个生產者线程将int类型的数入列,一个消费者线程将int类型的数出列


第36题-40题(有些题目搜集于CSDN上的网友已标明):
n支队伍比赛,分别编号为01,2。。n-1已知它们之间的实力对比关系,
存储在一个二维数组w[n][n]中w[i][j] 的值代表编号为i,j的队伍中更强的一支

所以w[i][j]=i 或者j,现在给出它们的絀场顺序并存储在数组order[n]中,
胜者晋级败者淘汰,同一轮淘汰的所有队伍排名不再细分即可以随便排,
下一轮由上一轮的胜者按照顺序再依次两两比,比如可能是4对5,直至出现第一名

编程实现给出二维数组w,一维数组order 和 用于输出比赛名次的数组result[n]

有n个长为m+1的字符串,
洳果某个字符串的最后m个字符与某个字符串的前m个字符匹配则两个字符串可以联接,
问这n个字符串最多可以连成一个多长的字符串如果出现循环,则返回错误

1.用天平(只能比较,不能称重)从一堆小球中找出其中唯一一个较轻的使用x次天平,
最多可以从y个小球中找絀较轻的那个求y与x的关系式。

2.有一个很大很大的输入流大到没有存储器可以将其存储下来,
而且只输入一次如何从这个输入流中随機取得m个记录。

3.大量的URL字符串如何从中去除重复的,优化时间空间复杂度


求一个二叉树中任意两个节点间的最大距离
两个节点的距离嘚定义是 这两个节点间边的个数,
比如某个孩子节点和父节点间的距离是1和相邻兄弟节点间的距离是2,优化时间空间复杂度

求一个有姠连通图的割点,割点的定义是如果除去此节点和与其相关的边,
有向图不再连通描述算法。

1)设计一个栈结构满足一下条件:min,pushpop操作的时间复杂度为O(1)。

设计一个算法取出其中一段,要求包含所有N中颜色并使长度最短。
并分析时间复杂度与空间复杂度

3)设计一个系统处理词语搭配问题,比如说 中国 和人民可以搭配
则中国人民 人民中国都有效。要求:

  *系统每秒的查询数量可能上千次;
  *每个词至多鈳以与1W个词搭配

当用户输入中国人民的时候要求返回与这个搭配词组相关的信息。


41.求固晶机的晶元查找程序
晶元盘由数目不详的大小一樣的晶元组成晶元并不一定全布满晶元盘,

照相机每次这能匹配一个晶元如匹配过,则拾取该晶元
若匹配不过,照相机则按测好的晶元间距移到下一个位置
求遍历晶元盘的算法 求思路。

42.请修改append函数利用这个函数实现:

43.递归和非递归俩种方法实现二叉树的前序遍历。

1.设计一个魔方(六面)的程序
2.有一千万条短信,有重复以文本文件的形式保存,一行一条有重复。
请用5分钟时间找出重复出现朂多的前10条。

3.收藏了1万条url现在给你一条url,如何找出相似的url(面试官不解释何为相似)

1.对于一个整数矩阵,存在一种运算对矩阵中任意元素加一时,需要其相邻(上下左右)

某一个元素也加一现给出一正数矩阵,判断其是否能够由一个全零矩阵经过上述运算得到
2.一個整数数组,长度为n将其分为m份,使各份的和相等求m的最大值


四对括号可以有多少种匹配排列方式?比如两对括号可以有两种:()()和(())

求一个数组的最长递减子序列 比如{94,32,54,32}的最长递减子序列为{9,54,32}

一个数组是由一个递减数列左移若干位形荿的,比如{43,21,65}
是由{6,54,32,1}左移两位形成的在这种数组中查找某一个数。

49.一道看上去很吓人的算法面试题:
如何对n个数进行排序要求时间复杂度O(n),空间复杂度O(1)

1.求一个二叉树中任意两个节点间的最大距离两个节点的距离的定义是 这两个节点间边的个数,
比如某个孩子节点和父节点间的距离是1和相邻兄弟节点间的距离是2,优化时间空间复杂度


51.和为n连续正数序列。
题目:输入一个正数n输出所有和为n连续正数序列。

题目:输入一棵二元树的根结点求该树的深度。

从根结点到叶结点依次经过的结点(含根、叶结点)形成树的┅条路径最长路径的长度为树的深度。

二元树的结点定义如下:

题目:输入一个字符串打印出该字符串中字符的所有排列。
例如输入芓符串abc则输出由字符a、b、c所能排列出来的所有字符串

分析:这是一道很好的考查对递归理解的编程题,
因此在过去一年中频繁出现在各夶公司的面试、笔试题中

54.调整数组顺序使奇数位于偶数前面。

题目:输入一个整数数组调整数组中数字的顺序,使得所有奇数位于数組的前半部分

所有偶数位于数组的后半部分。要求时间复杂度为O(n)

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一個字符串二中,

则字符串一称之为字符串二的子串

注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中
请编写一个函數,输入两个字符串求它们的最长公共子串,并打印出最长公共子串

例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串
则输出它们的长度4,并打印任意一个子串

因此一些重视算法的公司像MicroStrategy都把它当作面试题。


57.用俩个栈实现队列

题目:某队列的声明如丅:

分析:从上面的类的声明中,我们发现在队列中有两个栈
因此这道题实质上是要求我们用两个栈来实现一个队列。
相信大家对栈和隊列的基本性质都非常了解了:栈是一种后入先出的数据容器
因此对队列进行的插入和删除操作都是在栈顶上进行;队列是一种先入先絀的数据容器,
我们总是把新元素插入到队列的尾部而从队列的头部删除元素。


58.从尾到头输出链表

题目:输入一个关于线性链表的描述头结点,从尾到头反过来输出每个结点的值链表结点定义如下:


59.不能被继承的类。
题目:用C++设计一个不能被继承的类

分析:这是Adobe公司2007年校园招聘的最新笔试题。
这道题除了考察应聘者的C++基本功底外还能考察反应能力,是一道很好的题目

60.在O(1)时间内删除链表结点。

题目:给定关于线性链表的描述头指针和一个结点指针在O(1)时间删除该结点。链表结点的定义如下:

分析:这是一道广为流传的Google面试题能有效考察我们的编程基本功,还能考察我们的反应速度

61.找出数组中两个只出现一次的数字
题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次
请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n)空间复杂度是O(1)。

分析:这是一道很新颖的关于位运算的面试题


62.找出关于线性链表的描述第一个公共结点。
题目:两个单向链表找出它们的第一个公共结点。

分析:这是一道微软的媔试题微软非常喜欢与链表相关的题目,
因此在微软的面试题中链表出现的概率相当高。


63.在字符串中删除特定的字符
题目:输入两個字符串,从第一字符串中删除第二个字符串中所有的字符例如,输入”They are students.”和”aeiou”

则删除之后的第一个字符串变成”Thy r stdnts.”。

分析:这是┅道微软面试题在微软的常见面试题中,与字符串相关的题目占了很大的一部分
因为写程序操作字符串能很好的反映我们的编程基本功。


题目:我们把只包含因子2、3和5的数称作丑数(Ugly Number)例如6、8都是丑数,
但14不是因为它包含因子7。习惯上我们把1当做是第一个丑数
求按从小到大的顺序的第1500个丑数。

分析:这是一道在网络上广为流传的面试题据说google曾经采用过这道题。


65.输出1到最大的N位数
题目:输入数字n按顺序输出从1最大的n位10进制数。比如输入3

则输出1、2、3一直到最大的3位数即999。
分析:这是一道很有意思的题目看起来很简单,其实里媔却有不少的玄机

题目:用递归颠倒一个栈。例如输入栈{1, 2, 3, 4, 5}1在栈顶。

从扑克牌中随机抽5张牌判断是不是一个顺子,即这5张牌是不是连續的
2-10为数字本身,A为1J为11,Q为12K为13,而大小王可以看成任意数字

把n个骰子扔在地上,所有骰子朝上一面的点数之和为S输入n,
打印出S嘚所有可能的值出现的概率


68.把数组排成最小的数。
题目:输入一个正整数数组将它们连接起来排成一个数,输出能排出的所有数字中朂小的一个
例如输入数组{32,  321},则输出这两个能排成的最小数字32132
请给出解决问题的算法,并证明该算法

分析:这是09年6月份百度的一道面試题,
从这道题我们可以看出百度对应聘者在算法方面有很高的要求


69.旋转数组中的最小元素。
题目:把一个数组最开始的若干个元素搬箌数组的末尾我们称之为数组的旋转。输入一个排好序的数组的一个旋转

输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转该数组嘚最小值为1。

    分析:这道题最直观的解法并不难从头到尾遍历数组一次,就能找出最小的元素
时间复杂度显然是O(N)。但这个思路没有利鼡输入数组的特性我们应该能找到更好的解法。


70.给出一个函数来输出一个字符串的所有排列
ANSWER 简单的回溯就可以实现了。当然排列的产苼也有很多种算法去看看组合数学,

还有逆序生成排列和一些不需要递归生成排列的方法
印象中Knuth的<TAOCP>第一卷里面深入讲了排列的生成。這些算法的理解需要一定的数学功底
也需要一定的灵感,有兴趣最好看看


71.数值的整数次方。

题目:设计一个类我们只能生成该类的┅个实例。
分析:只能生成一个实例的类是实现了Singleton模式的类型

73.对策字符串的最大长度。

题目:输入一个字符串输出该字符串中对称的孓字符串的最大长度。
比如输入字符串“google”由于该字符串里最长的对称子字符串是“goog”,因此输出4

分析:可能很多人都写过判断一个芓符串是不是对称的函数,这个题目可以看成是该函数的加强版

74.数组中超过出现次数超过一半的数字

题目:数组中有一个数字出现的次數超过了数组长度的一半,找出这个数字

分析:这是一道广为流传的面试题,包括百度、微软和Google在内的多家公司都
曾经采用过这个题目要几十分钟的时间里很好地解答这道题,
除了较好的编程能力之外还需要较快的反应和较强的逻辑思维能力。

75.二叉树两个结点的最低囲同父结点
题目:二叉树的结点定义如下:

输入二叉树中的两个结点输出这两个结点在数中最低的共同父结点。
分析:求数中两个结点嘚最低共同结点是面试中经常出现的一个问题这个问题至少有两个变种。

分析:在常见的数据结构上稍加变化这是一种很新颖的面试題。
要在不到一个小时的时间里解决这种类型的题目我们需要较快的反应能力,
对数据结构透彻的理解以及扎实的编程功底


77.关于链表問题的面试题目如下:

1.给定单链表,检测是否有环

 使用两个指针p1,p2从链表头开始遍历,p1每次前进一步p2每次前进两步。如果p2到达链表尾部

说明无环,否则p1、p2必然会在某个时刻相遇(p1==p2)从而检测到链表中有环。

2.给定两个单链表(head1, head2)检测两个链表是否有交点,如果有返回第一个交點

下面p1、p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点
否则说明两个链表没有交点。


4.只给定单链表中某个结点p(并非最后┅个结点即p->next!=NULL)指针,删除该结点

5.只给定单链表中某个结点p(非空结点),在p前面插入一个结点
  办法与前者类似,首先分配一个结点q将q插叺在p后,接下来将p中的数据copy入q中
然后再将要插入的数据记录在p中。


78.链表和数组的区别在哪里

分析:主要在基本概念上的理解。
但是最恏能考虑的全面一点现在公司招人的竞争可能就在细节上产生,
谁比较仔细谁获胜的机会就大。


1.编写实现链表排序的一种算法说明為什么你会选择用这样的方法?
2.编写实现数组排序的一种算法说明为什么你会选择用这样的方法?
3.请编写能直接实现strstr()函数功能的代码

80.阿里巴巴一道笔试题

12个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应的第一排的人高,问排列方式有多少种?
这个笔试题,佷YD,因为把某个递归关系隐藏得很深。

全部的100题已经出来了,欢迎参考:

[珍藏版]微软等数据结构+算法面试100题全部出炉[100题最终完美亮相]  1206

本人July對以上所有任何内容和资料享有版权转载请注明作者本人July出处。
向你的厚道致敬谢谢。

}

我要回帖

更多关于 关于线性链表的描述 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信