2021年数据结构C++版第3章.pptx
合集下载
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
// 向前声明
class ListNode {
friend class List; // 使List的成员能够访问ListNode的私有成员
private:
int data;
ListNode *link;
};
JYP
8
class List { public:
// 表处理操作 … private: ListNode *first; };
(2)List<Type>的操作太多,导致类的定义很不紧 凑,难以理解,影响软件质量。
(3)即使可由用户将新的成员函数加入类中,用户 还需要掌握链表结构,而掌握复杂的结构将增加用户 的负担。
JYP
20
这启发我们将遍历(traversal)移到List<Type>定 义之外。
进一步观察,以上操作都不改动链表内容,但需 要访问List<Type>和ListNode<Type>的私有数据成员。 为 此 , 我 们 不 是 将 这 些 操 作 都 声 明 为 List<Type> 和 ListNode<Type> 的 友 元 , 而 是 定 义 第 三 个 类 ListIterator<Type>。
List( ) { first = 0;}; // 构造函数,将first初始化为0 // 表处理操作 … private: ListNode<Type> *first; };
JYP
17
注意,List<Type>是ListNode<Type>的友元,这 意味着ListNode<int>的成员可以被List<int>的成员 访问,但不能被List<float>的成员访问。
JYP
28
3.2.3 链表操作
1 将一个元素加到链表尾部
为了提高效率,假设类List<Type>中有私有数据 成员last。函数attach可用O(1)时间完成此操作。
template <class Type> void List<Type>::Attach(Type k) {
ListNode<Type> *newnode = new ListNode<Type>(k); if (firs == 0) first = last = newnode; else {
JYP
26
通过游标可以很方便地实现求整数链表所有元素 之和的操作,如下所示:
int sum(const List<int>& a) { ListIterator<int> ai(a); if (! ai.NotNull( )) return 0; int retvalue = *ai.First( ); while (ai.NextNotNull( )) retvalue += *ai.Next( ); return retvalue;
ListIterator<Type>处理遍历链表的细节并对外提 供链表中元素的值。
下面是新的链表和链表游标定义:
JYP
21
enum Boolean {FALSE, TRUE}; template <class Type> class List; template <class Type> class ListIterator; template <class Type> class ListNode { friend class List<Type>; friend class ListIterator<Type>; private:
if (!first) {
// 插入空表
first = t; return;
}
t→link = x→link;
x→link = t;
// 插入结点x之后
}
JYP
14
例3.3 设x如上例,y指向x前面的结点,若x == first 则令y = 0。List::Delete将结点x从表中删除:
void List::Delete(ListNode *y) {
last→link = newnode; last = newnode; } }
JYP
29
2 链表反转(invert) 可用三个指针来完成“就地”反转,如下所示:
template <class Type> class List; template<class Type> class ListNode { friend class List<Type>; private:
Type data; ListNode *link; };
// 向前声明
JYP
16
template<class Type> class List { public:
JYP
19
所有这些操作都需要遍历链表中的全部元素,而
且必须作为链表类的成员函数实现。如果链表ห้องสมุดไป่ตู้于模 板定义,这些操作都将是List<Type>的成员函数。这 将导致以下问题:
(1)模板类List<Type>的全部操作最好独立于Type 的实例化类型。然而,上述大部分操作对于特定数据 类型是无意义的。
因此,通过两个类List和ListNode的复合,可以 有效实现链表。
List和ListNode的概念关系如下所示:
JYP
6
由于链表的结点数是动态变化的,类List物理上只 包含访问指针first。
实际上List和ListNode的关系如下所示:
JYP
7
表示链表的复合定义:
class List;
}
// 游标ai 和链表a关联 // 空表 // 第一个元素 // 若存在下一个元素, 取其 // 值并加入总和
JYP
27
注意,函数sum不需要访问ListNode<Type>或 List<Type>的私有数据成员。
也可以用私有变量current和上述四个公共成员函 数扩展List<Type>,但当需要从不同角度遍历同一个 链表时,这种方法不如用若干个游标对象灵活。
Type data; ListNode *link; };
JYP
22
template <class Type> class List { friend class ListIterator<Type>; public:
List( ) { first = 0;}; // 表处理操作 … private: ListNode<Type> *first; };
第3章 链表
本章学习用链表表示线性表及广义线性表的 基本方法。
JYP
1
3.1 单链表
• 用数组或顺序映射表示线性表,相邻元素存储地 址的间距是常数,因此,可用O(1)的时间访问任 何元素。但是,删除或往其中插入元素将导致表 中其它元素的移动,代价太高。
• 链表表示(linked representation)可以有效地解 决上述数据移动问题。在链表表示中,表的数据 元素可存放在存储器中的任何位置,每一个元素 都与其下一个元素的地址(指针)相关联。
}
下面是链表的基本操作的一些例子:
JYP
11
例3.1 List::Create2创建有两个结点的链表,如下 所示:
void List::Create2( ) { first = new ListNode(10); // 创建和初始化第一个结点 first→link = new ListNode(20); // 创建和初始化第二个结点 // 并链到第一个结点之后
// 检查当前结点的下一个结点是否非空 if (current && current→link) return TRUE; else return FALSE; }
JYP
25
template <class Type> Type* ListIterator<Type>::First( ) {// 返回list第一个元素的指针
}
JYP
12
例3.2 设x是任意结点的指针,List::Insert80将一个 data字段为80的新结点插入到结点x之后。当first == 0则直接将新结点插入到空表中。如下所示:
JYP
13
void List::Insert80(ListNode *x) {
ListNode *t = new ListNode(80); // 创建和初始化新结点
Boolean NextNotNull( );
Type* First( );
Type* Next( );
private:
const List<Type>& list;
// 引用一个已有链表
ListNode<Type>* current;
// 指向表list中的结点
};
JYP
24
下面是ListIterator<Type>的成员函数定义:
JYP
9
3.1.2 基本操作
假设x和y的类型为ListNode*,则在下列图(a) 基础上执行x = y和*x = *y的效果分别如图(b)和 图(c)所示:
JYP
10
链表操作必须作为类List的成员函数才能访问结 点的数据字段。
ListNode的构造函数定义为:
ListNode::ListNode(int element = 0) { // 0是缺省参数 data = element; link = 0; // 空指针
• 只存储单个指针的链表称为单链表。
JYP
2
一个单链表的例子: 在“广州”和“南京”之间插入“杭州” :
JYP
3
删除“南京” :
以上插入和删除操作不需要移动表中任何已有元 素。当然,也付出了link字段的空间代价,但通常 这还是值得的。
JYP
4
3.1.1 单链表的表示
为了描述简单,假设结点的data字段的类型是整 型数,下一节将介绍基于模板定义的通用结点。
template <class Type> Boolean ListIterator<Type>::NotNull( ) {
// 检查当前结点是否非空 if (current) return TRUE; else return FALSE; }
template <class Type> Boolean ListIterator<Type>::NextNotNull( ) {
一个整型数的空链表intList可如下定义:
List<int> intList;
JYP
18
3.2.2 链表游标
链表游标(iterator)是一种用于遍历链表的全部 元素的对象。从first出发,很容易遍历链表的全部元 素,为什么还需要游标呢?
设链表L的元素都是整型数,考虑下列操作: (1)打印L中的所有元素。 (2)计算L中所有元素的最大值、最小值和平均值。 (3)计算L中所有元素的和与积。 (4)求L中满足谓词P(x)的元素(例如,P(x)为:x 是某个整数的平方)。 (5)求L中的元素x,使得对于某个函数f,f(x)达到 最大值。
ListNode *x;
if (!y) {
x = first;
first = first→link;
}
else {
x = y→link;
y→link = x→link;
}
delete x;
// 释放结点x
}
JYP
15
3.2 可重用链表类
3.2.1 用模板定义链表
链表是一种容器,很适合用模板实现。下面是 基于模板的链表定义。
if (list.first) return &list.first→data; else return 0; }
template <class Type> Type* ListIterator<Type>::Next( ) {
// 返回当前元素的下一个元素的指针 if (current ) {
current = current→link; if (current ) return ¤t→data; } return 0; }
结点可定义为:
class ListNode { private:
int data; ListNode *link; };
如果如下说明指针变量p:
ListNode *p;
并通过p→data和p→link访问结点的(私有)数据成员, 将导致编译错误。
JYP
5
链表本身可构成一个类(如List) 。如果在定义类 ListNode时将List声明为其友元类,List的成员就 可以访问ListNode的私有数据成员了。
JYP
23
template <class Type> class ListIterator {
public:
ListIterator(const List<Type>& a): list(a), current(a.first) { };
// 与链表a关联,指向a的第一个结点
Boolean NotNull( );