5表示在 数据结构算法与应用C++语言描述 书中所在章节。
本文包含了《数据结构、算法与应用C++语言描述》第五章主要练习题答案,给出了线性表数组描述完整测试代码。
数据对象(data object)是一组实例或值。
digit={0, 1,2,3,4,5,6,7,8, 9}
如digit是数据对象,0,1,…,9是digit的实例。
letter={A,B,C,...,Z, a, b,c,...z}
string={a,b,...,aa,ab,ac,...}
如数据对象string是由所有可能的串实例组成的集合。每个实例都由字符组成。例如,good,a trip to Hawaii,going down hill 和 abcabcdabcde 都是串实例。第一个串由4个元素g、o、o和d顺序组成,每个元素都是数据对象letter的实例。
在串good中,g是第一字母,o是第二和第三个字母,而d是最后一个字母。
这些函数可以把对象的某个实例转化成该对象的另外一个实例,或转化成另一个数据对象的实例,或者同时进行上述两种转化。函数也可以不用转化而创建一个新的实例。例如,把两个自然数相加的函数创建了一个新的自然数,而两个加数并没有发生转化。
数据结构(data structure)是一个数据对象,同时这个对象的实例以及构成实例的元素都存在着联系,而且这些联系由相关的函数来规定。
研究数据结构,关心的是数据对象(实际上是实例)的描述以及相关函数的具体实现。
数据对象描述得好,函数的实现就会高效。
最常用的数据对象以及操作都已经在C++中作为基本数据类型而实现,如整数对象(int)、布尔对象(bool)等。其他数据对象均可以用基本数据类型以及由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
在数组描述(array representation)中,用数组来存储线性表的元素。
假定使用一个一维数组element来存储线性表的元素。数组element的位置有element[0]…element[arrayLength-1],其中 arrayLength是数组长度或容量。数组的每一个位置都可以存储线性表的一个元素。我们需要一个映射,使线性表的一个元素对应数组的一个位置。映射方式可以有很多种,以下三种是比较经典的。
l o c a t i o n ( i ) = i location(i) = i location(i)=i
l o c a t i o n ( i ) = a r r a y L e n g t h − i − 1 location(i) = arrayLength - i - 1 location(i)=arrayLength−i−1
l o c a t i o n ( i ) = ( l o c a t i o n ( 0 ) + i ) % a r r a y L e n g t h location(i) = (location(0) + i) \% arrayLength location(i)=(location(0)+i)%arrayLength
对于一个数组,要增加或减少这个数组的长度,首先要建立一个具有新长度的数组,然后把数组a的元素复制到这个新数组,最后改变数组指针a的值,使它能够引用新数组。
本程序的时间复杂度为O(n)。
/*
作用:将数组的长度加倍
输入:指针a指向需要改变长度的数组,oldLength表示数组原来的长度,newLength表示需要改变的新长度
结果:将数组扩容/缩容 为newLength
*/
template<class T>
void changeLength(T*& a, int oldLength, int newLength)
{
if (newLength < 0)
throw illegalParameterValue("new length must be >= 0");
T* temp = new T[newLength];
int number = min(oldLength, newLength);
copy(a, a + number, temp);
delete[] a;
a = temp;
}
template<class T>
class arrayList : public linearList<T>
{
public:
/*构造函数*/
arrayList(int initialCapacity = 1);
/*复制构造函数*/
arrayList(const arrayList<T>&);
/*使用链表构造数组*/
arrayList(const chain<T>& theChain);
/*析构函数*/
~arrayList() { delete[] element; }
/*其他成员函数*/
bool empty() const { return arraySize == 0; }
int size() const { return arraySize; }
T& get(int theIndex) const;
int indexOf(const T& theElement) const;
void erase(int theIndex);
void insert(int theIndex, const T& theElement);
void insert(int theIndex, const T& theElement,int size);
void output(ostream& out) const;
/*练习题5 时间复杂度为O(1)*/
void trimToSize();
int capacity() const { return arrayLength; }
/*练习题11*/
void push_back(const T& theElement);//将元素theElement插入数组的最右端
/*练习题12*/
T pop_back();//删除数组最右端元素,并返回该元素
/*练习题13*/
void swap(arrayList<T>& theList);
/*练习题14*/
void reserve(int theCapacity);
/*练习题15*/
void set(int theIndex, const T& theElement);
/*练习题16*/
void clear();
/*练习题17*/
void removeRange(int theIndex1, int theIndex2);
/*练习题18*/
int lastIndexOf(T theElement);
/*练习题22*/
void reverse();
/*练习题23*/
void leftShift(int num);
/*练习题24*/
void circularShift(int i);
/*练习题25*/
void half();
/*练习题28*/
void meld(arrayList<T>& a, arrayList<T>& b);
/*练习题29*/
void merge(arrayList<T>& a, arrayList<T>& b);
/*练习题30*/
void split(arrayList<T>& a, arrayList<T>& b);
void reSet(int capacity);
/*迭代器相关*/
//问题:扩展迭代器,使之成为随机访问迭代器,这个暂时不太会。
class iterator
{
public:
//用C++的typedef语句实现双向迭代器
//这一小段typedef暂时不太懂
typedef bidirectional_iterator_tag iterator_category;//双向迭代器
typedef T value_type;
typedef ptrdiff_t difference_type;//是一个与机器相关的数据类型,通常用来保存两个指针减法操作的结果。
typedef T* pointer;
typedef T& reference;
//构造函数
iterator(T* thePosition = 0) { position = thePosition; }
//解引用操作符
T& operator*() const { return *position; }
//指针操作符->
T* operator->() const { return &*position; }
//迭代器的值增加
iterator& operator++()//前加
{
++position;
return *this;
}
iterator operator++(int)//后加
{
iterator old = *this;
++position;
return old;
}
//迭代器的值减少
iterator& operator--()//前减
{
--position;
return *this;
}
iterator operator--(int)//后减
{
iterator old = *this;
--position;
return old;
}
//测试是否相等
bool operator!=(const iterator right) const
{
return position != right.position;
}
bool operator==(const iterator right) const
{
return position == right.position;
}
protected:
T* position;
};
iterator begin() { return iterator(element); }
iterator end() { return iterator(element + arraySize); }//返回的是最后一个元素后面的一个位置
/*重载操作符*/
/*练习题7*/
T& operator[](int i);
const T& operator[](int i) const;
/*练习题8*/
bool operator==(arrayList<T> right) const;
/*练习题9*/
bool operator!=(arrayList<T> right) const;
/*练习题10*/
bool operator<(arrayList<T> right) const;
/*排序*/
void bubbleSort();
void rankSort();
int indexOfMax();
void selectionSort();
void insertSort();
/*友元函数*/
//这个函数用于一次性输入很多元素到线性表
friend istream& operator>> <T>(istream& in, arrayList<T>& m);//已测
protected:
void checkIndex(int theIndex) const;//若索引theIndex无效,则抛出异常
/*setLength()函数没有重新分配内存*/
/*setLength(),setSize(),setElement()函数只应用于swap()函数*/
void setLength(int Length) { arrayLength = Length; }
void setSize(int Size) { arraySize = Size; }
void setElement(T* theElement) { element = theElement; }
T* address() const { return element; }
T* element;//存储线性表元素的一维数组
int arrayLength;//一维数组的容量
int arraySize;//线性表的元素个数
};
所有练习题答案见完整测试代码,注释中说明了题目的对应关系。
5.编写一个方法arrayList::trimToSize,它使数组的长度等于max(listSize,1)。这个方法的复杂度是多少?测试你的代码。
7.重载操作符[],使得表达式x[i]返回对线性表第i个元素的引用。若线性表没有第i个元素,则抛出异常illegallndex。语句x[i]=y和y=x[i]按以往预期的方式执行。测试你的代码。
8.重载操作符==,使得表达式x==y返回true,当且仅当两个用数组描述的线性表x和y相等(即对所有的i,两个线性表的第i个元素相等)。测试你的代码。
9.重载操作符!=,使得表达式x!=y返回true,当且仅当两个用数组描述的线性表x和y不等(见练习8)。测试你的代码。
10.重载操作符<,使得表达式x
11.编写方法arrayList::push_back,它把元素theElement插到线性表的右端。不要利用insert方法。方法的时间复杂度是多少?测试你的代码。
12.编写方法arrayList::pop_back,它把线性表右端的元素删除。不要利用erase方法。方法的时间复杂度是多少?测试你的代码。
13.编写方法arrayList::swap(theList),它交换线性表的元素*this和theList。方法的时间复杂度是多少?测试你的代码。
14.编写方法arrayList::reserve(theCapacity),它把数组的容量改变为当前容量和theCapacity的较大者。测试你的代码。
15.编写方法arrayList::set(thelndex,theElement),它用元素theElement替换索引为thelndex的元素。若索引theIndex超出范围,则抛出异常。返回原来索引为thelndex的元素。测试你的代码。
16.编写方法arrayList::clear,它使线性表为空。方法的复杂度是多少?测试你的代码。
17.编写方法arrayList::removeRange,它删除指定索引范围内的所有元素。方法的复杂度是多少?测试你的代码。
18.编写方法arrayList::lastInexOf,它的返回值是指定元素最后出现时的索引。如果这样的元素不存在,则返回-1。方法的复杂度是多少?测试你的代码。
22.1)编写方法arrayList::reverse,它原地颠倒线性表元素的顺序(即在数组element中完成操作,不创建新的数组)。颠倒顺序之前,线性表的第k个元素是element[k],颠倒之后,线性表的第k个元素是element[listSize-k-1]。不要利用STL函数reverse。
2)方法应具有listSize的线性复杂度。证明这个性能。
3)设计测试数据,测试方法的正确性。
23.1)编写方法arrayList::leftShift(i),它将线性表的元素向左移动i个位置。如果x=[0,1,2,3,4],那么 x.leftShift(2)的结果是x=[2,3,4]。
2)计算方法的复杂度。
3)测试你的代码。
24.在一个循环移动的操作中,线性表的元素根据给定的值,按顺时针方向移动。例如,x=[0,1,2,3,4],循环移动2的结果是x=[2,3,4,0,1]。
1)描述一下如何利用3次逆转操作完成循环移动。每一次逆转操作都可以将线性表的一部分或全部逆转。
2)编写方法arrayList::circularShift(i),它将线性表的元素循环移动i个位置。方法应具有线性表长度的线性复杂度。
3)测试你的代码。
25.调用语句x.half(),可以将x的元素隔一个删除一个。如果x.size()是7,x.element[]=[2,13,4,5,17,8,29],那么x.half()的结果是x.size()是4,x.element[]=[2,4,17,29]。如果x.size()是4,x.element[]=[2,13,4,5],那么x.half()的结果是x.size()是2,x.element[]=[2,4]。如果x为空,那么x.half()的结果也是x为空。
1)编写方法arrayList::half()。不能利用类arrayList的其他方法。复杂度应该为O(listSize)。
2)证明方法的复杂度为O(listSize)。
3)测试你的代码。
28.令a和b是类arrayList的两个对象。
1)编写方法arrayList::meld(a,b),它生成一个新的线性表,从a的第0个元素开始,交替地包含a和b的元素。如果一个表的元素取完了,就把另一个表的剩余元素附加到新表中。调用语句c.meld(a,b)使c成为合并后的表。方法应具有两个输人线性表大小的线性复杂度。
2)证明方法具有a和b大小之和的线性复杂度。
3)测试你的代码。
29.令a和b是类arrayList的两个对象。假设它们的元素从左到右非递减有序。
1)编写方法arrayList::merge(a,b),它生成一个新的有序线性表,包含a和b的所有元素。归并后的线性表是调用对象*this。不要利用STL函数merge。
2)计算方法的复杂度。
3)测试你的代码。
30.1)编写方法arrayList::split(a,b),它生成两个线性表a和b。a包含*this中索引为偶数的元素,b包含其余的元素。2)计算方法的复杂度3)测试你的代码。
见Github::Data-Structures-Algorithms-and-Applications/_1arrayList/