上一节学习了C++中的多态性:c++:-5,本节学习C++的函数模版、数据结构以及排序查找操作:

模版

函数模版

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

解决:使用模板

例:求绝对值函数的模板





(1)定义

template <模板参数表>

(2)函数定义

模板参数表的内容:

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

(3)例子

#include <iostream>
using namespace std; //输出数组模版
template <class T> //定义函数模板,class或者typename
void outputArray(const T *array, int count) {
for (int i = 0; i < count; i++)
cout << array[i] << " "; //如果数组元素是类的对象,需要该对象所属类重载了流插入运算符“<<”
cout << endl;
} int main() {
const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 8;
int a [A_COUNT] = { 1, 2, 3, 4, 5, 6, 7, 8 };
double b[B_COUNT] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 };
char c[C_COUNT] = "Welcome"; cout << " a array contains:" << endl;
outputArray(a, A_COUNT);
cout << " b array contains:" << endl;
outputArray(b, B_COUNT);
cout << " c array contains:" << endl;
outputArray(c, C_COUNT);
return 0;
}

注意:

  • 一个函数模板并非自动可以处理所有类型的数据
  • 只有能够进行函数模板中运算的类型,可以作为类型实参
  • 自定义的类,需要重载模板中的运算符,才能作为类型实参

类模版

(1)类模板的作用

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

(2)类模板的声明

类模板 template <模板参数表>
class 类名 {类成员声明};

如果需要在类模板以外定义其成员函数,则要采用以下的形式:

template <模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表)

(3)举例

#include <iostream>
#include <cstdlib>
using namespace std; struct Student {
int id; //学号
float gpa; //平均分
}; template <typename T> //typename和class都可以
class Store { //类模板:实现对任意类型数据进行存取
private:
T item; // item用于存放任意类型的数据
bool haveValue; // haveValue标记item是否已被存入内容
public:
Store();
T &getElem(); //提取数据函数
void putElem(const T &x); //存入数据函数
}; //构造函数的实现,需要模版
template <class T>
Store<T>::Store(): haveValue(false) { } template <class T>
T &Store<T>::getElem() {
//如试图提取未初始化的数据,则终止程序
if (!haveValue) {
cout << "No item present!" << endl;
exit(1); //使程序完全退出,返回到操作系统。
}
return item; // 返回item中存放的数据
} template <class T>
void Store<T>::putElem(const T &x) {
// 将haveValue 置为true,表示item中已存入数值
haveValue = true;
item = x; //将x值存入item
} int main() {
Store<int> s1, s2;
s1.putElem(3);
s2.putElem(-7);
cout << s1.getElem() << " " << s2.getElem() << endl; Student g = { 1000, 23 };
Store<Student> s3;
s3.putElem(g);
cout << "The student id is " << s3.getElem().id << endl; Store<double> d;
cout << "Retrieving object D... ";
d.putElem(1.2);
cout << d.getElem() << endl;
//d若未初始化,执行函数D.getElement()时导致程序终止
return 0;
}
输出:
3 -7
The student id is 1000
Retrieving object D... 1.2

线性群体

  • 群体是指由多个数据元素组成的集合体。群体可以分为两个大类:线性群体非线性群体
  • 线性群体中的元素按位置排列有序,可以区分为第一个元素、第二个元素等。
  • 非线性群体不用位置顺序来标识元素。
  • 线性群体中的元素次序与其逻辑位置关系是对应的。在线性群体中,又可按照访问元素的不同方法分为直接访问顺序访问索引访问
  • 在本章我们只介绍直接访问和顺序访问。

数组类模版

(1)静态数组是具有固定元素个数的群体,其中的元素可以通过下标直接访问。

缺点:大小在编译时就已经确定,在运行时无法修改。

(2)动态数组由一系列位置连续的,任意数量相同类型的元素组成。

优点:其元素个数可在程序运行时改变。

(3)vector就是用类模板实现的动态数组。

(4)举例

#include <iostream>
using namespace std; template <class T> //数组类模板定义
class Array {
private:
T* list; //用于存放动态分配的数组内存首地址
int size; //数组大小(元素个数)
public:
Array(int sz = 50); //构造函数
Array(const Array<T> &a); //复制构造函数
~Array(); //析构函数
Array<T> & operator = (const Array<T> &rhs); //重载"=“
T & operator [] (int i); //重载"[]”
const T & operator [] (int i) const; //重载"[]”常函数
operator T * (); //重载到T*类型的转换
operator const T * () const;
int getSize() const; //取数组的大小
void resize(int sz); //修改数组的大小
}; template <class T> Array<T>::Array(int sz) {//构造函数
assert(sz >= 0);//sz为数组大小(元素个数),应当非负
size = sz; // 将元素个数赋值给变量size
list = new T [size]; //动态分配size个T类型的元素空间
} template <class T> Array<T>::~Array() { //析构函数
delete [] list;
} template <class T>
Array<T>::Array(const Array<T> &a) { //复制构造函数
size = a.size; //从对象x取得数组大小,并赋值给当前对象的成员
list = new T[size]; // 动态分配n个T类型的元素空间
for (int i = 0; i < size; i++) //从对象X复制数组元素到本对象
list[i] = a.list[i];
} //重载"="运算符,将对象rhs赋值给本对象。实现对象之间的整体赋值
template <class T>
Array<T> &Array<T>::operator = (const Array<T>& rhs) {
if (&rhs != this) {
//如果本对象中数组大小与rhs不同,则删除数组原有内存,然后重新分配
if (size != rhs.size) {
delete [] list; //删除数组原有内存
size = rhs.size; //设置本对象的数组大小
list = new T[size]; //重新分配size个元素的内存
}
//从对象X复制数组元素到本对象
for (int i = 0; i < size; i++)
list[i] = rhs.list[i];
}
return *this; //返回当前对象的引用
} //重载下标运算符,实现与普通数组一样通过下标访问元素,具有越界检查功能
template <class T>
T &Array<T>::operator[] (int n) {
assert(n >= 0 && n < size); //检查下标是否越界
return list[n]; //返回下标为n的数组元素
} //重载指针转换运算符,将Array类的对象名转换为T类型的指针
template <class T>
const T &Array<T>::operator[] (int n) const {
assert(n >= 0 && n < size); //检查下标是否越界
return list[n]; //返回下标为n的数组元素
} template<class T>
Array<T>::operator T *()
{
return list; //返回当前对象中私有数组的首地址
} //取当前数组的大小
template<class T>
int Array<T>::getSize() const {
return size;
} // 将数组大小修改为sz
template <class T>
void Array<T>::resize(int sz) {
assert(sz >= 0); //检查sz是否非负
if (sz == size) //如果指定的大小与原有大小一样,什么也不做
return;
T* newList = new T [sz]; //申请新的数组内存
int n = (sz < size) ? sz : size;//将sz与size中较小的一个赋值给n
//将原有数组中前n个元素复制到新数组中
for (int i = 0; i < n; i++)
newList[i] = list[i];
delete[] list; //删除原数组
list = newList; // 使list指向新数组
size = sz; //更新size
} void read(int *p, int n) {
for (int i = 0; i < n; i++)
cin >> p[i];
} int main() {
Array<int> a(10);
read(a, 10);
return 0;
}

为什么有的函数返回引用?

如果一个函数的返回值是一个对象的值,就是右值,不能成为左值。

如果返回值为引用。由于引用是对象的别名,通过引用可以改变对象的值,因此是左值。

(5)求范围2~N中的质数,N在程序运行时由键盘输入。

#include <iostream>
#include <iomanip>
using namespace std; template <class T> //数组类模板定义
class Array {
private:
T* list; //用于存放动态分配的数组内存首地址
int size; //数组大小(元素个数)
public:
Array(int sz = 50); //构造函数
Array(const Array<T> &a); //复制构造函数
~Array(); //析构函数
Array<T> & operator = (const Array<T> &rhs); //重载"=“
T & operator [] (int i); //重载"[]”
const T & operator [] (int i) const; //重载"[]”常函数
operator T * (); //重载到T*类型的转换
operator const T * () const;
int getSize() const; //取数组的大小
void resize(int sz); //修改数组的大小
}; template <class T> Array<T>::Array(int sz) {//构造函数
assert(sz >= 0);//sz为数组大小(元素个数),应当非负
size = sz; // 将元素个数赋值给变量size
list = new T [size]; //动态分配size个T类型的元素空间
} template <class T> Array<T>::~Array() { //析构函数
delete [] list;
} template <class T>
Array<T>::Array(const Array<T> &a) { //复制构造函数
size = a.size; //从对象x取得数组大小,并赋值给当前对象的成员
list = new T[size]; // 动态分配n个T类型的元素空间
for (int i = 0; i < size; i++) //从对象X复制数组元素到本对象
list[i] = a.list[i];
} //重载"="运算符,将对象rhs赋值给本对象。实现对象之间的整体赋值
template <class T>
Array<T> &Array<T>::operator = (const Array<T>& rhs) {
if (&rhs != this) {
//如果本对象中数组大小与rhs不同,则删除数组原有内存,然后重新分配
if (size != rhs.size) {
delete [] list; //删除数组原有内存
size = rhs.size; //设置本对象的数组大小
list = new T[size]; //重新分配size个元素的内存
}
//从对象X复制数组元素到本对象
for (int i = 0; i < size; i++)
list[i] = rhs.list[i];
}
return *this; //返回当前对象的引用
} //重载下标运算符,实现与普通数组一样通过下标访问元素,具有越界检查功能
template <class T>
T &Array<T>::operator[] (int n) {
assert(n >= 0 && n < size); //检查下标是否越界
return list[n]; //返回下标为n的数组元素
} //重载指针转换运算符,将Array类的对象名转换为T类型的指针
template <class T>
const T &Array<T>::operator[] (int n) const {
assert(n >= 0 && n < size); //检查下标是否越界
return list[n]; //返回下标为n的数组元素
} template<class T>
Array<T>::operator T *()
{
return list; //返回当前对象中私有数组的首地址
} //取当前数组的大小
template<class T>
int Array<T>::getSize() const {
return size;
} // 将数组大小修改为sz
template <class T>
void Array<T>::resize(int sz) {
assert(sz >= 0); //检查sz是否非负
if (sz == size) //如果指定的大小与原有大小一样,什么也不做
return;
T* newList = new T [sz]; //申请新的数组内存
int n = (sz < size) ? sz : size;//将sz与size中较小的一个赋值给n
//将原有数组中前n个元素复制到新数组中
for (int i = 0; i < n; i++)
newList[i] = list[i];
delete[] list; //删除原数组
list = newList; // 使list指向新数组
size = sz; //更新size
} void read(int *p, int n) {
for (int i = 0; i < n; i++)
cin >> p[i];
} int main() {
// 用来存放质数的数组,初始状态有10个元素
Array<int> a(10);
int n, count = 0;
cout << "Enter a value >= 2 as upper limit for prime numbers: ";
cin >> n;
for (int i = 2; i <= n; i++) { //检查i是否能被比它小的质数整除
bool isPrime = true;
for (int j = 0; j < count; j++)
//若i被a[j]整除,说明i不是质数
if (i % a[j] == 0) {
isPrime = false; break;
}
if (isPrime) {
if (count == a.getSize())
a.resize(count * 2);
a[count++] = i;
}
}
for (int i = 0; i < count; i++)
cout << setw(8) << a[i];
cout << endl;
return 0;
}
输出:
Enter a value >= 2 as upper limit for prime numbers: 23
2 3 5 7 11 13 17 19 23

链表类

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

结点类模板

template <class T>
void Node<T>::insertAfter(Node<T> *p) {
//p节点指针域指向当前节点的后继节点
p->next = next;
next = p; //当前节点的指针域指向p
}

结点之后插入一个结点

template <class T>
void Node<T>::insertAfter(Node<T> \*p) {
//p节点指针域指向当前节点的后继节点
p->next = next;
next = p; //当前节点的指针域指向p
}

删除结点之后的结点

Node<T> *Node<T>::deleteAfter(void) {
Node<T> *tempPtr = next;
if (next == 0)
return 0;
next = tempPtr->next;
return tempPtr;
}

结点类模扳

#ifndef NODE_H
#define NODE_H
//类模板的定义
template <class T>
class Node {
private:
Node<T> *next; //指向后继结点的指针
public:
T data; //数据域
Node (const T &data, Node<T> *next = 0); //构造函数
void insertAfter(Node<T> *p); //在本结点之后插入一个同类结点p
Node<T> *deleteAfter(); //删除本结点的后继结点,并返回其地址
Node<T> *nextNode(); //获取后继结点的地址
const Node<T> *nextNode() const; //获取后继结点的地址
}; //类的实现部分
//构造函数,初始化数据和指针成员
template <class T>
Node<T>::Node(const T& data, Node<T> *next = 0 ) : data(data), next(next) { }
//返回后继结点的指针
template <class T>
Node<T> *Node<T>::nextNode() {
return next;
}
//返回后继结点的指针
template <class T>
const Node<T> *Node<T>::nextNode() const {
return next;
}
//在当前结点之后插入一个结点p
template <class T>
void Node<T>::insertAfter(Node<T> *p) {
p->next = next; //p结点指针域指向当前结点的后继结点
next = p; //当前结点的指针域指向p
}
//删除当前结点的后继结点,并返回其地址
template <class T> Node<T> *Node<T>::deleteAfter() {
Node<T> *tempPtr = next;//将欲删除的结点地址存储到tempPtr中
if (next == 0) //如果当前结点没有后继结点,则返回空指针
return 0;
next = tempPtr->next;//使当前结点的指针域指向tempPtr的后继结点
return tempPtr; //返回被删除的结点的地址
}
#endif //NODE_H

操作

  • 生成链表
  • 插入结点
  • 查找结点
  • 删除结点
  • 遍历链表
  • 清空链表

#include "iostream"
using namespace std; //结点类模板的定义
template <class T>
class Node {
private:
Node<T> *next; //指向后继结点的指针
public:
T data; //数据域
Node (const T &data, Node<T> *next=NULL); //构造函数
void insertAfter(Node<T> *p); //在本结点之后插入一个同类结点p
Node<T> *deleteAfter(); //删除本结点的后继结点,并返回其地址
Node<T> *nextNode(); //获取后继结点的地址
const Node<T> *nextNode() const; //获取后继结点的地址
}; //结点类的实现部分
//构造函数,初始化数据和指针成员
template <class T>
Node<T>::Node(const T &data, Node<T> *next) : data(data), next(next) { } //返回后继结点的指针
template <class T>
Node<T> *Node<T>::nextNode() {
return next;
} //返回后继结点的指针
template <class T>
const Node<T> *Node<T>::nextNode() const {
return next;
} //在当前结点之后插入一个结点p
template <class T>
void Node<T>::insertAfter(Node<T> *p) {
p->next = next; //p结点指针域指向当前结点的后继结点
next = p; //当前结点的指针域指向p
} //删除当前结点的后继结点,并返回其地址
template <class T> Node<T> *Node<T>::deleteAfter() {
Node<T> *tempPtr = next;//将欲删除的结点地址存储到tempPtr中
if (next == NULL) //如果当前结点没有后继结点,则返回空指针
return 0;
next = tempPtr->next;//使当前结点的指针域指向tempPtr的后继结点
return tempPtr; //返回被删除的结点的地址
} //链表类模版
template <class T>
class LinkedList {
private:
//数据成员:
Node<T> *front, *rear; //表头和表尾指针
Node<T> *prevPtr, *currPtr; //记录表当前遍历位置的指针,由插入和删除操作更新
int size; //表中的元素个数
int position; //当前元素在表中的位置序号。由函数reset使用
//函数成员:
//生成新结点,数据域为item,指针域为ptrNext
Node<T> *newNode(const T &item,Node<T> *ptrNext=NULL);
//释放结点
void freeNode(Node<T> *p);
//将链表L 拷贝到当前表(假设当前表为空)。
//被拷贝构造函数、operator = 调用
void copy(const LinkedList<T>& L);
public:
LinkedList(); //构造函数
LinkedList(const LinkedList<T> &L); //拷贝构造函数
~LinkedList(); //析构函数
LinkedList<T> & operator = (const LinkedList<T> &L); //重载赋值运算符
int getSize() const; //返回链表中元素个数
bool isEmpty() const; //链表是否为空
void reset(int pos = 0);//初始化游标的位置
void next(); //使游标移动到下一个结点
bool endOfList() const; //游标是否到了链尾
int currentPosition() const; //返回游标当前的位置
void insertFront(const T &item); //在表头插入结点
void insertRear(const T &item); //在表尾添加结点
void insertAt(const T &item); //在当前结点之前插入结点
void insertAfter(const T &item); //在当前结点之后插入结点
T deleteFront(); //删除头结点
void deleteCurrent(); //删除当前结点
T& data(); //返回对当前结点成员数据的引用
const T& data() const; //返回对当前结点成员数据的常引用
//清空链表:释放所有结点的内存空间。被析构函数、operator= 调用
void clear();
}; //链表类实现
template <class T> //生成新结点
Node<T> *LinkedList<T>::newNode(const T& item, Node<T>* ptrNext)
{
Node<T> *p;
p = new Node<T>(item, ptrNext);
if (p == NULL)
{
cout << "Memory allocation failure!\n";
exit(1);
}
return p;
} template <class T>
void LinkedList<T>::freeNode(Node<T> *p) //释放结点
{
delete p;
} template <class T>
void LinkedList<T>::copy(const LinkedList<T>& L) //链表复制函数
{
Node<T> *p = L.front; //P用来遍历L
int pos;
while (p != NULL) //将L中的每一个元素插入到当前链表最后
{
insertRear(p->data);
p = p->nextNode();
}
if (position == -1) //如果链表空,返回
return;
//在新链表中重新设置prevPtr和currPtr
prevPtr = NULL;
currPtr = front;
for (pos = 0; pos != position; pos++)
{
prevPtr = currPtr;
currPtr = currPtr->nextNode();
}
} template <class T> //构造一个新链表,将有关指针设置为空,size为0,position为-1
LinkedList<T>::LinkedList() : front(NULL), rear(NULL),prevPtr(NULL), currPtr(NULL), size(0), position(-1)
{} template <class T>
LinkedList<T>::LinkedList(const LinkedList<T>& L) //拷贝构造函数
{
front = rear = NULL;
prevPtr = currPtr = NULL;
size = 0;
position = -1;
copy(L);
} template <class T>
LinkedList<T>::~LinkedList() //析构函数
{
clear();
} template <class T>
LinkedList<T>& LinkedList<T>::operator=(const LinkedList<T>& L)//重载"="
{
if (this == &L) // 不能将链表赋值给它自身
return *this;
clear();
copy(L);
return *this;
} template <class T>
int LinkedList<T>::getSize() const //返回链表大小的函数
{
return size;
} template <class T>
bool LinkedList<T>::isEmpty() const //判断链表为空否
{
return size == 0;
} template <class T>
void LinkedList<T>::reset(int pos) //将链表当前位置设置为pos
{
int startPos;
if (front == NULL) // 如果链表为空,返回
return;
if (pos < 0 || pos > size - 1) // 如果指定位置不合法,中止程序
{
std::cerr << "Reset: Invalid list position: " << pos << endl;
return;
}
// 设置与遍历链表有关的成员
if (pos == 0) // 如果pos为0,将指针重新设置到表头
{
prevPtr = NULL;
currPtr = front;
position = 0;
}
else // 重新设置 currPtr, prevPtr, 和 position
{
currPtr = front->nextNode();
prevPtr = front;
startPos = 1;
for (position = startPos; position != pos; position++)
{
prevPtr = currPtr;
currPtr = currPtr->nextNode();
}
}
} template <class T>
void LinkedList<T>::next() //将prevPtr和currPtr向前移动一个结点
{
if (currPtr != NULL)
{
prevPtr = currPtr;
currPtr = currPtr->nextNode();
position++;
}
} template <class T>
bool LinkedList<T>::endOfList() const // 判断是否已达表尾
{
return currPtr == NULL;
} template <class T>
int LinkedList<T>::currentPosition() const // 返回当前结点的位置
{
return position;
} template <class T>
void LinkedList<T>::insertFront(const T& item) // 将item插入在表头
{
if (front != NULL) // 如果链表不空则调用Reset
reset();
insertAt(item); // 在表头插入
} template <class T>
void LinkedList<T>::insertRear(const T& item) // 在表尾插入结点
{
Node<T> *nNode;
prevPtr = rear;
nNode = newNode(item); // 创建新结点
if (rear == NULL) // 如果表空则插入在表头
front = rear = nNode;
else
{
rear->insertAfter(nNode);
rear = nNode;
}
currPtr = rear;
position = size;
size++;
} template <class T>
void LinkedList<T>::insertAt(const T& item) // 将item插入在链表当前位置
{
Node<T> *nNode;
if (prevPtr == NULL) // 插入在链表头,包括将结点插入到空表中
{
nNode = newNode(item, front);
front = nNode;
}
else // 插入到链表之中. 将结点置于prevPtr之后
{
nNode = newNode(item);
prevPtr->insertAfter(nNode);
}
if (prevPtr == rear) //正在向空表中插入,或者是插入到非空表的表尾
{
rear = nNode; //更新rear
position = size; //更新position
}
currPtr = nNode; //更新currPtr
size++; //使size增值
} template <class T>
void LinkedList<T>::insertAfter(const T& item) // 将item 插入到链表当前位置之后
{
Node<T> *p;
p = newNode(item);
if (front == NULL) // 向空表中插入
{
front = currPtr = rear = p;
position = 0;
}
else // 插入到最后一个结点之后
{
if (currPtr == NULL)
currPtr = prevPtr;
currPtr->insertAfter(p);
if (currPtr == rear)
{
rear = p;
position = size;
}
else
position++;
prevPtr = currPtr;
currPtr = p;
}
size++; // 使链表长度增值
} template <class T>
T LinkedList<T>::deleteFront() // 删除表头结点
{
T item;
reset();
if (front == NULL)
{
cerr << "Invalid deletion!" << endl;
exit(1);
}
item = currPtr->data;
deleteCurrent();
return item;
} template <class T>
void LinkedList<T>::deleteCurrent() // 删除链表当前位置的结点
{
Node<T> *p;
if (currPtr == NULL) // 如果表空或达到表尾则出错
{
cerr << "Invalid deletion!" << endl;
exit(1);
}
if (prevPtr == NULL) // 删除将发生在表头或链表之中
{
p = front; // 保存头结点地址
front = front->nextNode(); //将其从链表中分离
}
else //分离prevPtr之后的一个内部结点,保存其地址
p = prevPtr->deleteAfter(); if (p == rear) // 如果表尾结点被删除
{
rear = prevPtr; //新的表尾是prevPtr
position--; //position自减
}
currPtr = p->nextNode(); // 使currPtr越过被删除的结点
freeNode(p); // 释放结点,并
size--; //使链表长度自减
} template <class T>
T& LinkedList<T>::data() //返回一个当前结点数值的引用
{
if (size == 0 || currPtr == NULL) // 如果链表为空或已经完成遍历则出错
{
cerr << "Data: invalid reference!" << endl;
exit(1);
}
return currPtr->data;
} template <class T>
void LinkedList<T>::clear() //清空链表
{
Node<T> *currPosition, *nextPosition;
currPosition = front;
while (currPosition != NULL)
{
nextPosition = currPosition->nextNode(); //取得下一结点的地址
freeNode(currPosition); //删除当前结点
currPosition = nextPosition; //当前指针移动到下一结点
}
front = rear = NULL;
prevPtr = currPtr = NULL;
size = 0;
position = -1;
} int main()
{
LinkedList<int> list;
for(int i=0;i<5;i++)
{
int item;
cin >> item;
list.insertFront(item);
}
cout << "list:";
list.reset();
while (!list.endOfList())
{
cout << list.data() <<" ";
list.next();
}
cout <<endl;
int key;
cout <<"请输入要查找的数:";
cin >>key;
list.reset();
while (!list.endOfList())
{
if(list.data()==key)
list.deleteCurrent();
list.next();
}
cout <<"list:";
list.reset();
while(!list.endOfList())
{
cout << list.data();
}
cout <<endl;
return 0;
}

栈类模版

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



栈的应用:处理表达式

栈的状态

(1)栈空

栈中没有元素(以数组容纳的栈为例)



(2)栈满

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



(3)一般状态

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

栈的操作

  • 初始化
  • 入栈
  • 出栈
  • 清空栈
  • 访问栈顶元素
  • 检测栈的状态(满、空)
template <class T, int SIZE = 50>
class Stack {
private:
T list[SIZE];
int top;
public:
Stack();
void push(const T &item);//入栈
T pop();//出栈
void clear();//清空栈
const T &peek() const;//打印栈顶元素
bool isEmpty() const;//判断是否为空
bool isFull() const;//判断是否为满
}; //模板的实现
template <class T, int SIZE>
Stack<T, SIZE>::Stack() : top(-1) { }//-1初始化为指针 template <class T, int SIZE>
void Stack<T, SIZE>::push(const T &item) {
assert(!isFull());
list[++top] = item;//入栈
} template <class T, int SIZE>
T Stack<T, SIZE>::pop() {
assert(!isEmpty());
return list[top--];//出栈(先出后减)
} template <class T, int SIZE>
const T &Stack<T, SIZE>::peek() const {
assert(!isEmpty());
return list[top]; //返回栈顶元素(指针所指当前元素)
} template <class T, int SIZE>
bool Stack<T, SIZE>::isEmpty() const {
return top == -1;
} template <class T, int SIZE>
bool Stack<T, SIZE>::isFull() const {
return top == SIZE - 1;
} template <class T, int SIZE>
void Stack<T, SIZE>::clear() {
top = -1;
}

栈的应用

一个简单的整数计算器

实现一个简单的整数计算器,能够进行加、减、乘、除和乘方运算。使用时算式采用后缀输入法,每个操作数、操作符之间都以空白符分隔。例如,若要计算"3+5"则输入"3 5 +"。乘方运算符用"^"表示。每次运算在前次结果基础上进行,若要将前次运算结果清除,可键入"c"。当键入"q"时程序结束。

#include <iostream>
#include <sstream>
#include <cmath>
using namespace std; //栈类的定义
template <class T, int SIZE = 50>
class Stack {
private:
T list[SIZE];
int top;
public:
Stack();
void push(const T &item);//入栈
T pop();//出栈
void clear();//清空栈
const T &peek() const;//打印栈顶元素
bool isEmpty() const;//判断是否为空
bool isFull() const;//判断是否为满
}; //模板的实现
template <class T, int SIZE>
Stack<T, SIZE>::Stack() : top(-1) { }//-1初始化为指针 template <class T, int SIZE>
void Stack<T, SIZE>::push(const T &item) {
assert(!isFull());
list[++top] = item;//入栈
} template <class T, int SIZE>
T Stack<T, SIZE>::pop() {
assert(!isEmpty());
return list[top--];//出栈(先出后减)
} template <class T, int SIZE>
const T &Stack<T, SIZE>::peek() const {
assert(!isEmpty());
return list[top]; //返回栈顶元素(指针所指当前元素)
} template <class T, int SIZE>
bool Stack<T, SIZE>::isEmpty() const {
return top == -1;
} template <class T, int SIZE>
bool Stack<T, SIZE>::isFull() const {
return top == SIZE - 1;
} template <class T, int SIZE>
void Stack<T, SIZE>::clear() {
top = -1;
} //计算器类
class Calculator {
private:
Stack<double> s; // 操作数栈
void enter(double num); //将操作数num压入栈
//连续将两个操作数弹出栈,放在opnd1和opnd2中
bool getTwoOperands(double &opnd1, double &opnd2);
void compute(char op); //执行由操作符op指定的运算
public:
void run(); //运行计算器程序
void clear(); //清空操作数栈
}; //工具函数,用于将字符串转换为实数
inline double stringToDouble(const string &str) {
istringstream stream(str); //字符串输入流
double result;
stream >> result;
return result;
} void Calculator::enter(double num) { //将操作数num压入栈
s.push(num);
} bool Calculator::getTwoOperands(double &opnd1, double &opnd2) {
if (s.isEmpty()) { //检查栈是否空
cerr << "Missing operand!" << endl;
return false;
}
opnd1 = s.pop(); //将右操作数弹出栈
if (s.isEmpty()) { //检查栈是否空
cerr << "Missing operand!" << endl;
return false;
}
opnd2 = s.pop(); //将左操作数弹出栈
return true;
} void Calculator::compute(char op) { //执行运算
double operand1, operand2;
bool result = getTwoOperands(operand1, operand2);
if (result) { //如果成功,执行运算并将运算结果压入栈
switch(op) {
case '+': s.push(operand2 + operand1); break;
case '-': s.push(operand2 - operand1); break;
case '*': s.push(operand2 * operand1); break;
case '/': if (operand1 == 0) { //检查除数是否为0
cerr << "Divided by 0!" << endl;
s.clear(); //除数为0时清空栈
} else
s.push(operand2 / operand1);
break;
case '^': s.push(pow(operand2, operand1)); break;
default: cerr << "Unrecognized operator!" << endl;
break;
}
cout << "= " << s.peek() << " "; //输出本次运算结果
} else
s.clear(); //操作数不够,清空栈
} void Calculator::run() { //读入并处理后缀表达式
string str;
while (cin >> str, str != "q") {
switch(str[0]) {
case 'c': s.clear(); break;
case '-': //遇'-'需判断是减号还是负号
if (str.size() > 1)
enter(stringToDouble(str));
else
compute(str[0]);
break;
case '+': //遇到其它操作符时
case '*':
case '/':
case '^':
compute(str[0]); break;
default: //若读入的是操作数,转换为整型后压入栈
enter(stringToDouble(str)); break;
}
}
}
void Calculator::clear() { //清空操作数栈
s.clear();
} int main() {
Calculator c;
c.run();
return 0;
}
输出:
3 5 ^
= 243
2 5 +
= 7
8 2 -
= 6 q

队列模版

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

基本状态

(1)队空

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



(2)队满

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



(3)一般状态

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

循环队列

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

#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);//如果assert的条件返回错误,则终止程序执行,相当于if
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;
}

排序

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

(1)数据元素: 数据的基本单位。在计算机中通常作为一个整体进行考虑。一个数据元素可由若干数据项组成。

(2)关键字: 数据元素中某个数据项的值,用它可以标识(识别)一个数据元素。

(3)在排序过程中需要完成的两种基本操作

  • 比较两个数的大小
  • 调整元素在序列中的位置

(4)内部排序与外部排序

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

下面给出几种简单的内部排序方法

插入排序

基本思想

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

例如:

程序

#include "iostream"
using namespace std; 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;
}
}
int main()
{
int A[]={8,5,2,4,3};
insertionSort(A,5);
for(int & i:A)
{
cout <<i<<" ";
}
return 0;
}

选择排序

基本思想

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

程序

#include "iostream"
using namespace std; 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]);
}
}
int main()
{
int A[]={8,5,2,4,3};
selectionSort(A,5);
for(int & i:A)
{
cout <<i<<" ";
}
return 0;
}

交换排序

基本思想

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

最简单的交换排序方法——起泡排序

起泡排序举例:对整数序列 8 5 2 4 3 按升序排序

  • 对具有n个元素的序列按升序进行起泡排序的步骤:

    (1)首先将第一个元素与第二个元素进行比较,若为逆序,则将两元素交换。然后比较第二、第三个元素,依次类推,直到第n-1和第n个元素进行了比较和交换。此过程称为第一趟起泡排序。经过第一趟,最大的元素便被交换到第n个位置。

    (2)对前n-1个元素进行第二趟起泡排序,将其中最大元素交换到第n-1个位置。

    (3)如此继续,直到某一趟排序未发生任何交换时,排序完毕。对n个元素的序列,起泡排序最多需要进行n-1趟。

程序

#include "iostream"
using namespace std; 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;
}
}
int main()
{
int A[]={8,5,2,4,3};
bubbleSort(A,5);
for(int & i:A)
{
cout <<i<<" ";
}
return 0;
}

查找

顺序查找

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

程序

#include "iostream"
using namespace std; 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;
} int main()
{
int A[]={8,5,2,4,3};
cout <<seqSearch(A,5,2);
return 0;
}

折半查找

也就是二分查找

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

举例

(1)用折半查找法,在下列序列中查找值为21的元素:



(2)用折半查找法,在下列序列中查找值为20的元素:

程序

实现节点类和操作

#include <iostream>
using namespace std; template <class T>
class Node {
private:
Node<T>* next;
public:
T data;
Node() {data = 0; next = NULL;}
// Node(const Node<T>& n) {data = n.data; next = n.next;}
Node(const T& data, Node<T>* next = 0);
void insertAfter(Node<T>* p);
Node<T>* deleteAfter();
Node<T>* nextNode();
const Node<T>* nextNode() const;
}; template <class T>
Node<T>::Node(const T &data, Node<T> *next): data(data), next(next) {} template <class T>
Node<T>* Node<T>::nextNode() {
return next;
} template <class T>
const Node<T>* Node<T>::nextNode() const {
return next;
} template <class T>
void Node<T>::insertAfter(Node<T> *p) {
p->next = next;
next = p;
} template <class T>
Node<T>* Node<T>::deleteAfter() {
Node<T>* tempPtr = next;
if (next == 0)
return 0;
next = tempPtr->next;
return tempPtr;
} //main.cpp
int main()
{
int a[10];
Node<int> n[10];
cout << "输入10个整数:" << endl;
for (int i = 0; i < 10; i ++) {
cin >> a[i];
}
for (int i = 0; i < 9; i ++) {
n[i].data = a[i];
n[i].insertAfter(&n[i+1]);
}
n[9].data = a[9];
Node<int>* np = &n[0];
while (np != NULL) {
cout << np->data << ' ';
np = np->nextNode();
}
cout << endl;
int f;
cout << "请输入要查找的数:";
cin >> f;
Node<int> p(0, &n[0]);
np = &p;
while (np->nextNode() != NULL) {
while (np->nextNode()->data == f)
np->deleteAfter();
np = np->nextNode();
}
cout << "删除后的链表:" << endl;
np = &n[0];
while (np != NULL) {
cout << np->data << ' ';
np = np->nextNode();
}
np = &p;
while (np->nextNode() != NULL)
np->deleteAfter();
cout << endl;
return 0;
}
输出:
输入10个整数:
1 2 4 2 3 5 5 8 6 9
1 2 4 2 3 5 5 8 6 9
请输入要查找的数:6
删除后的链表:
1 2 4 2 3 5 5 8 9

链表合并

#include <iostream>
using namespace std; template <class T>
class Node {
private:
T data;
public:
Node* next;
Node();
Node(const T& data, Node<T>* nt = 0);
// Node(T data, Node<T>* n = NULL);
T& getData();
}; template <class T>
Node<T>::Node() {
data = 0;
next = NULL;
} template <class T>
Node<T>::Node(const T& d, Node<T>* nt) {
data = d;
next = nt;
} template <class T>
T& Node<T>::getData() {
return data;
} /*
* 在LinkedList的设计中,采用了附加指针front和rear,即链表的结构为front->a1->a2->...->rear
* 只有在析构函数中才删除这两个指针,其余的时间这两个指针都是存在的,其中的数据始终为0,不存储用户数据
*/
template <class T>
class LinkedList {
private:
Node<T> *front, *rear; //表头和表尾指针
Node<T> *prevPtr, *currPtr; //记录表当前遍历位置的指针,由插入和删除操作更新
int size; //表中元素个数
int position; //当前元素在表中的位置序号。由函数reset使用 Node<T>* newNode(const T& item, Node<T> *ptrNext = NULL); //生成新节点,数据与为item,指针域为prtNext
void freeNode(Node<T> *p); //释放节点 void copy(const LinkedList<T>& L); //将链表L复制到当前表(假设当前表为空),被复制构造函数和operator=调用 public:
LinkedList(); //构造函数
LinkedList(const LinkedList<T>& L); //复制构造函数
~LinkedList(); //析构函数
LinkedList<T>& operator = (const LinkedList<T>& L); //重载赋值运算符 int getSize() const; //返回链表中元素个数
bool isEmpty() const; //链表是否为空 void reset(int pos = 0); //初始化游标的位置
void next(); //使游标移动到下一个节点
bool endOfList() const; //游标是否移动到链尾
int currentPosition() const; //返回游标当前的位置 void insertFront(const T& item); //在表头插入节点
void insertRear(const T& item); //在表尾插入节点
void insertAt(const T& item); //在当前节点之前插入节点
void insertAfter(const T& item); //在当前节点之后插入节点 T deleteFront(); //删除头节点
void deleteCurrent(); //删除当前节点 T& data(); //返回对当前节点成员数据的引用
const T& data() const; //返回对当前节点成员数据的常引用 void clear(); //清空链表:释放所有节点的内存空间,被析构函数和operator=使用
}; //生成新节点,数据与为item,指针域为prtNext
template <class T>
Node<T>* LinkedList<T>::newNode(const T& item, Node<T> *ptrNext) {
Node<T> *n = new Node<T>(item, ptrNext);
return n;
} //释放节点
template <class T>
void LinkedList<T>::freeNode(Node<T> *p) {
Node<T>* temp = front;
while (temp->next != p) {
temp = temp->next;
if (temp == currPtr)
position ++;
}
temp->next = p->next;
if (currPtr == p)
currPtr = currPtr->next;
if (prevPtr == p) {
prevPtr = prevPtr->next;
currPtr = currPtr->next;
}
delete p;
size --;
position --;
} //将链表L复制到当前表(假设当前表为空),被复制构造函数和operator=调用
template <class T>
void LinkedList<T>::copy(const LinkedList<T>& L) {
Node<T> *temp = L.front, *ptr = front;
while (temp != L.rear) {
ptr->next = new Node<T>(temp->getData, NULL);
ptr = ptr->next;
temp = temp->next;
}
ptr->next = rear;
size = L.getSize();
position = L.currentPosition();
} //构造函数
template <class T>
LinkedList<T>::LinkedList() {
front = new Node<T>();
rear = new Node<T>();
front->next = rear;
currPtr = rear;
prevPtr = front;
size = 0; //不计算front和rear
position = 0; //在front下一个元素视为0
} //复制构造函数
template <class T>
LinkedList<T>::LinkedList(const LinkedList<T>& L) {
clear();
copy(L);
} //析构函数
template <class T>
LinkedList<T>::~LinkedList() {
LinkedList::clear();
delete front;
delete rear;
} //重载赋值运算符
template <class T>
LinkedList<T>& LinkedList<T>::operator = (const LinkedList<T>& L) {
clear();
copy(L);
} //返回链表中元素个数
template <class T>
int LinkedList<T>::getSize() const {
return size;
} //链表是否为空
template <class T>
bool LinkedList<T>::isEmpty() const {
return (size == 0);
} //初始化游标的位置
template <class T>
void LinkedList<T>::reset(int pos) { //初始化游标位置
if (pos > size) {
cout << "越界,无法访问" << endl;
return;
}
int i = 0;
prevPtr = front;
currPtr = front->next;
while (i < pos) {
if (currPtr == rear) {
cout << "越界,无法访问" << endl;
return;
}
i ++;
currPtr = currPtr->next;
prevPtr = prevPtr->next;
}
position = pos;
} //使游标移动到下一个节点
template <class T>
void LinkedList<T>::next() {
if (currPtr == rear) {
cout << "已经到达链表尾,无法移动" << endl;
return;
}
currPtr = currPtr->next;
prevPtr = prevPtr->next;
position ++;
} //游标是否移动到链尾
template <class T>
bool LinkedList<T>::endOfList() const {
return (currPtr == rear);
} //返回游标当前的位置
template <class T>
int LinkedList<T>::currentPosition() const {
return position;
} //在表头插入节点
template <class T>
void LinkedList<T>::insertFront(const T& item) {
Node<T>* n = new Node<T>(item, front);
front = n;
size ++;
position ++;
} //在表尾插入节点
template <class T>
void LinkedList<T>::insertRear(const T& item) {
Node<T>* temp = front;
while (temp->next != rear)
temp = temp->next;
Node<T> *n = new Node<T>(item, rear);
temp->next = n;
size ++;
} //在当前节点之前插入节点
template <class T>
void LinkedList<T>::insertAt(const T& item) {
Node<T>* temp = new Node<T>(item, currPtr);
prevPtr->next = temp;
prevPtr = temp;
size ++;
position ++;
} //在当前节点之后插入节点
template <class T>
void LinkedList<T>::insertAfter(const T& item) {
Node<T>* temp = new Node<T>(item, NULL);
temp->next = currPtr->next;
currPtr->next = temp;
size ++;
} //删除头节点,实质是删除front->next
template <class T>
T LinkedList<T>::deleteFront() {
if (front->next == rear) {
cout << "没有节点可以删除" << endl;
}
if (prevPtr == front->next) {
prevPtr = prevPtr->next;
currPtr = currPtr->next;
}
Node<T>* temp = front->next;
T d = temp->getData();
front->next = temp->next;
delete temp;
size --;
if (front->next != rear)
position --;
return d;
} //删除当前节点
template <class T>
void LinkedList<T>::deleteCurrent() {
Node<T>* temp = currPtr;
currPtr = currPtr->next;
delete temp;
size --;
} //返回对当前节点成员数据的引用
template <class T>
T& LinkedList<T>::data() {
return currPtr->getData();
} //返回对当前节点成员数据的常引用
template <class T>
const T& LinkedList<T>::data() const {
return currPtr->getData();
} //清空链表:释放所有节点的内存空间,被析构函数和operator=使用
template <class T>
void LinkedList<T>::clear() {
Node<T>* temp;
while (front->next != rear) {
temp = front->next;
front->next = temp->next;
delete temp;
}
size = 0;
position = 0;
} int main()
{
LinkedList<int> A, B;
int i, item; cout << "请输入加入链表A的五个整数:";
for (i = 0; i < 5; i ++) {
cin >> item;
A.insertRear(item);
}
cout << "请输入加入链表B的五个整数:";
for (i = 0; i < 5; i ++) {
cin >> item;
B.insertRear(item);
}
cout << endl << "有序链表A中的元素为:";
A.reset();
while(!A.endOfList()) {
cout << A.data() << " ";
A.next();
}
cout << endl << "有序链表B中的元素为:";
B.reset();
while(!B.endOfList()) {
A.insertRear(B.data());//在遍历链表B的时候,一边插入A、一边输出
cout << B.data() << " ";
B.next();
} cout << endl << "加入链表B中元素后,链表A中的元素为:";
A.reset();
while(!A.endOfList()) {
cout << A.data() << " ";
A.next();
}
cout << endl;
return 0;
}
输出:
请输入加入链表A的五个整数:5 6 2 4 8
请输入加入链表B的五个整数:1 2 3 5 6 有序链表A中的元素为:5 6 2 4 8
有序链表B中的元素为:1 2 3 5 6
加入链表B中元素后,链表A中的元素为:5 6 2 4 8 1 2 3 5 6

链表实现队列类和操作

#include <iostream>
using namespace std; template <class T>
class Node {
private:
T data;
public:
Node* next;
Node();
Node(const T& data, Node<T>* nt = 0);
// Node(T data, Node<T>* n = NULL);
T& getData();
}; template <class T>
Node<T>::Node() {
data = 0;
next = NULL;
} template <class T>
Node<T>::Node(const T& d, Node<T>* nt) {
data = d;
next = nt;
} template <class T>
T& Node<T>::getData() {
return data;
}
/*
* 在LinkedList的设计中,采用了附加指针front和rear,即链表的结构为front->a1->a2->...->rear
* 只有在析构函数中才删除这两个指针,其余的时间这两个指针都是存在的,其中的数据始终为0,不存储用户数据
*/
template <class T>
class LinkedList {
private:
Node<T> *front, *rear; //表头和表尾指针
Node<T> *prevPtr, *currPtr; //记录表当前遍历位置的指针,由插入和删除操作更新
int size; //表中元素个数
int position; //当前元素在表中的位置序号。由函数reset使用 Node<T>* newNode(const T& item, Node<T> *ptrNext = NULL); //生成新节点,数据与为item,指针域为prtNext
void freeNode(Node<T> *p); //释放节点 void copy(const LinkedList<T>& L); //将链表L复制到当前表(假设当前表为空),被复制构造函数和operator=调用 public:
LinkedList(); //构造函数
LinkedList(const LinkedList<T>& L); //复制构造函数
~LinkedList(); //析构函数
LinkedList<T>& operator = (const LinkedList<T>& L); //重载赋值运算符 int getSize() const; //返回链表中元素个数
bool isEmpty() const; //链表是否为空 void reset(int pos = 0); //初始化游标的位置
void next(); //使游标移动到下一个节点
bool endOfList() const; //游标是否移动到链尾
int currentPosition() const; //返回游标当前的位置 void insertFront(const T& item); //在表头插入节点
void insertRear(const T& item); //在表尾插入节点
void insertAt(const T& item); //在当前节点之前插入节点
void insertAfter(const T& item); //在当前节点之后插入节点 T deleteFront(); //删除头节点
void deleteCurrent(); //删除当前节点 T& data(); //返回对当前节点成员数据的引用
const T& data() const; //返回对当前节点成员数据的常引用 void clear(); //清空链表:释放所有节点的内存空间,被析构函数和operator=使用
}; //生成新节点,数据与为item,指针域为prtNext
template <class T>
Node<T>* LinkedList<T>::newNode(const T& item, Node<T> *ptrNext) {
Node<T> *n = new Node<T>(item, ptrNext);
return n;
} //释放节点
template <class T>
void LinkedList<T>::freeNode(Node<T> *p) {
Node<T>* temp = front;
while (temp->next != p) {
temp = temp->next;
if (temp == currPtr)
position ++;
}
temp->next = p->next;
if (currPtr == p)
currPtr = currPtr->next;
if (prevPtr == p) {
prevPtr = prevPtr->next;
currPtr = currPtr->next;
}
delete p;
size --;
position --;
} //将链表L复制到当前表(假设当前表为空),被复制构造函数和operator=调用
template <class T>
void LinkedList<T>::copy(const LinkedList<T>& L) {
Node<T> *temp = L.front, *ptr = front;
while (temp != L.rear) {
ptr->next = new Node<T>(temp->getData, NULL);
ptr = ptr->next;
temp = temp->next;
}
ptr->next = rear;
size = L.getSize();
position = L.currentPosition();
} //构造函数
template <class T>
LinkedList<T>::LinkedList() {
front = new Node<T>();
rear = new Node<T>();
front->next = rear;
currPtr = rear;
prevPtr = front;
size = 0; //不计算front和rear
position = 0; //在front下一个元素视为0
} //复制构造函数
template <class T>
LinkedList<T>::LinkedList(const LinkedList<T>& L) {
clear();
copy(L);
} //析构函数
template <class T>
LinkedList<T>::~LinkedList() {
LinkedList::clear();
delete front;
delete rear;
} //重载赋值运算符
template <class T>
LinkedList<T>& LinkedList<T>::operator = (const LinkedList<T>& L) {
clear();
copy(L);
} //返回链表中元素个数
template <class T>
int LinkedList<T>::getSize() const {
return size;
} //链表是否为空
template <class T>
bool LinkedList<T>::isEmpty() const {
return (size == 0);
} //初始化游标的位置
template <class T>
void LinkedList<T>::reset(int pos) { //初始化游标位置
if (pos > size) {
cout << "越界,无法访问" << endl;
return;
}
int i = 0;
prevPtr = front;
currPtr = front->next;
while (i < pos) {
if (currPtr == rear) {
cout << "越界,无法访问" << endl;
return;
}
i ++;
currPtr = currPtr->next;
prevPtr = prevPtr->next;
}
position = pos;
} //使游标移动到下一个节点
template <class T>
void LinkedList<T>::next() {
if (currPtr == rear) {
cout << "已经到达链表尾,无法移动" << endl;
return;
}
currPtr = currPtr->next;
prevPtr = prevPtr->next;
position ++;
} //游标是否移动到链尾
template <class T>
bool LinkedList<T>::endOfList() const {
return (currPtr == rear);
} //返回游标当前的位置
template <class T>
int LinkedList<T>::currentPosition() const {
return position;
} //在表头插入节点
template <class T>
void LinkedList<T>::insertFront(const T& item) {
Node<T>* n = new Node<T>(item, front);
front = n;
size ++;
position ++;
} //在表尾插入节点
template <class T>
void LinkedList<T>::insertRear(const T& item) {
Node<T>* temp = front;
while (temp->next != rear)
temp = temp->next;
Node<T> *n = new Node<T>(item, rear);
temp->next = n;
size ++;
} //在当前节点之前插入节点
template <class T>
void LinkedList<T>::insertAt(const T& item) {
Node<T>* temp = new Node<T>(item, currPtr);
prevPtr->next = temp;
prevPtr = temp;
size ++;
position ++;
} //在当前节点之后插入节点
template <class T>
void LinkedList<T>::insertAfter(const T& item) {
Node<T>* temp = new Node<T>(item, NULL);
temp->next = currPtr->next;
currPtr->next = temp;
size ++;
} //删除头节点,实质是删除front->next
template <class T>
T LinkedList<T>::deleteFront() {
if (front->next == rear) {
cout << "没有节点可以删除" << endl;
}
if (prevPtr == front->next) {
prevPtr = prevPtr->next;
currPtr = currPtr->next;
}
Node<T>* temp = front->next;
T d = temp->getData();
front->next = temp->next;
delete temp;
size --;
if (front->next != rear)
position --;
return d;
} //删除当前节点
template <class T>
void LinkedList<T>::deleteCurrent() {
Node<T>* temp = currPtr;
currPtr = currPtr->next;
delete temp;
size --;
} //返回对当前节点成员数据的引用
template <class T>
T& LinkedList<T>::data() {
return currPtr->getData();
} //返回对当前节点成员数据的常引用
template <class T>
const T& LinkedList<T>::data() const {
return currPtr->getData();
} //清空链表:释放所有节点的内存空间,被析构函数和operator=使用
template <class T>
void LinkedList<T>::clear() {
Node<T>* temp;
while (front->next != rear) {
temp = front->next;
front->next = temp->next;
delete temp;
}
size = 0;
position = 0;
} template <class T>
class Queue : public LinkedList<T>
{
public:
Queue() {}
void push(const T& item) {LinkedList<T>::insertRear(item);}
T pop() {return LinkedList<T>::deleteFront();}
}; int main()
{
int item;
Queue<int> q;
cout << "输入5个要插入的元素" << endl;
for (int i = 0; i < 5; i ++) {
cin >> item;
q.push(item);
}
for (int i = 0; i < 5; i ++)
cout << "取出元素" << q.pop() << endl;
return 0;
}
输出:
输入5个要插入的元素
1 4 3 2 5
取出元素1
取出元素4
取出元素3
取出元素2
取出元素5

测试排序操作

#include <iostream>
using namespace std; template <class T>
class Array
{
T* alist;
int size;
public:
Array() {size = 0;}
Array(int sz) {alist = new T[sz]; size = sz;}
Array(T a[], int sz) {
size = sz;
alist = new T[size];
for (int i = 0; i < size; i ++)
alist[i] = a[i];
}
~Array() {size = 0; delete []alist;}
int getSize() {return size;}
T& operator [](int i) {return alist[i];}
void insertSort();
void selectSort();
void bubbleSort();
int seqSearch(T key) {
int i;
for (i = 0; i < size; i ++)
if (alist[i] == key)
return i;
if (i == size) {
cout << "没有找到元素" << endl;
return -1;
}
}
}; template <class T>
void Array<T>::insertSort()
{
int i, j;
T temp;
//将下标为1~size-1的元素逐个插入到已排序序列中适当的位置
for (i = 1; i < size; i ++)
{
//从a[i-1]开始向a[0]方向扫描各元素,寻找适当位置插入a[i]
j = i;
temp = alist[i];
while (j > 0 && temp < alist[j-1])
{ //逐个比较,直到temp>=a[j-1]时,j便是应插入的位置。
alist[j] = alist[j-1]; //将元素逐个后移,以便找到插入位置时可立即插入。
j--;
}
//插入位置已找到,立即插入。
alist[j] = temp;
}
//输出数据
for(i = 0; i < size; i ++)
cout << alist[i] << " ";
cout << endl;
} template <class T>
void Array<T>::selectSort()
{
int minIndex; //每一次选出的最小元素之下标
int i, j;
T temp;
//sort a[0]..a[size-2], and a[size-1] is in place
for (i = 0; i < size - 1; i ++)
{
minIndex = i; //最小元素之下标初值设为i
//在元素a[i+1]..a[size-1]中逐个比较显出最小值
for (j = i + 1; j < size; j++)
//minIndex始终记录当前找到的最小值的下标
if (alist[j] < alist[minIndex])
minIndex = j;
//将这一趟找到的最小元素与a[i]交换
temp = alist[i];
alist[i] = alist[minIndex];
alist[minIndex] = temp;
}
//输出数据
for(i = 0; i < size; i ++)
cout << alist[i] << " ";
cout << endl;
} template <class T>
void Array<T>::bubbleSort()
{
T temp;
int i, j;
for (i = 1; i < size; i ++) {
for (j = size - 1; j >= i; j --) {
if (alist[j - 1] > alist[j]) {
temp = alist[j];
alist[j] = alist[j - 1];
alist[j - 1] = temp;
}
}
}
for(i = 0; i < size; i ++)
cout << alist[i] << " ";
cout << endl;
} //输出模版
template <class T>
void print(T A,int n)
{
for(int i=0;i<n;i++)
{
cout <<A[i]<<" ";
}
cout<<endl;
}
int main()
{
int a[5] = {3, 6, 1, 8, 2};
double d[4] = {4.1, 3.2, 5.6, 1.9};
char c[3] = {'g', 'c', 'A'};
cout <<"排序前:"<<endl;
print(a,5);
print(d,4);
print(c,3); cout <<"排序后:"<<endl;
Array<int> aArray(a, 5);
aArray.selectSort();
Array<double> dArray(d, 4);
dArray.selectSort();
Array<char> cArray(c, 3);
cArray.bubbleSort();
return 0;
}
输出:
排序前:
3 6 1 8 2
4.1 3.2 5.6 1.9
g c A
排序后:
1 2 3 6 8
1.9 3.2 4.1 5.6
A c g

习题

类模版

(1)类模板是类声明的一种模式,它使得类中得某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取:

  • 基本类型
  • 用户自定义类型

数据结构

(1)下列数据结构中,属于非线性结构的是

  • 循环队列
  • 带链队列
  • 二叉树(对)
  • 带链栈

分析:顺序表、链表、栈、队列均是线性结构,而树和图非线性

(2)下列叙述中正确的是:

  • 顺序存储结构的存储一定是连续的,链式存储结构的存储空间不一定是连续的(对)
  • 顺序存储结构只针对线性结构,链式存储结构只针对非线性结构
  • 顺序存储结构能存储有序表,链式存储结构不能存储有序表
  • 链式存储结构比顺序存储结构节省存储空间

分析:顺序和链式存储结构都可以针对线性结构链式结构也可以存储有序表;存储同样的数据,顺序结构和链式结构消耗相同存储空间。

(3)对于所述循环队列的实现中,如果不使用count变量标定当前元素个数,以下说法正确的是:

(附加条件)trade-off: 假定一个规模为SIZE的数组只能最多存放SIZE-1个元素,仍保持队空条件为front == rear

  • 不在trade-off条件下时:统计元素个数需要额外的遍历时间开销(对)
  • 不在trade-off条件下时:无法区分队空和队满(对)
  • 在trade-off情况下:判断队满的条件变为font == (rear+1)%SIZE(对)
  • 在trade-off情况下:判断队满的条件变为font == rear%(SIZE-1)

排序

(1)对于整数序列 8 5 2 4 3 按照视频中程序进行升序排序,一共比较了多少次?(10)

分析:比较次数为:4+3+2+1,不管有没有交换都进行了比较。并且每轮比较次数减一。

(2)下列排序方法中,最坏情况下比较次数最少的是

  • 冒泡排序
  • 简单选择排序
  • 直接插入排序
  • 堆排序(对)

分析:ABC中的排序在最坏情况下的时间复杂度都是nn,只有堆排序在最坏情况下是nlogn

(3)插入排序的平均时间复杂度为?

  • O(n)
  • O(logn)
  • O(n^2)(对)

c++:-6的更多相关文章

  1. java web 开发三剑客 -------电子书

    Internet,人们通常称为因特网,是当今世界上覆盖面最大和应用最广泛的网络.根据英语构词法,Internet是Inter + net,Inter-作为前缀在英语中表示“在一起,交互”,由此可知In ...

  2. 所有selenium相关的库

    通过爬虫 获取 官方文档库 如果想获取 相应的库 修改对应配置即可 代码如下 from urllib.parse import urljoin import requests from lxml im ...

  3. In-Memory:内存数据库

    在逝去的2016后半年,由于项目需要支持数据的快速更新和多用户的高并发负载,我试水SQL Server 2016的In-Memory OLTP,创建内存数据库实现项目的负载需求,现在项目接近尾声,系统 ...

  4. 从直播编程到直播教育:LiveEdu.tv开启多元化的在线学习直播时代

    2015年9月,一个叫Livecoding.tv的网站在互联网上引起了编程界的注意.缘于Pingwest品玩的一位编辑在上网时无意中发现了这个网站,并写了一篇文章<一个比直播睡觉更奇怪的网站:直 ...

  5. 【.net 深呼吸】细说CodeDom(8):分支与循环

    有人会问,为啥 CodeDom 不会生成 switch 语句,为啥没生成 while 语句之类.要注意,CodeDom只关心代码逻辑,而不是语法,语法是给写代码的人用的.如果用.net的“反编译”工具 ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  7. App开发:模拟服务器数据接口 - MockApi

    为了方便app开发过程中,不受服务器接口的限制,便于客户端功能的快速测试,可以在客户端实现一个模拟服务器数据接口的MockApi模块.本篇文章就尝试为使用gradle的android项目设计实现Moc ...

  8. TODO:macOS编译PHP7.1

    TODO:macOS编译PHP7.1 本文主要介绍在macOS上编译PHP7.1,有兴趣的朋友可以去尝试一下. 1.下载PHP7.1源码,建议到PHP官网下载纯净到源码包php-7.1.0.tar.g ...

  9. In-Memory:在内存中创建临时表和表变量

    在Disk-Base数据库中,由于临时表和表变量的数据存储在tempdb中,如果系统频繁地创建和更新临时表和表变量,大量的IO操作集中在tempdb中,tempdb很可能成为系统性能的瓶颈.在SQL ...

  10. In-Memory:内存优化表的事务处理

    内存优化表(Memory-Optimized Table,简称MOT)使用乐观策略(optimistic approach)实现事务的并发控制,在读取MOT时,使用多行版本化(Multi-Row ve ...

随机推荐

  1. 基于Java+SpringBoot+Mysql实现的古诗词平台功能设计与实现九

    一.前言介绍: 1.1 项目摘要 随着信息技术的迅猛发展和数字化时代的到来,传统文化与现代科技的融合已成为一种趋势.古诗词作为中华民族的文化瑰宝,具有深厚的历史底蕴和独特的艺术魅力.然而,在现代社会中 ...

  2. Swagger注解中带有“/”导致SwaggerUI显示异常

    日常开发中一直使用swagger作为接口文档工具使用,这次在使用过程中发现一个问题. 正常情况下显示如下图 代码配置如下图 Controller Model 如果在Swagger注解中使用" ...

  3. Python网络爬虫之requests模块2

    Python网络爬虫之requests模块(2) 今日内容 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 知识点回顾 xpath的解析流程 bs4的解析流程 ...

  4. jQuery.validator验证无效的可能原因

    最近用jQuery.validator做表单的前端验证,却发现验证规则都无效.最后发现以下原因会导致校验无效 1.jquery.min.js重复引用. 2.js中有bug存在. 3.<input ...

  5. Apache Tomcat AJP 实现负载均衡

    大部分一开始接触WEB服务器的人可能和我一样对为什么有Apache又有Tomcat服务器感到奇怪(它们还都是Apache开发的呵呵),其实他们不是冗余的服务器,虽然他们都能对外提供WEB服务器,但总的 ...

  6. java——棋牌类游戏斗地主(webddz1.0)之一

    这是本人最近一段时间写的斗地主的java代码,大体框架都实现了,主要缺少,AI的智能算法. 本版本是在singleddz3.0的基础上修改的,修改为了网络版本. 主要修改代码如下: package c ...

  7. Winform Tab增加关闭标签页

    Winform的Tab控件,有新增有移除,但是呢,缺了一个标签页上的关闭按钮,这个东西说重要也重要,说不重要也不重要. 这里就说一下怎么添加这玩意. 这玩意需要重绘tab控件,所以我们需要处理Draw ...

  8. Element Plus组件el-select回显异常

    前情 公司有经常需要做一些后台管理页面,我们选择了Element Plus,它是基于 Vue 3,面向设计师和开发者的组件库,是Vue框架生态中比较火的UI组件库,组件库丰富易用,组件链接:一个 Vu ...

  9. 一图一知-vue强大的slot

    vue常用的slot知识点记录

  10. Typroa主题替换

    Typroa主题替换 从这里下载主题 1.解压后: 2.拷贝到typroa的主题目录下(打开typroa -> 偏好设置 -> 外观 -> 打开主题文件夹) 3.拷贝后: 4.重新打 ...