2.65k likes | 2.76k Views
第一部分 线性表. 具有线性关系的数据集合的处理 包括三部分内容 线性表 栈 队列. 第二章 线性表. 线性表的概念 线性表的实现 线性表类的实现 STL 中线性表的实现. 线性表的概念. 线性表是 N 个具有相同特征的结点 A 0 , A 1 , … , A N-1 构成的集合。在这个集合中,除了 A 0 和 A N-1 外,每个元素都有唯一的前趋和后继。对于每个 A i ,它的前驱是 A i-1 ,它的后继是 A i+1 。 A 0 只有后继没有前驱, A N-1 只有前驱没有后继。 表的术语: N 为表的大小
E N D
第一部分 线性表 • 具有线性关系的数据集合的处理 • 包括三部分内容 • 线性表 • 栈 • 队列
第二章 线性表 • 线性表的概念 • 线性表的实现 • 线性表类的实现 • STL中线性表的实现
线性表的概念 • 线性表是N个具有相同特征的结点A0, A1, …, AN-1构成的集合。在这个集合中,除了A0和AN-1外,每个元素都有唯一的前趋和后继。对于每个Ai,它的前驱是Ai-1,它的后继是Ai+1。A0只有后继没有前驱,AN-1只有前驱没有后继。 • 表的术语: • N为表的大小 • A0称为首结点, AN-1称为尾结点 • 空表:元素个数为零的表。 • 位置:元素Ai在表中的位置为i
表的基本操作 • 创建一个线性表create():创建一个空的线性表; • 清除一个线性表clear():删除线性表中的所有数据元素; • 求线性表的长度length():返回线性表的长度; • 在第i个位置插入一个元素insert(i, x):使线性表从(a0,a1,…ai-1,ai, …an-1)变成(a0,a1,…ai-1,x,ai, …an-1),参数i的合法取值范围是0到n;
删除第i个位置的元素remove(i):使线性表从(a0,a1,…ai-1,ai, ai+1…an-1)变成(a0,a1,…ai-1, ai+1 , …an-1),参数i的合法取值范围是0到n-1; • 搜索某个元素在线性表中是否出现search(x):在线性表中搜索x是否出现,并返回x的位置; • 访问线性表的第i个元素visit(i):返回线性表中第i个数据元素的值; • 遍历线性表运算traverse():按序访问线性表的每一数据元素。
第二章 线性表 • 线性表的概念 • 线性表的实现 • 线性表类的实现 • STL中线性表的实现
线性表的实现 • 线性表的顺序实现 • 线性表的链接实现
线性表的顺序存储结构 • 线性表中结点存放在存储器上一块连续的空间中。 • 借助存储空间的连续性,结点可以按照其逻辑顺序依次存放。 • 结点存放的物理位置和它的逻辑位置是一致的。
线性表的顺序存储 • 在程序设计语言中,一块连续的存储空间可以用一个数组实现。由于线性表中的元素个数是动态的,因此采用了动态数组。 • 保存一个动态数组,需要三个变量:指向线性表元素类型的指针,数组规模(容量),数组中的元素个数(表长)。 maxSize length data a0 a1 a2 … an-1 an
线性表的运算实现 • length():只需要返回length的值 • visit(i):返回data[i]的值 • traverse():输出数组data的前length个元素 • clear():只要将length置为0即可 • create (maxSize):申请一个maxSize大小的动态数组
search 运算的实现 int search(x) {for (num = 0; num < length; ++ num) if (data[num] == x) break; if (num == length) num = -1; return num; }
length insert(i, x) 运算的实现 在插入时,表长会增加。当表长等于容量时,新增加的元素将无法存储。此时有两种解决方法:一种是不执行插入,报告一个错误消息;另一种是扩大数组的容量
void insert(i, x) { if (length == maxSize) resize(); for ( j = n-1; j>=i; --j) data[j+1] = data[j]; data[i] = x; ++length; }
resize 操作的实现 • resize操作按一定的比例扩大数组的空间,常用的比例是扩大一倍。 • 数组空间在内存中必须是连续的,因此,扩大数组空间的操作必须重新申请一个更大规模的动态数组,将原有数组的内容拷贝到新数组中,释放原有数组空间,将新数组作为存储线性表的存储区。
data tmp
a0 a1 … ai ai+1 … an remove(i) 运算的实现 void remove(i) {for ( j = i; j < length - 1; ++j) data[j] = data[j+1]; --length; }
顺序实现的算法分析 • length, visit和clear的实现与表中的元素个数无关,因此它们的时间复杂度是O(1)。 • traverse()操作遍历整个表中的所有元素,因此时间复杂度为O(n)。 • create操作需要申请一块动态数组的空间,并设表为空。因此也是O(1)的时间复杂度。 • 插入操作,需要移动结点。当i等于n时,移动次数为0。当i等于0时,移动次数为n。 • 最好情况下的时间复杂度为O(1) • 最坏情况下的时间复杂度为O(n) • 平均的时间复杂度:如果在每个位置上的插入都是等概率的,则插入算法的平均时间复杂度为n/2
线性表的顺序实现总结 • 由于要保持逻辑次序和物理次序的一致性,顺序表在插入删除时需要移动大量的数据,性能不太理想。 • 由于逻辑次序和物理次序的一致性使得定位访问的性能很好。 • 顺序表比较适合静态的、经常做定位访问的线性表。
表的实现 • 线性表的顺序实现 • 线性表的链接实现
线性表的链接存储结构 • 将每个结点放在一个独立的存储单元中,结点间的逻辑关系依靠存储单元中附加的值针来给出。 • 结点的存储单元在物理位置上可以相邻,也可以不相邻。
线性表的链接存储 • 单链表 • 双链表 • 循环链表
head nil 单链表 • 每个结点附加了一个指针字段,如next,该指针指向它的直接后继结点,最后一个结点的next字段为空。
ai+1 ai x insert p
头结点 、头指针 • 为了消除特殊情况,通常在表头额外增加一个相同类型的特殊结点,称之为头结点。它们不是线性表中的组成部分。 • 头结点的出现,使得在表头位置上进行插入和删除和在其它结点位置上是完全一致的,从而使得插入和删除算法得到简化。
head 带头结点的单链表 an-1 a0 a1 … ∧
head Create函数的实现 • 申请一块存储结点的空间,设结点的指针部分为空指针。 • 将结点地址存入代表单链表的变量head。 ∧
清除一个线性表clear() • 把所有结点的空间还给系统 ,把头结点的指针部分置为空指针 void clear() { P = 头结点的直接后继; While (p != 空指针) { q = p; p = p的直接后继地址; 释放q的空间; } 头结点的后继指针置为空指针; }
求表的长度length() • 方法1:从起始结点开始,沿着后继指针链一个一个往后数,数到一个结点,长度加1 int length() { len = 0; p = 头结点的直接后继; While (p != 空指针) { ++len; p = p的直接后继的地址;} } • 方法2:用空间换取时间的方法。在保存单链表的时候增加一个变量,保存表的长度
在第i个位置插入一个元素insert(i, x) void insert(i, x) { for (j = 0, p = head; j < i; ++j) p = p的直接后继的地址; tmp = new 结点; tmp指向的结点的数据部分 = x; tmp指向的结点的指针部分 = p的直接后继的地址; p指向的结点的指针部分 = tmp; }
删除第i个位置的元素remove(i) void remove(i) { for (j = 0, p = head; j < i; ++j) p= p的直接后继的地址; tmp = p的直接后继的地址; p的指针部分 = tmp的直接后继的地址; delete tmp; }
搜索某个元素在线性表中是否出现search(x) int search(x) { num = 0; p = 头结点的直接后继; While (p != 空指针 && p的数据部分 != x) { ++num; p = p的直接后继的地址;} if (p == 空指针) num = -1; return num; }
访问线性表的第i个元素visit(i) dataType visit(i) { for (j = 0, p = head; j < i; ++j) p= p的直接后继的地址; return p指向的结点的数据部分; }
遍历运算traverse() void traverse() { p = 头结点的直接后继; While (p != 空指针) { cout << p指向结点的数据部分; p = p的直接后继的地址; } }
线性表的链接存储 • 单链表 • 双链表 • 循环链表
双链表 • 每个结点附加了两个指针字段,如prior和next • prior字段给出直接前驱结点的地址 • next给出直接后继结点的地址。
tail head ∧ ∧ 双链表的头尾节点 • 为了消除在镖头、表尾插入删除的特殊情况,通常双链表设一头结点,设一尾节点 • 头结点中prior字段为空,它的next字段给出线性表中的首结点的地址 • 尾结点中next字段为空,它的prior字段给出线性表中最后一个节点的地址
tail head ∧ ∧ Create运算 • 创建一个双链表就是创建一个只有头尾结点的链表,把头结点的地址保存在head中,尾结点的地址保存在tail中
x insert p
remove p x
线性表的链接存储 • 单链表 • 双链表 • 循环链表
head 单循环链表 • 一般单循环链表不带头结点
双循环链表 • 头结点中prior字段给出尾结点的地址,尾结点中next字段给出头结点的地址 • 一般也不设头尾结点
第二章 线性表 • 线性表的概念 • 线性表的实现 • 线性表类的实现 • STL中线性表的实现
线性表类的实现 • 线性表抽象类 • 顺序表类 • 双链表类
线性表的抽象类 • 线性表的抽象类是一个类模板 • 抽象类包括了除create运算以外的所有运算。create运算由每个线性表类的构造函数完成 • 增加了一个虚析构函数,以防内存泄漏
线性表的抽象类 template <class elemType> class list { public: virtual void clear() = 0; virtual int length() const = 0; virtual void insert(int i, const elemType &x) = 0; virtual void remove(int i) = 0; virtual int search(const elemType &x) const = 0 ; virtual elemType visit(int i) const = 0; virtual void traverse() const = 0 virtual ~list() {}; };
线性表类的实现 • 线性表抽象类 • 顺序表类 • 双链表类
顺序表类的定义 class OutOfBound{}; class IllegalSize{}; template <class elemType> class seqList: public list<elemType> { private: elemType *data; int currentLength; int maxSize; void doubleSpace();
public: seqList(int initSize = 10); ~seqList() {delete [] data;} void clear() {currentLength = 0;} int length() const {return currentLength;} void insert(int i, const elemType &x); void remove(int i); int search(const elemType &x) const ; elemType visit(int i) const; void traverse() const ; };
maxSize length 构造函数 template <class elemType> seqList<elemType>::seqList(int initSize) { if (initSize <= 0) throw IllegalSize(); data = new elemType[initSize]; maxSize = initSize; currentLength = 0; }