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

680
数据结构 用面向对象方法与C++语言描述 第二版 殷人昆编著 华大学出版社 课件 课件由迷若烟雨提供 烟雨科技有限责任公司

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

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

数据结构

用面向对象方法与C++语言描述

第二版殷人昆编著

清华大学出版社

课件

本课件由迷若烟雨提供

烟雨科技有限责任公司

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

课程介绍

1. 为什么要学习数据结构?

2. 该课程的主要内容是什么?如何学习?

3. 考核方式?

总成绩=平时( 40% )+期末(60%)

平时=上机实习+平时作业+上课回答问题

+4次课内考试(每次5分)

4. 教材:殷人昆,《数据结构(面向对象方法与C++语言描述)》(第2版)清华大学出版社

1. 为什么要学习数据结构?

2. 该课程的主要内容是什么?

数据结构=数据+结构(关系)

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

数据结构

数据(的集合) 结构

物理结构逻辑结构

线

性结

集合

续非连

序表

序存储

式存储

接矩阵

存储

接表存储

序存储

式存储

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

如何学习?

1. 预习:粗读教材,发现问题

2. 听课:重点、难点,初步解决问题

3. 复习:细读教材,解决问题(理解抽象数

据类型、看懂C++类的定义和实现)

4. 做题:巩固知识(C++类的定义和实现)

5. 实习:验证(调试程序)

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

上机实习作业:1. 顺序表及其应用

2. 链表及其应用.doc

3. 栈及其应用

4. 队列及其应用

5. 稀疏矩阵及其基本操作

6. 二叉树及其基本操作

7. 堆及其基本操作

8. Huffman树及其基本操作

9. 图及其基本操作

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

第一章 绪论

内容提要:

本章主要介绍数据结构的基本概念、

术语,以及有关算法的描述、分析的基本常识。

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

第一章 绪论§1.1 数据结构的概念

1.1.1 为什么要讨论数据结构?

同时出现了如下一些变化:

计算机科学是研究信息表示和信息处理的科学;

信息是当今社会的重要资源;

计算机产业及计算机科学技术飞速发展,计算机的硬件技

术和软件技术都有了极大的发展,计算机应用也已经渗透到

了社会的各个领域。

计算机由最初的单一科学计算到几乎无所不能;

加工处理的对象由数值型变为数值型和非数值型;

处理的数据量由小变为大、巨大(海量存储、计算);

数据之间的关系由简单变复杂、很复杂;

通过前面的学习,我们知道:

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

第一章 绪论

思路2:将一“大堆杂乱无章”的数据交给计算机处理是很不明智的,

结果是加工处理的效率会非常低,有时甚至根本无法进行。

于是:人们开始考虑如何更有效地描述、表示、处理数据的问题,

除了不断提高计算机技术外(其它课程),很重要的一个方面

是通过研究、分析问题数据本身的特点,利用这些特点提高数

据表示和处理的效率。——这就是数据结构

下面举几个例子,目的是加深对数据结构的理解,从中也

可以看出研究数据结构的重要性!

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

解决思路1:发展硬件技术,开发出更快、更高级的硬件产品,

但有没有其它途径呢?

§1.1 数据结构的概念

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

例1 书目自动检索系统

登录号:

书名:

作者名:

分类号:

出版单位:

出版时间:

价格:

书目卡片001 高等数学 樊映川 S01

002 理论力学 罗远祥 L01

003 高等数学 华罗庚 S01

004 线性代数 栾汝书 S02

…… …… …… ……

书目文件

按书名 按作者名 按分类号

高等数学 001,003……

理论力学 002,……..

线性代数 004,……

…… ……..

樊映川 001, …

华罗庚 002, ….

栾汝书 004, ….

……. …….

L 002, …

S 001, 003,

…… ……

索引表

线性

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

例1 书目自动检索系统

问题:图书管理,完成书目的自动检索。

数据:一本本书籍,更确切地说是每本书的

信息,如:书名、作者、出版社、出版日期、

书号、内容提要等等。

操作:书目检索、入库、借书、还书等。

涉及到:书目数据逻辑组织、存储方法;检

索、入库等操作实现算法。

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

例2 人机对奕问题(井字棋)树

……..……..

…... …... …... …...

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

例2 人机对奕问题

问题:人机对弈,即人与计算机下棋。

数据:各种棋局状态,确切说就是描述棋盘

格局的信息。

操作:走棋,即选择一种策略使棋局状态发

生变化(由一个格局派生出另一个格局)

涉及到:“格局‖数据逻辑组织、存储方法;走

棋操作实现算法。

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

例3 多叉路口交通灯管理问题

C

E

D

A

B

AB AC AD

BA BC BD

DA DB DC

EA EB EC ED

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

例3 多叉路口交通灯管理问题

问题:多叉路口的交通灯管理,即在多叉路口应怎样设置交通灯,以保证交通畅通。

数据:路口各条道路的信息 操作:设置信号灯(求出各个可以同时通行的路的集合)

涉及到:路口各条道路信息数据及其关系的逻辑组织、存储方法;信号灯设置的操作实现算法。

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

第一章 绪论§1.1 数据结构的概念

[数据对象 Data Object]具有相同性质的数据成员(数据元素)的

集合,是数据的子集。

[结构 Structure ] 数据元素之间的关系(Relation)。

[数据结构 Data Structure ]由一个数据对象以及该对象中的所有

数据元素之间的关系组成,即带结构的数据元素的集合。

[数据项 Data Item] 构成数据元素的成份,是数据不可分割的最小单位 。

1.1.2 数据结构及其术语

[数据 Data] 用于描述客观事物的数值、字符等一切可以输

入到计算机中,并由计算机加工处理的符号集合。[数据元素 Data Element] 表示一个事物的一组数据称作一个

数据元素。如学生信息可包括:学号、姓名、性别等。

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

第一章 绪论

数据结构=数据+结构

记作 Data_Structure=(D,R)

其中:Data_Structure是数据结构的名称

D是数据元素的有限集合(一般为一个数据对象)

R是D上关系的有限集

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

§1.1 数据结构的概念

我们研究数据结构的目的是要利用数据之间的关系(结构),

因此,在存储时既要存储数据本身,还要存储关系!!

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

第一章 绪论

数据在计算机中的存储:两种形式

连续:数据元素逐个连续存放(通过物理相邻来表示关系)

非连续:数据元素不连续存放(如何表示关系?)

[数据的存储结构]:是数据结构在计算机内的表示,它包括数据

元素的表示和关系的表示。

这样:一个数据结构要存放,一方面要把数据元素存起来,

另一方面,还必须把数据元素之间的逻辑关系也表示出来,那么:

要么用数据元素在物理上的相邻来表示逻辑关系要么用数据元素的存储地址、索引表、散列函数来表示逻辑关系

顺序存储结构 链式存储、索引存储、散列存储

§1.1 数据结构的概念

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

第一章 绪论

1.2.1 数据类型 Data Type

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

(1)高级语言中的数据类型实际上包括:数据的逻辑结构、数据的存储结构及所定义的操作的实现。

(2)高级语言中的数据类型按值的不同特性分为:

原子类型(如整型、实型、字符型、布尔型)

结构类型(由原子类型按照一定的规则构造而成,

如数组、结构体)

(3)数据类型逻辑概念与其在计算机程序中的实现有很重要的区

别,例如线性表数据类型有两种传统的实现方式:基于数组的顺序

表示和基于链表的链式表示;(4)我们可以撇开计算机不考虑,现实中的任何一个问题都可以

定义为一个数据类型——称为抽象数据类型。

§1.2 数据结构的抽象形式

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

第一章 绪论§1. 2 数据结构的抽象形式

抽象数据类型=数学模型+操作 (其它教材上对ADT的描述)

=数据结构+操作=数据+结构+操作

它是一种描述用户和数据之间接口的抽象模型,亦即它给出

了一种用户自定义的数据类型。

三元组表示:(D,R,P)其中D是数据对象,R是D上的关系集,P是对D的基本操作集。

ADT 抽象数据类型名

{

数据对象:<数据对象的定义>

数据关系:<数据关系的定义>

基本操作:<基本操作的定义>

}ADT 抽象数据类型名

抽象数据类型的作用:抽象数据类型可以使我们更容易描述现实世

界。例:用线性表描述学生成绩表,用树描述棋局关系等。

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

内容回顾:

1.数据结构=数据+结构=(D,R)

2.结构包括:

逻辑结构:集合、线性、树型、图

物理结构(存储结构):连续、非连续

3.数据类型:(D,R,P)

4.抽象数据类型: (D,R,P)

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

第一章 绪论§1.3 作为ADT的C++类

1.3.1 面向对象的概念

面向对象 = 对象+类+继承+通信

对象:在应用问题中出现的各种实体、事件、规

格说明等。由一组属性值和在这组值上的一组服

务(或称操作)构成。

类 (class),实例 (instance)

具有相同属性和服务的对象归于同一类,形成类。类中的一个对象为该类的一个实例。

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

继承:是面向对象方法最有特色的方面。

• 派生类:载重车,轿车,摩托车,…

子类、特化类(特殊化类)

• 基类:车辆

父类、超类、泛化类(一般化类)

各派生类中的公共部分,包括属性和服务,集

中到基类中,派生类中只保留自己特有的属性和

服务。这样减少了数据的存储和程序代码的重复

– 通信

• 各个类的对象间通过消息传递进行通信。

– 消息:一个类的对象要求另一个类的对象执行某个服务的指

令,必要时还要传递调用参数。

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

第一章 绪论

传统的大型结构化程序设计是面向过程的,首先着眼于系

统要实现的功能。一个数据结构可能被多个过程调用,修改数

据结构,意味着必须修改相关的过程,这样做烦琐又容易产生

错误。

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

位。

“类”具有封装、数据抽象、继承和多态等特点!

§ 1.3 作为ADT的C++类

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

第一章 绪论

1.3.2 C++中的类

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

圆是平面上与圆心等距离的所有点的集合。从图形显示角

度看,圆的抽象数据类型包括圆心和半径;而从计量角度看,

它所需要的抽象数据类型只需半径即可。如果从计量角度来

给出圆的抽象数据类型,那么它的数据取值范围应为半径的

取值范围,即为非负实数,而它的操作形式为确定圆的半径

(赋值);求圆的面积;求圆的周长。

§ 1.3 作为ADT的C++类

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

第一章 绪论

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

ADT定义:

ADT circle

data : 0 r ,实数structure: NULL

operations: area {计算面积 s=πr2}

circumference {计算周长 l=2πr}

END ADT

§ 1.3 作为ADT的C++类

Page 27: 数据结构(用面向对象方法与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++类

Page 28: 数据结构(用面向对象方法与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 作为ADT的C++类

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

第一章 绪论

C++对于面向对象程序设计的支持,核心部分就是类的定义,

类的定义体现了抽象数据类型的思想:

•说明与实现的分离

•信息隐蔽

§1.3.2 C++中的类

对类的成员规定三级存取:

• 公共(public):其它类的对象或操作可访问,构成类的接口

• 私有 (private):只能该类的对象和成员函数及友元访问

• 保护 (protected):除了具有私有成员的特性,允许该类的

派生类访问

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

第一章 绪论§1.4 算法定义

1.算法的定义和特性

算法 (Algorithm ): 算法是解决某一特定任务的指令的有限序列。

算法具有以下五个特性:

(1)有穷性 一个算法必须总是在执行有穷步之后结束,且每一步都在有穷时间内完成。

(2)确定性 算法中每一条指令必须有确切的含义。不存在二义性。且算法只有一个入口和一个出口。

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

(3)可行性 一个算法是可行的。即算法描述的操作都是可以通过已经实现的基本运算执行有

限次来实现的。

(4)输入 一个算法有零个或多个输入,这些输

入取自于某个特定的对象集合。

(5)输出 一个算法有一个或多个输出,这些输

出是同输入有特定关系的量。

第一章 绪论§1.4 算法定义

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

第一章 绪论

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

算法+数据结构=程序

在计算机解决问题的过程中算法与数据结构是缺一不

可的两个方面:问题的数据组织——数据结构

数据的处理——算法

算法与数据结构是相辅相承的。解决某一特定类型问

题的算法可以选定不同的数据结构,而且选择恰当与否直

接影响算法的效率。反之,一种数据结构的优劣由各种算

法的执行来体现。

§1.4 算法定义

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

怎样才能设计一个好的算法呢?

要设计一个好的算法通常要考虑以下的要求:

⑴正确:算法的执行结果应当满足预先规定的功能和性能

要求。

⑵可读:一个算法应当思路清晰、层次分明、简单明了、

易读易懂。

⑶健壮:当输入不合法数据时,应能作适当处理,不至引

起严重后果。

⑷高效:有效使用存储空间和有较高的时间效率。

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

第一章 绪论

1. 5.1 算法的描述

1. 自然语言描述:容易,但有时罗嗦、有二义性

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

4. 算法语言(伪代码):严谨、简洁,易程序实现

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

§1.5 算法的性能分析与度量

为了解决理解与执行这两者之间的矛盾,人们常常使用一

种称为伪码语言的描述方法来进行算法描述。伪码语言介于高

级程序设计语言和自然语言之间,它忽略高级程序设计语言中

一些严格的语法规则与描述细节,因此它比程序设计语言更容

易描述和被人理解,而比自然语言更接近程序设计语言。它虽

然不能直接执行但很容易被转换成高级语言。

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

第一章 绪论

我们可以从一个算法的时间复杂度与空间复杂度来评价算

法的优劣。

一个算法转换成程序并在计算机上执行时,其运行所需要

的时间取决于下列因素:

⑴硬件的速度。例如使用486机还是使用586机。⑵书写程序的语言。实现语言的级别越高,其执行效率就越低。

⑶编译程序所生成目标代码的质量。对于代码优化较好的编译程

序其所生成的程序质量较高。

⑷问题的规模。例如,求100以内的素数与求1000以内的素数其执行时间必然是不同的。

§1.5 算法的性能分析与度量

如何评价算法的优劣呢?

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

第一章 绪论

显然,在各种因素都不能确定的情况下,很难比较出算法

的执行时间。

也就是说,使用执行算法的绝对时间来衡量算法的效率是不合适的。为此,可以将上述各种与计算机相关的软、硬件因

素都确定下来,这样一个特定算法的运行工作量的大小就只依

赖于问题的规模(通常用正整数n表示),或者说它是问题规

模的函数。

§1.5 算法的性能分析与度量

算法效率的度量分为:事前估计、后期测试。

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

第一章 绪论

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

次数)来估算。

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

计算出空间、时间。

优点:可比性强

缺点:不精确,仅仅是估计

优点:精确

缺点:可比性差,效率低

那么,如何撇开计算机本身来估算一个算法的复杂性呢?

§1.5 算法的性能分析与度量1.5.2 算法效率的度量

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

第一章 绪论

一个程序的时间复杂度(Time complexity)是指程序运行从开始到结束所需要的时间。

算法复杂性的度量属于事前估计。分为:

时间复杂度和空间复杂度的度量。

一个算法是由控制结构和原操作构成的,其执行时间取决

于两者的综合效果。为了便于比较同一问题的不同的算法,通

常的做法是:从算法中选取一种对于所研究的问题来说是基本

运算的原操作,以该原操作重复执行的次数作为算法的时间度

量。一般情况下,算法中原操作重复执行的次数是规模n的某个函数T(n)。(计算T(n)见p29)

许多时候要精确地计算T(n)是困难的,我们引入渐进时间

复杂度在数量上估计一个算法的执行时间,也能够达到分析算

法的目的。

§1.5 算法的性能分析与度量

时间复杂度:

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

第一章 绪论

1.5.4 O表示的含义——算法的渐进分析

§1.5 算法的性能分析与度量

1. 算法的渐进分析

算法的渐进分析简称算法分析。通常将问题的规模作为

分析的参数,求算法的时间开销与问题规模n的关系。

当且仅当存在正整数c和n0,使得T(n)≤c*f(n),对所有

的n≥n0成立,则称该算法的时间增长率在O(f(n))中,记为T(n)=O(f(n)) 。

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

第一章 绪论

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

当我们评价一个算法的时间性能时,主要标准就是算法的

渐进时间复杂度,因此,在算法分析时,往往对两者不予区分,

经常将渐进时间复杂度T(n)=O(f(n))简称为时间复杂度。

§1.5 算法的性能分析与度量

例:考察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)

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

第一章 绪论

算法分析举例

(1)

i=1; k=0 ;

while(i<n)

{ k=k+10*i; i++; }

T(n)=O(n)

◇ 这个函数是按线性阶递增的

(2)

i=0; k=0;

do{

k=k+10*i; i++; }

while(i<n);

T(n)=O(n)

◇ 这也是线性阶递增的

例1:分析下面程序段的时间复杂性

§1.5 算法的性能分析与度量

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

第一章 绪论

(3)

for(i=1;i<=n; i ++)

for(j=1;j<=n; j ++)

{ ++x; s+=x; }

T(n)=O(n2)

◇是平方阶递增的

(4)

for(i=1;i<=n; i ++)

for(j=1;j<=i; j ++)

++x;

T(n)= O(n2)

◇ 这也是平方阶递增的

§1.5 算法的性能分析与度量

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

第一章 绪论

(5)

x=91; y=100;

while(y>0)

if(x>100)

{x=x-10;y--;}

else x++;

◆ T(n)=O(1)

◇ 这个程序看起来有点吓人,

总共循环运行了1000次,但是我们看到n没有? 没。这段程序

的运行是和n无关的,就算它再

循环一万年,我们也不管他,

只是一个常数阶的函数。

§1.5 算法的性能分析与度量

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

第一章 绪论

(6)

i=1;

while(i<=n)

i=i*2;

◆ T(n)=O(log2n)

设其原操作执行的次数为t(n)

于是,2t(n) n

从而有:t(n) log2n

§1.5 算法的性能分析与度量

(7)

s=0;

for(i=0;i<=n;i++)

for(j=0;j<=i;j++)

for(k=0;k<=j;k++)

s++;

◆ T(n)=O(n3)

n i j

t(n)= 1=……i=0 j=0 k=0

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

第一章 绪论

例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 算法的性能分析与度量

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

第一章 绪论

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

常见的时间复杂度,按数量级递增排列依次为:

常数阶O(1)、对数阶O(log2n)、线性阶O(n)、线性对数阶

O(nlog2n)、平方阶O(n2)、立方阶O(n3)、k次方阶O(nk)、指数阶

O(2n)。

§1.5 算法的性能分析与度量

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))

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

第一章 绪论

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 算法的性能分析与度量

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

第一章 绪论

例如:查找一个元素是否存在的算法

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 算法的性能分析与度量

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

第一章 绪论

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

算法与问题规模及时间的关系。

同一问题,规模相同,用不同的算法解决,花费时间是不同的;

同一问题,用不同的算法解决,在相同的时间内所解决的问题

规模大小不同;

思考:―复杂性渐进阶比较低的算法比复杂性渐进阶比较高

的算法有效‖,这种说法正确吗?

另外需要注意:当两个算法的复杂性渐进阶相同时,必须进一步考察T(n)

的常数因子。

§1.5 算法的性能分析与度量

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

第一章 绪论

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

本章小结

2. 数据结构的有关概念及它们之间的关系

数据结构、逻辑结构、存储结构、数据类型、

ADT等

3. 算法及算法分析基础

算法的定义及特点、算法分析的方法(渐进时间

复杂度)

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

作业:

1.总结本章主要内容

2.自己找出或设计三个算法,并分析其时

间复杂度。(要求三个算法的渐进时间

复杂度不能相同)

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

第二章 线性表

内容提要:

线性表是最简单、最基本、也是最常

用的一种线性结构。

它有两种存储方法:顺序存储和链式

存储,它的主要基本操作是插入、删除和

检索等。

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

2.1 线性表

2.2 顺序表

2.3 单链表

2.4 线性链表的其它变形

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

第二章 线性表

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

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 是最后一个元素无后继。

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

说明:

ai为序号为 i 的数据元素(i=1,2,…,n),

通常我们将它的数据类型抽象为datatype。

datatype根据具体问题而定,如在学生情况信息

表中,它是用户自定义的学生类型。

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

线性表的抽象数据类型:抽象数据类型是一个(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()

}

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

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页)

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

2.2 顺序表(Sequential List)

2.2.1 顺序表的定义和特点

定义 :把线性表中的所有表项按照其逻辑顺序依次存

储到计算机存储器中指定存储位置开始的一块连续

的存储空间中。

特点:

①逻辑顺序与物理顺序一致

②可顺序或随机访问表项

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

顺序表可以用C++的一维数组来实现。 C++的一维

数组可以是静态分配的,也可以是动态分配的。

a1 a2 … ai-1 ai … an

数组的下标位置:

0 1 i-2 i-1 n-1

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

的存储位置:

LOC(ai) = LOC(a1) + (i-1)×sizeof(T)

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

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; }

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

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();

}

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

顺序表部分公共操作的实现:

①构造函数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

②若申请失败,则提示出错

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

②搜索或定位:

23 75 41 38 54 62 17data

last

MaxSize

x = 38

i i i i i

i 012307

50

i

基本操作是:

将顺序表中的元素

逐个和定值x相比较。

(定位元素x的位置,返回值为x在

顺序表中的位置;返回值为-1表示

不存在)

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

int SeqList::Find ( T & x ) const {

//搜索函数:在表中从前向后顺序查找 x

int i = 0;

while ( )

i++;

if ( i > last ) return -1; else

return i+1;

}

设计思路:

① x与data[i]逐个循环比较,直到x=data[i]或i>last

② 若i>last,则没有找到x,否则返回i+1

i <= last && data[i] != x

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

搜索成功:

若搜索概率相等,则

搜索不成功 数据比较 n 次

i

n

i

i cpACN1

0

=

2

1

2

)(11

)2(11

1)(1

=1

0

nnn

n

nn

in

ACNn

i

算法时间复杂度: O(n)

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

(a1, …, ai-1, ai, …, an) 改变为(a1, …, ai-1, x, ai, …, an)

a1 a2 … ai-1 ai … an

a1 a2 … ai-1 …aix an

<ai-1, ai> <ai-1, x>, <x, ai>

表的长度增加1

分析:在i位置插入元素x时,线性表的逻辑结构发生

什么变化?

③插入元素

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

21 18 30 75 42 56 87

21 18 30 75

例如: x=66; Insert ( x, 4)

0

jjji

87564266

j

Last ++;//当前最后元素下标加1

for(j = Last ; j >i ; j --)

data[j]=data[j-1];data[i]=x;//在i位置插入x

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

Insert ( const T& x, int i)

思路:①若顺序表的长度等于MaxSize,则满(不能插入);

②若给定的位置小于0或大于长度,出错;

③否则,Last加1,把i及其后面的元素向后移动

一个位置;

④把数据X插入到i位置

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

//在指定位置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);

}

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

//从后向前把前一个元素迁移到后一个元素位置

直到存储位置为i为止

last++;//当前最后元素下标加1

for(int j = Last ; j > i ; j --)

data[j]=data[j-1];

data[i]=x;//在第i项处插入x

}

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

考虑移动元素的平均情况:

假设在第 i (0<=i<=n)个元素位置插入的

概率为 ,则在线性表中插入一个元素所需

移动元素次数的期望值为:

ip

n

i

iis inpE0

)(

n

i

is inn

E0

)(1

1

2

n

若假定在线性表中任何一个位置上进行插入

的概率都是相等的,则移动元素的期望值为:

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

(a1, …, ai-1, ai, …, an) 改变为(a1, …, ai-1, ai+1, …, an)

分析:删除i处的元素时,线性表的逻辑结构发生

什么变化?

④删除元素

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

a1 a2 … ai-1 ai ai+1 … an

a1 a2 … ai-1

表的长度减少

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

删除值为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; //成功删除

}

return 0; //表中没有 x

}

思路:①在顺序表中查找值为x的元素②若找到,last--,把x后的元素向前移动,返回1,若找不到,返回0。

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

⑤输入操作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;

}

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

⑥输出操作void SeqList :: output(){//将顺序表全部元素输出到屏幕上

cout <<“顺序表元素个数为:”<<Last+1<<endl;

for (int i=0;i<=last;i++)

cout<<i+1<< “:” <<data [i]<<endl;

}

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

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

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

例2. 集合的“并”运算

利用两个线性表LA和LB分别表示两个集合A和B,

现要求一个新的集合A=A∪B。

步骤:①取LB中的一个元素

x=LB.GetData(i)

②在LA中找这个元素

LA.Find(x)

③如果LA中没有这个元素则插入LA

LA.Insert(x,++Last)

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

求集合的“并”集

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())

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

例3. 集合的“交”运算

利用两个线性表LA和LB分别表示两个集合A和B,

现要求一个新的集合A=A∩B。

步骤:①依次取LA中的元素

x=LA.GetData(i)

②在LB中找这个元素

LB.Find(x)

③如果LB中没有这个元素则在LA中删除

LA.Remove(x)

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

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中删除它

}}

求“交”集

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

例4. 已知线性表LA和线性表LB中的数据元

素按值非递减有序排列,现要求将LA和LB

归并为一个新的线性表LC,且LC中的元素

仍按值非递减有序排列。

此问题的思路如下:

①从线性表LA,LB中依次取得每个数据元素:

LA.GetData(i), LB.GetData(j)

②较小者插入LC

③把没有插完的线性表中的元素全部插入 LC

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

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;}

}

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

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())

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

2.3 单链表(Singly Linked List)

一、单链表的概念

二、结点类的定义与实现

三、单链表类的定义与实现

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

顺序表的优缺点:

优点:

①无需为表示结点间的逻辑关系增加额外的存储

空间,存储利用率高。

②随机访问

缺点:

①在表中插入或删除元素时,平均移动一半的元

素,运行效率很低。

②占用连续空间

为了克服顺序表的缺点,采用链接方式存储线性

表。链接方式存储的线性表称作链表。

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

用一组地址任意的存储单元存放线性表中的数据

元素。

2.3.1 单链表的概念

元素(数据元素的映象)+ 指针(指示后继元素存储位置)

= 结点(node)

单链表的一个存储结点包含两个部分:

构成的线性表为:

data link

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

以线性表中第一个数据元素 的存储

地址作为线性表的地址,称作线性表的

头指针(first,head)。

1a

头结点

a1 a2 … ... an ^

头指针头指针

有时为了操作方便,在第一个结点

之前虚加一个“头结点”,以指向头结点的

指针为链表的头指针。

空指针线性表为空表时,

头结点的指针域为空

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

单链表的存储映像

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

2.3.2 单链表的类定义

通常使用两个类:链表的结点类和链表类,协同

表示单链表。

定义方式主要有两种: 复合方式、 嵌套方式

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

#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(){};

};

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

class List {

private:

LinkNode *first; //指向头结点的头指针

public:

List () { first = new LinkNode ();}

//带头结点构造函数

List ( const int &x ) {

//不带头结点构造函数

first = new LinkNode ( x ); }

单链表类的定义

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

~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 );

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

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();

};

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

思路:

①设指针q;

②当链表不为空(first的link不为NULL):

q指向first的下一结点;

把q结点从链表中摘除,删除q ;

①删去链表中除表头结点外的所有其它结点

MakeEmpty ( )

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

void List :: MakeEmpty ( ) {

//删去链表中除表头结点外的所有其它结点

LinkNode *q;

while ( first→link != NULL ) {

q = first→link;

first→link = q→link;

//将表头结点后第一个结点从链中摘下

delete q; //释放它

}

};

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

思路:

①指针p指向first的下一结点,计数器为0;

②当p不为NULL:

计数器+1;

p指向下一结点;

③返回计数器的值。

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

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

求表长

int List::Length ( ) const {

LinkNode *p = first→link;

//检测指针p指示第一个结点

int count = 0;

while ( p != NULL ) { //逐个结点检测

count++;

}

return count;

}

p = p→link;

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

③在表中搜索数据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

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

④定位函数,返回表中第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

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

⑤取出链表中第i个元素的值

DataType List::GetData( int i ) {//提取第 i 个结点的值

LinkNode *p = Locate( i ); // p 指向链表第 i 个结点

return p->data;

}

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

思路:①p指针定位到第i个元素

② 返回p指针的数据域

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

⑥给链表中第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

④返回

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

ai-1

⑦将元素x插入到链表中第i个位置处

Insert (DataType x , int i)

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

x

aiai-1

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

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个结点前

}

算法的时间复杂度为: O(Length())

ai-1

x

aiai-1p

newNode

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

⑧删除第i个结点并通过引用返回被删结点的data

Remove (DataType &x , int i)

有序对<ai-1, ai> 和 <ai, ai+1>

改变为 <ai-1, ai+1>

ai-1 ai ai+1ai-1

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

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;

}

算法的时间复杂度为: O(Length())

ai-1 ai ai+1ai-1

p q

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

⑨单链表的输入(前插法)

自行设置一个输入数据的结束标志,用以

结束结点的输入。

设计思路:

①建立一头结点

②输入一结点的值

③当结点的值不为结束标志时,创建新结点,插

入到头结点后,直到输入的值为结束标志。

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

⑨单链表的输入(前插法)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; }

}

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

⑩单链表的输出void List ::output ( ) {//依次输出各结点的值

LinkNode *p=first->link;

while(p!=NULL) {

cout<<p->data<<endl;

p=p->link;

}

}

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

2.4 线性链表的其它变形

2.4.1 循环链表

2.4.2 双向链表

例如 n = 8 m = 3

0

1

2

34

5

6

71

2

345

6

78

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

2.4.1 循环链表

对于单链表而言,最后一个结点的指针域是空指

针,如果将该链表头指针置入该指针域,则使得链表

头尾结点相连,就构成了单循环链表。

a1 a2 … ... an

和单链表的差别仅在于,判别链表中最后一个

结点的条件不再是“后继是否为空”,而是“后继是否

为头结点”。

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

循环链表的示例

带表头结点的循环链表

a0 a1 a2 an-1first

an-1first a1a0

first

(空表)

(非空表)

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

P67

循环链表与单链表的操作实现,最主要的

不同就是扫描到链尾,遇到的不是NULL,而

是表头。

循环链表类的定义

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

113

搜索不成功

循环链表的搜索算法

搜索25

搜索成功搜索15

first 31 48 15 57

p p p

first 31 48 15 57

p p p pp

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

循环链表的搜索算法

CircListNode * CircList::Find( DataType x )

{

//在链表中从头搜索其数据值为 x 的结点

CircListNode * p = first->link;

while ( )

p = p->link;

if ( p != first ) return p; //搜索成功

else return NULL; //搜索不成功

}

p!= first && p->data != x

搜索15

first 31 48 15 57

p

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

115

带尾指针的循环链表

rear

31 48 15 5722

如果插入与删除仅在链表的两端发生,可

采用带表尾指针的循环链表结构。

在表尾插入:

在表尾删除:

在表头插入:

在表头删除:

时间复杂性 O(1)

时间复杂性 O(n)

时间复杂性 O(1)

时间复杂性 O(1)

思考:如何改进使在表尾删除的时间复杂度也为O(1)?

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

116

用循环链表求解约瑟夫问题约瑟夫问题:

n 个人围成一个圆圈,首先第 1 个人从 1 开始

,一个人一个人顺时针报数, 报到第 m 个人,令

其出列。然后再从下一 个人开始,从 1 顺时针报

数,报到第 m 个人,再令其出列,…,如此下去,

直到圆圈中只剩一个人为止。此人即为优胜者。

用不带表头结点的循环链表来组织。

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

117

例如 n = 8 m = 3

0

1

2

34

5

6

71

2

345

6

78

0

1

2

34

5

6

71

2

345

6

78

0

1

2

34

5

6

71

2

345

6

78

0

1

2

34

5

6

71

2

345

6

78

0

1

2

34

5

6

71

2

345

6

78

0

1

2

34

5

6

71

2

345

6

78

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

118

n = 8 m = 3

0

1

2

34

5

6

71

2

345

6

78

0

1

2

34

5

6

71

2

345

6

78

0

1

2

34

5

6

71

2

345

6

78

算法讨论:

目前指针指向8,如何把8从链表中摘除?

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

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;

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

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); //解决约瑟夫问题

}

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

121

2.4.2 双向链表 (Doubly Linked

List)

双向链表是指在前驱和后继方向都能游历(

遍历)的线性链表。

双向链表每个结点结构:

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

式。

前驱方向 后继方向

lLink data rLink

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

122

结点指向

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

非空表 空表

p->lLink p->rLinkp

lLinkrLink

first first

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

123

双向循环链表的搜索算法

DblNode *DblList::Find (DataType x, int d) {

//在双向循环链表中寻找其值等于x的结点, //按d确定

搜索方向,d为0,向左搜索, d为1,向右搜索

DblNode *p= (d == 0)?first->lLink : first->rLink;

while ( )

p = (d == 0) ? p->lLink : p->rLink;

if ( p != first ) return p; //搜索成功

else return NULL; //搜索失败

};

p != first && p->data != x

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

双向循环链表的插入算法

newNode->rLink = p->rLink;

p->rLink = newNode;

newNode->rLink->lLink = newNode;

newNode->lLink = p;

first

first

31 48 15

后插入25 p

newNode

31 48 25 15

p

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

125

删除48

双向循环链表的删除算法

first 非空表31 48 15

p

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

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

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

126

2.5 单链表的应用多项式

(Polynomial)

in

i

i

n

nn

xa

xaxaxaaxP

0

2

210

)(

n阶多项式 Pn(x) 有 n+1 项。

系数 a0, a1, a2, …, an

指数 0, 1, 2, …, n。按升幂排列

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

127

2.5.1 多项式的存储表示

第一种: 静态数组表示

在类的私有域中定义多项式的数据成员:

private:

int degree;

float coef [maxDegree+1];

则Pn(x)可以表示为:

pl.degree = n, pl.coef[i] = ai, 0 i n

a0 a1 a2 …… an ………

0 1 2 degree maxDegree

coef

n

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

128

第二种:动态数组表示

在类的私有域中定义多项式的数据成员:

private:

int degree;

float * coef;

Polynomial :: Polynomial (int sz) {

degree = sz;

coef = new float [degree + 1];

}

以上两种存储表示适用于指数连续排列的

多项式。但对于多数项的系数为零的稀疏多项

式,如 P101(x) = 3+5x50-4x101, 空间利用率太低。

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

129

第三种:只存储非零系数项的系数和指数

struct term { //多项式的项定义

float coef; //系数

int exp; //指数

};

static term termArray[maxTerms]; //项数组

static int free, maxTerms; //当前空闲位置指针

a0 a1 a2 …… ai …… am

e0 e1 e2 …… ei …… em

coef

exp

0 1 2 i m

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

131

两个多项式存储的例子

A(x) = 2.0x1000+1.8

B(x) = 1.2 + 51.3x50 + 3.7x101

两个多项式存放在termArray中

A.start A.finish B.start B.finish free

coef

exp

1.8 2.0 1.2 51.3 3.7 ……

0 1000 0 50 101 ……

maxTerms

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

132

多项式顺序存储表示的缺点:

插入和删除时项数可能有较大变化,因此

要移动大量数据

不利于多个多项式的同时处理

采用多项式的链表表示可以克服这类困难:

多项式的项数可以动态地增长,不存在存

储溢出问题。

插入、删除斱便,不移动元素。

第四种:多项式的链表存储表示

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

133

在多项式的链表表示中,每个结点三个数据

成员:

通过链接指针,可以将多项式各项按指数递

增的顺序链接成一个单链表。

在此结构上,新项的加入和废项的删除执行

简单的链表插入和删除操作即可解决。

多项式的链表结构

coef exp link

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

155

2.6 静态链表 处理时中可以不改变各元素的物理位置,只

要重新链接就能改变这些元素的逻辑顺序。

它是利用数组定义的,在整个运算过程中存

储空间的大小不会变化。

静态链表每个结点由两个数据成员构成:

data域存储数据,link域存放链接指针。

所有结点形成一个结点数组。

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

2.1 线性表

2.2 顺序表

2.3 单链表

2.4 线性链表的其它变形

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

作业:(p84)6,20,21,27,28

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

157

• 栈

• 队列

• 栈的应用:表达式求值

• 栈的应用:递归

• 队列的应用:打印杨辉三角形

• 优先级队列

第三章 栈与队列

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

158

3.1.1 栈的定义

只允许在一端插入和删除的线性表。

允许插入和删除的一端称为栈顶

(top),另一端称为栈底(bottom)

• 特点

后进先出 (LIFO)

3.1 栈 ( Stack )

退栈 进栈

a0

an-1

an-2

top

bottom

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

159

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(); //判栈满

};

栈的抽象数据类型

栈的抽象数据类型有两种典型的存储表示,基

于数组的存储表示实现的栈称为顺序栈,基于链表

的存储表示实现的栈称为链式栈。

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

160

typedef int DataType;

class SeqStack { //顺序栈类定义

private:

DataType *elements; //栈元素存放数组

int top; //栈顶指针

int maxSize; //栈最大容量

栈的数组存储表示 — 顺序栈0 1 2 3 4 5 6 7 8 9 maxSize-1

top (栈空)

elements

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

161

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)

};

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

162

top 空栈

toptop

toptoptop

a 进栈 b 进栈

a ab

ab

cde

e 进栈

ab

cde

f 进栈溢出

ab

de

e 退栈

c

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

163

top

c 退栈 b 退栈

ab

a

a 退栈 空栈

top

ab

d

d 退栈

ctop

ab

c

top

top

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

①顺序栈的构造函数

#include <assert.h>

#include <iostream.h>

SeqStack:: SeqStack(int sz){

elements=new DataType[sz];

assert(elements!=NULL);

top=-1;

maxSize=sz;

}

断言(assert)机制是C++提供的一种功

能,若参数表中给定的条件满足,则继续执行

后续的语句;否则出错处理终止程序的执行。

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

165

②顺序栈的溢出处理

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指针

}; 思路:①创建更大的存储数组

②把原来的元素复制到新数组中

③改变栈大小,删除原来的数组,

栈指针指向新数组

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

166

③入栈操作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; //退栈成功};

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

167

⑤取栈顶元素

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;

};

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

168

双栈共享一个栈空间

b[0] t[0] t[1] b[1]

0 maxSize-1

V

两个栈共享一个数组空间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] //退到栈底

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

171

3.1.3 栈的链接存储表示 — 链式

• 链式栈无栈满问题,空间可扩充

• 插入与删除仅在栈顶处执行

• 链式栈的栈顶在链头

• 适合于多栈操作

top ^

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

172

链式栈 (LinkedStack)类的定义typedef int DataType;

struct StackNode { //栈结点类定义

public:

DataType data; //栈结点数据

StackNode *link; //结点链指针

StackNode(DataType d = 0, StackNode *next = NULL)

: data(d), link(next) { }

~StackNode();

};

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

173

class LinkedStack : { //链式栈类定义

private:

StackNode *top; //栈顶指针

public:

LinkedStack() : top(NULL) {} //构造函数

~LinkedStack() { makeEmpty(); } //析构函数

void Push(DataType &x); //进栈

bool Pop(DataType & x); //退栈

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

174

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) ; //输出栈元素的重载操作 <<

};

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

175

链式栈类操作的实现#include <iostream.h>

①清空栈操作

void LinkedStack::makeEmpty( ) {

//逐次删去链式栈中的元素直至栈顶指针为空。

StackNode *p;

while (top != NULL) { //逐个结点释放

p = top;

top = top->link;

delete p; }

};

top ^

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

top

176

②入栈操作

void LinkedStack::Push(DataType &x) {

//将元素值x插入到链式栈的栈顶,即链头。

top = new StackNode(x, top); //创建新结点

assert (top != NULL); //创建失败退出

};

③出栈操作

bool LinkedStack::Pop(DataType & x) {

//删除栈顶结点, 返回被删栈顶元素的值。

if (IsEmpty() == true) return false; //栈空返回

StackNode *p = top; //暂存栈顶元素

top = top->link; //退栈顶指针

x = p->data; delete p; //释放结点

return true;

};

top x ^

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

177

④取栈顶元素

bool LinkedStack::getTop(DataType & x) const {

if (IsEmpty() == true) return false; //栈空返回

x = top->data; //返回栈顶元素的值

return true;

};

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;

};

top ^

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

公共邮箱:

[email protected]

密码:098765

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

179

问题:当进栈元素的编号为1, 2, …, n时,

可能的出栈序列有多少种?

关于栈的进一步讨论

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

180

• 设进栈元素数为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}。

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

181

= 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}。

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

182

= 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

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

183

一般地,设有 n 个元素按序号1, 2, …, n

进栈,轮流让 1在出栈序列的第1, 第2, …

第n位,则可能的出栈序列数为:

推导结果为:

-1n

0i

01n2n11n01ini m*mm*mm*mm*m

Cn

2n

1n

0i

1ini1n

1m*m

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

算法基于原理:

N = (N div d)×d + N mod d

3.1.4 栈的应用--数制转换

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

例如:(1348)10 = (2504)8 ,其

运算过程如下:

N N div 8 N mod 8

1348 168 4

168 21 0

21 2 5

2 0 2

计算顺序

输出顺序

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

思路:

①初始化栈

②输入要转换的数据N

③当N不为0,把N%8取余入栈

④当栈不为空,栈顶元素出栈,输出。

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

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

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

假设在表达式中

([]())或[([ ][ ])]等为正确的格式,

[( ])或([( ))或 (()])为不正确的格式。

则 检验括号是否匹配的方法可用“期待的急迫程度”这

个概念来描述。

分析可能出现的不匹配的情况:

• 到来的右括弧并非是所“期待”的;

例如:考虑下列括号序列: [ ( [ ] [ ] )]

• 直到结束,也没有到来所“期待”的括弧。

3.1.5 栈的应用--括号匹配的检验

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

①左括号入栈

②右括号,检验栈空?

③表达式检验结束时,若空,则匹配,若非

空,则表明左括号多了。

若空,表明右括号多了

非空,与栈顶元素比较匹配,栈顶的左括号出栈

不匹配,出错

算法的设计思路:

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

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

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

1. 表达式的三种表示方法:

设 Exp = S1 + OP + S2

则称 OP + S1 + S2 为前缀表示法

S1 + OP + S2 为中缀表示法

S1 + S2 + OP 为后缀表示法

3.1.6 栈的应用举例--表达式求值

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

例如: Exp = a b + (c d / e) f

前缀式: + a b c / d e f

中缀式: a b + (c d / e) f

后缀式: a b c d e / f +

结论:

1)操作数之间的相对次序不变;

2)运算符的相对次序不同;

3)中缀式有操作符的优先级问题,还有可加括号改

变运算顺序的问题,所以编译程序一般不使用中缀表

示处理表达式。

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

例如: Exp = a b + (c d / e) f

前缀式: + a b c / d e f

中缀式: a b + (c d / e) f

后缀式: a b c d e / f +

结论:

4)前缀式的运算规则为:

连续出现的两个操作数和在它们之前且紧靠它们

的运算符构成一个最小表达式;

5)后缀式的运算规则为:

运算符在式中出现的顺序恰为表达式的运算顺序;

每个运算符和在它之前出现且紧靠它的两个操作数构成

一个最小表达式。

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

2.如何从后缀式求值?

先找运算符, 再找操作数

例如:

a b c d e / f +

a bd/e

c-d/e(c-d/e) f

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

模拟一个简单的后缀表达式计算器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);//形成运算指令,计算

};

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

①将操作数的值value入操作数栈

void Calculator:: AddOperand(double value){

S.Push(value);

};

②清栈

void Calculator:: Clear() {

S.MakeEmpty();

};

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

③从操作数栈中取出两个操作数

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;

};

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

④读字符串并求后缀表达式的值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;};

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

⑤取两个操作数,根据操作符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;

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

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();

}

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

201

3.如何将中缀表示→转后缀表示?

• 先对中缀表达式按运算优先次序加上括号,再

把操作符后移到右括号的后面并以就近移动为

原则,最后将所有括号消去。

• 如中缀表示 (A+B)*D-E/(F+A*D)+C,其转换为

后缀表达式的过程如下:

( ( – ) + C )( ( A + B ) * D ) ( F + ( A * D ) )( E / )

后缀表示 A B + D * E F A D * + / - C +

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

202

如何将中缀表示→转前缀表示?• 先对中缀表达式按运算优先次序通统加上括

号,再把操作符前移到左括号前并以就近移

动为原则,最后将所有括号消去。

• 如中缀表示 (A+B)*D-E/(F+A*D)+C,其转

换为前缀表达式的过程如下:

( ( ( ( A + B ) * D ) – ( E / ( F + ( A * D ) ) ) ) + C )

前缀表示 + - * + A B D / E + F * A D C

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

203

计算机如何将中缀表示转换为后缀表示?

• 使用栈可将表达式的中缀表示转换成它的前缀表

示和后缀表示。

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

• isp叫做栈内(in stack priority)优先数

• icp叫做栈外(in coming priority)优先数。

栈各个算术操作符的优先级

操作符 ch # ( *, /, % +, - )

isp (栈内) 0 1 5 3 6

icp (栈外) 0 6 4 2 1

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

栈顶算符为θ1:

θ 1 θ 2 + - * / ( ) #

+ > > < < < > >

- > > < < < > >

* > > > > < > >

/ > > > > < > >

( < < < < < = X

) > > > > X > >

# < < < < < X =

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

205

中缀表达式转换为后缀表达式的算法

• 例:中缀表达式为A+B*(C-D)-E/F,求其后缀表

达式。

• ①操作符栈初始化,将结束符‘#‟进栈。然后读

入中缀表达式字符流的首字符ch。

• ②重复执行以下步骤,直到ch = „#‟,同时栈

顶的操作符也是‘#‟,停止循环。

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

206

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,求其后缀表达式。

ABCD-*+EF/-操作符 ch # ( *, /, % +, - )

isp (栈内) 0 1 5 3 6

icp (栈外) 0 6 4 2 1

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

207

中缀表达式转换为后缀表达式的算法:

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); }

}

};

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

208

4.如何应用中缀表示计算表达式的值

使用两个栈,操作符栈OPTR (operator),

操作数栈OPND(operand)

为了实现这种计算,需要考虑各操作符的

优先级

rst1

rst2

rst3

rst4

rst5

a + b * ( c - d ) - e / f

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

209

中缀算术表达式求值 对中缀表达式求值的一般规则:

1. 建立并初始化OPTR栈和OPND栈,然后

在OPTR栈中压入一个“#”

2. 扫描中缀表达式,取一字符送入ch。

3. 当ch != „#‟ 或OPTR栈的栈顶 != „#‟时, 执

行以下工作, 否则结束算法。在OPND栈

的栈顶得到运算结果。

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

210

①若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;

a+b * (c-d)-e/f

操作符 ch # ( * , / , % +, - )

i sp(栈内) 0 1 5 3 6

i cp(栈外) 0 6 4 2 1

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

211

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); //读一个操作符

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

212

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*/

}

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

213

3.2 栈与递归

• 递归的定义

若一个对象部分地包含它自己,或用它自己

给自己定义, 则称这个对象是递归的;若一

个过程直接地或间接地调用自己, 则称这个

过程是递归的过程。

• 以下三种情况常常用到递归方法。

定义是递归的

数据结构是递归的

问题的解法是递归的

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

214

1. 定义是递归的

求解阶乘函数的递归算法

long Factorial(long n) {

if (n == 0) return 1;

else return n*Factorial(n-1);

}

例如,阶乘函数

时当

时当

1 ,)!1(

0 ,1!

n

n

nnn

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

215

求解阶乘 n! 的过程

主程序 main : fact(4)

参数 4 计算 4*fact(3)

参数 3 计算 3*fact(2)

参数 2 计算 2*fact(1)

参数 1 计算 1*fact(0)

参数 0 直接定值 = 1

返回 24

返回 6

返回 2

返回 1

返回 1

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

216

• 例如,单链表结构

• 一个结点,它的指针域为NULL,是一个单

链表;

• 一个结点,它的指针域指向单链表,仍是一

个单链表。

f

f

2.数据结构是递归的

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

217

搜索链表最后一个结点并打印其数值

void Print(ListNode *f) {

if (f ->link == NULL)

cout << f ->data << endl;

else Print(f ->link);

}

f

f f f f

a0 a1 a2 a3 a4

递归找链尾

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

218

在链表中寻找等于给定值的结点并打印其数值

void Print(ListNode*f, DataType value) {

if (f != NULL)

if (f -> data == value)

cout << f -> data << endl;

else Print(f -> link, value);

}

f

f f f

递归找含value值的结点

x

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

219

3. 问题的解法是递归的:汉诺塔问题

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

220

3. 问题的解法是递归的

• 汉诺塔(Tower of Hanoi)问题的解法:

如果 n = 1,则将这一个盘子直接从 A 柱移

到 C 柱上。否则,执行以下三步:

① 用 C 柱做过渡,将 A 柱上的 (n-1) 个盘

子移到 B 柱上;

② 将 A 柱上最后一个盘子直接移到 C 柱上;

③ 用 A 柱做过渡,将 B 柱上的 (n-1) 个盘

子移到 C 柱上。

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

• 解决方法: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 返回地址

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

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) }

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

223

构成递归的条件• 不能无限制地调用本身,必须有一个出口,

化简为非递归状况直接处理。

Procedure <name> ( <parameter list> ) {

if ( < initial condition> ) //递归结束条件

return ( initial value );

else //递归

return (<name> ( parameter exchange ));

}

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

224

• 递归过程在实现时,需要自己调用自己。

• 层层向下递归,退出时的次序正好相反:

递归调用

n! (n-1)! (n-2)! 1! 0!=1

返回次序

• 主程序第一次调用递归过程为外部调用;

• 递归过程每次递归调用自己为内部调用。

• 它们返回调用它的过程的地址不同。

3.2.2递归过程与递归工作栈

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

225

long Factorial(long n) {

int temp;if (n == 0) return 1;else temp = n * Factorial(n-1);

return temp;}

void main() {int result;result = Factorial(4);

cout << result <<endl;

}

RetLoc1

RetLoc2

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

226

1. 递归工作栈

• 每一次递归调用时,需要为过程中使用的参

数、局部变量等另外分配存储空间。

• 每层递归调用需分配的空间形成递归工作记

录,按后进先出的栈组织。

• 栈顶的工作记录是当前正在执行的这一层的

工作记录。称之为活动记录。

局部变量

返回地址

参 数

活动

记录

框架

递归

工作记录

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

227

函数递归时的活动记彔

……………….

<下一条指令>

Function(<参数表>)

……………….

<return>

调用块

函数块

返回地址(下一条指令) 局部变量 参数

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

229

2. 递归过程改为非递归过程

• 递归过程简洁、易编、易懂

• 递归过程效率低,重复计算多

• 改为非递归过程的目的是提高效率

• 单向递归和尾递归可直接用迭代实现其

非递归过程

• 其他情形必须借助栈实现非递归过程

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

230

斐波那契数列的函数Fib(n)的定义

求解斐波那契数列的递归算法

long Fib(long n) {

if (n <= 1) return n;

else return Fib(n-1)+Fib(n-2);

}

1n2),Fib(n1)Fib(n

0,1nn,)Fib(n

如 F0 = 0, F1 = 1, F2 = 1, F3 = 2, F4 = 3, F5 = 5

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

231

调用次数 NumCall(k) =

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

Fib(1)Fib(2)

Fib(3)

Fib(4)

Fib(1) Fib(0)

Fib(2)

Fib(1) Fib(0)

Fib(1)Fib(2)

Fib(3)

Fib(5)时间复杂度:2n

如 F0 = 0, F1 = 1, F2 = 1, F3 = 2, F4 = 3, F5 = 5

2*Fib(k+1)-1

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

232

单向递归用迭代法实现

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;

}

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

233

void recfunc(int A[ ], int n) {

//从n到0输出数组各项的值。

if (n >= 0) {

cout << “value:”<<A[n] << endl;

n--;

recfunc(A, n);

}

}

25 36 72 18 99 49 54 63

尾递归用迭代法实现

尾递归:递归调用语句只有一个,而且放在过程的最后

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

234

void sterfunc(int A[ ], int n) {

//消除了尾递归的非递归函数

while (n >= 0) {

cout << "value:" << A[n] << endl;

n--;

}

}

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

235

3.2.3 递归与回溯对一个包含有许多结点,且每个结点有多

个分支的问题,可以先选择一个分支进行搜索。

当搜索到某一结点,发现无法再继续搜索下去

时,可以沿搜索路径回退到前一结点,沿另一

分支继续搜索。

如果回退之后没有其它选择,再沿搜索路

径回退到更前结点,…。依次执行,直到搜索

到问题的解,或搜索完全部可搜索的分支没有

解存在为止。

回溯法与分治法本质相同,可用递归求解。

(p110迷宫问题)

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

236

3.3 队列 ( Queue )

• 定义

队列是只允许在一端删除,在另一端插入

的线性表

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

入的一端叫做队尾(rear)。

• 特性:先进先出(FIFO, First In First Out)

a0 a1 a2 an-1

front rear

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

237

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; //判队列满

};

队列的抽象数据类型

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

238

队列的进队和出队

front rear 空队列 front rear A进队

A

front rear B进队

A B

front rear C, D进队

A B C D

front rear A退队

B C D

front rear B退队

C D

front rear E,F,G进队

C D E F G C D E F G

front rear H进队,溢出

假溢出

3.3.2 队列的数组存储表示 ─顺序队

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

239

队列的进队和出队原则进队时先将新元素按 rear 指示位置加入,再将队

尾指针加一 rear = rear + 1。

队尾指针指示实际队尾的后一位置。

出队时先按队头指针指示位置取出元素,再将队

头指针进一 front = front + 1,

队头指针指示实际队头位置。

队满时再进队将溢出出错;

队空时再出队将队空处理。

解决假溢出的办法?

将队列元素存放数组首尾相接,形成循环

(环形)队列。

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

240

0

1

23

4

5

6 7 front

0

1

23

4

5

6 7 front

0

1

23

4

5

6 7 frontrear

A A

BC

rearrear

空队列 A进队 B, C进队

0

1

23

4

5

6 7

0

1

23

4

5

6 7

A退队 B退队

0

1

23

4

5

6 7

D,E,F,G,H,I 进队(队满)

front

BC

rear

A

front

BC

rear front

Crear

DE

FG H

I

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

241

队列存放数组被当作首尾相接的表处理。

队头、队尾指针加1时从maxSize-1直接进到0,

可用语言的取模(余数)运算实现。

队头指针进1:

队尾指针进1:

队列初始化:

队空条件:

队满条件:

循环队列 (Circular Queue)

front = (front+1) % maxSize;

rear = (rear+1) % maxSize;

front = rear = 0;

front == rear;

(rear+1) % maxSize == front

0

1

23

4

5

6 7 front

空队列

rear

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

242

#include <assert.h>

#include <iostream.h>

class SeqQueue { //队列类定义

protected:

int rear, front; //队尾与队头指针

DataType*elements; //队列存放数组

int maxSize; //队列最大容量

public:

SeqQueue(int sz = 10); //构造函数

循环队列的类定义

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

243

~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);//输出队列中元素的重载操作<<

};

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

244

循环队列操作的定义

①构造函数

SeqQueue::SeqQueue(int sz) { //构造函数

front=0; rear=0; maxSize=sz;

elements = new DataType[maxSize];

assert ( elements != NULL );

};

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

245

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;

};

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

246

3.3.3 队列的链接存储表示 — 链式队

• 队头在链头,队尾在链尾。

• 链式队列在进队时无队满问题,但有队空问

题。

• 队空条件为 front == NULL

front

rear

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

247

#include <iostream.h>

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

private:

DataType data; //队列结点数据

QueueNode *link; //结点链指针

public:

QueueNode(DataType d = 0, QueueNode

*next = NULL) : data(d), link(next) { }

};

链式队列类的定义

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

248

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)

};

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

249

①置空队列操作

LinkedQueue::MakeEmpty() { //释放链表中所有结点

QueueNode *p;

while (front != NULL) { //逐个释放结点

p = front; front = front->link; delete p;

}

};

front ^

rearp

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

250

②将新元素x插入到队列的队尾

bool LinkedQueue::EnQueue(DataType &x) {

if (front == NULL) { //创建第一个结点

front = rear = new QueueNode (x);

if (front == NULL) return false; } //分配失败

else { //队列不空, 插入

rear->link = new QueueNode(x);

if (rear->link == NULL) return false; //分配失败

rear = rear->link;

}

return true;

};

front ^

rear

frontrear

^front

rear

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

251

③如果队列不空,将队头结点从链式队列中删去

bool LinkedQueue::DeQueue(DataType & x) {

if (IsEmpty() == true) return false; //判队空

QueueNode *p = front;

x = front->data; front = front->link;

delete p; return true;

};

④若队列不空,则函数以引用返回队头元素的值

bool LinkedQueue::GetFront(DataType & x) {

if (IsEmpty() == true) return false;

x = front->data; return true;

};

front ^

rearp

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

252

⑤求队列元素个数

int LinkedQueue::getSize( )const {

QueueNode *p = front; int k=0;

while(p!=NULL) {k++; p= p->link;}

return k;

};

⑥输出队列中元素的重载操作

ostream& operator<<(ostream&os, LinkedQueue &Q) {

os<<“队列中元素个数为:”<<Q. getSize( )<<endl;

QueueNode *p = front; int i=0;

while(p!=NULL) { os<<++i<<―:‖<<p->data<<endl;

p = p->link; }

};

front ^

rearp

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

253

1 1 i = 1

1 2 1 2

1 3 3 1 3

1 4 6 4 1 4

1 5 10 10 5 1 5

1 6 15 20 15 6 1 6

3.3.4 队列的应用:打印杨辉三角形

• 逐行打印二项展开式 (a + b)i 的系数:杨辉

三角形 (Pascal‟s triangle)

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

254

分析第 i 行元素与第 i+1行元素的关系

从前一行的数据可以计算下一行的数据

i = 2

i = 3

i = 4

0 1 3 3 1 0

1 4 6 4 1

0 1 2 1 0

0 1 1 0s t s+t

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

255

从第 i 行数据计算并存放第 i+1 行数据

1 2 1 0 1 3 3 1 0 1 4 6

s=0 t=1 t=2 t=1 t=0 t=1 t=3 t=3 t=1 t=0 t=1s+t s=t s=t s=t s=t s=t s=t s=t s=t

s+t s+t s+t s+t s+t s+t s+t s+t

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

256

利用队列打印二项展开式系数的算法

#include <stdio.h>

#include <iostream.h>

#include "queue.h"

void YANGHUI(int n) {

SeqQueue q(n+2); //队列初始化p121

q.EnQueue(1); q.EnQueue(1);

int s = 0, t;

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

257

for (int i = 1; i <= n; i++) { //逐行计算

cout << endl;

q.EnQueue(0);

for (int j = 1; j <= i+2; j++) { //下一行

q.DeQueue(t);

q.EnQueue(s + t);

s = t;

if (j != i+2) cout << s << ' ';

}

}

}改进:杨辉三角格式输出

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

258

任务编号 1 2 3 4 5

优先权 20 0 40 30 10

执行顺序 3 1 5 4 2

3.4 优先级队列 (Priority Queue)

• 优先级队列:每次从队列中取出的是具有

最高优先权的元素

• 如下表:任务优先权及执行顺序的关系

在最小优先级队列中,数字越小,优先权越高

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

259

#include <assert.h>

#include <iostream.h>

#include <stdlib.h>

class PQueue {

private:

DataType *pqelements; //存放数组

int count; //队列元素计数

int maxPQSize; //最大元素个数

void adjust(); //调整

最小优先级队列的类定义

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

260

public:

PQueue(int sz = 50);

~PQueue() { delete [ ] pqelements; }

bool Insert(DataType& x);

bool RemoveMin(DataType & x);

bool GetFront(DataType & x);

void MakeEmpty() { count = 0; }

bool IsEmpty() const { return count == 0; }

bool IsFull() const

{ return count == maxPQSize; }

int Length() const { return count; }

};

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

261

①构造函数

PQueue::PQueue(int sz) {

maxPQSize = sz; count = 0;

pqelements = new DataType[maxPQSize];

assert (pqelements != NULL);

}

②插入元素

bool PQueue::Insert(DataType x) {

if (IsFull( ) == true) return false; //判队满

pqelements[count++] = x; //插入

adjust(); return true; //p125

}

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

262

10 20 40 50 70 90

插入60

10 20 40 50 70 90 60

60

10 20 40 50 70 90 60

60

9090707060

10 20 40 50 60 70 90

j

jj j

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

263

③按优先权调整元素位置操作void PQueue::adjust() {

DataType temp = pqelements[count-1];

//将最后元素暂存再从后向前找插入位置for (int j = count-2; j >= 0; j--)

if (pqelements[j] <= temp) break;

else pqelements[j+1] = pqelements[j];

pqelements[j+1] = temp;

}

10 20 40 50 70 90 60

j

temp

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

264

④具有最小优先级的元素出队

bool PQueue::RemoveMin(DataType& x) {

if (IsEmpty()) return false;

x = pqelements[0]; //取出0号元素

for (int i = 1; i < count; i++)

pqelements[i-1] = pqelements[i];

//从后向前逐个移动元素填补空位

count--;

return true;

}10 20 40 50 60 70 90

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

265

⑤取队头元素

bool PQueue::GetFront (DataType& x) {

if (IsEmpty() == true) return false;

x = pqelements[0];

return true;

}

10 20 40 50 60 70 90

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

1. 掌握栈和队列类型的特点,并能在相应

的应用问题中正确选用它们。

2. 熟练掌握栈类型的两种实现斱法,特别应

注意栈满和栈空的条件以及它们的描述斱法。

3. 熟练掌握循环队列和链队列的基本操作实

现算法,特别注意循环队列队满和队空的描述

斱法。4. 理解递归算法执行过程中栈的状态变化过

程。

本章学习要点

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

思考:

给定(已生成)一个带表头结点

的单链表,设head为头指针,结点

的结构为(data,next),data为整型元

素,next为指针,试写出算法:通

过遍历一趟链表,将链表中所有结

点按逆序链接。

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

270

第四章 数组、串与广义表

一维数组与多维数组

特殊矩阵

稀疏矩阵

字符串

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

271

4.1 多维数组的概念与存储

定义

数组是相同类型的数据元素的集合,而一

维数组的每个数组元素是一个序对,由下标

(index)和值(value)组成。

一维数组的示例

35 27 49 18 60 54 77 83 41 02

0 1 2 3 4 5 6 7 8 9

一维数组

在高级语言中的一维数组只能按元素的下标直接存取

数组元素的值。随机存取,即存取任何元素花的时间相同。

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

272

一维数组的定义和初始化

#include <iostream.h>

main ( ) {

int a[3] = { 3, 5, 7 }, *elem, i; // a[3]静态数组

for (i = 0; i < 3; i++)

cout << a[i] << endl;

elem = new int[3]; // *elem动态数组

for (i = 0; i < 3; i++)

cin >> elem[i];

}

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

273

多维数组

多维数组是一维数组的推广。

多维数组的特点是每一个数据元素可以有多个

直接前驱和多个直接后继。

数组元素的下标一般具有固定的下界和上界,

因此,它比其它复杂的非线性结构简单。

例如二维数组的数组元素有两个直接前驱,两

个直接后继,必须有两个下标(行、列)以标

识该元素的位置。

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

274

二维数组 三维数组

行向量 下标 i 页向量 下标 i

列向量 下标 j 行向量 下标 j

列向量 下标 k

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

275

数组的连续存储斱式

一维数组

LOC(i) =LOC(i-1)+l = a+i*l, i > 0

a, i = 0

35 27 49 18 60 54 77 83 41 02

0 1 2 3 4 5 6 7 8 9

l l l l l l l l l l

a+i*l

a

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

276

]][[]][[]][[

]][[]][[]][[

]][[]][[]][[

]][[]][[]][[

111101

121202

111101

101000

nnamama

naaa

naaa

naaa

a

二维数组

用一维内存来表示多维数组,就必须按某种次序将数组

元素排列到一个序列中。

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

278

二维数组中数组元素的顺序存放

]][[]][[]][[

]][[]][[]][[

]][[]][[]][[

]][[]][[]][[

111101

121202

111101

101000

mnanana

maaa

maaa

maaa

a

行优先存放:

设数组开始存放位置 LOC(0, 0) = a, 每个

元素占用 l 个存储单元

LOC ( j, k ) = a + ( j * m + k ) * l

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

279

]][[]][[]][[

]][[]][[]][[

]][[]][[]][[

]][[]][[]][[

111101

121202

111101

101000

mnanana

maaa

maaa

maaa

a

列优先存放:

设数组开始存放位置 LOC(0, 0) = a, 每个

元素占用 l 个存储单元

LOC ( j, k ) = a + ( k * n + j ) * l

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

280

三维数组 各维元素个数为 m1, m2, m3

下标为 i1, i2, i3的数组元素的存储地址:

(按页/行/列存放)

LOC ( i1, i2, i3 ) = a +

( i1* m2 * m3 + i2* m3 + i3 ) * l

前i1页

总元素

个数

第i1页

前i2行

总元素

个数

第 i2 行

前 i3 列

元素个

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

281

二维数组 三维数组

行向量 下标 i 页向量 下标 i

列向量 下标 j 行向量 下标 j

列向量 下标 k

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

282

4.2 特殊矩阵

特殊矩阵是指非零元素或零元素的分布有一

定规律的矩阵。

特殊矩阵的压缩存储主要是针对阶数很高的

特殊矩阵。为节省存储空间,对可以不存储

的元素,如零元素或对称元素,不再存储。

对称矩阵

三对角矩阵

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

283

对称矩阵的压缩存储

设有一个 n n 的对称矩阵 A。

11121110

12222120

11121110

10020100

A

nnnnn

n

n

n

aaaa

aaaa

aaaa

aaaa

对称矩阵中的元素关于主对角线对称,

aij = aji, 0≤i, j≤n-1

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

284

11121110

12222120

11121110

10020100

nnnnn

n

n

n

aaaa

aaaa

aaaa

aaaa

为节约存储,只存对角线及对角线以上的元

素,或者只存对角线和对角线以下的元素。

前者称为上三角矩阵,后者称为下三角矩阵。

下三角矩阵

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

285

11121110

12222120

11121110

10020100

nnnnn

n

n

n

aaaa

aaaa

aaaa

aaaa

三角矩阵

把它们按行存放于一个一维数组 B 中,称之

为对称矩阵 A 的压缩存储方式。

数组 B 共有:

n + ( n - 1 ) + + 1 =n*(n+1)/2 个元素。

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

286

11121110

12222120

11121110

10020100

nnnnn

n

n

n

aaaa

aaaa

aaaa

aaaa

下三角矩阵

若 i j, 数组元素A[i][j]在数组B中的存放位置为

1 + 2 + + i + j = (i + 1)* i / 2 + j

B a00 a10 a11 a20 a21 a22 a30 a31 a32 …… an-1n-1

0 1 2 3 4 5 6 7 8 n(n+1)/2-1

前i行元素总数 第i行第j个元素前元素个数

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

287

若i < j,数组元素 A[i][j] 在矩阵的上三角部分,在数组B中没有存放,可以找它的对称元素A[j][i]

: j *(j +1) / 2 + i

若已知某矩阵元素位于数组 B 的第 k个位置(k≥0),可寻找满足

i (i + 1) / 2 k < (i + 1)*(i + 2) / 2

的i, 此即为该元素的行号。

j = k - i * (i + 1) / 2

此即为该元素的列号。

例,当 k = 8, 3*4 / 2 = 6 k < 4*5 / 2 =10,

取 i = 3。则 j = 8 - 3*4 / 2 = 2。

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

288

33323130

23222120

13121110

03020100

aaaa

aaaa

aaaa

aaaa 上三角矩阵

B a00 a01 a02 a03 a11 a12 a13 a22 a23 a33

0 1 2 3 4 5 6 7 8 9

若i j,数组元素A[i][j]在数组B中的存放位置为

n + (n-1) + (n-2) + + (n-i+1) + j-i

前i行元素总数 第i行第j个元素前元素个数

n = 4

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

289

若 i j,数组元素A[i][j]在数组B中的存放位

置为:

n +(n-1)+(n-2)+ +(n-i+1)+ j-i

= (2*n-i+1) * i / 2 + j-i

= (2*n-i-1) * i / 2 + j

若i > j,数组元素A[i][j]在矩阵的下三角部分

,在数组 B 中没有存放。因此,找它的对称

元素A[j][i]。A[j][i]在数组 B 的第 (2*n-j-1) * j

/ 2 + i 的位置中找到。

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

290

三对角矩阵的压缩存储

1121

122232

232221

121110

0100

0000

000

000

000

0000

A

nnnn

nnnnnn

aa

aaa

aaa

aaa

aa

B a00 a01 a10 a11 a12 a21 a22 a23 … an-1n-2 an-1n-1

0 1 2 3 4 5 6 7 8 9 10

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

291

三对角矩阵中除主对角线及在主对角线上 下

最临近的两条对角线上的元素外,所有其它元素均为0。总共有3n-2个非零元素。

将三对角矩阵A中三条对角线上的元素按行存

放在一维数组 B 中,且a00存放于B[0]。

在三条对角线上的元素aij 满足

0 i n-1, i-1 j i+1

在一维数组 B 中 A[i][j] 在第 i 行,它前面有3*i-1 个非零元素, 在本行中第 j 列前面有 j-

i+1 个,所以元素 A[i][j] 在 B 中位置为

k = 2*i + j。

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

292

三对角矩阵的压缩存储

1121

122232

232221

121110

0100

0000

000

000

000

0000

A

nnnn

nnnnnn

aa

aaa

aaa

aaa

aa

B a00 a01 a10 a11 a12 a21 a22 a23 … an-1n-2 an-1n-1

0 1 2 3 4 5 6 7 8 9 10

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

293

若已知三对角矩阵中某元素 A[i][j] 在数组

B[ ] 存放于第 k 个位置,则有

i =

j =

例如,当 k = 8 时,

i = (8+1) / 3 = 3, j = 8 - 2*3 = 2

当 k = 10 时,

i = (10+1) / 3 = 3, j = 10 - 2*3 = 4

(k + 1) / 3

k - 2 * i k=(3i-1)+(j-i+1)

=2i+j

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

294

00002800

00000091

03900000

0006000

017000110

150022000

A 76

4.3 稀疏矩阵 (Sparse Matrix)

设矩阵 A 中有 s 个非零元素,若 s 远远小于

矩阵元素的总数(即s≤m×n),则称 A 为稀

疏矩阵。

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

295

设矩阵 A 中有 s 个非零元素。令 e = s/(m*n),

称 e 为矩阵的稀疏因子。

有人认为 e≤0.05 时称之为稀疏矩阵。

在存储稀疏矩阵时,为节省存储空间,应只

存储非零元素。但由于非零元素的分布一般

没有规律,故在存储非零元素时,必须记下

它所在的行和列的位置 ( i, j )。

每一个三元组 (i, j, aij) 唯一确定了矩阵A的一

个非零元素。因此,稀疏矩阵可由表示非零

元的一系列三元组及其行列数唯一确定。

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

问题:

①一维数组a[n]中,若a[0]存放于地址b,那么

a[i]的内存地址?

②二维数组a[m][n]中,若a[0][0]存放于地址b,

行主序,那么a[i][j]的内存地址?

③三维数组a[m1][m2][m3]中,若a[0][0][0]存

放于地址b,那么a[i][j][k]的内存地址?

296

b+i*l

b+(i*n+j)*l

b+(i* m2 * m3 + j* m3 + k ) * l

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

问题:

④下三角矩阵a[n][n]中,若a[0]存放于地址b,那么a[i][j]的内存地址?

⑤上三角矩阵a[n][n]中,若a[0][0]存放于地址b,

那么a[i][j]的内存地址?

⑥三对角矩阵a[n][n]中,若a[0][0]存放于地址b,

那么a[i][j]的内存地址?

297

b+(i*(i+1)/2+j)*l

b+((2*n-i-1)*i/2+j)*l

b+(2*i + j ) * l

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

298

稀疏矩阵的定义const int drows = 6, dcols = 7, dterms = 9;

struct Triple { //三元组

int row, col; //非零元素行号/列号

DataType value; //非零元素的值

void operator = (Triple & R) //赋值

{ row = R.row; col = R.col; value = R.value; }

};

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

299

class SparseMatrix {

public:

SparseMatrix (int Rw = drows, int Cl = dcols,

int Tm = dterms); //构造函数

void Transpose(SparseMatrix& b); //转置

void Add (SparseMatrix& a, SparseMatrix& b); //a = a+b

void Multiply (SparseMatrix& a, SparseMatrix& b);

private: //a = a*b

int Rows, Cols, Terms; //行/列/非零元素数

Triple *smArray; //三元组表

};

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

301

稀疏矩阵的转置 一个 m n 的矩阵 A,它的转置矩阵 B 是一个

n m 的矩阵,且A[i][j] = B[j][i]。即

矩阵 A 的行成为矩阵 B 的列

矩阵 A 的列成为矩阵 B 的行。

在稀疏矩阵的三元组表中,非零矩阵元素按行存

放。当行号相同时,按列号递增的顺序存放。

稀疏矩阵的转置运算要转化为对应三元组表的转

置。

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

302

00002800

00000091

03900000

0006000

017000110

150022000

稀疏矩阵 行行((rrooww))

列列((ccooll))

值值((vvaalluuee))

[0] 00 33 2222

[1] 00 66 1155

[2] 11 11 1111

[3] 11 55 1177

[4] 22 33 --66

[5] 33 55 3399

[6] 44 00 9911

[7] 55 22 2288

0 1 2 3 4 5 6

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

303

0000015

00390170

000000

0006022

2800000

0000110

0910000

转置矩阵 行行((rrooww))

列列((ccooll))

值值((vvaalluuee))

[0] 00 44 9911

[1] 11 11 1111

[2] 22 55 2288

[3] 33 00 2222

[4] 33 22 --66

[5] 55 11 1177

[6] 55 33 3399

[7] 66 00 1166

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

304

用三元组表表示的稀疏矩阵及其转置

(row)

(col)

(value)

(row)

(col)

(value)

[0] 0 3 22 [0] 0 4 91

[1] 0 6 15 [1] 1 1 11

[2] 1 1 11 [2] 2 5 28

[3] 1 5 17 [3] 3 0 22

[4] 2 3 -6 [4] 3 2 -6

[5] 3 5 39 [5] 5 1 17

[6] 4 0 91 [6] 5 3 39

[7] 5 2 28 [7] 6 0 16

原矩阵三元组表 转置矩阵三元组表

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

305

稀疏矩阵转置算法思想 设矩阵列数为 Cols,对矩阵三元组表扫描

Cols 次。第 k 次检测列号为 k 的项。

第 k 次扫描找寻所有列号为 k 的项,将其行

号变列号、列号变行号,顺次存于转置矩阵

三元组表。

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

306

稀疏矩阵的转置

void SparseMatrix::Transpose (SparseMatrix& B) {

//转置this矩阵,转置结果由B返回

B.Rows = Cols; B.Cols = Rows;

B.Terms = Terms;

//转置矩阵的列数,行数和非零元素个数

if (Terms > 0) {

int CurrentB = 0; //转置三元组表存放指针

int i, k;

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

307

for (k = 0; k < Cols; k++) //处理所有列号

for (i = 0; i < Terms; i++)

if (smArray[i].col == k) {

B.smArray[CurrentB].row = k;

B.smArray[CurrentB].col =

smArray[i].row;

B.smArray[CurrentB].value=

smArray[i].value;

CurrentB++;

}

}

};

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

308

用三元组表表示的稀疏矩阵及其转置

(row)

(col)

(value)

(row)

(col)

(value)

[0] 0 3 22 [0] 0 4 91

[1] 0 6 15 [1] 1 1 11

[2] 1 1 11 [2] 2 5 28

[3] 1 5 17 [3] 3 0 22

[4] 2 3 -6 [4] 3 2 -6

[5] 3 5 39 [5] 5 1 17

[6] 4 0 91 [6] 5 3 39

[7] 5 2 28 [7] 6 0 16

原矩阵三元组表 转置矩阵三元组表

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

309

快速转置算法

设矩阵三元组表总共有 t 项,上述算法的时间代价

为 O ( n* t )。当非零元素的个数 t 和 m*n 同数量

级时,算法的时间复杂度为O(m*n2)。

若矩阵有 200 行,200 列,10,000 个非零元素,总

共有 2,000,000 次处理。

快速转置算法的思想为:对原矩阵A 扫描一遍,

按 A 中每一元素的列号,立即确定在转置矩阵 B

三元组表中的位置,并装入它。

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

310

为加速转置速度,建立辅助数组 rowSize 和

rowStart:

rowSize记录矩阵转置前各列,即转置矩阵各

行非零元素个数;

rowStart记录各行非零元素在转置三元组表

中开始存放位置。

扫描矩阵三元组表,根据某项列号,确定它转

置后的行号, 查 rowStart 表, 按查到的位置直

接将该项存入转置三元组表中。

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

311

[0] [1] [2] [3] [4] [5] [6] 语 义

rowSize 1 1 1 2 0 2 1 矩阵 A 各列非

零元素个数

rowStart 0 1 2 3 5 5 7 矩阵 B 各行开

始存放位置

A三元组 (0) (1) (2) (3) (4) (5) (6) (7)

行row 0 0 1 1 2 3 4 5

列col 3 6 1 5 3 5 0 2

值value 22 15 11 17 -6 39 91 28

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

312

稀疏矩阵的快速转置算法

void SparseMatrix::

FastTranspos (SparseMatrix& B) {

int *rowSize = new int[Cols]; //列元素数数组

int *rowStart = new int[Cols]; //转置位置数组

B.Rows = Cols; B.Cols = Rows;

B.Terms = Terms;

if (Terms > 0) {

int i, j;

for (i = 0; i < Cols; i++) rowSize[i] = 0;

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

313

for (i = 0; i < Terms; i++)

rowSize[smArray[i].col]++;

rowStart[0] = 0;

for (i = 1; i < Cols; i++)

rowStart[i] = rowStart[i-1]+rowSize[i-1];

for (i = 0; i < Terms; i++) {

j = rowStart [smArray[i].col];

B.smArray[j].row = smArray[i].col;

B.smArray[j].col = smArray[i].row;

B.smArray[j].value = smArray[i].value;

rowStart [smArray[i].col]++;

}

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

314

}

delete [ ] rowSize; delete [ ] rowStart;

}

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

时间复杂性:O(Cols+2Terms)

当Terms和Rows*Cols同数量级时,为

O(Rows*Cols),与一般转置算法复杂性相

同,当Terms << Rows*Cols时,比一般转

置算法快,所以又称为快速转置算法。

在空间复杂性上,该算法多用了两个辅助

向量,是以少量空间换取了较快的执行速

度。

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

316

4.4 串

本节学习另一种特殊的线性表—串,它特殊在:

有些高级语言已经把串ADT物理实现了,所

以,我们这里只是简单讨论一下串的逻辑特性、

存储结构及一些操作的实现。

1. 数据元素都来自字符集!数据对象是字符集。

2. 由于数据元素特殊,它的操作有些不同于一般线

性表,例如:操作对象一般是子串(即一组数

据元素),而不是单个数据元素。

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

317

字符串

串的模式匹配

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

318

字符串 (String) 简单说,字符串是有限字符集中的n ( 0 ) 个

字符组成的有限序列。

按数据结构来定义:字符串是一种特殊的线性表(数据元素之间的关系是线性关系),特殊在其数据元素来自于字符集这个数据对象。定义为:

String=(D,R)

D={ci | ci D0 D0=CHARACTER i=1,2,...,n n>=0}

R= {<ci-1,ci> | ci-1,ci D0 i=2,3,...,n}

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

319

一个字符串一般记作:

S = “c1c2c3…cn”

其中,S 是串名字, “c1c2c3…cn”是串值,

ci 是串中字符, n 是串的长度,n = 0 称为空串。

串以‟\0‟为结束符,串长不包括该字符。

例如, S = “Tsinghua University”。

串结构的特点

数据元素都是字符,它的操作对象一般不再是单个数据元素,而是一组数据元素。

注意:空串和空白串不同,例如 “ ” 和 “” 分别

表示长度为1的空白串和长度为0的空串。

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

320

串中任意个连续字符组成的子序列称为该串

的子串,包含子串的串相应地称为主串。

通常将子串在主串中首次出现时,该子串首

字符对应的主串中的序号,定义为子串在主

串中的位置。例如,设A和B分别为

A = “This is a string” B = “is”

则 B 是 A 的子串,A 为主串。B 在 A 中出现

了两次,首次出现所对应的主串位置是2(从

0开始)。因此,称 B 在 A 中的位置为2。

特别地,空串是任意串的子串,任意串是其

自身的子串。

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

321

#include <string.h>

P155-156

4.4.2 C++中有关字符串的库函数

可以看出:串的操作有一个特点,即对一组数据

元素进行操作,它的基本操作对象是一个子串

!!

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

322

串的模式匹配 定义 就是定位操作,在主串中寻找子串(第

一个字符)在串中的位置

词汇 在模式匹配中,子串称为模式,主串称为目标。

示例 目标 T : “Beijing”

模式 P : “jin”

匹配结果 = 3

实现方法 BF算法、KMP算法、BM算法、KR算法等。

BF算法——最简单、最易理解的匹配算法

KMP算法——效率较高的匹配算法

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

323

朴素的模式匹配(B-F算法)

BF算法的基本思想

采用回溯法:从主串的第i个位置开始,与子串

的每个字符逐个比较,若均相等,则找到,位置为i

;否则,即在某个位置出现了不等,则说明从该起

点开始的子串不是模式串,换一个新起点(第i+1个

位置),重新开始,继续逐一比较,直到找到,或

i>=Length(T)为止。

T P

i j

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

324

第1趟 T a b b a b a

P a b a

第2趟 T a b b a b a

P a b a

i=2

j=2

i=1

j=0

第3趟 T a b b a b a

P a b a

第4趟 T a b b a b a

P a b a

i=2

j=0

i=6

j=3

每趟:主串回溯到上次起点的下一个位置;

模式串回到0

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

326

下面介绍一种改进的模式匹配算法——KMP算法

D.E.Knuth

J.H.Morris

V.R.Pratt

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

327

运用KMP算法的匹配过程

第1趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

第2趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

第3趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

第4趟 目标 a c a b a a b a a b c a c a a b c

模式 (a b) a a b c a c

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

328

按照BF算法:当比较到某个位置,不匹配时,

不管比过的字符串是什么情况,都要进行回溯!因

此,算法效率低。

假设主串为 T=„t0t1t2...tn-1‟

模式串为 P=„p0p1p2...pm-1‟ m<<n

基本思想:

KMP算法最大的改进之处:在匹配失败时,主

串中的指针i不需要回溯,而是在模式中找出适当的

字符继续比较。

改进的模式匹配—KMP算法

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

329

在主串的第i个字符ti与模式中pj匹配失败时,i

不回退,而是在模式中找适当字符pk与ti继续比较

,pk与ti pk+1与ti+1……..当然此pk不是任意的,得

根据一定算法找出。 k<j,为什么?

…Ti

P

jk

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

330

t0 t1 t 2 …… ti-k ti-(k-1) ti-(k-2)…. ti-2 ti-1 ti ti+1……

p0 p1 p2…pk-1 pk…. pj-k pj-(k-1) pj-(k-2)…. pj-2 pj-1 pj pj+1Pj

K

K

K

p0p1p2…pk-1 = ti-k ti-k+1 ti-k+2… ti-2 ti-1

pj-k pj-k+1 pj-k+2… pj-2 pj-1 = ti-k ti-k+1 ti-k+2… ti-2 ti-1

p0p1p2…pk-1 = pj-k pj-k+1 pj-k+2… pj-2 pj-1

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

331

从以上分析可知: p0p1p2…pk-1是 p0p1…pj-1 的既是

真前缀、又是真后缀的子串。(即长度小于j)当然

,要求这个子串取尽可能长,这个子串越长,说明

已经匹配的字符就越多。

所以,求 k 就是求 p0p1…pj-1中最长的既是真前

缀又是真后缀的子串的长度。

k与主串无关!!

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

333

运用KMP算法的匹配过程

第1趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

第2趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

第3趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

第4趟 目标 a c a b a a b a a b c a c a a b c

模式 (a b) a a b c a c

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

334

对于每个pj都求出了最长的既是真前缀又是真

后缀的串长度k,那么就有:主串的某个字符与pj

匹配失败时,应继续与 pk比较, k称为pj的失败

函数值,记作 next[j]=k。

KMP快速匹配算法的实现

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

335

根据前面的分析,失败函数可以定义为:

-1 当 j=0 时 ???

next[j]= max{ k | 0<k<j且 ‘p0p1...pk-1‟=„pj-k...pj-1‟ } 非空

0 其它情况

jk

k

k0

pj-k pj-1

失败函数的定义

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

336

利用next失败函数进行匹配处理若在进行某一趟匹配比较时在模式 P 的第 j 位失配:

如果 j > 0,那么在下一趟比较时模式串 P的起始比

较位置是 pnext(j),目标串 T 的指针不回溯,仍指向

上一趟失配的字符;

如果 j = 0,则目标串 T 指针进一,模式串P 指针回

到 p0,继续进行下一趟匹配比较。

j 0 1 2 3 4 5 6 7

P a b a a b c a c

next(j) -1 0 0 1 1 2 0 1

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

337

运用KMP算法的匹配过程

第1趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

j=1 next(1) = 0,下次p0

第2趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

j=0 下次p0, 目标指针进 1

第3趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

j=5 next(5) = 2,

下次p2j 0 1 2 3 4 5 6 7

P a b a a b c a c

next(j) -1 0 0 1 1 2 0 1

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

338

运用KMP算法的匹配过程

第3趟 目标 a c a b a a b a a b c a c a a b c

模式 a b a a b c a c

j=5 next(5) = 2,

下次p2

第4趟 目标 a c a b a a b a a b c a c a a b c

模式 (a b) a a b c a c

j 0 1 2 3 4 5 6 7

P a b a a b c a c

next(j) -1 0 0 1 1 2 0 1

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

339

注意两点:

2. 改进NEXT值: next[j]=k 即 si 与 pj 比较不相

等时,继续与 pk 比较,但是若 pk=pj 显然肯定还

要失败,所以 next[j] 可以改进.

1. KMP算法的特点:效率较高;主串指针不回溯

,对主串仅需要从头到尾扫描一遍(可以边读入边

匹配)。

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

改进Next[j]:

340

j= 0 1 2 3 4模式 a a a a bNext[j]= -1 0 1 2 3

P=a a a b a a a a b

T= a a a a b P=a a a b a a a a b

T=a a a a b

P=a a a b a a a a b

T= a a a a b

P=a a a b a a a a b

T= a a a a b

P=a a a b a a a a b

T= a a a a b

Nextval[j]=-1 -1 -1 -1 3

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

改进的Next[j]:

341

j= 0 1 2 3 4模式 a a a a bNext[j]= -1 0 1 2 3

Nextval[j]=-1 -1 -1 -1 3

P=a a a b a a a a b

T=a a a a b

P=a a a b a a a a b

T= a a a a b

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

1.串数据结构的特点

小结

数据元素都是字符,它的操作的对象一般不再是单个数据元素,而是一组数据元素。

串的操作的特点①数据对象限定了字符集

①对一组数据元素进行操作。

2. 模式匹配算法:BF、KMP

熟悉next函数的定义,手工会算next[j]和修正了的next[j],在已知next[j]时,能实现KMP算法

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

343

练习题

计算下列模式串的各个字符的失败函数值:

(1) ‗abcabcaabbc‘

(2) ‗aaaabaaa‘

(3) ‗01010000101‘

(4) ‗00000010001‘

-1 0 0 0 1 2 3 4 1 2 0

-1 0 1 2 3 0 1 2

-1 0 0 1 2 3 1 1 1 2 3

-1 0 1 2 3 4 5 0 1 2 3

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

344

小结:

ADT名称 共性 特殊性

一般线性表 元素之间线性关系 无

栈 元素之间线性关系 插入删除

限制在一端

队列 元素之间线性关系 插入在一端

删除在一端

字符串 元素之间线性关系 数据对象是字符集

操作的对象为子串

数组 各维元素之间线性 元素可以参与多个关系 线性关系

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

345345

第五章 树与二叉树

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

346346

第五章 树与二叉树5.1 树的基本概念5.2 二叉树

5.3 二叉树的存储表示

5.5 线索二叉树

5.6 树和森林

5.9 哈夫曼树与哈夫曼编码5.8 堆

5.4 二叉树的遍历及其应用

5.7 树和森林的遍历及其应用

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

347347

5.1 树的基本概念

5.1.1 树的定义和术语

两种树:自由树与有根树。

①自由树:

一棵自由树 Tf 可定义为一个二元组

Tf = (V, E)

其中V = {v1, ..., vn} 是由 n (n>0) 个元素组成的有限

非空集合,称为顶点集合。

E = {(vi, vj) | vi, vj V, 1≤i, j≤n} 是n-1个序对的集合,

称为边集合,E 中的元素 (vi, vj)称为边或分支。

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

348348

自由树

②有根树:

一棵有根树 T,简称为树,它是n (n≥0) 个

结点的有限集合。当n = 0时,T 称为空树;

否则,T 是非空树,记作

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

349349

r 是一个特定的称为根(root)的结点,它只

有直接后继,但没有直接前驱;

根以外的其他结点划分为 m (m 0) 个互不

相交的有限集合T1, T2, …, Tm,每个集合又

是一棵树,并且称之为根的子树。

每棵子树的根结点有且仅有一个直接前驱,

但可以有0个或多个直接后继。

0

0

n,T,...,T,Tr,

n,T

m21 }{

Φ

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

350

a.直观表示法:

用圆圈表示结点,元素写在圆圈中,连线表示元素之间的关系。根在上,叶子在下(即树向下生长)。

a

c

e

b d

f g

树的常见表示斱法

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

351

b.嵌套集合表示法:

根据树的集合定义,写出集合划分。

{ a, {b,{e},{f}}, {c}, {d,{g}} }

c.文氏图表示法:

集合表示的一种直观表示,用图表示集合。

a c

b e f

d g

a

c

e

b d

f g

直观表示法

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

352

d.目彔表示法(凹入表示法):

将一棵树描述为一本书,书-章-节-小节-

a

c

e

b d

f g

直观表示法

1a

2b

3e

3f

2c

2d

3g 目彔表示法

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

353

结点:

结点的度:

树的度?

叶子结点?

分支结点?

数据元素+若干指向子树的分支

分支的个数即结点拥有的子树数

一棵树中各结点度数的最大值

度为零的结点

度大于零的结点

D

H I J

M

树的基本术语

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

354

子女结点、双亲结点

兄弟结点、堂兄弟

祖先结点、子孙结点

结点的层次:

树的深度:

(从根到结点的)路径:由从根到该结

点所经分支和结点构成。A

B C D

E F G H I J

MK L

规定根结点的层次为1,

它的孩子为第2层……

树中叶子结点所在的最大层次

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

355355

等于根结点的高度,即根结点所有子女

高度的最大值加一。

高度:

树的高度?

有序树:

无序树?

森林?

规定叶结点的高度为1,其双亲结点的

高度等于它的高度加一。

树中结点的各棵子树 T0, T1, …是有次序

的,即为有序树。

树中结点的各棵子树之间的次序是不重

要的,可以互相交换位置。

是m(m≥0)棵树的集合。

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

356

A

B C D

E F G H I J

K L M

结点A的度:结点M的度:

叶子:

结点A的孩子:

结点I的双亲:

结点B,C,D为兄弟

树的度:

结点A的层次:结点M的层次:

树的深度:

结点F,G为堂兄弟结点A是结点F,G的祖先

3

0

B,C,D

K,L,F,G,M,I,J

3

1

4

D

4

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

357357

5.1.2 树的抽象数据类型class Tree {

//对象: 树是由n (≥0) 个结点组成的有限集合。在

//类界面中的 position 是树中结点的地址。在顺序

//存储方式下是下标型, 在链表存储方式下是指针

//型。DataType 是树结点中数据的类型, 要求所有

//结点的数据类型都是一致的。

public:

Tree ();

~Tree ();

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

358358

BuildRoot (const DataType& value);

//建立树的根结点

position FirstChild(position p);

//返回 p 第一个子女地址, 无子女返回 0

position NextSibling(position p);

//返回 p 下一兄弟地址, 若无下一兄弟返回 0

position Parent(position p);

//返回 p 双亲结点地址, 若 p 为根返回 0

DataType getData(position p);

//返回结点 p 中存放的值

bool InsertChild(position p, DataType& value);

//在结点 p 下插入值为 value 的新子女, 若插

//入失败, 函数返回false, 否则返回true

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

359359

bool DeleteChild (position p, int i);

//删除结点 p 的第 i 个子女及其全部子孙结

//点, 若删除失败, 则返回false, 否则返回true

void DeleteSubTree (position t);

//删除以 t 为根结点的子树

bool IsEmpty ();

//判树空否, 若空则返回true, 否则返回false

void Traversal (void (*visit)(position p));

//遍历以 p 为根的子树

};

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

360

讨论:

1. 树的逻辑结构(特点):

一对多(1:n),有多个直接后继(如家谱树、目录树

等等),但只有一个根结点,且子树之间互不相交。

2. 树的存储结构

讨论1:树是非线性结构,该怎样存储?

————仍然有顺序存储、链式存储方式。

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

361

可规定为:从上至下、从左至右将树的结点依次存入内

存。

重大缺陷:复原困难(不能唯一复原就没有实用价值)。

讨论2:树的顺序存储方案应该怎样制定?

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

362

讨论3:树的链式存储方案应该怎样制定?

可用多重链表:一个前趋指针,n个后继指针。

细节问题:树中结点的结构类型样式该如何设计?

即应该设计成“等长”还是“不等长”?

缺点:等长结构太浪费(每个结点的度不一定相同);

不等长结构太复杂(要定义好多种结构类型)。

解决思路:先研究最简单、最有规律的树,然后设

法把一般的树转化为简单树。

二叉树

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

363

要明确:

1. 普通树(即多叉树)若不转化为二叉树,则

运算很难实现。

2. 二叉树的运算仍然是插入、删除、修改、查

找、排序等,但这些操作必须建立在对树结

点能够“遍历”的基础上!

(遍历——指每个结点都被访问且仅访问一次,

不遗漏不重复)。

本章重点:二叉树的表示和实现

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

364364

二叉树的五种不同形态

L LR R

5.2 二叉树 (Binary Tree)5.2.1 二叉树的定义

一棵二叉树是结点的一个有限集合,该集合

或者为空,或者是由一个根结点加上两棵分别称

为左子树和右子树的、互不相交的二叉树组成。

问:二叉树有几种可能的形态?

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

365365

二叉树的性质

性质1: 若二叉树结点的层次从 1 开始, 则在二叉

树的第 i 层最多有 2i-1 个结点。( i≥1)

[证明用数学归纳法]

性质2: 深度为 k 的二叉树最少有 k 个结点,最

多有 2k-1个结点。( k≥1 )

每一层最少要有1个结点,因此,最少结点

数为 k。

最多结点个数借助性质1:用求等比级数前

k项和的公式: 20 +21 +22 + …+2k-1 = 2k-1

问:第i层上至少有 个结点?1

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

366366

性质3: 对任何一棵二叉树,如果其叶结点有 n0 个,

度为 2 的非叶结点有 n2 个, 则有

n0=n2+1

若设度为 1 的结点有 n1 个,总结点数为n,

总边数为e,则根据二叉树的定义,

n = n0+n1+n2 e = 2n2+n1 = n-1

因此,有 2n2+n1 = n0+n1+n2-1

n2 = n0-1 n0 = n2+1

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

367367

定义1: 满二叉树 (Full Binary Tree)

定义2: 完全二叉树 (Complete Binary Tree)

若设二叉树的深度为 k,则共有 k 层。除第 k

层外,其它各层 (1~k-1) 的结点数都达到最大个

数,第k层从右向左连续缺若干结点,这就是完全

二叉树。

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

368

1

2 3

11

4 5

8 9 12 13

5 7

10 14 15

1

2 3

11

4 5

8 9 12

5 7

10

1

2 3

4 5

5 7

1

2 3

4 5 5

问:下列哪棵二叉树为完全二叉树?

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

369369

性质4: 具有 n (n≥0) 个结点的完全二叉树的深度

为 log2(n+1) 。

设完全二叉树的深度为k,则有

2k-1-1 < n ≤ 2k-1

变形 2k-1 < n+1≤2k

取对数

k-1< log2(n+1) ≤k , 有

log2(n+1) = k

上面k-1层结点数 包括第k层的最大结点数

23-1

24-1

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

370370

性质5: 如将一棵有n个结点的完全二叉树自顶

向下,同一层自左向右连续给结点编号1,

2, …, n,则有以下关系:

若i = 1, 则 i 无双亲

若i > 1, 则 i 的双亲为 i/2

若2*i <= n, 则 i 的左子女为 2*i,

若2*i+1 <= n, 则 i 的右子女为2*i+1

若 i 为奇数, 且i != 1,

则其左兄弟为i-1,

若 i 为偶数, 且i != n,

则其右兄弟为i+1

1

2 3

4

8

5 6 7

9 10

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

371

讨论:

②设一棵完全二叉树具有1000个结点,则它有 个叶子结点,

有 个度为2的结点,有 个结点只有非空左子树,

有 个结点只有非空右子树。

500499 1

0

①为什么要研究满二叉树和完全二叉树这两种特殊形式?

因为只有这两种形式可以实现顺序存储!

由于最后一层叶子数为489个,是奇数,说明有1个结点只有

非空左子树;而完全二叉树中不可能出现非空右子树(0个)。

易求出总层数和末层叶子数。总层数k= log2n +1 =10;

且前9层总结点数为29-1=511 (完全二叉树的前k-1层肯定是满的)

所以末层叶子数为1000-511=489个。末层的叶子只占据了上层

的245个结点( 489/2 ),上层(k=9)右边的0度结点数还有29-1-

245=11个!

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

372372

二叉树的抽象数据类型class BinaryTree {

//对象: 结点的有限集合, 二叉树是有序的

public:

BinaryTree (); //构造一棵空二叉树

BinaryTree (BinTreeNode*lch,

BinTreeNode *rch, DataType item);

//构造函数, 以item为根, lch和rch为左、右子

//树构造一棵二叉树

int Height (); //求树深度或高度

int Size (); //求树中结点个数

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

373373

bool IsEmpty (); //判二叉树空否?

BinTreeNode *Parent (BinTreeNode *t);

//求结点 t 的双亲

BinTreeNode *LeftChild (BinTreeNode *t);

//求结点 t 的左子女

BinTreeNode *RightChild (BinTreeNode *t);

//求结点 t 的右子女

bool Insert (DataType item); //在树中插入新元素

bool Remove (DataType item); //在树中删除元素

bool Find (DataType& item); //判断item是否在树中

bool getData (DataType& item); //取得结点数据

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

374374

BinTreeNode *getRoot (); //取根

void preOrder (BinTreeNode *t); //前序遍历

void inOrder (BinTreeNode *t); //中序遍历

void postOrder (BinTreeNode *t); //后序遍历

void levelOrder (BinTreeNode*t); //层次序遍历

private:

BinTreeNode *lchild,*rchild, *root;

DataType item

};

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

375

5.3 二叉树的存储结构

5.3.2 二叉树的链式存储表示

5.3.1 二叉树的顺序存储表示

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

376376

5.3.1 二叉树的顺序表示

完全二叉树

的顺序表示

1

1 2 3 4 5 6 7 8 9 10

2

4

8 9 10

5 6 7

3

14

1 2 3 4 6 7 8 9 12 14

1

2 3

764

8 9 12

5

10 11 13

一般二叉树

的顺序表示

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

377377

1

3

7

15

31

极端情形: 只有右单支的二叉树

1 3 7 15

31

顺序存储的特点:结点间关系蕴含在其存储位置中

浪费空间,适于存满二叉树和完全二叉树

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

378378

二叉树结点定义:每个结点有3个数据成员,data

域存储结点数据,leftChild和rightChild分别存放指向

左子女和右子女的指针。

leftChild data rightChild

data

leftChild rightChild

二叉链表

5.3.2 二叉树的链表表示(二叉链表)

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

379379

leftChild data parent rightChild

parent

data

leftChild rightChild

三叉链表

二叉树的链表表示(三叉链表)

每个结点增加一个指向双亲的指针parent,使

得查找双亲也很方便。

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

380380

二叉树链表表示的示例

A A

B B

CC D D

FFE E

root root

A

B

C D

FE

root

二叉树 二叉链表 三叉链表

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

381381

二叉链表的静态结构

A

B

C D

FE

rootdata parent leftChild rightChild

0

1

2

3

4

5

A -1 1 -1

B 0 2 3

C 1 -1 -1

D 1 4 5

E 3 -1 -1

F 3 -1 -1

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

382382

二叉树的类定义#include <iostream.h>

class BinTreeNode { //结点类的定义

public:

BinTreeNode ( ) { leftChild =NULL, rightChild =NULL }

BinTreeNode ( DataType x, BinTreeNode *left =

NULL, BinTreeNode *right = NULL ) : data (x),

leftChild (left), rightChild(right) { } //构造函数

~BinTreeNode ( ) { } //析构函数

private:

BinTreeNode *leftChild, *rightChild; //左、右子女链域

DataType data; //数据域

};

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

383383

class BinaryTree {

public:

BinaryTree(): root (NULL) { }

BinaryTree ( DataType value ) { RefValue =value),

root =NULL; }

~BinaryTree(){ destroy ( root );}

void CreateBinTree( ) { CreateBinTree(root);}

//建立二叉树

int IsEmpty ( ) { return (root == NULL) ? true : false; }

BinTreeNode *Parent ( BinTreeNode *current )

{ return (root == NULL || root == current)?NULL :

Parent ( root, current ); }

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

384384

BinTreeNode *LeftChild (BinTreeNode *current )

{ return (current != NULL )? current->leftChild :NULL; }

BinTreeNode *RightChild (BinTreeNode *current )

{ return ( current!= NULL) ? current->rightChild : NULL; }

int Height( ){return Height(root);}

int Size( ){return Size(root);}

BinTreeNode *GetRoot ( ) const { return root; }

void preOrder( ) {preOrder(root);} //前序遍历

void inOrder( ) {inOrder(root);} //中序遍历

void postOrder( ) {postOrder(root);} //后序遍历

void levelOrder( ) {levelOrder(root);} //层序遍历

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

385385

private:

BinTreeNode *root; //二叉树的根指针

DataType RefValue; //数据输入停止标志

void CreateBinTree( BinTreeNode * & subTree)

BinTreeNode *Parent ( BinTreeNode * subTree,

BinTreeNode *current );

int Height(BinTreeNode *subTree);

int Size(BinTreeNode *subTree);

void preOrder(BinTreeNode &subTree ); //前序遍历

void inOrder(BinTreeNode &subTree ); //中序遍历

void postOrder(BinTreeNode &subTree ); //后序遍历

void levelOrder(BinTreeNode &subTree); //层序遍历

void destroy(BinTreeNode* &subTree); };

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

386386

BinTreeNode *BinaryTree::Parent (BinTreeNode

*subTree, BinTreeNode *current) {

//私有函数: 从结点 subTree 开始, 搜索结点 current 的

//双亲, 若找到则返回双亲结点地址, 否则返回NULL

if (subTree == NULL) return NULL;

if (subTree->leftChild ==current ||

subTree->rightChild == current )

return subTree; //找到, 返回父结点地址

部分成员函数的实现

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

387387

BinTreeNode *p;

if ((p = Parent (subTree->leftChild, current)) != NULL)

return p; //递归在左子树中搜索

else return Parent (subTree->rightChild, current);

//递归在右子树中搜索

};

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

388388

void BinaryTree::destroy (BinTreeNode* subTree) {

//私有函数: 删除根为subTree的子树

if (subTree != NULL) {

destroy (subTree->leftChild); //删除左子树

destroy (subTree->rightChild); //删除右子树

delete subTree; //删除根结点

}

};

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

389389

5.4 二叉树遍历及其应用二叉树的遍历就是按某种次序访问树中的结

点,要求每个结点访问且仅访问一次。

设访问根结点记作 V

遍历根的左子树记作 L

遍历根的右子树记作 R

则可能的遍历次序有(只考虑先左后右)

前序 VLR

中序 LVR

后序 LRV

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

390390

中序遍历二叉树算法的框架是:

若二叉树为空,则空操作;

否则

中序遍历左子树 (L);

访问根结点 (V);

中序遍历右子树 (R)。

遍历结果:

a + b * c - d - e / f

5.4.1 二叉树遍历的递归算法

①中序遍历 (Inorder

Traversal)-

-

/+

*a

b

c d

e f

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

391391

void BinaryTree ::InOrder( BinTreeNode *subTree ) {

//私有函数,中序遍历以subTree为根的二叉树

if (subTree != NULL) {

//终止递归的条件

InOrder (subTree->leftChild);

cout << subTree->data;

InOrder (subTree->rightChild );

}

}

二叉树递归的中序遍历算法

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

392392

前序遍历二叉树算法的框架是:

若二叉树为空,则空操作;

否则

访问根结点 (V);

前序遍历左子树 (L);

前序遍历右子树 (R)。

遍历结果:

- + a * b - c d / e f

②前序遍历 (Preorder Traversal)

-

-

/+

*a

b

c d

e f

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

393393

void BinaryTree ::PreOrder( BinTreeNode *subTree ) {

//私有函数,前序遍历以subTree为根的二叉树

if (subTree != NULL ) { //终止递归的条件

cout << subTree->data;

PreOrder (subTree->leftChild );

PreOrder (subTree->rightChild );

}

}

二叉树递归的前序遍历算法

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

394394

后序遍历二叉树算法的框架是:

若二叉树为空,则空操作;

否则

后序遍历左子树 (L);

后序遍历右子树 (R);

访问根结点 (V)。

遍历结果:

a b c d - * + e f / -

③后序遍历 (Postorder Traversal)

-

-

/+

*a

b

c d

e f

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

395395

void BinaryTree ::PostOrder( BinTreeNode *subTree ) {

//私有函数,后序遍历以subTree为根的二叉树

if (subTree != NULL ) {

//终止递归的条件

PostOrder (subTree->leftChild );

PostOrder (subTree->rightChild );

cout << subTree->data;

}

}

二叉树递归的后序遍历算法

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

396396

① 计算二叉树深度或高度的函数

int BinaryTree ::Depth( const BinTreeNode * subTree )

const { //私有函数,计算根指针为subTree的二叉树的

深度或高度

if (subTree == NULL ) return 0; //空二叉树的深度为0

else return 1+ Max(Depth(subTree->leftChild ),

Depth (subTree->rightChild ) );

}

5.4.2 二叉树遍历的应用

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

397397

②计算二叉树结点个数的函数

int BinaryTree ::Size ( const BinTreeNode * subTree )

const { //私有函数,计算根指针为subTree的二叉树中

的结点个数

if (subTree == NULL ) return 0;

//空二叉树的结点个数为0

else return 1 + Size (subTree->leftChild )

+ Size (subTree->rightChild );

}

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

398398

③利用二叉树完全前序遍历建立二叉树

以递归方式建立二叉树。

输入结点值的顺序必须对应二叉树结点完

全前序遍历的顺序。

约定以输入序列中不可能出现的值作为空

结点的值以结束递归, 此值在RefValue中。例如

用“@”或用“-1”表示字符序列或正整数序列空

结点。

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

399399

如图所示的二叉树的前序遍历顺序为

A B C @ @ D E @ G @ @ F @ @ @

A

B

C D

E

G

F@ @

@

@ @

@ @

@

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

400

void BinaryTree ::CreateBinTree(BinTreeNode* &

subTree) {//私有函数: 建立根为subTree的子树

DataType item; cin>> item;

if (item != RefValue) {

subTree = new BinTreeNode (item);

if( subTree == NULL )

{cerr<<"存储分配错!" <<endl; exit(1);}

CreateBinTree( subTree->leftChild);

CreateBinTree( subTree->rightChild);

}

else subTree = NULL;

};400

^ item ^

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

401

思路:

①若根不为空,访问根,若右子树不

为空则入栈;

②若左子树不为空,则左进,若左子

树为空,栈不空时退栈。

重复执行上述过程,直到遍历完

401

a

b c

d e

5.4.3 二叉树遍历的非递归算法①前序遍历的非递归算法

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

402402

ca

b c

d e

d

c^

访问

a

进栈

c

左进

b

访问

b

进栈

d

左空

退栈

d

访问

d

右空左空

退栈

c

访问

c

右空左进

e

访问

e

右空左空栈空

结束

5.4.3 二叉树遍历的非递归算法①前序遍历的非递归算法

^ ^

c

^

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

403403

前序遍历的非递归算法void BinaryTree::PreOrder ( BinTreeNode *t ) {

Seqstack S;

BinTreeNode *p = root;

S.Push (NULL);

while (p != NULL) {

cout<<p->data; //访问结点

if (p->rightChild != NULL)

S.Push (p->rightChild); //预留右指针在栈中

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

404404

if (p->leftChild != NULL)

p = p->leftChild; //进左子树

else S.Pop(p); //左子树为空

}

};

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

405405

层次序遍历二叉树就是从根结点开始,按层次

逐层遍历,如图:

遍历顺序

a

b

c d

e f

-

-

+ /

*

层次序遍历二叉树的算法

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

406406

这种遍历需要使用一个先进先出的队列,在

处理上一层时,将其下一层的结点直接进到

队列(的队尾)。在上一层结点遍历完后,

下一层结点正好处于队列的队头,可以继续

访问它们。

算法是非递归的。

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

407407

a

b c

d e

Q a a 进队

Q a出队,访问a

b进队,c 进队b c

Q b出队,访问b

d 进队c d

Q c出队,访问c

e 进队d e

Q d出队,访问de

Q e出队,访问e

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

410410

二叉树的计数二叉树遍历的结果是将一个非线性结构中

的数据通过访问排列到一个线性序列中。

前序序列:a b d c e ,特点是第一个访问的a一定

是树根,只要左子树非空,后面紧跟的b 一定

是根的左子女,…

中序序列:b d a e c ,特点是树

根 a 把整个中序分成两部分,

a 左侧子序列是根的左子树上

的结点数据,右侧子序列是根

的右子树上的结点数据。

a

b c

d e

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

411411

思考:由二叉树的前序序列和中序序列可唯一

地确定一棵二叉树吗?

试验:前序序列 { A B H F D E C K G }

中序序列 { H B D F A E K C G }

构造一棵二叉树

HBDF EKCG

A

EKCG

A

B

H DF

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

412412

前序序列 { A B H F D E C K G }

中序序列 { H B D F A E K C G }

EKCG

A

B

H DF

EKCG

A

B

H F

D

KCG

E

A

B

H F

D

E

A

B

H F

D

C

K G

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

413413

如果前序序列固定不变,给出不同的中序

序列,可得到不同的二叉树。

思考:固定前序排列,选择所有可能的中序排列,可

能构造多少种不同的二叉树?

P209-210

6

1

2

3 4

5

7

8 9

6

1

2

3 7

5

8

4 9

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

414414

例如, 有 3 个数据 { 1, 2, 3 },可得 5 种不

同的二叉树。它们的前序排列均为 123,中序序

列可能是 321,231,213,132,123。

问题:前序序列为 123,中序序列为 312 的二叉

树存在吗?

1

2

3

1

2

3

1

2 3

1

2

3

1

2

3

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

415415

有0个, 1个, 2个, 3个,4个结点的不同二叉树如下

b0 =1 b1 =1 b2 =2b3 =5

b4 =14

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

416416

!!

)!(2

1

1

1

12 nn

n

nnb C

n

nn

计算具有n个结点的不同二叉树的棵数

Catalan函数bi bn-i-1

11

0

1

n

i

inin bbb

5123

456

4

1

13

1 3

63 Cb

141234

5678

5

1

14

1 4

84 Cb

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

417417

5.5 线索化二叉树(Threaded Binary Tree)

又称为穿线树。

通过二叉树的遍历,可将二叉树中所有结

点的数据排列在一个线性序列中,可以找到某

数据在这种排列下它的前驱和后继。

希望不必每次都通过遍历找出这样的线性

序列。只要事先做预处理,将某种遍历顺序下

的前驱、后继关系记在树的存储结构中,以后

就可以高效地找出某结点的前驱、后继。

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

418418

线索 (Thread)

增加前驱Pred指针和后继Succ指针的二叉树

pred leftChild data rightChild succ

a

b c

d e

pred

pred

predsucc

succ

succ

D∧ ∧

A

E∧ ∧

B∧∧ C ∧∧

root

pred

pred

predpredsucc

succ

succ

succ

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

419419

缺点:每个结点增加两个指针,当结点数很大

时存储消耗较大。

改造树结点,将 pred 指针和 succ 指针压缩到

leftChild 和 rightChild 的空闲指针中,并增

设两个标志 ltag 和 rtag,指明指针是指示子

女还是前驱/后继。后者称为线索。

ltag (或rtag) = 0,表示相应指针指示左子女

(或右子女结点);当ltag (或rtag) = 1, 表示

相应指针为前驱(或后继)线索。

leftChild ltag data rtag rightChild

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

420420

leftChild ltag data rtag rightChild

a

b c

d e

pred

pred

predsucc

succ

succD

A

E

B∧ C ∧

root

pred pred

succ

succ

0 0

0 0 11

1 1 1 1

线索化二叉树及其链表表示

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

421421

线索化二叉树的类定义typedef int T;

struct ThreadNode { //线索二叉树的结点类

int ltag, rtag; //线索标志

ThreadNode *leftChild, *rightChild;

//线索或子女指针

T data; //结点数据

ThreadNode ( const T item) //构造函数

: data(item), leftChild (NULL),

rightChild (NULL), ltag(0), rtag(0) {}

};

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

424424

if (current->rtag ==1)

后继为current->rightChild

else //current->rtag == 0

后继为当前结点右子树

的中序下的第一个结点

寻找当前结点在中序下的后继A

B

D E

C

F

H I

K

G

J

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

425425

寻找当前结点在中序下的前驱

if (current->ltag == 1)

前驱为current->leftChild

else //current->ltag == 0

前驱为当前结点左子树

中序下的最后一个结点

A

B

D E

C

F

H I

K

G

J

L

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

426426

5.6.1 树的存储表示

5.6 树与森林

A

B C D

E F G

data

parent

A B C D E F G

-1 0 0 0 1 1 3

0 1 2 3 4 5 6

1. 双亲表示

树中结点的存放顺序一般不做特殊要求,但为了操

作实现的方便,有时也会规定结点的存放顺序。例如,可

以规定按树的前序次序存放树中的各个结点,或规定按树

的层次次序安排所有结点。

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

427427

2. 子女链表表示

无序树情形链表中各结点顺序任意,有序树

必须自左向右链接各个子女结点。

A

B C D

E F G

1 2 3 ∧

4 5 ∧

6 ∧

A

B

C

D

E

F

G

0

1

2

3

4

5

6

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

428428

3. 子女指针表示一个合理的想法是在结点中存放指向每一

个子女结点的指针。但由于各个结点的子女数

不同,每个结点设置数目不等的指针,将很难

管理。

为此,设置等长的结点,每个结点包含的

指针个数相等,等于树的度(degree)。

这保证结点有足够的指针指向它的所有子

女结点。但可能产生很多空闲指针,造成存储

浪费。

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

429429

等数量的链域

A

B C D

E F G

data child1 child2 child3 childd

A

B C D

E F G空链域2n+1个

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

430430

4. 子女-兄弟表示

也称为树的二叉树表示。结点构造为:

firstChild 指向该结点的第一个子女结点。无

序树时,可任意指定一个结点为第一个子女。

nextSibling 指向该结点的下一个兄弟。任一

结点在存储时总是有顺序的。

若想找某结点的所有子女,可先找firstChild,

再反复用 nextSibling 沿链扫描。

data firstChild nextSibling

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

431431

树的子女 -

兄弟表示

data firstChild nextSibling

A

B C D

E F G

A

B

C

D

G

F

E

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

432432

用子女-兄弟表示实现的树的类定义

struct TreeNode { //树的结点类

T data; //结点数据

TreeNode *firstChild, *nextSibling;

//子女及兄弟指针

TreeNode (T value = 0, TreeNode *fc = NULL,

TreeNode *ns = NULL) //构造函数

: data (value), firstChild (fc), nextSibling (ns) { }

};

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

433433

class Tree { //树类

private:

TreeNode *root, *current;

//根指针及当前指针

int Find (TreeNode *p, T value);

//在以p为根的树中搜索value

void RemovesubTree (TreeNode *p);

//删除以p为根的子树

bool FindParent (TreeNode *t, TreeNode *p);

public:

Tree () { root = current = NULL; } //构造函数

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

434434

bool Root (); //置根结点为当前结点

bool IsEmpty () { return root == NULL; }

bool FirstChild ();

//将当前结点的第一个子女置为当前结点

bool NextSibling ();

//将当前结点的下一个兄弟置为当前结点

bool Parent ();

//将当前结点的双亲置为当前结点

bool Find (T value);

//搜索含value的结点, 使之成为当前结点

…… //树的其它公共操作

};

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

435435

树的二叉树表示

5.7 树的遍历

①深度优先遍历

先根次序遍历

后根次序遍历

②广度优先遍历

A

B C D

E F G

A

B

CE

D

G

F

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

436436

A

B

CE

D

G

F

树的先根次序遍历

当树非空时

访问根结点

依次先根遍历根的各棵

子树

树先根遍历 :

对应二叉树前序遍历:

树的先根遍历结果与其对应二叉树

表示的前序遍历结果相同

树的先根遍历可以借助对应二叉树的前序遍

历算法实现

A

B C D

E F G

ABEFCDG

ABEFCDG

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

437437

A

B

CE

D

G

F

树的后根次序遍历

当树非空时

依次后根遍历根的各棵子树

访问根结点

树后根遍历 :

对应二叉树中序遍历:

树的后根遍历结果与其对应二叉树

表示的中序遍历结果相同

树的后根遍历可以借助对应二叉树的中序遍历

算法实现

A

B C D

E F G

EFBCGDA

EFBCGDA

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

438438

广度优先(层次次序)遍历

按广度优先次序遍历树的结果

ABCDEFG

遍历算法用到一个队列。

A

B

CE

D

G

FA

B C D

E F G

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

439439

将一般树化为二叉树表示就是用树的子女-兄

弟表示来存储树的结构。

森林与二叉树表示的转换可以借助树的二叉树

表示来实现。

5.6.2 森林与二叉树的转换

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

440440

T1 T2 T3A

T1 T2 T3A F H

B C D G I J

E K

F

B

C

D

E

G

H

I

K J

A

B

C

E

DH

I

K J

F

G3 棵树的森林

各棵树的二叉树表示

森林的二叉树表示

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

441441

1. 森林转化成二叉树的觃则 若 F 为空,即 n = 0,

则对应的二叉树 B 为空。

若 F 不空,则

二叉树 B 的根是 F 第一棵

树 T1 的根;

T1 T2 T3A F H

B C D G I J

E KA

B

C

E

DH

I

K J

F

G

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

442442

1. 森林转化成二叉树的觃则

其左子树为B (T11, T12, …,

T1m),其中,T11, T12, …,

T1m 是 T1 的根的子树;

其右子树为 B (T2, T3, …,

Tn),其中,T2, T3, …, Tn

是除 T1 外其它树构成的

森林。

T1 T2 T3A F H

B C D G I J

E KA

B

C

E

DH

I

K J

F

G

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

443443

2. 二叉树转换为森林的觃则

如果二叉树 B 为空,则

对应的森林 F 也为空。

如果 B 非空,则

F 中第一棵树 T1 的根

为 B 的根;

T1 T2 T3A F H

B C D G I J

E KA

B

C

E

DH

I

K J

F

G

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

444444

二叉树转换为森林的觃则

T1 的根的子树森林

{ T11, T12, …, T1m } 是

由 B 的根的左子树

LB 转换而来;

F 中除了 T1 之外其

余的树组成的森林

{ T2, T3, …, Tn } 是由

B 的根的右子树 RB

转换而成的森林。

T1 T2 T3A F H

B C D G I J

E KA

B

C

E

DH

I

K J

F

G

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

445445

5.7 森林的遍历 森林的遍历也分为深度优先遍历和广度优先

遍历,深度优先遍历又可分为先根次序遍历

和后根次序遍历。

给定森林 F,若 F = Ø,则遍历结束。否则

若F = {{T1 = { r1, T11, …, T1k }, T2, ..., Tm},则

可以导出先根遍历、后根遍历两种方法。其

中,r1是第一棵树的根结点,{T11, …, T1k}是

深度优先遍历

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

446446

第一棵树的子树森林,{T2, ...,Tm}是除去第一

棵树之后剩余的树构成的森林。

若森林F = Ø,返回;否则

访问森林的根(也是第一棵树的根)r1;

先根遍历森林第一棵树的根的子树森林

{T11, …, T1k};

先根遍历森林中除第一棵树外其他树组成

的森林{T2, ...,Tm}。

森林的先根次序遍历

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

447447

森林的先根次序遍历的结果序列:

ABCDE FG HIKJ

这相当于对应二叉树的前序遍历结果。

A

B

C

E

DH

I

K J

F

G

T1 T2 T3A F H

B C D G I J

E K

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

448448

森林的后根次序遍历

若森林 F = Ø,返回;否则

后根遍历森林 F 第一棵树的根结点的子树森

林{T11, …, T1k};

访问森林的根结点 r1;

后根遍历森林中除第一棵树外其他树组成的

森林{T2, ..., Tm}。 T1 T2 T3A F H

B C D G I J

E K

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

449449

A

B

C

E

DH

I

K J

F

G

森林的后根次序遍历的结果序列:

BCEDA GF KIJH

这相当于对应二叉树中序遍历的结果。

T1 T2 T3A F H

B C D G I J

E K

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

450450

广度优先遍历(层次序遍历)

AFH BCDGIJ EK

若森林 F 为空,返回;

否则

依次遍历各棵树的

根结点;

依次遍历各棵树根

结点的所有子女;

依次遍历这些子女

结点的子女结点;

T1 T2 T3A F H

B C D G I J

E K

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

451451

5.8 堆 ( Heap )

数据集合如果有序,将为各种操作带来便利。

在许多应用中,需要从收集到的数据中挑选具有最小或最大关键码的记彔。

对于这类应用,我们期望的数据结构应能支持插入操作,并能斱便地从中取出具有最小或最大关键码的记彔。这样的结构即为:

优先级队列

每次出队列的是优先权最高的元素。

在优先级队列的各种实现中,堆是最高效的一种

数据结构。

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

452

452

完全二叉树顺序表示

Ki≤K2i+1 &&Ki≤K2i+2

最小堆

完全二叉树顺序表示

Ki≥K2i+1 &&Ki≥K2i+2

最大堆

09

09

87

87

78

7845 45

65

65 31

3153 23

23

53

17

17

5.8.1 最小堆和最大堆

i从0开始

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

453453

堆的元素下标计算

由于堆存储在下标从 0 开始计数的一维数组中,

因此在堆中给定下标为 i 的结点时

a) 如果 i = 0,结点 i 是根结点,无双亲;否则

结点 i 的父结点为结点 (i-1)/2) ;

b) 如果 2i+1>n-1,则结点 i 无左子女;否则

结点 i 的左子女为结点 2i+1;

c) 如果 2i+2>n-1,则结点 i 无右子女;否则

结点 i 的右子女为结点 2i+2。

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

454454

最小堆的类定义

#define DefaultSize 10;

typedef int E;

class MinHeap {

public:

MinHeap (int sz = DefaultSize); //构造函数

MinHeap (E arr[], int n);//根据数组建立堆的构造函数

~MinHeap() { delete [ ] heap; } //析构函数

bool Insert (E& d); //插入元素

bool Remove (E& d); //删除元素

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

455455

bool IsEmpty () const //判堆空否

{ return currentSize == 0; }

bool IsFull () const //判堆满否

{ return currentSize == maxHeapSize; }

void MakeEmpty () { currentSize = 0; } //置空堆

private:

E *heap; //最小堆元素存储数组

int currentSize; //最小堆当前元素个数

int maxHeapSize; //最小堆最大容量

void siftDown (int start, int m); //调整算法

void siftUp (int start); //调整算法

};

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

456456

堆的建立

MinHeap::MinHeap (int sz) { //建立空堆

maxHeapSize = (DefaultSize < sz) ? sz : DefaultSize;

heap = new E[maxHeapSize]; //创建堆空间

if (heap == NULL) {

cerr << ―堆存储分配失败!” << endl; exit(1);

}

currentSize = 0; //建立当前大小

};

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

457457

MinHeap::MinHeap (E arr[], int n) { //据arr[]建立堆

maxHeapSize = (DefaultSize < n) ? n : DefaultSize;

heap = new E[maxHeapSize];

if (heap = = NULL) {

cerr << ―堆存储分配失败!” << endl; exit(1);

}

for (int i = 0; i < n; i++) heap[i] = arr[i];

currentSize = n; //复制堆数组, 建立当前大小

int currentPos = (currentSize-2)/2;

//找最初调整位置:最后分支结点

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

458458

while (currentPos >= 0) { //逐步向上扩大堆

siftDown (currentPos, currentSize-1);

//局部自上向下下滑调整

currentPos--;

}

};

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

459459

自下向上逐步调整为最小堆

currentPos = i = 3 currentPos = i = 2

53

17 78

09

23 45 65 87

i

53

17 78

09

23

45 65 87

i

将一组用数组存放的任意数据调整成堆

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

460460

currentPos = i = 1

53

17

7809

23

45

65

87

i 53

17 78

09

23

45

65

87

i

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

461461

currentPos = i = 0

53

17 78

09

23

45

65

87

i

17 78

09

23

45

65

87

i

0953

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

462462

17

78

09

23

45

65

87

i

53

17

78

09

23 45

65

87i1753

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

463

下滑调整的算法设计思路:

①设置调整的初始位置(从start开始),把其值赋

给临时变量temp(用于交换)

②若没有调整完,temp与其子女的小者比较

③若比小者小,则中断这次比较,否则,进行交换

④设置新的调整位置

重复②~④,直到调整完

463

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

464464

最小堆的下滑调整算法

void MinHeap::siftDown (int start, int m ) {

//私有函数: 从结点start开始到m为止, 自上向下比较,

//如果子女的值小于父结点的值, 则关键码小的上浮,

//继续向下层比较, 将一个集合局部调整为最小堆。

int i = start, j = 2*i+1; //j是i的左子女位置

E temp = heap[i];

while (j <= m) { //检查是否到最后位置

if ( j < m && heap[j] > heap[j+1] ) j++;

//让j指向两子女中的小者

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

465465

if ( temp <= heap[j] ) break; //小则不做调整

else { heap[i] = heap[j];

i = j; j = 2*j+1;}

} //否则小者上移, i, j下降

heap[i] = temp; //回放temp中暂存的元素

};

每次插入都加在堆的最后,再自下向上执

行调整,使之重新形成堆。

最小堆的插入

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

466466

在堆中插入新元素11

17

78

09

23 45

65

87

i

1153

j

53

17

78

09

23 45

65

87

j

11

23

i

最小堆的向上调整

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

467467

17

78

09

45

65

87

j

11

53

i

23

17

53

11

78

09

17 45

65

87

23

j

i

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

468468

bool MinHeap::Insert (const E& x ) {

//公共函数: 将x插入到最小堆中

if ( currentSize == maxHeapSize ) //堆满

{ cerr << "Heap Full" << endl; return false; }

heap[currentSize] = x; //插入

siftUp (currentSize); //向上调整

currentSize++; //堆计数加1

return true;

};

时间复杂性O(log2n)

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

469469

void MinHeap::siftUp (int start) {

//私有函数: 从结点start开始到结点0为止, 自下向上

//比较, 如果子女的值小于父结点的值, 则相互交换.

int j = start, i = (j-1)/2; E temp = heap[j];

while (j > 0) { //沿父结点路径向上直达根

if (heap[i] <= temp) break;//父结点值小, 不调整

else { heap[j] = heap[i]; j = i; i = (j-1)/2; }

//父结点值大, 调整

}

heap[j] = temp; //回送

};

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

470470

最小堆的删除算法bool MinHeap::Remove (E& x) {

if ( !currentSize ) { //堆空, 返回FALSE

cout << "Heap empty" << endl; return false;

}

x = heap[0];

heap[0] = heap[currentSize-1];

currentSize--;

siftDown(0, currentSize-1);

//自上向下调整为堆

return ture;

};

53

11

78

09

17 45

65

87

23

j

i

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

471471

5.9 Huffman树

路径长度 (Path Length)

两个结点之间的路径长度 PL 是连接两结点

的路径上的分支数。

树的外部路径长度是各叶结点(外结点)到

根结点的路径长度之和 EPL。

树的内部路径长度是各非叶结点(内结点)

到根结点的路径长度之和 IPL。

树的路径长度 PL = EPL + IPL。

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

472472

1 1

2 23 3

4 45 56

6

7

78

8IPL = 0+1+1+2 = 4

EPL = 2+2+2+3 = 9

PL = 13IPL = 0+1+2+3 = 6EPL = 1+2+3+4 = 10PL = 16

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

473473

n 个结点的二叉树的路径长度不小于下述数

列前 n 项的和,即

其路径长度最小者为

完全二叉树或理想平衡树满足这个要求。

n

i

iPL1

2log

332222110

n

i

iPL1

2log

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

474474

带权路径长度(Weighted Path Length, WPL)

在很多应用问题中为树的叶结点赋予一个权值,

用于表示出现频度、概率值等。因此,在问题

处理中把叶结点定义得不同于非叶结点,把叶

结点看成“外结点”,非叶结点看成“内结点”。这

样的二叉树称为扩充二叉树。

扩充二叉树中只有度为 2 的内结点和度为 0的

外结点。根据二叉树的性质,有 n 个外结点就

有 n-1 个内结点,总结点数为2n-1。

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

475475

若一棵扩充二叉树有 n 个外结点,第 i 个外结点

的权值为wi,它到根的路径长度为li,则该外结

点到根的带权路径长度为wi*li。

扩充二叉树的带权路径长度定义为树的各外结点

到根的带权路径长度之和。

对于同样一组权值,如果放在外结点上,组织方

式不同,带权路径长度也不同。

n

i

ii lwWPL1

2

4

5 7

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

476476

具有不同带权路径长度的扩充二叉树

WPL = 2*2+ WPL = 2*1+ WPL = 7*1+

4*2+5*2+ 4*2+5*3+ 5*2+2*3+

7*2 = 36 7*3 = 46 4*3 = 35

带权路径长度达到最小

2 4

5

2

4

5 7

7

2 4 5 7

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

477477

Huffman树 带权路径长度达到最小的扩充二叉树即为

Huffman树。

在Huffman树中,权值大的结点离根最近。

Huffman树的构造算法

1. 由给定 n 个权值 {w0, w1, w2, …, wn-1},构造

具有 n 棵扩充二叉树的森林 F = { T0, T1, T2,

…, Tn-1 },其中每棵扩充二叉树 Ti 只有一个

带权值 wi 的根结点, 其左、右子树均为空。

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

478478

2. 重复以下步骤, 直到 F 中仅剩一棵树为止:

a)在 F 中选取两棵根结点的权值最小的扩充

二叉树, 做为左、右子树构造一棵新的

二叉树。置新的二叉树的根结点的权值为

其左、右子树上根结点的权值之和。

b)在 F 中删去这两棵二叉树。

c)把新的二叉树加入 F。

Huffman树的构造过程

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

479479

F : {7} {5} {2} {4}

7 5 2 4

初始

F : {7} {5} {6}

合并{2} {4}

7 5

2 4

6

F : {7} {11}

7 5

2 4

6

11

合并{5} {6}

F : {18}

5

合并{7} {11}2

7

4

6

11

18

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

480

练习:

已知某图片中只可能出现八种颜色,其

概率分别为0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11,试设计其赫夫曼编码。

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

测试内容:

1.写出二叉树类的定义,并实现前序、中序

、后序遍历、统计结点个数、求二叉树深

度的函数。

2.实现二叉树的前序、中序、后序线索化

3.能够实现树、森林与二叉树的相互转换

4.根据给定的一组权值,能够建立Huffman树

并生成各个权值所对应的Huffman编码。

481

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

482482

Huffman树的类定义#include "heap.h"

const int DefaultSize = 20; //缺省权值集合大小

typedef int E;

struct HuffmanNode { //树结点类的定义

E data; //结点数据

HuffmanNode *parent;

HuffmanNode *leftChild, *rightChild;

//左、右子女和父结点指针

HuffmanNode () : data(0),Parent(NULL),

leftChild(NULL), rightChild(NULL) { } //构造函数

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

483483

HuffmanNode (E elem, //构造函数

HuffmanNode *pr = NULL,

HuffmanNode *left = NULL,

HuffmanNode*right = NULL)

: data (elem), parent (pr), leftChild (left),

rightChild (right) { }

};

class HuffmanTree { //Huffman树类定义

public:

HuffmanTree (E w[], int n); //构造函数

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

484484

~HuffmanTree() { deleteTree(root);} //析构函数

protected:

HuffmanNode *root; //树的根

void deleteTree (HuffmanNode *t);

//删除以 t 为根的子树

void mergeTree (HuffmanNode & ht1,

HuffmanNode& ht2,

HuffmanNode*& parent);

};

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

486486

建立Huffman树的算法HuffmanTree::HuffmanTree (E w[], int n) {

//给出 n 个权值w[0]~w[n-1], 构造Huffman树

minHeap hp; //使用最小堆存放森林

HuffmanNode*parent, first, second;

HuffmanNode*NodeList =

new HuffmanNode[n]; //森林

for (int i = 0; i < n; i++) {

NodeList[i].data = w[i];

NodeList[i].leftChild = NULL;

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

487487

NodeList[i].rightChild = NULL;

NodeList[i].parent = NULL;

hp.Insert(NodeList[i]); //插入最小堆中

}

for (i = 0; i < n-1; i++) { //n-1趟, 建Huffman树

hp.Remove (first); //根权值最小的树

hp.Remove (second); //根权值次小的树

merge (first, second, parent); //合并

hp.Insert (*parent); //重新插入堆中

root = parent; //建立根结点

}

};

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

488488

void HuffmanTree::

mergeTree (HuffmanNode & bt1,

HuffmanNode & bt2,

HuffmanNode*& parent) {

parent = new HuffmanNode;

parent->leftChild = &bt1;

parent->rightChild = &bt2;

parent->data=bt1.root->data+bt2.root->data;

bt1->parent = bt2->parent = parent;

};

F : {7} {5} {6}

合并{2} {4}

7 5

2 4

6

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

489

主函数:void main(){

int w[8]={3,5,29,7,8,14,23,11};

HuffmanTree ht(w,8);

}

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

490490

5.9.4 Huffman编码 主要用途是实现数据压缩。

设给出一段报文:

CAST CAST SAT AT A TASA

字符集合是 { C, A, S, T },各个字符出现的频度

(次数)是 W={ 2, 7, 4, 5 }。

若给每个字符以等长编码(2位二进制足够)

A : 00 T : 10 C : 01 S : 11

则总编码长度为 ( 2+7+4+5 ) * 2 = 36。

能否减少总编码长度,使得发出同样报文,可以用

最少的二进制代码?

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

491491

若按各个字符出现的概率不同而给予不等长

编码,可望减少总编码长度。

各字符出现概率为{ 2/18, 7/18, 4/18, 5/18 },化

整为 { 2, 7, 4, 5 }。以它们为各叶结点上的权

值, 建立Huffman树。左分支赋 0,右分支赋

1,得Huffman编码(变长编码)。

7 2 5 4

0 1

00 11

A C T S

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

492492

A : 0 T : 10 C : 110 S : 111

它的总编码长度:7*1+5*2+( 2+4 )*3 = 35。

比等长编码的情形要短。

总编码长度正好等于Huffman树的带权路径

长度WPL。

Huffman编码是一种

前缀编码,即任一个

二进制编码不是其他

二进制编码的前缀。

解码时不会混淆。Huffman编码树

0

0

0

1

1

1

2 4

5

7

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

493

第七章 搜索结构

数据结构电子教案

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

494

静态搜索表(顺序 、折半)

二叉搜索树

哈希搜索

第七章 搜索结构

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

495

第7章 搜索结构

在英汉字典中查找某个英文单词的中文解释;在新

华字典中查找某个汉字的读音、含义;在对数表、平方

根表中查找某个数的对数、平方根;邮递员送信件要按

收件人的地址确定位置等等。可以说查找是为了得到某

个信息而常常进行的工作。

计算机、计算机网络使信息查询更快捷、方便、准

确。要从计算机、计算机网络中查找特定的信息,就需

要在计算机中存储包含该特定信息的表。如要从计算机

中查找英文单词的中文解释,就需要存储类似英汉字典

这样的信息表,以及对该表进行的查找操作。本章将讨

论的问题即是―信息的存储和查找‖。

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

496

基本概念

以学校招生录取登记表为例,来讨论计算机中表的概念。

1.数据项 (也称项或字段)

项是具有独立含义的标识单位,是数据不可分割的

最小单位。如表中―学号‖、―姓名‖、 ―年‖等。

学 号 姓 名性别

出生日期来 源 总分 录取专业

年 月 日

┆20010983

20010984

20010985

┆赵剑平

蒋伟峰

郭 娜┆

┆男男女┆

┆1982

1982

1983

┆11

09

01

┆05

12

25

┆石家庄一中保定三中易县中学

┆593

601

598

┆计算机

计算机

计算机

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

497

2.组合项

由若干项、组合项构成,表中―出生日期‖就是组合

项,它由―年‖、―月‖、―日‖三项组成。

3.数据元素(记录)

数据元素是由若干项、组合项构成的数据单位,

是在某一问题中作为整体进行考虑和处理的基本单位。

4.关键码

关键码是数据元素(记录)中某个项或组合项的

值,用它可以标识一个数据元素(记录)。能唯一确定

一个数据元素(记录)的关键码,称为主关键码;而不

能唯一确定一个数据元素(记录)的关键码,称为次关

键码。表中―学号‖即可看成主关键码,―姓名‖则应视为

次关键码,因可能有同名同姓的学生。

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

498

5.搜索表

是由具有同一类型(属性)的数据元素(记录)组

成的集合。分为静态搜索表和动态搜索表两类。

静态搜索表:仅对搜索表进行搜索操作,而不能改变的表;

动态搜索表:对搜索表除进行搜索操作外,可能还

要进行向表中插入数据元素,或删除表中数据元素的表。

6.搜索

按给定的某个值kx,在搜索表中搜索关键码为给定

值kx的数据元素(记录)。

一旦找到,搜索成功,结束搜索过程,并给出找到

的数据元素(记录)的信息,若搜索失败,搜索结果应给

出一个―空‖记录或―空‖指针。

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

499

7.1.1 静态搜索表

静态搜索表是数据元素的线性表,可以是基于数

组的顺序存储或以线性链表存储。

7.1.2 顺序搜索

其查找方法为:从表的一端开始,向另一端逐个

按给定值kx与关键码进行比较,若找到,查找成功,并

给出数据元素在表中的位置;若整个表检测完,仍未找

到与kx相同的关键码,则查找失败,给出失败信息。

7.1 静态搜索结构

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

500

一般的顺序搜索算法在第二章已经讨论过,

本章介绍一种使用“监视哨”的顺序搜索方法。

设在数据表 dataList 中顺序搜索关键码与

给定值 x 相等的数据元素,要求数据元素在表

中从下标 0 开始存放, 下标为 CurrentSize 的元

素作为控制搜索过程自动结束的“监视哨”使用。

若搜索成功,则函数返回该元素在表中序

号 Location(比下标大 1), 若搜索失败,则

函数返回 CurrentSize+1。

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

501

使用监视哨的顺序搜索算法

int dataList::SeqSearch (const K x) const {

Element[CurrentSize].key = x;

int i = 0; //将x设置为监视哨

while (Element[i].key != x) i++;

//从前向后顺序搜索

return i;

};

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

算法分析:

查找成功时的平均查找长度ASL成功为:

n

i

i

n

i

i nin

CPASL11

2/)1(1

成功

时间效率为: O(n)

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

7.1.3 基于有序顺序表的折半搜索

有序顺序表上的查找算法主要有顺序搜索和折半搜索两种方法。

①顺序搜索

有序顺序表上的顺序搜索算法和顺序表上的搜索算法方法类同。

②二分查找(又称折半查找)

算法的基本思想:先给数据排序(例如按升序排好),形成有序表,然后再将key与正中元素相比,若key小,则缩小至前半部内查找;再取其中值比较,每次缩小1/2

的范围,直到查找成功或失败为止。反之,如果key大,则缩小至后半部内查找。

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

二分查找算法如下:

int BiSearch(DataType a[], int n, KeyType key){//在有序表a[0]--a[n-1]中二分查找关键码为key的数据元素//查找成功时返回该元素的下标序号;失败时返回-1

int low = 0, high = n – 1;//确定初始查找区间上下界

int mid;

while(low <= high)

{

mid = (low + high)/2;//确定查找区间中心下标

if(a[mid].key == key) return mid; //查找成功

else if(a[mid].key < key) low = mid + 1;

else high = mid - 1;

}

return -1; //查找失败

}

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

折半查找有序表

(4,6,10,12,20,30,50,70,88,100)。若在查找表中查找元素58,则它将依次与表中的( )比较大小,查找结果是失败。

A.20,70,30,50

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

算法分析

查找成功时的平均查找长度ASL成功为:

查找失败时的平均查找长度ASL失败为:

nnn

ni

nCPASL

n

i

k

i

i

ii 22

1 1

1 log1)1(log11

2成功

)1(log)1(log1

22

1 1

nnn

CPASLn

i

n

i

ii失败

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

7.2 二叉搜索树

动态查找表主要有二叉树结构和树结构两种类型。

二叉树结构有二叉搜索树(排序树)、平衡二叉树

等。树结构有B-树、B+树等。

7.2.1二叉搜索树的基本概念

----或是一棵空树;或者是具有如下性质的非空二叉树:

(1)左子树的所有结点均小于根的值;

(2)右子树的所有结点均大于根的值;

(3)它的左右子树也分别为二叉排序树。

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

381

12 410

9 40 394 540

35 190

146

476 760

445 600 800

下图所示就是一棵二叉排序搜索树:

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

下图依次插入数据元素4,5,7,2,1,9,8,11,3的过程:

4

52

71

4

52

7

4

5

7

4

5

4

(d)(c)(b)(a)

(g)(f)(e)

4

52

71

9

4

52

71

9

8

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

下图依次插入数据元素4,5,7,2,1,9,8,11,3的过程:

(h)

4

52

71

9

811

(i)

4

52

71

9

811

3

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

一棵二叉排序树的平均查找长度为:

其中:ni 是每层结点个数;

Ci 是结点所在层次数;

m 为树深。

当二叉排序树是一棵单分支退化树时,查找成功的平

均查找长度和有序顺序表的平均查找长度相同,即为:n

i

nin

ASL1

2/)1(1

成功

若每个数据元素的查找概率相等,则二叉排序树查找

成功的平均查找长度为:

)1(log)2(1

2

1

1 nin

ASLk

i

i

成功

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

3

4

5

6

7

8

10

6

4 8

3 5 7 10

(a)(b)

(a)满二叉排序树时,k =

log2(7+1)=3,所以查找成功的平

均查找长度为:

7

17)34221(

7

1)2(

1 1

1

in

ASL ik

i

成功

(b)左分支退化二叉排序树时,

k = n=7,所以查找成功的平均

查找长度为:

42/)17(2/)1(1

1

n

i

nin

ASL成功

在最坏情况下,二叉排序树的平均查找长度为O(n)。在一般情况下,二叉排序树的平均查找长度为O(log2n)。

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

513

输入数据 { 53, 78, 65, 17, 87, 09, 81, 15 }

53 53

78

53

78

65

53

78

65

17

53

78

65 87

17

53

78

6509

17

87

53

78

65

81

17

8709

53

78

65

15

17

8709

81

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

514

二叉搜索树的删除算法 在二叉搜索树中删除一个结点时,必须将因

删除结点而断开的二叉链表重新链接起来,

同时确保二叉搜索树的性质不会失去。

为保证在删除后树的搜索性能不至于降低,

还需要防止重新链接后树的高度增加。

删除叶结点,只需将其双亲结点指向它的

指针清零,再释放它即可。

被删结点右子树为空,可以拿它的左子女

结点顶替它的位置,再释放它。

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

515

被删结点左子树为空,可以拿它的右子女结

点顶替它的位置,再释放它。

被删结点左、右子树都不为空,可以在它的

右子树中寻找中序下的第一个结点(关键码

最小),用它的值填补到被删结点中,再来处

理这个结点的删除问题。

53

78

65

17

8709

23

45

删除45

右子树空, 用左子女顶替

53

78

65

17

8709 23

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

516

88

53

78

88

17

9409 23

删除78

左子树空, 用右子女顶替

53

94

88

17

09 23

53

78

81

17

9409 45

删除78

在右子树上找中序下第一个结点填补23

65

53

81

88

17

9409 45

23

65

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

6.5 散列

哈希函数:数据元素的关键字和该数据元素的存放位置之间的映射函数

哈希表:通过哈希函数来确定数据元素存放位置的一种特殊表结构。

6.5.1.哈希表

构造斱法是:设要存储的数据元素个数为n,设置一个长度为m(m≥n)的连续内存单元,分别以每个数据元素的关键字Ki(0≤i≤n-1)为自变量,通过哈希函数h(Ki),把Ki映射为内存单元的某个地址h(Ki),并把该数据元素存储在这个内存单元中。哈希函数h(Ki)实际上是关键字Ki到内存单元的映射,因此,h(Ki)也称为哈希地址,哈希表也称作散列表。

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

构造哈希表时出现Ki≠Kj(i≠j),但h(Ki)=h(Kj)的现

象称作哈希冲突。这种具有不同关键字而具有相同哈希地址的数据元素称作“同义词”,由同义词引起的冲突称作同义词冲突。

解决哈希冲突的基本思想是通过哈希冲突函数(设为 hl(K)(l=1,2,…,m-1))产生一个新的哈希地址使hl(Ki)≠hl(Kj)。把要存储的n个数据元素通过哈希函数映射到了m个连续内存单元中,从而完成了哈希表的构造。

可见,构造哈希表时一定要使用主关键字,不能使用次关键字。

例:建立数据元素集合a的哈希表,a =

{180,750,600,430,541,900,460},并比较m取值不同时的哈希冲突情冴。

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

构造哈希表时 ,冲突是不可避免的,有关因素主要有如下三个:

(1)装填因子。装填因子是指哈希表中已存入的数据元素个数n与哈希地址空间大小m的比值,即α=n/m,α越小,冲突的可能性就越小,但哈希表中空闲单元的比例就越大; α越大(最大可取1)时,冲突的可能性

就越大,但哈希表中空闲单元的比例就越小,存储空间的利用率就越高。

(2)与所采用的哈希函数有关。

(3)与解决哈希冲突的哈希冲突函数有关。

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

6.5.2.哈希函数的构造方法

常用的哈希函数构造斱法有:

1.除法取余

2.直接定址法

3.数字分析法

4.折叠法

5.平斱取中

函数设计目标:使通过哈希函数得到的n个数据元素的哈希地址尽可能均匀地分布在m个连续内存单元上,同时使计算过程尽可能简单以达到尽可能高的时间效率。

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

h(K) = K mod m

优点:计算简单,适用范围广关键:选好哈希表长度m

技巧:哈希表长m取质数时效果最好,一般为一个4k+3的质数。

①除法取余

②直接定址法

h(K) = K + C

优点:计算简单,不会发生冲突缺点:有可能造成内存单元的大量浪费

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

特点:取数据元素关键字中某些取值较均匀的数字位作为哈希地址,只适合于所有关键字值已知的情冴

④折叠法

将关键字分割成若干部分,然后取它们的叠加和为哈希地址。有两种叠加处理的斱法:移位叠加和间界叠加。⑤平斱取中

以关键字的平斱值的中间几位作为存储地址。求“关键字的平斱值” 的目的是“扩大差别” ,同时平斱值的中间各位又能受到整个关键字中各位的影响。

③数字分析法

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

常见的冲突处理斱法有:

一、开放定址法

二、链表法

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

)11(mod)1(

)(

1

0

mimdd

Khd

ii

优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素;

缺点:容易产生―堆积” ,大大降低了查找效率。

1.线性探查法

6.5.3 处理冲突的闭散列斱法

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

2.平方(二次)探查法

midd

midd

Khd

i

i

mod)(

mod)(

)(

2

0

2

0

0

平斱探查法的探查跨步很大,所以可避免出现堆积问题

)11( mi

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

设定哈希函数 H(key) = key MOD 11 ( 表长=11 )

0 1 2 3 4 5 6 7 8 9 10

1901 23 1455 68

若采用线性探测再散列处理冲突

若采用二次探测再散列处理冲突

11 82 36

1 1 1 1 3 6 2 5 1

例如: 关键字集合

{ 19, 01, 23, 14, 55, 68, 11, 82, 36 }

8 1 2 3 0 2 0 5 3

0 1 2 3 4 5 6 7 8 9 10

1901 23 14 6855 118236

1 1 1 1 2 1 4 1 3

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

二次探测再散列处理冲突 设哈希表长为14,哈希函数是H(key)=key%11,表中已有数据

的关键字为15,38,61,84共四个,现要将关键字为49的结点加到表中,用二次探测再散列法解决冲突,则放入的位置是( ) 【南京理工大学 2001 一、15 (1.5分)】

A.8 B.3 C.5 D.9计算步骤如下:15,38,61,84用哈希函数H(key)=key%11计算后得地址:4,5,6,749计算后为5,发生冲突,用二次探测再散列法解决冲突: 1:(key+1^2)%14=(49+1)%14=8,不再发生冲突.得出结果为A

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

二次探测再散列处理冲突 设哈希表长为14,哈希函数是H(key)=key%11,表中已有数据

的关键字为15,38,61,84共四个,现要将关键字为49的结点加到表中,用二次探测再散列法解决冲突,则放入的位置是( ) 【南京理工大学 2001 一、15 (1.5分)】

A.8 B.3 C.5 D.9计算步骤如下:15,38,61,84用哈希函数H(key)=key%11计算后得地址:4,5,6,749计算后为5,发生冲突,用二次探测再散列法解决冲突: 1:(d0+1^2)%14=(5+1)%14=6,

2:(d0+2^2)%14=(5+4)%14=9,不再发生冲突,得出结果为D

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

6.5.4 处理冲突的开散列(链地址)方法

基本思想:如果没有发生哈希冲突,则直接存放该数据元素;如果发生了哈希冲突,则把发生哈希冲突的数据元素另外存放在单链表中。

例:建立数据元素集合a的哈希表。a = {16, 74, 60, 43, 54, 90,

46, 31, 29, 88, 77, 66, 55}。要求哈希函数采用除留余数法,解决冲突斱法采用链表法。设计分析:数据元素集合a中共有13个数据元素,取哈希表的

内存单元个数m=13。除留余数法的哈希函数为:h(K) = K mod

m有: h(16) = 3 h(74) = 9 h(60) = 8

h(43) = 4 h(54) = 2 h(90) = 12

h(46) = 7 h(31) = 5 h(29) = 3

h(88) = 10 h(77) = 12 h(66) = 1

h(55) = 3

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

采用链表斱法建立的哈希表存储结构

如下图所示

linkdata下标

0

661

542

163

434

315

6

467

608

749

8810

11

9012

29 55

77

∧∧

∧∧

data next

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

将所有哈希地址相同的记彔都链接在同一链表中。

0

1

2

3

4

5

6

14

01 36

19 82

23

11

68

55

ASL=(6×1+2×2+3)/9=13/9

例如:关键字集合

{19, 01, 23, 14, 55, 68,

11, 82, 36} ,哈希函数

为 H(key)=key MOD 7

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

532532

第八章 图

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

533146-533

图的基本概念

图的存储表示

图的遍历与连通性

最小生成树

最短路径

网络(拓扑排序、关键路径)

第八章 图

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

534146-534

8.1.1 图的有关概念

图定义 图是由顶点集合(vertex)及顶点间的

关系集合组成的一种数据结构:

Graph=( V, E )

其中 V = { x | x 某个数据对象}

是顶点的有穷非空集合;

E = {(x, y) | x, y V }

或 E = {<x, y> | x, y V && Path (x, y)}

是顶点之间关系的有穷集合,也叫做边(edge)

集合。

Path (x, y)表示从 x 到 y 的一条单向通路, 它是

有方向的。

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

535146-535

有向图与无向图 在有向图中,顶点对 <x, y>

是有序的。在无向图中,顶点对(x, y)是无序

的。

完全图 若有 n 个顶点的无向图有 n(n-1)/2 条

边, 则此图为完全无向图。有 n 个顶点的有向

图有n(n-1) 条边, 则此图为完全有向图。

0 0 0 0

1

1

1 12 2

2 265433

8.1.1 图的有关概念

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

536146-536

邻接顶点 如果 (u, v) 是 E(G) 中的一条边,

则称 u 与 v 互为邻接顶点。

子图 设有两个图G=(V, E) 和G'=(V', E')。

若V ' V 且E' E, 则称图G'是图G的子图。

权 某些图的边具有与它相关的数, 称之为权。

这种带权图叫做网络。

子图

0

1 2

3

0

1

3

0

1 2

3

0

2

3

8.1.1 图的有关概念

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

537146-537

顶点的度 一个顶点v的度是与它相关联的边的

条数。记作TD(v)。在有向图中, 顶点的度等于

该顶点的入度与出度之和。

顶点 v 的入度是以 v 为终点的有向边的条数,

记作 ID(v); 顶点 v 的出度是以 v 为始点的有向

边的条数, 记作 OD(v)。

路径 在图 G=(V, E) 中, 若从顶点 vi 出发, 沿

一些边经过一些顶点 vp1, vp2, …, vpm,到达顶

点vj。则称顶点序列 (vi vp1 vp2 ... vpm vj) 为从顶

点vi 到顶点 vj 的路径。它经过的边(vi, vp1)、

(vp1, vp2)、...、(vpm, vj) 应是属于E的边。

8.1.1 图的有关概念

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

538146-538

路径长度 非带权图的路径长度是指此路径上

边的条数。带权图的路径长度是指路径上各边

的权之和。

简单路径 若路径上各顶点 v1, v2, ..., vm 均不

互相重复, 则称这样的路径为简单路径。

回路 若路径上第一个顶点 v1 与最后一个顶

点vm 重合, 则称这样的路径为回路或环。

0

1 2

3

0

1 2

3

0

1 2

3

8.1.1 图的有关概念

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

539

连通图与连通分量 在无向图中, 若从顶点v1

到顶点v2有路径, 则称顶点v1与v2是连通的。

如果图中任意一对顶点都是连通的, 则称此图

是连通图。非连通图的极大连通子图叫做连

通分量。

强连通图与强连通分量 在有向图中, 若对于

每一对顶点vi和vj, 都存在一条从vi到vj和从vj到

vi的路径, 则称此图是强连通图。非强连通图

的极大强连通子图叫做强连通分量。

生成树 一个连通图的生成树是其极小连通子

图,在 n 个顶点的情形下,有 n-1 条边。

8.1.1 图的有关概念

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

540540

8.2 图的存储表示

图的特点:非线性结构(m :n )

• 邻接表

• 邻接多重表

• 十字链表

设计为邻接矩阵链式存储结构:

顺序存储结构:无! (多个顶点,无序可言)

但可用数组描述元素间关系。

可用多重链表

重点介绍:邻接矩阵(数组)表示法

邻接表(链式)表示法

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

541541

8.2.1 图的邻接矩阵(数组)表示

,

),( , ,]][[ .

否则

或者如果

0

><1A

EjiEjijiEdge

建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表

示各个顶点之间关系)。

设图 A = (V, E) 有 n 个顶点,则图的邻接矩阵是一个二维数

组 A.Edge[n][n],定义为:

v1 v2

v3

v5v4v4

A

例1:

邻接矩阵:

A.Edge =

( v1 v2 v3 v4 v5 )

v1

v2

v3

v4

v5

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0分析1:无向图的邻接矩阵是对称的;

分析2:顶点i 的度=第 i 行 (列) 中1 的个数;

特别:完全图的邻接矩阵中,对角元素为0,其余全1。

0 1 0 1 0

1 0 1 0 1

0 1 0 1 1

1 0 1 0 1

0 1 1 1 0

顶点表:

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

542542

例2 :有向图的邻接矩阵

分析1:有向图的邻接矩阵可能是不对称的。

分析2:顶点的出度=第i行元素之和,OD( Vi )= A.Edge[ i ][j ]

顶点的入度=第i列元素之和。ID( Vi )= A.Edge[ j ][i ]

顶点的度=第i行元素之和+第i列元素之和,

即:TD(Vi)=OD( Vi ) + ID( Vi )

v1 v2

v3 v4

A邻接矩阵:

A.Edge =

( v1 v2 v3 v4 )

v1

v2

v3

v4

0 0 0 0

0 0 0 0

0 0 0 0

0 0 0 0

注:在有向图的邻接矩阵中,

第i行含义:以结点vi为尾的弧(即出度边);

第i列含义:以结点vi为头的弧(即入度边)。

顶点表:

0 1 1 0

0 0 0 0

0 0 0 1

1 0 0 0

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

543543

特别讨论 :网(即带权图)的邻接矩阵

定义为:

v1 v2

v3

v4

N

v5

v6

548

97

5

5

61

3

以有向网为例:

邻接矩阵: 0 ∞ ∞ ∞ ∞ ∞

∞ 0 ∞ ∞ ∞ ∞

∞ ∞ 0 ∞ ∞ ∞

∞ ∞ ∞ 0 ∞ ∞

∞ ∞ ∞ ∞ 0 ∞

∞ ∞ ∞ ∞ ∞ 0

N.Edge =

( v1 v2 v3 v4 v5 v6 )顶点表:

0 5 ∞ 7 ∞ ∞

∞ 0 4 ∞ ∞ ∞

8 ∞ 0 ∞ ∞ 9

∞ ∞ 5 0 ∞ 6

∞ ∞ ∞ 5 0 ∞

3 ∞ ∞ ∞ 1 0

v1

v2

v3

v4

v5

v6

ji

ji,ji,ji

ji,ji,jiji

ji

若或且若或且若

,

)(,

)(),,(

]][[

0

EE

EEW

A.edge

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

544544

容易实现图的操作,如:求某顶点的度、判断顶点之间

是否有边(弧)、找顶点的邻接点等等。

n个顶点需要n*n个单元存储边(弧);空间效率为O(n2)。

对稀疏图而言尤其浪费空间。

特别讨论 :网(即带权图)的邻接矩阵

邻接矩阵法优点:

邻接矩阵法缺点:

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

545146-545

用邻接矩阵表示的图的类定义

typedef int E; //边的权值

typedef char T; //顶点名

class Graphmtx {

friend istream& operator >> ( istream& in,

Graphmtx & G); //输入

friend ostream& operator << (ostream& out,

Graphmtx & G); //输出

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

546146-546

private:

T *VerticesList; //顶点表

E **Edge; //邻接矩阵

int getVertexPos (T vertex) {

//给出顶点vertex在VerticesList中的下标

for (int i = 0; i < numVertices; i++)

if (VerticesList[i] == Vertex) return i;

return -1;

};

public:

Graphmtx (int sz = DefaultVertices); //构造函数

~Graphmtx () //析构函数

{ delete [ ]VerticesList; delete [ ]Edge; }

用邻接矩阵表示的图的类定义

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

547146-547

T getValue (int i) {

//取顶点 i 的值, i 不合理返回0

if (i >= 0 && i < numVertices ) return VerticesList[i];

else { cout<<“位置错!”<<endl;exit(1);}

}

E getWeight (int v1, int v2) { //取边(v1,v2)上权值return (v1 != -1 && v2 != -1 )? Edge[v1][v2] : 0;

}

int getFirstNeighbor (int v);

//取顶点 v 的第一个邻接顶点

int getNextNeighbor (int v, int w);

//取 v 的邻接顶点 w 的下一邻接顶点

用邻接矩阵表示的图的类定义

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

548146-548

bool insertVertex (const T vertex);

//插入顶点vertex

bool insertEdge (int v1, int v2, E cost);

//插入边(v1, v2),权值为cost

bool removeVertex (int v);

//删去顶点 v 和所有与它相关联的边

bool removeEdge (int v1, int v2);

//在图中删去边(v1,v2)

Private :

int numVertices;

int maxVertices;

int numEdges;

};

用邻接矩阵表示的图的类定义

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

549146-549

Graphmtx::Graphmtx (int sz) { //构造函数

maxVertices = sz;

numVertices = 0; numEdges = 0;

int i, j;

VerticesList = new T[maxVertices]; //创建顶点表

Edge = (int **) new int *[maxVertices];

for (i = 0; i < maxVertices; i++)

Edge[i] = new int[maxVertices]; //邻接矩阵

for (i = 0; i < maxVertices; i++) //矩阵初始化

for (j = 0; j < maxVertices; j++)

Edge[i][j] = (i == j) ? 0 : maxWeight;

};

用邻接矩阵表示的图类的部分成员函数

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

550146-550

int Graphmtx::getFirstNeighbor (int v) {

//给出顶点位置为v的第一个邻接顶点的位置,

//如果找不到, 则函数返回-1

if (v != -1) {

for (int col = 0; col < numVertices; col++)

if (Edge[v][col] && Edge[v][col] < maxWeight)

return col;

}

return -1;

};

用邻接矩阵表示的图类的部分成员函数

v1 v2

v3

v5v4v4

A.Edge =

( v1 v2 v3 v4 v5 )

v1

v2

v3

v4

v5

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 3 ∞ 2 ∞

3 0 7 ∞ 4

∞ 7 0 6 8

2 ∞ 6 0 5

∞ 4 8 5 0

3

2 6

5

7 48

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

551146-551

int Graphmtx::getNextNeighbor (int v, int w) {

//给出顶点 v 的某邻接顶点 w 的下一个邻接顶点

if (v != -1 && w != -1) {

for (int col = w+1; col < numVertices; col++)

if (Edge[v][col] && Edge[v][col] < maxWeight)

return col;

}

return -1;

};

用邻接矩阵表示的图类的部分成员函数

v1 v2

v3

v5v4v4

A.Edge =

( v1 v2 v3 v4 v5 )

v1

v2

v3

v4

v5

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 0 0 0 0

0 3 ∞ 2 ∞

3 0 7 ∞ 4

∞ 7 0 6 8

2 ∞ 6 0 5

∞ 4 8 5 0

3

2 6

5

7 48

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

552146-552

bool Graphmtx::insertVertex (const T vertex ) {

//插入顶点 vertex

if (numVertices==maxVertices) return false;

VerticesList[numVertices++] =vertex;

return ture;

};

插入顶点成员函数

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

553146-553

bool Graphmtx::removeVertex (int v ) {

//删除序号为v的顶点 及其相关联的边

if (v < 0 ||v >= numVertices) {

cout << "参数v越界出错!" << endl;return false;}

for(int i = 0; i < numVertices; i++)

for(int j = 0; j < numVertices; j++)

if((i == v || j == v) && i != j && Edge[i][j] <maxWeight )

{ Edge[i][j] =maxWeight; //删除该边

Edge[j][i] = maxWeight;

numEdges --; } //边的个数减1

删除顶点成员函数

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

554

v1 v2

v3

v5v4v4

A.Edge =

v1 v2 v3 v4 v5 v1

v2

v3

v4

v5

0 3 ∞ 2 ∞

3 0 7 ∞ 4

∞ 7 0 6 8

2 ∞ 6 0 5

∞ 4 8 5 0

3

2 6

5

7 48

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

555146-555

for (i = v; i < numVertices-1; i ++)

for (j = 0;j < numVertices;j ++)

Edge[i][j]=Edge[i+1][j]; //删除邻接矩阵的第v行

for (i = 0;i < numVertices-1;i++)

for (j = v;j < numVertices-1;j ++)

Edge[i][j]=Edge[i][j+1]; //删除邻接矩阵的第v列

for (i=v;i< numVertices-1;i++)

Verticeslist[i] = Verticeslist[i + 1];

//在顶点表中删除顶点v

numVertices --;

return true;

};

删除顶点成员函数

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

556146-556

bool Graphmtx::insertEdge (int v1,int v2,E cost ) {

//插入一条起始顶点为v1、终止顶点为 v2的边

if(v1 < 0 || v1 > =numVertices

|| v2 < 0 || v2 >= numVertices)

{cout << "参数v1或v2越界出错!" << endl;return false;}

Edge[v1][v2] = cost; Edge[v2][v1] = cost; //插入边

numEdges++; //边的个数加1

return true;

}

插入边成员函数

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

557146-557

bool Graphmtx::removeEdge (int v1,int v2 ) {

//删除顶点v1与v2之间的边

if(v1 < 0 || v1 > =numVertices

|| v2 < 0 || v2 >= numVertices)

{cout << "参数v1或v2越界出错!" << endl;return false;}

if(Edge[v1][v2] == maxWeight || v1 == v2)

{cout << "该边不存在!" << endl; return false;}

Edge[v1][v2] = maxWeight; Edge[v2][v1] = maxWeight;

numEdges--; //边的个数减1

return true;}

删除边成员函数

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

558558

8.2.2 邻接表(链式)表示法

对每个顶点vi 建立一个单链表,把与vi有关联的边的信息(即入

度或出度边)链接起来,表中每个结点都设有2个域;

每个单链表附设一个头结点(设有2个域),存vi信息;

vertex link

表结点头结点

邻接点域,

表示vi一个

邻接点的值

链域,指向

vi下一个边

或弧的结点

数据域

存储顶点vi

链域

指向单链表的

第一个结点

每个单链表的头结点另外用顺序存储结构存储。

adjvex next

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

559559

例1:无向图的邻接表

v1 v2

v3

v5v4v4

邻接表

1

2

3

4

5

^42

42 ^5

1 3 ^5

例2:有向图的邻接表

v1 v2

v3 v4

邻接表

V4

V3

V2

V1^4

^1

^3

^1

逆邻接表

注:邻接表不唯一,因各个边结点的链入顺序是任意的。

v1

v2

v3

v4

v5 32 ^4

1 3 ^5

出边表

V4

V3

^V2

V1

顶点表 顶点表 入边表

2 ^3

^4

^1

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

560146-560

B

A

C

D6

95 2

8

data adj

A

B

C

D

0

1

2

3

dest cost link

1 5 3 6

2 8

3 2

1 9

(出边表)(顶点表)

网络 (带权图) 的邻接表

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

561561

80

641

25

例3:已知某网的邻接(出边)表,请画出该网络。

当邻接表的存

储结构形成后,

图便唯一确定!

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

562562

分析1:

对于n个顶点e条边的无向图,邻接表中除了n个头

结点外,只有2e个表结点,空间效率为O(n+2e)。

若是稀疏图(e<<n2),则比邻接矩阵表示法O(n2)省空间。

邻接表存储法的特点:

———它其实是对邻接矩阵法的一种改进

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

563563

分析2:

在有向图中,邻接表中除了n个头结点外,只有e个表

结点,空间效率为O(n+e)。若是稀疏图,则比邻接矩阵

表示法合适。

邻接表的缺点:

怎样计算有向图顶点的出度?

怎样计算有向图顶点的入度?

怎样计算有向图顶点Vi的度:

邻接表的优点:

I D( Vi ) =逆邻接表中链接的结点数

TD(Vi) = OD( Vi ) + I D( Vi )

空间效率高;容易寻找顶点的邻接点;

判断两顶点间是否有边或弧,需搜索两结点

对应的单链表,没有邻接矩阵方便。

OD(Vi)=单链出边表中链接的结点数

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

564564

讨论:邻接表与邻接矩阵有什么异同之处?

1. 联系:邻接表中每个链表对应于邻接矩阵中的一行,

链表中结点个数等于一行中非零元素的个数。

2. 区别:

① 对于任一确定的图,邻接矩阵是唯一的(行列号与

顶点编号一致),但邻接表不唯一(链接次序与顶

点编号无关)。

② 邻接矩阵的空间复杂度为O(n2),而邻接表的空间复

杂度为O(n+e)(有向图)或O(n+2e)(无向图)。

3. 用途:邻接矩阵多用于稠密图的存储(e接近n(n-

1)/2);而邻接表多用于稀疏图的存储(e<<n2)

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

565146-565

用邻接表表示的图的类定义

struct Edge { //边结点的定义

int dest; //边的另一顶点位置

E cost; //边上的权值

Edge *link; //下一条边链指针

Edge () {} //构造函数

Edge (int num, E cost) //构造函数

: dest (num), weight (cost), link (NULL) { }

bool operator != (Edge& R) const

{ return dest != R.dest; } //判边等否

};

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

566146-566

struct Vertex { //顶点的定义

T data; //顶点的名字

Edge *adj; //边链表的头指针

};

class Graphlnk { //图的类定义

friend istream& operator >> (istream& in,

Graphlnk& G); //输入

friend ostream& operator << (ostream& out,

Graphlnk & G); //输出

用邻接表表示的图的类定义

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

567146-567

private:

Vertex *NodeTable;

//顶点表 (各边链表的头结点)

int getVertexPos (const T vertx) {

//给出顶点vertex在图中的位置

for (int i = 0; i < numVertices; i++)

if (NodeTable[i].data == vertx) return i;

return -1;

}

public:

Graphlnk (int sz = DefaultVertices); //构造函数

~Graphlnk(); //析构函数

用邻接表表示的图的类定义

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

568146-568

T getValue (int i) { //取顶点 i 的值

return (i >= 0 && i < NumVertices) ?

NodeTable[i].data : 0;

}

E getWeight (int v1, int v2); //取边(v1,v2)权值

bool insertVertex (const T& vertex);

bool removeVertex (int v);

bool insertEdge (int v1, int v2, E cost);

bool removeEdge (int v1, int v2);

int getFirstNeighbor (int v);

int getNextNeighbor (int v, int w);

};

用邻接表表示的图的类定义

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

569146-569

Graphlnk::Graphlnk (int sz) {

//构造函数:建立一个空的邻接表

maxVertices = sz;

numVertices = 0; numEdges = 0;

NodeTable = new Vertex[maxVertices];

//创建顶点表数组

if (NodeTable == NULL)

{ cerr << "存储分配错!" << endl; exit(1); }

for (int i = 0; i < maxVertices; i++)

NodeTable[i].adj = NULL;

};

用邻接表表示的图类的构造函数

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

570146-570

Graphlnk::~Graphlnk() {

//析构函数:删除一个邻接表

for (int i = 0; i < numVertices; i++ ) {

Edge *p = NodeTable[i].adj;

while (p != NULL) {

NodeTable[i].adj = p->link;

delete p; p = NodeTable[i].adj;

}

}

delete [ ]NodeTable; //删除顶点表数组

};

用邻接表表示的图类的析构函数

B

A

C

D6

95 2

8

A 1 5 3 6

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

571146-571

int Graphlnk::getFirstNeighbor (int v) {

//给出顶点位置为 v 的第一个邻接顶点的位置,

//如果找不到, 则函数返回-1

if (v != -1) { //顶点v存在

Edge *p = NodeTable[v].adj;

//对应边链表第一个边结点

if (p != NULL) return p->dest;

//存在, 返回第一个邻接顶点

}

return -1;

};

取第一邻接点的函数

data adj dest cost link

A 1 5 3 6

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

572146-572

int Graphlnk::getNextNeighbor (int v, int w) {

//给出顶点v的邻接顶点w的下一个邻接顶点的位置,

//若没有下一个邻接顶点, 则函数返回-1

if (v != -1) { //顶点v存在

Edge *p = NodeTable[v].adj;

while (p != NULL && p->dest != w)

p = p->link;

if (p != NULL && p->link != NULL)

return p->link->dest; //返回下一个邻接顶点

}

return -1;

};

取下一邻接点的函数

data adj dest cost link

A 1 5 3 6

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

573

8.3 图的遍历

遍历:从已给的连通图中某一顶点出发,沿着一些边

访遍图中所有的顶点,且使每个顶点仅被访问一

次,就叫做图的遍历,它是图的基本运算。

遍历图的实质:找每个顶点的邻接点的过程。

图的特点:图中可能存在回路,且图的任一顶点都可

能与其它顶点相通,在访问完某个顶点之后可能

会沿着某些边又回到了曾经访问过的顶点。

怎样避免重复访问?

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

574

一、深度优先搜索

二、广度优先搜索

三、非连通图的遍历

8.3 图的遍历

解决思路:

可设置一个辅助数组 visited [n ],用来标记每个被访

问过的顶点。它的初始状态为0,在图的遍历过程中,

一旦某一个顶点i 被访问,就立即改 visited [i]为1,防

止它被多次访问。

图常用的遍历:

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

575575

一、深度优先搜索( DFS )

基本思想:——仿树的先序遍历过程。

Depth_First Search

v1

v1

v2 v3

v8

v7v6v4 v5

DFS 结果:例1:

→ → → →

→ → →

v2 v4 v8

v5 v3 v6 v7

例2:v2 → v1 → v3 → v5 →

DFS 结果:

v4 → v6

起点

起点

遍历步骤

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

576576

深度优先搜索(遍历)步骤:

①在访问图中某一起始顶点 v 后,由 v 出发,访问它的一

邻接顶点 w1;

②从 w1 出发,访问与 w1邻接但还未被访问过的顶点 w2;

然后再从 w2 出发,进行类似的访问,…

③如此进行下去,直至到达所有的邻接顶点都被访问过的

顶点 u 为止。

④接着,退回一步,退到前一次刚访问过的顶

点,看是否还有其它没有被访问的邻接顶点。

如果有,则访问此顶点,之后再从此顶点出

发,进行与前述类似的访问;

如果没有,就再退回一步进行搜索。重复上

述过程,直到连通图中所有顶点都被访问过为

止。

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

577577

0

0

0

0

0

0

1

2

3

4

5

6

0

1

0

0

0

0

1

1

0

0

0

0

1

1

1

0

0

0

1

1

1

0

1

0

1

1

1

1

1

0

1

1

1

1

1

1

DFS 结果:

A

辅助数组 visited [n ]

起点

v2→v1→v3→v5→

v4→v6

——开辅助数组 visited [n ]!

例: 1 2 3 4 5 6

1 0 1 1 1 0 0

2 1 0 0 0 1 0

3 1 0 0 0 1 0

4 1 0 0 0 0 1

5 0 1 1 0 0 0

6 0 0 0 1 0 0

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

578146-578

图的深度优先搜索算法

void DFS (Graph& G, const T& v) {

//从顶点v出发对图G进行深度优先遍历的主过程

int i, loc, n = G.NumVertices(); //顶点个数

bool *visited = new bool[n]; //创建辅助数组

for (i = 0; i < n; i++) visited [i] = false;

//辅助数组visited初始化

loc = G.getVertexPos(v);

DFS (G, loc, visited); //从顶点v开始深度优先搜索

delete [] visited; //释放visited

};

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

579146-579

void DFS (Graph& G, int v, bool &visited[]) {

cout << G.getValue(v) << ' '; //访问顶点v

visited[v] = true; //作访问标记

int w = G.getFirstNeighbor (v); //第一个邻接顶点

while (w != -1) { //若邻接顶点w存在

if ( !visited[w] ) DFS(G, w, visited);

//若w未访问过, 递归访问顶点w

w = G.getNextNeighbor (v, w); //下一个邻接顶点

}

};

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

580580

讨论3:在图的邻接表中如何进行DFS?

v0 → v1 → v2 → v3

DFS 结果

0

0

0

0

0

1

2

3

辅助数组 visited [n ]

1

0

0

0

1

1

0

0

1

1

1

0

1

1

1

1

例:

—照样借用visited [n ]!

起点

0

1

2

3

注意:在邻接表中,并非每个

链表元素(表结点)都被扫描

到,遍历速度很快。

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

581581

二、广度优先搜索( BFS )

基本思想:——仿树的层次遍历过程。

Breadth_First Search

v1

v1

v2 v3

v8

v7v6v4 v5

BFS 结果例1:

→ →

→v2 v3

→v4 v5 →v6 v7→v8

例2:

v3 →

BFS 结果

v4 → v5 →

起点

遍历步骤

起点

v2 → v1 → v6 →

v9 → v8 → v7

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

582582

广度优先搜索(遍历)步骤:

简单归纳:• 在访问了起始点v之后,依次访问 v的邻接点;

• 然后再依次访问这些顶点中未被访问过的邻接点;

• 直到所有顶点都被访问过为止。

广度优先搜索是一种分层的搜索过程,每向前走

一步可能访问一批顶点,不像深度优先搜索那样有

回退的情况。因此,广度优先搜索不是一个递归的

过程,其算法也不是递归的。

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

583583

讨论1:计算机如何实现BFS?

邻接表

——除辅助数组visited [n ]外,

还需再开一辅助队列!例:

起点

辅助队列

v2已访问过了

BFS 遍历结果

入队!

初始f=0,r=0

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

584146-584

void BFS (Graph& G, const T& v) {

int i, w, n = G.NumVertices();

//图中顶点个数

bool *visited = new bool[n];

for (i = 0; i < n; i++) visited[i] = false;

int loc = G.getVertexPos (v); //取顶点号

cout << G.getValue (loc) << ' '; //访问顶点v

visited[loc] = true; //做已访问标记

图的广度优先搜索算法

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

585/123146-585

Queue Q; Q.EnQueue (loc);

//顶点进队列, 实现分层访问

while (!Q.IsEmpty() ) { //循环, 访问所有结点

Q.DeQueue (loc);

w = G.getFirstNeighbor (loc); //第一个邻接顶点

while (w != -1) { //若邻接顶点w存在

if (!visited[w]) { //若未访问过

cout << G.getValue (w) << ' '; //访问

visited[w] = true;

Q.EnQueue (w); //顶点w进队列

}

w = G.getNextNeighbor (loc, w);

} //找顶点loc的下一个邻接顶点

} //外层循环,判队列空否

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

586/123146-586

delete [] visited;

};

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

588146-588

8.3.3 连通分量 (Connected component)

• 当无向图为非连通图时,从图中某一顶点出发,

利用深度优先搜索算法或广度优先搜索算法不可

能遍历到图中的所有顶点,只能访问到该顶点所

在最大连通子图(连通分量)的所有顶点。

• 若从无向图每一连通分量中的一个顶点出发进行

遍历, 可求得无向图的所有连通分量。

• 例如,对于非连通的无向图,所有连通分量的生

成树组成了非连通图的生成森林。

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

589146-589

A

C D EB

F G O

I

H

J

NML

K

非连通无向图

A H K

C D E IB

F OG J

NML

非连通图的连通分量

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

590146-590

8.4 最小生成树( minimum cost spanning

tree )设图G是一个具有n个顶点的连通图。则从G的任

一顶点(源点)出发,作一次深度优先搜索(广度优

先搜索),搜索到的n个顶点和搜索过程中从一个已

访问过的顶点v i 搜索到一个未曾访问过的邻接点v j

,所经过的边(共n-1条)组成的极小连通子图就是

生成树。(源点是生成树的根)

A

C D EB

F G

A

C D EB

F G

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

591146-591

8.4 最小生成树( minimum cost spanning

tree ) 使用不同的遍历图的方法,可以得到不同的生

成树;从不同的顶点出发,也可能得到不同的

生成树。

按照生成树的定义,n 个顶点的连通网络的生成树有 n 个顶点、n-1条边。

假设有一个网络,用以表示 n 个城市之间架设

通信线路,边上的权值代表架设通信线路的成

本。如何架设才能使线路架设的成本达到最小?

构造最小生成树

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

592146-592

构造最小生成树的准则

必须使用且仅使用该网络中的 n-1 条边

来联结网络中的 n 个顶点;

不能使用产生回路的边;

各边上的权值的总和达到最小。

北京 天津

南京

上海

广州

西安

成都

昆明

武汉

347

6

41

58

31

24

19

25

3822

2219

31

39

4450

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

593146-593

北京 天津

南京

上海

广州

西安

成都

昆明

武汉

7

6

24

19

22

2219

31

北京 天津

南京

上海

广州

西安

成都

昆明

武汉

347

6

41

58

31

24

19

25

3822

2219

31

39

4450

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

594594

讨论:如何求得最小生成树?

——有多种算法,但最常用的是以下两种:

最小生成树的 MST 性质如下:

Kruskal(克鲁斯卡尔)算法

Prim(普里姆)算法

Kruskal算法特点:将边归并,适于求稀疏网的最小生成树。

Prime算法特点: 将顶点归并,适于稠密网。

这两个算法,都是利用MST 性质来构造最小生成树的。

若U集是V的一个非空子集,若(u0, v0)是一条最

小权值的边,其中u0 U,v0 V-U;则:(u0, v0)必在

最小生成树上。

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

595595

例:应用克鲁斯卡尔算法构造最小生成树的过程

√ √

×

×√

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

596596

步骤:

(1) 首先构造一个只有 n 个顶点但没有边的非连通图 T = { V,

}, 图中每个顶点自成一个连通分量。

(2) 当在边集 E 中选到一条具有最小权值的边时,若该边的

两个顶点落在T中不同的连通分量上,则将此边加入到

生成树的边集合T 中;否则将此边舍去,重新选择一条

权值最小的边。

(3) 如此重复下去,直到所有顶点在同一个连通分量上为止。

此时的T即为所求(最小生成树)。

克鲁斯卡尔(Kruskal)算法:

设N = { V, E }是有 n 个顶点的连通网,

Kruskal算法采用邻接表作为图的存储表示

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

597597

Kruskal(克鲁斯卡尔)算法

练习 :1

4

65

23

1

56

55

463

6

2

15

43 2

1

3

5

2 4

6

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

598598

(1)初始状态: U ={u0 },( u0 ∈V ), TE={ },

(2)从E中选择顶点分别属于U、V-U两个集合、且

权值最小的边( u0, v0),将顶点v0归并到集合U

中,边(u0, v0)归并到TE中;

(3)直到U=V为止。此时TE中必有n-1条边,

T=(V,{TE})就是最小生成树。

设:N =(V , E)是个连通网,

另设U为最小生成树的顶点集,TE为最小生成树的边集。

构造步骤:

普利姆(Prim)算法

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

599599

例:

1

4

65

23

1

56

55

4636

23

6

42

5

1

[注]:在最小生成树的生成过程中,所选的边都是

一端在V-U中,另一端在U中。

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

600600

一顶点到其余

各顶点

8.5 最短路径

两种常见的最短路径问题:

一、 单源最短路径—用Dijkstra(迪杰斯特拉)算法

二、所有顶点间的最短路径—用Floyd(弗洛伊德)算法

典型用途:交通问题。如:城市A到城市B有多条线路,但每

条线路的交通费(或所需时间)不同,那么,如何选择一条线

路,使总费用(或总时间)最少?

问题抽象:在带权有向图中A点(源点)到达B点(终点)的

多条路径中,寻找一条各边权值之和最小的路径,即最短路径。

任意两顶点

之间

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

601601

8.5.1 非负权值的单源最短路径 (Dijkstra算法)

目的: 设一有向图G=(V, E),已知各边的权值,以某指定

点v0为源点,求从v0到图的其余各点的最短路径。限定各边上

的权值大于或等于0。

例1:

源点

从F→A的路径有4条:

① F→A: 24

② F→B→A: 5+18=23

③ F→B→C→A:5+7+9=21

④ F→D→C→A:25+12+9=36

又:

从F→B的最短路径是哪条?

从F→C的最短路径是哪条?

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

602602

v0 (v0, v1) 10

源点 终点 最 短 路 径 路径长度

(v0, v1, v2) (v0, v3, v2)

(v0, v 3) 30

v1

v2

v3

v4 100(v0, v4) (v0, v3, v4)(v0, v3, v2, v4)

例2:

60

0

1

2

3

4

50

90 70

讨论:计算机如何自动求出这些最短路径?

(v0, v1, v2, v4) 60

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

603603

Dijkstra(迪杰斯特拉)算法

算法思想:①设置两个顶点的集合S和T,集合S中存放已找到最短路径的顶点,集合T中存放当前还未找到最短路径的顶点。初始状态

时,集合S中只包含源点,设为v0 。

②先找出从源点v0到各终点vk的直达路径(v0,vk),即通过一

条弧到达的路径。从这些路径中找出一条长度最短的路径

(v0,u),然后对其余各条路径进行适当调整:

若在图中存在弧(u,vk),且(v0,u)+(u,vk)<(v0,vk),

则以路径(v0,u,vk)代替(v0,vk)。

③在调整后的各条路径中,再找长度最短的路径,依此类推。

直到所有的顶点全部加入到T中为止。

总之,按路径“长度” 递增的次序来逐步产生最短路径。

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

604604

(v0,v2)+ (v2,v3)<(v0,v3)

点从v0到各终点的dist值和最短路径

v1

v2

v3

v4

v5

vj

S之外的当前最

短路径之顶点

60{v0,v2,v3}

50{v0,v4,v3}

30{v0,v4}

90{v0,v4, v5}

60{v0,v4,v3,v5}

5

5

40

3

1 2

100 60

30

10

10 20

50

s {v0,v2} {v0 ,v2 ,v4} {v0 ,v2 ,v4 ,v3} {v0 ,v2 ,v4 ,v3 ,v5}

10{v0,v2}

30{v0,v4}

100{v0, v5}

∞ ∞ ∞ ∞

例3:

060020100

50050

10030100

v2 v4 v3 v5

100{v0, v5}

0

1

2

3

4

5

dist[w]

0 1 2 3 4 5

与最小生成树的不同点:路径可能是累加的!

10{v0,v2}

50{v0,v4,v3}

30{v0,v4}

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

605605

引入辅助数组dist。它的每一个分量dist[i]表示

当前找到的从源点 v0 到终点 vi 的最短路径的长

度。初始状态:

若从v0到顶点vi有边, 则dist[i]为该边的权值;

若从v0到顶点vi无边, 则dist[i]为 。

假设S是已求得的最短路径的终点的集合,则可

证明:下一条最短路径必然是从v0 出发,中间

只经过S中的顶点便可到达的那些顶点vx

(vx V-S )的路径中的一条。

每次求得一条最短路径后, 其终点vk 加入集合S,

然后对所有的vi V-S,修改其 dist[i]值。

Dijkstra算法的思路:

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

606146-606

Dijkstra算法可描述如下:

① 初始化: S {v0};

dist[j] Edge[0][j], j = 1, 2, …, n-1;

// n为图中顶点个数

② 求出最短路径的长度:

dist[k] min {dist[i]}, i V-S ;

S S∪{k};

③ 修改:

dist[i] min{dist[i], dist[k]+Edge[k][i]},

对于每一个 i V-S ;

④ 判断:若 S = V, 则算法结束,否则转②。

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

607146-607

计算从单个顶点到其它各顶点最短路径的算法

void ShortestPath (Graph& G, T v, E dist[],int path[]) {

//Graph是一个带权有向图。dist[j], 0≤j<n, 是当前

//求到的从顶点v到顶点j的最短路径长度, path[j],

//0≤j<n, 存放求到的最短路径。

int n = G.NumVertices();

bool *S = new bool[n]; //最短路径顶点集

int i, j, k; E w, min;

for (i = 0; i < n; i++) {

dist[i] = G.getWeight(v, i);

S[i] = false;

if (i != v && dist[i] < maxWeight) path[i] = v;

else path[i] = -1;

}

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

608146-608

S[v] = true; dist[v] = 0; //顶点v加入顶点集合

for (i = 0; i < n-1; i++) { //求解各顶点最短路径

min = maxWeight; int u = v;

//选不在S中具有最短路径的顶点u

for (j = 0; j < n; j++)

if (!S[j] && dist[j] < min)

{ u = j; min = dist[j];}

S[u] = true; //将顶点u加入集合S

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

609146-609

for (k = 0; k < n; k++) { //修改

w = G.GetWeight(u, k);

if (!S[k] && w < maxWeight &&dist[u]+w < dist[k])

{ //顶点k未加入S

dist[k] = dist[u]+w;

path[k] = u; //修改到k的最短路径

}

}

}

};

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

610146-610

Dijkstra算法中各辅助数组的最终结果

从表中读取源点0到终点v的最短路径的方法 : 以顶点5为例path[5] = 3 path[3] = 4

path[4] = 0,反过来排列,得到路径 0, 4, 3, 5,这就是源点0

到终点5的最短路径。

序号 顶点1 顶点2 顶点3 顶点4

Dist ∞ 10 50 30

path -1 0 4 0

5

5

40

3

1 2

100 60

30

10

10 20

50

顶点5

60

3

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

611611

8.5.3 所有顶点之间的最短路径

问题的提出:已知一个各边权值均大于0的带权有

向图,对每一对顶点 vi vj,希望求出vi 与vj之

间的最短路径和最短路径长度。

解决思路:

可以通过调用n次Dijkstra算法来完成,具体方法

是:每次以不同的顶点作为源点,调用狄克斯特

拉算法求出从该源点到其余顶点的最短路径。重

复n次就可求出每对顶点之间的最短路径。由于

狄克斯特拉算法的时间复杂度为O(n2),所以这

种算法的时间复杂度为O(n3)。

改进:Floyd算法(略)

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

612612

8.6 用顶点表示活动的网络

① AOV网(Activity On Vertices)用顶点表示活动的网络

AOV网定义:若用有向图表示一个工程,在图中用顶点表

示活动,用弧表示活动间的优先关系。Vi 必须先于活动Vj

进行。则这样的有向图叫做用顶点表示活动的网络,简称

AOV。

② AOE网(Activity On Edges)用边表示活动的网络

AOE网定义:如果在无环的带权有向图中, 用有向边表

示一个工程中的活动,用边上权值表示活动持续时间,用

顶点表示事件,则这样的有向图叫做用边表示活动的网络,

简称 AOE。

有两种常用的活动网络( Activity Network):

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

613146-613

用顶点表示活动的网络 (AOV网络)

计划、施工过程、生产流程、程序流程等都是

“工程”。除了很小的工程外,一般都把工程分为

若干个叫做“活动”的子工程。完成了这些活动,

这个工程就可以完成了。

例如,计算机专业学生的学习就是一个工程,

每一门课程的学习就是整个工程的一些活动。

其中有些课程要求先修课程,有些则不要求。

这样在有的课程之间有领先关系,有的课程可

以并行地学习。

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

614146-614

C1 高等数学

C2 程序设计基础

C3 离散数学 C1, C2

C4 数据结构 C3, C2

C5 高级语言程序设计 C2

C6 编译方法 C5, C4

C7 操作系统 C4, C9

C8 普通物理 C1

C9 计算机原理 C8

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

615146-615

学生课程学习工程图

C8

C3

C5

C4

C9

C6

C7C1

C2

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

616146-616

AOV网络 (Activity On Vertices)用有向图表示

一个工程。在这种有向图中,用顶点表示活动,用

有向边<Vi, Vj>表示活动Vi 必须先于活动Vj 进行。

在AOV网络中不能出现有向回路, 即有向环。

如果出现了有向环,则意味着某项活动应以自己作

为先决条件。

因此,对给定的AOV网络,必须先判断它是否

存在有向环。

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

617146-617

检测有向环的一种方法是对AOV网络构造它

的拓扑有序序列。即将各个顶点 (代表各个活

动)排列成一个线性有序的序列,使得AOV网络中

所有应存在的前驱和后继关系都能得到满足。

1

2

3

4

拓扑排序

41

2

3

41 2 3

偏序关系 全序关系

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

618146-618

这种构造AOV网络全部顶点的拓扑有序序

列的运算就叫做拓扑排序。

如果通过拓扑排序能将AOV网络的所有顶

点都排入一个拓扑有序的序列中, 则该网络中必

定不会出现有向环。

如果AOV网络中存在有向环,此AOV网络

所代表的工程是不可行的。

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

619

拓扑排序的方法

1 2

34

56

0 2

2 1

0 3

1

拓扑序列:

3 2 6 4 5

算法虚拟实现中的几个问题:

1.图的存储结构——邻接表(同时记每个顶点的入度);

2.入度为0顶点的收集,可以采用堆栈(实际上无顺序)

1

01

0

2

10

0

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

620146-620

例如, 对学生选课工程图的拓扑有序序列为:

C1 , C2 , C3 , C4 , C5 , C6 , C8 , C9 , C7

或 C1 , C8 , C9 , C2 , C5 , C3 , C4 , C7 , C6

C8

C3

C5

C4

C9

C6

C7C1

C2

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

621146-621

进行拓扑排序的斱法

① 输入AOV网络。令 n 为顶点个数。

② 在AOV网络中选一个没有直接前驱的顶点,

并输出之;

③ 从图中删去该顶点, 同时删去所有它发出的有

向边;

④ 重复以上 ②、③步, 直到

全部顶点均已输出,拓扑有序序列形成,

拓扑排序完成;或

图中还有未输出的顶点, 但已跳出处理循

环。说明图中还剩下一些顶点, 它们都有

直接前驱。这时网络中必存在有向环。

拓扑排序:重复选择没有直接前驱的顶点。

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

622146-622

8.7用边表示活动的网络(AOE网络)

如果在无有向环的带权有向图中, 用有向边表

示一个工程中的活动 (Activity), 用边上权值表

示活动持续时间 (Duration), 用顶点表示事件

(Event), 则这样的有向图叫做用边表示活动的

网络, 简称 AOE ( Activity On Edges ) 网络。

AOE网络在某些工程估算方面非常有用。例如,

可以使人们了解:

完成整个工程至少需要多少时间(假设网络

中没有环)?

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

623146-623

为缩短完成工程所需的时间, 应当加快哪些活动?

源点与汇点:

从源点到各个顶点, 以至从源点到汇点的有向路

径可能不止一条。 这些路径的长度也可能不同。

完成不同路径的活动所需的时间虽然不同, 但只有各

条路径上所有活动都完成了, 整个工程才算完成。

关键路径:

完成整个工程所需的时间取决于从源点到汇点

的最长路径长度, 即在这条路径上所有活动的持续时

间之和。这条路径长度最长的路径就叫做关键路径

(Critical Path)。

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

624146-624

关键活动:要找出关键路径,必须找出关键活动, 即

不按期完成就会影响整个工程完成的活动。

关键路径上的所有活动都是关键活动。因此, 只要找

到了关键活动, 就可以找到关键路径。例如, 下图就

是一个AOE网。

1

2

3

4

5

6

7

8

9

a1=6

a2=4

a3=5

a4=1

a5=1

a6=2

a7=9

a8=7

a9=4

a10=2

a11=4

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

625146-625

几个与计算关键活动有关的量:

1. 事件Vi 的最早可能开始时间Ve(i)

是从源点V0 到顶点Vi 的最长路径长度。

2. 事件Vi 的最迟允许开始时间Vl[i]

是在保证汇点Vn-1 在Ve[n-1] 时刻完成的前提

下,事件Vi 的允许的最迟开始时间。

1

2

3

4

5

6

a1=3

a2=2

a3=2

a4=3

a5=4

a6=3

a7=2

a8=1

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

626146-626

几个与计算关键活动有关的量:

3. 活动ak 的最早可能开始时间 e[k]

设活动ak 在边<Vi, Vj>上, 则e[k]是从源点V0

到顶点Vi 的最长路径长度。因此,

e[k] = Ve[i]。

4. 活动ak 的最迟允许开始时间 l[k]

1

2

3

4

5

6

a1=3

a2=2

a3=2

a4=3

a5=4

a6=3

a7=2

a8=1

l[k]是在不会引起时间延误

的前提下, 该活动允许的最

迟开始时间。l[k] = Vl[j]-dur(<i, j>)。

其中, dur(<i, j>)是完成 ak

所需的时间。

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

627146-627

5. 时间余量 l[k]-e[k]

表示活动ak的最早可能开始时间和最迟允许

开始时间的时间余量。l[k] == e[k]表示活动 ak 是

没有时间余量的关键活动。

为找出关键活动, 需要求各个活动的 e[k] 与

l[k],以判别是否 l[k] == e[k]。

1

2

3

4

5

6

a1=3

a2=2

a3=2

a4=3

a5=4

a6=3

a7=2

a8=1

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

628

例 1:

1

2

3

4

5

6

7

8

9

a1=6

a2=4

a3=5

a4=1

a5=1

a6=2

a7=9

a8=7

a9=4

a10=2

a11=4Vi

1

2

3

4

5

6

7

8

9

VE064577161418

VL

181416

1078660

a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11

AE 0 0 0 6 4 5 7 7 7 16 14AL 0 2 3 6 6 8 7 7 10 16 14

关键路径:a1-a4-a7-a10-a11, a1-a4-a8-a10-a11

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

629

例2:

1

2

3

4

5

6

a1=3

a2=2

a3=2

a4=3

a5=4

a6=3

a7=2

a8=1

Vi

1

2

3

4

5

6

VE

0

3

2

6

6

8

VL

0

4

2

6

7

8

ek

a1

a2

a3

a4

a5

a6

a7

a8

AE

0

0

3

3

2

2

6

6

AL

1

0

4

4

2

5

6

7

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

630146-630

①先求得从源点V0到各个顶点Vi 的Ve[i]和Vl[i]

求Ve[i]的递推公式

从 Ve[0] = 0 开始,向前递推

< Vi, Vj > S2, j = 1, 2, , n-1

S2 是所有指向Vj 的有向边<Vi,Vj>的集合。

}, ) ,(][ {][ jii

VVduriVemaxjVe

Ve = 6

Ve = 12

Ve = 9

5

2

4

Ve = 11? 14? 13?

已知 求解

计算关键路径的算法分析:

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

631146-631

从Vl[n-1] = Ve[n-1]开始,反向递推

< Vj, Vk > S1, j = n-2, n-3, , 0

S1是所有源自Vj 的有向边< Vj , Vk>的集合。

这两个递推公式的计算必须分别在拓扑有序及

逆拓扑有序的前提下进行。

}, ) ,(][ {][ kjk

VVdurkVlminjVl

67

5

Vl = 19

Vl = 24

Vl = 11

Vl = 13? 17? 6?

已知求解

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

632146-632

②求关键路径上的活动

设活动ak (k=1, 2, …, e)在带权有向边< Vi, Vj >上,

其持续时间用dur (<Vi,Vj>)表示, 则有

e[k] = Ve[i];

l[k] = Vl[j]-dur(<Vi,Vj>);k = 1, 2, …, e。

这样就得到计算关键路径的算法。

为了简化算法, 假定在求关键路径之前已经对

各顶点实现了拓扑排序, 并按拓扑有序的顺序对各

顶点重新进行了编号。

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

633

关键路径的求法:

1.按拓扑顺序求各事件的最早发生时间 VE(Vj)

2.求出各个事件的最晚发生时间 VL(Vi)

7.结论(缩短工期的方法)

3.求出各个活动的最早开始时间 AE(ek)

4.求出各个活动的最晚开始时间 AL(ek)

5.求出关键活动

6.求出关键路径、工期

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

634

结论:

1.对于一个工程,可以利用AOV网络分析工程在分解

时是否合理(各个子工程间有否冲突);得到工程施工

的 调度顺序。

2.对于一个工程,在AOV的基础上,可以利用AO

E网络分析工程的关键子工程(抓主要矛盾),计算工

程的工期。

3.在不改变关键路径的前提下,提高关键活动的功效,

可以缩短工期!

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

635635

存储结构

遍 历

邻接矩阵

邻 接 表

十字链表

邻接多重表

深度优先搜索DFS

广度优先搜索DFS

无向图的应用

应用

图的连通分量

图的生成树 最小生成树Prim算法

Kruskal算法

有向(无环)图的应用

最短路径 Dijkstra算法

Floyd算法

(利用DFS)

本章小结

(利用DFS和BFS)

关键路径

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

636636

练习题

1. 在一个图中,所有顶点的度数之和等于图的边数的 倍。

2. 在一个有向图中,所有顶点的入度之和等于所有顶点的出度之

和的 倍。

3. 有8个结点的无向图最多有 条边。

4. 有8个结点的无向连通图最少有 条边。

5. 有8个结点的有向完全图有 条边。

6. 用邻接表表示图进行广度优先遍历时,通常是采用 来辅

助实现算法的。

7. 用邻接表表示图进行深度优先遍历时,通常是采用 来辅助

实现算法的。

2

1

28

7

56

队列

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

637637

8. 已知图的邻接矩阵,根据算法思想,则从顶点0

出发按深度优先遍历的结点序列

是 。

9. 已知图的邻接矩阵同上题8,根据算法,则从顶

点0出发,按广度优先遍历的结点序列是 。

10. 已知图的邻接表如下所示,根据算法,则从顶

点0出发按深度优先遍历的结点序列是 。

11. 已知图的邻接表如下所示,根据算法,则从顶

点0出发按广度优先遍历的结点序列

是 。

0134256

0123465

0 1 2 3

0 1 2 3

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

638638

书面作业:

1.已知如图所示的有向

图,请给出该图的:

①每个顶点的入/出度;

②邻接矩阵;

③邻接表;

④逆邻接表。

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

639639

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

640640

2.请对下图的无向带权图:

写出它的邻接矩阵,

并按普里姆算法求其最小生成树;

写出它的邻接表,

并按克鲁斯卡尔算法求

其最小生成树。

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

641641

3.已知二维数组表示的

图的邻接矩阵如下图

所示。试分别画出自

顶点1出发进行遍历

所得的深度优先生成

树和广度优先生成树。

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

642642

4.给定下列网G

1 试着找出网G的最小生成树,画出

其逻辑结构图;

2 用两种不同的表

示法画出网G的存储结构图;

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

643643

01012

069

6084

10015

1215020

9820012

4120

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

644

第九章 排序

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

645

概述

插入排序(直接、折半、希尔(重点))

快速排序 (重点)

交换排序(气泡)

选择排序(直接、堆(重点))

归并排序

分配排序(基数)

第九章 排序

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

646

概述

排序:将一组杂乱无章的数据按一定的规律顺次

排列起来。

数据表(datalist): 是待排序数据元素的有限集合。

排序码(key): 通常数据元素有多个属性域, 即多个

数据成员组成, 其中有一个属性域可用来区分元素,

作为排序依据。该域即为排序码。每个数据表用哪

个属性域作为排序码,要视具体的应用需要而定。

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

647

排序算法的稳定性: 如果在元素序列中有两

个元素r[i]和r[j], 它们的排序码 k[i] == k[j] , 且在

排序之前, 元素r[i]排在r[j]前面。如果在排序之

后, 元素r[i]仍在元素r[j]的前面, 则称这个排序方

法是稳定的, 否则称这个排序方法是不稳定的。

内排序与外排序: 内排序是指在排序期间

数据元素全部存放在内存的排序;外排序是指在

排序期间全部元素个数太多,不能同时存放在内

存,必须根据排序过程的要求,不断在内、外存

之间移动的排序。

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

648

排序的时间开销: 排序的时间开销是衡量算

法好坏的最重要的标志。排序的时间开销可用算

法执行中的数据比较次数与数据移动次数来衡量。

算法运行时间代价的大略估算一般都按平均

情况进行估算。对于那些受元素排序码序列初始

排列及元素个数影响较大的,需要按最好情况和

最坏情况进行估算。

算法执行时所需的附加存储: 评价算法好坏

的另一标准。

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

649

9.2 插入排序 (Insert Sorting)基本方法是:每步将一个待排序的元素,按其排

序码大小,插入到前面已经排好序的一组元素的适当

位置上, 直到元素全部插入为止。

基本思想是 : 当插入第i (i≥1) 个元素时,前面的

V[0], V[1], …, V[i-1]已经排好序。这时,用V[i]的排

序码与V[i-1], V[i-2], …的排序码顺序进行比较,找

到插入位置即将V[i]插入,原来位置上的元素向后顺

移。

9.2.1 直接插入排序 (Insert Sort)

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

650

各趟排序结果

21 25 49 25* 16 08

0 1 2 3 4 5

0 1 2 3 4 5 temp

21 25 49 25* 16 0825i = 1

0 1 2 3 4 5 temp

21 25 49 25* 16 0849

i = 2

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

651

0 1 2 3 4 5

i = 4

i = 5

i = 3

0 1 2 3 4 5 temp

21 25 4925* 16 08 16

0 1 2 3 4 5 temp

21 25 49 25* 16 0825*

0 1 2 3 4 5 temp

21 25 4925*16 08 08

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

652

21 25 4925*1608

0 1 2 3 4 5

i = 4 时的排序过程

完成

16

16

0 1 2 3 4 5 temp

21 25 4925*08 16

4916

0 1 2 3 4 5 temp

21 25 4925*08 16

i = 4j = 2

i = 4j = 3

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

653

2516

0 1 2 3 4 5 temp

214925*

08 1625

25*16

21 25 4925*08

0 1 2 3 4 5

16

i = 4j = 1

i = 4j = 0

i = 4j = -1

0 1 2 3 4 5 temp

21 25 4925*08 1621

16

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

654

算法分析

设待排序元素个数为currentSize = n, 则该算法的

主程序执行n-1趟(第一个元素不用插入)。

排序码比较次数和元素移动次数与元素排序码的

初始排列有关。

最好情况下,排序前元素已按排序码从小到大有

序,每趟只需与前面有序元素序列的最后一个元素比

较1次,总的排序码比较次数为 n-1, 元素移动次数为0。

21 25 4925*1608

0 1 2 3 4 5

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

655

最坏情况下, 第 i 趟时第 i 个元素必须与前

面 i 个元素都做排序码比较, 并且每做1次比较就

要做1次数据移动。则总排序码比较次数KCN和

元素移动次数RMN分别为:

1

1

1

1

22142

221

n

i

n

i

nnniRMN

nnniKCN

//))(()(

,//)(

2

2

212549

28 16 08

0 1 2 3 4 5

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

656

平均情况下排序的时间复杂度为 o(n2)。

直接插入排序是一种稳定的排序方法。

基本思想是 : 设在顺序表中有一 个元素序列

V[0], V[1], …, V[n-1]。其中, V[0], V[1], …, V[i-1]

是已经排好序的元素。在插入V[i] 时, 利用折半搜

索法寻找V[i] 的插入位置。

折半插入排序的算法

#include "dataList.h"

折半插入排序 (Binary Insertsort)

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

659

算法分析

折半搜索比顺序搜索快, 所以折半插入排序就

平均性能来说比直接插入排序要快。

它所需的排序码比较次数与待排序元素序列的初

始排列无关,仅依赖于元素个数。在插入第 i 个元素

时,需要经过 log2i +1 次排序码比较, 才能确定它应

插入的位置。因此,将 n 个元素(为推导方便, 设为

n=2k ) 用折半插入排序所进行的排序码比较次数为:

折半插入排序是一个稳定的排序方法。

1

1

22 log1logn

i

nni

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

660

当 n 较大时,总排序码比较次数比直接插入

排序的最坏情况要好得多,但比其最好情况要差。

在元素的初始排列已经按排序码排好序或接

近有序时,直接插入排序比折半插入排序执行的

排序码比较次数要少。折半插入排序的元素移动

次数与直接插入排序相同,依赖于元素的初始排

列。

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

661

希尔排序方法又称为缩小增量排序。该方

法的基本思想是 :

①设待排序元素序列有 n 个元素, 首先取一个整

数 gap < n 作为间隔,将全部元素分为 gap 个

子序列,所有距离为 gap 的元素放在同一个子

序列中,在每一个子序列中分别施行直接插入

排序。

9.2.3 希尔排序 (Shell Sort)(重点)

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

662

②然后缩小间隔 gap, 例如取 gap = gap/2 ,

重复上述的子序列划分和排序工作。直到最

后取 gap == 1,将所有元素放在同一个序列中

排序为止。

开始时 gap 的值较大,子序列中的元素

较少,排序速度较快; 随着排序进展,gap 值

逐渐变小, 子序列中元素个数逐渐变多,由于

前面工作的基础,大多数元素已基本有序,

所以排序速度仍然很快。

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

663

21 25 49 25* 16 08

0 1 2 3 4 5

21 25*

i = 1

0849

Gap = 3

2516

4925 16 08

49 25*08

21

2521 25* 16

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

664

21 25 4925*16 08

0 1 2 3 4 5

21

i = 2

08

49

Gap = 2

25

16

4916 25*

0821 25 4925*

08 16 21 25* 25

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

665

21 25 4925*1608

0 1 2 3 4 5

21

i = 3

08

Gap = 1

25164925*

#include "dataList.h"

template <class T>

希尔排序的算法

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

666

算法分析:

希尔排序是一种不稳定的排序方法。

Gap的取法有多种。最初 shell 提出取 gap = n/2 ,gap = gap/2 ,直到gap = 1。knuth 提出取 gap = gap/3 +1。还有

人提出都取奇数为好,也有人提出各 gap 互质为好。

对特定的待排序元素序列,可以准确地估算排序码的比较次数和元素移动次数。

想要弄清排序码比较次数和元素移动次数与增量选择之

间的依赖关系,并给出完整的数学分析,还没有人能够做到。

Knuth利用大量实验统计资料得出 : 当 n 很大时,排序码

平均比较次数和元素平均移动次数大约在 n1.25 到 1.6n1.25 的范

围内。这是在利用直接插入排序作为子序列排序方法的情况下

得到的。

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

667

交换排序 ( Exchange Sort )基本思想是两两比较待排序元素的排序码,如果

发生逆序(即排列顺序与排序后的次序正好相反),则

交换之。直到所有元素都排好序为止。

基本方法是:设待排序元素序列中的元素个数为

n。最多作 n-1 趟,i = 1, 2, , n-1。在第 i 趟中从后

向前,j = n-1, n-2, , i,顺次两两比较V[j-1].key和

V[j].key。如果发生逆序,则交换V[j-1]和V[j]。

起泡排序 (Bubble Sort)

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

668

21 25 49 25* 16 08

0 1 2 3 4 5

21 25*i = 1 25 1649

Exchang=108

25160825*49

21Exchang=1

i = 2

49i = 308 16

Exchang=1

25*2521

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

669

25*

0 1 2 3 4 5

i = 4 4916

Exchang=008

2521

起泡排序

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

670

基本思想:

①任取待排序元素序列中的某个元素作为基准,按照该元素

的排序码大小,将整个元素序列划分为左右两个子序列:

左侧子序列中所有元素的排序码都小于或等于基准元素

的排序码

右侧子序列中所有元素的排序码都大于基准元素的排序

②基准元素则排在这两个子序列中间(这也是该元素最终应安

放的位置)。

③然后分别对这两个子序列重复施行上述方法,直到所有的

元素都排在相应位置上为止。

9.3 快速排序 (Quick Sort)(重点)

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

671

QuickSort ( List ) {

if ( List的长度大于1) {

将序列List划分为两个子序列

LeftList 和 Right List;

QuickSort ( LeftList );

QuickSort ( RightList );

将两个子序列 LeftList 和 RightList

合并为一个序列List;

}

}

算法描述:

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

672

21 25 49 25* 16 08

0 1 2 3 4 5

pivot

49i = 308 16 2525*21

21 25*i = 1 251649

08

pivotpivot

25*160825 49

21i = 2

pivot

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

673

21 25 49 25* 16 08

0 1 2 3 4 5

25*

i = 1

划分

2516

2516

08

49

pivotpos

08

25* 49

08 16 25* 2521

pivotpos

21

比较4次

交换25,16

i

ipivotpos

21

比较1次

交换49,08

49

low pivotpos

交换21,08

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

快速排序算法各次快速排序过程

例:初始关键字序列:

(1)

(2)

{60 55 48 37 10 90 84 36

{36 55 48 37 10} 60 { 90}

{10} 36 { 37 55} 60 84 { }

(3) {10} 36 { } 48 { } 60 84 90

最后结果 10 36 37 48 55 60 84 90

{37}

48

55

84

90

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

675

快速排序的算法#include "dataList.h―

void QuickSort (dataList & L, const int left, const int right) {

//对元素Vector[left], ..., Vector[right]进行排序,

//pivot=L.Vector[left]是基准元素, 排序结束后它的位置在

//pivotPos, 把参加排序的序列分成两部分,左边元素的排序

//码都小于或等于它, 右边都大于它

if (left < right) { //元素序列长度大于1时

int pivotpos = L.Partition (left, right); //划分

QuickSort (L, left, pivotpos-1);

QuickSort (L, pivotpos+1, right);

}

};

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

678

算法quicksort是一个递归的算法, 其递归树如

图所示。

算法partition利用序列第一个元素作为基准,

将整个序列划分为左右两个子序列。算法中执

行了一个循环,只要是排序码小于基准元素排

序码的元素都移到序列左侧,最后基准元素安

21

25*

25

49

08

16

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

679

置到位, 函数返回其位置。

从快速排序算法的递归树可知,快速排序的趟

数取决于递归树的高度。

如果每次划分对一个元素定位后,该元素的左

侧子序列与右侧子序列的长度相同,则下一步

将是对两个长度减半的子序列进行排序,这是

最理想的情况。

在 n个元素的序列中,对一个元素定位所需时间

为 O(n)。若设 T (n) 是对 n 个元素的序列进行

排序所需的时间,且每次对一个元素正确定位

后,正好把序列分为长度相等的两个子序列,

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

680

此时, 总的计算时间为:

T(n) cn + 2T(n/2 ) // c 是一个常数

cn + 2 ( cn/2 + 2T(n/4) ) = 2cn + 4T(n/4)

2cn + 4 ( cn/4 +2T(n/8) ) = 3cn + 8T(n/8)

………

cn log2n + nT(1) = O(n log2n )

可以证明,函数quicksort的平均计算时间也是

O(nlog2n)。实验结果表明: 就平均计算时间而

言,快速排序是内排序方法中最好的一个。

快速排序是递归的,需要有一个栈存放每层递

归调用时的指针和参数。

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

681

最大递归调用层次数与递归树高度一致,理

想情况为 log2(n+1) 。存储开销为 O(log2n)。

在最坏的情况,即待排序元素序列已经按其

排序码从小到大排好序的情况下,其递归树

成为单支树,每次划分只得到一个比上一次

少一个元素的子序列。必须经过n-1 趟才能

把所有元素定位,而且第 i 趟需要经过 n-i 次

排序码比较才能找到第 i 个元素的安放位置,

总的排序码比较次数将达到

21

2

121

1

nnnin

n

i

)()(

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

682

用第一个元素作为基准元素

快速排序退化的例子

08 16 21 25 25* 49 08

0 1 2 3 4 5 pivot

初始

16 21 25 25* 4908 16

21 25 25* 49 2108 16

2525 25* 4908 16 21

25* 49 25*08 16 21 25

4908 16 21 25 25*

i = 1

i = 2

i = 3

i = 4

i = 5

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

683

用居中排序码元素作为基准元素

08 16 21 25 25* 49

0 1 2 3 4 5 pivot

21初始

08 16 21 25 25* 49 08 25*

08 16 21 25 25* 49

i = 1

i = 2

其排序速度退化到简单排序的水平, 比直接插

入排序还慢。占用附加存储(栈)将达到O(n)。

改进办法: 取每个待排序元素序列的第一个元

素、最后一个元素和位置接近正中的 3 个元素,取

其排序码居中者作为基准元素。

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

684

快速排序是一种不稳定的排序方法。

对于 n 较大的平均情况而言, 快速排序是“快

速”的, 但是当 n 很小时, 这种排序方法往往比其

它简单排序方法还要慢。

因此,当n很小时可以用直接插入排序方法。

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

685

9.4 选择排序

基本思想是:

每一趟 (例如第 i 趟, i = 0, 1, …, n-2) 在后面

n-i 个待排序元素中选出排序码最小的元素,作

为有序元素序列的第 i 个元素。待到第 n-2 趟作

完,待排序元素只剩下1个, 就不用再选了。

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

686

直接选择排序 (Select Sort)直接选择排序的基本步骤是:

① 在一组元素 V[i]~V[n-1] 中选择具有最小排序

码的元素;

② 若它不是这组元素中的第一个元素, 则将它与这

组元素中的第一个元素对调;

③ 在这组元素中剔除这个具有最小排序码的元素。

在剩下的元素V[i+1] ~ V[n-1]中重复执行第①、

②步, 直到剩余元素只有一个为止。

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

687

21 25 49 25* 16 08

0 1 2 3 4 5

21 25*i = 0

49

25 16

25 16

08

4908

25*4921

i = 1

i = 208 16 25* 25 21

初始

最小者 08交换21,08

最小者 16交换25,16

最小者 21交换49,21

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

688

4925*

0 1 2 3 4 5

25*i = 4

251608

49

25* 4921

结果

i = 308 16 2521

最小者 25*无交换

最小者 25无交换

25211608

各趟排序后的结果

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

689

直接选择排序的算法#include "dataList.h"

void SelectSort (dataList & L, const int left, const int right) {

for (int i = left; i < right; i++) {

int k = i; //在L[i]到L[n-1]找最小排序码元素

for (int j = i+1; j <= right; j++)

if (L[j] < L[k]) k = j;

if (k != i) Swap (L[i], L[k]); //交换

}

};

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

690

i =1时选择排序的过程

0 1 2 3 4 5

160825*49

2125

i k j 49 25

492508

25* 16 21

i k j 25* 25

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

691

4908

25* 211625

i k j 16 < 25

492508

25* 16 21

0 1 2 3 4 5

i k j 21 16

k 指示当前序列中最小者

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

692

2

0 2

11

n

i

nninKCN

)()(

直接选择排序的排序码比较次数 KCN 与元

素的初始排列无关。设整个待排序元素序列有 n

个元素,则第 i (从0开始)趟选择具有最小排

序码元素所需的比较次数总是 n-i-1 次。总的排

序码比较次数为:

元素移动次数与元素序列初始排列有关。当

这组元素初始状态是按其排序码从小到大有序的

时候, 元素的移动次数达到最少RMN = 0。

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

693

最坏情况是每一趟都要进行交换,总的元素移

动次数为 RMN = 3(n-1)。

直接选择排序是一种不稳定的排序方法。

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

694

9.4.3 堆排序 (Heap Sort)

利用堆及其运算, 可以很容易地实现选择排序的思

路。

堆排序分为两个步骤:

①根据初始输入数据,利用堆的调整算法

siftDown( ) 形成初始堆;

②通过一系列的元素交换和重新调整堆进行排序。

为了实现元素按排序码从小到大排序,要求建立最

大堆。

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

698

49

25

25*

21

16 08

0

1 2

3 4 5

08

25

25* 16

21

49

0

2

543

1

49 25 21 25* 16 08 08 25 21 25* 16 49

交换 0 号与 5 号元素,

5 号元素就位

初始最大堆

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

699

25

25*

08

21

16 49

0

1 2

3 4 5

16

25*

08 25

21

49

0

2

543

1

25 25* 21 08 16 49 16 25* 21 08 25 49

交换 0 号与 4 号元素,

4 号元素就位

从 0 号到 4 号 重新

调整为最大堆

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

700

25*

16

08

21

25 49

0

1 2

3 4 5

08

16

25* 25

21

49

0

2

543

1

25* 16 21 08 25 49 08 16 21 25* 25 49

交换 0 号与 3 号元素,

3 号元素就位

从 0 号到 3 号 重新

调整为最大堆

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

701

21

16

25*

08

25 49

0

1 2

3 4 5

08

16

25* 25

21

49

0

2

543

1

21 16 08 25* 25 49 08 16 21 25* 25 49

交换 0 号与 2 号元素,

2 号元素就位

从 0 号到 2 号 重新

调整为最大堆

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

702

16

08

25*

21

25 49

0

1 2

3 4 5

08

16

25* 25

21

49

0

2

543

1

16 08 21 25* 25 49 08 16 21 25* 25 49

交换 0 号与 1 号元素,

1 号元素就位

从 0 号到 1 号 重新

调整为最大堆

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

703

算法 分析 :

第一个循环是建立 初始堆的过程,调用了n/2(下取整)次siftDown()算法,其时间复杂度为

O(nlog2n).

第二个 for 循环中调用了n-1次siftDown()算法,

该循环的计算时间为O(nlog2n)。因此, 堆排序的时间

复杂性为O(nlog2n)。

该算法的附加存储主要是在第二个 for 循环中用

来执行元素交换时所用的一个临时元素。因此,该算

法的空间复杂性为O(1)。

堆排序是一个不稳定的排序方法。

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

704

9.5 归并排序 (Merge Sort)

归并,是将两个或两个以上的有序表合并成

一个新的有序表。

元素序列L1中有两个有序表Vector[left..mid]

和Vector[mid+1..right]。它们可归并成一个有序表,

存于另一元素序列L2的Vector[left..right] 中。

这种方法称为两路归并 (2-way merging)。

变量 i 和 j 分别是表Vector[left..mid]和Vector

[mid+1..right]的检测指针。k 是存放指针。

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

705

08 21 25 25* 49 62 72 93 16 37 54

left mid mid+1 right

initList

i j

08 16 21 25 25* 37 49 54 62 72 93

left right

k

mergeList

当 i 和 j 都在两个表的表长内变化时, 根据对应

项的排序码的大小, 依次把排序码小的元素排放到

新表 k 所指位置中;

当 i 与 j 中有一个已经超出表长时,将另一 个

表中的剩余部分照抄到新表中。

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

706

迭代的归并排序算法

迭代的归并排序算法就是利用两路归并过程

进行排序。其基本思想是:

设初始元素序列有 n 个元素,首先把它看成

是 n 个长度为 1 的有序子序列(归并项),做两

两归并,得到 n/2 个长度为 2 的归并项(最后一

个归并项的长度为1);

再做两两归并,得到 n/4 个长度为 4 的归并

项(最后一个归并项长度可以短些)…,如此重

复,最后得到一个长度为 n 的有序序列。

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

707

迭代的归并排序算法

21 25

25*

25* 93 62 72 08 37 16 5449

21 25 49 62 93 08 72 16 37 54

21 25 25* 49 08 62 72 93 16 37 54

08

08

21

16

25

21

25*

25

49

25*

62

37

72

49

93

54

16 37 54

62 72 93

len=1

len=2

len=4

len=8

len=16

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

练习:对序列{39,80,76,41,13,29,50,78,

30,11,100,7}进行二路归并,试给出前四趟归并

的结果。

初始序列:39,80,76,41,13, 29,50,78,30,11,100, 7

第一趟:[39,80],[41,76],[13,29],[50,78],[11,30],[7,100]

第二趟:[39,41,76,80], [13,29,50,78], [7,11,30,100]

第三趟:[13,29,39,41,50,76,78,80], [7,11,30,100]

第四趟:[7, 11, 13, 29, 30, 39, 41, 50, 76, 78, 80, 100]

709

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

715

在迭代的归并排序算法中:

算法总的时间复杂度为O(nlog2n)。

归并排序占用附加存储较多, 需要另外一个

与原待排序元素数组同样大小的辅助数组。这

是这个算法的缺点。

归并排序是一个稳定的排序方法。

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

716

分配排序是采用“分配”与“收集”的办法,

用对多排序码进行排序的思想实现对单排序码

进行排序的方法。

以扑克牌排序为例。每张扑克牌有两个

“排序码”:花色和面值。其有序关系为:

花色:

面值:2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J

< Q < K < A

9.7 分配排序 (Radix Sort)

多排序码排序

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

717

如果我们把所有扑克牌排成以下次序:

2, …, A, 2, …, A, 2, …, A, 2, …,

A

这就是多排序码排序。排序后形成的有序序列叫

做词典有序序列。

对于上例两排序码的排序,可以先按花色排序,

之后再按面值排序;也可以先按面值排序,再按花色

排序。

一般情况下,假定有n 个元素组成的一序列 :

{V0, V1, …, Vn-1 },且每个元素Vi 中含有 d 个排序码

d

iii KKK , , , 21

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

718

如果对于序列中任意两个元素Vi 和Vj (0≤i < j

n-1 ) 都满足:

则称序列对排序码 (K1, K2, …, Kd) 有序。其中,

K1 称为最高位排序码,Kd 称为最低位排序码。

如果排序码是由多个数据项组成的数据项组,

则依据它进行排序时就需要利用多排序码排序。

实现多排序码排序有两种常用的方法:

d

jjj

d

iii KKKKKK , , , , , , 2121

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

719

最高位优先MSD(Most Significant Digit first )

最低位优先LSD(Least Significant Digit first)

最高位优先法通常是一个递归的过程:

先根据最高位排序码 K1排序, 得到若干元素

组, 元素组中各元素都有相同排序码K1。

再分别对每组中元素根据排序码 K2 进行排序,

按 K2 值的不同, 再分成若干个更小的子组, 每

个子组中的元素具有相同的 K1和 K2值。

依此重复, 直到对排序码Kd完成排序为止。

最后, 把所有子组中的元素依次连接起来,

就得到一个有序的元素序列。

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

720

最低位优先法首先依据最低位排序码Kd对所有元素进

行一趟排序,再依据次低位排序码Kd-1对上一趟排

序的结果再排序,依次重复,直到依据排序码K1

最后一趟排序完成,就可以得到一个有序的序列。

使用这种排序方法对每一个排序码进行排序时,

不需要再分组,而是整个元素组都参加排序。

LSD和MSD方法也可应用于对一个排序码进行

的排序。此时可将单排序码 Ki 看作是一个子排

序码组:

d

iii KKK , , , 21

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

721

链式基数排序

基数排序是典型的LSD排序方法, 利用“分配”

和“收集”对单排序码进行排序。在这种方法中,

把单排序码 Ki 看成是一个d元组:

其中的每一个分量 (1≤j≤d) 也可看成是一个

排序码。

分量 有radix种取值, 称radix为基数。例如,

排序码984可以看成是一个3元组(9, 8, 4), 每一

位有 0, 1, …, 9 等10种取值,基数radix = 10。

d

iii KKK , , , 21

j

iK

j

iK

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

722

一趟“分配”、“收集” 完成后, 所有元素就按其

排序码的值从小到大排好序了。

各队列采用链式队列结构, 分配到同一队列的

排序码用链接指针链接起来。每一队列设置

两 个队列指针: int front [radix]指示队头,

int rear [radix] 指向队尾。

为了有效地存储和重排 n 个待排序元素,以

静态链表作为它们的存储结构。

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

723

基数排序的“分配”与“收集”过程 第一趟

614 921 485 637738 101 215 530 790 306

第一趟分配(按最低位 i = 3 )

re[0] re[1] re[2] re[3] re[4] re[5] re[6] re[7] re[8] re[9]

614 738921 485 637

101 215

530

790

306

fr[0] fr[1] fr[2] fr[3] fr[4] fr[5] fr[6] fr[7] fr[8] fr[9]

第一趟收集

530 790 921 101 614 485 215 306 637 738

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

724

基数排序的“分配”与“收集”过程 第二趟

614921 485 637 738101 215530 790 306

第二趟分配(按次低位 i = 2 )

re[0] re[1] re[2] re[3] re[4] re[5] re[6] re[7] re[8] re[9]

614

738

921 485

637

101

215

530 790

306

fr[0] fr[1] fr[2] fr[3] fr[4] fr[5] fr[6] fr[7] fr[8] fr[9]

第二趟收集

530 790921101 614 485215306 637 738

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

725

基数排序的“分配”与“收集”过程 第三趟

614 921 485637 738101 215 530 790306

第三趟分配(按最高位 i = 1 )

re[0] re[1] re[2] re[3] re[4] re[5] re[6] re[7] re[8] re[9]

614 738 921485

637

101 215 530

790

306

fr[0] fr[1] fr[2] fr[3] fr[4] fr[5] fr[6] fr[7] fr[8] fr[9]

第三趟收集

530 790 921101 614485215 306 637 738

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

练习:将数据序列3,7,43,15,17,100,20,29,190,790,

348,5252,3999进行基数排序。

第一趟排序結果:100,20,190,790,5252,3,43,15,7,17,348,29,3999

第二趟排序結果:

100,3,7,15,17,20,29,43,348,5252,190,790,3999

第三趟排序結果:

3,7,15,17,20,29, 43,100,190,5252,348,790,3999

第四趟排序結果:

3,7,15,17,20,29, 43,100,190,348,790,3999,5252726

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

730

若每个排序码有d位, 需要重复执行d趟“分配”

与“收集”。每趟对n个元素进行“分配”,对

radix个队列进行“收集”。总时间复杂度为

O(d(n+radix))

若基数radix相同, 对于元素个数较多而排序码

位数较少的情况, 使用链式基数排序较好。

基数排序需要增加n+2radix个附加链接指针。

基数排序是稳定的排序方法。

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

731

各种排序斱法的比较

比较次数 移动次数 稳定 附加存储 排 序 方 法

最好 最差 最好 最差 性 最好 最差

直接插入排序 n n2 0 n2 1

折半插入排序 n log2n 0 n2 1

起泡排序 n n2 0 n2 1

快速排序 nlog2n n2 log2n n log2n n

简单选择排序 n2 n2 0 n 1

堆排序 n log2n n log2n 1

归并排序 n log2n n log2n n

基数排序 d(n+radix) d(n+radix) n+2radix