Matlab, 从200个已知一组数据有40个的实验数据点(t,H)中提取出有用的数据点(Lowt, LowH). 求高手指教

数据采集设备 | 横河测试测量官网 - YokogawaPID基础PID电路常被用作为控制环路反馈控制器,并且通常用于各种形式的伺服电路。缩写PID中的每个字母分别代表比例(P)、积分(I)和微分(D),表示一个PID电路的三种控制设置。任何伺服电路的目的都是将系统长时间控制在一个预先确定的数值(设定值)上。PID电路可以主动控制系统,以便于在当前值与设定值发生差异时产生一个错误信号,从而将系统控制在设定值上。其三种控制设置对应时间相关的错误信号;简而言之,可以如此考虑:比例控制依赖于当前错误,积分控制依赖于之前错误的累加,微分控制则是对未发生错误的预测。每种控制设置的结果都会进行加权,然后加权值会对输出电路u(t)进行调整。该输出电路会通向一个控制器,其数值会反馈回电路中,该过程可以主动稳定电路的输出从而达到并稳定在设定值。下面的框图说明了PID电路的简单原理。一个伺服电路中可以采用一个或更多的控制,这取决于系统的需要(即,P,I,PI,PD,或PID)。在一个PID电路中,如果合理安排控制设置,就可以在最小超调(超过设定值)和震荡(在设定值附近摆动)下实现相对快速的响应。让我们以温度伺服电路为例,如激光二极管的温度稳定电路。PID电路将会最终控制流入热电制冷器(TEC)的电流(通常通过控制施加在FET上的栅压来实现)。在该例子中,电流可以看作操作变量(MV)。我们用一个热敏电阻来监控激光二极管的温度,热敏电阻上的电压用作过程变量(PV)。设定值(SP)电压设定在对应所需温度的数值上。错误信号,e(t),就只是SP和PV之间的差值。一个PID控制器将会产生这样的错误信号,然后改变MV从而达到所需的结果。例如,如果e(t)表明激光二极管过热,电路将会让更多电流通过TEC(比例控制)。由于比例控制是与e(t)成比例的,它不能足够快速地将激光二极管冷却下来。这样一来,电路将会根据之前的错误信号来调节输出电流,进一步增大流入TEC的电流量(积分控制),从而达到预先设定的温度值。当达到SP时【e(t)数值为0】,电路将会减小通过TEC 的电流,预先估计达到SP(微分控制)。请注意,PID电路并不会保证优化控制。不合适的PID控制设置会引起电路的剧烈振荡,从而导致控制不稳定。PID增益是根据用户需求进行调节的,这样能够保证最佳性能。PID理论PID控制电路的输出u(t)可以写为其中Kp= 比例增益Ki&= 积分增益Kd&= 微分增益e(t) = SP - PV(t)从这里我们可以通过它们的数学定义式来定义控制单元,并分别讨论更多细节。比例控制与错误信号成比例;这样一来,它可以直接与电路产生的错误信号相关:更大的比例增益导致对错误信号更大的响应变化,这样就会影响控制器对系统变化而响应的速度。较高的比例增益可以使电路响应更加平滑,但数值太高的话则会使电路在SP附近震荡。若数值太低,电路将不能有效地对系统变化进行相应。积分控制比起比例增益具有更进一步的作用,这是因为它不仅仅与与错误信号的大小成比例关系,它还和错误经历的时间成比例关系。积分控制能够高度有效地增加电路的响应时间,同时可以消除纯粹比例控制而引起的稳态误差。本质上,积分控制只是将之前未矫正的错误信号进行叠加,然后乘以Ki&来产生集成响应。这样,即使是很小的持续错误,也可以引起很大的合成集成响应。但是,由于积分控制的快速响应特点,高增益值会引起SP值的过度超调,从而导致电路振荡和不稳定。若增益太低,电路在系统发生改变时响应将十分缓慢。微分制旨在减少比例和积分控制中潜在的超调和振荡。它能决定电路随时间变化的速度(通过对错误信号进行微分),将该速度乘以Kd&就可以产生微分响应。与比例控制和积分控制不同,微分控制将会降低电路的响应速度。这样一来,它就可以部分地补偿超调以及减弱积分和比例控制所引起的任何振荡。高增益值会使电路响应速度非常缓慢,并使电路对噪声和高频振荡非常敏感(这是由于电路不能快速响应)。如果增益太低,电路则很有可能会发生SP值的超调。但是,在一些情况下,必须避免任何程度的SP值超调,因此应该采用更高的微分增益(同时采用更低的比例增益)。下表解释了单独增大任一参数的增益值的效果。Parameter IncreasedRise TimeOvershootSettling TimeSteady-State ErrorStabilityKpDecreaseIncreaseSmall ChangeDecreaseDegradeKiDecreaseIncreaseIncreaseDecrease SignificantlyDegradeKdMinor DecreaseMinor DecreaseMinor DecreaseNo EffectImprove (for small Kd)调节一般地,用户需要对P、I和D的增益进行调节,从而更好地对系统进行控制。对于任何特定系统而言,都不存在一套固定的增益设置法则,下面我们将介绍一些通用的调节过程,可以帮助用户调节电路来匹配其系统和使用环境。一般地,PID电路通常都会超调SP值,然后快速减振回到SP值。增益设置的手动调节是设置PID控制的最简单方法。但是,该过程需要主动进行(启动PID控制器并与系统合理连接),并且需要一定的经验才能实现完全积分。如需手动调节PID控制器,首先应将积分和微分增益设置为零。然后增大比例增益值到观察到输出信号出现振荡。接着将比例增益值减小至目前数值的一半左右。在设置好比例增益后,增大积分增益值到任何漂移都根据用户系统所适用的时间标度进行校正。如果把该增益调节过大,将会观察到SP值的严重超调和电路发生不稳定现象。一旦积分增益设定好后,就可以开始增大微分增益。微分增益可以减小超调,并阻碍系统振荡,使其快速稳定到SP值。如果把微分增益调节过大,竟会观察到大幅度的超调(这是因为电路响应太缓慢)。通过进行增益设定,您可以使您的PID电路达到最佳的工作状态,使电路对系统的变化进行快速响应,并有效地抑制SP值附近的振荡。Control TypeKpKiKdP0.50 Ku--PI0.45 Ku1.2 Kp/Pu-PID0.60 Ku2 Kp/PuKpPu/8手动调节可以非常有效地对您的系统进行PID电路设置,但这需要有一定的经验并能够理解PID电路和响应。用于PID调节的Ziegler-Nichols方法可以一定程度地作为结构指南来设定PID值。此外,您如果把积分和微分增益设置为0,增大比例增益,直到电路开始出现振荡,我们将这时的增益称为Ku级,该振荡的周期为Pu。不同控制电路的增益可以参看下表。
Main and Auxillary Output ConnectorHIROSE HR10A-7R-6S这些辅助的接头是从后面板角度看的。PinAssignmentHeater Output PositiveHeater Output Return (Ground)Reserved (Do not connect to this pin)Sensor Input (+)Sensor Input (Ground)Reserved (Do not connect to this pin)计算机连接&B型USB类型B到类型A的USB电源线包括在内TC200是兼容PT100(型号)和PT1000等铂热敏电阻,以及热敏电阻。使用下列参数确定这些类型的热敏电阻的设定点和读回值。PT100和PT1000探头对于温度范围在0到850&C的情况:RT = R0 (1 + AT + BT2)(参考IEC 751, 2:1995-07 [DIN EN 6-07])A = 3.9083 x 10-3 &C-1B = -5.775 x 10-7 &C-2RT为在温度T下的阻抗,用&O表示T为用&C表示的温度R0 = 100 &O,PT100R0 = 1000 &O,PT1000TH10K探头RT = 10000exp&[(1/T) - (1/298)]RT为在温度T下的阻抗,用&O表示T为用K表示的温度&为特定热敏电阻下的常数TC200加热控制器的软件&软件版本 1.6.0软件可以操作TC200,包含GUI、驱动程序和用于第三方开发的 LabVIEW&/C++ SDK。Please Give Us Your Feedback&(Optional. Your email address will NOT be displayed.)Contact Me: &ALL PRODUCTSPresentation FeedbackTC200TC200-ECLittle Use
&&1&&&&2&&&&3&&&&4&&&Very UsefulCharacters remaining &<SPAN id=myCounter_&&& Characters are Case-Sensitive +1 数量 文档 产品型号 - 英制 单价温度控制器,120 VAC电源线¥5,088.922 Weeks+1 数量 文档 产品型号 - 公制 单价温度控制器,230 VAC电源线¥5,088.92TodayAdd To CartSpecificationsConnectors6-Pin, Male HiroseLength10 Feet (3 m)Insulating MaterialPVC - Polyvinyl ChlorideInsulation Thickness0.381 mmOuter Jacket Thickness0.940 mmBend Radius (Min)57.150 mmNumber of Conductors6Conductor Gauge26 AWGCapacitance85.306 pF/mPropagation Velocity60%aConductor Resistance122.381 O/kmOperating Voltage (Max)300 VRMSCurrent per Conductor (Max)1.2 AOperating Temperature-20 to 80 &CContact Resistance (1 A, DC)10 mOInsulation Resistance1000 MOVoltage (Max)140 VDCCurrent (Max)2 AOperating Temperature-25 to 85 &C6针公头对公头Hirose接头连线兼容各种Thorlabs公司的产品温度控制器开关控制器光束开关,&O1/2英寸的孔径光束开关,&O1英寸的孔径可以根据定制应用剪裁和暴露电线TC200CAB10是一种6针公头对公头的Hirose接头连线。这种长10英尺的接线可以兼容我们的TC200温度控制器、SH05和SH1光束开关,以及SC10开关控制器。接线中的电线截面图如右图所示。这不是一种直通接线。该Hirose接头连线还可以剪裁成任意长度,留下一个可连接末端和一个裸线末端。右边的彩色电线图显示了六根彩色电线与接头上的引脚的关系,经过剪裁的接线还可以用于各种定制应用当中。 +1 数量 文档 产品型号 - 公英制通用 单价6针公头对公头Hirose接线,长10英尺¥782.40TodayAdd To Cart加热电阻温度控制器&&|&&&&|&&&&|&&&&|&&&&|&&&&|&&&&|&&区域网站:
| High Quality Thorlabs Logo 1000px:0、常考基础必知必会
A. 排序:排序有几种,各种排序的比较,哪些排序是稳定的,快排的算法;
B. 查找:哈希查找、二叉树查找、折半查找的对比,哈希映射和哈希表的区别?
C. 链表和数组的区别,在什么情况下用链表什么情况下用数组?
D. 栈和队列的区别?
E. 多态,举例说明;overload和override的区别?
F. 字符串有关的函数,比如让你写一个拷贝字符串的函数啊,或者字符串反转啊什么的。strcpy和memcpy?
G. 继承、多继承?
H. 面向对象有什么好处?
I. 说说static的与众不同之处,如果一个变量被声明为static,它会被分配在哪里?在什么时候分配空间等?
J. 什么是虚函数、纯虚函数、虚的析构函数,用途?
K. 内存泄漏及解决方法?
网络部分:
OSI模型7层结构,TCP/IP模型结构?
B. TCP/UDP区别?
C. TCP建立连接的步骤?
D. 香农定理?
1、二叉树三种遍历的非递归算法(背诵版)
&&&&&&&&&本贴给出二叉树先序、中序、后序三种遍历的非递归算法,此三个算法可视为标准算法,直接用于考研答题。
1.先序遍历非递归算法
#define maxsize 100
typedef struct
& & Bitree Elem[maxsize];
void PreOrderUnrec(Bitree t)
& & StackInit(s);
& & while (p!=null || !StackEmpty(s))
& && &&&while (p!=null)& &&&& && & //遍历左子树
& && && && &visite(p-&data);
& && && && &push(s,p);
& && && && &p=p-&&&& &&
& && &&&}//endwhile
& && &&&if (!StackEmpty(s))& &&&& &//通过下一次循环中的内嵌while实现右子树遍历
& && && && &p=pop(s);
& && && && &p=p-&&&& &&&
& && &&&}//endif& && &&&& &&
& & }//endwhile& &&
}//PreOrderUnrec
2.中序遍历非递归算法
#define maxsize 100
typedef struct
& & Bitree Elem[maxsize];
void InOrderUnrec(Bitree t)
& & StackInit(s);
& & while (p!=null || !StackEmpty(s))
& && &&&while (p!=null)& &&&& && & //遍历左子树
& && && && &push(s,p);
& && && && &p=p-&
& && &&&}//endwhile
& && &&&if (!StackEmpty(s))
& && && && &p=pop(s);
& && && && &visite(p-&data);&&& &&&//访问根结点
& && && && &p=p-&&&& && && &//通过下一次循环实现右子树遍历
& && &&&}//endif& & &&
& & }//endwhile
}//InOrderUnrec
3.后序遍历非递归算法
#define maxsize 100
typedef enum{L,R}
typedef struct&
typedef struct
& & stacknode Elem[maxsize];
//后序遍历
void PostOrderUnrec(Bitree t)
& & StackInit(s);
& && &&&while (p!=null)& &&&&&//遍历左子树
& && && && &x.ptr =&
& && && && &x.tag = L;&&& && &//标记为左子树
& && && && &push(s,x);
& && && && &p=p-&
& && &&&while (!StackEmpty(s) &&s.Elem[s.top].tag==R)&&
& && && && &x = pop(s);
& && && && &p = x.
& && && && &visite(p-&data);& &//tag为R,表示右子树访问完毕,故访问根结点& && &&
& && &&&if (!StackEmpty(s))
& && && && &s.Elem[s.top].tag =R;&&&&//遍历右子树
& && && &&&p=s.Elem[s.top].ptr-&& && &&&
& && &&&}& &&
& & }while (!StackEmpty(s));
}//PostOrderUnrec
4.层次遍历算法
// 二叉树的数据结构
structBinaryTree
&&&&&& // 不写模板了,暂时用整形代替节点的数据类型
&&& BinaryTree *
&&& BinaryTree *
BinaryTree*&&& // 已知二叉树的根节点
//层次遍历
voidLevel( const BinaryTree *root )
&&& Queue *buf = new Queue();&&& // 定义一个空队列,假设此队列的节点数据类型也是整形的
&&& BinaryT&&&&&&&&&&&&&&& // 一个临时变量
&&& buf.push_back(root);&&&&&&& &//令根节点入队
&&& while( buf.empty == false )&&& // 当队列不为空
&&&&&&& p = buf.front();&&&&&&& &&// 取出队列的第一个元素
&&&&&&& cout&&p-&value&&' ';
&&&&&&& if( p-&left != NULL )&&& &// 若左子树不空,则令其入队
&&&&&&&&&&& q.push( p-&left );
&&&&&&& if( p-&right != NULL )&&& // 若右子树不空,则令其入队
&&&&&&&&&&& q.push( p-&right );
&&&&&&& }&&&&&&&
&&&&&&& buf.pop();&&&&&&&&&&&&& // 遍历过的节点出队
&&& cout&&
(1) 性表的链式存储方式及以下几种常用链表的特点和运算:单链表、循环链表,双向链表,双向循环链表。
(2)单链表的归并算法、循环链表的归并算法、双向链表及双向循环链表的插入和删除算法等都是较为常见的考查方式。
(3)单链表中设置头指针、循环链表中设置尾指针而不设置头指针以及索引存储结构的各自好处。
3、栈与队列
你可以问一下自己是不是已经知道了以下几点:
&&&&&& (1)栈、队列的定义及其相关数据结构的概念,包括:顺序栈,链栈,共享栈,循环队列,链队等。栈与队列存取数据(请注意包括:存和取两部分)的特点。
&&&&&& (2)递归算法。栈与递归的关系,以及借助栈将递归转向于非递归的经典算法:n!阶乘问题,fib数列问题,hanoi问题,背包问题,二叉树的递归和非递归遍历问题,图的深度遍历与栈的关系等。其中,涉及到树与图的问题,多半会在树与图的相关章节中进行考查。
&&&&&& (3)栈的应用:数&#20540;表达式的求解,括号的配对等的原理,只作原理性了解,具体要求考查此为题目的算法设计题不多。
&&&&&& (4)循环队列中判队空、队满条件,循环队列中入队与出队(循环队列在插入时也要判断其是否已满,删除时要判断其是否已空)算法。
& 【循环队列的队空队满条件
& 为了方便起见,约定:初始化建空队时,令
&&&&& front=rear=0,
& 当队空时:front=rear,
& 当队满时:front=rear 亦成立,
& 因此只凭等式front=rear无法判断队空还是队满。
& 有两种方法处理上述问题:
&&& (1)另设一个标志位以区别队列是空还是满。
&&& (2)少用一个元素空间,约定以“队列头指针front在队尾指针rear的下一个位置上”作为队列“满”状态的标志。
&&队空时:&front=rear,
&&队满时:&(rear&#43;1)%maxsize=front】
&&&&&& 如果你已经对上面的几点了如指掌,栈与队列一章可以不看书了。注意,我说的是可以不看书,并不是可以不作题哦。
////////////////////////////////////////////////////////////////////////////////////////////////
循环队列的主要操作:
  (1)创建循环队列
  (2)初始化循环队列
  (3)判断循环队列是否为空
  (4)判断循环队列是否为满
  (5)入队、出队
  //空出头尾之间的一个元素不用
  #include
  #include
  #define MAXSIZE 100
  typedef struct
  intelem[MAXSIZE];
  intfront,
  }Q //定义队头
  int initQue(Quque **q) //初始化
  (*q)-&front=0;
  (*q)-&rear=0;
  int isFull(Quque *q)
  if(q-&front==(q-&rear&#43;1)%MAXSIZE)//判满(空出一个元素不用) 刘勉刚
  && return 1;
  && return 0;
  int insertQue(Quque **q,int elem)
  if(isFull(*q))return -1;
  (*q)-&elem[(*q)-&rear]=
  (*q)-&rear=((*q)-&rear&#43;1)%MAXSIZE;//插入
  return0;
  int isEmpty(Quque *q)
  if(q-&front==q-&rear)//判空
  && return 1;
  && return 0;
  int deleteQue(Quque ** q,int *pelem)
  if(isEmpty(*q))
  && return 0;
  *pelem=(*q)-&elem[(*q)-&front];
  (*q)-&front=((*q)-&front &#43;1)%MAXSIZE;
  return0;
串一章需要攻破的主要堡垒有:
1. 串的基本概念,串与线性表的关系(串是其元素均为字符型数据的特殊线性表),空串与空&#26684;串的区别,串相等的条件;
2. 串的基本操作,以及这些基本函数的使用,包括:取子串,串连接,串替换,求串长等等。运用串的基本操作去完成特定的算法是很多学校在基本操作上的考查重点。
3.&顺序串与链串及块链串的区别和联系,实现方式。
4. KMP算法思想。KMP中next数组以及nextval数组的求法。明确传统模式匹配算法的不足,明确next数组需要改进。可能进行的考查方式是:求next和nextval数组&#20540;,根据求得的next或nextval数组&#20540;给出运用KMP算法进行匹配的匹配过程。
5、多维数组和广义表
矩阵包括:对称矩阵,三角矩阵,具有某种特点的稀疏矩阵等。
熟悉稀疏矩阵的三种不同存储方式:三元组,带辅助行向量的二元组,十字链表存储。
掌握将稀疏矩阵的三元组或二元组向十字链表进行转换的算法。
6、树与二叉树
树一章的知识点包括:
&&&&&& 二叉树的概念、性质和存储结构,二叉树遍历的三种算法(递归与非递归),在三种基本遍历算法的基础上实现二叉树的其它算法,线索二叉树的概念和线索化算法以及线索化后的查找算法,最优二叉树的概念、构成和应用,树的概念和存储形式,树与森林的遍历算法及其与二叉树遍历算法的联系,树与森林和二叉树的转换。
&&&&&& (1) 二叉树的概念、性质和存储结构
考查方法可有:直接考查二叉树的定义,让你说明二叉树与普通双分支树(左右子树无序)的区别;考查满二叉树和完全二叉树的性质,普通二叉树的五个性质:
A.第i层的最多结点数,
B.深度为k的二叉树的最多结点数,
C.n0=n2&#43;1的性质,
D.n个结点的完全二叉树的深度,
E. 顺序存储二叉树时孩子结点与父结点之间的换算关系(root从1开始,则左为:2*i,右为:2*i&#43;1)。
二叉树的顺序存储和二叉链表存储的各自优缺点及适用场合,二叉树的三叉链表表示方法。
&&&&&& (2) 二叉树的三种遍历算法
&&&&&& 这一知识点掌握的好坏,将直接关系到树一章的算法能否理解,进而关系到树一章的算法设计题能否顺利完成。二叉树的遍历算法有三种:先序,中序和后序。其划分的依据是视其每个算法中对根结点数据的访问顺序而定。不仅要熟练掌握三种遍历的递归算法,理解其执行的实际步骤,并且应该熟练掌握三种遍历的非递归算法。由于二叉树一章的很多算法,可以直接根据三种递归算法改造而来(比如:求叶子个数),所以,掌握了三种遍历的非递归算法后,对付诸如:“利用非递归算法求二叉树叶子个数”这样的题目就下笔如有神了。
&&&&&& (3) 可在三种遍历算法的基础上改造完成的其它二叉树算法:
&&&&&& 求叶子个数,求二叉树结点总数,求度为1或度为2的结点总数,复制二叉树,建立二叉树,交换左右子树,查找&#20540;为n的某个指定结点,删除&#20540;为n的某个指定结点,诸如此类等等等等。如果你可以熟练掌握二叉树的递归和非递归遍历算法,那么解决以上问题就是小菜一碟了。
&&&&&& (4)&线索二叉树:
&&&&&& 线索二叉树的引出,是为避免如二叉树遍历时的递归求解。众所周知,递归虽然形式上比较好理解,但是消耗了大量的内存资源,如果递归层次一多,势必带来资源耗尽的危险,为了避免此类情况,线索二叉树便堂而皇之地出现了。对于线索二叉树,应该掌握:线索化的实质,三种线索化的算法,线索化后二叉树的遍历算法,基本线索二叉树的其它算法问题(如:查找某一类线索二叉树中指定结点的前驱或后继结点就是一类常考题)。
&&&&&& (5)&最优二叉树(哈夫曼树):
&&&&&& 最优二叉树是为了解决特定问题引出的特殊二叉树结构,它的前提是给二叉树的每条边赋予了权&#20540;,这样形成的二叉树按权相加之和是最小的。最优二叉树一节,直接考查算法源码的很少,一般是给你一组数据,要求你建立基于这组数据的最优二叉树,并求出其最小权&#20540;之和,此类题目不难,属送分题。
&&&&&& (6) 树与森林:
&&&&&&&二叉树是一种特殊的树,这种特殊不仅仅在于其分支最多为2以及其它特征,一个最重要的特殊之处是在于:二叉树是有序的!即:二叉树的左右孩子是不可交换的,如果交换了就成了另外一棵二叉树。&&&&&树与森林的遍历,不像二叉树那样丰富,他们只有两种遍历算法:先根与后根(对于森林而言称作:先序与后序遍历)。此二者的先根与后根遍历与二叉树中的遍历算法是有对应关系的:先根遍历对应二叉树的先序遍历,而后根遍历对应二叉树的中序遍历。二叉树、树与森林之所以能有以上的对应关系,全拜二叉链表所赐。二叉树使用二叉链表分别存放他的左右孩子,树利用二叉链表存储孩子及兄弟(称孩子兄弟链表),而森林也是利用二叉链表存储孩子及兄弟。&
&1. 图的基本概念:图的定义和特点,无向图,有向图,入度,出度,完全图,生成子图,路径长度,回路,(强)连通图,(强)连通分量等概念。
&&&&&& 2. 图的几种存储形式:邻接矩阵,(逆)邻接表,十字链表及邻接多重表。在考查时,有的学校是给出一种存储形式,要求考生用算法或手写出与给定的结构相对应的该图的另一种存储形式。
&&&&&& 3. 考查图的两种遍历算法:深度遍历和广度遍历
&&&&&& 深度遍历和广度遍历是图的两种基本的遍历算法,这两个算法对图一章的重要性等同于“先序、中序、后序遍历”对于二叉树一章的重要性。在考查时,图一章的算法设计题常常是基于这两种基本的遍历算法而设计的,比如:“求最长的最短路径问题”和“判断两顶点间是否存在长为K的简单路径问题”,就分别用到了广度遍历和深度遍历算法。
&&&&&& 4.&生成树、最小生成树的概念以及最小生成树的构造:PRIM算法和KRUSKAL算法。
&&&&&& 考查时,一般不要求写出算法源码,而是要求根据这两种最小生成树的算法思想写出其构造过程及最终生成的最小生成树。
&&&&&& 5.&拓扑排序问题:
&&&&&& 拓扑排序有两种方法,一是无前趋的顶点优先算法,二是无后继的顶点优先算法。换句话说,一种是“从前向后”的排序,一种是“从后向前”排。当然,后一种排序出来的结果是“逆拓扑有序”的。
&&&&&& 6.&关键路径问题:
&&&&&& 这个问题是图一章的难点问题。理解关键路径的关键有三个方面:
一是何谓关键路径;
二是最早时间是什么意思、如何求;
三是最晚时间是什么意思、如何求。
简单地说,最早时间是通过“从前向后”的方法求的,而最晚时间是通过“从后向前”的方法求解的,并且,要想求最晚时间必须是在所有的最早时间都已经求出来之后才能进行。
&&&&&& 在实际设计关键路径的算法时,还应该注意以下这一点:采用邻接表的存储结构,求最早时间和最晚时间要采用不同的处理方法,即:在算法初始时,应该首先将所有顶点的最早时间全部置为0。关键路径问题是工程进度控制的重要方法,具有很强的实用性。
&&&&&& 7.&最短路径问题:
&&&&&& 与关键路径问题并称为图一章的两只拦路虎。概念理解是比较容易的,关键是算法的理解。最短路径问题分为两种:一是求从某一点出发到其余各点的最短路径(单源最短路径);二是求图中每一对顶点之间的最短路径。这个问题也具有非常实用的背景特色,一个典型的应该就是旅游景点及旅游路线的选择问题。解决第一个问题用DIJSKTRA算法,解决第二个问题用FLOYD算法,注意区分。
8、查找(search)
先弄清楚以下几个概念:关键字、主关键字、次关键字的含义;静态查找与动态查找的含义及区别;平均查找长度ASL的概念及在各种查找算法中的计算方法和计算结果,特别是一些典型结构的ASL&#20540;,应该记住。
一般将search分为三类:在顺序表上的查找;在树表上的查找;在哈希表上的查找。
&&&&&& (1) 线性表上的查找:
&&&&&& 主要分为三种线性结构:
顺序表——传统查找方法:逐个比较;
有序顺序表——二分查找法(注意适用条件以及其递归实现方法);
索引顺序表——对索引结构,采用索引查找算法。注意这三种表下的ASL&#20540;以及三种算法的实现。
&&&&&& (2) 树表上的查找:
&&&&&& 树表主要分为以下几种:二叉排序树(即二叉查找树),平衡二叉查找树(AVL树),B树,键树。其中,尤以前两种结构为重,也有部分名校偏爱考B树的。由于二叉排序树与平衡二叉树是一种特殊的二叉树。
二叉排序树,简言之,就是“左小右大”,它的中序遍历结果是一个递增的有序序列。平衡二叉排序树是二叉排序树的优化,其本质也是一种二叉排序树,只不过,平衡排序二叉树对左右子树的深度有了限定:深度之差的绝对&#20540;不得大于1。对于二叉排序树,“判断某棵二叉树是否二叉排序树”这一算法经常被考到,可用递归,也可以用非递归。平衡二叉树的建立也是一个常考点,但该知识点归根结底还是关注的平衡二叉树的四种调整算法,调整的一个参照是:调整前后的中序遍历结果相同。
&&&&&&&B树是二叉排序树的进一步改进,也可以把B树理解为三叉、四叉....排序树。除B树的查找算法外,应该特别注意一下B树的插入和删除算法,因为这两种算法涉及到B树结点的分裂和合并,是一个难点。&&&键树(keywordtree),又称数字搜索树(digitalsearch
tree)或字符树。trie树也可说等同于键树或属于键树的一种。键树特别适用于查找英文单词的场合。一般不要求能完整描述算法源码,多是根据算法思想建立键树及描述其大致查找过程。
&&&&&& (3) 基于哈希表的查找算法:
&&&&&& 哈希译自“hash”一词,意为“散列”或“杂凑”。哈希表查找的基本思想是:根据当前待查找数据的特征,以记录关键字为自变量,设计一个function,该函数对关键字进行转换后,其解释结果为待查的地址。基于哈希表的考查点有:哈希函数的设计,冲突解决方法的选择及冲突处理过程的描述。
9、内部排序
考查你对书本上的各种排序算法及其思想以及其优缺点和性能指标(时间复杂度)能否了如指掌。
排序方法分类有:插入、选择、交换、归并、计数等五种排序方法。
&&&&&& (1)插入排序中又可分为:直接插入、折半插入、2路插入(?)、希尔排序。这几种插入排序算法的最根本的不同点,说到底就是根据什么规则寻找新元素的插入点。直接插入是依次寻找,折半插入是折半寻找,希尔排序,是通过控制每次参与排序的数的总范围“由小到大”的增量来实现排序效率提高的目的。&&&&&
(2)交换排序,又称冒泡排序,在交换排序的基础上改进又可以得到快速排序。快速排序的思想,一语以敝之:用中间数将待排数据组一分为二。
&&&&&& (3)选择排序可以分为:简单选择、树选择、堆排序。选择排序相对于前面几种排序算法来说,难度大一点。这三种方法的不同点是,根据什么规则选取最小的数。
简单选择,是通过简单的数组遍历方案确定最小数;
树选择,是通过“锦标赛”类&#20284;的思想,让两数相比,不断淘汰较大(小)者,最终选出最小(大)数;
而堆排序,是利用堆这种数据结构的性质,通过堆元素的删除、调整等一系列操作将最小数选出放在堆顶。堆排序中的堆建立、堆调整是重要考点。
(4)归并排序,是通过“归并”这种操作完成排序的目的,既然是归并就必须是两者以上的数据集合才可能实现归并。所以,在归并排序中,关注最多的就是2路归并。算法思想比较简单,有一点,要铭记在心:归并排序是稳定排序。
&&&&&& (5)基数排序,是一种很特别的排序方法,也正是由于它的特殊,所以,基数排序就比较适合于一些特别的场合,比如扑克牌排序问题等。基数排序,又分为两种:多关键字的排序(扑克牌排序),链式排序(整数排序)。基数排序的核心思想也是利用“基数空间”这个概念将问题规模规范、变小,并且,在排序的过程中,只要按照基排的思想,是不用进行关键字比较的,这样得出的最终序列就是一个有序序列。
&&&&&& 本章各种排序算法的思想以及伪代码实现,及其时间复杂度都是必须掌握的。
//////////////////////////////////////////////稳定性分析////////////////////////////////////////////////
排序算法的稳定性,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。
稳定性的好处:若排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,对基于比较的排序算法而言,元素交换的次数可能会少一些(个人感觉,没有证实)。
&&&&&&&& 分析一下常见的排序算法的稳定性,每个都给出简单的理由。
&&&&&&&& (1) 冒泡排序
&&&&&&&& 冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
&&&&&&&& (2) 选择排序
&&& 选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。
&&&&&&&& (3) 插入排序
&&&&&&&& 插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
&&&&&&&& (4) 快速排序
&&& 快速排序有两个方向,左边的i下标一直往右走,当a[i] &= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j]& a[center_index]。如果i和j都走不动了,i &= j, 交换a[i]和a[j],重复上面的过程,直到i&j。交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10
11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和&a[j]&交换的时刻。
&&&&&&&& (5) 归并排序
&&& 归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。
&&&&&&&& (6) 基数排序
&& 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
&&&&&&&& (7) 希尔排序(shell)
&&& 希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
&&&&&&&& (8) 堆排序
&& 我们知道堆的结构是节点i的孩子为2*i和2*i&#43;1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个&#20540;选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n /2-1, n/2-2, ...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
冒泡排序&&插入排序&二路插入排序&希尔排序&&快速排序&选择排序&归并排序&&堆排序算法的C/C&#43;&#43;实现//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include &iostream&
//交换两个数的&#20540;
void swap(int &a,int &b)
&&& tmp=a;
//屏幕输出数组
void display(int array[],int len)
&&& cout&&&the resultis:&&&
&&& for (int i = 0 ;i &i&#43;&#43; )
&&&&&&&cout&&array[i]&&&&&;
&&& cout&&
算法思想:将被排序的记录数组R[1..n]垂直排列,每个记录R[i]看作是重量为R[i].key的气泡。
&&&&&&&&& 根据轻气泡不能在重气泡之下的原则,从下往上扫描数组 R:凡扫描到违反本原则的
&&&&&&&&& 轻气泡,就使其向上&飘浮&。如此反复进行,直到最后任何两个气泡都是轻者在上,
&&&&&&&&& 重者在下为止。
时间复杂度 o(n^2)
空间复杂度 o(1)
比较次数 n(n&#43;1)/2
void bubble_sort(int array[],int len)
&&& for (int i = len-1 ;i &= 0;i-- )
&&&&&&& for(int j = 0;j &j&#43;&#43;)
&&&&&&&&&&& if(array[j] & array[j&#43;1])
&&&&&&&&&&&&&&& swap(array[j],array[j&#43;1]);
直接插入排序
算法思想:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元
&&&&&&&&& 素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它
&&&&&&&&& 插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。
时间复杂度 o(n^2)
空间复杂度 o(1)
比较次数 n(n&#43;1)/2
void insert_sort(int array[],int len)
&&& int tmp,i,j;
&&& for(i = 1;i &i&#43;&#43;)
&&&&&&& if (array[i] & array[i-1])
&&&&&&&&&&& tmp = array[i];
&&&&&&&&&&& array[i] = array[i-1];
&&&&&&&&&&& //插入到相应位置
&&&&&&&&&&& for (j = i-2;j &= 0;j--)
&&&&&&&&&&& {
&&&&&&&&&&&&&&& //往后移
&&&&&&&&&&&&&&&&&&& if (array[j] & tmp)
&&&&&&&&&&&&&&&&&&&&&&& array[j&#43;1] =array[j];
&&&&&&&&&&&&&&&&&&& else
&&&&&&&&&&&&&&&&&&& {
&&&&&&& &&&&&&&&&&&&&&&&array[j&#43;1] =
&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&& }
&&&&&&&&&&& }
&&&&&&&&&&& if(j == -1)
&&&&&&&&&&& array[j&#43;1] =
2-路插入排序
算法思想:增加一个辅助空间d,把r[1]赋&#20540;给d[1],并将d[1]看成是排好序后处于中间
&&&&&&&&& 位置的记录。然后从r[2]开始依次插入到d[1]之前或之后的有序序列中。
时间复杂度 o(n^2)
空间复杂度 o(1)
比较次数 n(n&#43;1)/2
void bi_insert_sort(int array[],int len)
&&& int* arr_d = (int*)malloc(sizeof(int) * len);
&&& arr_d[0] = array[0];
&&& int head = 0,tail = 0;
&&& for (int i = 1;i & i&#43;&#43; )
&&&& &&&if (array[i] & arr_d[0])
&&&&&&&&&&&
&&&&&&&&&&& for ( j=j&0;j--)
&&&&&&&&&&& {
&&&&&&&&&&&&&&& if (array[i] &arr_d[j])
&&&&&&&&&&&&&&&&&&& arr_d[j&#43;1] =arr_d[j];
&&&&&&&&&&&&&&& else
&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&& }
&&& &&&&&&&&arr_d[j&#43;1] = array[i];
&&&&&&&&&&& tail &#43;= 1;
&&&&&&& else
&&&&&&&&&&& if (head ==0)
&&&&&&&&&&& {
&&&&&&&&&&&&&&& arr_d[len-1] = array[i];
&&&&&&&&&&&&&&& head =len-1;
&&&&&&&&&&& }
&&&&&&&&&&& else
&&&&&&&&&&& {
&&&&&&&&&&&& &&&
&&&&&&&&&&&&&&& for (j =j &=len-1;j&#43;&#43;)
&&&&&&&&&&&&&&& {
&&&&&&&&&&&&&&&&&&& if (array[i] &arr_d[j])
&&&&&&&&&&&&&&&&&&&&&&& arr_d[j-1] =arr_d[j];
&&&&&&&&&&&&&&&&&&& else
&&&&&&&&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&& arr_d[j-1] = array[i];
&&&&&&&&&&&&&&& head -= 1;
&&&&&&&&&&& }
&&& for (int i = 0;i & i&#43;&#43;)
&&&&&&& int pos = (i &#43; head );
&&&&&&& if(pos &= len) pos -=
&&&&&&& array[i] = arr_d[pos];
&&& free(arr_d);
算法思想:先将整个待排序记录分割成若干子序列分别进行直接插入排
&&&&&&&&& 序,待整个序列中的记录基本有序时,再对全体记录进行一
&&&&&&&&& 次直接插入排序
时间复杂度 o(n^2)
空间复杂度 o(1)
比较次数 ?
void shell_insert(int array[],int d,int len)
&&& int tmp,j;
&&& for (int i =i &i&#43;&#43;)
&&&&&&& if(array[i] & array[i-d])
&&&&&&&&&&& tmp = array[i];
&&&&&&&&&&& j = i -
&&&&&&&&&&& do&
&&&&&&&&&&& {
&&&&&&&&&&&&&&& array[j&#43;d] = array[j];
&&&&&&&&&&&&&&& j = j -
&&&&&&&&&&& } while (j &= 0 &&tmp & array[j]);
&&&&&&&&&&& array[j&#43;d] =&
void shell_sort(int array[],int len)
&&& int inc =
&&&&&&& inc = inc/2;
&&&&&&& shell_insert(array,inc,len);
&&& } while (inc & 1);
算法思想:将原问题分解为若干个规模更小但结构与原问题相&#20284;的子问题。
递归地解这些子问题,然后将这些子问题的解组合成为原问题的解。
时间复杂度 o(nlogn)
空间复杂度 o(logn)
比较次数& ?
void quick_sort(int array[],int low,int high)
&&& if (low & high)
&&&&&&& int pivotloc =partition(array,low,high);
&&&&&&& quick_sort(array,low,pivotloc-1);
&&&&&&&quick_sort(array,pivotloc&#43;1,high);
int partition(int array[],int low,int high)
&&& int&pivotkey = array[low];
&&& while (low & high)
&&&&&&& while(low & high &&array[high] &= pivotkey)
&&&&&&&&&&& --
&&& &&&&swap(array[low],array[high]);
&&&&&&& while(low & high &&array[low] &= pivotkey)
&&&&&&&&&&& &#43;&#43;
&&&&&&& swap(array[low],array[high]);
&&& array[low] =
直接选择排序
算法思想:每一趟在n-i&#43;1个记录中选取关键字最小的记录作为有序序列中的第i个记录
时间复杂度 o(n^2)
空间复杂度 o(1) ?
比较次数& n(n&#43;1)/2
int SelectMinKey(int array[],int iPos,int len)
&&& int ret = 0;
&&& for (int i = iP i & i&#43;&#43;)
&&&&&&& if (array[ret] & array[i])
&&&&&&&& &&&ret =
void select_sort(int array[],int len)
&&& for (int i = 0; i & i&#43;&#43;)
&&&&&&& int j =SelectMinKey(array,i,len);
&&&&&&& if (i != j)
&&&&&&&&&&& swap(array[i],array[j]);
算法思想:设两个有序的子文件(相当于输入堆)放在同一向量中相邻的位置上:R[low..m],R[m&#43;1..high],先将它们合并到一个局部的暂存向量R1(相当于输出堆)中,待合并完成后将R1复制回R[low..high]中。
时间复杂度 o(nlogn)
空间复杂度 o(n)&
比较次数& ?
void merge(int array[],int i,int m, int n)
&&& int j,
&&& int iStart = i, iEnd =
&&& int arrayDest[256];
&&& for ( j = m &#43; 1,k = i &= m&& j &= &#43;&#43;k)
&&&&&&& if (array[i] & array[j])
&&&&&&&&&&& arrayDest[k] = array[i&#43;&#43;];
&&&&&&& else
&&&&&&&&&&& arrayDest[k] = array[j&#43;&#43;];
&&& if (i &= m)
&&&&&&& for (;k &= k&#43;&#43;,i&#43;&#43;)
&&&&&&& &&&&arrayDest[k] = array[i];
&&& if(j &= n)
&&&&&&& for (;k &= k&#43;&#43;,j&#43;&#43;)
&&&&&&&&&&& arrayDest[k] = array[j];
&&& for(j = iS j &= iE j&#43;&#43;)
&&&&&&& array[j] = arrayDest[j];
void merge_sort(int array[],int s,int t)
&&& if (s & t)&
&&&&&&& m = (s &#43; t )/2;
&&&&&&& merge_sort(array,s,m);
&&&&&&& merge_sort(array,m&#43;1,t);
&&&&&&& merge(array,s,m,t);
算法思想:堆排序(Heap Sort)是指利用堆(heaps)这种数据结构来构造的一种排序算法。
&&&&&&&&& 堆是一个近&#20284;完全二叉树结构,并同时满足堆属性:即子节点的键&#20540;或索引总是
&&&&&&&&& 小于(或者大于)它的父节点。
时间复杂度 o(nlogn)
空间复杂度 o(1)&
比较次数:较多
void heap_adjust(int array[],int i,int len)
&&& int rc = array[i];
&&& for(int j = 2 * j & j *= 2)
&&&&&&& if(j & len && array[j]& array[j&#43;1]) j&#43;&#43;;
&&&&&&& if(rc &= array[j])
&&&&&&& array[i] = array[j]; i =
&&& array[i] =
void heap_sort(int array[],int len)
&&& for(i = (len-1)/2; i &= 0; i--)
&&&&&&& heap_adjust(array,i,len);
&&& for(&i = (len-1); i & 0; i--)
&&&&&&& swap(array[0],array[i]);&& //弹出最大&#20540;,重新对i-1个元素建堆
&&&&&&& heap_adjust(array,0,i-1);
int main()
&&& int array[] = {45, 56, 76, 234, 1, 34,23, 2, 3, 55, 88, 100};
&&& int len = sizeof(array)/sizeof(int);
&&& //bubble_sort(array,len);&&&&&&&&&& //冒泡排序
&&& /*insert_sort(array,len);*/&&&&&&& &&//插入排序
&&& /*bi_insert_sort(array,len);*/&&&&& &&//二路插入排序
&&& /*shell_sort(array,len);*/&&&&&&&&& &//希尔排序
&&& /*quick_sort(array,0,len-1);*/&&&&& &//快速排序&&&&
&&& /*select_sort(array,len);*/&&&&&&&& &//选择排序
&&& /*merge_sort(array,0,len-1);*/&&&& &&//归并排序 &&&
&&& heap_sort(array,len);&&&&&&&&&&&&& //堆排序
&&& display(array,len);
&&& return 0;
&|&对排序算法的总结
按平均时间将排序分为四类:
(1)平方阶(O(n2))排序
&&&  一般称为简单排序,例如直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlgn))排序
&&&  如快速、堆和归并排序;
(3)O(n1&#43;£)阶排序
&&&  £是介于0和1之间的常数,即0&£&1,如希尔排序;
(4)线性阶(O(n))排序
&&&  如桶、箱和基数排序。
各种排序方法比较
&&&& 简单排序中直接插入最好,快速排序最快,当文件为正序时,直接插入和冒泡均最佳。
影响排序效果的因素
&&&  因为不同的排序方法适应不同的应用环境和要求,所以选择合适的排序方法应综合考虑下列因素:
  ①待排序的记录数目n;
  ②记录的大小(规模);
  ③关键字的结构及其初始状态;
  ④对稳定性的要求;
  ⑤语言工具的条件;
  ⑥存储结构;
  ⑦时间和辅助空间复杂度等。
不同条件下,排序方法的选择
(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
&&&  当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
&&&  快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
&&&  堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
&&&  若要求排序稳定,则可选用归并排序。但本章介绍的从单个记录起进行两两归并的& 排序算法并不&#20540;得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。
10、OSI模型7层结构,TCP/IP模型结构?
osi参考模型
osi参考模型中的数据封装过程
下面的图表试图显示不同的TCP/IP和其他的协议在最初中的位置:
例如、、、、、、、、、、、
例如、、、、
例如、、、ISO
8327 / CCITT X.225、、、、、
例如、、、、、、
例如、、、、、、、、、、、&
数据链路层
例如、、、、、、、、
例如、、、
tcp/ip参考模型
tcp/ip参考模型分为四个层次:应用层、传输层、网络互连层和主机到网络层:
tcp/ip参考模型的层次结构
通常人们认为OSI模型的最上面三层(应用层、表示层和会话层)在TCP/IP组中是一个应用层。由于TCP/IP有一个相对较弱的会话层,由TCP和RTP下的打开和关闭连接组成,并且在TCP和UDP下的各种应用提供不同的端口号,这些功能能够被单个的应用程序(或者那些应用程序所使用的库)增加。与此相&#20284;的是,IP是按照将它下面的网络当作一个黑盒子的思想设计的,这样在讨论TCP/IP的时候就可以把它当作一个独立的层。
(OSI5 到 7层)
(如和这样的路由协议,尽管由于各种各样的原因它们分别运行在TCP和UDP上,仍然可以将它们看作网络层的一部分)
(OSI4 和 5层)
例如、、、
(如这样的路由协议,尽管运行在IP上也可以看作是网络层的一部分)
网络互连层
(OSI3层)
对于TCP/IP来说这是(IP)
(如和这样的必须协议尽管运行在IP上,也仍然可以看作是网络互连层的一部分;不运行在IP上)
网络接口层
(OSI1和2层)
例如、、等。
该层包括所有和应用程序协同工作,利用基础网络交换应用程序专用的数据的协议。应用层是大多数普通与网络相关的程序为了通过网络与其他程序通信所使用的层。这个层的处理过程是应用特有的;数据从网络相关的程序以这种应用内部使用的&#26684;式进行传送,然后被编码成标准协议的&#26684;式。
一些特定的程序被认为运行在这个层上。它们提供服务直接支持用户应用。这些程序和它们对应的协议包括HTTP(The WorldWide Web)、FTP(文件传输)、SMTP(电子邮件)、SSH(安全远程登陆)、DNS(名称&-& IP 地址寻找)以及许多其他协议。
一旦从应用程序来的数据被编码成一个标准的应用层协议,它将被传送到IP栈的下一层。
在传输层,应用程序最常用的是TCP或者UDP,并且服务器应用程序经常与一个公开的端口号相联系。服务器应用程序的端口由InternetAssigned Numbers Authority(IANA)正式地分配,但是现今一些新协议的开发者经常选择它们自己的端口号。由于在同一个系统上很少超过少数几个的服务器应用,端口冲突引起的问题很少。应用软件通常也允许用户强制性地指定端口号作为运行参数。
连结外部的客户端程序通常使用系统分配的一个随机端口号。监听一个端口并且然后通过服务器将那个端口发送到应用的另外一个副本以建立对等连结(如IRC上的dcc文件传输)的应用也可以使用一个随机端口,但是应用程序通常允许定义一个特定的端口范围的规范以允许端口能够通过实现网络地址转换(NAT)的路由器映射到内部。
每一个应用层(TCP/IP参考模型 的最高层)协议一般都会使用到两个传输层协议之一:面向连接的TCP传输控制协议和无连接的包传输的UDP用户数据报文协议 。
常用的应用层协议有:
运行在协议上的协议:&
(HypertextTransfer Protocol,超文本传输协议),主要用于普通浏览。(HypertextTransfer Protocol
over Secure Socket Layer, or HTTP over SSL,安全超文本传输协议),HTTP协议的安全版本。(File Transfer Protocol,文件传输协议),由名知义,用于文件传输。(PostOffice Protocol,
version 3,邮局协议),收邮件用。(SimpleMail Transfer Protocol,简单邮件传输协议),用来发送电子邮件
。(Teletypeover the
Network,网络电传),通过一个终端(terminal)登陆到网络。(Secure Shell,用于替代安全性差的),用于加密安全登陆。
运行在协议上的协议:&
(BootProtocol,启动协议),应用于无盘设备。(Network Time Protocol,网络时间协议),用于网络同步。
(Domain Name Service,域名服务),用于完成地址查找,邮件转发等工作(运行在和协议上)。(EchoProtocol,回绕协议),用于查错及测量应答时间(运行在和协议上)。(SimpleNetwork Management
Protocol,简单网络管理协议),用于网络信息的收集和网络管理。(DynamicHost Configuration
Protocol,动态主机配置协议),动态配置IP地址。(Address Resolution Protocol,地址解析协议),用于动态解析以太网硬件的地址。
传输层的协议,能够解决诸如可靠性(“数据是否已经到达目的地?”)和保证数据按照正确的顺序到达这样的问题。在TCP/IP协议组中,传输协议也包括所给数据应该送给哪个应用程序。
在TCP/IP协议组中技术上位于这个层的动态路由协议通常被认为是网络层的一部分;一个例子就是OSPF(IP协议89)。
TCP(IP协议6)是一个“可靠的”、面向连结的传输机制,它提供一种可靠的字节流保证数据完整、无损并且按顺序到达。TCP尽量连续不断地测试网络的负载并且控制发送数据的速度以避免网络过载。另外,TCP试图将数据按照规定的顺序发送。这是它与UDP不同之处,这在实时数据流或者路由高网络层丢失率应用的时候可能成为一个缺陷。
较新的SCTP也是一个“可靠的”、面向连结的传输机制。它是面向纪录而不是面向字节的,它在一个单独的连结上提供了通过多路复用提供的多个子流。它也提供了多路自寻址支持,其中连结终端能够被多个IP地址表示(代表多个物理接口),这样的话即使其中一个连接失败了也不中断。它最初是为电话应用开发的(在IP上传输SS7),但是也可以用于其他的应用。
UDP(IP协议号17)是一个无连结的数据报协议。它是一个“best effort”或者“不可靠”协议——不是因为它特别不可靠,而是因为它不检查数据包是否已经到达目的地,并且不保证它们按顺序到达。如果一个应用程序需要这些特点,它必须自己提供或者使用TCP。
UDP的典型性应用是如流媒体(音频和视频等)这样按时到达比可靠性更重要的应用,或者如DNS查找这样的简单查询/响应应用,如果建立可靠的连结所作的额外工作将是不成比例地大。
DCCP目前正由IEFT开发。它提供TCP流动控制语义,但对于用户来说保留了UDP的数据报服务模型。
TCP和UDP都用来支持一些高层的应用。任何给定网络地址的应用通过它们的TCP或者UDP端口号区分。根据惯例使一些大众所知的端口与特定的应用相联系。
RTP是为如音频和视频流这样的实时数据设计的数据报协议。RTP是使用UDP包&#26684;式作为基础的会话层,然而据说它位于因特网协议栈的传输层。
网络互连层
正如最初所定义的,网络层解决在一个单一网络上传输数据包的问题。类&#20284;的协议有X.25和ARPANET的Host/IMP Protocol。
随着因特网思想的出现,在这个层上添加了附加的功能,也就是将数据从源网络传输到目的网络。这就牵涉到在网络组成的网上选择路径将数据包传输,也就是因特网。
在因特网协议组中,IP完成数据从源发送到目的基本任务。IP能够承载多种不同的高层协议的数据;这些协议使用一个唯一的IP协议号进行标识。ICMP和IGMP分别是1和2。
一些IP承载的协议,如ICMP(用来发送关于IP发送的诊断信息)和IGMP(用来管理多播数据),它们位于IP层之上但是完成网络层的功能,这表明了因特网和OSI模型之间的不兼容性。所有的路由协议,如BGP、 OSPF、和RIP实际上也是网络层的一部分,尽管&#20284;乎它们应该属于更高的协议栈。
网络接口层
网络接口层实际上并不是因特网协议组中的一部分,但是它是数据包从一个设备的网络层传输到另外一个设备的网络层的方法。这个过程能够在网卡的软件驱动程序中控制,也可以在韧体或者专用芯片中控制。这将完成如添加报头准备发送、通过物理媒介实际发送这样一些数据链路功能。另一端,链路层将完成数据帧接收、去除报头并且将接收到的包传到网络层。
然而,链路层并不经常这样简单。它也可能是一个虚拟专有网络(VPN)或者隧道,在这里从网络层来的包使用隧道协议和其他(或者同样的)协议组发送而不是发送到物理的接口上。VPN和隧道通常预先建好,并且它们有一些直接发送到物理接口所没有的特殊特点(例如,它可以加密经过它的数据)。由于现在链路“层”是一个完整的网络,这种协议组的递归使用可能引起混淆。但是它是一个实现常见复杂功能的一个优秀方法。(尽管需要注意预防一个已经封装并且经隧道发送下去的数据包进行再次地封装和发送)。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
1、物理层(physical layer)  
  物理层规定了激活、维持、关闭通信端点之间的机械特性、电气特性、功能特性以及过程特性。该层为上层协议提供了一个传输数据的物理媒体。  
  在这一层,数据的单位称为比特(bit)。  
  属于物理层定义的典型规范代表包括:eia/tia rs-232、eia/tia rs-449、v.35、rj-45等。
  2、数据链路层(data link layer)  
  数据链路层在不可靠的物理介质上提供可靠的传输。该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。  
  在这一层,数据的单位称为帧(frame)。  
  数据链路层协议的代表包括:sdlc、hdlc、ppp、stp、帧中继等。  
  3、网络层(network layer)  
  网络层负责对子网间的数据包进行路由选择。此外,网络层还可以实现拥塞控制、网际互连等功能。
  在这一层,数据的单位称为数据包(packet)。  
  网络层协议的代表包括:ip、ipx、rip、ospf等。  
  4、传输层(transport layer)  
  传输层是第一个端到端,即主机到主机的层次。传输层负责将上层数据分段并提供端到端的、可靠的或不可靠的传输。此外,传输层还要处理端到端的差错控制和流量控制问题。  & 在这一层,数据的单位称为数据段(segment)。  
  传输层协议的代表包括:tcp、udp、spx等。  
  5、会话层(session layer)  
  会话层管理主机之间的会话进程,即负责建立、管理、终止进程之间的会话。会话层还利用在数据中插入校验点来实现数据的同步。  
  会话层协议的代表包括:netbios、zip(appletalk区域信息协议)等。  
  6、表示层(presentation layer)  
  表示层对上层数据或信息进行变换以保证一个主机应用层信息可以被另一个主机的应用程序理解。表示层的数据转换包括数据的加密、压缩、&#26684;式转换等。  
  表示层协议的代表包括:ascii、asn.1、jpeg、mpeg等。  
  7、应用层(application layer)  
  应用层为操作系统或网络应用程序提供访问网络服务的接口。
  应用层协议的代表包括:telnet、ftp、http、snmp等。
集线器hub工作在OSI参考模型的(物理)层;
网卡工作在OSI参考模型的(物理)层;
路由器router工作在OSI参考模型的(网络)层;
交换机Switch工作在OSI参考模型的(数据链路)层。
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
&(附)10、tcp建立连接为什么要三次握手?
tcp是一个面向连接的协议,在传送数据以前,必须要首先建立一条连接。连接的建立需要经过三次握手。为什么要经过三次握手呢,每次握手双方都做了些什么?
1)什么是tcp报文?
tcp报文就是通过tcp协议发送的数据包,由tcp头和数据段组成。
tcp头是固定的20个字节,它的&#26684;式为:
2)第一次握手做什么?
请求端(客户端)会向服务端(被请求端)发送一个tcp报文,申请打开某一个端口。因为没有数据,所以这个报文仅包含一个tcp头。其中:
SYN=1;当建立一个新的连接时, SYN标志变1。
序号;序号用来标识从客户端向服务端发送的数据字节流。
此时客户端进入SYN_SENT状态。
3)第二次握手做什么?
服务端收到客户端的SYN包,也会发一个只包含tcp头的报文给客户端。
ACK=1;服务端确认收到信息
确认序号;客户端序号&#43;1,作为应答
SYN=1;因为tcp的连接是双向的,服务端作为应答的同时请求建立连接。
此时服务端进入SYN_RECV状态
4)第三次握手做什么?
ACK=1;客户端确认收到信息
确认序号;服务端序号&#43;1,作为应答
此时客户端进入ESTABLISHED状态,服务端收到ACK后也会进入此状态
可见,客户端和服务端都保留了对方的序号,这三次握手缺少任何一步都无法实现这一目标。在三次握手过程中,出现了一些中间状态。
5)什么是半连接队列?
第一次握手完成后,服务端发送ACK&#43;SYN包到客户端,在收到客户端返回前的状态为SYN_RECV,服务端为此状态维护一个半连接队列。当服务端收到客户的确认包时,删除该条目,服务端进入ESTABLISHED状态。Listen中的backlog参数表示这两个状态合的最大&#20540;。若客户端完成第一次握手后不再发送ACK包,导致服务端未完成队列溢出,达到Dos攻击的目的。
6)什么是SYN-ACK 重传?
Dos攻击可以达到目的的一个重要因素是服务端在发送完SYN&#43;ACK包后会等待客户端的确认包,如果等待时间内未收到,服务端会进行首次重传,等待一段时间仍未收到客户确认包,会进行第二次重传,直到重传次数超过系统规定的最大&#20540;,系统将该连接信息从半连接队列中删除。如果系统删除的频率小于半连接状态的增长频率,服务端就无法正常提供服务。
7)Tcp关闭连接需要四次握手,这又是为什么呢?
这是由tcp半关闭(harf-close)造成的。既然一个TCP连接是全双工(即数据在两个方向上能同时传递),因此每个方向必须单独地进行关闭。即一方发送一个FIN,另一方收到后发送一个ACK,这就是所谓的四次握手了。
8)第一次握手做什么?
客户端发送一个FIN(这个客户端是主动发起关闭的一端,与建立连接时的客户端不一定是同一主机)
此时客户端进入FIN_WAIT_1状态。
9)第二次握手做什么?
服务端收到FIN,发回客户端一个ACK,确认序号为收到的序号加1(因为FIN和SYN一样,会占用一个序号);客户端收到ACK之后会进入FIN_WAIT_2状态,服务端会进入CLOSE_WAIT状态。
10)第三次握手做什么?
服务端发送给客户端一个FIN。服务端进入LAST_ACK状态。
11)第四次握手做什么?
客户端收到FIN,发回服务端一个ACK,确认序号为收到的序号加1;客户端会进入TIME_WAIT状态,2MSL超时后进入CLOSE状态。服务端收到ACK后也会进入CLOSE状态。
其实我们通俗的说每次握手其实就是发一次数据包的过程。建立连接时双方共发送了3个包,关闭连接时发送和确认的两次握手决定了一端数据流的关闭,四次握手可以保证两方都关闭。
12)为什么建立连接是三次握手,而关闭连接是四次呢?
建立连接时,服务端可以把应答ACK和同步SYN放在一个报文里进行发送。而关闭连接时,收到FIN通知仅仅表示对方没有数据发送过来了,并不表示自己的数据全部发送给了对方。所以ACK和FIN是分了两次进行发送。如果服务端收到FIN,恰恰自己也没有数据要发,是不是ACK和FIN可以一起发给客户端呢,这样就可以少一次数据流了。世界是美好的,经典的TCP连接状态图中也考虑到了这种情况,tcp关闭连接确实是只有三次数据流动,服务端将ACK和FIN放在一个包里进行发送,但四次握手这个概念却已经根深蒂固无法更改了。
13)Tcp的各个状态是怎样的?
客户端的正常tcp状态:
CLOSED-&SYN_SENT(第1次)-&ESTABLISHED(第3次)-&FIN_WAIT_1(第1次)-&FIN_WAIT_2(第2次)-&TIME_WAIT(第4次)-&CLOSED
服务端的正常tcp状态:
CLOSED-&LISTEN-&SYN_RCVD(第2次)-&ESTABLISHED(第3次)-&CLOSE_WAIT(第2次)-&LAST_ACK(第3次)-&CLOSED(第4次)
tcp还有其他的非正常状态,在此不做讨论,下篇文章再说。
11、数组和链表的优缺点
数组,在内存上给出了连续的空间。链表,内存地址上可以是不连续的,每个链表的节点包括原来的内存和下一个节点的信息(单向的一个,双向链表的话,会有两个)。
数组优于链表的:
A. 内存空间占用的少,因为链表节点会附加上一块或两块下一个节点的信息。
但是数组在建立时就固定了。所以也有可能会因为建立的数组过大或不足引起内存上的问题。
B. 数组内的数据可随机访问,但链表不具备随机访问性。这个很容易理解,数组在内存里是连续的空间,比如如果一个数组地址从100到200,且每个元素占用两个字节,那么100-200之间的任何一个偶数都是数组元素的地址,可以直接访问。
链表在内存地址可能是分散的。所以必须通过上一节点中的信息找能找到下一个节点。
C. 查找速度上。这个也是因为内存地址的连续性的问题,不罗索了。
链表优于数组的:
A. 插入与删除的操作。如果数组的中间插入一个元素,那么这个元素后的所有元素的内存地址都要往后移动。删除的话同理。只有对数据的最后一个元素进行插入删除操作时,才比较快。链表只需要更改有必要更改的节点内的节点信息就够了。并不需要更改节点的内存地址。
B. 内存地址的利用率方面。不管你内存里还有多少空间,如果没办法一次性给出数组所需的要空间,那就会提示内存不足,磁盘空间整理的原因之一在这里。而链表可以是分散的空间地址。
C. 链表的扩展性比数组好。因为一个数组建立后所占用的空间大小就是固定的,如果满了就没法扩展,只能新建一个更大空间的数组;而链表不是固定的,可以很方便的扩展。
12、C&#43;&#43;操作符优先级:
记忆方法:&&
&&&&去掉一个最高的,去掉一个最低的,剩下的是一、二、三、赋&#20540;;双目运算符中,顺序为算术、关系和逻辑,移位和逻辑位插入其中。
--摘自《C语言程序设计实用问答》&&&&&&
&&&& 问题:如何记住运算符的15种优先级和结合性?&&&
&&&& 解答:C语言中运算符种类比较繁多,优先级有15种,结合性有两种。&&&
&&&& 如何记忆两种结合性和15种优先级?下面讲述一种记忆方法。&&&
&&&& 结合性有两种,一种是自左至右,另一种是自右至左,大部分运算符的结合性是自左至右,只有单目运算符、三目运算符的赋&#20540;运算符的结合性自右至左。&&&
&&&& 优先级有15种,记忆方法如下:&&&
&&&& 记住一个最高的:构造类型的元素或成员以及小括号。&&&
&&&& 记住一个最低的:逗号运算符。 &&
&&&& 剩余的是一、二、三、赋&#20540;——意思是单目、双目、三目和赋&#20540;运算符。&&&
&&&& 在诸多运算符中,又分为:算术、关系、逻辑。&&&
&&&& 两种位操作运算符中,移位运算符在算术运算符后边,逻辑位运算符在逻辑运算符的前面。
&再细分如下:&&&
&&&& 算术运算符*,/,%高于&#43;,-。&&&
&&&& 关系运算符中:&,&=,&和&=高于==,!=。&&&
&&&& 逻辑运算符中,除了逻辑求反(!)是单目外,逻辑与(&&)高于逻辑或(||)。&&&
&&&& 逻辑位运算符中,除了逻辑按位求反(~)外,按位与(&)高于按位半加(^),高于按位或(|)。&&
Precedence
Description
Overloadable
Associativity
Scope resolution operator
Class::age = 2;
left to right
Function call
printf(“Hello world\n”);
left to right
Member initalization
c_tor(int x, int y) : _x(x), _y(y * 10) {}
Array access
array[4] = 2;
Member access from a pointer
ptr-&age = 34;
Member access from an object
obj.age = 34;
&#43;&#43;
Post-increment
for (int i = 0; i & 10; i&#43;&#43;) cout &&
Post-decrement
for (int i = 10; i & 0; i--) cout &&
dynamic_cast
Runtime-checked type conversion
Y& y = dynamic_cast&Y&&(x);
static_cast
Unchecked type conversion
Y& y = static_cast&Y&&(x);
reinterpret_cast
Reinterpreting type conversion
int const* p = reinterpret_cast&int const*&(0x1234);
const_cast
Cast away/Add constness
int* q = const_cast&int*&(p);
Get type information
std::type_info const& t = typeid(x);
Logical negation
if (!done) ...
right to left
Alternate spelling for !
Bitwise complement
Alternate spelling for ~
&#43;&#43;
Pre-increment
for (i = 0; i & 10; &#43;&#43;i) cout &&
Pre-decrement
for (i = 10; i & 0; --i) cout &&
Unary minus
int i = -1;
Unary plus
int i = &#43;1;
Dereference
int data = *intP
Address of
int *intPtr = &
Size (of the type) of the operand in bytes
size_t s = sizeof(int);
Dynamic memory allocation
long* pVar =
Dynamic memory allocation of array
long* array = new long[20];
Deallocating the memory
Deallocating the memory of array
Cast to a given type
int i = (int)floatN
Member pointer selector
ptr-&*var = 24;
left to right
Member object selector
obj.*var = 24;
Multiplication
int i = 2 * 4;
left to right
float f = 10.0 / 3.0;
int rem = 4 % 3;
int i = 2 &#43; 3;
left to right
Subtraction
int i = 5 - 1;
Bitwise shift left
int flags = 33 && 1;
left to right
Bitwise shift right
int flags = 33 && 1;
Comparison less-than
if (i & 42) ...
left to right
Comparison less-than-or-equal-to
if (i &= 42) ...
Comparison greater-than
if (i & 42) ...
Comparison greater-than-or-equal-to
if (i &= 42) ...
Comparison equal-to
if (i == 42) ...
left to right
Alternate spelling for ==
Comparison not-equal-to
if (i != 42) ...
Alternate spelling for !=
Bitwise AND
flags = flags & 42;
left to right
Alternate spelling for &
Bitwise exclusive OR (XOR)
flags = flags ^ 42;
left to right
Alternate spelling for ^
Bitwise inclusive (normal) OR
flags = flags | 42;
left to right
Alternate spelling for |
Logical AND
if (conditionA && conditionB) ...
left to right
Alternate spelling for &&
Logical OR
if (conditionA || conditionB) ...
left to right
Alternate spelling for ||
Ternary conditional (if-then-else)
int i = a & b ? a :
right to left
Assignment operator
right to left
Increment and assign
a &#43;= 3;
Decrement and assign
Multiply and assign
Divide and assign
Modulo and assign
Bitwise AND and assign
flags &= new_
Alternate spelling for &=
Bitwise exclusive or (XOR) and assign
flags ^= new_
Alternate spelling for ^=
Bitwise normal OR and assign
flags |= new_
Alternate spelling for |=
Bitwise shift left and assign
flags &&= 2;
Bitwise shift right and assign
flags &&= 2;
throw exception
throw EClass(“Message”);
Sequential evaluation operator
for (i = 0, j = 0; i & 10; i&#43;&#43;, j&#43;&#43;) ...
left to right
13、B树、B-树、B&#43;树、B*树、红黑树和trie树
(1)B树:即二叉搜索树.
&&&&&& 1.所有非叶子结点至多拥有两个儿子(Left和Right);
&&&&&& 2.所有结点各存储一个关键字;
&&&&&& 3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;
&&&&&& 如:
&&&&&& B树的搜索,从根结点开始,如果查询的关键字与结点的关键字相等,那么就命中;否则,如果查询关键字比结点关键字小,就进入左儿子;如果比结点关键字大,就进入右儿子;如果左儿子或右儿子的指针为空,则报告找不到相应的关键字;
&&&&&& 如果B树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么B树的搜索性能&#36924;近二分查找;但它比连续内存空间的二分查找的优点是,改变B树结构(插入与删除结点)不需要移动大段的内存数据,甚至通常是常数开销;
&&&&&& 如:
&&& 但B树在经过多次插入与删除后,有可能导致不同的结构:
&& 右边也是一个B树,但它的搜索性能已经是线性的了;同样的关键字集合有可能导致不同的树结构索引;所以,使用B树还要考虑尽可能让B树保持左图的结构,和避免右图的结构,也就是所谓的“平衡”问题;&&&&&
&&&&&&实际使用的B树都是在原B树的基础上加上平衡算法,即“平衡二叉树”;如何保持B树结点分布均匀的平衡算法是平衡二叉树的关键;平衡算法是一种在B树中插入和删除结点的策略;
&&&&&& 是一种多路搜索树(并不是二叉的), 多路平衡查找树:
&&&&&& 1.定义任意非叶子结点最多只有M个儿子;且M&2;
&&&&&& 2.根结点的儿子数为[2, M];
&&&&&& 3.除根结点以外的非叶子结点的儿子数为[M/2, M];
&&&&&& 4.每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字)
&&&&&& 5.非叶子结点的关键字个数=指向儿子的指针个数-1;
&&&&&& 6.非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] & K[i&#43;1];
&&&&&& 7.非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;
&&&&& &8.所有叶子结点位于同一层;
&&&&&& 如:(M=3)
&&&&&&B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已经是叶子结点;
B-树的特性:
&&&&&& 1.关键字集合分布在整颗树中;
& &&&&&2.任何一个关键字出现且只出现在一个结点中;
&&&&&& 3.搜索有可能在非叶子结点结束;
&&&&&& 4.其搜索性能等价于在关键字全集内做一次二分查找;
&&&&&& 5.自动层次控制;
&&&&&& 由于限制了除根结点以外的非叶子结点,至少含有M/2个儿子,确保了结点的至少利用率,其最底搜索性能为:
&&&&&& 其中,M为设定的非叶子结点最多子树个数,N为关键字总数;
&&&&&& 所以B-树的性能总是等价于二分查找(与M&#20540;无关),也就没有B树平衡的问题;
&&&&&& 由于M/2的限制,在插入结点时,如果结点已满,需要将结点分裂为两个各占M/2的结点;删除结点时,需将两个不足M/2的兄弟结点合并;
(3)B&#43;树
&&&&&& B&#43;树是B-树的变体,也是一种多路搜索树:
&&&&&& 1.其定义基本与B-树同,除了:
&&&&&& 2.非叶子结点的子树指针与关键字个数相同;
&&&&& &3.非叶子结点的子树指针P[i],指向关键字&#20540;属于[K[i], K[i&#43;1])的子树(B-树是开区间);
&&&&&& 5.为所有叶子结点增加一个链指针;
&&&&&& 6.所有关键字都在叶子结点出现;
&&&&&& 如:(M=3)
&& B&#43;的搜索与B-树也基本相同,区别是B&#43;树只有达到叶子结点才命中(B-树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找;
&&&&&& B&#43;的特性:
&&&&&& 1.所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的;
&&&&&& 2.不可能在非叶子结点命中;
&&&&&& 3.非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层;
&&&&&& 4.更适合文件索引系统;
&&&&&& 是B&#43;树的变体,在B&#43;树的非根和非叶子结点再增加指向兄弟的指针;
&& B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B&#43;树的1/2);
&&&&&& B&#43;树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B&#43;树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;
&&&&&& B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;
&&&&&& 所以,B*树分配新结点的概率比B&#43;树要低,空间使用率更高;
(5)红黑树
&&&& 红黑树(Red-Black Tree)是二叉搜索树(BinarySearch Tree)的一种改进。我们知道二叉搜索树在最坏的情况下可能会变成一个链表(当所有节点按从小到大的顺序依次插入后)。而红黑树在每一次插入或删除节点之后都会花O(log N)的时间来对树的结构作修改,以保持树的平衡。也就是说,红黑树的查找方法与二叉搜索树完全一样;插入和删除节点的的方法前半部分节与二叉搜索树完全一样,而后半部分添加了一些修改树的结构的操作。
map就是采用红黑树存储的,红黑树(RB Tree)是平衡二叉树,其优点就是树到叶子节点深度一致,查找的效率也就一样,为logN。在实行查找,插入,删除的效率都一致,而当是全部静态数据时,没有太多优势,可能采用hash表各合适。
相对来说,hash_map是一个hashtable占用内存更多,查找效率高一些,但是hash的时间比较费时。总体而言,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小, hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严&#26684;,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且
hash_map的构造速度较慢。
现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用。
&&& 红黑树的每个节点上的属性除了有一个key、3个指针:parent、lchild、rchild以外,还多了一个属性:color。它只能是两种颜色:红或黑。而红黑树除了具有二叉搜索树的所有性质之外,还具有以下4点性质:
1. 根节点是黑色的。
2. 空节点是黑色的(红黑树中,根节点的parent以及所有叶节点lchild、rchild都不指向NULL,而是指向一个定义好的空节点)。
3.&红色节点的父、左子、右子节点都是黑色。
4. 在任何一棵子树中,每一条从根节点向下走到空节点的路径上包含的黑色节点数量都相同。
(6)trie树
Trie树,即Double Array字典查找树,既可用于一般的字典搜索,也可用于索引查找。
每个节点相当于DFA的一个状态,终止状态为查找结束。有序查找的过程相当于状态的不断转换
对于给定的一个字符串a1,a2,a3,...,an.则
采用TRIE树搜索经过n次搜索即可完成一次查找。不过好像还是没有B树的搜索效率高,B树搜索算法复杂度为logt(n&#43;1/2).当t趋向大,搜索效率变得高效。
Trie树的优点举例
已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串。下面对比3种方法:
1.&& 最容易想到的:即从字符串集中从头往后搜,看每个字符串是否为字符串集中某个字符串的前缀,复杂度为O(n^2)。
2.&& 使用hash:我们用hash存下所有字符串的所有的前缀子串。建立存有子串hash的复杂度为O(n*len)。查询的复杂度为O(n)* O(1)= O(n)。
3.&& 使用trie:因为当查询如字符串abc是否为某个字符串的前缀时,显然以b,c,d....等不是以a开头的字符串就不用查找了。所以建立trie的复杂度为O(n*len),而建立&#43;查询在trie中是可以同时执行的,建立的过程也就可以成为查询的过程,hash就不能实现这个功能。所以总的复杂度为O(n*len),实际查询的复杂度只是O(len)。
&&&&&&&& 解释一下hash为什么不能将建立与查询同时执行,例如有串:911,911456输入,如果要同时执行建立与查询,过程就是查询911,没有,然后存入9、91、911,查询911456,没有然后存入、911456,而程序没有记忆功能,并不知道911在输入数据中出现过。所以用hash必须先存入所有子串,然后for循环查询。
而trie树便可以,存入911后,已经记录911为出现的字符串,在存入911456的过程中就能发现而输出答案;倒过来亦可以,先存入911456,在存入911时,当指针指向最后一个1时,程序会发现这个1已经存在,说明911必定是某个字符串的前缀,该思想是我在做pku上的3630中发现的,详见本文配套的“入门练习”。
B树:二叉树,每个结点只存储一个关键字,等于则命中,小于走左结点,大于走右结点;&
B-树:多路搜索树,每个结点存储M/2到M个关键字,非叶子结点存储指向关键字范围的子结点;所有关键字在整颗树中出现,且只出现一次,非叶子结点可以命中;&
B&#43;树:在B-树基础上,为叶子结点增加链表指针,所有关键字都在叶子结点中出现,非叶子结点作为叶子结点的索引;B&#43;树总是到叶子结点才命中;&
B*树:在B&#43;树基础上,为非叶子结点也增加链表指针,将结点的最低利用率从1/2提高到2/3;&
14、最小生成树算法之Prim算法(C&#43;&#43;实现)
在无向带权连通图G中,如果一个连通子树包含所有顶点,并且连接这些顶点的边权之和最小,那么这个连通子图就是G的最小生成树。求最小生成树的一个常见算法是Prim算法,该算法的基本思想是:
1)设置两个集合V和S,任意选择一个顶点作为起始顶点,将起始顶点放入集合S,其余顶点存入集合V中;
2)然后使用贪心策略,选择一条长度最短并且端点分别在S和V中边(即为最小生成树的中的一条边),将这条边在V中的端点加入到集合S中;
3)循环执行第2)步直到S中包含了所有顶点。
&根据以上思想我们很快可以给出一个O(N^3)的算法,即选择一条最短边需要O(N^2)的时间复杂度,具体实现代码如下:
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include &iostream&
//用邻接矩阵表示无向图
#define N 6 //节点个数
#define M 100000//最大&#20540;,表示不可达
int matrix[N][N]=
&&&&&&&& M,6,1,5,M,M,
&&&&&&&& 6,M,5,M,3,M,
&&&&&&&& 1,5,M,5,6,4,
&&&&&&&& 5,M,5,M,M,2,
&&&&&&&& M,3,6,M,M,6,
&&&&&&&& M,M,4,2,6,M
void prim()
&&& bool flag[N]; //标记某个点是否当前生成树集合中
&&& int i,j;
&&& //初始化集合
&&& for(i = 0; i &N; &#43;&#43;i)
&&&&&&& flag[i] =
&&& flag[0] =
&&& int count = 1;
&&& while(count&#43;&#43;& N)
&&&&&&& int min =M;&&
&&&&&&& int e1 = -1,e2 = -1;&&
&&&&&&& for(i = 0; i& N; &#43;&#43;i)&&
&&&&& &&{&&&
&&&&&&&&&&&if(flag[i])&&&
&&&&&&&&&&& {&&&&
&&&&&&&&&&&&&&& for(j= 0; j & N; &#43;&#43;j)&&&&
&&&&&&&&&&&&&&&{&&&&&
&&&&&&&&&&&&&&&&&&&if(!flag[j])&&&&&
&&&&&&&&&&&&&&&&&&&{&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&if(matrix[i][j] & min)&&&&&&
&&&&&&&&&& &&&&&&&&&&&&&{&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&min = matrix[i][j];&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&e1 =&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&e2 =&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&&&&}&
&&&&&&&&&&&&&&&}&&&
&&&&&&&&&&& }
&& &&&&&}&&
&&&&&&&cout&&e1 &#43; 1&&&-&&&e2 &#43; 1&&&&&&matrix[e1][e2]&&&&
&&&&&&& flag[e2] =
int main(int argc, char* *argv)
&&& prim();
&&&system(&pause&);
&&& return 0;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
上面的算法有三个循环,时间复杂度为O(N^3),考虑到由于使用的是贪心策略,则每添加一个新顶点到集合S中的时候,才会改变V中每个点到S中点的最小边的长度。因此可以用一个数组nearest[N](N为顶点个数)记录在生成最小数的过程中,记录V中每个点的到S中点的最小变长,用另外一个数组adjecent[N]记录使得该边最小的对应的邻接点。那么O(N)的时间了找到最短的边,并且能在O(N)的时间里更新nearest[N]和adjecent[N]。因此可以得到O(N^2)的算法。
源码实现如下:
#include &iostream&
#define N 6 //节点个数
#define M 100000//最大&#20540;,表示不可达
//用邻接矩阵表示无向图
int matrix[N][N] =
&&& M,6,1,5,M,M,
&&& 6,M,5,M,3,M,
&&& 1,5,M,5,6,4,
&&& 5,M,5,M,M,2,
&&& M,3,6,M,M,6,
&&& M,M,4,2,6,M
void prim()
&&& //记当前生成树的节点集合为S
&&& //未使用的节点结合为V
&&& bool flag[N]; //标记某个点是否在S中
&&& int nearest[N];//记录V中每个点到S中邻接点的最短边
&&& intadjecent[N];//记录与V中每个点最邻接近的点
&&& int i,j,
&&& //初始化集合
&&& for(i = 0; i &N; &#43;&#43;i)
&&&&&&& flag[i] =
&&& flag[0] =
&&& for(i = 1; i &N; &#43;&#43;i)
&&&&&&& nearest[i] =matrix[0][i];
&&&&&&& adjecent[i] =0;
&&& int count = N;
&&& while(--count)
&&&&&&& min = M;
&&&&&&& j = 0;
&&&&&&& for(i = 0; i& N; &#43;&#43;i)
&&&&&&&&&&&if(!flag[i] && nearest[i] & min)
&&&&&&&&&& &{
&&&&&&&&&&&&&&& min =nearest[i];
&&&&&&&&&&&&&&& j =i;
&&&&&&&&&&& }
&&&&&&& cout&&j&#43; 1&&&-&&&adjecent[j] &#43; 1&&&&&&matrix[j][adjecent[j]]&&
&&&&&&& flag[j] =
&&&&&&& for(i = 0; i& N; &#43;&#43;i)
&&&&&&&&&& &if(!flag[i] && matrix[i][j] &nearest[i])
&&&&&&&&&&& {
&&&&&&&&&&&&&&&nearest[i] = matrix[i}

我要回帖

更多关于 已知一组数据有40个 的文章

更多推荐

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

点击添加站长微信