SlideShare a Scribd company logo
1 of 680
烟雨科技有限责任公司


     数据结构
用面向对象方法与C++语言描述
     第二版
    殷人昆编著
   清华大学出版社
      课件
        本课件由迷若烟雨提供
课程介绍
1. 为什么要学习数据结构?
2. 该课程的主要内容是什么?如何学习?
3. 考核方式?
    总成绩=平时( 40% )+期末(60%)
    平时=上机实习+平时作业+上课回答问题
       +4次课内考试(每次5分)
4. 教材:殷人昆,《数据结构(面向对象方法与C++
   语言描述)》(第2版)清华大学出版社
1. 为什么要学习数据结构?
2. 该课程的主要内容是什么?
    数据结构=数据+结构(关系)
数据结构
          数据(的集合)             结构

          逻辑结构                     物理结构

线         树       图       集        连   非
性         结       结       合        续   连
结         构       构                    续
构
                      顺   链
      顺   链   邻   邻   序   式
顺   链 序   式   接   接   存   存
序   表 存   存   矩   表   储   储
表     储   储   阵   存
              存   储
              储
如何学习?
1. 预习:粗读教材,发现问题
2. 听课:重点、难点,初步解决问题
3. 复习:细读教材,解决问题(理解抽象数
   据类型、看懂C++类的定义和实现)
4. 做题:巩固知识(C++类的定义和实现)
5. 实习:验证(调试程序)
上机实习作业:
1.   顺序表及其应用
2.   链表及其应用.doc
3.   栈及其应用
4.   队列及其应用
5.   稀疏矩阵及其基本操作
6.   二叉树及其基本操作
7.   堆及其基本操作
8.   Huffman树及其基本操作
9.   图及其基本操作
第一章   绪论

内容提要:
  本章主要介绍数据结构的基本概念、
术语,以及有关算法的描述、分析的基本
常识。
§1.1 数据结构的概念            第一章 绪论
1.1.1 为什么要讨论数据结构?
通过前面的学习,我们知道:
  计算机科学是研究信息表示和信息处理的科学;
  信息是当今社会的重要资源;
  计算机产业及计算机科学技术飞速发展,计算机的硬件技
 术和软件技术都有了极大的发展,计算机应用也已经渗透到
 了社会的各个领域。
同时出现了如下一些变化:
   计算机由最初的单一科学计算到几乎无所不能;
   加工处理的对象由数值型变为数值型和非数值型;
   处理的数据量由小变为大、巨大(海量存储、计算);
   数据之间的关系由简单变复杂、很复杂;
§1.1 数据结构的概念           第一章 绪论
解决思路1:发展硬件技术,开发出更快、更高级的硬件产品,
但有没有其它途径呢?

思路2:
  将一“大堆杂乱无章”的数据交给计算机处理是很不明智的,
结果是加工处理的效率会非常低,有时甚至根本无法进行。
 于是:
   人们开始考虑如何更有效地描述、表示、处理数据的问题,
 除了不断提高计算机技术外(其它课程),很重要的一个方面
 是通过研究、分析问题数据本身的特点,利用这些特点提高数
 据表示和处理的效率。——这就是数据结构

信息的表示和组织形式直接影响到数据处理的效率!

下面举几个例子,目的是加深对数据结构的理解,从中也
可以看出研究数据结构的重要性!
线性
      例1 书目自动检索系统                      表

                                          书目文件
                 书目卡片
        001    高等数学      樊映川    S01
        002   登录号:
               理论力学      罗远祥    L01
                                            索引表
        003   书名:
               高等数学      华罗庚    S01
        004    线性代数      栾汝书    S02
        ……
              作者名:
                ……       ……     ……
按书名            分类号:        按作者名               按分类号

高等数学           出版单位:
       001,003……    樊映川   001, …      L    002, …
       002,……..
理论力学
               出版时间:华罗庚   002, ….     S    001, 003,
线性代数   004,……                       ……     ……
                    栾汝书   004, ….
 ……    ……..    价格:
                   …….    …….
例1 书目自动检索系统
 问题:图书管理,完成书目的自动检索。
 数据:一本本书籍,更确切地说是每本书的
 信息,如:书名、作者、出版社、出版日期、
 书号、内容提要等等。
 操作:书目检索、入库、借书、还书等。
 涉及到:书目数据逻辑组织、存储方法;检
 索、入库等操作实现算法。
例2 人机对奕问题(井字棋)                  树




……..                               ……..




       …...   …...   …...   …...
例2 人机对奕问题

 问题:人机对弈,即人与计算机下棋。
 数据:各种棋局状态,确切说就是描述棋盘
 格局的信息。
 操作:走棋,即选择一种策略使棋局状态发
 生变化(由一个格局派生出另一个格局)
 涉及到:“格局‖数据逻辑组织、存储方法;走
 棋操作实现算法。
例3 多叉路口交通灯管理问题               图


      AB   AC   AD
                         C
                             D
BA    BC   BD        B


      DA   DB   DC
                                 E
 EA   EB   EC   ED       A
例3 多叉路口交通灯管理问题

 问题:多叉路口的交通灯管理,即在多
 叉路口应怎样设置交通灯,以保证交通
 畅通。
 数据:路口各条道路的信息
 操作:设置信号灯(求出各个可以同时
 通行的路的集合)
 涉及到:路口各条道路信息数据及其关
 系的逻辑组织、存储方法;信号灯设置
 的操作实现算法。
§1.1 数据结构的概念                            第一章 绪论
 1.1.2 数据结构及其术语

 [数据 Data] 用于描述客观事物的数值、字符等一切可以输
 入到计算机中,并由计算机加工处理的符号集合。
 [数据元素 Data Element] 表示一个事物的一组数据称作一个
 数据元素。如学生信息可包括:学号、姓名、性别等。

 [数据项 Data Item] 构成数据元素的成份,是数据不可分割的最
 小单位 。
 [数据对象 Data Object]具有相同性质的数据成员(数据元素)的
 集合,是数据的子集。
 [结构 Structure ] 数据元素之间的关系(Relation)。
 [数据结构 Data Structure ]由一个数据对象以及该对象中的所有
 数据元素之间的关系组成,即带结构的数据元素的集合。
§1.1 数据结构的概念             第一章 绪论


 数据结构=数据+结构

      记作 Data_Structure=(D,R)
 其中:Data_Structure是数据结构的名称
    D是数据元素的有限集合(一般为一个数据对象)
    R是D上关系的有限集

注:这里说的数据元素之间的关系是指元素之间本身固有的逻辑
关系,与计算机无关。因此又称为:数据的逻辑结构


   我们研究数据结构的目的是要利用数据之间的关系(结构),
 因此,在存储时既要存储数据本身,还要存储关系!!
§1.1 数据结构的概念           第一章 绪论

[数据的存储结构]:是数据结构在计算机内的表示,它包括数据
元素的表示和关系的表示。

数据在计算机中的存储:两种形式
 连续:数据元素逐个连续存放(通过物理相邻来表示关系)
 非连续:数据元素不连续存放(如何表示关系?)

这样:一个数据结构要存放,一方面要把数据元素存起来,
另一方面,还必须把数据元素之间的逻辑关系也表示出来,那么:
要么用数据元素在物理上的相邻来表示逻辑关系
要么用数据元素的存储地址、索引表、散列函数来表示逻辑关系


   顺序存储结构   链式存储、索引存储、散列存储
§1.2 数据结构的抽象形式          第一章 绪论
1.2.1 数据类型 Data Type

    一个值的集合和定义在这个值集上的一组操作的总称。

(1) 高级语言中的数据类型实际上包括:数据的逻辑结构、数据
的存储结构及所定义的操作的实现。
(2)高级语言中的数据类型按值的不同特性分为:
   原子类型(如整型、实型、字符型、布尔型)
   结构类型(由原子类型按照一定的规则构造而成,
             如数组、结构体)
(3)数据类型逻辑概念与其在计算机程序中的实现有很重要的区
别,例如线性表数据类型有两种传统的实现方式:基于数组的顺序
表示和基于链表的链式表示;
(4)我们可以撇开计算机不考虑,现实中的任何一个问题都可以
   定义为一个数据类型——称为抽象数据类型。
§1. 2 数据结构的抽象形式          第一章 绪论

抽象数据类型=数学模型+操作    (其它教材上对ADT的描述)
        =数据结构+操作
        =数据+结构+操作
  它是一种描述用户和数据之间接口的抽象模型,亦即它给出
了一种用户自定义的数据类型。
 三元组表示:(D,R,P)
 其中D是数据对象,R是D上的关系集,P是对D的基本操作集。
ADT 抽象数据类型名
 {
   数据对象:<数据对象的定义>
   数据关系:<数据关系的定义>
   基本操作:<基本操作的定义>
 }ADT 抽象数据类型名
抽象数据类型的作用:抽象数据类型可以使我们更容易描述现实世
界。例:用线性表描述学生成绩表,用树描述棋局关系等。
内容回顾:
1.数据结构=数据+结构=(D,R)
2.结构包括:
逻辑结构:集合、线性、树型、图
物理结构(存储结构):连续、非连续
3.数据类型:(D,R,P)
4.抽象数据类型: (D,R,P)
§1.3 作为ADT的C++类              第一章 绪论

 1.3.1 面向对象的概念

 面向对象 = 对象+类+继承+通信
  对象:在应用问题中出现的各种实体、事件、规
  格说明等。由一组属性值和在这组值上的一组服
  务(或称操作)构成。

   类 (class),实例 (instance)

   具有相同属性和服务的对象归于同一类,形成类。
 类中的一个对象为该类的一个实例。
继承:是面向对象方法最有特色的方面。
  • 派生类:载重车,轿车,摩托车,…
     子类、特化类(特殊化类)
  • 基类:车辆
     父类、超类、泛化类(一般化类)
     各派生类中的公共部分,包括属性和服务,集
    中到基类中,派生类中只保留自己特有的属性和
    服务。这样减少了数据的存储和程序代码的重复
    。
– 通信
  • 各个类的对象间通过消息传递进行通信。
– 消息:一个类的对象要求另一个类的对象执行某个服务的指
  令,必要时还要传递调用参数。
§ 1.3 作为ADT的C++类                    第一章 绪论


  传统的大型结构化程序设计是面向过程的,首先着眼于系
统要实现的功能。一个数据结构可能被多个过程调用,修改数
据结构,意味着必须修改相关的过程,这样做烦琐又容易产生
错误。

   面向对象程序设计(Object-Oriented Programing,OOP)
 程序围绕被操作的数据来设计, “类”作为构造程序的基本单
 位。



    “类”具有封装、数据抽象、继承和多态等特点!
§ 1.3 作为ADT的C++类       第一章 绪论

 1.3.2 C++中的类

 例1:计算圆的周长和面积

  圆是平面上与圆心等距离的所有点的集合。从图形显示角
 度看,圆的抽象数据类型包括圆心和半径;而从计量角度看,
 它所需要的抽象数据类型只需半径即可。如果从计量角度来
 给出圆的抽象数据类型,那么它的数据取值范围应为半径的
 取值范围,即为非负实数,而它的操作形式为确定圆的半径
 (赋值);求圆的面积;求圆的周长。
§ 1.3 作为ADT的C++类                               第一章 绪论

问题描述:给定圆的半径,求其周长和面积

 ADT定义:
    ADT circle
       data : 0 r ,实数
      structure: NULL
      operations: area {计算面积 s=πr2}
                  circumference {计算周长 l=2πr}
    END ADT
§1.3 作为ADT的C++类                   第一章 绪论
例2:掷色子游戏。
问题描述:每次掷出N个色子,统计每个色子的点数和每次的总
        点数,看看谁的高。
问题分析:该问题的数据包括色子数、每个色子的点数和总点数,
         色子数是大于0的整数N;每个色子的点数是1-6;
         总点数是N~6N;
         该问题的操作包括掷色子、输出各个色子的点数、
         求总点数。
ADT定义:
   ADT dice
      data : N {色子数} ;K1,K2…Kn {每个色子的点数}
             S {总点数}
     structure: N S 6N, S=K1+K2+…+KN, 1 Ki 6
     operations: toss {掷色子}
                 displaytoss {显示每个色子的点数}
                 total {计算总点数}
   END ADT
§ 1.3 作为ADT的C++类                                第一章 绪论
例3:复数的运算
问题描述:在高级语言中,没有复数类型,但是我们可以借助已
     有的数据类型解决复数类型的问题,如复数运算。
ADT定义:
   ADT complex
      data : c1,c2 {c1,c2均为实数}
     structure: z=c1+c2i
     operations: create(z,x,y)          {生成一复数}
                   add(z1,z2,s)        {复数加法}
                   subtract(z1,z2,difference) {复数减法}
                   multiply(z1,z2,product) {复数乘法}
                   get_realpart(z) {求实部}
                   get_imagpart(z) {求虚部}
                   printc(z) {输出一复数}
   END ADT
§1.3.2 C++中的类               第一章 绪论
  C++对于面向对象程序设计的支持,核心部分就是类的定义,
类的定义体现了抽象数据类型的思想:

   •说明与实现的分离

   •信息隐蔽

对类的成员规定三级存取:
 • 公共(public):其它类的对象或操作可访问,构成类的接口
 • 私有 (private):只能该类的对象和成员函数及友元访问
 • 保护 (protected):除了具有私有成员的特性,允许该类的
 派生类访问
§1.4 算法定义                 第一章 绪论


  1.算法的定义和特性
   算法 (Algorithm ): 算法是解决某一特定任
    务的指令的有限序列。

   算法具有以下五个特性:
    (1)有穷性 一个算法必须总是在执行有穷步
    之后结束,且每一步都在有穷时间内完成。
    (2)确定性 算法中每一条指令必须有确切的
    含义。不存在二义性。且算法只有一个入口和
    一个出口。
§1.4 算法定义           第一章 绪论


 (3)可行性 一个算法是可行的。即算法描述的
  操作都是可以通过已经实现的基本运算执行有
  限次来实现的。
 (4)输入 一个算法有零个或多个输入,这些输
  入取自于某个特定的对象集合。
 (5)输出 一个算法有一个或多个输出,这些输
  出是同输入有特定关系的量。
§1.4 算法定义              第一章 绪论

2. 算法与数据结构的关系

   算法+数据结构=程序

    在计算机解决问题的过程中算法与数据结构是缺一不
  可的两个方面:
    问题的数据组织——数据结构
    数据的处理——算法

    算法与数据结构是相辅相承的。解决某一特定类型问
  题的算法可以选定不同的数据结构,而且选择恰当与否直
  接影响算法的效率。反之,一种数据结构的优劣由各种算
  法的执行来体现。
怎样才能设计一个好的算法呢?
 要设计一个好的算法通常要考虑以下的要求:
⑴正确: 算法的执行结果应当满足预先规定的功能和性能
 要求。
⑵可读: 一个算法应当思路清晰、层次分明、简单明了、
 易读易懂。
⑶健壮:当输入不合法数据时,应能作适当处理,不至引
 起严重后果。
⑷高效:有效使用存储空间和有较高的时间效率。
§1.5 算法的性能分析与度量             第一章 绪论
1. 5.1 算法的描述
 1. 自然语言描述:容易,但有时罗嗦、有二义性

 2. 图示(流程图、N-S图、PAD图等):直观清晰,但不易实现

 3. 程序设计语言:可以直接运行,但太严格。

 4. 算法语言(伪代码):严谨、简洁,易程序实现
   为了解决理解与执行这两者之间的矛盾,人们常常使用一
 种称为伪码语言的描述方法来进行算法描述。伪码语言介于高
 级程序设计语言和自然语言之间,它忽略高级程序设计语言中
 一些严格的语法规则与描述细节,因此它比程序设计语言更容
 易描述和被人理解,而比自然语言更接近程序设计语言。它虽
 然不能直接执行但很容易被转换成高级语言。
§1.5 算法的性能分析与度量          第一章 绪论

 如何评价算法的优劣呢?
   我们可以从一个算法的时间复杂度与空间复杂度来评价算
 法的优劣。

   一个算法转换成程序并在计算机上执行时,其运行所需要
 的时间取决于下列因素:


 ⑴硬件的速度。例如使用486机还是使用586机。
 ⑵书写程序的语言。实现语言的级别越高,其执行效率就越低。
 ⑶编译程序所生成目标代码的质量。对于代码优化较好的编译程
 序其所生成的程序质量较高。
 ⑷问题的规模。例如,求100以内的素数与求1000以内的素数其
 执行时间必然是不同的。
§1.5 算法的性能分析与度量        第一章 绪论

   显然,在各种因素都不能确定的情况下,很难比较出算法
 的执行时间。
   也就是说,使用执行算法的绝对时间来衡量算法的效率是
 不合适的。为此,可以将上述各种与计算机相关的软、硬件因
 素都确定下来,这样一个特定算法的运行工作量的大小就只依
 赖于问题的规模(通常用正整数n表示),或者说它是问题规
 模的函数。




   算法效率的度量分为:事前估计、后期测试。
§1.5 算法的性能分析与度量         第一章 绪论
 1.5.2 算法效率的度量

  先验估计(事前估计):根据算法的逻辑特征(基本操作的
             次数)来估算。

  后期测试(事后计算):选择样本数据、运行环境,运行算法
             计算出空间、时间。




 优点:可比性强         优点:精确
 缺点:不精确,仅仅是估计    缺点:可比性差,效率低

 那么,如何撇开计算机本身来估算一个算法的复杂性呢?
§1.5 算法的性能分析与度量              第一章 绪论

 算法复杂性的度量属于事前估计。分为:
   时间复杂度和空间复杂度的度量。
 时间复杂度:
   一个程序的时间复杂度(Time complexity)是指程序运行
 从开始到结束所需要的时间。

  一个算法是由控制结构和原操作构成的,其执行时间取决
于两者的综合效果。为了便于比较同一问题的不同的算法,通
常的做法是:从算法中选取一种对于所研究的问题来说是基本
运算的原操作,以该原操作重复执行的次数作为算法的时间度
量。一般情况下,算法中原操作重复执行的次数是规模n的某个
函数T(n)。(计算T(n)见p29)
  许多时候要精确地计算T(n)是困难的,我们引入渐进时间
复杂度在数量上估计一个算法的执行时间,也能够达到分析算
法的目的。
§1.5 算法的性能分析与度量            第一章 绪论
1.5.4 O表示的含义——算法的渐进分析

 1. 算法的渐进分析

    算法的渐进分析简称算法分析。通常将问题的规模作为
   分析的参数,求算法的时间开销与问题规模n的关系。



  当且仅当存在正整数c和n0,使得T(n)≤c*f(n),对所有
  的n≥n0成立,则称该算法的时间增长率在O(f(n))中,
  记为T(n)=O(f(n)) 。
§1.5 算法的性能分析与度量                        第一章 绪论

例:考察T(n)=3n+2。当n>=2时,3n+2<=3n+n=4n,所以
  T(n)=O(n)

例:考察T(n)=10n2+4n+2。当n>=2时, 10n2+4n+2 <= 10n2+5n,
  当n >=5时, 10n2+5n <= 11n2 ,所以, T(n)=O(n2)

例:考察T(n)=6*2n+n2。当n>=4时, n2<= 2n,有: 6*2n+n2<=7*2n
  所以, T(n)=O(2n)


   当我们评价一个算法的时间性能时,主要标准就是算法的
 渐进时间复杂度,因此,在算法分析时,往往对两者不予区分,
 经常将渐进时间复杂度T(n)=O(f(n))简称为时间复杂度。
§1.5 算法的性能分析与度量                  第一章 绪论
算法分析举例
 例1:分析下面程序段的时间复杂性
 (1)                 T(n)=O(n)
 i=1; k=0 ;          ◇ 这个函数是按线性阶递增的
 while(i<n)
  { k=k+10*i; i++; }


 (2)
 i=0; k=0;
 do{                    T(n)=O(n)
     k=k+10*i; i++; }   ◇ 这也是线性阶递增的
 while(i<n);
§1.5 算法的性能分析与度量                  第一章 绪论

 (3)
 for(i=1;i<=n; i ++)     T(n)=O(n2)
   for(j=1;j<=n; j ++)   ◇ 是平方阶递增的
       { ++x; s+=x; }


 (4)                     T(n)= O(n2)
 for(i=1;i<=n; i ++)     ◇ 这也是平方阶递增的
   for(j=1;j<=i; j ++)
      ++x;
§1.5 算法的性能分析与度量                第一章 绪论



 (5)                ◆ T(n)=O(1)
 x=91; y=100;       ◇ 这个程序看起来有点吓人,
 while(y>0)         总共循环运行了1000次,但是
  if(x>100)         我们看到n没有? 没。这段程序
    {x=x-10;y--;}   的运行是和n无关的,就算它再
  else x++;         循环一万年,我们也不管他,
                    只是一个常数阶的函数。
§1.5 算法的性能分析与度量                                第一章 绪论


 (6)                     ◆ T(n)=O(log2n)
 i=1;                    设其原操作执行的次数为t(n)
 while(i<=n)             于是,2t(n) n
   i=i*2;                从而有:t(n) log2n

  (7)
  s=0;                    ◆ T(n)=O(n3)
 for(i=0;i<=n;i++)            n   i   j
   for(j=0;j<=i;j++)      t(n)=             1=……
                              i=0 j=0 k=0
     for(k=0;k<=j;k++)
       s++;
§1.5 算法的性能分析与度量                          第一章 绪论
例2:分析下面函数的复杂性
  A是一个含有n个不同元素的实数数组,给出求A之最大和最小
元素的算法如下,分析它的时间复杂性。
  算法 SM(A,n,max,min)
  SM1 [初始化]
      max min a[1]                 1
  SM2 [比较]
      for(i=2;i<=n;i++)            n
       { if(a[i]>max) max a[i]     n-1
         if(a[i]<min) min a[i] }   n-1



       T(n)=3n-1
       T(n)=O(n)
§1.5 算法的性能分析与度量                        第一章 绪论

 2. 常见的算法时间复杂度:

 常见的时间复杂度,按数量级递增排列依次为:
     常数阶O(1)、对数阶O(log2n)、线性阶O(n)、线性对数阶
 O(nlog2n)、平方阶O(n2)、立方阶O(n3)、k次方阶O(nk)、指数阶
 O(2n)。


 3. 大O运算规则
   O(f(n))+O(g(n))=O(max(f(n),g(n)))
   O(f(n))+O(g(n))=O(f(n)+g(n))
   O(f(n)) •O(g(n))=O(f(n) •g(n))
   O(c•f(n))=O(f(n))
§1.5 算法的性能分析与度量             第一章 绪论

4. 平均时间复杂性
定义:设一个领域问题的输入规模为n,Dn是该领域问题的所有
输入的集合,任一输入I Dn,P(I)是I出现的概率, P(I)=1,
t(I)是算法在输入I下执行的基本运算次数,则平均时间复杂性
定义为:
         T(n)= {P(I)*t(I)}
           I Dn


最好时间复杂性、最坏时间复杂性、平均时间复杂性

对于有些算法,问题规模相同,如果输入集不同,其效率不同
 Tmin:最好情况不能代表算法的性能
 Tmax:最坏情况可以知道算法至少能达到的性能
 Tavg:较好地反映了算法的性能,但并不一定总是可行的!
§1.5 算法的性能分析与度量                 第一章 绪论

  例如:查找一个元素是否存在的算法
  int loc(int a[],int x)
  { a[0]=x;
    i=n;
    while(a[i]!=x)
       i=i-1;
    return i
  }


  最好:T ( n ) =c            O(1)
  最坏:T ( n ) =n+c          O( n )
  平均:T ( n ) =n/2+c        O( n )
§1.5 算法的性能分析与度量        第一章 绪论

5. 算法时间复杂性的实质:

 算法与问题规模及时间的关系。
 同一问题,规模相同,用不同的算法解决,花费时间是不同的;
 同一问题,用不同的算法解决,在相同的时间内所解决的问题
 规模大小不同;

 思考:―复杂性渐进阶比较低的算法比复杂性渐进阶比较高
     的算法有效‖,这种说法正确吗?
  另外需要注意:
  当两个算法的复杂性渐进阶相同时,必须进一步考察T(n)
的常数因子。
本章小结                第一章 绪论


1. 为什么要讨论数据结构(数据结构的重要性)

2. 数据结构的有关概念及它们之间的关系
   数据结构、逻辑结构、存储结构、数据类型、
  ADT等

3. 算法及算法分析基础
   算法的定义及特点、算法分析的方法(渐进时间
复杂度)
作业:
1.总结本章主要内容
2.自己找出或设计三个算法,并分析其时
  间复杂度。(要求三个算法的渐进时间
  复杂度不能相同)
第二章 线性表
内容提要:
  线性表是最简单、最基本、也是最常
用的一种线性结构。
  它有两种存储方法:顺序存储和链式
存储,它的主要基本操作是插入、删除和
检索等。
第二章 线性表
2.1 线性表

2.2 顺序表

2.3 单链表

2.4 线性链表的其它变形

2.5 单链表的应用:一元多项式及其运算
2.1 线性表 linear list
2.1.1 线性表的概念
 线性表是具有相同数据类型的n(n>=0)个数据元素的有限
序列,通常记为:
      L=(a1,a2,… ai-1,ai,ai+1,…an)

几个概念:
表中相邻元素之间存在着顺序关系。将 ai-1 称为 ai 的
直接前趋,ai+1 称为 ai 的直接后继。就是说:对于ai,
表名 数据元素(结点、表项) 表长 空表
当 i=2,...,n 时,有且仅有一个直接前趋 ai-1.,当
表头 表尾
i=1,2,...,n-1 时,有且仅有一个直接后继 ai+1,而
a1 是表中第一个元素,它没有前趋,an 是最后一个元素
无后继。
说明:

  ai为序号为 i 的数据元素(i=1,2,…,n),
通常我们将它的数据类型抽象为datatype。
datatype根据具体问题而定,如在学生情况信息
表中,它是用户自定义的学生类型。
线性表的抽象数据类型:
 抽象数据类型是一个(D,R,P)三元组,分析可得线性表的
抽象数据类型:
 ADT List {
 D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 }
                             ( i 为 ai 在线性表中的位序)
 R={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n }
 操作:
 初始化操作 Create() CopyList()
 结构销毁操作
 引用型操作Length()search()Locate()getData()Isempty()Isfull()
 加工型操作SetData()Insert()Remove()Sort()
 }
2.1.2 线性表的类定义
Enum bool {false, true}
class LinearList {
Public:
LinearList();
~LinearList();
virtual int Size() const =0;
virtual int Length() const =0;
virtual int Search(int &x) const =0;
virtual int Locate(int i) const =0;
virtual datetype *GetData(int i) const =0;
}                        (44页)
2.2 顺序表(Sequential List)
2.2.1 顺序表的定义和特点
定义 :把线性表中的所有表项按照其逻辑顺序依次存
  储到计算机存储器中指定存储位置开始的一块连续
  的存储空间中。
特点:
  ①逻辑顺序与物理顺序一致
  ②可顺序或随机访问表项
顺序表可以用C++的一维数组来实现。 C++的一维
数组可以是静态分配的,也可以是动态分配的。

数组的下标位置:
      0 1           i-2   i-1       n-1
      a1 a2   …     ai-1 ai     …   an

  所有数据元素的存储位置均取决于第一个数据元素

的存储位置:

   LOC(ai) = LOC(a1) + (i-1)×sizeof(T)
2.2.2顺序表(SeqList)类的定义
#include<iostream.h>
#include<stdlib.h>
typedef int T;
class SeqList {
    T *data;       //顺序表存储数组
    int MaxSize;         //是问题要求的元素数目的最大值
    int last;               //当前最后元素下标
public:
    SeqList ( int sz );
    ~SeqList ( ) { delete [ ] data; }
int Length ( ) const { return last+1; }   //返回元素的个数
int Find (T & x ) const; //返回元素x在表中的位置
void Insert (T & x, int i ); //在位置i插入元素x
int Remove (T & x );        //删除值为x的元素
int IsEmpty ( ) { return last ==-1; } //表空否
int IsFull ( ) { return last == MaxSize-1; }
T GetData ( int i ) {       //取第i个表项的值
    return data[i-1] };
void SetData ( int i, T & x) { //为第i个表项赋值
    if (i >0 && i <= last+1) data[i-1] = x ;}
void input();
void output();
 }
顺序表部分公共操作的实现:
①构造函数
SeqList :: SeqList ( int sz ) {
              //构造函数,通过指定sz,定义数组的长度
  if ( sz > 0 ) {
      data = new T[sz];
      if ( data != NULL ) {
         MaxSize = sz; last = -1;}
      else {cerr <<“存储分配错误!”<<endl;exit(1);}
  }
}                   设计思路:
                    若sz>0,则为数组申请空间:
                    ①若申请成功,MaxSize=sz,last=-1
                    ②若申请失败,则提示出错
(定位元素x的位置,返回值为x在
②搜索或定位:顺序表中的位置;返回值为-1表示
       不存在)
 data            MaxSize
 23 75 41 38 54 62 17
  ii   i     i   i     last   i

       i 1
         0
         7
         3
         2           基本操作是:
                     将顺序表中的元素
 x = 38 50           逐个和定值x相比较。
int SeqList::Find ( T & x ) const {
 //搜索函数:在表中从前向后顺序查找 x
    int i = 0;
    while (           i <= last && )
                                   data[i] != x
         i++;
    if ( i > last ) return -1;                    else
return i+1;
}

设计思路:
① x与data[i]逐个循环比较,直到x=data[i]或i>last
② 若i>last,则没有找到x,否则返回i+1
搜索成功:
           n 1
ACN =              pi   ci
           i   0
 若搜索概率相等,则
      1n1        1
ACN =     ( i 1)   (1 2  n)
      ni0        n
      1 (1 n) n 1 n
      n       2      2
 搜索不成功             数据比较 n 次
    算法时间复杂度: O(n)
③插入元素

分析:在i位置插入元素x时,线性表的逻辑结构发生
什么变化?
(a1, …, ai-1, ai, …, an) 改变为(a1, …, ai-1, x, ai, …, an)
     <ai-1, ai>              <ai-1, x>, <x, ai>

a1 a2 … ai-1 ai                     … an

a1 a2 … ai-1 x                      ai      … an
                              表的长度增加1
例如: x=66; Insert ( x, 4)
 Last ++;//当前最后元素下标加1
 for(j = Last ; j >i ; j --)
           data[j]=data[j-1];
   data[i]=x;//在i位置插入x j
                    j j
                j  i
    21 18 30 75 42 56 87
     0


    21 18 30 75 66 42 56 87
Insert ( const T& x, int i)
思路:
①若顺序表的长度等于MaxSize,则满(不能插入);
②若给定的位置小于0或大于长度,出错;
③否则,Last加1,把i及其后面的元素向后移动
 一个位置;
④把数据X插入到i位置
//在指定位置i插入一个数据元素x
void SeqList::Insert ( const T& x, int i)
{//i为下标,不是序号
     if(last == MaxSize-1)
      {
         cerr<<“顺序表已满无法插入!”<<endl;exit(1);
       }
    if(i<0‖i>last+1)
                   //当i等于Last+1时表示插入在最后
          { cerr<<"参数i越界出错!"<<endl; exit(1);
          }
//从后向前把前一个元素迁移到后一个元素位置
  直到存储位置为i为止
 last++;//当前最后元素下标加1
for(int j = Last ; j > i ; j --)
     data[j]=data[j-1];
 data[i]=x;//在第i项处插入x
}
考虑移动元素的平均情况:
  假设在第 i (0<=i<=n)个元素位置插入的
概率为 pi ,则在线性表中插入一个元素所需
移动元素次数的期望值为:
             n
     Eis           pi (n i )
             i 0
 若假定在线性表中任何一个位置上进行插入
的概率都是相等的,则移动元素的期望值为:
                       n
                   1               n
       Eis                 (n i)
              n 1i     0           2
④删除元素

分析:删除i处的元素时,线性表的逻辑结构发生
什么变化?
(a1, …, ai-1, ai, …, an) 改变为(a1, …, ai-1, ai+1, …, an)

<ai-1, ai>, <ai, ai+1>                    <ai-1, ai+1>

a1 a2 … ai-1 ai ai+1 … an
a1 a2 … ai-1
                          表的长度减少
删除值为x的元素
int SeqList :: Remove ( T & x ) {
   int i = Find (x)-1;            //在表中搜索 x
        if ( i >= 0 ) {
        last-- ;
       for ( int j = i; j <= last; j++ )
           data[j] = data[j+1];
      return 1; //成功删除
      }                        思路:
                               ①在顺序表中查找值为x的元素
   return 0; //表中没有 x ②若找到,last--,把x后的元
 }                             素向前移动,返回1,若找不到,
                               返回0。
⑤输入操作
void SeqList :: input(){//从键盘逐个输入数据建立顺序表
cout <<“开始建立顺序表,请输入表中元素个数”;
   while (1){
       cin >>last;
       if (last<=MaxSize-1) break;
 cout <<“元素个数有误,范围1~”<<MaxSize-1<<endl;
        }
   for (int i=0;i<=last;i++)
   {cout<<“请输入第”<<i+1<<“个元素:”<<endl;
    cin>>data [i];}
   cout<<“表建立完成!” <<endl;
}
⑥输出操作
void SeqList :: output(){//将顺序表全部元素输出到屏幕上
    cout <<“顺序表元素个数为:”<<Last+1<<endl;
    for (int i=0;i<=last;i++)
       cout<<i+1<< “:” <<data [i]<<endl;
 }
2.2.4 顺序表的应用
例1:编写一个程序向顺序表中插入5个整数值,然后以插
  入次序显示这5个数,最后删除这5个数。
typedef int T;
#include”SeqList.h”
void main(void)
{ SeqList myList(100);
    for(int i=0;i<5;i++) ∥插入5个整型元素
         myList.Insert(i+10,i);
    for(int i=0;i<5;i++)
         cout<<myList.GetData(i)<<“ ”;
    for(int i=0;i<5;i++)
         myList.Remove(i);
}                        程序输出为:
                         10 11 12 13 14
例2. 集合的“并”运算
利用两个线性表LA和LB分别表示两个集合A和B,
 现要求一个新的集合A=A∪B。
步骤:①取LB中的一个元素
    x=LB.GetData(i)
   ②在LA中找这个元素
    LA.Find(x)
   ③如果LA中没有这个元素则插入LA
    LA.Insert(x,++Last)
求集合的“并”集
void Union ( SeqList &LA, SeqList LB ) {
   int n = LA.Length ( );
   int m = LB.Length ( );
   for ( int i = 1; i <= m; i++ ) {
           T x = LB.GetData(i);
                        //在B中取一元素
         int k = LA.Find (x); //在A中搜索它
         if ( k == -1 )      //若未找到插入它
          { LA.Insert (x,n++); }
   }
 }
           时间复杂度:
        O(LA.Length()×LB.Length())
例3. 集合的“交”运算
利用两个线性表LA和LB分别表示两个集合A和B,
 现要求一个新的集合A=A∩B。
步骤:①依次取LA中的元素
    x=LA.GetData(i)
   ②在LB中找这个元素
    LB.Find(x)
   ③如果LB中没有这个元素则在LA中删除
    LA.Remove(x)
求“交”集

     void Intersection(SeqList &LA, SeqList LB ) {
    int n = LA.Length ( );
    int m = LB.Length ( );
    int i = 1;
    while ( i <= n ) {
       T x = LA.GetData (i);
                          //在LA中取一元素
           int k = LB.Find (x);   //在LB中搜索它
           if ( k == -1 ) { LA.Remove (x); n--; }
           else i++;         //未找到在LA中删除它
    }
}
例4. 已知线性表LA和线性表LB中的数据元
素按值非递减有序排列,现要求将LA和LB
归并为一个新的线性表LC,且LC中的元素
仍按值非递减有序排列。

 此问题的思路如下:
 ①从线性表LA,LB中依次取得每个数据元素:
 LA.GetData(i), LB.GetData(j)
 ②较小者插入LC
 ③把没有插完的线性表中的元素全部插入 LC
void MergeList(SeqList LA,SeqList LB,SeqList &LC)
 { int i=j=1;k=0;
    T xa,xb;
    int LALength=LA.Length();
    int LBLength=LB.Length();
    while((i<=LALength)&&(j<=LBLength)){
    xa=LA.GetData(i); xb=LB.GetData(j);
    if(xa<=xb) {LC.Insert(xa,k++);++i;}
    else {LC.Insert(xb,k++);++j;}
   }
while(i<=LALength){
    xa=LA.GetData(i++); LC.Insert(xa,k++);
    }
while(j<= LBLength){
    xb=LB.GetData(j++); LC.Insert(xb,k++);
    }
  }

                    时间复杂度为:
           O(LA.Length()+LB.Length())
2.3 单链表(Singly Linked List)

一、单链表的概念

二、结点类的定义与实现

三、单链表类的定义与实现
顺序表的优缺点:
优点:
  ①无需为表示结点间的逻辑关系增加额外的存储
 空间,存储利用率高。
  ②随机访问
缺点:
  ①在表中插入或删除元素时,平均移动一半的元
 素,运行效率很低。
  ②占用连续空间
  为了克服顺序表的缺点,采用链接方式存储线性
 表。链接方式存储的线性表称作链表。
2.3.1 单链表的概念
 用一组地址任意的存储单元存放线性表中的数据
元素。
元素(数据元素的映象)+ 指针(指示后继元素存储位置)
= 结点(node)
  单链表的一个存储结点包含两个部分:
        data   link
  构成的线性表为:
头指针     线性表为空表时,
       头指针
       头结点的指针域为空     空指针
 头结点
       a1   a2   … ...   an ^
 以线性表中第一个数据元素 的存储a1
地址作为线性表的地址,称作线性表的
头指针(first,head)。
  有时为了操作方便,在第一个结点
之前虚加一个“头结点”,以指向头结点的
指针为链表的头指针。
单链表的存储映像
2.3.2 单链表的类定义

  通常使用两个类:链表的结点类和链表类,协同
表示单链表。
 定义方式主要有两种: 复合方式、 嵌套方式
#include <iostream.h>
 #include <stdlib.h>
 class List; //前视定义,否则友元无法定义
 class LinkNode{        链表结点类的定义
     friend class List;
     private:
         LinkNode *link;
         int data;
public:
LinkNode (LinkNode *ptr = NULL)       {link=ptr;}
   LinkNode(const int & item, LinkNode *ptr = NULL)
       { data=item;link=ptr;}
   ~LinkNode(){};
};
单链表类的定义
 class List {
private:
     LinkNode *first; //指向头结点的头指针
public:
     List () { first = new LinkNode ();}
                              //带头结点构造函数
     List ( const int &x ) {
                            //不带头结点构造函数
     first = new LinkNode ( x ); }
~List (){makeEmpty(); delete first;}    //析构函数
void MakeEmpty ( );     //链表置空
int Length ( ) const;      //求链表长度
LinkNode * getHead() const {return first;}
LinkNode *Find ( int x );
LinkNode *Locate ( int i );
DataType GetData ( int i );
void SetData (int x,int i );
void Insert (int x, int i );
void Remove (int &x, int i );
 int IsEmpty()const{
    return(first->link==NULL? 1:0;}
  void input(DataType endTag);
 void output();
};
①删去链表中除表头结点外的所有其它结点
MakeEmpty ( )


思路:
①设指针q;
②当链表不为空(first的link不为NULL):
  q指向first的下一结点;
  把q结点从链表中摘除,删除q ;
void List :: MakeEmpty ( ) {
//删去链表中除表头结点外的所有其它结点
   LinkNode *q;
   while ( first→link != NULL ) {
      q = first→link;
      first→link = q→link;
      //将表头结点后第一个结点从链中摘下
      delete q;     //释放它
   }

};
②求单链表的长度:Length ( )

 思路:
①指针p指向first的下一结点,计数器为0;
②当p不为NULL:
  计数器+1;
  p指向下一结点;
③返回计数器的值。
求表长

int List::Length ( ) const {
LinkNode *p = first→link;
    //检测指针p指示第一个结点
    int count = 0;
    while ( p != NULL ) {    //逐个结点检测
        count++;        p = p→link;
    }
    return count;
}
③在表中搜索数据x的结点:Find ( int x )
LinkNode * List::Find ( int x ){
   LinkNode *p = first→link; //指针 p 指示第一个结点
    while ( p != NULL && p→data != x )
          p = p→link;
    return p;   // p 在搜索成功时返回找到的结点地址
                // p 在搜索不成功时返回空值
}        思路:①指针p指向first->link
                ②当链表没有结束且结点的数
         据不为x,
                    则p指向下一结点。
                ③返回p
④定位函数,返回表中第i个数据的地址
LinkNode * List::Locate ( int i ){
   if ( i < 0 ) return NULL;
    LinkNode *p = first;     int j = 0;
    while ( p != NULL && j < i )          // j = i 停
    { p = p→link; j++; }
    return p;
}               思路:①i<0,返回空
                        ② 指针p指向first ,j=0
                  ③当链表没有结束且结点的序号不为i,
                则p指向下一结点,j加1。
                        ④返回p
⑤取出链表中第i个元素的值
DataType List::GetData( int i ) {//提取第 i 个结点的值
    LinkNode *p = Locate( i ); // p 指向链表第 i 个结点
      return p->data;
}


              教材上的程序和功能不完全一致p63

           思路:①p指针定位到第i个元素
                  ② 返回p指针的数据域
⑥给链表中第i个元素赋值
void List::SetData(DataType x,int i ) {//给第 i 个结点赋值
    if ( i <= 0 ) return;
    LinkNode *p = Locate ( i ); // p 指向链表第 i 个结点
    if (p!=NULL) p->data=x;
}

             思路:① 若i<=0,返回
               ② p指针定位到第i个元素
               ③若p不为空,使指针的数据域为x
               ④返回
⑦将元素x插入到链表中第i个位置处
 Insert (DataType x , int i)

有序对 <ai-1, ai>改变为 <ai-1, x> 和<x, ai>


        ai-1           ai

                 x
void List::Insert (DataType x , int i)
{//在第i个结点处插入一个data域值为x的新结点
    LinkNode *p = Locate ( i-1);
     LinkNode * newNode = new LinkNode (x) ;
                             //构造新结点newNode
    newnode->link=p->link;
    p->link=newNode;     //新结点插入第i个结点前

                 p       ai-1              ai
}

                 newNode            x

    算法的时间复杂度为: O(Length())
⑧删除第i个结点并通过引用返回被删结点的data
 Remove (DataType &x , int i)


    有序对<ai-1, ai> 和 <ai, ai+1>
      改变为 <ai-1, ai+1>


       ai-1     ai       ai+1
void List::Remove (DataType &x, int i )
{
    LinkNode *p = Locate (i-1), *q;
    q = p->link;
    p->link = q->link;   //重新链接
    x = q->data;
    delete q;
}      p                 q
                ai-1         ai       ai+1


算法的时间复杂度为: O(Length())
⑨单链表的输入(前插法)
  自行设置一个输入数据的结束标志,用以
结束结点的输入。

设计思路:
①建立一头结点
②输入一结点的值
③当结点的值不为结束标志时,创建新结点,插
 入到头结点后,直到输入的值为结束标志。
⑨单链表的输入(前插法)
void List :: input (DataType endTag){
  LinkNode *newnode; DataType val;
  first=new LinkNode ();
  if (first==NULL) {cerr<<"存储分配错误"<<endl;exit(1);}
  cin>>val;
  while(val!=endTag) {
       newnode=new LinkNode (val);
       if (newnode==NULL)
              {cerr<<"存储分配错误"<<endl;exit(1);}
       newnode->link=first->link;
       first->link=newnode; cin>>val; }
}
⑩单链表的输出
void List ::output ( ) {//依次输出各结点的值
LinkNode *p=first->link;
while(p!=NULL) {
   cout<<p->data<<endl;
   p=p->link;
   }
}
2.4 线性链表的其它变形
             例如 n = 8 m = 3
2.4.1 循环链表                0
                  7
                      8 1         1
             6 7              2
2.4.2 双向链表
                  6           3 2
              5       5 4
                              3
                      4
2.4.1 循环链表
   对于单链表而言,最后一个结点的指针域是空指
针,如果将该链表头指针置入该指针域,则使得链表
头尾结点相连,就构成了单循环链表。



      a1   a2   … ... an
  和单链表的差别仅在于,判别链表中最后一个
结点的条件不再是“后继是否为空”,而是“后继是否
为头结点”。
   循环链表的示例

first   a0   a1     a2    an-1

   带表头结点的循环链表

first        a0     a1    an-1

                         (非空表)
first
             (空表)
循环链表类的定义

P67
  循环链表与单链表的操作实现,最主要的
不同就是扫描到链尾,遇到的不是NULL,而
是表头。
循环链表的搜索算法

first        31   48     15    57
搜索15              p      p     搜索成功
             p


first        31   48     15    57

        p    p    p      p     p
搜索25                          搜索不成功
                   113
循环链表的搜索算法
CircListNode * CircList::Find( DataType x )
{
//在链表中从头搜索其数据值为 x 的结点
    CircListNode * p = first->link;
    while ( p!= first && p->data != x )
       p = p->link;
    if ( p != first ) return p;  //搜索成功
       else return NULL;             //搜索不成功
}
 first               31      48   15    57
搜索15
              p
带尾指针的循环链表

    22    31   48   15   57
                              rear
     如果插入与删除仅在链表的两端发生,可
  采用带表尾指针的循环链表结构。
   在表尾插入: 时间复杂性 O(1)
   在表尾删除: 时间复杂性 O(n)
   在表头插入: 时间复杂性 O(1)
   在表头删除: 时间复杂性 O(1)

思考:如何改进使在表尾删除的时间复杂度也为O(1)?
                                     115
用循环链表求解约瑟夫问题
  约瑟夫问题:
  n 个人围成一个圆圈,首先第 1 个人从 1 开始
,一个人一个人顺时针报数, 报到第 m 个人,令
其出列。然后再从下一 个人开始,从 1 顺时针报
数,报到第 m 个人,再令其出列,…,如此下去,
直到圆圈中只剩一个人为止。此人即为优胜者。
   用不带表头结点的循环链表来组织。



             116
例如 n = 8 m = 3

             0                                 0                                 0
     7                                 7                                 7
         8 1              1                8 1              1                8 1              1
6 7                   2           6 7                   2           6 7                   2
     6                3 2              6                3 2              6                3 2
 5       5 4                       5       5 4                       5       5 4
                  3                                 3                                 3
         4                                 4                                 4
                              0                                 0                                 0
                  7                                 7                                 7
                          8 1          1                    8 1          1                    8 1         1
             6 7                   2           6 7                   2           6 7                  2
                  6                3 2              6                3 2              6               3 2
              5       5 4                       5           5 4                   5           5 4
                                  3                                 3                                 3
                          4                                 4                                 4
                                                    117
0                          0                        0
     7                        7                          7
         8 1         1            8 1           1            8 1         1
6 7              2       6 7                2       6 7              2
     6           3 2          6             3 2          6           3 2
 5       5 4              5       5 4                5       5 4
                 3                          3                        3
         4                        4                          4
                         n=8 m=3


算法讨论:
  目前指针指向8,如何把8从链表中摘除?

                                      118
求解Josephus问题的算法
#include <iostream.h>
#include ―CircList.h‖
void Josephus(CircList & Js, int n, int m) {
   CircLinkNode *p = Js.getHead();
                     *pre = NULL;
   int i, j;
   for ( i = 0; i < n-1; i++ ) {       //执行n-1次
      for ( j = 1; j < m; j++)         //数m-1个人
          { pre = p; p = p->link; }
      cout << ―出列的人是” << p->data << endl;

                        119
pre->link = p->link; delete p;     //删去
         p = pre->link;
     }
};
void main() {
   CircList clist;
   int i, n m;
   cout << ―输入游戏者人数和报数间隔 : ‖;
   cin >> n >> m;
   for (i = 1; i <= n; i++ ) clist.insert(i, i); //约瑟夫环
   Josephus(clist, n, m);             //解决约瑟夫问题
}
                             120
2.4.2 双向链表 (Doubly Linked
          List)

  双向链表是指在前驱和后继方向都能游历(
遍历)的线性链表。
  双向链表每个结点结构:

        lLink   data     rLink
      前驱方向             后继方向

     双向链表通常采用带表头结点的循环链表形
式。
                 121
first                             first

            非空表                   空表
    结点指向
     p == p->lLink->rLink == p->rLink->lLink

            rLink               lLink


        p->lLink      p         p->rLink
                          122
双向循环链表的搜索算法
DblNode *DblList::Find (DataType x, int d) {
//在双向循环链表中寻找其值等于x的结点, //按d确定
  搜索方向,d为0,向左搜索, d为1,向右搜索

     DblNode *p= (d == 0)?first->lLink : first->rLink;
      while ( p != first && p->data != x         )
             p = (d == 0) ? p->lLink : p->rLink;
     if ( p != first ) return p;            //搜索成功
     else return NULL;                     //搜索失败
};

                           123
双向循环链表的插入算法
first         31       48   15

后插入25                   p

first         31       48   25   15

                   p         newNode
   newNode->rLink = p->rLink;
   p->rLink = newNode;
   newNode->rLink->lLink = newNode;
   newNode->lLink = p;
双向循环链表的删除算法
first             31     48     15    非空表

删除48                      p


        p->rLink->lLink = p->lLink;

        p->lLink->rLink = p->rLink;


                          125
2.5      单链表的应用
          多项式
(Polynomial)
    Pn ( x ) a0 a1 x a2 x 2  an x n
            n
                        i
                 ai x
           i 0

    n阶多项式 Pn(x) 有 n+1 项。
   系数 a0, a1, a2, …, an
   指数 0, 1, 2, …, n。按升幂排列
                            126
2.5.1 多项式的存储表示
  第一种: 静态数组表示
  在类的私有域中定义多项式的数据成员:
  private:
   int degree;
   float coef [maxDegree+1];
        则Pn(x)可以表示为:
        pl.degree = n, pl.coef[i] = ai, 0   i   n
       0    1    2             degree   maxDegree
coef   a0 a1 a2       …… an             ………
                                   n
                         127
第二种:动态数组表示

在类的私有域中定义多项式的数据成员:
private:
   int degree;
   float * coef;
      Polynomial :: Polynomial (int sz) {
          degree = sz;
          coef = new float [degree + 1];
      }
  以上两种存储表示适用于指数连续排列的
多项式。但对于多数项的系数为零的稀疏多项
式,如 P101(x) = 3+5x50128 101, 空间利用率太低。
                     -4x
第三种:只存储非零系数项的系数和指数
       0   1   2          i          m
coef   a0 a1 a2 ……        ai    ……   am
exp    e0 e1 e2 ……        ei    ……   em

struct term {       //多项式的项定义
          float coef;    //系数
          int exp;       //指数
};
static term termArray[maxTerms]; //项数组
static int free, maxTerms;  //当前空闲位置指针
                   129
两个多项式存储的例子
  A(x) = 2.0x1000+1.8
  B(x) = 1.2 + 51.3x50 + 3.7x101
   A.start A.finish B.start     B.finish free
                                            maxTerms

coef   1.8 2.0 1.2 51.3 3.7               ……
exp    0 1000 0     50 101                ……

       两个多项式存放在termArray中
                          131
第四种:多项式的链表存储表示
   多项式顺序存储表示的缺点:
     插入和删除时项数可能有较大变化,因此

      要移动大量数据
     不利于多个多项式的同时处理

   采用多项式的链表表示可以克服这类困难:
     多项式的项数可以动态地增长,不存在存

      储溢出问题。
     插入、删除斱便,不移动元素。




             132
多项式的链表结构
   在多项式的链表表示中,每个结点三个数据
    成员:
        coef   exp     link


   通过链接指针,可以将多项式各项按指数递
    增的顺序链接成一个单链表。
   在此结构上,新项的加入和废项的删除执行
    简单的链表插入和删除操作即可解决。

                 133
2.6 静态链表
   处理时中可以不改变各元素的物理位置,只
    要重新链接就能改变这些元素的逻辑顺序。
   它是利用数组定义的,在整个运算过程中存
    储空间的大小不会变化。
   静态链表每个结点由两个数据成员构成:
    data域存储数据,link域存放链接指针。
   所有结点形成一个结点数组。



              155
2.1 线性表

2.2 顺序表

2.3 单链表

2.4 线性链表的其它变形

2.5 单链表的应用:一元多项式及其运
算

作业:(p84)6,20,21,27,28
第三章 栈与队列
•   栈
•   队列
•   栈的应用:表达式求值
•   栈的应用:递归
•   队列的应用:打印杨辉三角形
•   优先级队列
         157
3.1 栈 ( Stack )
3.1.1 栈的定义
     只允许在一端插入和删除的线性表。 退栈             进栈
     允许插入和删除的一端称为栈顶
      (top),另一端称为栈底(bottom) top
                                an-1
•   特点
                                an-2
    后进先出 (LIFO)

                                    a0
                           bottom
                    158
栈的抽象数据类型
class Stack {                         //栈的类定义
  public:
   Stack(){ };                           //构造函数
   ~Stack(){ };
   virtual void Push(DataType & x) ;          //进栈
   virtual bool Pop(DataType& x);             //出栈
   virtual bool getTop(DataType & x);      //取栈顶
   virtual bool IsEmpty();                 //判栈空
   virtual bool IsFull();                  //判栈满
}; 栈的抽象数据类型有两种典型的存储表示,基
 于数组的存储表示实现的栈称为顺序栈,基于链表
 的存储表示实现的栈称为链式栈。
            159
栈的数组存储表示 — 顺序栈
           0 1   2 3 4 5 6 7 8 9         maxSize-1
elements

       top (栈空)

   typedef int DataType;
   class SeqStack {              //顺序栈类定义
   private:
   DataType *elements;           //栈元素存放数组
      int top;                         //栈顶指针
      int maxSize;                   //栈最大容量
                           160
void overflowProcess();            //栈的溢出处理
public:
   SeqStack(int sz =50);                      //构造函数
   ~SeqStack() { delete []elements; }       //析构函数
   void Push(DataType &x);                        //进栈
   bool Pop(DataType& x);                         //出栈
   bool getTop(DataType& x);              //取栈顶内容
   bool IsEmpty() const { return top == -1; }
   bool IsFull() const { return top == maxSize-1; }
   int getSize() const {return top+1;}
   void MakeEmpty(){top=-1;}
friend ostream&operator<<(ostream&os,SeqStack &S)
};
                          161
top     b
             top      a            a
top   空栈            a 进栈         b 进栈

top     e    top      e            e
        d             d   top      d
        c             c            c
        b             b            b
        a             a            a
      e 进栈         f 进栈溢出        e 退栈
                     162
d
top   c            c
      b    top     b            b
      a            a    top     a
    d 退栈         c 退栈         b 退栈




      a
top a 退栈   top   空栈
                  163
①顺序栈的构造函数
#include <assert.h>
#include <iostream.h>
SeqStack:: SeqStack(int sz){
    elements=new DataType[sz];
    assert(elements!=NULL);
     top=-1;
    maxSize=sz;
}
      断言(assert)机制是C++提供的一种功
  能,若参数表中给定的条件满足,则继续执行
②顺序栈的溢出处理
void SeqStack::overflowProcess() {
//私有函数:当栈满则执行扩充栈存储空间处理
   DataType *newArray = new DataType [2*maxSize];
                           //创建更大的存储数组
   for (int i = 0; i <= top; i++)
      newArray[i] = elements[i];
   maxSize += maxSize;
   delete [ ]elements;
   elements = newArray;           //改变elements指针
};       思路:①创建更大的存储数组
                   ②把原来的元素复制到新数组中
                   ③改变栈大小,删除原来的数组,
                      栈指针指向新数组
                            165
③入栈操作
void SeqStack::Push(DataType & x) {
//若栈满,则溢出处理,将元素x插入该栈栈顶
   if (IsFull() == true) overflowProcess( );  //栈满
   elements[++top] = x;          //栈顶指针先加1, 再进栈
};
④出栈操作
bool SeqStack::Pop(DataType & x) {
//若栈不空,函数退出栈顶元素并将栈顶元素的值赋给x,
//返回true,否则返回false
   if (IsEmpty() == true) return false;
   x = elements[top--];         //先取元素,栈顶指针退1
   return true;                        //退栈成功
};
                       166
⑤取栈顶元素
bool SeqStack::getTop(DataType& x) {
//若栈不空则x为该栈栈顶元素
    if (IsEmpty() == true) return false;
    x = elements[top];
    return true;
};
⑥输出栈中元素的重载操作<<
ostream& operator<<(ostream&os,SeqStack &S){
   os<<―top=―<<S.top<<endl;
   for (int i=0;i<=S.top;i++)
         os<<i<<―:‖<<S. elements[i] <<endl;
   return os;
                            167
};
双栈共享一个栈空间
    0                                maxSize-1
V

b[0]                t[0]     t[1]            b[1]
两个栈共享一个数组空间V[maxSize]
设立栈顶指针数组 t[2] 和栈底指针数组 b[2]
 t[i]和b[i]分别指示第 i 个栈的栈顶与栈底
初始 t[0] = b[0] = -1, t[1] = b[1] = maxSize
栈满条件:t[0]+1 == t[1]            //栈顶指针相遇
栈空条件:t[0] = b[0]或t[1] = b[1] //退到栈底
                       168
3.1.3 栈的链接存储表示 — 链式
栈
top                   ^


 •   链式栈无栈满问题,空间可扩充
 •   插入与删除仅在栈顶处执行
 •   链式栈的栈顶在链头
 •   适合于多栈操作


             171
链式栈 (LinkedStack)类的定义
typedef int DataType;
struct StackNode {                   //栈结点类定义
public:
   DataType data;                       //栈结点数据
   StackNode *link;                     //结点链指针

     StackNode(DataType d = 0, StackNode *next = NULL)
        : data(d), link(next) { }
     ~StackNode();
};

                           172
class LinkedStack : { //链式栈类定义
private:
   StackNode *top;             //栈顶指针

public:
  LinkedStack() : top(NULL) {}      //构造函数
  ~LinkedStack() { makeEmpty(); }   //析构函数
  void Push(DataType &x);           //进栈
  bool Pop(DataType & x);                //退栈



                         173
bool getTop(DataType & x) const; //取栈顶 元素
     bool IsEmpty() const {
            return (top == NULL)? true:false; }
     int getSize()const;
     void makeEmpty();                 //清空栈的内容
     friend ostream& operator << (ostream& os,
        LinkedStack& s) ; //输出栈元素的重载操作 <<
};




                       174
链式栈类操作的实现
#include <iostream.h>
①清空栈操作
void LinkedStack::makeEmpty( ) {
//逐次删去链式栈中的元素直至栈顶指针为空。
   StackNode *p;
   while (top != NULL) {         //逐个结点释放
           p = top;
           top = top->link;
           delete p; }
};
 top                                ^
                  175
②入栈操作
void LinkedStack::Push(DataType &x) {
//将元素值x插入到链式栈的栈顶,即链头。
   top = new StackNode(x, top);        //创建新结点
   assert (top != NULL);               //创建失败退出
};              top x top                         ^
③出栈操作
bool LinkedStack::Pop(DataType & x) {
//删除栈顶结点, 返回被删栈顶元素的值。
   if (IsEmpty() == true) return false;     //栈空返回
   StackNode *p = top;                   //暂存栈顶元素
   top = top->link;                 //退栈顶指针
   x = p->data; delete p;                    //释放结点
   return true;           176
};
④取栈顶元素
bool LinkedStack::getTop(DataType & x) const {
     if (IsEmpty() == true) return false; //栈空返回
     x = top->data;               //返回栈顶元素的值
     return true;
    }; top
⑤                                                   ^
ostream& operator << (ostream& os, LinkedStack& S){
// //输出栈元素的重载操作 <<
    os<<“栈中元素个数=”<<S.getSize()<<endl;
     StackNode *p=S.top;int i=0;
    while(p!=NULL)
          {os<<++i<<“:”<<p->data<<endl; p=p->link;}
    return os;                177
};
公共邮箱:
rj09123@163.com
  密码:098765
关于栈的进一步讨论

  问题:当进栈元素的编号为1, 2, …, n时,
可能的出栈序列有多少种?




            179
•    设进栈元素数为n,可能出栈序列数为mn:
     n = 0,m0 = 1: 出栈序列{}。
     n = 1,m1 = 1: 出栈序列{1}。
     n = 2,m2 = 2:= m0*m1+m1*m0
      a) 出栈序列中1在第1位。1进 1出 2进 2出,
           出栈序列为{1, 2}。= m0*m1= 1
      b) 出栈序列中1在第2位。1进 2进 2出 1出,
           出栈序列为{2, 1}。= m1*m0 = 1
     n = 3,m3 = 5: = m0*m2+m1*m1+m2*m0
      a) 出栈序列中1在第1位。后面2个元素有m2 =
         2个出栈序列:{1, 2, 3}, {1, 3, 2}。

                    180
= m0*m2 = 2
   出栈序列中1在第2位。1前有2后有3,
     出栈序列为 {2, 1, 3}。= m1*m1 = 1
   出栈序列中1在第3位。前面2个元素有
     m2 = 2个出栈序列:{2, 3, 1}, {3, 2, 1}。
         = m2*m0 = 2
 n = 4,m4 = 14:
    = m0*m3+m1*m2+m2*m1+m3*m0
  a) 出栈序列中1在第1位。后面3个元素有
     m3 = 5个出栈序列: {1, 2, 3, 4}, {1, 2, 4,
     3}, {1, 3, 2, 4}, {1, 3, 4, 2}, {1, 4, 3, 2}。
                      181
= m0*m3 = 5
b) 出栈序列中1在第2位。前面有2,后面3、
   4有m2 = 2个出栈序列: {2, 1, 3, 4}, {2, 1,
   4, 3}。 = m1*m2 = 2
c) 出栈序列中1在第3位。前面2、3有m2 =
   2个出栈序列,后面有4: {3, 2,1, 4}, {2, 3,
   1,4}。 = m2*m1 = 2
d) 出栈序列中1在第4位。前面3个元素有
   m3 = 5个出栈序列:{2, 3, 4, 1}, {2, 4, 3,
   1}, {3, 2, 4, 1}, {3, 4, 2, 1}, {4, 3, 2, 1}。
        = m3*m0 = 5
                  182
 一般地,设有 n 个元素按序号1, 2, …, n
  进栈,轮流让 1在出栈序列的第1, 第2, …
  第n位,则可能的出栈序列数为:
  n -1
         mi * mn   i 1   m0 * mn    1   m1 * m n   2    mn 1 * m0
  i 0


 推导结果为:
         n 1
                                1        n

                               n 1 C2n
               mi * mn   i 1
         i 0




                                                               183
3.1.4 栈的应用--数制转换


    算法基于原理:
 N = (N div d)×d + N mod d
例如:(1348)10 = (2504)8 ,其
   运算过程如下:

     N N div 8 N mod 8
 计   1348 168    4         输
 算    168  21    0         出
 顺                         顺
 序      21   2   5         序
         2   0   2
思路:
①初始化栈
②输入要转换的数据N
③当N不为0,把N%8取余入栈
④当栈不为空,栈顶元素出栈,输出。
void conversion ()
{
SeqStack S; int N; int x=0; bool continue1;
cin >>N;
   while (N) {
     S.Push( N % 8);
     N = N/8;
     }
   continue1=S.Pop(x);
   while (continue) {
      cout << x <<endl; continue=S.Pop(x);
      }
} // conversion
3.1.5 栈的应用--括号匹配的检验
假设在表达式中
([]())或[([ ][ ])]等为正确的格式,
[( ])或([( ))或 (()])为不正确的格式。
则 检验括号是否匹配的方法可用“期待的急迫程度”这
个概念来描述。
   例如:考虑下列括号序列: [ ( [ ] [ ] )]
  分析可能出现的不匹配的情况:

• 到来的右括弧并非是所“期待”的;
• 直到结束,也没有到来所“期待”的括弧。
算法的设计思路:
①左括号入栈
②右括号,检验栈空?
 若空,表明右括号多了
              匹配,栈顶的左括号出栈
 非空,与栈顶元素比较
              不匹配,出错


③表达式检验结束时,若空,则匹配,若非
 空,则表明左括号多了。
bool matching(char *exp) {
 int state = 1; i = 0;L = Length(exp);char x;
 while (i<L && state) {
    switch (exp[i] ){
     case ‗(‗,‘[‗:{S.Push(exp[i]); i++; break;}
     case‟)‘: {
       if(!S.StackEmpty()&&S.GetTop()==‗(‗ )
         {S.Pop(char &x); i++;}
       else {state = 0;}
       break; }
     case‘]‘:{自己写出}
 }
 if (S.StackEmpty()&&state) return true
else return false
3.1.6 栈的应用举例--表达式求值
1. 表达式的三种表示方法:
设 Exp = S1 + OP + S2
则称 OP + S1 + S2        为前缀表示法
     S1 + OP + S2   为中缀表示法
     S1 + S2 + OP   为后缀表示法
例如: Exp = a b + (c d / e) f
 前缀式:      + ab     c/def
 中缀式:      a b + (c d / e) f
 后缀式:      ab cde/ f +
结论:
 1)操作数之间的相对次序不变;
 2)运算符的相对次序不同;
 3)中缀式有操作符的优先级问题,还有可加括号改
 变运算顺序的问题,所以编译程序一般不使用中缀表
 示处理表达式。
例如: Exp = a b + (c d / e) f
 前缀式:      + ab     c/def
 中缀式:      a b + (c d / e) f
 后缀式:      ab cde/ f +
结论:
4)前缀式的运算规则为:
   连续出现的两个操作数和在它们之前且紧靠它们
的运算符构成一个最小表达式;
5)后缀式的运算规则为:
   运算符在式中出现的顺序恰为表达式的运算顺序;
每个运算符和在它之前出现且紧靠它的两个操作数构成
一个最小表达式。
2.如何从后缀式求值?
先找运算符, 再找操作数
例如:
      ab      cde/   f    +
a b                      d/e
      c-d/e
                               (c-d/e) f
模拟一个简单的后缀表达式计算器
Calculator类的定义:
class Calculator{
public:
       Calculator(int sz){S=sz;};
       void Run();                  //执行表达式计算
       void Clear();
private:
       SeqStack S;
       void AddOperand(double value); //进操作数栈
       bool Get2Operands(double&left, double&right);
       void DoOperator(char op);//形成运算指令,计算
  };
①将操作数的值value入操作数栈
void Calculator:: AddOperand(double value){
      S.Push(value);
};

②清栈
void Calculator:: Clear() {
      S.MakeEmpty();
};
③从操作数栈中取出两个操作数
bool Calculator::Get2Operands(double&left,
   double&right){
   if (S.IsEmpty())
        {cerr<<“缺少右操作数!”<<endl;return false;}
   S.Pop(right);
   if (S.IsEmpty())
        {cerr<<“缺少左操作数!”<<endl;return false;}
   S.Pop(left);
   return true;
};
④读字符串并求后缀表达式的值
void Calculator::Run{
      char ch; double newoperand;
      while(cin.get(ch),ch!=„#‟){
        switch(ch){
            case „+‟:case „-‟: case „*‟: case „/‟:
                  DoOperator(ch); break;
            default:cin.putback(ch);
                  cin>>newOperand;
                  AddOperand(newOperand); }
          }

       S.Pop(newoperand ); cout<< newoperand;
  };
⑤取两个操作数,根据操作符op计算
void Calculator:: DoOperator(char op){
      double left,right,value; bool result;
      result = get2Operands(left, right)
      if (result)
         switch(op){
         case „+‟:value=left+right;S.Push(value);break;
         case „-‟: value=left-right;S.Push(value);break;
          case „*‟: value=left*right;S.Push(value);break;
case „/‟: if (right==0.0){
                   cerr<<“Divide by 0!”<<endl; Clear();}
                   else{value=left/right;S.Push(value);}
                   break;
                 }
    else Clear();
  };
void main(){
      Calculator CALC(20);
      CALC.Run();
}
3.如何将中缀表示→转后缀表示?
•   先对中缀表达式按运算优先次序加上括号,再
    把操作符后移到右括号的后面并以就近移动为
    原则,最后将所有括号消去。
•   如中缀表示 (A+B)*D-E/(F+A*D)+C,其转换为
    后缀表达式的过程如下:
    ( (( (A+ B ) * D ) – ( E / ( F+ (A* D ) )) ) + C )


    后缀表示 A B + D * E F A D * + / - C +
                           201
如何将中缀表示→转前缀表示?
•   先对中缀表达式按运算优先次序通统加上括
    号,再把操作符前移到左括号前并以就近移
    动为原则,最后将所有括号消去。
•   如中缀表示 (A+B)*D-E/(F+A*D)+C,其转
    换为前缀表达式的过程如下:
( ( ( (A+ B ) * D ) – ( E / ( F+ (A* D ) ) ) ) + C )


前缀表示 + - * + A B D / E + F * A D C
                        202
计算机如何将中缀表示转换为后缀表示?
    使用栈可将表达式的中缀表示转换成它的前缀表


                     栈
•

    示和后缀表示。
•   为了实现这种转换,需要考虑各操作符的优先级。

          各个算术操作符的优先级
        操作符 ch #     ( *, /, % +, -   )
        isp (栈内) 0   1    5     3     6
        icp (栈外) 0   6    4     2     1
•   isp叫做栈内(in stack priority)优先数
•   icp叫做栈外(in coming 203
                       priority)优先数。
栈顶算符为θ1:

θ 1       θ 2   +   -   *   /   (   )   #
      +         >   >   <   <   <   >   >
      -         >   >   <   <   <   >   >
      *         >   >   >   >   <   >   >
      /         >   >   >   >   <   >   >
      (         <   <   <   <   <   =   X
      )         >   >   >   >   X   >   >
      #         <   <   <   <   <   X   =
中缀表达式转换为后缀表达式的算法
•   例:中缀表达式为A+B*(C-D)-E/F,求其后缀表
    达式。
•   ①操作符栈初始化,将结束符‘#‟进栈。然后读
    入中缀表达式字符流的首字符ch。
•   ②重复执行以下步骤,直到ch = „#‟,同时栈
    顶的操作符也是‘#‟,停止循环。




                 205
a.若ch是操作数直接输出,读入下一个字符ch。
     b.若ch是操作符,判断ch的优先级icp和位于栈顶的
       操作符op的优先级isp:
      若 icp(ch) > isp(op),令ch进栈,读入下一个字符

       ch。
      若 icp(ch) < isp(op),退栈并输出。

      若 icp(ch) == isp(op),退栈但不输出,若退出的

       是“(”号读入下一个字符ch。
  • ③算法结束,输出序列即为所需的后缀表达式。
  • 例:中缀表达式为A+B*(C-D)-E/F,求其后缀表达式。
操作符 ch # ( *, /, % +, - )          ABCD-*+EF/-
isp (栈内) 0 1            5      3 6
icp (栈外) 0 6            4
                           206
                               2 1
中缀表达式转换为后缀表达式的算法:
void postfix(expression e) //把中缀表达式e转换成后缀表示并输出
   Stack S; char ch=„#‟,ch1,op;
   S.Push(ch);cin.get(ch);
   While(S.IsEmpty()==false&&ch!=„#‟)
        if (isdigit(ch)){cout <<ch;cin.get(ch);}
        else {S.getTop(ch1);
              if (isp(ch1)<icp(ch)){S.Push(ch);cin.get(ch);}
          else if (isp(ch1)>icp(ch)) {S.Pop(op);cout<<op;}
                 else { S.Pop(op);
                        if(op==„(„) cin.get(ch); }
      }
                                 207
};
4.如何应用中缀表示计算表达式的值
        a+b*(c-d)-e/f
                  rst1     rst4
               rst2
             rst3
                    rst5
   使用两个栈,操作符栈OPTR (operator),
    操作数栈OPND(operand)
   为了实现这种计算,需要考虑各操作符的
    优先级
                   208
中缀算术表达式求值
   对中缀表达式求值的一般规则:
    1. 建立并初始化OPTR栈和OPND栈,然后
       在OPTR栈中压入一个“#”
    2. 扫描中缀表达式,取一字符送入ch。
    3. 当ch != „#‟ 或OPTR栈的栈顶 != „#‟时, 执
       行以下工作, 否则结束算法。在OPND栈
       的栈顶得到运算结果。



                  209
①若ch是操作数,进OPND栈,从中缀表达式取下一字符
送入ch;
②若ch是操作符,比较icp(ch)的优先级和isp(OPTR)的优
先级:
若icp(ch) > isp(OPTR),则ch进OPTR栈,从中缀表达式
取下一字符送入ch;
若icp(ch) < isp(OPTR),则从OPND栈退出a2和a1,从
OPTR栈退出θ, 形成运算指令 (a1)θ(a2),结果进OPND栈;
 若icp(ch) == isp(OPTR) 且ch == ')',则从OPTR栈退出
'(',对消括号,然后从中缀表达式取下一字符送入ch;

                      操作符 ch      #   ( * , / , %+, -   )
    a+b * (c-d)-e/f   i sp( 栈内)   0   1    5      3     6
                      i cp( 栈外)
                            210
                                  0   6    4      2     1
void InFixRun() {
 SeqStack <char> OPTR, SeqStack <double>OPND;
 char ch, op; double x;
 OPTR.Push(„#‟);
 cin.get (ch);                     //读入一个字符
 op = '#' ;
 while (ch != '#' || op != '#') {
    if (isdigit(ch))               //是操作数, 进栈
       { cin.putback(ch);cin>>x
         OPND.Push(x); cin.get(ch); }
    else {                     //是操作符, 比较优先级
       OPTR.GetTop(op);              //读一个操作符

                      211
if (icp(ch) > isp(op))     //栈顶优先级低
             { OPTR.Push (ch); cin.get(ch); }
          else if (icp(ch) < isp(op)) {
             OPTR.Pop(op);           //栈顶优先级高
             DoOperator(op);
          }
          else if (ch == „)‟)      //优先级相等
             { OPTR.Pop(op); cin.get(ch); }
      }
      OPTR.GetTop(op);
    } /*end of while*/
}
                         212
3.2 栈与递归
•   递归的定义
    若一个对象部分地包含它自己,或用它自己
    给自己定义, 则称这个对象是递归的;若一
    个过程直接地或间接地调用自己, 则称这个
    过程是递归的过程。
•   以下三种情况常常用到递归方法。
     定义是递归的

     数据结构是递归的

     问题的解法是递归的


               213
1. 定义是递归的
例如,阶乘函数
            1,     当n 0时
    n!
         n (n 1)!, 当 n 1时

求解阶乘函数的递归算法
long Factorial(long n) {
  if (n == 0) return 1;
  else return n*Factorial(n-1);
}
                     214
求解阶乘 n! 的过程
           主程序 main : fact(4)

        参数 4   计算 4*fact(3)   返回 24
递   参                                 结   回
归   数   参数 3   计算 3*fact(2)   返回 6    果   归
调   传                                 返   求
用   递   参数 2   计算 2*fact(1)   返回 2    回   值

        参数 1   计算 1*fact(0)   返回 1

        参数 0   直接定值 = 1       返回 1
                     215
2.数据结构是递归的
•   例如,单链表结构
     f

     f
•   一个结点,它的指针域为NULL,是一个单
    链表;
•   一个结点,它的指针域指向单链表,仍是一
    个单链表。

               216
搜索链表最后一个结点并打印其数值
void Print(ListNode *f) {
  if (f ->link == NULL)
      cout << f ->data << endl;
  else Print(f ->link);
}

                递归找链尾
  f    a0       a1       a2       a3       a4
            f        f        f        f
                          217
在链表中寻找等于给定值的结点并打印其数值
void Print(ListNode*f, DataType value) {
   if (f != NULL)
      if (f -> data == value)
          cout << f -> data << endl;
      else Print(f -> link, value);
}
               递归找含value值的结点
    f                           x
           f      f         f
                      218
3. 问题的解法是递归的:汉诺塔问题




         219
3. 问题的解法是递归的
•   汉诺塔(Tower of Hanoi)问题的解法:
    如果 n = 1,则将这一个盘子直接从 A 柱移
    到 C 柱上。否则,执行以下三步:
    ① 用 C 柱做过渡,将 A 柱上的 (n-1) 个盘
      子移到 B 柱上;
    ② 将 A 柱上最后一个盘子直接移到 C 柱上;
    ③ 用 A 柱做过渡,将 B 柱上的 (n-1) 个盘
      子移到 C 柱上。

               220
• 解决方法:
   n=1时,直接把圆盘从A移到C
 – n>1时,
 – ①先把上面n-1个圆盘从A移到B
   ②然后将n号盘从A移到C
 – ③再将n-1个盘从B移到C。
 – 即把求解n个圆盘的Hanoi问题转化为求解n-1个圆盘的
   Hanoi问题,依次类推,直至转化成只有一个圆盘的Hanoi
   问题
   – 执行情况:
      » 递归工作栈保存内容:形参n,x,y,z和返回地址
      » 返回地址用行编号表示
                    n x   y   z   返回地址
main()
 { int m;
   printf("Input number of disks:‖);

     scanf("%d",&m);
     hanoi(m,'A','B','C');
(0) }
void hanoi(int n,char x,char y,char z)
(1) {
(2) if(n==1)
(3)      move(1,x,z);
(4) else{
(5)        hanoi(n-1,x,z,y);
(6)        move(n,x,z);
(7)        hanoi(n-1,y,x,z);
(8)      }
(9) }
构成递归的条件
•   不能无限制地调用本身,必须有一个出口,
    化简为非递归状况直接处理。
     Procedure <name> ( <parameter list> ) {
      if ( < initial condition> ) //递归结束条件
        return ( initial value );
      else                        //递归
        return (<name> ( parameter exchange ));
    }

                       223
3.2.2递归过程与递归工作栈
•   递归过程在实现时,需要自己调用自己。
•   层层向下递归,退出时的次序正好相反:
              递归调用
    n! (n-1)!   (n-2)! 1! 0!=1
              返回次序
•   主程序第一次调用递归过程为外部调用;
•   递归过程每次递归调用自己为内部调用。
•   它们返回调用它的过程的地址不同。
                224
long Factorial(long n) {
            int temp;
            if (n == 0) return 1;
            else temp = n * Factorial(n-1);
RetLoc2
              return temp;
          }
        void main() {
          int result;
          result = Factorial(4);
RetLoc1        cout << result <<endl;

          }            225
1. 递归工作栈
•   每一次递归调用时,需要为过程中使用的参
    数、局部变量等另外分配存储空间。
•   每层递归调用需分配的空间形成递归工作记
    录,按后进先出的栈组织。
•   栈顶的工作记录是当前正在执行的这一层的
    工作记录。称之为活动记录。

     活动   局部变量
                     递归
     记录   返回地址      工作记录
     框架   参 226 数
函数递归时的活动记彔
调用块   ……………….
      <下一条指令>

      Function(<参数表>)
函数块      ……………….
           <return>

返回地址(下一条指令) 局部变量 参数
         227
2. 递归过程改为非递归过程
•   递归过程简洁、易编、易懂
•   递归过程效率低,重复计算多
•   改为非递归过程的目的是提高效率
•   单向递归和尾递归可直接用迭代实现其
    非递归过程
•   其他情形必须借助栈实现非递归过程



            229
斐波那契数列的函数Fib(n)的定义
               n,         n 0,1
Fib(n)
       Fib(n 1) Fib(n 2), n 1
如 F0 = 0, F1 = 1, F2 = 1, F3 = 2, F4 = 3, F5 = 5

求解斐波那契数列的递归算法
 long Fib(long n) {
   if (n <= 1) return n;
   else return Fib(n-1)+Fib(n-2);
 }
                      230
时间复杂度:      2 n                    Fib(5)

                 Fib(4)                        Fib(3)

        Fib(3)            Fib(2)            Fib(2) Fib(1)

    Fib(2) Fib(1)    Fib(1) Fib(0) Fib(1)       Fib(0)

 Fib(1) Fib(0)
                 斐波那契数列的递归调用树

   调用次数 NumCall(k) = 2*Fib(k+1)-1
 如 F0 = 0, F1 = 1, F2 = 231 F3 = 2, F4 = 3, F5 = 5
                         1,
单向递归用迭代法实现
long FibIter(long n) {
   if (n <= 1) return n;
   long twoback = 0, oneback = 1, Current;
   for (int i = 2; i <= n; i++) {
      Current = twoback + oneback;
      twoback = oneback; oneback = Current;
   }
   return Current;
}

                     232
尾递归用迭代法实现
25 36 72 18 99        49 54 63

void recfunc(int A[ ], int n) {
   //从n到0输出数组各项的值。
   if (n >= 0) {
       cout << “value:”<<A[n] << endl;
       n--;
       recfunc(A, n);
  }              尾递归:递归调用语句只有一个,
}                而且放在过程的最后
                233
void sterfunc(int A[ ], int n) {
//消除了尾递归的非递归函数
  while (n >= 0) {
     cout << "value:" << A[n] << endl;
     n--;
  }
}




                     234
3.2.3 递归与回溯
  对一个包含有许多结点,且每个结点有多
个分支的问题,可以先选择一个分支进行搜索。
当搜索到某一结点,发现无法再继续搜索下去
时,可以沿搜索路径回退到前一结点,沿另一
分支继续搜索。
  如果回退之后没有其它选择,再沿搜索路
径回退到更前结点,…。依次执行,直到搜索
到问题的解,或搜索完全部可搜索的分支没有
解存在为止。 (p110迷宫问题)
  回溯法与分治法本质相同,可用递归求解。
         235
3.3 队列 ( Queue )
               a0 a1 a2     an-1
     front                         rear
•   定义
     队列是只允许在一端删除,在另一端插入

      的线性表
     允许删除的一端叫做队头(front),允许插

      入的一端叫做队尾(rear)。
•   特性:先进先出(FIFO, First In First Out)

                      236
队列的抽象数据类型
class Queue {
public:
   Queue() { };        //构造函数
   ~Queue() { };       //析构函数
   virtual bool EnQueue(DataType x) = 0;    //进队列
   virtual bool DeQueue(DataType& x) = 0; //出队列
   virtual bool getFront(DataType& x) = 0; //取队头
   virtual bool IsEmpty() const = 0;      //判队列空
   virtual bool IsFull() const = 0;       //判队列满
};

                       237
3.3.2 队列的数组存储表示 ─顺序队
          列
       队列的进队和出队
                           A
front rear 空队列         front rear A进队

 A B                       A B C D
front rear B进队         front rear C, D进队

   B C D                       C D
front rear A退队         front rear   B退队 假溢出
       C D E F G               C D E F G
front rear   E,F,G进队 front rear      H进队,溢出
                     238
队列的进队和出队原则
进队时先将新元素按 rear 指示位置加入,再将队
 尾指针加一 rear = rear + 1。
队尾指针指示实际队尾的后一位置。
出队时先按队头指针指示位置取出元素,再将队
头指针进一 front = front + 1,
队头指针指示实际队头位置。
队满时再进队将溢出出错;
队空时再出队将队空处理。
解决假溢出的办法?
  将队列元素存放数组首尾相接,形成循环
(环形)队列。
            239
6        7 front     6          7 front         6         7 front
                 rear
5                0 5                        0   5
                                        A                      A0
4                1   4                   1 4                   B1
                                        rear
                                                          C
    3    2               3      2            rear 3        2
    空队列                  A进队                            B, C进队
    6        7           6          7               6         7
5                    5                      0   5       GH
             A0                                     F          I   0

4
             B1      4
                                    B       1   4
                                                    E              1   rear
         C                    C                         DC
rear 3   2 front rear 3        2 front              3      2 front
    A退队                      B退队                D,E,F,G,H,I 进队(队满)
                              240
6   7 front
              rear
5           0
                循环队列 (Circular Queue)
4           1
    3队列存放数组被当作首尾相接的表处理。
        2
 空队列
  队头、队尾指针加1时从maxSize-1直接进到0,
可用语言的取模(余数)运算实现。
  队头指针进1: front = (front+1) % maxSize;
  队尾指针进1: rear = (rear+1) % maxSize;
  队列初始化:       front = rear = 0;
  队空条件:         front == rear;
  队满条件: (rear+1)241 maxSize == front
                  %
循环队列的类定义
#include <assert.h>
#include <iostream.h>
class SeqQueue {              //队列类定义
protected:
   int rear, front;             //队尾与队头指针
   DataType*elements;             //队列存放数组
   int maxSize;                    //队列最大容量
public:
   SeqQueue(int sz = 10);           //构造函数


                        242
~SeqQueue() { delete[ ] elements; } //析构函数
   bool EnQueue(DataType x);       //新元素进队列
   bool DeQueue(DataType& x);        //退出队头元素
   bool getFront(DataType& x);       //取队头元素值
   void makeEmpty() { front = rear = 0; }
   bool IsEmpty() const { return front == rear; }
   bool IsFull() const
      { return ((rear+1)% maxSize == front); }
   int getSize() const
      { return (rear-front+maxSize) % maxSize; }
   friend ostream& operator <<(ostream& os,
SeqQueue &Q);//输出队列中元素的重载操作<<
};
                      243
循环队列操作的定义
 ①构造函数
SeqQueue::SeqQueue(int sz) { //构造函数
   front=0; rear=0; maxSize=sz;
   elements = new DataType[maxSize];
   assert ( elements != NULL );
};




                    244
bool SeqQueue::EnQueue(DataType &x) {
//若队列不满, 则将x插入到该队列队尾, 否则返回
   if (IsFull() == true) return false;
    elements[rear] = x;                  //先存入
    rear = (rear+1) % maxSize;         //尾指针加一
    return true;
};
bool SeqQueue::DeQueue(DataType& x) {
//若队列不空则函数退队头元素并返回其值
   if (IsEmpty() == true) return false;
   x = elements[front];                   //先取队头
   front = (front+1) % maxSize;      //再队头指针加一
   return true;
};                          245
3.3.3 队列的链接存储表示 — 链式队
           列
front
                     rear
•   队头在链头,队尾在链尾。
•   链式队列在进队时无队满问题,但有队空问
    题。

•   队空条件为   front == NULL

               246
链式队列类的定义
#include <iostream.h>

struct QueueNode {            //队列结点类定义

private:
   DataType data;                   //队列结点数据
   QueueNode *link;                   //结点链指针
public:
   QueueNode(DataType d = 0, QueueNode
    *next = NULL) : data(d), link(next) { }
};
                        247
class LinkedQueue {
private:
    QueueNode *front, *rear;     //队头、队尾指针
public:
   LinkedQueue() : rear(NULL), front(NULL) { }
   ~LinkedQueue(MakeEmpty());
   bool EnQueue(DataType &x);
   bool DeQueue(DataType & x);
   bool GetFront(DataType & x);
   void MakeEmpty();
   bool IsEmpty() const {
            return (front == NULL)?true:false ;}
    int getSize( )const;
    friend ostream& operator<<(ostream&os,
LinkedQueue &Q)            248
};
①置空队列操作
LinkedQueue::MakeEmpty() { //释放链表中所有结点
   QueueNode *p;
   while (front != NULL) {                 //逐个释放结点
      p = front; front = front->link; delete p;
    }
};


   front                                     ^
           p                        rear


                        249
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社
数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社

More Related Content

What's hot

第01章 绪论(java版)
第01章  绪论(java版)第01章  绪论(java版)
第01章 绪论(java版)Yan Li
 
20150620 序列分析工作坊 發佈版
20150620 序列分析工作坊   發佈版20150620 序列分析工作坊   發佈版
20150620 序列分析工作坊 發佈版Yung-Ting Chen
 
[系列活動] 手把手打開Python資料分析大門
[系列活動] 手把手打開Python資料分析大門[系列活動] 手把手打開Python資料分析大門
[系列活動] 手把手打開Python資料分析大門台灣資料科學年會
 
一种基于拟合函数的图形识别算法
一种基于拟合函数的图形识别算法一种基于拟合函数的图形识别算法
一种基于拟合函数的图形识别算法Lixun Peng
 
计算机组成原理题
计算机组成原理题计算机组成原理题
计算机组成原理题Huaijin Chen
 
手把手教你 R 語言分析實務
手把手教你 R 語言分析實務手把手教你 R 語言分析實務
手把手教你 R 語言分析實務Helen Afterglow
 
20160905 序列分析工作坊 public
20160905 序列分析工作坊   public20160905 序列分析工作坊   public
20160905 序列分析工作坊 publicYung-Ting Chen
 
第四章 串操作应用举例
第四章 串操作应用举例第四章 串操作应用举例
第四章 串操作应用举例Wang Yizhe
 
Kid171 chap04 traditional Chinese Version
Kid171 chap04 traditional Chinese VersionKid171 chap04 traditional Chinese Version
Kid171 chap04 traditional Chinese VersionFrank S.C. Tseng
 
同余式(上)
同余式(上)同余式(上)
同余式(上)zhangxl
 
Slide08 807007748
Slide08 807007748Slide08 807007748
Slide08 807007748Shiyao Ma
 
7 第七章 学习与进化模型ann
7 第七章 学习与进化模型ann7 第七章 学习与进化模型ann
7 第七章 学习与进化模型annzhang shuren
 
STL Container Usage Tips
STL Container Usage TipsSTL Container Usage Tips
STL Container Usage Tips永泉 韩
 
二次剩余
二次剩余二次剩余
二次剩余zhangxl
 
R 語言教學: 探索性資料分析與文字探勘初探
R 語言教學: 探索性資料分析與文字探勘初探R 語言教學: 探索性資料分析與文字探勘初探
R 語言教學: 探索性資料分析與文字探勘初探Sean Yu
 

What's hot (18)

第01章 绪论(java版)
第01章  绪论(java版)第01章  绪论(java版)
第01章 绪论(java版)
 
第5章数组
第5章数组第5章数组
第5章数组
 
20150620 序列分析工作坊 發佈版
20150620 序列分析工作坊   發佈版20150620 序列分析工作坊   發佈版
20150620 序列分析工作坊 發佈版
 
[系列活動] 手把手打開Python資料分析大門
[系列活動] 手把手打開Python資料分析大門[系列活動] 手把手打開Python資料分析大門
[系列活動] 手把手打開Python資料分析大門
 
08 指標
08 指標08 指標
08 指標
 
一种基于拟合函数的图形识别算法
一种基于拟合函数的图形识别算法一种基于拟合函数的图形识别算法
一种基于拟合函数的图形识别算法
 
计算机组成原理题
计算机组成原理题计算机组成原理题
计算机组成原理题
 
手把手教你 R 語言分析實務
手把手教你 R 語言分析實務手把手教你 R 語言分析實務
手把手教你 R 語言分析實務
 
20160905 序列分析工作坊 public
20160905 序列分析工作坊   public20160905 序列分析工作坊   public
20160905 序列分析工作坊 public
 
第四章 串操作应用举例
第四章 串操作应用举例第四章 串操作应用举例
第四章 串操作应用举例
 
Kid171 chap04 traditional Chinese Version
Kid171 chap04 traditional Chinese VersionKid171 chap04 traditional Chinese Version
Kid171 chap04 traditional Chinese Version
 
同余式(上)
同余式(上)同余式(上)
同余式(上)
 
Slide08 807007748
Slide08 807007748Slide08 807007748
Slide08 807007748
 
7 第七章 学习与进化模型ann
7 第七章 学习与进化模型ann7 第七章 学习与进化模型ann
7 第七章 学习与进化模型ann
 
STL Container Usage Tips
STL Container Usage TipsSTL Container Usage Tips
STL Container Usage Tips
 
二次剩余
二次剩余二次剩余
二次剩余
 
系統程式
系統程式系統程式
系統程式
 
R 語言教學: 探索性資料分析與文字探勘初探
R 語言教學: 探索性資料分析與文字探勘初探R 語言教學: 探索性資料分析與文字探勘初探
R 語言教學: 探索性資料分析與文字探勘初探
 

Viewers also liked

研英 写作 08 讲义
研英 写作 08 讲义研英 写作 08 讲义
研英 写作 08 讲义pingjiang
 
Edu290cellpresentation
Edu290cellpresentationEdu290cellpresentation
Edu290cellpresentationhanso1lm
 
Brochure 2011/ 2012
Brochure 2011/ 2012Brochure 2011/ 2012
Brochure 2011/ 2012HetHof
 
Sonnet 43 by_elizabeth_barrett_browning
Sonnet 43 by_elizabeth_barrett_browningSonnet 43 by_elizabeth_barrett_browning
Sonnet 43 by_elizabeth_barrett_browningreenab2004
 
Where are my friends? The effects of real and imagined online communities on ...
Where are my friends? The effects of real and imagined online communities on ...Where are my friends? The effects of real and imagined online communities on ...
Where are my friends? The effects of real and imagined online communities on ...Lauren Wagner
 
Lesson 6 In Paris with You
Lesson 6 In Paris with YouLesson 6 In Paris with You
Lesson 6 In Paris with Youreenab2004
 
Praise song for my mother
Praise song for my motherPraise song for my mother
Praise song for my motherreenab2004
 
Edu290cellpresentation
Edu290cellpresentationEdu290cellpresentation
Edu290cellpresentationhanso1lm
 
Empathy map
Empathy mapEmpathy map
Empathy mapgad_84
 

Viewers also liked (16)

Dios te ama
Dios te amaDios te ama
Dios te ama
 
Dios te ama
Dios te amaDios te ama
Dios te ama
 
Simple past
Simple pastSimple past
Simple past
 
研英 写作 08 讲义
研英 写作 08 讲义研英 写作 08 讲义
研英 写作 08 讲义
 
Sección K4.
Sección K4.Sección K4.
Sección K4.
 
Edu290cellpresentation
Edu290cellpresentationEdu290cellpresentation
Edu290cellpresentation
 
Dios te ama
Dios te amaDios te ama
Dios te ama
 
Brochure 2011/ 2012
Brochure 2011/ 2012Brochure 2011/ 2012
Brochure 2011/ 2012
 
Simple Past
Simple PastSimple Past
Simple Past
 
Sonnet 43 by_elizabeth_barrett_browning
Sonnet 43 by_elizabeth_barrett_browningSonnet 43 by_elizabeth_barrett_browning
Sonnet 43 by_elizabeth_barrett_browning
 
Where are my friends? The effects of real and imagined online communities on ...
Where are my friends? The effects of real and imagined online communities on ...Where are my friends? The effects of real and imagined online communities on ...
Where are my friends? The effects of real and imagined online communities on ...
 
Lesson 6 In Paris with You
Lesson 6 In Paris with YouLesson 6 In Paris with You
Lesson 6 In Paris with You
 
Praise song for my mother
Praise song for my motherPraise song for my mother
Praise song for my mother
 
Edu290cellpresentation
Edu290cellpresentationEdu290cellpresentation
Edu290cellpresentation
 
Sonnet 43
Sonnet 43Sonnet 43
Sonnet 43
 
Empathy map
Empathy mapEmpathy map
Empathy map
 

Similar to 数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社

软件工程
软件工程软件工程
软件工程bill0077
 
软件工程 第二章
软件工程 第二章软件工程 第二章
软件工程 第二章浒 刘
 
文件空間中正交分類樹的建構
文件空間中正交分類樹的建構 文件空間中正交分類樹的建構
文件空間中正交分類樹的建構 鍾誠 陳鍾誠
 
管理資訊系統之資訊架構
管理資訊系統之資訊架構管理資訊系統之資訊架構
管理資訊系統之資訊架構5045033
 
数据结构 总结与复习
数据结构 总结与复习数据结构 总结与复习
数据结构 总结与复习Wang Yizhe
 
软件工程 第十一章
软件工程 第十一章软件工程 第十一章
软件工程 第十一章浒 刘
 
Python 脚本入门基础
Python 脚本入门基础Python 脚本入门基础
Python 脚本入门基础wklken
 
ajax_onlinemad
ajax_onlinemadajax_onlinemad
ajax_onlinemadKitor23
 
Java Script 引擎技术
Java Script 引擎技术Java Script 引擎技术
Java Script 引擎技术bigqiang zou
 
Excel函數進階班(北市政府公訓處) 2
Excel函數進階班(北市政府公訓處) 2Excel函數進階班(北市政府公訓處) 2
Excel函數進階班(北市政府公訓處) 2terry28853669
 
Data Analysis In Service Research2009 11 12
Data Analysis In Service Research2009 11 12Data Analysis In Service Research2009 11 12
Data Analysis In Service Research2009 11 12Kai Wu
 
Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化向 翔
 
AI Development (Chinese Version Tutorial)
AI Development (Chinese Version Tutorial)AI Development (Chinese Version Tutorial)
AI Development (Chinese Version Tutorial)churuihang
 
数据分析架构实例与安全的云挖掘
数据分析架构实例与安全的云挖掘数据分析架构实例与安全的云挖掘
数据分析架构实例与安全的云挖掘mysqlops
 
Se2009 ch9
Se2009 ch9Se2009 ch9
Se2009 ch9浒 刘
 
软件工程 第九章
软件工程 第九章软件工程 第九章
软件工程 第九章浒 刘
 

Similar to 数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社 (20)

软件工程
软件工程软件工程
软件工程
 
软件工程 第二章
软件工程 第二章软件工程 第二章
软件工程 第二章
 
getPDF.aspx
getPDF.aspxgetPDF.aspx
getPDF.aspx
 
getPDF.aspx
getPDF.aspxgetPDF.aspx
getPDF.aspx
 
文件空間中正交分類樹的建構
文件空間中正交分類樹的建構 文件空間中正交分類樹的建構
文件空間中正交分類樹的建構
 
管理資訊系統之資訊架構
管理資訊系統之資訊架構管理資訊系統之資訊架構
管理資訊系統之資訊架構
 
数据结构 总结与复习
数据结构 总结与复习数据结构 总结与复习
数据结构 总结与复习
 
软件工程 第十一章
软件工程 第十一章软件工程 第十一章
软件工程 第十一章
 
Python 脚本入门基础
Python 脚本入门基础Python 脚本入门基础
Python 脚本入门基础
 
ajax_onlinemad
ajax_onlinemadajax_onlinemad
ajax_onlinemad
 
Python 爬蟲實戰
Python 爬蟲實戰Python 爬蟲實戰
Python 爬蟲實戰
 
Java Script 引擎技术
Java Script 引擎技术Java Script 引擎技术
Java Script 引擎技术
 
Excel函數進階班(北市政府公訓處) 2
Excel函數進階班(北市政府公訓處) 2Excel函數進階班(北市政府公訓處) 2
Excel函數進階班(北市政府公訓處) 2
 
Data Analysis In Service Research2009 11 12
Data Analysis In Service Research2009 11 12Data Analysis In Service Research2009 11 12
Data Analysis In Service Research2009 11 12
 
hibernate
hibernatehibernate
hibernate
 
Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化Sql Server 高级技巧系列之三整体优化
Sql Server 高级技巧系列之三整体优化
 
AI Development (Chinese Version Tutorial)
AI Development (Chinese Version Tutorial)AI Development (Chinese Version Tutorial)
AI Development (Chinese Version Tutorial)
 
数据分析架构实例与安全的云挖掘
数据分析架构实例与安全的云挖掘数据分析架构实例与安全的云挖掘
数据分析架构实例与安全的云挖掘
 
Se2009 ch9
Se2009 ch9Se2009 ch9
Se2009 ch9
 
软件工程 第九章
软件工程 第九章软件工程 第九章
软件工程 第九章
 

Recently uploaded

educ6506presentationtc3302771-240427173057-06a46de5.pptx
educ6506presentationtc3302771-240427173057-06a46de5.pptxeduc6506presentationtc3302771-240427173057-06a46de5.pptx
educ6506presentationtc3302771-240427173057-06a46de5.pptxmekosin001123
 
哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制
哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制
哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制jakepaige317
 
1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...
1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...
1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...黑客 接单【TG/微信qoqoqdqd】
 
泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书
泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书
泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书jakepaige317
 
EDUC6506_ClassPresentation_TC330277 (1).pptx
EDUC6506_ClassPresentation_TC330277 (1).pptxEDUC6506_ClassPresentation_TC330277 (1).pptx
EDUC6506_ClassPresentation_TC330277 (1).pptxmekosin001123
 
EDUC6506(001)_ClassPresentation_2_TC330277 (1).pptx
EDUC6506(001)_ClassPresentation_2_TC330277 (1).pptxEDUC6506(001)_ClassPresentation_2_TC330277 (1).pptx
EDUC6506(001)_ClassPresentation_2_TC330277 (1).pptxmekosin001123
 

Recently uploaded (6)

educ6506presentationtc3302771-240427173057-06a46de5.pptx
educ6506presentationtc3302771-240427173057-06a46de5.pptxeduc6506presentationtc3302771-240427173057-06a46de5.pptx
educ6506presentationtc3302771-240427173057-06a46de5.pptx
 
哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制
哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制
哪里可以购买日本筑波学院大学学位记/做个假的文凭可认证吗/仿制日本大学毕业证/意大利语CELI证书定制
 
1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...
1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...
1.🎉“入侵大学入学考试中心修改成绩”来袭!ALEVEL替考大揭秘,轻松搞定考试成绩! 💥你还在为无法进入大学招生系统而烦恼吗?想知道如何通过技术手段更改...
 
泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书
泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书
泽兰应用科学大学毕业证制作/定制国外大学录取通知书/购买一个假的建国科技大学硕士学位证书
 
EDUC6506_ClassPresentation_TC330277 (1).pptx
EDUC6506_ClassPresentation_TC330277 (1).pptxEDUC6506_ClassPresentation_TC330277 (1).pptx
EDUC6506_ClassPresentation_TC330277 (1).pptx
 
EDUC6506(001)_ClassPresentation_2_TC330277 (1).pptx
EDUC6506(001)_ClassPresentation_2_TC330277 (1).pptxEDUC6506(001)_ClassPresentation_2_TC330277 (1).pptx
EDUC6506(001)_ClassPresentation_2_TC330277 (1).pptx
 

数据结构(用面向对象方法与C++语言描述第二版)殷人昆编著清华大学出版社

  • 1. 烟雨科技有限责任公司 数据结构 用面向对象方法与C++语言描述 第二版 殷人昆编著 清华大学出版社 课件 本课件由迷若烟雨提供
  • 2. 课程介绍 1. 为什么要学习数据结构? 2. 该课程的主要内容是什么?如何学习? 3. 考核方式? 总成绩=平时( 40% )+期末(60%) 平时=上机实习+平时作业+上课回答问题 +4次课内考试(每次5分) 4. 教材:殷人昆,《数据结构(面向对象方法与C++ 语言描述)》(第2版)清华大学出版社 1. 为什么要学习数据结构? 2. 该课程的主要内容是什么? 数据结构=数据+结构(关系)
  • 3. 数据结构 数据(的集合) 结构 逻辑结构 物理结构 线 树 图 集 连 非 性 结 结 合 续 连 结 构 构 续 构 顺 链 顺 链 邻 邻 序 式 顺 链 序 式 接 接 存 存 序 表 存 存 矩 表 储 储 表 储 储 阵 存 存 储 储
  • 4. 如何学习? 1. 预习:粗读教材,发现问题 2. 听课:重点、难点,初步解决问题 3. 复习:细读教材,解决问题(理解抽象数 据类型、看懂C++类的定义和实现) 4. 做题:巩固知识(C++类的定义和实现) 5. 实习:验证(调试程序)
  • 5. 上机实习作业: 1. 顺序表及其应用 2. 链表及其应用.doc 3. 栈及其应用 4. 队列及其应用 5. 稀疏矩阵及其基本操作 6. 二叉树及其基本操作 7. 堆及其基本操作 8. Huffman树及其基本操作 9. 图及其基本操作
  • 6. 第一章 绪论 内容提要: 本章主要介绍数据结构的基本概念、 术语,以及有关算法的描述、分析的基本 常识。
  • 7. §1.1 数据结构的概念 第一章 绪论 1.1.1 为什么要讨论数据结构? 通过前面的学习,我们知道: 计算机科学是研究信息表示和信息处理的科学; 信息是当今社会的重要资源; 计算机产业及计算机科学技术飞速发展,计算机的硬件技 术和软件技术都有了极大的发展,计算机应用也已经渗透到 了社会的各个领域。 同时出现了如下一些变化: 计算机由最初的单一科学计算到几乎无所不能; 加工处理的对象由数值型变为数值型和非数值型; 处理的数据量由小变为大、巨大(海量存储、计算); 数据之间的关系由简单变复杂、很复杂;
  • 8. §1.1 数据结构的概念 第一章 绪论 解决思路1:发展硬件技术,开发出更快、更高级的硬件产品, 但有没有其它途径呢? 思路2: 将一“大堆杂乱无章”的数据交给计算机处理是很不明智的, 结果是加工处理的效率会非常低,有时甚至根本无法进行。 于是: 人们开始考虑如何更有效地描述、表示、处理数据的问题, 除了不断提高计算机技术外(其它课程),很重要的一个方面 是通过研究、分析问题数据本身的特点,利用这些特点提高数 据表示和处理的效率。——这就是数据结构 信息的表示和组织形式直接影响到数据处理的效率! 下面举几个例子,目的是加深对数据结构的理解,从中也 可以看出研究数据结构的重要性!
  • 9. 线性 例1 书目自动检索系统 表 书目文件 书目卡片 001 高等数学 樊映川 S01 002 登录号: 理论力学 罗远祥 L01 索引表 003 书名: 高等数学 华罗庚 S01 004 线性代数 栾汝书 S02 …… 作者名: …… …… …… 按书名 分类号: 按作者名 按分类号 高等数学 出版单位: 001,003…… 樊映川 001, … L 002, … 002,…….. 理论力学 出版时间:华罗庚 002, …. S 001, 003, 线性代数 004,…… …… …… 栾汝书 004, …. …… …….. 价格: ……. …….
  • 10. 例1 书目自动检索系统  问题:图书管理,完成书目的自动检索。  数据:一本本书籍,更确切地说是每本书的 信息,如:书名、作者、出版社、出版日期、 书号、内容提要等等。  操作:书目检索、入库、借书、还书等。  涉及到:书目数据逻辑组织、存储方法;检 索、入库等操作实现算法。
  • 11. 例2 人机对奕问题(井字棋) 树 …….. …….. …... …... …... …...
  • 12. 例2 人机对奕问题  问题:人机对弈,即人与计算机下棋。  数据:各种棋局状态,确切说就是描述棋盘 格局的信息。  操作:走棋,即选择一种策略使棋局状态发 生变化(由一个格局派生出另一个格局)  涉及到:“格局‖数据逻辑组织、存储方法;走 棋操作实现算法。
  • 13. 例3 多叉路口交通灯管理问题 图 AB AC AD C D BA BC BD B DA DB DC E EA EB EC ED A
  • 14. 例3 多叉路口交通灯管理问题  问题:多叉路口的交通灯管理,即在多 叉路口应怎样设置交通灯,以保证交通 畅通。  数据:路口各条道路的信息  操作:设置信号灯(求出各个可以同时 通行的路的集合)  涉及到:路口各条道路信息数据及其关 系的逻辑组织、存储方法;信号灯设置 的操作实现算法。
  • 15. §1.1 数据结构的概念 第一章 绪论 1.1.2 数据结构及其术语 [数据 Data] 用于描述客观事物的数值、字符等一切可以输 入到计算机中,并由计算机加工处理的符号集合。 [数据元素 Data Element] 表示一个事物的一组数据称作一个 数据元素。如学生信息可包括:学号、姓名、性别等。 [数据项 Data Item] 构成数据元素的成份,是数据不可分割的最 小单位 。 [数据对象 Data Object]具有相同性质的数据成员(数据元素)的 集合,是数据的子集。 [结构 Structure ] 数据元素之间的关系(Relation)。 [数据结构 Data Structure ]由一个数据对象以及该对象中的所有 数据元素之间的关系组成,即带结构的数据元素的集合。
  • 16. §1.1 数据结构的概念 第一章 绪论 数据结构=数据+结构 记作 Data_Structure=(D,R) 其中:Data_Structure是数据结构的名称 D是数据元素的有限集合(一般为一个数据对象) R是D上关系的有限集 注:这里说的数据元素之间的关系是指元素之间本身固有的逻辑 关系,与计算机无关。因此又称为:数据的逻辑结构 我们研究数据结构的目的是要利用数据之间的关系(结构), 因此,在存储时既要存储数据本身,还要存储关系!!
  • 17. §1.1 数据结构的概念 第一章 绪论 [数据的存储结构]:是数据结构在计算机内的表示,它包括数据 元素的表示和关系的表示。 数据在计算机中的存储:两种形式 连续:数据元素逐个连续存放(通过物理相邻来表示关系) 非连续:数据元素不连续存放(如何表示关系?) 这样:一个数据结构要存放,一方面要把数据元素存起来, 另一方面,还必须把数据元素之间的逻辑关系也表示出来,那么: 要么用数据元素在物理上的相邻来表示逻辑关系 要么用数据元素的存储地址、索引表、散列函数来表示逻辑关系 顺序存储结构 链式存储、索引存储、散列存储
  • 18. §1.2 数据结构的抽象形式 第一章 绪论 1.2.1 数据类型 Data Type 一个值的集合和定义在这个值集上的一组操作的总称。 (1) 高级语言中的数据类型实际上包括:数据的逻辑结构、数据 的存储结构及所定义的操作的实现。 (2)高级语言中的数据类型按值的不同特性分为: 原子类型(如整型、实型、字符型、布尔型) 结构类型(由原子类型按照一定的规则构造而成, 如数组、结构体) (3)数据类型逻辑概念与其在计算机程序中的实现有很重要的区 别,例如线性表数据类型有两种传统的实现方式:基于数组的顺序 表示和基于链表的链式表示; (4)我们可以撇开计算机不考虑,现实中的任何一个问题都可以 定义为一个数据类型——称为抽象数据类型。
  • 19.
  • 20. §1. 2 数据结构的抽象形式 第一章 绪论 抽象数据类型=数学模型+操作 (其它教材上对ADT的描述) =数据结构+操作 =数据+结构+操作 它是一种描述用户和数据之间接口的抽象模型,亦即它给出 了一种用户自定义的数据类型。 三元组表示:(D,R,P) 其中D是数据对象,R是D上的关系集,P是对D的基本操作集。 ADT 抽象数据类型名 { 数据对象:<数据对象的定义> 数据关系:<数据关系的定义> 基本操作:<基本操作的定义> }ADT 抽象数据类型名 抽象数据类型的作用:抽象数据类型可以使我们更容易描述现实世 界。例:用线性表描述学生成绩表,用树描述棋局关系等。
  • 22. §1.3 作为ADT的C++类 第一章 绪论 1.3.1 面向对象的概念 面向对象 = 对象+类+继承+通信 对象:在应用问题中出现的各种实体、事件、规 格说明等。由一组属性值和在这组值上的一组服 务(或称操作)构成。 类 (class),实例 (instance) 具有相同属性和服务的对象归于同一类,形成类。 类中的一个对象为该类的一个实例。
  • 23. 继承:是面向对象方法最有特色的方面。 • 派生类:载重车,轿车,摩托车,… 子类、特化类(特殊化类) • 基类:车辆 父类、超类、泛化类(一般化类) 各派生类中的公共部分,包括属性和服务,集 中到基类中,派生类中只保留自己特有的属性和 服务。这样减少了数据的存储和程序代码的重复 。 – 通信 • 各个类的对象间通过消息传递进行通信。 – 消息:一个类的对象要求另一个类的对象执行某个服务的指 令,必要时还要传递调用参数。
  • 24. § 1.3 作为ADT的C++类 第一章 绪论 传统的大型结构化程序设计是面向过程的,首先着眼于系 统要实现的功能。一个数据结构可能被多个过程调用,修改数 据结构,意味着必须修改相关的过程,这样做烦琐又容易产生 错误。 面向对象程序设计(Object-Oriented Programing,OOP) 程序围绕被操作的数据来设计, “类”作为构造程序的基本单 位。 “类”具有封装、数据抽象、继承和多态等特点!
  • 25. § 1.3 作为ADT的C++类 第一章 绪论 1.3.2 C++中的类 例1:计算圆的周长和面积 圆是平面上与圆心等距离的所有点的集合。从图形显示角 度看,圆的抽象数据类型包括圆心和半径;而从计量角度看, 它所需要的抽象数据类型只需半径即可。如果从计量角度来 给出圆的抽象数据类型,那么它的数据取值范围应为半径的 取值范围,即为非负实数,而它的操作形式为确定圆的半径 (赋值);求圆的面积;求圆的周长。
  • 26. § 1.3 作为ADT的C++类 第一章 绪论 问题描述:给定圆的半径,求其周长和面积 ADT定义: ADT circle data : 0 r ,实数 structure: NULL operations: area {计算面积 s=πr2} circumference {计算周长 l=2πr} END ADT
  • 27. §1.3 作为ADT的C++类 第一章 绪论 例2:掷色子游戏。 问题描述:每次掷出N个色子,统计每个色子的点数和每次的总 点数,看看谁的高。 问题分析:该问题的数据包括色子数、每个色子的点数和总点数, 色子数是大于0的整数N;每个色子的点数是1-6; 总点数是N~6N; 该问题的操作包括掷色子、输出各个色子的点数、 求总点数。 ADT定义: ADT dice data : N {色子数} ;K1,K2…Kn {每个色子的点数} S {总点数} structure: N S 6N, S=K1+K2+…+KN, 1 Ki 6 operations: toss {掷色子} displaytoss {显示每个色子的点数} total {计算总点数} END ADT
  • 28. § 1.3 作为ADT的C++类 第一章 绪论 例3:复数的运算 问题描述:在高级语言中,没有复数类型,但是我们可以借助已 有的数据类型解决复数类型的问题,如复数运算。 ADT定义: ADT complex data : c1,c2 {c1,c2均为实数} structure: z=c1+c2i operations: create(z,x,y) {生成一复数} add(z1,z2,s) {复数加法} subtract(z1,z2,difference) {复数减法} multiply(z1,z2,product) {复数乘法} get_realpart(z) {求实部} get_imagpart(z) {求虚部} printc(z) {输出一复数} END ADT
  • 29. §1.3.2 C++中的类 第一章 绪论 C++对于面向对象程序设计的支持,核心部分就是类的定义, 类的定义体现了抽象数据类型的思想: •说明与实现的分离 •信息隐蔽 对类的成员规定三级存取: • 公共(public):其它类的对象或操作可访问,构成类的接口 • 私有 (private):只能该类的对象和成员函数及友元访问 • 保护 (protected):除了具有私有成员的特性,允许该类的 派生类访问
  • 30. §1.4 算法定义 第一章 绪论 1.算法的定义和特性  算法 (Algorithm ): 算法是解决某一特定任 务的指令的有限序列。  算法具有以下五个特性: (1)有穷性 一个算法必须总是在执行有穷步 之后结束,且每一步都在有穷时间内完成。 (2)确定性 算法中每一条指令必须有确切的 含义。不存在二义性。且算法只有一个入口和 一个出口。
  • 31. §1.4 算法定义 第一章 绪论 (3)可行性 一个算法是可行的。即算法描述的 操作都是可以通过已经实现的基本运算执行有 限次来实现的。 (4)输入 一个算法有零个或多个输入,这些输 入取自于某个特定的对象集合。 (5)输出 一个算法有一个或多个输出,这些输 出是同输入有特定关系的量。
  • 32. §1.4 算法定义 第一章 绪论 2. 算法与数据结构的关系 算法+数据结构=程序 在计算机解决问题的过程中算法与数据结构是缺一不 可的两个方面: 问题的数据组织——数据结构 数据的处理——算法 算法与数据结构是相辅相承的。解决某一特定类型问 题的算法可以选定不同的数据结构,而且选择恰当与否直 接影响算法的效率。反之,一种数据结构的优劣由各种算 法的执行来体现。
  • 33. 怎样才能设计一个好的算法呢? 要设计一个好的算法通常要考虑以下的要求: ⑴正确: 算法的执行结果应当满足预先规定的功能和性能 要求。 ⑵可读: 一个算法应当思路清晰、层次分明、简单明了、 易读易懂。 ⑶健壮:当输入不合法数据时,应能作适当处理,不至引 起严重后果。 ⑷高效:有效使用存储空间和有较高的时间效率。
  • 34. §1.5 算法的性能分析与度量 第一章 绪论 1. 5.1 算法的描述 1. 自然语言描述:容易,但有时罗嗦、有二义性 2. 图示(流程图、N-S图、PAD图等):直观清晰,但不易实现 3. 程序设计语言:可以直接运行,但太严格。 4. 算法语言(伪代码):严谨、简洁,易程序实现 为了解决理解与执行这两者之间的矛盾,人们常常使用一 种称为伪码语言的描述方法来进行算法描述。伪码语言介于高 级程序设计语言和自然语言之间,它忽略高级程序设计语言中 一些严格的语法规则与描述细节,因此它比程序设计语言更容 易描述和被人理解,而比自然语言更接近程序设计语言。它虽 然不能直接执行但很容易被转换成高级语言。
  • 35. §1.5 算法的性能分析与度量 第一章 绪论 如何评价算法的优劣呢? 我们可以从一个算法的时间复杂度与空间复杂度来评价算 法的优劣。 一个算法转换成程序并在计算机上执行时,其运行所需要 的时间取决于下列因素: ⑴硬件的速度。例如使用486机还是使用586机。 ⑵书写程序的语言。实现语言的级别越高,其执行效率就越低。 ⑶编译程序所生成目标代码的质量。对于代码优化较好的编译程 序其所生成的程序质量较高。 ⑷问题的规模。例如,求100以内的素数与求1000以内的素数其 执行时间必然是不同的。
  • 36. §1.5 算法的性能分析与度量 第一章 绪论 显然,在各种因素都不能确定的情况下,很难比较出算法 的执行时间。 也就是说,使用执行算法的绝对时间来衡量算法的效率是 不合适的。为此,可以将上述各种与计算机相关的软、硬件因 素都确定下来,这样一个特定算法的运行工作量的大小就只依 赖于问题的规模(通常用正整数n表示),或者说它是问题规 模的函数。 算法效率的度量分为:事前估计、后期测试。
  • 37. §1.5 算法的性能分析与度量 第一章 绪论 1.5.2 算法效率的度量 先验估计(事前估计):根据算法的逻辑特征(基本操作的 次数)来估算。 后期测试(事后计算):选择样本数据、运行环境,运行算法 计算出空间、时间。 优点:可比性强 优点:精确 缺点:不精确,仅仅是估计 缺点:可比性差,效率低 那么,如何撇开计算机本身来估算一个算法的复杂性呢?
  • 38. §1.5 算法的性能分析与度量 第一章 绪论 算法复杂性的度量属于事前估计。分为: 时间复杂度和空间复杂度的度量。 时间复杂度: 一个程序的时间复杂度(Time complexity)是指程序运行 从开始到结束所需要的时间。 一个算法是由控制结构和原操作构成的,其执行时间取决 于两者的综合效果。为了便于比较同一问题的不同的算法,通 常的做法是:从算法中选取一种对于所研究的问题来说是基本 运算的原操作,以该原操作重复执行的次数作为算法的时间度 量。一般情况下,算法中原操作重复执行的次数是规模n的某个 函数T(n)。(计算T(n)见p29) 许多时候要精确地计算T(n)是困难的,我们引入渐进时间 复杂度在数量上估计一个算法的执行时间,也能够达到分析算 法的目的。
  • 39. §1.5 算法的性能分析与度量 第一章 绪论 1.5.4 O表示的含义——算法的渐进分析 1. 算法的渐进分析 算法的渐进分析简称算法分析。通常将问题的规模作为 分析的参数,求算法的时间开销与问题规模n的关系。 当且仅当存在正整数c和n0,使得T(n)≤c*f(n),对所有 的n≥n0成立,则称该算法的时间增长率在O(f(n))中, 记为T(n)=O(f(n)) 。
  • 40. §1.5 算法的性能分析与度量 第一章 绪论 例:考察T(n)=3n+2。当n>=2时,3n+2<=3n+n=4n,所以 T(n)=O(n) 例:考察T(n)=10n2+4n+2。当n>=2时, 10n2+4n+2 <= 10n2+5n, 当n >=5时, 10n2+5n <= 11n2 ,所以, T(n)=O(n2) 例:考察T(n)=6*2n+n2。当n>=4时, n2<= 2n,有: 6*2n+n2<=7*2n 所以, T(n)=O(2n) 当我们评价一个算法的时间性能时,主要标准就是算法的 渐进时间复杂度,因此,在算法分析时,往往对两者不予区分, 经常将渐进时间复杂度T(n)=O(f(n))简称为时间复杂度。
  • 41. §1.5 算法的性能分析与度量 第一章 绪论 算法分析举例 例1:分析下面程序段的时间复杂性 (1) T(n)=O(n) i=1; k=0 ; ◇ 这个函数是按线性阶递增的 while(i<n) { k=k+10*i; i++; } (2) i=0; k=0; do{ T(n)=O(n) k=k+10*i; i++; } ◇ 这也是线性阶递增的 while(i<n);
  • 42. §1.5 算法的性能分析与度量 第一章 绪论 (3) for(i=1;i<=n; i ++) T(n)=O(n2) for(j=1;j<=n; j ++) ◇ 是平方阶递增的 { ++x; s+=x; } (4) T(n)= O(n2) for(i=1;i<=n; i ++) ◇ 这也是平方阶递增的 for(j=1;j<=i; j ++) ++x;
  • 43. §1.5 算法的性能分析与度量 第一章 绪论 (5) ◆ T(n)=O(1) x=91; y=100; ◇ 这个程序看起来有点吓人, while(y>0) 总共循环运行了1000次,但是 if(x>100) 我们看到n没有? 没。这段程序 {x=x-10;y--;} 的运行是和n无关的,就算它再 else x++; 循环一万年,我们也不管他, 只是一个常数阶的函数。
  • 44. §1.5 算法的性能分析与度量 第一章 绪论 (6) ◆ T(n)=O(log2n) i=1; 设其原操作执行的次数为t(n) while(i<=n) 于是,2t(n) n i=i*2; 从而有:t(n) log2n (7) s=0; ◆ T(n)=O(n3) for(i=0;i<=n;i++) n i j for(j=0;j<=i;j++) t(n)= 1=…… i=0 j=0 k=0 for(k=0;k<=j;k++) s++;
  • 45. §1.5 算法的性能分析与度量 第一章 绪论 例2:分析下面函数的复杂性 A是一个含有n个不同元素的实数数组,给出求A之最大和最小 元素的算法如下,分析它的时间复杂性。 算法 SM(A,n,max,min) SM1 [初始化] max min a[1] 1 SM2 [比较] for(i=2;i<=n;i++) n { if(a[i]>max) max a[i] n-1 if(a[i]<min) min a[i] } n-1 T(n)=3n-1 T(n)=O(n)
  • 46. §1.5 算法的性能分析与度量 第一章 绪论 2. 常见的算法时间复杂度: 常见的时间复杂度,按数量级递增排列依次为: 常数阶O(1)、对数阶O(log2n)、线性阶O(n)、线性对数阶 O(nlog2n)、平方阶O(n2)、立方阶O(n3)、k次方阶O(nk)、指数阶 O(2n)。 3. 大O运算规则 O(f(n))+O(g(n))=O(max(f(n),g(n))) O(f(n))+O(g(n))=O(f(n)+g(n)) O(f(n)) •O(g(n))=O(f(n) •g(n)) O(c•f(n))=O(f(n))
  • 47. §1.5 算法的性能分析与度量 第一章 绪论 4. 平均时间复杂性 定义:设一个领域问题的输入规模为n,Dn是该领域问题的所有 输入的集合,任一输入I Dn,P(I)是I出现的概率, P(I)=1, t(I)是算法在输入I下执行的基本运算次数,则平均时间复杂性 定义为: T(n)= {P(I)*t(I)} I Dn 最好时间复杂性、最坏时间复杂性、平均时间复杂性 对于有些算法,问题规模相同,如果输入集不同,其效率不同 Tmin:最好情况不能代表算法的性能 Tmax:最坏情况可以知道算法至少能达到的性能 Tavg:较好地反映了算法的性能,但并不一定总是可行的!
  • 48. §1.5 算法的性能分析与度量 第一章 绪论 例如:查找一个元素是否存在的算法 int loc(int a[],int x) { a[0]=x; i=n; while(a[i]!=x) i=i-1; return i } 最好:T ( n ) =c O(1) 最坏:T ( n ) =n+c O( n ) 平均:T ( n ) =n/2+c O( n )
  • 49. §1.5 算法的性能分析与度量 第一章 绪论 5. 算法时间复杂性的实质: 算法与问题规模及时间的关系。 同一问题,规模相同,用不同的算法解决,花费时间是不同的; 同一问题,用不同的算法解决,在相同的时间内所解决的问题 规模大小不同; 思考:―复杂性渐进阶比较低的算法比复杂性渐进阶比较高 的算法有效‖,这种说法正确吗? 另外需要注意: 当两个算法的复杂性渐进阶相同时,必须进一步考察T(n) 的常数因子。
  • 50. 本章小结 第一章 绪论 1. 为什么要讨论数据结构(数据结构的重要性) 2. 数据结构的有关概念及它们之间的关系 数据结构、逻辑结构、存储结构、数据类型、 ADT等 3. 算法及算法分析基础 算法的定义及特点、算法分析的方法(渐进时间 复杂度)
  • 52. 第二章 线性表 内容提要: 线性表是最简单、最基本、也是最常 用的一种线性结构。 它有两种存储方法:顺序存储和链式 存储,它的主要基本操作是插入、删除和 检索等。
  • 53. 第二章 线性表 2.1 线性表 2.2 顺序表 2.3 单链表 2.4 线性链表的其它变形 2.5 单链表的应用:一元多项式及其运算
  • 54. 2.1 线性表 linear list 2.1.1 线性表的概念 线性表是具有相同数据类型的n(n>=0)个数据元素的有限 序列,通常记为: L=(a1,a2,… ai-1,ai,ai+1,…an) 几个概念: 表中相邻元素之间存在着顺序关系。将 ai-1 称为 ai 的 直接前趋,ai+1 称为 ai 的直接后继。就是说:对于ai, 表名 数据元素(结点、表项) 表长 空表 当 i=2,...,n 时,有且仅有一个直接前趋 ai-1.,当 表头 表尾 i=1,2,...,n-1 时,有且仅有一个直接后继 ai+1,而 a1 是表中第一个元素,它没有前趋,an 是最后一个元素 无后继。
  • 55. 说明: ai为序号为 i 的数据元素(i=1,2,…,n), 通常我们将它的数据类型抽象为datatype。 datatype根据具体问题而定,如在学生情况信息 表中,它是用户自定义的学生类型。
  • 56. 线性表的抽象数据类型: 抽象数据类型是一个(D,R,P)三元组,分析可得线性表的 抽象数据类型: ADT List { D={ ai | ai ∈ElemSet, i=1,2,...,n, n≥0 } ( i 为 ai 在线性表中的位序) R={ <ai-1 ,ai >|ai-1 ,ai∈D, i=2,...,n } 操作: 初始化操作 Create() CopyList() 结构销毁操作 引用型操作Length()search()Locate()getData()Isempty()Isfull() 加工型操作SetData()Insert()Remove()Sort() }
  • 57. 2.1.2 线性表的类定义 Enum bool {false, true} class LinearList { Public: LinearList(); ~LinearList(); virtual int Size() const =0; virtual int Length() const =0; virtual int Search(int &x) const =0; virtual int Locate(int i) const =0; virtual datetype *GetData(int i) const =0; } (44页)
  • 58. 2.2 顺序表(Sequential List) 2.2.1 顺序表的定义和特点 定义 :把线性表中的所有表项按照其逻辑顺序依次存 储到计算机存储器中指定存储位置开始的一块连续 的存储空间中。 特点: ①逻辑顺序与物理顺序一致 ②可顺序或随机访问表项
  • 59. 顺序表可以用C++的一维数组来实现。 C++的一维 数组可以是静态分配的,也可以是动态分配的。 数组的下标位置: 0 1 i-2 i-1 n-1 a1 a2 … ai-1 ai … an 所有数据元素的存储位置均取决于第一个数据元素 的存储位置: LOC(ai) = LOC(a1) + (i-1)×sizeof(T)
  • 60. 2.2.2顺序表(SeqList)类的定义 #include<iostream.h> #include<stdlib.h> typedef int T; class SeqList { T *data; //顺序表存储数组 int MaxSize; //是问题要求的元素数目的最大值 int last; //当前最后元素下标 public: SeqList ( int sz ); ~SeqList ( ) { delete [ ] data; }
  • 61. int Length ( ) const { return last+1; } //返回元素的个数 int Find (T & x ) const; //返回元素x在表中的位置 void Insert (T & x, int i ); //在位置i插入元素x int Remove (T & x ); //删除值为x的元素 int IsEmpty ( ) { return last ==-1; } //表空否 int IsFull ( ) { return last == MaxSize-1; } T GetData ( int i ) { //取第i个表项的值 return data[i-1] }; void SetData ( int i, T & x) { //为第i个表项赋值 if (i >0 && i <= last+1) data[i-1] = x ;} void input(); void output(); }
  • 62. 顺序表部分公共操作的实现: ①构造函数 SeqList :: SeqList ( int sz ) { //构造函数,通过指定sz,定义数组的长度 if ( sz > 0 ) { data = new T[sz]; if ( data != NULL ) { MaxSize = sz; last = -1;} else {cerr <<“存储分配错误!”<<endl;exit(1);} } } 设计思路: 若sz>0,则为数组申请空间: ①若申请成功,MaxSize=sz,last=-1 ②若申请失败,则提示出错
  • 63. (定位元素x的位置,返回值为x在 ②搜索或定位:顺序表中的位置;返回值为-1表示 不存在) data MaxSize 23 75 41 38 54 62 17 ii i i i last i i 1 0 7 3 2 基本操作是: 将顺序表中的元素 x = 38 50 逐个和定值x相比较。
  • 64. int SeqList::Find ( T & x ) const { //搜索函数:在表中从前向后顺序查找 x int i = 0; while ( i <= last && ) data[i] != x i++; if ( i > last ) return -1; else return i+1; } 设计思路: ① x与data[i]逐个循环比较,直到x=data[i]或i>last ② 若i>last,则没有找到x,否则返回i+1
  • 65. 搜索成功: n 1 ACN = pi ci i 0 若搜索概率相等,则 1n1 1 ACN = ( i 1) (1 2  n) ni0 n 1 (1 n) n 1 n n 2 2 搜索不成功 数据比较 n 次 算法时间复杂度: O(n)
  • 66. ③插入元素 分析:在i位置插入元素x时,线性表的逻辑结构发生 什么变化? (a1, …, ai-1, ai, …, an) 改变为(a1, …, ai-1, x, ai, …, an) <ai-1, ai> <ai-1, x>, <x, ai> a1 a2 … ai-1 ai … an a1 a2 … ai-1 x ai … an 表的长度增加1
  • 67. 例如: x=66; Insert ( x, 4) Last ++;//当前最后元素下标加1 for(j = Last ; j >i ; j --) data[j]=data[j-1]; data[i]=x;//在i位置插入x j j j j i 21 18 30 75 42 56 87 0 21 18 30 75 66 42 56 87
  • 68. Insert ( const T& x, int i) 思路: ①若顺序表的长度等于MaxSize,则满(不能插入); ②若给定的位置小于0或大于长度,出错; ③否则,Last加1,把i及其后面的元素向后移动 一个位置; ④把数据X插入到i位置
  • 69. //在指定位置i插入一个数据元素x void SeqList::Insert ( const T& x, int i) {//i为下标,不是序号 if(last == MaxSize-1) { cerr<<“顺序表已满无法插入!”<<endl;exit(1); } if(i<0‖i>last+1) //当i等于Last+1时表示插入在最后 { cerr<<"参数i越界出错!"<<endl; exit(1); }
  • 70. //从后向前把前一个元素迁移到后一个元素位置 直到存储位置为i为止 last++;//当前最后元素下标加1 for(int j = Last ; j > i ; j --) data[j]=data[j-1]; data[i]=x;//在第i项处插入x }
  • 71. 考虑移动元素的平均情况: 假设在第 i (0<=i<=n)个元素位置插入的 概率为 pi ,则在线性表中插入一个元素所需 移动元素次数的期望值为: n Eis pi (n i ) i 0 若假定在线性表中任何一个位置上进行插入 的概率都是相等的,则移动元素的期望值为: n 1 n Eis (n i) n 1i 0 2
  • 72. ④删除元素 分析:删除i处的元素时,线性表的逻辑结构发生 什么变化? (a1, …, ai-1, ai, …, an) 改变为(a1, …, ai-1, ai+1, …, an) <ai-1, ai>, <ai, ai+1> <ai-1, ai+1> a1 a2 … ai-1 ai ai+1 … an a1 a2 … ai-1 表的长度减少
  • 73. 删除值为x的元素 int SeqList :: Remove ( T & x ) { int i = Find (x)-1; //在表中搜索 x if ( i >= 0 ) { last-- ; for ( int j = i; j <= last; j++ ) data[j] = data[j+1]; return 1; //成功删除 } 思路: ①在顺序表中查找值为x的元素 return 0; //表中没有 x ②若找到,last--,把x后的元 } 素向前移动,返回1,若找不到, 返回0。
  • 74. ⑤输入操作 void SeqList :: input(){//从键盘逐个输入数据建立顺序表 cout <<“开始建立顺序表,请输入表中元素个数”; while (1){ cin >>last; if (last<=MaxSize-1) break; cout <<“元素个数有误,范围1~”<<MaxSize-1<<endl; } for (int i=0;i<=last;i++) {cout<<“请输入第”<<i+1<<“个元素:”<<endl; cin>>data [i];} cout<<“表建立完成!” <<endl; }
  • 75. ⑥输出操作 void SeqList :: output(){//将顺序表全部元素输出到屏幕上 cout <<“顺序表元素个数为:”<<Last+1<<endl; for (int i=0;i<=last;i++) cout<<i+1<< “:” <<data [i]<<endl; }
  • 76. 2.2.4 顺序表的应用 例1:编写一个程序向顺序表中插入5个整数值,然后以插 入次序显示这5个数,最后删除这5个数。 typedef int T; #include”SeqList.h” void main(void) { SeqList myList(100); for(int i=0;i<5;i++) ∥插入5个整型元素 myList.Insert(i+10,i); for(int i=0;i<5;i++) cout<<myList.GetData(i)<<“ ”; for(int i=0;i<5;i++) myList.Remove(i); } 程序输出为: 10 11 12 13 14
  • 77. 例2. 集合的“并”运算 利用两个线性表LA和LB分别表示两个集合A和B, 现要求一个新的集合A=A∪B。 步骤:①取LB中的一个元素 x=LB.GetData(i) ②在LA中找这个元素 LA.Find(x) ③如果LA中没有这个元素则插入LA LA.Insert(x,++Last)
  • 78. 求集合的“并”集 void Union ( SeqList &LA, SeqList LB ) { int n = LA.Length ( ); int m = LB.Length ( ); for ( int i = 1; i <= m; i++ ) { T x = LB.GetData(i); //在B中取一元素 int k = LA.Find (x); //在A中搜索它 if ( k == -1 ) //若未找到插入它 { LA.Insert (x,n++); } } } 时间复杂度: O(LA.Length()×LB.Length())
  • 79. 例3. 集合的“交”运算 利用两个线性表LA和LB分别表示两个集合A和B, 现要求一个新的集合A=A∩B。 步骤:①依次取LA中的元素 x=LA.GetData(i) ②在LB中找这个元素 LB.Find(x) ③如果LB中没有这个元素则在LA中删除 LA.Remove(x)
  • 80. 求“交”集 void Intersection(SeqList &LA, SeqList LB ) { int n = LA.Length ( ); int m = LB.Length ( ); int i = 1; while ( i <= n ) { T x = LA.GetData (i); //在LA中取一元素 int k = LB.Find (x); //在LB中搜索它 if ( k == -1 ) { LA.Remove (x); n--; } else i++; //未找到在LA中删除它 } }
  • 81. 例4. 已知线性表LA和线性表LB中的数据元 素按值非递减有序排列,现要求将LA和LB 归并为一个新的线性表LC,且LC中的元素 仍按值非递减有序排列。 此问题的思路如下: ①从线性表LA,LB中依次取得每个数据元素: LA.GetData(i), LB.GetData(j) ②较小者插入LC ③把没有插完的线性表中的元素全部插入 LC
  • 82. void MergeList(SeqList LA,SeqList LB,SeqList &LC) { int i=j=1;k=0; T xa,xb; int LALength=LA.Length(); int LBLength=LB.Length(); while((i<=LALength)&&(j<=LBLength)){ xa=LA.GetData(i); xb=LB.GetData(j); if(xa<=xb) {LC.Insert(xa,k++);++i;} else {LC.Insert(xb,k++);++j;} }
  • 83. while(i<=LALength){ xa=LA.GetData(i++); LC.Insert(xa,k++); } while(j<= LBLength){ xb=LB.GetData(j++); LC.Insert(xb,k++); } } 时间复杂度为: O(LA.Length()+LB.Length())
  • 84. 2.3 单链表(Singly Linked List) 一、单链表的概念 二、结点类的定义与实现 三、单链表类的定义与实现
  • 85. 顺序表的优缺点: 优点: ①无需为表示结点间的逻辑关系增加额外的存储 空间,存储利用率高。 ②随机访问 缺点: ①在表中插入或删除元素时,平均移动一半的元 素,运行效率很低。 ②占用连续空间 为了克服顺序表的缺点,采用链接方式存储线性 表。链接方式存储的线性表称作链表。
  • 86. 2.3.1 单链表的概念 用一组地址任意的存储单元存放线性表中的数据 元素。 元素(数据元素的映象)+ 指针(指示后继元素存储位置) = 结点(node) 单链表的一个存储结点包含两个部分: data link 构成的线性表为:
  • 87. 头指针 线性表为空表时, 头指针 头结点的指针域为空 空指针 头结点 a1 a2 … ... an ^ 以线性表中第一个数据元素 的存储a1 地址作为线性表的地址,称作线性表的 头指针(first,head)。 有时为了操作方便,在第一个结点 之前虚加一个“头结点”,以指向头结点的 指针为链表的头指针。
  • 89. 2.3.2 单链表的类定义 通常使用两个类:链表的结点类和链表类,协同 表示单链表。 定义方式主要有两种: 复合方式、 嵌套方式
  • 90. #include <iostream.h> #include <stdlib.h> class List; //前视定义,否则友元无法定义 class LinkNode{ 链表结点类的定义 friend class List; private: LinkNode *link; int data; public: LinkNode (LinkNode *ptr = NULL) {link=ptr;} LinkNode(const int & item, LinkNode *ptr = NULL) { data=item;link=ptr;} ~LinkNode(){}; };
  • 91. 单链表类的定义 class List { private: LinkNode *first; //指向头结点的头指针 public: List () { first = new LinkNode ();} //带头结点构造函数 List ( const int &x ) { //不带头结点构造函数 first = new LinkNode ( x ); }
  • 92. ~List (){makeEmpty(); delete first;} //析构函数 void MakeEmpty ( ); //链表置空 int Length ( ) const; //求链表长度 LinkNode * getHead() const {return first;} LinkNode *Find ( int x ); LinkNode *Locate ( int i ); DataType GetData ( int i ); void SetData (int x,int i );
  • 93. void Insert (int x, int i ); void Remove (int &x, int i ); int IsEmpty()const{ return(first->link==NULL? 1:0;} void input(DataType endTag); void output(); };
  • 95. void List :: MakeEmpty ( ) { //删去链表中除表头结点外的所有其它结点 LinkNode *q; while ( first→link != NULL ) { q = first→link; first→link = q→link; //将表头结点后第一个结点从链中摘下 delete q; //释放它 } };
  • 96. ②求单链表的长度:Length ( ) 思路: ①指针p指向first的下一结点,计数器为0; ②当p不为NULL: 计数器+1; p指向下一结点; ③返回计数器的值。
  • 97. 求表长 int List::Length ( ) const { LinkNode *p = first→link; //检测指针p指示第一个结点 int count = 0; while ( p != NULL ) { //逐个结点检测 count++; p = p→link; } return count; }
  • 98. ③在表中搜索数据x的结点:Find ( int x ) LinkNode * List::Find ( int x ){ LinkNode *p = first→link; //指针 p 指示第一个结点 while ( p != NULL && p→data != x ) p = p→link; return p; // p 在搜索成功时返回找到的结点地址 // p 在搜索不成功时返回空值 } 思路:①指针p指向first->link ②当链表没有结束且结点的数 据不为x, 则p指向下一结点。 ③返回p
  • 99. ④定位函数,返回表中第i个数据的地址 LinkNode * List::Locate ( int i ){ if ( i < 0 ) return NULL; LinkNode *p = first; int j = 0; while ( p != NULL && j < i ) // j = i 停 { p = p→link; j++; } return p; } 思路:①i<0,返回空 ② 指针p指向first ,j=0 ③当链表没有结束且结点的序号不为i, 则p指向下一结点,j加1。 ④返回p
  • 100. ⑤取出链表中第i个元素的值 DataType List::GetData( int i ) {//提取第 i 个结点的值 LinkNode *p = Locate( i ); // p 指向链表第 i 个结点 return p->data; } 教材上的程序和功能不完全一致p63 思路:①p指针定位到第i个元素 ② 返回p指针的数据域
  • 101. ⑥给链表中第i个元素赋值 void List::SetData(DataType x,int i ) {//给第 i 个结点赋值 if ( i <= 0 ) return; LinkNode *p = Locate ( i ); // p 指向链表第 i 个结点 if (p!=NULL) p->data=x; } 思路:① 若i<=0,返回 ② p指针定位到第i个元素 ③若p不为空,使指针的数据域为x ④返回
  • 102. ⑦将元素x插入到链表中第i个位置处 Insert (DataType x , int i) 有序对 <ai-1, ai>改变为 <ai-1, x> 和<x, ai> ai-1 ai x
  • 103. void List::Insert (DataType x , int i) {//在第i个结点处插入一个data域值为x的新结点 LinkNode *p = Locate ( i-1); LinkNode * newNode = new LinkNode (x) ; //构造新结点newNode newnode->link=p->link; p->link=newNode; //新结点插入第i个结点前 p ai-1 ai } newNode x 算法的时间复杂度为: O(Length())
  • 104. ⑧删除第i个结点并通过引用返回被删结点的data Remove (DataType &x , int i) 有序对<ai-1, ai> 和 <ai, ai+1> 改变为 <ai-1, ai+1> ai-1 ai ai+1
  • 105. void List::Remove (DataType &x, int i ) { LinkNode *p = Locate (i-1), *q; q = p->link; p->link = q->link; //重新链接 x = q->data; delete q; } p q ai-1 ai ai+1 算法的时间复杂度为: O(Length())
  • 107. ⑨单链表的输入(前插法) void List :: input (DataType endTag){ LinkNode *newnode; DataType val; first=new LinkNode (); if (first==NULL) {cerr<<"存储分配错误"<<endl;exit(1);} cin>>val; while(val!=endTag) { newnode=new LinkNode (val); if (newnode==NULL) {cerr<<"存储分配错误"<<endl;exit(1);} newnode->link=first->link; first->link=newnode; cin>>val; } }
  • 108. ⑩单链表的输出 void List ::output ( ) {//依次输出各结点的值 LinkNode *p=first->link; while(p!=NULL) { cout<<p->data<<endl; p=p->link; } }
  • 109. 2.4 线性链表的其它变形 例如 n = 8 m = 3 2.4.1 循环链表 0 7 8 1 1 6 7 2 2.4.2 双向链表 6 3 2 5 5 4 3 4
  • 110. 2.4.1 循环链表 对于单链表而言,最后一个结点的指针域是空指 针,如果将该链表头指针置入该指针域,则使得链表 头尾结点相连,就构成了单循环链表。 a1 a2 … ... an 和单链表的差别仅在于,判别链表中最后一个 结点的条件不再是“后继是否为空”,而是“后继是否 为头结点”。
  • 111. 循环链表的示例 first a0 a1 a2 an-1  带表头结点的循环链表 first a0 a1 an-1 (非空表) first (空表)
  • 113. 循环链表的搜索算法 first 31 48 15 57 搜索15 p p 搜索成功 p first 31 48 15 57 p p p p p 搜索25 搜索不成功 113
  • 114. 循环链表的搜索算法 CircListNode * CircList::Find( DataType x ) { //在链表中从头搜索其数据值为 x 的结点 CircListNode * p = first->link; while ( p!= first && p->data != x ) p = p->link; if ( p != first ) return p; //搜索成功 else return NULL; //搜索不成功 } first 31 48 15 57 搜索15 p
  • 115. 带尾指针的循环链表 22 31 48 15 57 rear 如果插入与删除仅在链表的两端发生,可 采用带表尾指针的循环链表结构。  在表尾插入: 时间复杂性 O(1)  在表尾删除: 时间复杂性 O(n)  在表头插入: 时间复杂性 O(1)  在表头删除: 时间复杂性 O(1) 思考:如何改进使在表尾删除的时间复杂度也为O(1)? 115
  • 116. 用循环链表求解约瑟夫问题 约瑟夫问题: n 个人围成一个圆圈,首先第 1 个人从 1 开始 ,一个人一个人顺时针报数, 报到第 m 个人,令 其出列。然后再从下一 个人开始,从 1 顺时针报 数,报到第 m 个人,再令其出列,…,如此下去, 直到圆圈中只剩一个人为止。此人即为优胜者。 用不带表头结点的循环链表来组织。 116
  • 117. 例如 n = 8 m = 3 0 0 0 7 7 7 8 1 1 8 1 1 8 1 1 6 7 2 6 7 2 6 7 2 6 3 2 6 3 2 6 3 2 5 5 4 5 5 4 5 5 4 3 3 3 4 4 4 0 0 0 7 7 7 8 1 1 8 1 1 8 1 1 6 7 2 6 7 2 6 7 2 6 3 2 6 3 2 6 3 2 5 5 4 5 5 4 5 5 4 3 3 3 4 4 4 117
  • 118. 0 0 0 7 7 7 8 1 1 8 1 1 8 1 1 6 7 2 6 7 2 6 7 2 6 3 2 6 3 2 6 3 2 5 5 4 5 5 4 5 5 4 3 3 3 4 4 4 n=8 m=3 算法讨论: 目前指针指向8,如何把8从链表中摘除? 118
  • 119. 求解Josephus问题的算法 #include <iostream.h> #include ―CircList.h‖ void Josephus(CircList & Js, int n, int m) { CircLinkNode *p = Js.getHead(); *pre = NULL; int i, j; for ( i = 0; i < n-1; i++ ) { //执行n-1次 for ( j = 1; j < m; j++) //数m-1个人 { pre = p; p = p->link; } cout << ―出列的人是” << p->data << endl; 119
  • 120. pre->link = p->link; delete p; //删去 p = pre->link; } }; void main() { CircList clist; int i, n m; cout << ―输入游戏者人数和报数间隔 : ‖; cin >> n >> m; for (i = 1; i <= n; i++ ) clist.insert(i, i); //约瑟夫环 Josephus(clist, n, m); //解决约瑟夫问题 } 120
  • 121. 2.4.2 双向链表 (Doubly Linked List) 双向链表是指在前驱和后继方向都能游历( 遍历)的线性链表。 双向链表每个结点结构: lLink data rLink 前驱方向   后继方向 双向链表通常采用带表头结点的循环链表形 式。 121
  • 122. first first 非空表 空表  结点指向 p == p->lLink->rLink == p->rLink->lLink rLink lLink p->lLink p p->rLink 122
  • 123. 双向循环链表的搜索算法 DblNode *DblList::Find (DataType x, int d) { //在双向循环链表中寻找其值等于x的结点, //按d确定 搜索方向,d为0,向左搜索, d为1,向右搜索 DblNode *p= (d == 0)?first->lLink : first->rLink; while ( p != first && p->data != x ) p = (d == 0) ? p->lLink : p->rLink; if ( p != first ) return p; //搜索成功 else return NULL; //搜索失败 }; 123
  • 124. 双向循环链表的插入算法 first 31 48 15 后插入25 p first 31 48 25 15 p newNode newNode->rLink = p->rLink; p->rLink = newNode; newNode->rLink->lLink = newNode; newNode->lLink = p;
  • 125. 双向循环链表的删除算法 first 31 48 15 非空表 删除48 p p->rLink->lLink = p->lLink; p->lLink->rLink = p->rLink; 125
  • 126. 2.5 单链表的应用 多项式 (Polynomial) Pn ( x ) a0 a1 x a2 x 2  an x n n i ai x i 0 n阶多项式 Pn(x) 有 n+1 项。  系数 a0, a1, a2, …, an  指数 0, 1, 2, …, n。按升幂排列 126
  • 127. 2.5.1 多项式的存储表示 第一种: 静态数组表示 在类的私有域中定义多项式的数据成员: private: int degree; float coef [maxDegree+1]; 则Pn(x)可以表示为: pl.degree = n, pl.coef[i] = ai, 0 i n 0 1 2 degree maxDegree coef a0 a1 a2 …… an ……… n 127
  • 128. 第二种:动态数组表示 在类的私有域中定义多项式的数据成员: private: int degree; float * coef; Polynomial :: Polynomial (int sz) { degree = sz; coef = new float [degree + 1]; } 以上两种存储表示适用于指数连续排列的 多项式。但对于多数项的系数为零的稀疏多项 式,如 P101(x) = 3+5x50128 101, 空间利用率太低。 -4x
  • 129. 第三种:只存储非零系数项的系数和指数 0 1 2 i m coef a0 a1 a2 …… ai …… am exp e0 e1 e2 …… ei …… em struct term { //多项式的项定义 float coef; //系数 int exp; //指数 }; static term termArray[maxTerms]; //项数组 static int free, maxTerms; //当前空闲位置指针 129
  • 130. 两个多项式存储的例子 A(x) = 2.0x1000+1.8 B(x) = 1.2 + 51.3x50 + 3.7x101 A.start A.finish B.start B.finish free maxTerms coef 1.8 2.0 1.2 51.3 3.7 …… exp 0 1000 0 50 101 …… 两个多项式存放在termArray中 131
  • 131. 第四种:多项式的链表存储表示  多项式顺序存储表示的缺点:  插入和删除时项数可能有较大变化,因此 要移动大量数据  不利于多个多项式的同时处理  采用多项式的链表表示可以克服这类困难:  多项式的项数可以动态地增长,不存在存 储溢出问题。  插入、删除斱便,不移动元素。 132
  • 132. 多项式的链表结构  在多项式的链表表示中,每个结点三个数据 成员: coef exp link  通过链接指针,可以将多项式各项按指数递 增的顺序链接成一个单链表。  在此结构上,新项的加入和废项的删除执行 简单的链表插入和删除操作即可解决。 133
  • 133. 2.6 静态链表  处理时中可以不改变各元素的物理位置,只 要重新链接就能改变这些元素的逻辑顺序。  它是利用数组定义的,在整个运算过程中存 储空间的大小不会变化。  静态链表每个结点由两个数据成员构成: data域存储数据,link域存放链接指针。  所有结点形成一个结点数组。 155
  • 134. 2.1 线性表 2.2 顺序表 2.3 单链表 2.4 线性链表的其它变形 2.5 单链表的应用:一元多项式及其运 算 作业:(p84)6,20,21,27,28
  • 135. 第三章 栈与队列 • 栈 • 队列 • 栈的应用:表达式求值 • 栈的应用:递归 • 队列的应用:打印杨辉三角形 • 优先级队列 157
  • 136. 3.1 栈 ( Stack ) 3.1.1 栈的定义 只允许在一端插入和删除的线性表。 退栈 进栈 允许插入和删除的一端称为栈顶 (top),另一端称为栈底(bottom) top an-1 • 特点 an-2 后进先出 (LIFO) a0 bottom 158
  • 137. 栈的抽象数据类型 class Stack { //栈的类定义 public: Stack(){ }; //构造函数 ~Stack(){ }; virtual void Push(DataType & x) ; //进栈 virtual bool Pop(DataType& x); //出栈 virtual bool getTop(DataType & x); //取栈顶 virtual bool IsEmpty(); //判栈空 virtual bool IsFull(); //判栈满 }; 栈的抽象数据类型有两种典型的存储表示,基 于数组的存储表示实现的栈称为顺序栈,基于链表 的存储表示实现的栈称为链式栈。 159
  • 138. 栈的数组存储表示 — 顺序栈 0 1 2 3 4 5 6 7 8 9 maxSize-1 elements top (栈空) typedef int DataType; class SeqStack { //顺序栈类定义 private: DataType *elements; //栈元素存放数组 int top; //栈顶指针 int maxSize; //栈最大容量 160
  • 139. void overflowProcess(); //栈的溢出处理 public: SeqStack(int sz =50); //构造函数 ~SeqStack() { delete []elements; } //析构函数 void Push(DataType &x); //进栈 bool Pop(DataType& x); //出栈 bool getTop(DataType& x); //取栈顶内容 bool IsEmpty() const { return top == -1; } bool IsFull() const { return top == maxSize-1; } int getSize() const {return top+1;} void MakeEmpty(){top=-1;} friend ostream&operator<<(ostream&os,SeqStack &S) }; 161
  • 140. top b top a a top 空栈 a 进栈 b 进栈 top e top e e d d top d c c c b b b a a a e 进栈 f 进栈溢出 e 退栈 162
  • 141. d top c c b top b b a a top a d 退栈 c 退栈 b 退栈 a top a 退栈 top 空栈 163
  • 142. ①顺序栈的构造函数 #include <assert.h> #include <iostream.h> SeqStack:: SeqStack(int sz){ elements=new DataType[sz]; assert(elements!=NULL); top=-1; maxSize=sz; } 断言(assert)机制是C++提供的一种功 能,若参数表中给定的条件满足,则继续执行
  • 143. ②顺序栈的溢出处理 void SeqStack::overflowProcess() { //私有函数:当栈满则执行扩充栈存储空间处理 DataType *newArray = new DataType [2*maxSize]; //创建更大的存储数组 for (int i = 0; i <= top; i++) newArray[i] = elements[i]; maxSize += maxSize; delete [ ]elements; elements = newArray; //改变elements指针 }; 思路:①创建更大的存储数组 ②把原来的元素复制到新数组中 ③改变栈大小,删除原来的数组, 栈指针指向新数组 165
  • 144. ③入栈操作 void SeqStack::Push(DataType & x) { //若栈满,则溢出处理,将元素x插入该栈栈顶 if (IsFull() == true) overflowProcess( ); //栈满 elements[++top] = x; //栈顶指针先加1, 再进栈 }; ④出栈操作 bool SeqStack::Pop(DataType & x) { //若栈不空,函数退出栈顶元素并将栈顶元素的值赋给x, //返回true,否则返回false if (IsEmpty() == true) return false; x = elements[top--]; //先取元素,栈顶指针退1 return true; //退栈成功 }; 166
  • 145. ⑤取栈顶元素 bool SeqStack::getTop(DataType& x) { //若栈不空则x为该栈栈顶元素 if (IsEmpty() == true) return false; x = elements[top]; return true; }; ⑥输出栈中元素的重载操作<< ostream& operator<<(ostream&os,SeqStack &S){ os<<―top=―<<S.top<<endl; for (int i=0;i<=S.top;i++) os<<i<<―:‖<<S. elements[i] <<endl; return os; 167 };
  • 146. 双栈共享一个栈空间 0 maxSize-1 V b[0] t[0] t[1] b[1] 两个栈共享一个数组空间V[maxSize] 设立栈顶指针数组 t[2] 和栈底指针数组 b[2] t[i]和b[i]分别指示第 i 个栈的栈顶与栈底 初始 t[0] = b[0] = -1, t[1] = b[1] = maxSize 栈满条件:t[0]+1 == t[1] //栈顶指针相遇 栈空条件:t[0] = b[0]或t[1] = b[1] //退到栈底 168
  • 147. 3.1.3 栈的链接存储表示 — 链式 栈 top ^ • 链式栈无栈满问题,空间可扩充 • 插入与删除仅在栈顶处执行 • 链式栈的栈顶在链头 • 适合于多栈操作 171
  • 148. 链式栈 (LinkedStack)类的定义 typedef int DataType; struct StackNode { //栈结点类定义 public: DataType data; //栈结点数据 StackNode *link; //结点链指针 StackNode(DataType d = 0, StackNode *next = NULL) : data(d), link(next) { } ~StackNode(); }; 172
  • 149. class LinkedStack : { //链式栈类定义 private: StackNode *top; //栈顶指针 public: LinkedStack() : top(NULL) {} //构造函数 ~LinkedStack() { makeEmpty(); } //析构函数 void Push(DataType &x); //进栈 bool Pop(DataType & x); //退栈 173
  • 150. bool getTop(DataType & x) const; //取栈顶 元素 bool IsEmpty() const { return (top == NULL)? true:false; } int getSize()const; void makeEmpty(); //清空栈的内容 friend ostream& operator << (ostream& os, LinkedStack& s) ; //输出栈元素的重载操作 << }; 174
  • 151. 链式栈类操作的实现 #include <iostream.h> ①清空栈操作 void LinkedStack::makeEmpty( ) { //逐次删去链式栈中的元素直至栈顶指针为空。 StackNode *p; while (top != NULL) { //逐个结点释放 p = top; top = top->link; delete p; } }; top ^ 175
  • 152. ②入栈操作 void LinkedStack::Push(DataType &x) { //将元素值x插入到链式栈的栈顶,即链头。 top = new StackNode(x, top); //创建新结点 assert (top != NULL); //创建失败退出 }; top x top ^ ③出栈操作 bool LinkedStack::Pop(DataType & x) { //删除栈顶结点, 返回被删栈顶元素的值。 if (IsEmpty() == true) return false; //栈空返回 StackNode *p = top; //暂存栈顶元素 top = top->link; //退栈顶指针 x = p->data; delete p; //释放结点 return true; 176 };
  • 153. ④取栈顶元素 bool LinkedStack::getTop(DataType & x) const { if (IsEmpty() == true) return false; //栈空返回 x = top->data; //返回栈顶元素的值 return true; }; top ⑤ ^ ostream& operator << (ostream& os, LinkedStack& S){ // //输出栈元素的重载操作 << os<<“栈中元素个数=”<<S.getSize()<<endl; StackNode *p=S.top;int i=0; while(p!=NULL) {os<<++i<<“:”<<p->data<<endl; p=p->link;} return os; 177 };
  • 155. 关于栈的进一步讨论 问题:当进栈元素的编号为1, 2, …, n时, 可能的出栈序列有多少种? 179
  • 156. 设进栈元素数为n,可能出栈序列数为mn:  n = 0,m0 = 1: 出栈序列{}。  n = 1,m1 = 1: 出栈序列{1}。  n = 2,m2 = 2:= m0*m1+m1*m0 a) 出栈序列中1在第1位。1进 1出 2进 2出, 出栈序列为{1, 2}。= m0*m1= 1 b) 出栈序列中1在第2位。1进 2进 2出 1出, 出栈序列为{2, 1}。= m1*m0 = 1  n = 3,m3 = 5: = m0*m2+m1*m1+m2*m0 a) 出栈序列中1在第1位。后面2个元素有m2 = 2个出栈序列:{1, 2, 3}, {1, 3, 2}。 180
  • 157. = m0*m2 = 2  出栈序列中1在第2位。1前有2后有3, 出栈序列为 {2, 1, 3}。= m1*m1 = 1  出栈序列中1在第3位。前面2个元素有 m2 = 2个出栈序列:{2, 3, 1}, {3, 2, 1}。 = m2*m0 = 2  n = 4,m4 = 14: = m0*m3+m1*m2+m2*m1+m3*m0 a) 出栈序列中1在第1位。后面3个元素有 m3 = 5个出栈序列: {1, 2, 3, 4}, {1, 2, 4, 3}, {1, 3, 2, 4}, {1, 3, 4, 2}, {1, 4, 3, 2}。 181
  • 158. = m0*m3 = 5 b) 出栈序列中1在第2位。前面有2,后面3、 4有m2 = 2个出栈序列: {2, 1, 3, 4}, {2, 1, 4, 3}。 = m1*m2 = 2 c) 出栈序列中1在第3位。前面2、3有m2 = 2个出栈序列,后面有4: {3, 2,1, 4}, {2, 3, 1,4}。 = m2*m1 = 2 d) 出栈序列中1在第4位。前面3个元素有 m3 = 5个出栈序列:{2, 3, 4, 1}, {2, 4, 3, 1}, {3, 2, 4, 1}, {3, 4, 2, 1}, {4, 3, 2, 1}。 = m3*m0 = 5 182
  • 159.  一般地,设有 n 个元素按序号1, 2, …, n 进栈,轮流让 1在出栈序列的第1, 第2, … 第n位,则可能的出栈序列数为: n -1 mi * mn i 1 m0 * mn 1 m1 * m n 2  mn 1 * m0 i 0 推导结果为: n 1 1 n n 1 C2n mi * mn i 1 i 0 183
  • 160. 3.1.4 栈的应用--数制转换 算法基于原理: N = (N div d)×d + N mod d
  • 161. 例如:(1348)10 = (2504)8 ,其 运算过程如下: N N div 8 N mod 8 计 1348 168 4 输 算 168 21 0 出 顺 顺 序 21 2 5 序 2 0 2
  • 163. void conversion () { SeqStack S; int N; int x=0; bool continue1; cin >>N; while (N) { S.Push( N % 8); N = N/8; } continue1=S.Pop(x); while (continue) { cout << x <<endl; continue=S.Pop(x); } } // conversion
  • 164. 3.1.5 栈的应用--括号匹配的检验 假设在表达式中 ([]())或[([ ][ ])]等为正确的格式, [( ])或([( ))或 (()])为不正确的格式。 则 检验括号是否匹配的方法可用“期待的急迫程度”这 个概念来描述。 例如:考虑下列括号序列: [ ( [ ] [ ] )] 分析可能出现的不匹配的情况: • 到来的右括弧并非是所“期待”的; • 直到结束,也没有到来所“期待”的括弧。
  • 165. 算法的设计思路: ①左括号入栈 ②右括号,检验栈空? 若空,表明右括号多了 匹配,栈顶的左括号出栈 非空,与栈顶元素比较 不匹配,出错 ③表达式检验结束时,若空,则匹配,若非 空,则表明左括号多了。
  • 166. bool matching(char *exp) { int state = 1; i = 0;L = Length(exp);char x; while (i<L && state) { switch (exp[i] ){ case ‗(‗,‘[‗:{S.Push(exp[i]); i++; break;} case‟)‘: { if(!S.StackEmpty()&&S.GetTop()==‗(‗ ) {S.Pop(char &x); i++;} else {state = 0;} break; } case‘]‘:{自己写出} } if (S.StackEmpty()&&state) return true else return false
  • 167. 3.1.6 栈的应用举例--表达式求值 1. 表达式的三种表示方法: 设 Exp = S1 + OP + S2 则称 OP + S1 + S2 为前缀表示法 S1 + OP + S2 为中缀表示法 S1 + S2 + OP 为后缀表示法
  • 168. 例如: Exp = a b + (c d / e) f 前缀式: + ab c/def 中缀式: a b + (c d / e) f 后缀式: ab cde/ f + 结论: 1)操作数之间的相对次序不变; 2)运算符的相对次序不同; 3)中缀式有操作符的优先级问题,还有可加括号改 变运算顺序的问题,所以编译程序一般不使用中缀表 示处理表达式。
  • 169. 例如: Exp = a b + (c d / e) f 前缀式: + ab c/def 中缀式: a b + (c d / e) f 后缀式: ab cde/ f + 结论: 4)前缀式的运算规则为: 连续出现的两个操作数和在它们之前且紧靠它们 的运算符构成一个最小表达式; 5)后缀式的运算规则为: 运算符在式中出现的顺序恰为表达式的运算顺序; 每个运算符和在它之前出现且紧靠它的两个操作数构成 一个最小表达式。
  • 171. 模拟一个简单的后缀表达式计算器 Calculator类的定义: class Calculator{ public: Calculator(int sz){S=sz;}; void Run(); //执行表达式计算 void Clear(); private: SeqStack S; void AddOperand(double value); //进操作数栈 bool Get2Operands(double&left, double&right); void DoOperator(char op);//形成运算指令,计算 };
  • 172. ①将操作数的值value入操作数栈 void Calculator:: AddOperand(double value){ S.Push(value); }; ②清栈 void Calculator:: Clear() { S.MakeEmpty(); };
  • 173. ③从操作数栈中取出两个操作数 bool Calculator::Get2Operands(double&left, double&right){ if (S.IsEmpty()) {cerr<<“缺少右操作数!”<<endl;return false;} S.Pop(right); if (S.IsEmpty()) {cerr<<“缺少左操作数!”<<endl;return false;} S.Pop(left); return true; };
  • 174. ④读字符串并求后缀表达式的值 void Calculator::Run{ char ch; double newoperand; while(cin.get(ch),ch!=„#‟){ switch(ch){ case „+‟:case „-‟: case „*‟: case „/‟: DoOperator(ch); break; default:cin.putback(ch); cin>>newOperand; AddOperand(newOperand); } } S.Pop(newoperand ); cout<< newoperand; };
  • 175. ⑤取两个操作数,根据操作符op计算 void Calculator:: DoOperator(char op){ double left,right,value; bool result; result = get2Operands(left, right) if (result) switch(op){ case „+‟:value=left+right;S.Push(value);break; case „-‟: value=left-right;S.Push(value);break; case „*‟: value=left*right;S.Push(value);break;
  • 176. case „/‟: if (right==0.0){ cerr<<“Divide by 0!”<<endl; Clear();} else{value=left/right;S.Push(value);} break; } else Clear(); }; void main(){ Calculator CALC(20); CALC.Run(); }
  • 177. 3.如何将中缀表示→转后缀表示? • 先对中缀表达式按运算优先次序加上括号,再 把操作符后移到右括号的后面并以就近移动为 原则,最后将所有括号消去。 • 如中缀表示 (A+B)*D-E/(F+A*D)+C,其转换为 后缀表达式的过程如下: ( (( (A+ B ) * D ) – ( E / ( F+ (A* D ) )) ) + C ) 后缀表示 A B + D * E F A D * + / - C + 201
  • 178. 如何将中缀表示→转前缀表示? • 先对中缀表达式按运算优先次序通统加上括 号,再把操作符前移到左括号前并以就近移 动为原则,最后将所有括号消去。 • 如中缀表示 (A+B)*D-E/(F+A*D)+C,其转 换为前缀表达式的过程如下: ( ( ( (A+ B ) * D ) – ( E / ( F+ (A* D ) ) ) ) + C ) 前缀表示 + - * + A B D / E + F * A D C 202
  • 179. 计算机如何将中缀表示转换为后缀表示? 使用栈可将表达式的中缀表示转换成它的前缀表 栈 • 示和后缀表示。 • 为了实现这种转换,需要考虑各操作符的优先级。 各个算术操作符的优先级 操作符 ch # ( *, /, % +, - ) isp (栈内) 0 1 5 3 6 icp (栈外) 0 6 4 2 1 • isp叫做栈内(in stack priority)优先数 • icp叫做栈外(in coming 203 priority)优先数。
  • 180. 栈顶算符为θ1: θ 1 θ 2 + - * / ( ) # + > > < < < > > - > > < < < > > * > > > > < > > / > > > > < > > ( < < < < < = X ) > > > > X > > # < < < < < X =
  • 181. 中缀表达式转换为后缀表达式的算法 • 例:中缀表达式为A+B*(C-D)-E/F,求其后缀表 达式。 • ①操作符栈初始化,将结束符‘#‟进栈。然后读 入中缀表达式字符流的首字符ch。 • ②重复执行以下步骤,直到ch = „#‟,同时栈 顶的操作符也是‘#‟,停止循环。 205
  • 182. a.若ch是操作数直接输出,读入下一个字符ch。 b.若ch是操作符,判断ch的优先级icp和位于栈顶的 操作符op的优先级isp:  若 icp(ch) > isp(op),令ch进栈,读入下一个字符 ch。  若 icp(ch) < isp(op),退栈并输出。  若 icp(ch) == isp(op),退栈但不输出,若退出的 是“(”号读入下一个字符ch。 • ③算法结束,输出序列即为所需的后缀表达式。 • 例:中缀表达式为A+B*(C-D)-E/F,求其后缀表达式。 操作符 ch # ( *, /, % +, - ) ABCD-*+EF/- isp (栈内) 0 1 5 3 6 icp (栈外) 0 6 4 206 2 1
  • 183. 中缀表达式转换为后缀表达式的算法: void postfix(expression e) //把中缀表达式e转换成后缀表示并输出 Stack S; char ch=„#‟,ch1,op; S.Push(ch);cin.get(ch); While(S.IsEmpty()==false&&ch!=„#‟) if (isdigit(ch)){cout <<ch;cin.get(ch);} else {S.getTop(ch1); if (isp(ch1)<icp(ch)){S.Push(ch);cin.get(ch);} else if (isp(ch1)>icp(ch)) {S.Pop(op);cout<<op;} else { S.Pop(op); if(op==„(„) cin.get(ch); } } 207 };
  • 184. 4.如何应用中缀表示计算表达式的值 a+b*(c-d)-e/f rst1 rst4 rst2 rst3 rst5  使用两个栈,操作符栈OPTR (operator), 操作数栈OPND(operand)  为了实现这种计算,需要考虑各操作符的 优先级 208
  • 185. 中缀算术表达式求值  对中缀表达式求值的一般规则: 1. 建立并初始化OPTR栈和OPND栈,然后 在OPTR栈中压入一个“#” 2. 扫描中缀表达式,取一字符送入ch。 3. 当ch != „#‟ 或OPTR栈的栈顶 != „#‟时, 执 行以下工作, 否则结束算法。在OPND栈 的栈顶得到运算结果。 209
  • 186. ①若ch是操作数,进OPND栈,从中缀表达式取下一字符 送入ch; ②若ch是操作符,比较icp(ch)的优先级和isp(OPTR)的优 先级: 若icp(ch) > isp(OPTR),则ch进OPTR栈,从中缀表达式 取下一字符送入ch; 若icp(ch) < isp(OPTR),则从OPND栈退出a2和a1,从 OPTR栈退出θ, 形成运算指令 (a1)θ(a2),结果进OPND栈;  若icp(ch) == isp(OPTR) 且ch == ')',则从OPTR栈退出 '(',对消括号,然后从中缀表达式取下一字符送入ch; 操作符 ch # ( * , / , %+, - ) a+b * (c-d)-e/f i sp( 栈内) 0 1 5 3 6 i cp( 栈外) 210 0 6 4 2 1
  • 187. void InFixRun() { SeqStack <char> OPTR, SeqStack <double>OPND; char ch, op; double x; OPTR.Push(„#‟); cin.get (ch); //读入一个字符 op = '#' ; while (ch != '#' || op != '#') { if (isdigit(ch)) //是操作数, 进栈 { cin.putback(ch);cin>>x OPND.Push(x); cin.get(ch); } else { //是操作符, 比较优先级 OPTR.GetTop(op); //读一个操作符 211
  • 188. if (icp(ch) > isp(op)) //栈顶优先级低 { OPTR.Push (ch); cin.get(ch); } else if (icp(ch) < isp(op)) { OPTR.Pop(op); //栈顶优先级高 DoOperator(op); } else if (ch == „)‟) //优先级相等 { OPTR.Pop(op); cin.get(ch); } } OPTR.GetTop(op); } /*end of while*/ } 212
  • 189. 3.2 栈与递归 • 递归的定义 若一个对象部分地包含它自己,或用它自己 给自己定义, 则称这个对象是递归的;若一 个过程直接地或间接地调用自己, 则称这个 过程是递归的过程。 • 以下三种情况常常用到递归方法。  定义是递归的  数据结构是递归的  问题的解法是递归的 213
  • 190. 1. 定义是递归的 例如,阶乘函数 1, 当n 0时 n! n (n 1)!, 当 n 1时 求解阶乘函数的递归算法 long Factorial(long n) { if (n == 0) return 1; else return n*Factorial(n-1); } 214
  • 191. 求解阶乘 n! 的过程 主程序 main : fact(4) 参数 4 计算 4*fact(3) 返回 24 递 参 结 回 归 数 参数 3 计算 3*fact(2) 返回 6 果 归 调 传 返 求 用 递 参数 2 计算 2*fact(1) 返回 2 回 值 参数 1 计算 1*fact(0) 返回 1 参数 0 直接定值 = 1 返回 1 215
  • 192. 2.数据结构是递归的 • 例如,单链表结构 f f • 一个结点,它的指针域为NULL,是一个单 链表; • 一个结点,它的指针域指向单链表,仍是一 个单链表。 216
  • 193. 搜索链表最后一个结点并打印其数值 void Print(ListNode *f) { if (f ->link == NULL) cout << f ->data << endl; else Print(f ->link); } 递归找链尾 f a0 a1 a2 a3 a4 f f f f 217
  • 194. 在链表中寻找等于给定值的结点并打印其数值 void Print(ListNode*f, DataType value) { if (f != NULL) if (f -> data == value) cout << f -> data << endl; else Print(f -> link, value); } 递归找含value值的结点 f x f f f 218
  • 196. 3. 问题的解法是递归的 • 汉诺塔(Tower of Hanoi)问题的解法: 如果 n = 1,则将这一个盘子直接从 A 柱移 到 C 柱上。否则,执行以下三步: ① 用 C 柱做过渡,将 A 柱上的 (n-1) 个盘 子移到 B 柱上; ② 将 A 柱上最后一个盘子直接移到 C 柱上; ③ 用 A 柱做过渡,将 B 柱上的 (n-1) 个盘 子移到 C 柱上。 220
  • 197. • 解决方法: n=1时,直接把圆盘从A移到C – n>1时, – ①先把上面n-1个圆盘从A移到B ②然后将n号盘从A移到C – ③再将n-1个盘从B移到C。 – 即把求解n个圆盘的Hanoi问题转化为求解n-1个圆盘的 Hanoi问题,依次类推,直至转化成只有一个圆盘的Hanoi 问题 – 执行情况: » 递归工作栈保存内容:形参n,x,y,z和返回地址 » 返回地址用行编号表示 n x y z 返回地址
  • 198. main() { int m; printf("Input number of disks:‖); scanf("%d",&m); hanoi(m,'A','B','C'); (0) } void hanoi(int n,char x,char y,char z) (1) { (2) if(n==1) (3) move(1,x,z); (4) else{ (5) hanoi(n-1,x,z,y); (6) move(n,x,z); (7) hanoi(n-1,y,x,z); (8) } (9) }
  • 199. 构成递归的条件 • 不能无限制地调用本身,必须有一个出口, 化简为非递归状况直接处理。 Procedure <name> ( <parameter list> ) { if ( < initial condition> ) //递归结束条件 return ( initial value ); else //递归 return (<name> ( parameter exchange )); } 223
  • 200. 3.2.2递归过程与递归工作栈 • 递归过程在实现时,需要自己调用自己。 • 层层向下递归,退出时的次序正好相反: 递归调用 n! (n-1)! (n-2)! 1! 0!=1 返回次序 • 主程序第一次调用递归过程为外部调用; • 递归过程每次递归调用自己为内部调用。 • 它们返回调用它的过程的地址不同。 224
  • 201. long Factorial(long n) { int temp; if (n == 0) return 1; else temp = n * Factorial(n-1); RetLoc2 return temp; } void main() { int result; result = Factorial(4); RetLoc1 cout << result <<endl; } 225
  • 202. 1. 递归工作栈 • 每一次递归调用时,需要为过程中使用的参 数、局部变量等另外分配存储空间。 • 每层递归调用需分配的空间形成递归工作记 录,按后进先出的栈组织。 • 栈顶的工作记录是当前正在执行的这一层的 工作记录。称之为活动记录。 活动 局部变量 递归 记录 返回地址 工作记录 框架 参 226 数
  • 203. 函数递归时的活动记彔 调用块 ………………. <下一条指令> Function(<参数表>) 函数块 ………………. <return> 返回地址(下一条指令) 局部变量 参数 227
  • 204. 2. 递归过程改为非递归过程 • 递归过程简洁、易编、易懂 • 递归过程效率低,重复计算多 • 改为非递归过程的目的是提高效率 • 单向递归和尾递归可直接用迭代实现其 非递归过程 • 其他情形必须借助栈实现非递归过程 229
  • 205. 斐波那契数列的函数Fib(n)的定义 n, n 0,1 Fib(n) Fib(n 1) Fib(n 2), n 1 如 F0 = 0, F1 = 1, F2 = 1, F3 = 2, F4 = 3, F5 = 5 求解斐波那契数列的递归算法 long Fib(long n) { if (n <= 1) return n; else return Fib(n-1)+Fib(n-2); } 230
  • 206. 时间复杂度: 2 n Fib(5) Fib(4) Fib(3) Fib(3) Fib(2) Fib(2) Fib(1) Fib(2) Fib(1) Fib(1) Fib(0) Fib(1) Fib(0) Fib(1) Fib(0) 斐波那契数列的递归调用树 调用次数 NumCall(k) = 2*Fib(k+1)-1 如 F0 = 0, F1 = 1, F2 = 231 F3 = 2, F4 = 3, F5 = 5 1,
  • 207. 单向递归用迭代法实现 long FibIter(long n) { if (n <= 1) return n; long twoback = 0, oneback = 1, Current; for (int i = 2; i <= n; i++) { Current = twoback + oneback; twoback = oneback; oneback = Current; } return Current; } 232
  • 208. 尾递归用迭代法实现 25 36 72 18 99 49 54 63 void recfunc(int A[ ], int n) { //从n到0输出数组各项的值。 if (n >= 0) { cout << “value:”<<A[n] << endl; n--; recfunc(A, n); } 尾递归:递归调用语句只有一个, } 而且放在过程的最后 233
  • 209. void sterfunc(int A[ ], int n) { //消除了尾递归的非递归函数 while (n >= 0) { cout << "value:" << A[n] << endl; n--; } } 234
  • 210. 3.2.3 递归与回溯 对一个包含有许多结点,且每个结点有多 个分支的问题,可以先选择一个分支进行搜索。 当搜索到某一结点,发现无法再继续搜索下去 时,可以沿搜索路径回退到前一结点,沿另一 分支继续搜索。 如果回退之后没有其它选择,再沿搜索路 径回退到更前结点,…。依次执行,直到搜索 到问题的解,或搜索完全部可搜索的分支没有 解存在为止。 (p110迷宫问题) 回溯法与分治法本质相同,可用递归求解。 235
  • 211. 3.3 队列 ( Queue ) a0 a1 a2 an-1 front rear • 定义  队列是只允许在一端删除,在另一端插入 的线性表  允许删除的一端叫做队头(front),允许插 入的一端叫做队尾(rear)。 • 特性:先进先出(FIFO, First In First Out) 236
  • 212. 队列的抽象数据类型 class Queue { public: Queue() { }; //构造函数 ~Queue() { }; //析构函数 virtual bool EnQueue(DataType x) = 0; //进队列 virtual bool DeQueue(DataType& x) = 0; //出队列 virtual bool getFront(DataType& x) = 0; //取队头 virtual bool IsEmpty() const = 0; //判队列空 virtual bool IsFull() const = 0; //判队列满 }; 237
  • 213. 3.3.2 队列的数组存储表示 ─顺序队 列 队列的进队和出队 A front rear 空队列 front rear A进队 A B A B C D front rear B进队 front rear C, D进队 B C D C D front rear A退队 front rear B退队 假溢出 C D E F G C D E F G front rear E,F,G进队 front rear H进队,溢出 238
  • 214. 队列的进队和出队原则 进队时先将新元素按 rear 指示位置加入,再将队 尾指针加一 rear = rear + 1。 队尾指针指示实际队尾的后一位置。 出队时先按队头指针指示位置取出元素,再将队 头指针进一 front = front + 1, 队头指针指示实际队头位置。 队满时再进队将溢出出错; 队空时再出队将队空处理。 解决假溢出的办法? 将队列元素存放数组首尾相接,形成循环 (环形)队列。 239
  • 215. 6 7 front 6 7 front 6 7 front rear 5 0 5 0 5 A A0 4 1 4 1 4 B1 rear C 3 2 3 2 rear 3 2 空队列 A进队 B, C进队 6 7 6 7 6 7 5 5 0 5 GH A0 F I 0 4 B1 4 B 1 4 E 1 rear C C DC rear 3 2 front rear 3 2 front 3 2 front A退队 B退队 D,E,F,G,H,I 进队(队满) 240
  • 216. 6 7 front rear 5 0 循环队列 (Circular Queue) 4 1 3队列存放数组被当作首尾相接的表处理。 2 空队列 队头、队尾指针加1时从maxSize-1直接进到0, 可用语言的取模(余数)运算实现。 队头指针进1: front = (front+1) % maxSize; 队尾指针进1: rear = (rear+1) % maxSize; 队列初始化: front = rear = 0; 队空条件: front == rear; 队满条件: (rear+1)241 maxSize == front %
  • 217. 循环队列的类定义 #include <assert.h> #include <iostream.h> class SeqQueue { //队列类定义 protected: int rear, front; //队尾与队头指针 DataType*elements; //队列存放数组 int maxSize; //队列最大容量 public: SeqQueue(int sz = 10); //构造函数 242
  • 218. ~SeqQueue() { delete[ ] elements; } //析构函数 bool EnQueue(DataType x); //新元素进队列 bool DeQueue(DataType& x); //退出队头元素 bool getFront(DataType& x); //取队头元素值 void makeEmpty() { front = rear = 0; } bool IsEmpty() const { return front == rear; } bool IsFull() const { return ((rear+1)% maxSize == front); } int getSize() const { return (rear-front+maxSize) % maxSize; } friend ostream& operator <<(ostream& os, SeqQueue &Q);//输出队列中元素的重载操作<< }; 243
  • 219. 循环队列操作的定义 ①构造函数 SeqQueue::SeqQueue(int sz) { //构造函数 front=0; rear=0; maxSize=sz; elements = new DataType[maxSize]; assert ( elements != NULL ); }; 244
  • 220. bool SeqQueue::EnQueue(DataType &x) { //若队列不满, 则将x插入到该队列队尾, 否则返回 if (IsFull() == true) return false; elements[rear] = x; //先存入 rear = (rear+1) % maxSize; //尾指针加一 return true; }; bool SeqQueue::DeQueue(DataType& x) { //若队列不空则函数退队头元素并返回其值 if (IsEmpty() == true) return false; x = elements[front]; //先取队头 front = (front+1) % maxSize; //再队头指针加一 return true; }; 245
  • 221. 3.3.3 队列的链接存储表示 — 链式队 列 front rear • 队头在链头,队尾在链尾。 • 链式队列在进队时无队满问题,但有队空问 题。 • 队空条件为 front == NULL 246
  • 222. 链式队列类的定义 #include <iostream.h> struct QueueNode { //队列结点类定义 private: DataType data; //队列结点数据 QueueNode *link; //结点链指针 public: QueueNode(DataType d = 0, QueueNode *next = NULL) : data(d), link(next) { } }; 247
  • 223. class LinkedQueue { private: QueueNode *front, *rear; //队头、队尾指针 public: LinkedQueue() : rear(NULL), front(NULL) { } ~LinkedQueue(MakeEmpty()); bool EnQueue(DataType &x); bool DeQueue(DataType & x); bool GetFront(DataType & x); void MakeEmpty(); bool IsEmpty() const { return (front == NULL)?true:false ;} int getSize( )const; friend ostream& operator<<(ostream&os, LinkedQueue &Q) 248 };
  • 224. ①置空队列操作 LinkedQueue::MakeEmpty() { //释放链表中所有结点 QueueNode *p; while (front != NULL) { //逐个释放结点 p = front; front = front->link; delete p; } }; front ^ p rear 249