一、模板

函数的形参可以让函数处理不同数值的数据,模板可以让函数处理不同类型的数据。

原理上,一个模板会生成一些列不同类型形参的同名函数

函数模板

解决问题:如果重载的函数,其解决问题的逻辑是一致的、函数体语句相同,只是处理的数据类型不同,那么写多个相同的函数体,是重复劳动,而且还可能因为代码的冗余造成不一致性。

语法形式:template <模板参数表>

  • 类型参数:class(或typename) 标识符
  • 常量参数:类型说明符 标识符
  • 模板参数:template <参数表> class标识符

1235405842145218560.png

注意

  • 一个函数模板并非自动可以处理所有类型的数据。只有能够进行函数模板中运算的类型,可以作为类型实参
  • 自定义的类,需要重载模板中的运算符,才能作为类型实
  1. template<class out_type,class in_value>
  2. out_type convert(const in_value & t)
  3. {
  4. stringstream stream;
  5. stream<<t;//向流中传值
  6. out_type result;//这里存储转换结果
  7. stream>>result;//向result中写入值
  8. return result;
  9. }
  10. int main()
  11. {
  12. double d;
  13. string salary;
  14. string s="12.56";
  15. d=convert<double>(s);//d等于12.56
  16. salary=convert<string>(9000.0);//salary等于”9000”
  17. return 0;
  18. }

类模板

类模板通过实例化以后的对象被称为模板类

类模板的作用

使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括基本类型的和用户自定义类型)。

类模板的声明

  • 类模板 template <模板参数表> class 类名 {类成员声明};
  • 如果需要在类模板以外定义其成员函数,则要采用以下的形式: template <模板参数表> 类型名 类名<模板参数标识符列表>::函数名(参数表) 【其实就是在正常情况多一个类模板声明】

例9-2 类模板示例

  1. #include <iostream>
  2. #include <cstdlib>
  3. using namespace std;
  4. struct Student {
  5. int id; //学号
  6. float gpa; //平均分
  7. };
  8. template <class T>
  9. class Store {//类模板:实现对任意类型数据进行存取
  10. private:
  11. T item; // item用于存放任意类型的数据
  12. bool haveValue; // haveValue标记item是否已被存入内容
  13. public:
  14. Store();
  15. T &getElem(); //提取数据函数
  16. void putElem(const T &x); //存入数据函数
  17. };
  18. template <class T>
  19. Store<T>::Store(): haveValue(false) { }
  20. template <class T>
  21. T &Store<T>::getElem() {
  22. //如试图提取未初始化的数据,则终止程序
  23. if (!haveValue) {
  24. cout << "No item present!" << endl;
  25. exit(1); //使程序完全退出,返回到操作系统。
  26. }
  27. return item; // 返回item中存放的数据
  28. }
  29. template <class T>
  30. void Store<T>::putElem(const T &x) {
  31. // 将haveValue 置为true,表示item中已存入数值
  32. haveValue = true;
  33. item = x; // 将x值存入item
  34. }
  35. int main() {
  36. Store<int> s1, s2;
  37. s1.putElem(3);
  38. s2.putElem(-7);
  39. cout << s1.getElem() << " " << s2.getElem() << endl;
  40. Student g = { 1000, 23 };
  41. Store<Student> s3;
  42. s3.putElem(g);
  43. cout << "The student id is " << s3.getElem().id << endl;
  44. Store<double> d;
  45. cout << "Retrieving object D... ";
  46. cout << d.getElem() << endl;
  47. //d未初始化,执行函数D.getElement()时导致程序终止
  48. return 0;
  49. }

二、线性群体

群体是指由多个数据元素组成的集合体。群体可以分为两个大类:线性群体非线性群体

  • 线性群体中的元素按位置排列有序,可以区分为第一个元素、第二个元素等。
    • 线性群体中的元素次序与其逻辑位置关系是对应的。在线性群体中,又可按照访问元素的不同方法分为
      • 直接访问 ,比如下标
      • 顺序访问,比如节点
      • 索引访问
  • 非线性群体不用位置顺序来标识元素。

在本章我们只介绍直接访问和顺序访问。

  • 顺序表、链表、栈、队列均是线性结构
  • 而树和图非线性

三、数组

  • 静态数组是具有固定元素个数的群体,其中的元素可以通过下标直接访问。
    • 缺点:大小在编译时就已经确定,在运行时无法修改。
  • 动态数组由一系列位置连续的,任意数量相同类型的元素组成。
    • 优点:其元素个数可在程序运行时改变。
  • vector就是用类模板实现的动态数组。

数组就是通过物理上的连续存放关系(内存地址相邻),维持逻辑上的连续,所以可以直接访问。

动态数组类模板程序

这里手动实现一个动态数组,并且体现指针转化运算符的作用

  1. #ifndef ARRAY_H
  2. #define ARRAY_H
  3. #include <cassert>
  4. template <class T> //数组类模板定义
  5. class Array {
  6. private:
  7. T* list; //用于存放动态分配的数组内存首地址
  8. int size; //数组大小(元素个数)
  9. public:
  10. Array(int sz = 50); //构造函数
  11. Array(const Array<T> &a); //复制构造函数
  12. ~Array(); //析构函数
  13. Array<T> & operator = (const Array<T> &rhs); //重载"=“
  14. T & operator [] (int i); //重载"[]”
  15. const T & operator [] (int i) const; //重载"[]”常函数
  16. operator T * (); //重载到T*类型的转换
  17. operator const T * () const;
  18. int getSize() const; //取数组的大小
  19. void resize(int sz); //修改数组的大小
  20. };
  21. template <class T> Array<T>::Array(int sz) {//构造函数
  22. assert(sz >= 0);//sz为数组大小(元素个数),应当非负
  23. size = sz; // 将元素个数赋值给变量size
  24. list = new T [size]; //动态分配size个T类型的元素空间
  25. }
  26. template <class T> Array<T>::~Array() { //析构函数
  27. delete [] list;
  28. }
  29. template <class T>
  30. Array<T>::Array(const Array<T> &a) { //复制构造函数
  31. size = a.size; //从对象x取得数组大小,并赋值给当前对象的成员
  32. list = new T[size]; // 动态分配n个T类型的元素空间
  33. for (int i = 0; i < size; i++) //从对象X复制数组元素到本对象
  34. list[i] = a.list[i];
  35. }
  36. //续上,重载"="运算符,将对象rhs赋值给本对象。实现对象之间的整体赋值
  37. template <class T>
  38. Array<T> &Array<T>::operator = (const Array<T>& rhs) {
  39. if (&rhs != this) {
  40. //如果本对象中数组大小与rhs不同,则删除数组原有内存,然后重新分配
  41. if (size != rhs.size) {
  42. delete [] list; //删除数组原有内存
  43. size = rhs.size; //设置本对象的数组大小
  44. list = new T[size]; //重新分配size个元素的内存
  45. }
  46. //从对象X复制数组元素到本对象
  47. for (int i = 0; i < size; i++)
  48. list[i] = rhs.list[i];
  49. }
  50. return *this; //返回当前对象的引用
  51. }
  52. //重载下标运算符,实现与普通数组一样通过下标访问元素,具有越界检查功能
  53. template <class T>
  54. T &Array<T>::operator[] (int n) {
  55. assert(n >= 0 && n < size); //检查下标是否越界
  56. return list[n]; //返回下标为n的数组元素
  57. }
  58. template <class T>
  59. const T &Array<T>::operator[] (int n) const {
  60. assert(n >= 0 && n < size); //检查下标是否越界
  61. return list[n]; //返回下标为n的数组元素
  62. //重载指针转换运算符,将Array类的对象名转换为T类型的指针
  63. template <class T>
  64. Array<T>::operator T * () {
  65. return list; //返回当前对象中私有数组的首地址
  66. }
  67. //取当前数组的大小
  68. template <class T>
  69. int Array<T>::getSize() const {
  70. return size;
  71. }
  72. // 将数组大小修改为sz
  73. template <class T>
  74. void Array<T>::resize(int sz) {
  75. assert(sz >= 0); //检查sz是否非负
  76. if (sz == size) //如果指定的大小与原有大小一样,什么也不做
  77. return;
  78. T* newList = new T [sz]; //申请新的数组内存
  79. int n = (sz < size) ? sz : size;//将sz与size中较小的一个赋值给n
  80. //将原有数组中前n个元素复制到新数组中
  81. for (int i = 0; i < n; i++)
  82. newList[i] = list[i];
  83. delete[] list; //删除原数组
  84. list = newList; // 使list指向新数组
  85. size = sz; //更新size
  86. }
  87. #endif //ARRAY_H
  1. #include <iostream>
  2. using namespace std;
  3. void read(int *p, int n) {
  4. for (int i = 0; i < n; i++)
  5. cin >> p[i];
  6. }
  7. int main() {
  8. int a[10];
  9. read(a, 10);
  10. return 0;
  11. }
  12. ----------------------------------------------------
  13. #include "Array.h"
  14. #include <iostream>
  15. using namespace std;
  16. void read(int *p, int n) {
  17. for (int i = 0; i < n; i++)
  18. cin >> p[i];
  19. }
  20. int main() {
  21. Array<int> a(10);
  22. read(a, 10);
  23. return 0;
  24. }

对Array类的应用,求0-n之间的质数:

  1. #include <iostream>
  2. #include <iomanip>
  3. #include "Array.h"
  4. using namespace std;
  5. int main() {
  6. // 用来存放质数的数组,初始状态有10个元素
  7. Array<int> a(10);
  8. int n, count = 0;
  9. cout << "Enter a value >= 2 as upper limit for prime numbers: ";
  10. cin >> n;
  11. for (int i = 2; i <= n; i++) { //检查i是否能被比它小的质数整除
  12. bool isPrime = true;
  13. for (int j = 0; j < count; j++)
  14. //若i被a[j]整除,说明i不是质数
  15. if (i % a[j] == 0) {
  16. isPrime = false; break;
  17. }
  18. if (isPrime) {
  19. if (count == a.getSize())
  20. a.resize(count * 2);
  21. a[count++] = i;
  22. }
  23. }
  24. for (int i = 0; i < count; i++)
  25. cout << setw(8) << a[i];
  26. cout << endl;
  27. return 0;
  28. }

四、链表

顺序访问的线性群体——链表类

  • 链表是一种动态数据结构,可以用来表示顺序访问的线性群体。
  • 链表是由系列 结点 组成的,结点可以在运行时动态生成。
  • 每一个结点包括 数据域 和指向链表中下一个结点的 指针 (即下一个结点的地址)。如果链表每个结点中只有一个指向后继结点的指针,则该链表称为单链表。

1235410999209758720.png

构建链表节点类:

  1. //Node.h
  2. #ifndef NODE_H
  3. #define NODE_H
  4. //类模板的定义
  5. template <class T>
  6. class Node {
  7. private:
  8. Node<T> *next; //指向后继结点的指针
  9. public:
  10. T data; //数据域
  11. Node (const T &data, Node<T> *next = 0); //构造函数
  12. void insertAfter(Node<T> *p); //在本结点之后插入一个同类结点p
  13. Node<T> *deleteAfter(); //删除本结点的后继结点,并返回其地址
  14. Node<T> *nextNode(); //获取后继结点的地址
  15. const Node<T> *nextNode() const; //获取后继结点的地址
  16. };
  17. //类的实现部分
  18. //构造函数,初始化数据和指针成员
  19. template <class T>
  20. Node<T>::Node(const T& data, Node<T> *next = 0 ) : data(data), next(next) { }
  21. //返回后继结点的指针
  22. template <class T>
  23. Node<T> *Node<T>::nextNode() {
  24. return next;
  25. }
  26. //返回后继结点的指针
  27. template <class T>
  28. const Node<T> *Node<T>::nextNode() const {
  29. return next;
  30. }
  31. //在当前结点之后插入一个结点p
  32. template <class T>
  33. void Node<T>::insertAfter(Node<T> *p) {
  34. p->next = next; //p结点指针域指向当前结点的后继结点
  35. next = p; //当前结点的指针域指向p
  36. }
  37. //删除当前结点的后继结点,并返回其地址
  38. template <class T> Node<T> *Node<T>::deleteAfter() {
  39. Node<T> *tempPtr = next;//将欲删除的结点地址存储到tempPtr中
  40. if (next == 0) //如果当前结点没有后继结点,则返回空指针
  41. return 0;
  42. next = tempPtr->next;//使当前结点的指针域指向tempPtr的后继结点
  43. return tempPtr; //返回被删除的结点的地址
  44. }
  45. #endif //NODE_H

链表的基本操作

  • 生成链表
  • 插入结点
  • 查找结点
  • 删除结点
  • 遍历链表
  • 清空链表
  1. //LinkedList.h
  2. #ifndef LINKEDLIST_H
  3. #define LINKEDLIST_H
  4. #include "Node.h"
  5. template <class T>
  6. class LinkedList {
  7. private:
  8. //数据成员:
  9. Node<T> *front, *rear; //表头和表尾指针
  10. Node<T> *prevPtr, *currPtr; //记录表当前遍历位置的指针,由插入和删除操作更新
  11. int size; //表中的元素个数
  12. int position; //当前元素在表中的位置序号。由函数reset使用
  13. //函数成员:
  14. //生成新结点,数据域为item,指针域为ptrNext
  15. Node<T> *newNode(const T &item,Node<T> *ptrNext=NULL);
  16. //释放结点
  17. void freeNode(Node<T> *p);
  18. //将链表L 拷贝到当前表(假设当前表为空)。
  19. //被拷贝构造函数、operator = 调用
  20. void copy(const LinkedList<T>& L);
  21. public:
  22. LinkedList(); //构造函数
  23. LinkedList(const LinkedList<T> &L); //拷贝构造函数
  24. ~LinkedList(); //析构函数
  25. LinkedList<T> & operator = (const LinkedList<T> &L); //重载赋值运算符
  26. int getSize() const; //返回链表中元素个数
  27. bool isEmpty() const; //链表是否为空
  28. void reset(int pos = 0);//初始化游标的位置
  29. void next(); //使游标移动到下一个结点
  30. bool endOfList() const; //游标是否到了链尾
  31. int currentPosition() const; //返回游标当前的位置
  32. void insertFront(const T &item); //在表头插入结点
  33. void insertRear(const T &item); //在表尾添加结点
  34. void insertAt(const T &item); //在当前结点之前插入结点
  35. void insertAfter(const T &item); //在当前结点之后插入结点
  36. T deleteFront(); //删除头结点
  37. void deleteCurrent(); //删除当前结点
  38. T& data(); //返回对当前结点成员数据的引用
  39. const T& data() const; //返回对当前结点成员数据的常引用
  40. //清空链表:释放所有结点的内存空间。被析构函数、operator= 调用
  41. void clear();
  42. };
  43. template <class T> //生成新结点
  44. Node<T> *LinkedList<T>::newNode(const T& item, Node<T>* ptrNext)
  45. {
  46. Node<T> *p;
  47. p = new Node<T>(item, ptrNext);
  48. if (p == NULL)
  49. {
  50. cout << "Memory allocation failure!\n";
  51. exit(1);
  52. }
  53. return p;
  54. }
  55. template <class T>
  56. void LinkedList<T>::freeNode(Node<T> *p) //释放结点
  57. {
  58. delete p;
  59. }
  60. template <class T>
  61. void LinkedList<T>::copy(const LinkedList<T>& L) //链表复制函数
  62. {
  63. Node<T> *p = L.front; //P用来遍历L
  64. int pos;
  65. while (p != NULL) //将L中的每一个元素插入到当前链表最后
  66. {
  67. insertRear(p->data);
  68. p = p->nextNode();
  69. }
  70. if (position == -1) //如果链表空,返回
  71. return;
  72. //在新链表中重新设置prevPtr和currPtr
  73. prevPtr = NULL;
  74. currPtr = front;
  75. for (pos = 0; pos != position; pos++)
  76. {
  77. prevPtr = currPtr;
  78. currPtr = currPtr->nextNode();
  79. }
  80. }
  81. template <class T> //构造一个新链表,将有关指针设置为空,size为0,position为-1
  82. LinkedList<T>::LinkedList() : front(NULL), rear(NULL),
  83. prevPtr(NULL), currPtr(NULL), size(0), position(-1)
  84. {}
  85. template <class T>
  86. LinkedList<T>::LinkedList(const LinkedList<T>& L) //拷贝构造函数
  87. {
  88. front = rear = NULL;
  89. prevPtr = currPtr = NULL;
  90. size = 0;
  91. position = -1;
  92. copy(L);
  93. }
  94. template <class T>
  95. LinkedList<T>::~LinkedList() //析构函数
  96. {
  97. clear();
  98. }
  99. template <class T>
  100. LinkedList<T>& LinkedList<T>::operator=(const LinkedList<T>& L)//重载"="
  101. {
  102. if (this == &L) // 不能将链表赋值给它自身
  103. return *this;
  104. clear();
  105. copy(L);
  106. return *this;
  107. }
  108. template <class T>
  109. int LinkedList<T>::getSize() const //返回链表大小的函数
  110. {
  111. return size;
  112. }
  113. template <class T>
  114. bool LinkedList<T>::isEmpty() const //判断链表为空否
  115. {
  116. return size == 0;
  117. }
  118. template <class T>
  119. void LinkedList<T>::reset(int pos) //将链表当前位置设置为pos
  120. {
  121. int startPos;
  122. if (front == NULL) // 如果链表为空,返回
  123. return;
  124. if (pos < 0 || pos > size - 1) // 如果指定位置不合法,中止程序
  125. {
  126. std::cerr << "Reset: Invalid list position: " << pos << endl;
  127. return;
  128. }
  129. // 设置与遍历链表有关的成员
  130. if (pos == 0) // 如果pos为0,将指针重新设置到表头
  131. {
  132. prevPtr = NULL;
  133. currPtr = front;
  134. position = 0;
  135. }
  136. else // 重新设置 currPtr, prevPtr, 和 position
  137. {
  138. currPtr = front->nextNode();
  139. prevPtr = front;
  140. startPos = 1;
  141. for (position = startPos; position != pos; position++)
  142. {
  143. prevPtr = currPtr;
  144. currPtr = currPtr->nextNode();
  145. }
  146. }
  147. }
  148. template <class T>
  149. void LinkedList<T>::next() //将prevPtr和currPtr向前移动一个结点
  150. {
  151. if (currPtr != NULL)
  152. {
  153. prevPtr = currPtr;
  154. currPtr = currPtr->nextNode();
  155. position++;
  156. }
  157. }
  158. template <class T>
  159. bool LinkedList<T>::endOfList() const // 判断是否已达表尾
  160. {
  161. return currPtr == NULL;
  162. }
  163. template <class T>
  164. int LinkedList<T>::currentPosition() const // 返回当前结点的位置
  165. {
  166. return position;
  167. }
  168. template <class T>
  169. void LinkedList<T>::insertFront(const T& item) // 将item插入在表头
  170. {
  171. if (front != NULL) // 如果链表不空则调用Reset
  172. reset();
  173. insertAt(item); // 在表头插入
  174. }
  175. template <class T>
  176. void LinkedList<T>::insertRear(const T& item) // 在表尾插入结点
  177. {
  178. Node<T> *nNode;
  179. prevPtr = rear;
  180. nNode = newNode(item); // 创建新结点
  181. if (rear == NULL) // 如果表空则插入在表头
  182. front = rear = nNode;
  183. else
  184. {
  185. rear->insertAfter(nNode);
  186. rear = nNode;
  187. }
  188. currPtr = rear;
  189. position = size;
  190. size++;
  191. }
  192. template <class T>
  193. void LinkedList<T>::insertAt(const T& item) // 将item插入在链表当前位置
  194. {
  195. Node<T> *nNode;
  196. if (prevPtr == NULL) // 插入在链表头,包括将结点插入到空表中
  197. {
  198. nNode = newNode(item, front);
  199. front = nNode;
  200. }
  201. else // 插入到链表之中. 将结点置于prevPtr之后
  202. {
  203. nNode = newNode(item);
  204. prevPtr->insertAfter(nNode);
  205. }
  206. if (prevPtr == rear) //正在向空表中插入,或者是插入到非空表的表尾
  207. {
  208. rear = nNode; //更新rear
  209. position = size; //更新position
  210. }
  211. currPtr = nNode; //更新currPtr
  212. size++; //使size增值
  213. }
  214. template <class T>
  215. void LinkedList<T>::insertAfter(const T& item) // 将item 插入到链表当前位置之后
  216. {
  217. Node<T> *p;
  218. p = newNode(item);
  219. if (front == NULL) // 向空表中插入
  220. {
  221. front = currPtr = rear = p;
  222. position = 0;
  223. }
  224. else // 插入到最后一个结点之后
  225. {
  226. if (currPtr == NULL)
  227. currPtr = prevPtr;
  228. currPtr->insertAfter(p);
  229. if (currPtr == rear)
  230. {
  231. rear = p;
  232. position = size;
  233. }
  234. else
  235. position++;
  236. prevPtr = currPtr;
  237. currPtr = p;
  238. }
  239. size++; // 使链表长度增值
  240. }
  241. template <class T>
  242. T LinkedList<T>::deleteFront() // 删除表头结点
  243. {
  244. T item;
  245. reset();
  246. if (front == NULL)
  247. {
  248. cerr << "Invalid deletion!" << endl;
  249. exit(1);
  250. }
  251. item = currPtr->data;
  252. deleteCurrent();
  253. return item;
  254. }
  255. template <class T>
  256. void LinkedList<T>::deleteCurrent() // 删除链表当前位置的结点
  257. {
  258. Node<T> *p;
  259. if (currPtr == NULL) // 如果表空或达到表尾则出错
  260. {
  261. cerr << "Invalid deletion!" << endl;
  262. exit(1);
  263. }
  264. if (prevPtr == NULL) // 删除将发生在表头或链表之中
  265. {
  266. p = front; // 保存头结点地址
  267. front = front->nextNode(); //将其从链表中分离
  268. }
  269. else //分离prevPtr之后的一个内部结点,保存其地址
  270. p = prevPtr->deleteAfter();
  271. if (p == rear) // 如果表尾结点被删除
  272. {
  273. rear = prevPtr; //新的表尾是prevPtr
  274. position--; //position自减
  275. }
  276. currPtr = p->nextNode(); // 使currPtr越过被删除的结点
  277. freeNode(p); // 释放结点,并
  278. size--; //使链表长度自减
  279. }
  280. template <class T>
  281. T& LinkedList<T>::data() //返回一个当前结点数值的引用
  282. {
  283. if (size == 0 || currPtr == NULL) // 如果链表为空或已经完成遍历则出错
  284. {
  285. cerr << "Data: invalid reference!" << endl;
  286. exit(1);
  287. }
  288. return currPtr->data;
  289. }
  290. template <class T>
  291. void LinkedList<T>::clear() //清空链表
  292. {
  293. Node<T> *currPosition, *nextPosition;
  294. currPosition = front;
  295. while (currPosition != NULL)
  296. {
  297. nextPosition = currPosition->nextNode(); //取得下一结点的地址
  298. freeNode(currPosition); //删除当前结点
  299. currPosition = nextPosition; //当前指针移动到下一结点
  300. }
  301. front = rear = NULL;
  302. prevPtr = currPtr = NULL;
  303. size = 0;
  304. position = -1;
  305. }
  306. #endif //LINKEDLIST_H

五、栈

栈是只能从一端访问的线性群体,可以访问的这一端称栈顶,另一端称栈底。栈是一种后进先出的数据结构。

1235412812306714624.png

栈的应用举例:

1235412873690353664.png

栈的基本状态

  • 栈空
  • 栈满
  • 一般状态

栈的基本操作

  • 初始化
  • 入栈
  • 出栈
  • 清空栈
  • 访问栈顶元素
  • 检测栈的状态(满、空)

类模板代码

  1. //Stack.h
  2. #ifndef STACK_H
  3. #define STACK_H
  4. #include <cassert>
  5. template <class T, int SIZE = 50>
  6. class Stack {
  7. private:
  8. T list[SIZE];
  9. int top;
  10. public:
  11. Stack();
  12. void push(const T &item);
  13. T pop();
  14. void clear();
  15. const T &peek() const;
  16. bool isEmpty() const;
  17. bool isFull() const;
  18. };
  19. //模板的实现
  20. template <class T, int SIZE>
  21. Stack<T, SIZE>::Stack() : top(-1) { }
  22. template <class T, int SIZE>
  23. void Stack<T, SIZE>::push(const T &item) {
  24. assert(!isFull());
  25. list[++top] = item;
  26. }
  27. template <class T, int SIZE>
  28. T Stack<T, SIZE>::pop() {
  29. assert(!isEmpty());
  30. return list[top--];
  31. }
  32. template <class T, int SIZE>
  33. const T &Stack<T, SIZE>::peek() const {
  34. assert(!isEmpty());
  35. return list[top]; //返回栈顶元素
  36. }
  37. template <class T, int SIZE>
  38. bool Stack<T, SIZE>::isEmpty() const {
  39. return top == -1;
  40. }
  41. template <class T, int SIZE>
  42. bool Stack<T, SIZE>::isFull() const {
  43. return top == SIZE - 1;
  44. }
  45. template <class T, int SIZE>
  46. void Stack<T, SIZE>::clear() {
  47. top = -1;
  48. }
  49. #endif //STACK_H

六、队列

队列类模板

队列是只能向一端添加元素,从另一端删除元素的线性群体

1236307281902178304.png

队列的基本状态

  • 队空
  • 队满
  • 一般状态

队空

  • 队列中没有元素(以数组容纳的队列为例)

1236307433329135616.png

队满

  • 队列中元素个数达到上限(以数组容纳的队列为例)

1236307549410693120.png

一般状态

  • 队列中有元素,但未达到队满状态(以数组容纳的队列为例)

1236307759008452608.png

循环队列

  • 在想象中将数组弯曲成环形,元素出队时,后继元素不移动,每当队尾达到数组最后一个元素时,便再回到数组开头。

1236307865313087488.png

例9-10 队列类模板

//Queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include <cassert>
//类模板的定义
template <class T, int SIZE = 50>
class Queue {
private:
    int front, rear, count; //队头指针、队尾指针、元素个数
    T list[SIZE];   //队列元素数组
public:
    Queue();          //构造函数,初始化队头指针、队尾指针、元素个数
    void insert(const T &item); //新元素入队
    T remove(); //元素出队
    void clear();   //清空队列
    const T &getFront() const;  //访问队首元素
    //测试队列状态
    int getLength() const;//求队列长度
    bool isEmpty() const;//判断队列空否
    bool isFull() const;//判断队列满否
};
//构造函数,初始化队头指针、队尾指针、元素个数
template <class T, int SIZE>
Queue<T, SIZE>::Queue() : front(0), rear(0), count(0) { }

template <class T, int SIZE>
void Queue<T, SIZE>::insert (const T& item) {//向队尾插入元素
    assert(count != SIZE);
    count++;    //元素个数增1
    list[rear] = item;  //向队尾插入元素
    rear = (rear + 1) % SIZE;   //队尾指针增1,用取余运算实现循环队列
}
template <class T, int SIZE> T Queue<T, SIZE>::remove() {   
    assert(count != 0);
    int temp = front;   //记录下原先的队首指针
    count--;        //元素个数自减
    front = (front + 1) % SIZE;//队首指针增1。取余以实现循环队列
    return list[temp];  //返回首元素值
}
template <class T, int SIZE>
const T &Queue<T, SIZE>::getFront() const { 
    return list[front];
}
template <class T, int SIZE>
int Queue<T, SIZE>::getLength() const { //返回队列元素个数
    return count;
}

template <class T, int SIZE>
bool Queue<T, SIZE>::isEmpty() const {  //测试队空否
    return count == 0;
}
template <class T, int SIZE>
bool Queue<T, SIZE>::isFull() const {   //测试队满否
    return count == SIZE;
}
template <class T, int SIZE>
void Queue<T, SIZE>::clear() {  //清空队列
    count = 0;
    front = 0; 
    rear = 0; 
}
#endif  //QUEUE_H

七、排序

排序是将一个数据元素的任意序列,重新排列成一个按关键字有序的序列。

  • 数据元素: 数据的基本单位。在计算机中通常作为一个整体进行考虑。一个数据元素可由若干数据项组成。
  • 关键字: 数据元素中某个数据项的值,用它可以标识(识别)一个数据元素。
  • 在排序过程中需要完成的两种基本操作
    • 比较两个数的大小
    • 调整元素在序列中的位置

内部排序与外部排序

  • 内部排序:待排序的数据元素存放在计算机内存中进行的排序过程。
  • 外部排序:待排序的数据元素数量很大,以致内存存中一次不能容纳全部数据,在排序过程中尚需对外存进行访问的排序过程。

几种简单的内部排序方法

最坏情况下的时间复杂度都是n*n

  • 插入排序
  • 选择排序
  • 交换排序

插入排序

每一步将一个待排序元素按其关键字值的大小插入到已排序序列的适当位置上,直到待排序元素插入完为止。

1236309007266877440.png

template <class T>
void insertionSort(T a[], int n) {
    int i, j;
    T temp;

    for (int i = 1; i < n; i++) {       
        int j = i;
        T temp = a[i];
        while (j > 0 && temp < a[j - 1]) {
            a[j] = a[j - 1];
            j--;
        }
        a[j] = temp;
    }
}

这个程序其实就是每个数先保存一下,然后比较是不是比前一个数小,是的话让前一个数往后挪一挪(挤占了内存数据)给这个数腾个空间,然后把保存的值放进去。

选择排序

每次从待排序序列中选择一个关键字最小的元素,(当需要按关键字升序排列时),顺序排在已排序序列的最后,直至全部排完。

1236310177897123840.png

template <class T>
void mySwap(T &x, T &y) {
    T temp = x;
    x = y;
    y = temp;
}

template <class T>
void selectionSort(T a[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int leastIndex = i; 
        for (int j = i + 1; j < n; j++)
            if (a[j] < a[leastIndex])
                leastIndex = j;
        mySwap(a[i], a[leastIndex]);
    }
}

交换排序(起泡排序)

两两比较待排序序列中的元素,并交换不满足顺序要求的各对元素,直到全部满足顺序要求为止。

1236311762211573760.png

template <class T>
void mySwap(T &x, T &y) {
    T temp = x;
    x = y;
    y = temp;
}

template <class T>
void bubbleSort(T a[], int n) {
    int i = n – 1;
    while (i > 0) {
      int lastExchangeIndex = 0;
      for (int j = 0; j < i; j++)
        if (a[j + 1] < a[j]) {
         mySwap(a[j], a[j + 1]);
         lastExchangeIndex = j;
        }
       i = lastExchangeIndex;
    }
}

八、查找

顺序查找

从序列的首元素开始,逐个元素与待查找的关键字进行比较,直到找到相等的。若整个序列中没有与待查找关键字相等的元素,就是查找不成功。

template <class T>
int seqSearch(const T list[], int n, const T &key) {
    for(int i = 0; i < n; i++)
        if (list[i] == key)
            return i;            
    return -1;                 
}

二分查找

对于已按关键字排序的序列,经过一次比较,可将序列分割成两部分,然后只在有可能包含待查元素的一部分中继续查找,并根据试探结果继续分割,逐步缩小查找范围,直至找到或找不到为止。

1236321005920718848.png

template <class T>
int binSearch(const T list[], int n, const T &key) {
    int low = 0;
    int high = n - 1;
    while (low <= high) {   
        int mid = (low + high) / 2;
        if (key == list[mid])    
            return mid; 
        else if (key < list[mid])
            high = mid – 1;
        else
            low = mid + 1;
    }
    return -1;
}