710 likes | 784 Views
第九章 群体类. C++ 语言程序设计. 清华大学计算机与信息管理中心 郑 莉. 线性群体 线性群体的概念 直接访问群体 -- 数组类 顺序访问群体 -- 链表类 栈类 队列类. 本章主要内容. 群体的概念. 群体 是指由多个数据元素组成的集合体。群体可以分为两个大类: 线性群体 和 非线性群体 。 线性群体中的元素按位置排列有序,可以区分为第一个元素、第二个元素等。 非线性群体不用位置顺序来标识元素。. …. 第一个元素. 第二个元素. 第三个元素. 最后一个元素. 线性群体的概念.
E N D
第九章 群体类 C++语言程序设计 清华大学计算机与信息管理中心 郑 莉
线性群体 线性群体的概念 直接访问群体--数组类 顺序访问群体--链表类 栈类 队列类 本章主要内容
群体的概念 群体是指由多个数据元素组成的集合体。群体可以分为两个大类:线性群体和非线性群体。 线性群体中的元素按位置排列有序,可以区分为第一个元素、第二个元素等。 非线性群体不用位置顺序来标识元素。
… 第一个元素 第二个元素 第三个元素 最后一个元素 线性群体的概念 线性群体中的元素次序与其位置关系是对应的。在线性群体中,又可按照访问元素的不同方法分为直接访问、顺序访问和索引访问。 在本章我们只介绍直接访问和顺序访问。
数组 直接访问线性群体 • 静态数组是具有固定元素个数的群体,其中的元素可以通过下标直接访问。 • 缺点:大小在编译时就已经确定,在运行时无法修改。 • 动态数组由一系列位置连续的,任意数量相同类型的元素组成。 • 优点:其元素个数可在程序运行时改变。
动态数组类模板 直接访问线性群体 • 数组类模板:例9.1(9_1.h)
#ifndef ARRAY_CLASS #define ARRAY_CLASS #include <iostream.h> #include <stdlib.h> #ifndef NULL const int NULL = 0; #endif // NULL enum ErrorType { invalidArraySize, memoryAllocationError, indexOutOfRange }; char *errorMsg[] = { "Invalid array size", "Memory allocation error", "Invalid index: " };
template <class T> class Array { private: T* alist; int size; void Error(ErrorType error,int badIndex=0) const; public: Array(int sz = 50); Array(const Array<T>& A); ~Array(void); Array<T>& operator= (const Array<T>& rhs); T& operator[](int i); operator T* (void) const; int ListSize(void) const; void Resize(int sz); };
数组类模板部分成员函数的实现 // 构造函数 template <class T> Array<T>::Array(int sz) { if (sz <= 0) //sz为数组大小(元素个数),若小于0,则输出错误信息 Error(invalidArraySize); size = sz; // 将元素个数赋值给变量size alist = new T[size]; //动态分配size个T类型的元素空间 if (alist == NULL) //如果分配内存不成功,输出错误信息 Error(memoryAllocationError); }
拷贝构造函数 template <class T> Array<T>::Array(const Array<T>& X) { int n = X.size; size = n; alist = new T[n]; if (alist == NULL) Error(memoryAllocationError); T* srcptr = X.alist; // X.alist是对象X的数组首地址 T* destptr = alist; // alist是本对象中的数组首地址 while (n--) // 逐个复制数组元素 *destptr++ = *srcptr++; }
A的数组元素 占用的内存 A的数组元素 占用的内存 A A alist size alist size B alist size 拷贝前 拷贝后 浅拷贝 template <class T> Array<T>::Array( const Array<T>& X) { size = X.size; alist= X.alist; } void main(void) { Array<int> A(10); ...... Array<int> B(A); ...... }
A的数组元素 占用的内存 A alist size A的数组元素 占用的内存 A alist size B alist size B的数组元素 占用的内存 拷贝前 拷贝后 深拷贝
重载"="运算符 template <class T> Array<T>& Array<T>::operator= (const Array<T>& rhs) { int n = rhs.size; if (size != n) { delete [] alist; alist = new T[n]; if (alist == NULL) Error(memoryAllocationError); size = n; } T* destptr = alist; T* srcptr = rhs.alist; while (n--) *destptr++ = *srcptr++; return *this; }
重载下标操作符 template <class T> T& Array<T>::operator[] (int n) { // 检查下标是否越界 if (n < 0 || n > size-1) Error(indexOutOfRange,n); // 返回下标为n的数组元素 return alist[n]; }
为什么有的函数返回引用 • 如果一个函数的返回值是一个对象的值,它就被认为是一个常量,不能成为左值。 • 如果返回值为引用。由于引用的实质就是对象的地址,所以通过引用当然可以改变对象的值。
重载指针转换操作符 template <class T> Array<T>::operator T* (void) const { // 返回当前对象中私有数组的首地址 return alist; }
#include <iostream.h> void main() { int a[10]; void read(int *p, int n); read(a, 10); } void read(int *p, int n) { for (int i=0; i<n; i++) cin>>p[i]; } void main() { Array<int> a(10); void read(int *p, n); read(a, 10); } void read(int *p, int n) { for (int i=0; i<n; i++) cin>>p[i]; } 指针转换运算符的作用
Array类的应用 直接访问线性群体 • 例9.2 求范围2~N中的质数,N在程序运行时由键盘输入。
#include <iostream.h> #include <iomanip.h> #include "9_1.h" void main(void) { Array<int> A(10); int n; int primecount = 0, i, j; cout << "Enter a value >= 2 as upper limit for prime numbers: "; cin >> n; A[primecount++] = 2; // 2是一个质数
for(i = 3; i < n; i++) { if (primecount == A.ListSize()) A.Resize(primecount + 10); if (i % 2 == 0) continue; j = 3; while (j <= i/2 && i % j != 0) j += 2; if (j > i/2) A[primecount++] = i; } for (i = 0; i < primecount; i++) { cout << setw(5) << A[i]; if ((i+1) % 10 == 0) cout << endl; } cout << endl; }
链表 顺序访问线性群体 • 链表是一种动态数据结构,可以用来表示顺序访问的线性群体。 • 链表是由系列结点组成的,结点可以在运行时动态生成。 • 每一个结点包括数据域和指向链表中下一个结点的指针(即下一个结点的地址)。如果链表每个结点中只有一个指向后继结点的指针,则该链表称为单链表。
… head rear data1 data2 data3 datan NULL 单链表 顺序访问线性群体
单链表的结点类模板(例9-3) 顺序访问线性群体 template <class T> class Node { private: Node<T> *next; public: T data; Node(const T& item,Node<T>* ptrnext = NULL); void InsertAfter(Node<T> *p); Node<T> *DeleteAfter(void); Node<T> *NextNode(void) const; };
data2 在结点之后插入一个结点 顺序访问线性群体 … data1 … data p template <class T> void Node<T>::InsertAfter(Node<T> *p) { //p节点指针域指向当前节点的后继节点 p->next = next; next = p; //当前节点的指针域指向p }
data2 data3 删除结点之后的结点 顺序访问线性群体 data1 … … tempPtr Node<T> *Node<T>::DeleteAfter(void) { Node<T> *tempPtr = next; if (next == NULL) return NULL; next = tempPtr->next; return tempPtr; }
链表的基本操作 顺序访问线性群体 • 生成结点 • 输出链表 • 查找结点 • 插入结点 • 删除结点 • 清空链表
实现链表操作函数(例9-4) 顺序访问线性群体 #ifndef NODE_LIBRARY #define NODE_LIBRARY #include <iostream.h> #include <stdlib.h> #include "9_3.h"
// 生成节点 template <class T> Node<T> *GetNode(const T& item, Node<T> *nextPtr = NULL) { Node<T> *newNode; newNode = new Node<T>(item, nextPtr); if (newNode == NULL) { cerr << "Memory allocation failure!" << endl; exit(1); } return newNode; }
enum AppendNewline {noNewline,addNewline}; // 输出链表 template <class T> void PrintList(Node<T> *head, AppendNewline addnl = noNewline) { Node<T> *currPtr = head; while(currPtr != NULL) { if(addnl == addNewline) cout << currPtr->data << endl; else cout << currPtr->data << " "; currPtr = currPtr->NextNode(); } }
//查找节点 template <class T> int Find(Node<T> *head, T& item, Node<T>* &prevPtr) { Node<T> *currPtr = head; prevPtr = NULL; while(currPtr != NULL) { if (currPtr->data == item) return 1; prevPtr = currPtr; currPtr = currPtr->NextNode(); } return 0; }
//在表头插入节点 template <class T> void InsertFront(Node<T>* & head, T item) { head = GetNode(item,head); } //在表尾添加节点 template <class T> void InsertRear(Node<T>* & head, const T& item) { Node<T> *newNode, *currPtr = head; if (currPtr == NULL) InsertFront(head,item); else { while(currPtr->NextNode() != NULL) currPtr = currPtr->NextNode(); newNode = GetNode(item); currPtr->InsertAfter(newNode); } }
// 删除链表的第一个节点 template <class T> void DeleteFront(Node<T>* & head) { Node<T> *p = head; if (head != NULL) { head = head->NextNode(); delete p; } }
// 删除链表中第一个数据域等于key的节点 template <class T> void Delete (Node<T>* & head, T key) { Node<T> *currPtr = head, *prevPtr = NULL; if (currPtr == NULL) return; while (currPtr != NULL && currPtr->data != key) { prevPtr = currPtr; currPtr = currPtr->NextNode(); } if (currPtr != NULL) { if(prevPtr == NULL) head = head->NextNode(); else prevPtr->DeleteAfter(); delete currPtr; } }
// 在有序链表中插入一个节点 template <class T> void InsertOrder(Node<T>* & head, T item) { Node<T> *currPtr, *prevPtr, *newNode; prevPtr = NULL; currPtr = head; while (currPtr != NULL) { if (item < currPtr->data) break; prevPtr = currPtr; currPtr = currPtr->NextNode(); } if (prevPtr == NULL) InsertFront(head,item); else { newNode = GetNode(item); prevPtr->InsertAfter(newNode); } }
//清空链表--删除链表中的所有节点 template <class T> void ClearList(Node<T> * &head) { Node<T> *currPtr, *nextPtr; currPtr = head; while(currPtr != NULL) { nextPtr = currPtr->NextNode(); delete currPtr; currPtr = nextPtr; } head = NULL; } #endif // NODE_LIBRARY
链表应用举例 顺序访问线性群体 • 例9.5 从键盘输入10个整数,用这些整数值作为结点数据,生成一个链表,按顺序输出链表中结点的数值。然后从键盘输入一个待查找整数,在链表中查找该整数,若找到则删除该整数所在的结点(如果出现多次,全部删除),然后输出删除结点以后的链表。在程序结束之前清空链表。 9-5.cpp
#include <iostream.h> #include "9_3.h" #include "9_4.h" void main(void) { Node<int> *head = NULL, *prevPtr, *delPtr; int i, key, item; for (i=0;i < 10;i++) { cin>>item; InsertFront(head, item); } cout << "List: "; PrintList(head,noNewline); cout << endl;
cout << "请输入一个需要删除的整数: "; cin >> key; prevPtr = head; while (Find(head,key,prevPtr) != NULL) { if(prevPtr == NULL) head = head->NextNode(); else delPtr=prevPtr->DeleteAfter(); delete delPtr; } cout << "List: "; PrintList(head,noNewline); cout << endl; ClearList(head); }
链表类模板(例9-6) 顺序访问线性群体 //9_6.h #ifndef LINKEDLIST_CLASS #define LINKEDLIST_CLASS #include <iostream.h> #include <stdlib.h> #ifndef NULL const int NULL = 0; #endif // NULL #include "9_3.h"
template <class T> class LinkedList { private: Node<T> *front, *rear; Node<T> *prevPtr, *currPtr; int size; int position; Node<T> *GetNode(const T& item, Node<T> *ptrNext=NULL); void FreeNode(Node<T> *p); void CopyList(const LinkedList<T>& L);
public: LinkedList(void); LinkedList(const LinkedList<T>& L); ~LinkedList(void); LinkedList<T>& operator= (const LinkedList<T>& L); int ListSize(void) const; int ListEmpty(void) const; void Reset(int pos = 0); void Next(void); int EndOfList(void) const; int CurrentPosition(void) const;
void InsertFront(const T& item); void InsertRear(const T& item); void InsertAt(const T& item); void InsertAfter(const T& item); T DeleteFront(void); void DeleteAt(void); T& Data(void); void ClearList(void); }; #endif // LINKEDLIST_CLASS
链表类应用举例(例9-7) 顺序访问线性群体 #include <iostream.h> #include "9_6.h" #include "9_6.cpp" void main(void) { LinkedList<int> Link; int i, key, item; for (i=0;i < 10;i++) { cin>>item; Link.InsertFront(item); }
cout << "List: "; Link.Reset(); while(!Link.EndOfList()) { cout <<Link.Data() << " "; Link.Next(); } cout << endl; cout << "请输入一个需要删除的整数: "; cin >> key; Link.Reset();
while (!Link.EndOfList()) { if(Link.Data() == key) Link.DeleteAt(); Link.Next(); } cout << "List: "; Link.Reset(); while(!Link.EndOfList()) { cout <<Link.Data() << " "; Link.Next(); } cout << endl; }
入栈 出栈 an ┆ a2 a1 栈顶 栈底 特殊的线性群体——栈 栈是只能从一端访问的线性群体,可以访问的这一端称栈顶,另一端称栈底。
入栈 出栈 ④ 参数 当前现场 返回地址 参数 main{} 调fun(参数) 结束 fun(参数) 返回 ③ 当前现场 返回地址 ② ① ⑤ 出栈 ⑧ ⑦ ⑥ 当前现场 返回地址 栈的应用举例——函数调用 特殊的线性群体——栈
b a / t1 + d c t1 * + t1=a/b a/b+c*d a/b+c*d a/b+c*d (a) (b) (c) t2 t1 + t3 t2=c*d t3=t1+t2 a/b+c*d a/b+c*d (d) (e) 栈的应用举例——表达式处理 特殊的线性群体——栈
栈的基本状态 特殊的线性群体——栈 • 栈空 • 栈中没有元素 • 栈满 • 栈中元素个数达到上限 • 一般状态 • 栈中有元素,但未达到栈满状态
入栈 出栈 入栈 出栈 数组下标 数组下标 max n 1 0 max n 1 0 ┆ an ┆ a1 a0 栈顶 栈顶 初始状态(栈空) 一般状态 入栈 出栈 数组下标 amax ┆ an ┆ a1 a0 max n 1 0 栈顶 栈满状态