11表示第11篇博文,6表示在 数据结构算法与应用C++语言描述 书中所在章节。
本文包含了《数据结构、算法与应用C++语言描述》第六章主要练习题答案,给出了线性表链表描述完整测试代码。
线性表(linear list)也称有序表(ordered list),它的每一个实例都是元素的一个有序集合。每一个实例的形式为( e 0 e_0 e0, e 1 e_1 e1,…, e n − 1 e_{n-1} en−1),其中n是有穷自然数, e i e_i ei是线性表的元素,i是元素 e i e_i ei的索引,n是线性表的长度或大小。元素可以被看做原子,它们本身的结构与线性表的结构无关。当n=0时,线性表为空;当n>0时, e 0 e_0 e0是线性表的第0个元素或首元素, e n − 1 e_{n-1} en−1是线性表的最后一个元素。可以认为 e 0 e_{0} e0先于 e 1 e_{1} e1, e 1 e_{1} e1先于 e 2 e_{2} e2,等等。除了这种先后关系之外,线性表不再有其他关系。
抽象数据类型(abstract data type,ADT),既能说明数据结构的实例,也说明对它的操作。抽象数据类型的说明独立于任何程序语言的描述。所有对抽象数据类型的语言描述必须满足抽象数据类型的说明,抽象数据类型的说明保证了程序语言描述的有效性。另外,所有满足抽象数据类型说明的语言描述,都可以在应用中替换使用。
抽象数据类型linearList
{
实例
有限个元素的有序集合
操作
empty():若表空,则返回true,否则返回false
size():返回线性表的大小(表的元素个数)
get(index):返回线性表中索引为index的元素
indexOf(x):返回线性表中第一次出现的x的索引。若x不存在,则返回-1
erase(index):删除索引为index的元素,索引大于index的元素其索引减1
insert(index,x):把x插人线性表中索引为index的位置上,索引大于等于index的元素其索引加1
output():从左到右输出表元素
}
抽象类
一个抽象类包含着没有实现代码的成员函数。这样的成员函数称为纯虚函数(pure virtual function)。纯虚函数用数字0作为初始值来说明,形式如下:
virtual int myPurevirtualFunction(int x)=0;
抽象数据类型不依赖程序语言,C++抽象类依赖程序语言。可以建立抽象类的对象指针。
抽象类的派生类,只有实现了基类的所有纯虚函数才是具体类,否则依然是抽象类而不能实例化。
具体类
具体类是没有纯虚函数的类。只有具体类才可以实例化。也就是说,我们只能对具体类建立实例或对象。
linearList抽象类定义
将抽象类的析构函数定义为虚函数,目的是,当一个线性表的实例离开作用域时需要调用的缺省析构函数时引用对象中数据类型的析构函数。
/*
Project name : allAlgorithmsTest
Last modified Date: 2022年8月13日17点38分
Last Version: V1.0
Descriptions: 线性表虚类
*/
#pragma once
#ifndef __LINEARLIST_H_
#define __LINEARLIST_H_
#include
using std::ostream;
template <class T>
class linearList
{
public:
virtual ~linearList() {};
virtual bool empty() const = 0;//返回true 当且仅当线性表为空
virtual int size() const = 0;//返回线性表的元素个数
virtual T& get(int theIndex) const = 0;//返回索引为theIndex的元素
virtual int indexOf(const T& theElement) const = 0;//返回元素theElement第一次出现时的索引
virtual void erase(int theIndex) = 0;//删除索引为theIndex的元素
virtual void insert(int theIndex, const T& theElement) = 0;//把元素theElement插入线性表中索引为theIndex的位置上
virtual void output(ostream& out) const = 0;//把线性表插入输入流out
};
#endif
设L=( e 0 e_0 e0, e 1 e_1 e1,…, e n − 1 e_{n-1} en−1)是一个线性表。在对这个线性表的一个可能的链式描述中,每个元素都在一个单独的节点中描述,每一个节点都有一个链域,它的值是线性表的下一个元素的位置,即地址。这样一来,元素 e i e_{i} ei的节点链接着元素 e i + 1 e_{i+1} ei+1的节点,0<=i
每一个节点只有一条链,这种结构称为单链表。
原则上,数组适合查询比较频繁的数据存储,链表适合插入和删除比较频繁的数据存储,但是由于高速缓存容量的问题,导致单链表的插入和删除操作的运行时间高于数组。
也就是说,对于单表而言,性能比不上数组;但是对于某些特殊应用而言,比如把一个链表的尾节点和另一个链表的首节点链接起来,合并为一个链表,对于链表合并的时间复杂度为O(1),对于数组的时间复杂度比O(1)大。
1)把线性表描述成一个单向循环链表(singly linked circular list)(简称循环链表);只要将单向链表的尾节点与头节点链接起来,单向链表就成为循环链表。
2)在链表的前面增加一个节点,称为头节点(header node)。使用头节点的链表非常普遍,这样可以使程序更简洁、运行速度更快。
双向链表(doubly linked list)的每个节点都有两个指针:next 和previous。next指针指向右边节点(如果存在),previous指针指向左边节点(如果存在)。当双向链表只有一个元素节点p时,firstNode=lastNode=p。当双向链表为空时,firstNode=lastNode=NULL。
用链表保存班级学生清单。节点的数据域有:学生姓名、社会保险号码、每次作业和考试的分数、所有作业和考试的加权总分。假设分数是0~ 100的整数,要求按总分排序。
箱子排序(bin sort)就是首先把分数相同的节点放在同一个箱子里,然后把箱子链接起来就得到有序的链表。
通用箱子排序步骤:
1)逐个删除输入链表的节点,把删除的节点分配到相应的箱子里;
2)把每一个箱子中的链表收集并链接起来,使其成为一个有序链表。
链表箱子排序步骤:
1)连续删除链表的首元素,并将其插入相应的某个箱子的链表首位;
2)从最后一个箱子开始,逐个删除每个箱子的元素,并将其插人一个初始为空的链表的首位。
稳定排序(stable sort):排序方法能够保持同值元素之间的相对次序,箱子排序就是稳定排序。
定义
基数排序可以仅在Θ(n)时间内,就可以对0~ n c − 1 n^c-1 nc−1之间的n个整数进行排序,其中c>=0是一个整数常量。如果用原来的箱子排序方法对range= n c n^{c} nc来排序,则复杂度为 Θ ( n + r a n g e ) = O ( n c ) Θ(n+range)=O(n^{c}) Θ(n+range)=O(nc)。扩展后的方法与binSort不同,它不直接对数进行排序,而是把数(number)按照某种基数(radix)分解为数字(digit),然后对数字排序。基数可以根据需要选择。
举例
例6-1 假定对0~999之间的10个整数进行排序。如果使用range=1000的箱子排序方法,那么箱子链表的初始化需要1000个执行步,节点分配需要10个执行步,从箱子中收集节点需要1000个执行步,总的执行步数为2010。而基数排序方法是:
1)利用箱子排序方法,根据最低位数字(即个位数字),对10个数进行排序。因为每个数字都在0~9之间,所以range=10。
2)利用箱子排序方法,对1)的结果按次低位数字(即十位数字)进行排序。同样有range=10。因为箱子排序是稳定排序,所以次低位数字相同的节点,按最低位数字排序所得到的次序保持不变。因此,现在的链表是按照最后两位数字进行排序的。
3)利用箱子排序方法,对2)的结果按第三位数字(即百位数字)进行排序。小于100的数,第三位数字为0。因为按第三位数字排序是稳定排序,所以第三位数字相同的节点,按最后两位数字排序所得到的次序保持不变。因此,现在的链表是按照后三位数字进行排序的。
数字分解式
对于一般的基数r,相应的分解式为:
当使用基数r=n对0~ n c − 1 n^{c}-1 nc−1范围内的n个整数进行分解时,每个数可以分解出c个数字。因此,对n个数,可以用c次range=n个箱子排序。因为c是一个常量,所以整个排序时间为Θ(cn)=Θ(n)。
见Github仓库:Github::Data-Structures-Algorithms-and-Applications/_2chain/