1.1.2 算法的空间复杂度
与算法无关,只与定义的变量与输入输出有关
1.2 应用举例
1.2.1 数组的最小值及其下标
题目:已知求数组最小元素的值及下标的算法如下,编写主函数进行测试,要求输入任意数组,给出运行结果
#include
using namespace std;
void min(double arr[], int m)
{
double h = arr[0];
int loc = 0;
for (int i = 0; i < m; i++)
{
if (arr[i] < h)
{
h = arr[i];
loc = i;
}
}
cout << "数组中最小的元素为:" << h <<"\t"<<"最小的元素的下标为:" << loc << endl;
}
int main()
{
int m;
double n;
cout << "请输入数组的位数:";
cin >> m;
double* arr = new double[m];
cout << "请输入数组元素:";
for (int i = 0; i < m; i++)
{
cin >> n;
arr[i] = n;
}
min(arr, m);
}
运行结果:
总结:1.构建动态数组
int m;
double n;
cout << "请输入数组的位数:";
cin >> m;
double* arr = new double[m];
cout << "请输入数组元素:";
for (int i = 0; i < m; i++)
{
cin >> n;
arr[i] = n;
}
2.直接排序算法
void min(double arr[], int m)
{
double h = arr[0]; //数组最小值的初始化
int loc = 0;
for (int i = 0; i < m; i++)
{
if (arr[i] < h)
{
h = arr[i];
loc = i;
}
}
cout << "数组中最小的元素为:" << h <<"\t"<<"最小的元素的下标为:" << loc << endl;
}
1.2.2 数组元素循环左移
题目:设计一个算法,实现将数组arr[n]中所有元素循环左移k个位置
#include
using namespace std;
void Reverse(int arr[],int from,int to)
{
for(int i=0;i<(to-from+1)/2;i++)
{
int temp=arr[from+i];
arr[from+i]=arr[to-i];
arr[to-i]=temp;
}
}
void Converse(int arr[],int n,int k)
{
Reverse(arr,0,k-1);
Reverse(arr,k,n-1);
Reverse(arr,0,n-1);
}
int main()
{
int n,m;
cout<<"请输入数组的位数:";
cin>>n;
int *arr=new int[n];
cout<<"请输入"<>m;
arr[i]=m;
}
int k;
cout<<"请输入左移位数:";
cin>>k;
cout<<"左移后的数组变为:";
Converse(arr,n,k);
for(int i=0;i
运行结果:
知识点:
1.首先相对应位置交换位置,再分块逆置
第2章 线性表
2.1 引言
线性表是描述单一的前驱和后继的关系
2.2 线性表的逻辑结构
2.2.1 线性表的定义
线性表是一个有限的序列,每个元素有且只有一个前驱和一个 后继,表头和表尾元素分别无前驱和后继。
线性表数据元素的个数称为线性表的长度,长度为0的线性表称为空表
线性表的常用表达方式为:L=(a1,a2,a3,a4....an)
2.2.2 线性表的抽象数据类型定义
DataModel
数据元素具有相同类型,相邻元素具有前驱和后继的关系
InitList
输入:无
功能:线性表的初始化
输出:空的线性表
CreatList
输入:n个数据元素
功能:建立一个线性表
输出:具有n个元素的线性表
DestroyList
输入:无
功能:销毁线性表
输出:释放线性表的存储空间
PrintList
输入:无
功能:遍历操作,按序号依次输出线性表中的元素
输出:线性表的各个数据元素
Length
输入:无
功能:求线性表的长度
输出:线性表中数据元素的个数
Locate
输入:数据元素x
功能:按值查找,在线性表中查找值等于x的元素
输出:如果查找成功,返回元素x在线性表中的序号,否则返回0
Get
输入:元素的符号i
功能
2.3 线性表的顺序存储结构及实现
2.3.1 顺序表的存储结构
线性表的顺序存储结构称为顺序表
确定了起始的首地址,就可以确定整个线性表的地址
顺序表的存储结构指的是一块连续的地址存储单元格的信息,且存储的信息和下标具有一一对应的关系
用一维数组相邻的信息存储在相邻的位置
需要提前设定好数组的长度,一般来说,数组长度要大于当前线性表的长度
2.3.2 顺序表的实现
由于顺序表的类型不确定,因此在实现时要调用C++模板机制
template //定义类模板(由于线性表的类型不确定)
class SeqList
{
public:
SeqList(); //无参构造函数,建立空的顺序表
SeqList(DataType a[], int n); //有参构造函数,建立长度为n的顺序表
~SeqList(); //析构函数
int Length(); //求线性表的长度
int Empty(); //判空
DataType Get(int i); //按位查找,查找第i个元素的值
int Locate(DataType x); //按值查找,返回值为x的元素序号
void Insert(int i, DataType x);
DataType Delete(int i);
void PrintList();
private:
DataType data[MaxSize]; //存放数据元素的数组
int length; //线性表的长度
};
1.无参构造函数——初始化顺序表
将length初始化为0
template
SeqList::SeqList()
{
length = 0;
}
2.有参构造函数——建立顺序表
传入个数和元素
template
SeqList::SeqList(DataType a[], int n)
{
if (n > MaxSize) throw"参数非法";
for (int i = 0; i < n; i++)
data[i] = a[i];
length = n;
}
3.析构函数——销毁顺序表
由于顺序表是静态内存分配,在退出顺序表时会自动销毁所占的内存单元,因此析构函数为空
template
SeqList::~SeqList()
{
}
4.判空操作
length是否为0
template
int SeqList::Empty()
{
if (length == 0)
return 1;
else
return 0;
}
5.求顺序表的长度
返回length
template
int SeqList::Length()
{
return length;
}
6.遍历操作
template
void SeqList::PrintList()
{
for (int i = 0; i < length; i++)
cout << data[i];
}
7.按位查找
template
DataType SeqList::Get(int i)
{
if (i<1 || i>length)
throw"查找位置非法";
else
return data[i - 1];
}
8.按值查找
返回的是序号
template
int SeqList::Locate(DataType x)
{
for (int i = 0; i < length; i++)
if (data[i] == x) return i + 1;
return 0;
}
9.插入操作
template
void SeqList::Insert(int i, DataType x)
{
if (length >= MaxSize)
throw"上溢";
if (i<1 || i>length + 1)
throw"位置非法";
for (int j = length; j >= i; j--)
data[j] = data[j - 1];
data[i - 1] = x;
length++;
}
10.删除操作
template
DataType SeqList::Delete(int i)
{
if (length == 0)
throw"下溢";
if (i<1 || i>length)
throw"位置非法";
int x = data[i - 1];
for (int j = i; j < length; j++)
data[j - 1] = data[j];
length--;
return x;
}
2.3.3 顺序表的使用
int main()
{
int r[5] = { 1,2,3,4,5 }, i, x;
SeqList L{ r,5 }; //建立具有5个元素的顺序表
cout << "当前线性表的数据为:";
L.PrintList();
try
{
L.Insert(2, 8);
cout << endl << "执行插入操作后数据为:";
L.PrintList();
cout << endl;
}
catch (char* str) {
cout << str << "插入位置错误!" << endl;
}
cout << "当前线性表的长度为:" << L.Length();
cout << endl;
cout << "请输入要查找的元素值:";
cin >> x;
i = L.Locate(x);
if (0 == i) cout << "查找失败" << endl;
else cout << "元素" << x << "的位置为:" << i << endl;
try
{
cout << "请输入查找第几个元素值:";
cin >> i;
cout << "第" << i << "个元素值是" << L.Get(i) << endl;
}
catch (char* str) {
cout << "线性表中没有该元素" << endl;
}
try
{
cout << "请输入要删除第几个元素:";
cin >> i;
x = L.Delete(i);
cout << "删除的元素是" << x << ",删除后的数据为:";
L.PrintList();
}
catch (char* str)
{
cout << "删除错误!" << endl;
}
return 0;
}
2.3.4 完整源代码
#include
using namespace std;
const int MaxSize = 100; //为顺序表预留足够的内存空间
template //定义类模板(由于线性表的类型不确定)
class SeqList
{
public:
SeqList(); //无参构造函数,建立空的顺序表
SeqList(DataType a[], int n); //有参构造函数,建立长度为n的顺序表
~SeqList(); //析构函数
int Length(); //求线性表的长度
int Empty(); //判空
DataType Get(int i); //按位查找,查找第i个元素的值
int Locate(DataType x); //按值查找,返回值为x的元素序号
void Insert(int i, DataType x);
DataType Delete(int i);
void PrintList();
private:
DataType data[MaxSize]; //存放数据元素的数组
int length; //线性表的长度
};
template
SeqList::~SeqList()
{
}
template
SeqList::SeqList()
{
length = 0;
}
template
int SeqList::Empty()
{
if (length == 0)
return 1;
else
return 0;
}
template
int SeqList::Length()
{
return length;
}
template
SeqList::SeqList(DataType a[], int n)
{
if (n > MaxSize) throw"参数非法";
for (int i = 0; i < n; i++)
data[i] = a[i];
length = n;
}
template
void SeqList::PrintList()
{
for (int i = 0; i < length; i++)
cout << data[i];
}
template
int SeqList::Locate(DataType x)
{
for (int i = 0; i < length; i++)
if (data[i] == x) return i + 1;
return 0;
}
template
DataType SeqList::Get(int i)
{
if (i<1 || i>length)
throw"查找位置非法";
else
return data[i - 1];
}
template
DataType SeqList::Delete(int i)
{
if (length == 0)
throw"下溢";
if (i<1 || i>length)
throw"位置非法";
int x = data[i - 1];
for (int j = i; j < length; j++)
data[j - 1] = data[j];
length--;
return x;
}
template
void SeqList::Insert(int i, DataType x)
{
if (length >= MaxSize)
throw"上溢";
if (i<1 || i>length + 1)
throw"位置非法";
for (int j = length; j >= i; j--)
data[j] = data[j - 1];
data[i - 1] = x;
length++;
}
int main()
{
int r[5] = { 1,2,3,4,5 }, i, x;
SeqList L{ r,5 }; //建立具有5个元素的顺序表
cout << "当前线性表的数据为:";
L.PrintList();
try
{
L.Insert(2, 8);
cout << endl << "执行插入操作后数据为:";
L.PrintList();
cout << endl;
}
catch (char* str) {
cout << str << "插入位置错误!" << endl;
}
cout << "当前线性表的长度为:" << L.Length();
cout << endl;
cout << "请输入要查找的元素值:";
cin >> x;
i = L.Locate(x);
if (0 == i) cout << "查找失败" << endl;
else cout << "元素" << x << "的位置为:" << i << endl;
try
{
cout << "请输入查找第几个元素值:";
cin >> i;
cout << "第" << i << "个元素值是" << L.Get(i) << endl;
}
catch (char* str) {
cout << "线性表中没有该元素" << endl;
}
try
{
cout << "请输入要删除第几个元素:";
cin >> i;
x = L.Delete(i);
cout << "删除的元素是" << x << ",删除后的数据为:";
L.PrintList();
}
catch (char* str)
{
cout << "删除错误!" << endl;
}
return 0;
}
2.4 线性表的链接存储结构及实现
顺序表的不足:1.容量难以确定2.地址不连续就不能使用3.需要移动大量元素
根本原因是静态内存存储,可通过链表动态存储解决问题
2.4.1 单链表的存储结构
template
struct Node
{
DataType data;
Node* next;
};
链表存储指的是一组任意的存储地址存储数据元素,地址可以是连续的,也可以是不连续的。一个单元当中,除了存放自身的数据之外,还要存放下一个元素的地址,即为指针,由此的数据域和指针域构成一个链表的结点
由于只有一个指针域,因此叫做单链表
每一个节点的地址都存储在前一个节点的指针域当中。由于第一个节点无前驱节点,因此设立头指针指向链表的第一个节点,链表的操作必须要从头指针开始,因此,头指针具有标识一个链表的作用。
由于最后一个节点无后继节点,因此设置一个尾标志来表示。
为了统一非空表和空表,因此设立链表的第一个结点为头结点,头结点是与链表类型一致的节点,通常不存储任何的信息。
2.4.2 单链表的实现
template
class LinkList
{
public:
LinkList();
LinkList(DataType a[], int n);
~LinkList();
int Length();
int Empty();
DataType Get(int i);
int Locate(DataType x);
void Insert(int i, DataType x);
DataType Delete(int i);
void PrintList();
private:
Node* first; //单链表的头指针
};
1.无参构造函数——单链表的初始化
生成只有头结点的空单链表
template
LinkList::LinkList()
{
first = new Node;
first->next = NULL;
}
2.判空条件
template
int LinkList::Empty()
{
if (first->next == NULL)
return 1;
else return 0;
}
3.遍历操作
template
void LinkList::PrintList()
{
Node* p = first->next;
while (p != NULL)
{
cout << p->data << "\t";
p = p->next;
}
}
4.求单链表的长度
template
int LinkList::Length()
{
Node* p = first->next;
int count = 0;
while (p != NULL)
{
p = p->next;
count++;
}
return count;
}
5.按位查找
template
DataType LinkList::Get(int i)
{
Node* p = first->next;
int count = 1;
while (p != NULL && count < i)
{
p = p->next;
count++;
}
if (p == NULL) throw"位置非法";
else return p->data;
}
6.按值查找
template
int LinkList::Locate(DataType x)
{
Node* p = first->next;
int count = 1;
while (p != NULL)
{
if (p->data == x) return count;
p = p->next;
count++;
}
return 0; //退出循环表明查找失败
}
7.插入操作
template
void LinkList::Insert(int i, DataType x)
{
Node* p = first, * s = NULL;
int count = 0;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL) throw"位置异常";
else {
s = new Node;
s->data = x; //s结点的数据域为x
s->next = p->next;
p->next = s;
}
}
8.构造函数——建立单链表
头插法:
template
//LinkList::LinkList(DataType a[], int n)
//{
// first = new Node;
// first->next = NULL;
// for (int i = 0; i < n; i++)
// {
// Node* s;
// s = new Node;
// s->data = a[i];
// s->next = first->next;
// first->next = s; //将结点s插在头结点之后
// }
//}
尾插法:
template
LinkList::LinkList(DataType a[], int n)
{
first = new Node;
Node* r = first, * s = NULL; //头指针和尾指针的初始化
for (int i = 0; i < n; i++)
{
s = new Node;
s->data = a[i];
r->next = s;
r = s; //将结点s插入到终端结点之后
}
r->next = NULL; //单链表建立完毕,将终端结点的指针域置空
}
9.删除操作
template
DataType LinkList::Delete(int i)
{
DataType x;
Node* p = first, * q = NULL;
int count = 0;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL || p->next == NULL)
throw"位置不存在";
else
{
q = p->next; //暂存被删结点
x = q->data;
p->next = q->next;
delete q;
return x;
}
}
2.4.3 单链表的使用
int main()
{
int r[5] = { 1,2,3,4,5 }, i, x;
LinkList L{ r,5 };
cout << "当前线性表的数据为:";
L.PrintList();
cout << endl;
try
{
L.Insert(2, 8);
cout << "执行插入操作之后的数据为:";
L.PrintList();
cout << endl;
}
catch (char* str)
{
cout << str << endl;
}
cout << "当前链表的长度为:" << L.Length() << endl;
cout << "请输入要查找的元素值:";
cin >> x;
i = L.Locate(x);
if (i >= 1)
cout << "元素" << x << "的元素位置为:" << i << endl;
else
cout << "单链表中没有元素" <> i;
x = L.Delete(i);
cout << "删除的元素值是" << x << "执行删除操作后数据为:";
L.PrintList();
}
catch (char* str)
{
cout << str << endl;
}
return 0;
}
2.4.4 完整源代码
#include
using namespace std;
template
struct Node
{
DataType data;
Node* next;
};
template
class LinkList
{
public:
LinkList();
LinkList(DataType a[], int n);
~LinkList();
int Length();
int Empty();
DataType Get(int i);
int Locate(DataType x);
void Insert(int i, DataType x);
DataType Delete(int i);
void PrintList();
private:
Node* first;
};
template
LinkList::LinkList()
{
first = new Node;
first->next = NULL;
}
template
LinkList::~LinkList()
{
Node* q = NULL;
while (first != NULL)
{
q = first;
first = first->next;
delete q;
}
}
template
int LinkList::Empty()
{
if (first->next == NULL)
return 1;
else return 0;
}
template
void LinkList::PrintList()
{
Node* p = first->next;
while (p != NULL)
{
cout << p->data << "\t";
p = p->next;
}
}
template
int LinkList::Length()
{
Node* p = first->next;
int count = 0;
while (p != NULL)
{
p = p->next;
count++;
}
return count;
}
template
DataType LinkList::Get(int i)
{
Node* p = first->next;
int count = 1;
while (p != NULL && count < i)
{
p = p->next;
count++;
}
if (p == NULL) throw"位置非法";
else return p->data;
}
template
int LinkList::Locate(DataType x)
{
Node* p = first->next;
int count = 1;
while (p != NULL)
{
if (p->data == x) return count;
p = p->next;
count++;
}
return 0; //退出循环表明查找失败
}
template
void LinkList::Insert(int i, DataType x)
{
Node* p = first, * s = NULL;
int count = 0;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL) throw"位置异常";
else {
s = new Node;
s->data = x; //s结点的数据域为x
s->next = p->next;
p->next = s;
}
}
//头插法构造
//template
//LinkList::LinkList(DataType a[], int n)
//{
// first = new Node;
// first->next = NULL;
// for (int i = 0; i < n; i++)
// {
// Node* s;
// s = new Node;
// s->data = a[i];
// s->next = first->next;
// first->next = s; //将结点s插在头结点之后
// }
//}
template
LinkList::LinkList(DataType a[], int n)
{
first = new Node;
Node* r = first, * s = NULL; //头指针和尾指针的初始化
for (int i = 0; i < n; i++)
{
s = new Node;
s->data = a[i];
r->next = s;
r = s; //将结点s插入到终端结点之后
}
r->next = NULL; //单链表建立完毕,将终端结点的指针域置空
}
template
DataType LinkList::Delete(int i)
{
DataType x;
Node* p = first, * q = NULL;
int count = 0;
while (p != NULL && count < i - 1)
{
p = p->next;
count++;
}
if (p == NULL || p->next == NULL)
throw"位置不存在";
else
{
q = p->next; //暂存被删结点
x = q->data;
p->next = q->next;
delete q;
return x;
}
}
int main()
{
int r[5] = { 1,2,3,4,5 }, i, x;
LinkList L{ r,5 };
cout << "当前线性表的数据为:";
L.PrintList();
cout << endl;
try
{
L.Insert(2, 8);
cout << "执行插入操作之后的数据为:";
L.PrintList();
cout << endl;
}
catch (char* str)
{
cout << str << endl;
}
cout << "当前链表的长度为:" << L.Length() << endl;
cout << "请输入要查找的元素值:";
cin >> x;
i = L.Locate(x);
if (i >= 1)
cout << "元素" << x << "的元素位置为:" << i << endl;
else
cout << "单链表中没有元素" <> i;
x = L.Delete(i);
cout << "删除的元素值是" << x << "执行删除操作后数据为:";
L.PrintList();
}
catch (char* str)
{
cout << str << endl;
}
return 0;
}
2.4.5 双链表
为每个单链表的结点在原先存储自身数据和后继结点的地址之外,还要存储前驱结点的地址
prior表示前驱指针域
template
struct Node
{
DataType data;
DulNode *prior,*next;
}
1.插入操作
在结点p后面插入新结点s
1.s->prior=p;
2.s->next=p->next;
3.p->next->prior=s;
4.p->next=s;
顺序不可修改
2.删除操作
设指针p指向待删除结点
1.(p->prior)->next=p->next;
2.(p->next)->prior=p->prior;
这两个语句顺序可以调换,执行删除操作完了以后要将p所占内存释放
2.4.6 循环链表
将单链表的尾部空指针由空改为指向头结点 ,形成一个环
为了更加方便,在循环单链表的尾部添加一个指向尾部的指针rear,则尾结点可表示为rear,而头结点表示为(rear->next)->next
将尾部结点的指针指向头结点,将头结点的指针指向尾结点的循环链表名为循环双链表,则开始结点(不是头结点)表示为first->next,终端结点表示为first->prior
2.5 顺序表和链表的比较
1.时间性能比较
按位查找操作时,顺序表实现为O(1),链表还需要从左到右遍历
顺序表按位查找:
template
DataType SeqList::Get(int i)
{
if (i<1 || i>length)
throw"查找位置非法";
else
return data[i - 1];
}
链表按位查找:
template
DataType LinkList::Get(int i)
{
Node* p = first->next;
int count = 1;
while (p != NULL && count < i)
{
p = p->next;
count++;
}
if (p == NULL) throw"位置非法";
else return p->data;
}
插入和删除操作时,顺序表需移动大量元素位置,而链表不用
顺序表插入操作:
template
void SeqList::Insert(int i, DataType x)
{
if (length >= MaxSize)
throw"上溢";
if (i<1 || i>length + 1)
throw"位置非法";
for (int j = length; j >= i; j--)
data[j] = data[j - 1];
data[i - 1] = x;
length++;
}
链表插入操作:
template
LinkList::LinkList(DataType a[], int n)
{
first = new Node;
Node* r = first, * s = NULL; //头指针和尾指针的初始化
for (int i = 0; i < n; i++)
{
s = new Node;
s->data = a[i];
r->next = s;
r = s; //将结点s插入到终端结点之后
}
r->next = NULL; //单链表建立完毕,将终端结点的指针域置空
}
2.空间性能比较
空间性能指的是占用内存空间的大小
线性表只存储当前的数据,而链表除了存储自身的数据之外,还要存储前后结点的地址,则线性表每个结点占据的空间要小
但线性表需要提前预留足够的长度,否则会造成溢出等问题
因此,当数组大小确定时,采用线性表最佳。否则采用链表
2.6 扩展与提高
2.6.1 线性表的静态链表存储
1.静态链表的存储结构
用数组来模拟链表存储,用数组的下标来模拟表示元素的地址指针
由于是用数组存储,因此属于静态内存分配
每一个结点由两个部分组成:数据域以及指针域,指针域存储后继元素的下标
template
struct SNode
{
DataType data;
int next; //指针域,注意不是指针类型
};
2.静态链表的实现
用first表示非空闲链表的头指针,用avail表示空闲链表的头指针
const int MaxSize = 100;
template
class StaList
{
public:
StaList();
StaList(DataType a[], int n);
~StaList();
private:
SNode SList[MaxSize]; //静态链表数组
int first, avail; //游标,链表头指针以及空闲指针
};
(1)插入操作
将空闲链的最前端结点摘下,插入到链表尾部
s = avail; //不用申请新结点,利用空闲链表的第一个结点
avail = SList[avail].next; //空闲链的头指针后移
SList[s].data = x; //将x填入下标为s的结点
SList[s].data = SList[p].next;
SList[p].next = s;
(2)删除操作
q = SList[p].next; //暂存被删结点的下标
SList[p].next = SList[q].next; //摘链
SList[q].next = avail; //将结点q插在空闲链avail的最前端
avail = q; //空闲链头指针avail指向结点q
2.6.2 顺序表的动态分配方式
当内存不够时,可以灵活分配内存
2.7 应用实例
2.7.1 单链表管理学生成绩
编制C/C++程序,利用链式存储方式实现下列功能:从键盘输入学生成绩建立一个单链表(整数),然后根据屏幕提示,进行求学生成绩个数、查找学生成绩、删除学生成绩等操作。
#include
using namespace std;
struct Node
{
double data;
Node* next;
};
Node* CreatStudent()
{
double score;
Node* h, * s, * p;
h = new Node;
h->next = NULL;
p = h;
cin >> score;
while (score != -1)
{
s = new Node;
s->data = score;
p->next = s;
p = s;
cin >> score;
}
p->next = NULL;
return h;
}
int ListLength(Node* h)
{
Node* p;
p = h;
int j = 0;
while (p->next != NULL)
{
p = p->next;
j++;
}
return j;
}
void PrintList(Node* h)
{
Node* p = h->next;
while (p != NULL)
{
cout << p->data << "\t";
p = p->next;
}
cout << endl;
}
int Get(Node* h, int i)
{
int m;
m = ListLength(h);
Node* p;
p = h;
int j = 0;
if (i<1 || i>m)
{
cout << "您查找的学生不存在" << endl;
return 0;
}
while (j < i)
{
p = p->next;
j++;
}
return p->data;
}
int DelList(Node* h, int i)
{
Node* pre, * r;
pre = h;
int j = 0;
int m = ListLength(h);
if (i<1 || i>m)
{
cout << "删除位置不合法" << endl;
return 0;
}
while (j < i - 1)
{
pre = pre->next;
j = j + 1;
}
r = pre->next;
pre->next = r->next;
return r->data;
free(r);
}
void main()
{
Node* head = new Node; //创建头指针
head->next = NULL;
int i;
cout << "请输入学生的成绩,以-1结束:" << endl;
head = CreatStudent();
cout << "您输入的学生成绩个数为:" << ListLength(head);
cout << endl << "您要查找第几个同学的成绩:";
cin >> i;
cout << "他的成绩是:" << Get(head, i);
cout << endl << "您要删除第几个同学的成绩:";
cin >> i;
cout << "您删除的第" << i << "个学生的成绩是:" << DelList(head, i) << endl;
}
运行结果:
总结:
1.
出现成绩乱码的原因:
改为:
2.7.2 约瑟夫环问题
我的结果:
1.分析:构建环并设定相关操作即可
2.我的不足:(1)不知道怎样创建环,最终只创建出来了一个线性表
(2)不知道怎样一次循环到底一直到最后一位数
3.我的代码:
#include
using namespace std;
template
struct Node
{
DataType data;
Node* next;
};
template
class LinkList
{
public:
LinkList();
LinkList(DataType a[], int n);
~LinkList();
void PrintList();
DataType Delete(int i);
private:
Node* next,*first;
};
template
LinkList::LinkList()
{
first = new Node;
first->next =NULL;
}
template
LinkList::~LinkList()
{
Node* p = NULL;
while (first != NULL)
{
p = first;
first = first->next;
delete p;
}
}
template
void LinkList::PrintList()
{
Node* p = first->next;
while (p!=NULL)
{
cout << p->data << "\t";
p = p->next;
}
}
template
DataType LinkList::Delete(int i)
{
Node* p = first, * q = NULL;
DataType x;
int count = 0;
while (i>1&&countnext;
++count;
}
if (p == NULL || p->next == NULL)
{
throw"位置不存在";
}
else
{
q = p->next;
p->next = q->next;
x = q->data;
delete q;
return x;
}
}
template
LinkList::LinkList(DataType a[], int n)
{
first = new Node;
Node* p = first, * q = NULL;
for (int i = 0; i < n; i++)
{
q = new Node;
q->data = a[i];
p->next = q;
p = q;
}
p->next =NULL;
}
int main()
{
int r[10] = {1,2,3,4,5,6,7,8,9,10},i;
LinkList L{ r,10 };
cout << "当前参与游戏的人的编号为:";
L.PrintList();
cout << endl;
while (r != NULL)
{
try
{
cout << "请输入要杀死的位数:";
cin >> i;
L.Delete(i);
cout << "杀死之后剩余的编号变成:";
L.PrintList();
cout << endl;
}
catch (char* str)
{
cout << str << endl;
}
}
}
运行结果:
书上答案:
代码:
#include
using namespace std;
struct Node
{
int data;
struct Node* next;
};
class JosephRing
{
public:
JosephRing(); //初始化空循环链表
JosephRing(int n); //初始化n个结点的环
~JosephRing();
void Joseph(int m); //密码为m,打印出环的顺序
private:
Node* rear;
};
JosephRing::JosephRing()
{
rear = NULL;
}
JosephRing::JosephRing(int n)
{
Node* s = NULL;
rear = new Node;
rear->data = 1;
rear->next = rear; //建立长为1的循环单链表
for (int i = 2; i <= n; i++)
{
s = new Node;
s->data = i;
s->next = rear->next;
rear->next = s;
rear = s; //指针rear指向当前的尾结点
}
}
JosephRing::~JosephRing()
{
if (rear != NULL)
{
Node* p = rear->next;
while (rear->next != rear)
{
rear->next = p->next;
delete p;
p = rear->next;
}
delete rear;
}
}
void JosephRing::Joseph(int m)
{
Node* pre = rear, * p = rear->next; //初始化工作指针pre和p
int count = 1;
cout << "出环的顺序是:";
while (p->next != p)
{
if (count < m) //计数器未累加到密码值
{
pre = p;
p = p->next; //将工作指针分别后移
count++;
}
else
{
cout << p->data << "\t"; //输出出环的编号
pre->next = p->next; //将结点p摘链
delete p;
p = pre->next; //工作指针p后移,但pre不动
count = 1; //count从1开始重新计数
}
}
cout << p->data << "\t"; //输出最后一个结点的编号
delete p; //释放最后一个结点
rear = NULL;
}
int main()
{
int n, m;
cout << "请输入约瑟夫环的长度:";
cin >> n;
cout << "请输入密码:";
cin >> m;
JosephRing R(n);
R.Joseph(m);
return 0;
}
输出结果:
总结:
1.
原因:将后移和计数器加1的操作弄反了,应是先执行pre和p指针的后移操作,再执行count++
即:
2. 原因:依旧是操作顺序弄反了,要记得等号的左边是未知数
即:
3.
原因:未进行rear的定义和初始化
即:
4.设置有参构造:将约瑟夫环的长度传入有参构造函数当中——设置工作指针——创建rear结点——将当前循环链表的值设为1,并令其循环——继续从2~n构造函数,方法等同于一般的有参构造
5.设置删除函数:将要删除的序号传入删除函数当中——设置两个工作指针并令其初始化——设置计数器,从1开始,到要求的序号——当前序号<指定序号时,pre和p指针分别后移,计数器++;当前序号=指定序号时,删除指定序号的值,令计数器重新从1开始——重复上述操作,直到链表中只剩下一个结点为止——删除最后一个结点
6.构造此题的数组:
rear->data = 1;
rear->next = rear; //建立长为1的循环单链表
for (int i = 2; i <= n; i++)
{
s = new Node;
s->data = i;
s->next = rear->next;
rear->next = s;
rear = s; //指针rear指向当前的尾结点
}
2.7.3 一元多项式求和
#include
using namespace std;
struct Node
{
int coef, exp;
Node* next;
};
class Polynomial
{
public:
Polynomial();
~Polynomial();
Polynomial(const Polynomial& B); //拷贝构造函数
Polynomial operator+(Polynomial& B); //重载运算符,多项式相加
void Print();
private:
Node* first;
};
Polynomial::~Polynomial()
{
Node* q = NULL;
while (first != NULL)
{
q = first;
first = first->next;
delete q;
}
}
Polynomial::Polynomial(const Polynomial& B)
{
first = B.first;
}
Polynomial::Polynomial()
{
Node* r = NULL, * s = NULL;
int coef, exp;
first = new Node; //申请头结点
r = first;
r->next = NULL; //尾插法建立单链表
cout << "请输入系数和指数:";
cin >> coef >> exp; //输入第一项的系数和指数
while (coef != 0 || exp != 0)
{
s = new Node;
s->coef = coef;
s->exp = exp;
r->next = s;
r = s; //将结点s插入单链表的尾部
}
r->next = NULL;
}
Polynomial Polynomial::operator+(Polynomial& B)
{
Node* pre = first, * p = pre->next;
Node* qre = B.first, * q = qre->next;
Node* qtemp = NULL;
while (p != NULL && q != NULL)
{
if (p->exp < q->exp)
{
pre = p;
p = p->next;
}
else if(p->exp>q->exp)
{
qtemp = q->next;
pre->next = p;
q = qtemp;
pre = q;
qre->next = q; //将结点q插入到结点p之前
}
else
{
p->coef = p->coef + q->coef;
if (p->coef == 0) //系数相加为0,则删除结点p
{
pre->next = p->next;
delete p;
p = pre->next;
}
else
{
pre = p;
p = p->next;
}
qre->next = q->next;
delete q;
q = qre->next;
}
}
if (p == NULL)
pre->next = q; //将结点q链接到第一个单链表后面
B.first->next = NULL;
return *this;
}
void Polynomial::Print()
{
Node* p = first->next;
if (p != NULL)
cout << p->coef << "x" << p->exp;
p = p->next;
while (p != NULL)
{
if (p->coef > 0) //输出系数的正号或负号
cout << "+" << p->coef << "x" << p->exp;
else
cout << p->coef << "x" << p->exp;
p = p->next;
}
cout << endl;
}
int main()
{
Polynomial A{};
A.Print();
Polynomial B{};
B.Print();
Polynomial C = A + B;
cout << "结果是:";
C.Print();
return 0;
}
2.7.4 合并循环单链表
编制C/C++程序,利用循环单链表实现下列功能:
1)从键盘输入数据建立两个循环单链表(整数),并输出相应链表,输入输出要有相应的字幕提示。
2)实现两个循环单链表的连接。要求结果链表仍旧使用原来两个链表的存储空间,不另外开辟空间。如下图所示。
#include
using namespace std;
typedef int ElemType; //定义一个int类型的ElemType
typedef struct node
{
ElemType data;
struct node* next;
}SLink,*LinkList;
LinkList Create(int n)
{
LinkList h, s, p;
int i;
h = new SLink;
h->next = NULL;
p = h;
for (int i = 1; i <= n; i++)
{
s = new node;
cin >> s->data;
p->next = s;
p = s;
}
p->next = h; //循环单链表
return h;
}
void Print(LinkList h)
{
LinkList p;
p = h->next;
while (p != h) //循环单链表
{
cout << p->data << "\t";
p = p->next;
}
cout << endl;
}
void LinkAdd(LinkList h1, LinkList h2)
{
LinkList p;
p = h1;
while (p->next != h1)
{
p = p->next;
}
p->next = h2->next;
while (p->next != h2)
{
p = p->next;
}
p->next = h1;
free(h2);
}
void main()
{
LinkList head1, head2; //创建头指针
int i, j;
cout << "请输入第一个循环单链表的结点个数:";
cin >> i;
cout << "请输入第一个循环单链表各个结点的值:";
head1 = Create(i);
cout << "您输入的第一个循环单链表各个结点的值是:";
Print(head1);
cout << "请输入第二个循环单链表的结点个数:";
cin >> j;
cout << "请输入第二个循环单链表各个结点的值:";
head2= Create(j);
cout << "您输入的第一个循环单链表各个结点的值是:";
Print(head2);
LinkAdd(head1, head2);
cout << "合并后的循环单链表是:";
Print(head1);
}
第3章 栈和队列
3.1 引言
栈混洗:将第一个栈的元素转移到另一个栈当中
n个元素对应的混洗数
序列为3 1 2的序列,为禁形,不可能为混洗
甄别是否为栈混洗的程序的时间复杂度,可以通过栈的特性将其时间复杂度缩短为O(n) 括号匹配:
逆波兰表达式的构建
队列
3.2 栈
3.2.1 栈的逻辑结构
1.栈的定义
栈指的是仅在一端进行插入和删除的线性表,具有先进后出的特性
允许操作的一端称为栈顶,则另一端为栈底
2.栈的抽象数据类型定义
3.2.2 栈的顺序存储结构及实现
1.顺序栈的存储结构
栈是特殊的线性表结构,通常将下标为0的数据元素设置为栈底,设置top游标指向栈顶元素
当栈为空时,top=-1;栈满时,top=StackSize-1
2.顺序栈的实现
const int StackSize = 10;
template
class SeqStack
{
public:
SeqStack();
~SeqStack();
void Push(DataType x);
DataType Pop();
DataType Gettop();
int Empty();
private:
DataType data[StackSize];
int top;
};
(1)构造函数——顺序栈的初始化
template
SeqStack::SeqStack()
{
top = -1;
}
(2)析构函数——顺序栈的销毁
template
SeqStack::~SeqStack()
{
}
(3)入栈操作
template
void SeqStack::Push(DataType x)
{
if (top == StackSize - 1)
throw "上溢";
data[++top] = x;
}
(4)出栈操作
template
DataType SeqStack::Pop()
{
DataType x;
if (top == -1)
throw "下溢";
x = data[top--];
return x;
}
(5)取栈顶操作
template
DataType SeqStack::Gettop()
{
if (top == -1)
throw "下溢异常";
else
return data[top];
}
(6)判空操作
template
int SeqStack::Empty()
{
if (top == -1)
return 1;
else
return 0;
}
3.顺序栈的使用
int main()
{
int x;
SeqStack S{};
S.Push(15);
S.Push(10);
cout << "当前栈顶元素为:" << S.Gettop() << endl;
try
{
x = S.Pop();
cout << "执行一次出栈操作,删除元素:" << x << endl;
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入待入栈元素:";
cin >> x;
S.Push(x);
}
catch (char* str)
{
cout << str << endl;
}
if (S.Empty() == 1)
cout << "栈为空" << endl;
else cout << "栈非空" << endl;
return 0;
}
3.2.3 完整源代码
#include
using namespace std;
const int StackSize = 10;
template
class SeqStack
{
public:
SeqStack();
~SeqStack();
void Push(DataType x);
DataType Pop();
DataType Gettop();
int Empty();
private:
DataType data[StackSize];
int top;
};
template
SeqStack::SeqStack()
{
top = -1;
}
template
SeqStack::~SeqStack()
{
}
template
int SeqStack::Empty()
{
if (top == -1)
return 1;
else
return 0;
}
template
DataType SeqStack::Gettop()
{
if (top == -1)
throw "下溢异常";
else
return data[top];
}
template
void SeqStack::Push(DataType x)
{
if (top == StackSize - 1)
throw "上溢";
data[++top] = x;
}
template
DataType SeqStack::Pop()
{
DataType x;
if (top == -1)
throw "下溢";
x = data[top--];
return x;
}
int main()
{
int x;
SeqStack S{};
S.Push(15);
S.Push(10);
cout << "当前栈顶元素为:" << S.Gettop() << endl;
try
{
x = S.Pop();
cout << "执行一次出栈操作,删除元素:" << x << endl;
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入待入栈元素:";
cin >> x;
S.Push(x);
}
catch (char* str)
{
cout << str << endl;
}
if (S.Empty() == 1)
cout << "栈为空" << endl;
else cout << "栈非空" << endl;
return 0;
}
3.2.4 栈的链接存储结构及实现
1.链栈的存储结构
链栈指的是栈的链接存储结构,与单链表大致相似,只不过只在一端进行插入和删除操作
不需要头结点
2.链栈的实现
将链栈的抽象数据类型定义用类表示
template
class LinkStack
{
public:
LinkStack();
~LinkStack();
void Push(DataType x);
DataType Pop();
DataType GetTop();
int Empty();
private:
Node* top;
};
(1)构造函数——链栈的初始化
template
LinkStack::LinkStack()
{
top = new Node; //生成第一个结点
top->next = NULL;
}
(2)析构函数——链栈的销毁
template
LinkStack::~LinkStack()
{
Node* q = NULL;
while (top != NULL)
{
q = top;
top = top->next;
delete q;
}
}
(3)入栈操作
template
void LinkStack::Push(DataType x)
{
Node* s = NULL;
s = new Node;
s->data = x;
s->next = top;
top = s;
}
(4)出栈操作
template
DataType LinkStack::Pop()
{
Node* p = NULL;
DataType x;
if (top == NULL)
throw "下溢";
x = top->data;
p = top; //暂存栈顶元素
top = top->next; //栈顶元素摘链
delete p;
return x;
}
(5)取栈顶操作
template
DataType LinkStack::GetTop()
{
if (top == NULL)
throw "下溢异常";
else
return top->data;
}
(6)判空操作
template
int LinkStack::Empty()
{
if (top == NULL)
return 1;
else
return 0;
}
3.链栈的使用
int main()
{
int x;
LinkStack S{};
cout << "对15和10进行入栈操作,";
S.Push(15);
S.Push(10);
cout << "当前栈顶元素为:" << S.GetTop() << endl;
try
{
x = S.Pop();
cout << "执行一次出栈操作,删除元素:" << x << endl;
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入待插入元素:";
cin >> x;
S.Push(x);
}
catch (char* str)
{
cout << str << endl;
}
if (S.Empty() == 1)
cout << "栈为空" << endl;
else cout << "栈非空" << endl;
return 0;
}
3.2.5 源代码
#include
using namespace std;
template
struct Node
{
DataType data;
Node* next;
};
template
class LinkStack
{
public:
LinkStack();
~LinkStack();
void Push(DataType x);
DataType Pop();
DataType GetTop();
int Empty();
private:
Node* top;
};
template
LinkStack::LinkStack()
{
top = new Node; //生成头结点
top->next = NULL;
}
template
LinkStack::~LinkStack()
{
Node* q = NULL;
while (top != NULL)
{
q = top;
top = top->next;
delete q;
}
}
template
DataType LinkStack::GetTop()
{
if (top == NULL)
throw "下溢异常";
else
return top->data;
}
template
void LinkStack::Push(DataType x)
{
Node* s = NULL;
s = new Node;
s->data = x;
s->next = top;
top = s;
}
template
DataType LinkStack::Pop()
{
Node* p = NULL;
DataType x;
if (top == NULL)
throw "下溢";
x = top->data;
p = top; //暂存栈顶元素
top = top->next; //栈顶元素摘链
delete p;
return x;
}
template
int LinkStack::Empty()
{
if (top == NULL)
return 1;
else
return 0;
}
int main()
{
int x;
LinkStack S{};
cout << "对15和10进行入栈操作,";
S.Push(15);
S.Push(10);
cout << "当前栈顶元素为:" << S.GetTop() << endl;
try
{
x = S.Pop();
cout << "执行一次出栈操作,删除元素:" << x << endl;
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入待插入元素:";
cin >> x;
S.Push(x);
}
catch (char* str)
{
cout << str << endl;
}
if (S.Empty() == 1)
cout << "栈为空" << endl;
else cout << "栈非空" << endl;
return 0;
}
3.2.6 顺序栈和链栈的比较
当个数变化较大时,用链栈,反之。
3.3 应用实例
3.3.1 执行各个菜单功能(顺序栈)
#include
using namespace std;
const int maxSize = 50;
typedef int T;
class SeqStack
{
private:
T* Sstack;
int top;
int Size;
void overflow();
public:
SeqStack();
~SeqStack();
void Push(T x);
void Pop(T& x);
void GetTop(T& x);
void IsEmpty();
void IsFull();
void Print();
int Length();
void MakeEmpty();
};
SeqStack::SeqStack()
{
Sstack = new T[maxSize];
top = -1;
Size = maxSize;
}
SeqStack::~SeqStack()
{
delete Sstack;
}
void SeqStack::overflow()
{
if(Sstack != NULL)
{
T* Array = new T[Size + 20];
for (int i = 0; i <= top; i++)
{
Array[i]=Sstack[i];
}
Size = Size + 20;
Sstack = Array;
}
}
void SeqStack::IsEmpty()
{
if (top == -1)
cout << "栈为空" << endl;
else
cout << "栈不为空" << endl;
}
void SeqStack::IsFull()
{
if (top ==maxSize - 1)
cout << "栈已满" << endl;
else
cout<<"栈不满"<> x;
ss->Push(x);
break;
case 2:
ss->Pop(x);
break;
case 3:
ss->GetTop(x);
break;
case 4:
ss->IsEmpty();
break;
case 5:
ss->IsFull();
break;
case 6:
ss->MakeEmpty();
break;
case 7:
ss->Print();
break;
default:
exit(0);
}
}
int main(int argv, char** argr)
{
SeqStack *ss = new SeqStack;
mune();
int num;
while (true)
{
cout << "请输入你要选择的数字:";
cin >> num;
function(num, ss);
}
delete ss;
return 0;
}
运行结果:
知识点:
1.typedef的相关介绍:(35条消息) typedef介绍_liitdar的博客-CSDN博客_typedef
2.菜单的构建
3.3.2 执行各个菜单的功能(链栈)
#include
using namespace std;
template
struct Node
{
DataType data;
Node* next;
};
template
class LinkStack
{
public:
LinkStack();
~LinkStack();
void Push(DataType x);
DataType Pop();
DataType GetTop();
int Length();
int Empty();
void Print();
private:
Node* top;
};
template
LinkStack::LinkStack()
{
top = NULL;
}
template
LinkStack::~LinkStack()
{
Node* p =top;
while (top != NULL)
{
top = top->next;
delete p;
p = top;
}
}
template
void LinkStack::Push(DataType x)
{
Node* p = NULL;
p = new Node;
p->data = x;
p->next = top;
top = p;
}
template
DataType LinkStack::Pop()
{
Node* p = NULL;
DataType x;
if (top == NULL)
throw "栈为空,无法实现出栈操作";
x = top->data;
p = top;
top = top->next;
delete p;
return x;
}
template
DataType LinkStack::GetTop()
{
if (top == NULL)
throw "下溢";
return top->data;
}
template
int LinkStack::Length()
{
Node* p = top;
int count =0;
while (p != NULL)
{
p = p->next;
count++;
}
return count;
}
template
int LinkStack::Empty()
{
if (top == NULL)
{
return 1;
}
else
{
return 0;
}
}
template
void LinkStack::Print()
{
Node* p = top;
while (p != NULL)
{
cout << p->data << "\t";
p = p->next;
}
cout << endl;
}
void menu()
{
cout << "1.入栈" << endl;
cout << "2.出栈" << endl;
cout << "3.获取栈顶" << endl;
cout << "4.获取长度" << endl;
cout << "5.判空" << endl;
cout << "6.打印" << endl;
cout << "7.退出" << endl;
}
void function(int num, LinkStack* ss)
{
switch (num)
{
int x;
case 1:
cout << "请输入想要进栈的数字:";
cin >> x;
ss->Push(x);
break;
case 2:
try
{
cout<<"出栈的元素是:"<< ss->Pop()<GetTop() << endl;;
}
catch (const char* str)
{
cout << str << endl;
}
break;
case 4:
cout << "栈的长度为:" << ss->Length() << endl;;
break;
case 5:
cout << "栈是否为空:" << ss->Empty() << endl;;
break;
case 6:
ss->Print();
break;
default:
exit(0);
}
}
int main()
{
LinkStack s;
int num;
menu();
while (true)
{
cout << "请输入你想要实现的功能:";
cin >> num;
function(num, &s);
}
return 0;
}
运行结果:
知识点:
1.最开始输出不出来结果,是因为没有给功能设置cout<
void function(int num, LinkStack* ss)
{
switch (num)
{
int x;
case 1:
cout << "请输入想要进栈的数字:";
cin >> x;
ss->Push(x);
break;
case 2:
ss->Pop();
break;
case 3:
ss->GetTop();
break;
case 4:
ss->Length();
break;
case 5:
ss->Empty();
break;
case 6:
ss->Print();
break;
default:
exit(0);
}
}
改正后如答案所示
3.4 队列
3.4.1 队列的逻辑结构
1.队列的定义
队列是指是允许在一端进入,在另一端出去的线性表
进入的那一端称为队头,删除的那一端称为队尾
2.队列的抽象数据类型定义
InitQueue
输入:无
功能:队列的初始化
输出:一个空队列
DestroyQueue
输入:无
功能:队列的销毁
输出:释放队列占据的存储空间
EnQueue
输入:元素值x
功能:入队操作,在队尾插入元素x
输出:如果插入成功,队尾增加了一个元素,否则返回失败信息
DeQueue:删除队头元素
GetHead:读取队头元素
Empty:判空
3.4.2 队列的顺序存储结构及实现
1.顺序队列的存储结构
队列的顺序存储结构称为顺序队列
入队时间复杂度是O(1),出队时由于后面元素都需要向前挪动1位,因此时间复杂度为o(n)
为了改善出队的时间复杂度,设置两个指针front和rear,front指向队头元素的前一个元素,rear指向队尾元素。在出队时后面的元素不必往前挪动位置,因此时间复杂度为O(1)
2.循环队列的存储结构
在未设置循环队列之前,由于队列的单向移动性,front和rear指针在移动到数组下标最大时,尽管数组当中依然有位置,但由于已经到了下标最大,无法再在其中加入数据,这种结构称其为假溢出
为了解决假溢出的问题,我们设置当下标到了最大之后依旧可以在数组下标较小的位置插入,这种就组成了循环队列,即:rear=(rear+1)%MaxSize,front=(front+1)%MaxSize
当front下标+1=rear下标时,可判定队为空
判断队满:(rear+1)%MaxSize=front
判断队空或队满,也可以使用一个计数器count记录数组长度,当count=MaxSize的时候,队满
3.循环队列的实现
const int QueueSize = 100;
template
class CirQueue
{
public:
CirQueue();
~CirQueue();
void EnQueue(DataType x);
DataType DeQueue();
DataType GetQueue();
int Empty();
private:
DataType data[QueueSize];
int front, rear; //游标,队头和队尾指针
};
(1)构造函数——循环队列的初始化
template
CirQueue::CirQueue()
{
rear = front = QueueSize - 1;
}
(2)析构函数——循环队列的销毁
template
CirQueue::~CirQueue()
{
}
(3)入队操作
template
void CirQueue::EnQueue(DataType x)
{
if ((rear + 1) % QueueSize == front)
throw "上溢";
rear = (rear + 1) % QueueSize; //队尾指针在循环意义下加1
data[rear] = x; //在队尾处插入元素
}
(4)出队操作
template
DataType CirQueue::DeQueue()
{
if (rear == front) throw "下溢";
front = (front + 1) % QueueSize; //队头指针在循环意义下加1
return data[front]; //读取并返回出队前的队头元素
}
(5)取队头元素
template
DataType CirQueue::GetQueue()
{
if (rear = front)
throw "下溢";
return data[(front + 1) % QueueSize];
}
(6)判空操作
template
int CirQueue::Empty()
{
if (front == rear)
return 1;
else
return 0;
}
4.循环队列的使用
int main()
{
int x;
CirQueue Q{};
cout << "对5和8进行入队操作,";
Q.EnQueue(5);
Q.EnQueue(8);
cout << "当前队头元素为:" << Q.GetQueue() << endl;
try
{
x = Q.DeQueue();
cout << "执行一次出队操作,出队元素是:" << x << endl;
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入入队元素:";
cin >> x;
Q.EnQueue(x);
}
catch (char* str)
{
cout << str << endl;
}
if (Q.Empty() == 1)
cout << "队列为空" << endl;
else
cout << "队列非空" << endl;
return 0;
}
3.4.3 源代码
#include
using namespace std;
const int QueueSize = 100;
template
class CirQueue
{
public:
CirQueue();
~CirQueue();
void EnQueue(DataType x);
DataType DeQueue();
DataType GetQueue();
int Empty();
private:
DataType data[QueueSize];
int front, rear; //游标,队头和队尾指针
};
template
CirQueue::CirQueue()
{
rear = front = QueueSize - 1;
}
template
CirQueue::~CirQueue()
{
}
template
void CirQueue::EnQueue(DataType x)
{
if ((rear + 1) % QueueSize == front)
throw "上溢";
rear = (rear + 1) % QueueSize; //队尾指针在循环意义下加1
data[rear] = x; //在队尾处插入元素
}
template
DataType CirQueue::DeQueue()
{
if (rear == front) throw "下溢";
front = (front + 1) % QueueSize; //队头指针在循环意义下加1
return data[front]; //读取并返回出队前的队头元素
}
template
DataType CirQueue::GetQueue()
{
if (rear = front)
throw "下溢";
return data[(front + 1) % QueueSize];
}
template
int CirQueue::Empty()
{
if (front == rear)
return 1;
else
return 0;
}
int main()
{
int x;
CirQueue Q{};
cout << "对5和8进行入队操作,";
Q.EnQueue(5);
Q.EnQueue(8);
cout << "当前队头元素为:" << Q.GetQueue() << endl;
try
{
x = Q.DeQueue();
cout << "执行一次出队操作,出队元素是:" << x << endl;
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入入队元素:";
cin >> x;
Q.EnQueue(x);
}
catch (char* str)
{
cout << str << endl;
}
if (Q.Empty() == 1)
cout << "队列为空" << endl;
else
cout << "队列非空" << endl;
return 0;
}
3.4.4 队列的链接存储结构及实现
1.链队列的存储结构
用单链表表示队列,为了统一空队列和非空队列,同样设置头结点和头指针,尾指针rear指向最后一个结点
2.链队列的实现
template
struct Node
{
DataType data;
Node* next;
};
template
class LinkQueue
{
public:
LinkQueue();
~LinkQueue();
void EnQueue(DataType x);
DataType DeQueue();
DataType GetQueue();
int Empty();
void Print();
private:
Node* front, * rear;
};
(1)构造函数——链队列的初始化
template
LinkQueue::LinkQueue()
{
Node* s = NULL;
s = new Node;
s->next = NULL;
front = rear = s; //将队头指针和队尾指针都指向头结点
}
(2)析构函数——链队列的销毁
template
LinkQueue::~LinkQueue()
{
Node* q = NULL;
while (front != NULL)
{
q = front;
front = front->next;
delete q;
}
}
(3)入队操作
template
void LinkQueue::EnQueue(DataType x)
{
Node* s = NULL;
s = new Node;
s->data = x;
s->next = NULL;
rear->next = s;
rear = s;
}
(4)出队操作
template
DataType LinkQueue::DeQueue()
{
DataType x;
Node* p = NULL;
if (rear == front)
throw "下溢";
p = front->next;
x = p->data;
front->next = p->next;
if (p->next == NULL)
rear = front;
delete p;
return x;
}
(5)取队头操作
template
DataType LinkQueue::GetQueue()
{
if (front == rear)
throw "下溢异常";
else
return front->next->data;
}
(6)判空操作
template
int LinkQueue::Empty()
{
if (front == rear)
return 1;
else
return 0;
}
(7)遍历操作
template
void LinkQueue::Print()
{
Node* p = front->next;
while (p!=NULL)
{
cout << p->data << "\t";
p = p->next;
}
cout << endl;
}
3.栈队列的使用
int main()
{
int x;
LinkQueue Q{};
cout << "队列执行入队操作,";
Q.EnQueue(1);
Q.EnQueue(2);
Q.EnQueue(3);
Q.EnQueue(4);
Q.EnQueue(5);
Q.EnQueue(6);
Q.EnQueue(7);
Q.EnQueue(8);
Q.EnQueue(9);
Q.EnQueue(10);
cout << "当前队列的元素为:";
Q.Print();
cout << "当前队头元素为:" << Q.GetQueue() << endl;
try
{
x = Q.DeQueue();
cout << "执行一次出队操作,出队元素是:" << x << endl;
cout << "当前队列的元素为:";
Q.Print();
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入入队元素:";
cin >> x;
Q.EnQueue(x);
cout << "当前队列的元素为:";
Q.Print();
}
catch (char* str)
{
cout << str << endl;
}
cout << "对当前队列是否为空进行判断:";
if (Q.Empty() == 1)
cout << "队列为空" << endl;
else
cout << "队列非空" << endl;
return 0;
}
3.4.5 源代码
#include
using namespace std;
template
struct Node
{
DataType data;
Node* next;
};
template
class LinkQueue
{
public:
LinkQueue();
~LinkQueue();
void EnQueue(DataType x);
DataType DeQueue();
DataType GetQueue();
int Empty();
void Print();
private:
Node* front, * rear;
};
template
LinkQueue::LinkQueue()
{
Node* s = NULL;
s = new Node;
s->next = NULL;
front = rear = s; //将队头指针和队尾指针都指向头结点
}
template
LinkQueue::~LinkQueue()
{
Node* q = NULL;
while (front != NULL)
{
q = front;
front = front->next;
delete q;
}
}
template
void LinkQueue::EnQueue(DataType x)
{
Node* s = NULL;
s = new Node;
s->data = x;
s->next = NULL;
rear->next = s;
rear = s;
}
template
DataType LinkQueue::DeQueue()
{
DataType x;
Node* p = NULL;
if (rear == front)
throw "下溢";
p = front->next;
x = p->data;
front->next = p->next;
if (p->next == NULL)
rear = front;
delete p;
return x;
}
template
DataType LinkQueue::GetQueue()
{
if (front == rear)
throw "下溢异常";
else
return front->next->data;
}
template
int LinkQueue::Empty()
{
if (front == rear)
return 1;
else
return 0;
}
template
void LinkQueue::Print()
{
Node* p = front->next;
while (p!=NULL)
{
cout << p->data << "\t";
p = p->next;
}
cout << endl;
}
int main()
{
int x;
LinkQueue Q{};
cout << "队列执行入队操作,";
Q.EnQueue(1);
Q.EnQueue(2);
Q.EnQueue(3);
Q.EnQueue(4);
Q.EnQueue(5);
Q.EnQueue(6);
Q.EnQueue(7);
Q.EnQueue(8);
Q.EnQueue(9);
Q.EnQueue(10);
cout << "当前队列的元素为:";
Q.Print();
cout << "当前队头元素为:" << Q.GetQueue() << endl;
try
{
x = Q.DeQueue();
cout << "执行一次出队操作,出队元素是:" << x << endl;
cout << "当前队列的元素为:";
Q.Print();
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入入队元素:";
cin >> x;
Q.EnQueue(x);
cout << "当前队列的元素为:";
Q.Print();
}
catch (char* str)
{
cout << str << endl;
}
cout << "对当前队列是否为空进行判断:";
if (Q.Empty() == 1)
cout << "队列为空" << endl;
else
cout << "队列非空" << endl;
return 0;
}
3.5 扩展与提高
3.5.1 两栈共享空间
存储相同类型的两个数组时,可为此设计两个栈,但有可能会造成栈的溢出或另外一个栈大量空间没有利用到而造成的浪费,因此,可以利用一个数组来存储两个栈
即第一个栈的栈底为数组下标为0的起始点,而第二个栈的栈底为数组下标最大的地方来存储。但要求两个栈要有相反的需求关系
(1)入栈操作
栈满标志:top1=top2-1 top2=top1+1
入栈时,栈2的下标是-1而不是+1
(2)出栈操作
top1=-1时栈1为空,top2=MaxSize时栈2为空
3.5.2 源代码
#include
using namespace std;
const int StackSize = 10;
template
class BothStack
{
public:
BothStack();
~BothStack();
void push(int i, DataType x);
DataType Pop(int i);
DataType GetTop(int i);
int Empty(int i);
private:
DataType data[StackSize];
int top1, top2;
};
template
BothStack::BothStack()
{
top1 = -1;
top2 = StackSize;
}
template
int BothStack::Empty(int i)
{
if (i == 1)
{
if (top1 == -1)
return 1;
else
return 0;
}
else {
if (top2 == StackSize)
return 1;
else
return 0;
}
}
template
BothStack::~BothStack()
{
}
template
DataType BothStack::GetTop(int i)
{
if (i == 1)
{
if (top1 == -1)
throw "下溢异常";
else
return data[top1];
}
else
{
if (top2 == StackSize)
throw "下溢异常";
else
return data[top2];
}
}
template
void BothStack::push(int i, DataType x)
{
if (top1 = top2 - 1)
throw "上溢";
if (i == 1)
x = data[++top1];
else
x = data[--top2];
}
template
DataType BothStack::Pop(int i)
{
DataType x;
if (i == 1)
{
if (top1 == -1)
throw "下溢";
x = data[top1--];
}
else
{
if (top2 == StackSize)
throw "下溢";
x = data[top2++];
}
return x;
}
int main()
{
int x;
BothStack S{};
cout << "对栈1进行入栈操作:";
S.push(1,1);
S.push(1,2);
S.push(1,3);
S.push(1,4);
S.push(1,5);
S.push(1,6);
S.push(1,7);
cout << "当前栈1的栈顶元素为:" << S.GetTop(1) << endl;
cout << "对10执行栈2的入栈操作,";
S.push(2, 10);
cout << "当前栈2的栈顶元素为:" << S.GetTop(2) << endl;
try
{
x = S.Pop(1);
cout << "对栈1进行出栈操作,删除元素" << x << endl;
}
catch (char* str)
{
cout << str << endl;
}
try
{
cout << "请输入待入栈元素:";
cin >> x;
S.push(2, x);
}
catch (char* str)
{
cout << str << endl;
}
if (S.Empty(1) == 1)
cout << "栈1为空" << endl;
else
cout << "栈1非空" << endl;
if (S.Empty(2) == 1)
cout << "栈2为空" << endl;
else
cout << "栈2非空" << endl;
return 0;
}
3.5.3 双端队列
可以在队列的两端进行插入或删除操作
其中,只能在一端进行插入,两端都可以进行删除的队列叫做一进二出队列
只能在一端进行删除,两端都可以进行插入的队列叫做一出二进队列
3.6 应用举例
3.6.1 括号匹配问题
#include
#include
using namespace std;
class Stack
{
public:
Stack(string str);
~Stack();
int jieguo();
private:
string str;
};
Stack::Stack(string str)
{
this->str = str;
}
Stack::~Stack()
{
}
int Stack::jieguo()
{
char S[10];
int top = -1;
for (int i = 0; str[i] != '\0'; i++)
{
if (str[i] == ')')
{
if (top > -1)
top--;
else
return -1;
}
else if (str[i] == '(')
S[++top] = str[i];
}
if (top == -1)
return 0;
else
return 1;
}
int main()
{
string str;
cout << "请输入一个表达式:";
cin >> str;
Stack S{ str };
int k = S.jieguo();
if (k == 0)
{
cout << "表达式匹配" << endl;
}
else if (k == 1)
{
cout << "多出了左括号" << endl;
}
else
{
cout << "多出了右括号" << endl;
}
return 0;
}
3.6.2 表达式求值
题目:设运算符有+、-、*、/、#、(),其中#为表达式的定界符,对于任意给定的表达式进行求值运算,给出求值结果
#include
#include
using namespace std;
class Expression
{
public:
Expression(string str);
~Expression();
int Compute();
private:
int Comp(char str1, char str2); //比较str1和str2的优先级
string str;
};
Expression::Expression(string str)
{
this->str = str + "#";
}
Expression::~Expression()
{
}
int Expression::Compute()
{
char OPND[100], OPTR[100]; //定义两个顺序栈
OPTR[0] = '#'; //栈OPTR初始化为定界符
int top1 = -1, top2 = 0;
int i, k, x, y, z, op;
for (int i = 0; str[i] != '\0';) //依次扫描每一个字符
{
if (str[i] >= 48 && str[i] <= 57) //0的ASCII值是48,9的ASCII码值是57
OPND[++top1] = str[i++] - 48; //将字符转换为数字压栈
else {
k = Comp(str[i], OPTR[top2]);
if (k == 1) //str[i]的优先级高
OPTR[++top2] = str[i++];
else if (k == -1) //str[i]的优先级低
{
y = OPND[top1--];
x = OPND[top1--];
op = OPTR[top2--];
switch (op)
{
case '+':z = x + y;break;
case '-':z = x - y; break;
case '*':z = x * y; break;
case '/':z = x / y; break;
default:break;
}
OPND[++top1] = z; //将运算结果入运算对象栈
}
else { //str[i]和运算符栈的栈顶元素优先级相同
top2--;
i++;
}
}
}
return OPND[top1]; //运算对象栈的栈顶元素即为运算结果
}
int Expression::Comp(char str1, char str2) //比较str1和str2的优先级,1表示str1高,0表示相同,-1表示str1低
{
switch (str1)
{
case '+':case '-':if (str2 == '(' || str2 == '#') return 1;
else return -1;
break;
case '*':case '/':if (str2 == '*' || str2 == '/') return -1;
else return 1;
break;
case '(':return 1; break;
case ')':if (str2 == '(') return 0;
else return -1;
break;
case '#':if (str2 == '#') return 0;
else return -1;
break;
default:break;
}
}
int main()
{
string str;
cout << "请输入一个表达式:" << endl;
cin >> str;
Expression E{ str };
int result = E.Compute();
cout << "表达式的值是:" << result << endl;
return 0;
}
第4章 字符串和多维数组
4.1 引言
4.2 字符串
4.2.1 字符串的逻辑结构
1.字符串的定义
字符串是指多个字符组成的有限序列,通常由S=“a1,a2,a3...”表示,其中,“”为定界符,不算入字符串当中
字符串中没有任何东西的叫做空串,即为S=“”
字符串中仅有一个或多个空格的串称为空格串,在计算字符串的长度时,需要将空格算入字符串的长度当中
串在主串当中出现的串称为子串,子串第一个字符在主串中的序号称为子串在主串中的位置
字符之间的大小比较用ASCII码值的大小确定
2.字符串的抽象数据类型定义
字符串通常以整体作为操作对象,而不像线性表那样以单个元素作为操作对象
StrAssign
输入:串S,串T
功能:串赋值,将T的串值赋值给串S
输出:串S
StrLength
输入:串S
功能:求串的长度
输出:串S中的字符个数
Strcat
输入:串S,串T
功能:串连接,将串T放在串S的后面连接成一个新串S
输出:连接之后的串S
StrSub
输入:串S,位置i,长度len
功能:求子串,返回从串S的第i个字符开始长为len的子串
输出:S的一个子串
StrCmp
输入:串S,串T
功能:串比较
输出:若S=T,返回0;若ST,返回1;
StrIndex
输入:串S,串T
功能:子串定位
输出:子串T在主串S中首次出现的位置
StrInsert
输入:串S,串T,位置i
功能:串插入,将串T插入到串S的第i个位置上
输出:插入之后的串s
StrDelete
输入:串S,位置i,长度len
功能:串删除,删除串S从位置i开始连续的len个字符
输出:删除之后的串s
字符串通过调用string头文件就可以直接调用相关操作
4.2.2 字符串的存储结构
存储字符串的长度方法:
1.用一个变量表示数组的长度
2.在数组中用下标为0的位置存储数组 的长度,则字符串从下标为1的位置开始
(常用)3.用一个在数组中不会出现的字符表示结束的标志。在C++中,一般使用'\0'表示字符串的结束。'\0'的出现并不能直接得到数组的长度,但可以通过得知数组的结束从而求字符串的长度
4.2.3 模式匹配
寻找子串在主串中的位置称为模式匹配
1.BF算法
算法原理:
BF(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串P的第一个字符进行匹配,若相等,则继续比较S的第二个字符和P的第二个字符;若不相等,则比较S的第二个字符和P的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
————————————————
版权声明:本文为CSDN博主「酷小川」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/kksc1099054857/article/details/78035764
#include
using namespace std;
int B_F(char S[], char T[])
{
int i = 0, j = 0, start = 0;
while (S[i] != '\0' && T[j] != '\0')
{
if (S[i] == T[j])
{
i++;
j++;
}
else
{
i = ++start;
j = 0;
}
}
if (T[j] == '\0')
return start;
else
return 0;
}
int main()
{
char a[] = { 'a','a','a','a','b','c','\0' };
char b[] = { 'a','b','c','\0' };
cout << "子串b在主串a中的位置为:" << B_F(a, b) << endl;
}
2.KMP算法
「天勤公开课」KMP算法易懂版_哔哩哔哩_bilibili
KMP算法之求next数组代码讲解_哔哩哔哩_bilibili
KMP算法代码:
#include
#include
using namespace std;
int ne[20];///这个数组中盛放的是匹配字符串的next值
void GetNext(char* a)///这个函数是为了标记匹配字符串的next值
{
int len = strlen(a);///先求字符串的长度,便于循环赋值
int i = 0, j = -1;
ne[0] = -1;
while (i < len)
{
if (j == -1 || a[i] == a[j])
{
ne[++i] = ++j;
}
else j = ne[j];
}
}
///实际上每求一个next值要循环两遍
int KMP(char* a, char* b)
{
int lena = strlen(a);
int lenb = strlen(b);
int i = 0, j = 0;
while (i < lena && j < lenb)
{
if (j == -1 || a[i] == b[j])
{
j++;
i++;
}
else
j = ne[j];
}
if (j == lenb)
return i - j + 1;
else
return -1;
}
int main()
{
char s[100] = { 'a','g','f','a','a','a','a','b','c','\0' };
char f[100] = { 'a','b','c','\0' };
GetNext(f);
cout << KMP(s, f) << endl;
return 0;
}
4.3 多维数组
4.3.1 数组的逻辑结构
1.数组的定义
数组是类型相同的数据元素的集合
本身具有结构,但类型一定相同
数组是线性表的推广
2.数组的抽象数据类型定义
数组具有固定格式和数量,因此无法进行单一元素的插入或删除
只能进行读和写操作,实质上是 寻址——根据一组下标定位相应的数组元素
写:存储或修改相应的数组元素 读:读取相应的数组元素
InitMatrix
输入:数组的维度n和各维的长度
功能:数组的初始化
输出:一个空的n维数组
GetMatrix
输入:一组下标
功能:读操作,读取这组下标对应的数组元素
输出:对应下标的元素值
SetMatrix
输入:元素值e,一组下标
功能:写操作,存储或修改这组下标对应的数组元素
输出:对应下标的元素值改为e
4.3.2 数组的存储结构与寻址
由于数组建立后元素个数与元素之间的关系不再发生改变,且要能够进行随机存取,因此采取顺序存储结构
多维数组用映射的方法映射到一维数组进行构建,即以行序为主序或以列序为主序
对于二维数组,按行进行优先存储的思想是:先行后列,行号较小的先存储,行号相同的先存储列号较小的
设二维数组行下标与列下标的范围分别为[L1,h1]与[L2,h2],则元素aij的存储地址为:
c是每个元素所占存储单元数目,LOC(al1l2)是第一个元素的存储地址,即基地址
二维数组按列优先存储的特点是:先存储列再行,列相同的情况下先存储行号较小的元素
4.4 矩阵的压缩存储
当矩阵满足:1.阶很高 2.很多值相同的元素且分布有一定的规律,即 特殊矩阵
当矩阵满足:1.有很多0元素,即 稀疏矩阵
矩阵压缩存储的思想是:1.为很多值相同的元素只分配一个内存空间 2.为0元素不分配存储空间
4.4.1 特殊矩阵的压缩存储
1.对称矩阵的压缩存储
对称矩阵关于主对角线对称,且具有aij=aji的特点,则只要存储下三角区域+对角线中的元素即可
原本需要n*n个存储空间,现在只需要n*(n+1)/2个存储空间
将相应的下三角区域+对角线区域的元素通过按行存储至一维数组当中,一维数组的下标k与原先下三角矩阵下标i,j的关系是:k=i*(i-1)/2+j-1,寻址的计算方法如图4-11
访问上三角矩阵的方法:1.通过下三角矩阵aij=aji 2.k=j*(j-1)/2+i-1
2.三角矩阵的压缩存储
三角矩阵的压缩存储与对称矩阵的压缩存储类似,以下三角矩阵为例,除了要存储下三角区域+对角线区域之外,还要存储上三角区域相同的元素,由于元素相同,因此只分配一个存储空间
三角矩阵的压缩存储也是按行存储到一维数组当中,一维数组k与三角矩阵i,j的关系是:k=(i-1)*(2n-i+2)/2+j-i,寻址的计算方法如图4-15
3.对角矩阵的压缩存储
对角矩阵指的是所有的非零元素都集中在对角线以及以若干条对角线为中心的次对角线上,其余位置全部为0
对角矩阵的压缩存储即把非零元素按行存储在一维数组内
4.4.2 稀疏矩阵的压缩存储
稀疏矩阵指的是零元素很多的矩阵,在压缩存储时,仅存储非零元素以及非零元素的行号和列号
则将非零元素表示为三元组(行号,列号,非零元素值),三元组的存储结构定义:
template
struct element
{
int row,col;
DataType item;
}
将一个矩阵中得到的所有三元组按照按行优先存储的元素转换成一个线性表,即三元组表
也就是把一个矩阵压缩为三元组表
1.三元组顺序表
三元组表的顺序存储结构即三元组顺序表
三元组顺序表在原来的元素信息上,还要存储线性表的总行数、总列数以及总的个数
const int MaxTerm=100;
struct SparseMatrix
{
element data[MaxTerm];
int mu,nu,tu; //行数、列数、非零元个数
}
2.十字链表
三元组表的链接存储结构即十字链表
链表结点由5个域构成,data存储本链表的信息,right指向元素的同一行的下一个三元组结点,down指向元素同一列的下一个三元组结点
struct OrthNode
{
element data;
struct OrthNode *right,*down;
}
为了实现对链表元素的快速读取,将各个元素的行、列头指针分别存储在一个数组内
4.5 扩展与提高
4.5.1 稀疏矩阵的转置运算
稀疏矩阵的压缩存储节约了内存空间,但失去了矩阵可以按下标存储的特性
设稀疏矩阵采用三元表顺序存储,将稀疏矩阵A转换为稀疏矩阵B
1.转置算法1——直接取,顺序存
步骤:(1)查找每一列三元组(2)转置(3)存储到B的三元组顺序表
#include
using namespace std;
struct element
{
int row, col;
int item;
};
const int MaxTerm = 100; //三元组表的最大存储空间
struct SparseMatrix
{
element data[MaxTerm];
int mu, nu, tu; //行数,列数,非零元个数
};
void Trans1(SparseMatrix& A, SparseMatrix& B);
void Print(SparseMatrix A);
int main()
{
SparseMatrix A = { {{1,1,3},{1,4,7},{2,3,-1},{3,1,2},{5,4,-8}},5,4,5 };
SparseMatrix B;
Trans1(A, B);
Print(B);
return 0;
}
void Trans1(SparseMatrix & A, SparseMatrix & B)
{
int pa, pb, col;
B.mu = A.nu;
B.nu = A.mu;
B.tu = A.tu;
pb = 0;
for(col=1;col<=A.nu;col++)
for(pa=0;pa
2.转置算法2——顺序取,直接存
转置算法1效率低是因为要对矩阵顺序表扫描第一列、再扫描第二列、再而扫描第三列等,要返回扫描多次顺序表
转置算法2只需要扫描一次
步骤:①找出非零三元组,顺序转置②放入B中
A中的第一列的第一个非零三元组元素一定存储在矩阵B的下标为0的位置,其余都存在后续连续的位置
第二列的第一个非零元素的位置=第一列的非零个数+第一列的第一个元素在B中存储的位置
加入两个辅助数组帮助确定在B中的位置
num[col]:矩阵A中某一列非零元素的个数
cpot[col]:矩阵A中某一列的第一个非零元素在矩阵B的位置
cpot[1]=0;
cpot[n]=num[n-1]+cpot[n-1];
#include
using namespace std;
struct element
{
int row, col;
int item;
};
const int MaxTerm = 100; //三元组表的最大存储空间
struct SparseMatrix
{
element data[MaxTerm];
int mu, nu, tu; //行数,列数,非零元个数
};
void Trans1(SparseMatrix& A, SparseMatrix& B);
void Print(SparseMatrix A);
int main()
{
SparseMatrix A = { {{1,1,3},{1,4,7},{2,3,-1},{3,1,2},{5,4,-8}},5,4,5 };
SparseMatrix B;
Trans1(A, B);
Print(B);
return 0;
}
void Trans1(SparseMatrix & A, SparseMatrix & B)
{
int i,j,k,num[MaxTerm]={0},cpot[MaxTerm]={0};
B.mu=A.nu;
B.nu=A.mu;
B.tu=A.tu;
for(int i=0;i
3.5.2 广义表
规定数据类型相同的才可以为线性表
而广义表则指的是可以容纳几种类型不同(结构不同)的线性表
1.广义表的定义
广义表是n个元素的有限序列,每个元素可以表示单个元素,也可以表示一个单独的广义表,分别表示广义表的单元素和子表,通常用大写字母表示广义表,小写字母表示单元素
在非空广义表中,第一个元素称为表头,其余元素称为表尾,元素的个数称为广义表的长度,括号的嵌套层数称为广义表的深度
实例:
A=() 空表,长度为0,深度为1
B=(e) 只有一个单元素,长度为1,深度为1
C=(a,(b,c,d)) 只有一个单元素和子表,长度为2,深度为2
D=(A,B,C) 有三个子表,长度为3,深度为3
E=(e,E) 递归表,长度为2,深度为无穷大
F=(()) 只有一个空表,长度为1,深度炜2
用逻辑结构图表示以上例子:
2.广义表的抽象数据类型定义
广义表的2个重要操作:取表头和取表尾,与线性表的操作基本一致
InitLists
输入:无
功能:初始化广义表
输出:空的广义表
DestroyLists
输入:无
功能:销毁广义表
输出:无
Length
输入:无
功能:求广义表的长度
输出:广义表所含有的数据元素个数
Depth
输入:无
功能:求广义表的深度
输出:括号的最大嵌套数
Head
输入:无
功能:求广义表的表头
输出:广义表中第一个元素
Tail
输入:无
功能:求广义表的表尾
输出:广义表的表尾
3.广义表的存储结构
由于广义表的共享性与递归性等,用顺序表难以进行表示,因此用链接存储结构可以很好地表示
一个表头和表尾可唯一地表示一个广义表,即头尾表示法
链接存储的结点有两种,一种是元素结点,存储单元素,一种是表结点,存储子表,因此要用一个标志域对这两种结点加以区分,当标志域=1时,表示表结点,标志域=0时,表示元素结点
用C++代码表示广义表的存储结构:
enum Elemtag(Atom,List); //子元素、子表
template
struct GLNode
{
Elemtag tag; //区分表结点和元素结点
union //元素结点和表结点的联合部分
{
DataType data;
struct
{
struct GLNode *hp,*tp; //分别指向表头和表尾
}ptr;
};
};
4.6 应用实例
4.6.1 发纸牌
题目:发纸牌,假设纸牌的花色有梅花、方块、红桃、黑桃,纸牌的点数有2 3 4 5 6 7 8 9 10 J Q K A,要求根据用户输入的纸牌张数n,随机发n张纸牌
解题思路:用sign数组标记是否发出了纸牌,用card数组表示要输出的纸牌——创建纸牌类,完成创建纸牌、销毁纸牌、设定发出纸牌、输出纸牌的函数定义和实现——用memset初始化数组,用srand完成随机数设定
知识点:
memset函数:(39条消息) memset_面包呢的博客-CSDN博客
memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值, 这个函数通常为新申请的内存做初始化工作。
srand:(39条消息) 关于rand()和srand()_微念的博客-CSDN博客
srand()为初始化随机数发生器,用于设置rand()产生随机数时的种子。传入的参数seed为unsigned int类型,通常我们会使用time(0)或time(NULL)的返回值作为seed。
代码:
#include
#include
#include
using namespace std;
int sign[4][13] = { 0 }; //标志数组
string str1[4] = { "梅花","黑桃","红桃","方块" };
string str2[13] = { "2","3","4","5","6","7","8","9","10","J","Q","K","A" };
class PlayCard
{
public:
PlayCard();
~PlayCard();
void SendCards(int n);
void PrintCards();
private:
string card[13]; //最多发13张牌
int num; //发牌张数
};
PlayCard::PlayCard()
{
num = 0;
}
PlayCard::~PlayCard()
{
}
void PlayCard::SendCards(int n)
{
int i, j, k;
num = n;
memset(sign, 0, n); //初始化标志数组,所有牌未发出
srand(time(NULL)); //初始化随机种子为当前系统时间
for (k = 0; k < n;) //省略表达式3,发第k张牌
{
i = rand() % 4; //随机生成花色的下标
j = rand() % 13; //随机生成点数的下标
if (sign[i][j] == 1) //这张牌已发出
continue; //跳过循环体余下语句
else
{
card[k] = str1[i] + str2[j]; //连接str1和str2
sign[i][j] = 1; //标识这张牌已发出
k++; //准备发下一张牌
}
}
}
void PlayCard::PrintCards()
{
for (int k = 0; k < num; k++)
cout << card[k] << "\t"; //输出第k张牌
cout << endl;
}
int main()
{
int n;
PlayCard P{};
cout << "请输入要发牌的张数:";
cin >> n;
P.SendCards(n);
P.PrintCards();
return 0;
}
4.6.2 以列序为主序存储矩阵
#include
using namespace std;
const int MaxSize=100;
int main()
{
int *A=new int[MaxSize];
int i,j,x,y;
cout<<"请输入矩阵的行和列:";
cin>>x>>y;
cout<<"请输入你的矩阵元素:";
for(i=1;i<=x;i++)
for(j=1;j<=y;j++)
cin>>A[(j-1)*x+i-1];
cout<<"按列为主序输出你的矩阵为:";
for(i=0;i>i>>j;
cout<<"第"<
第5章 数和二叉树
5.1 引言
5.2 树的逻辑结构
5.2.1 树的定义和基本术语
1.树的定义
结点:树的元素
树:由n个有限结点组成的集合
空树:0个结点的树
根的结点:每棵树顶头的那个结点。每棵树有且具有一个根节点
子树:除了根结点之外其他结点组成的树的互不相交的有限序列
2.树的基本术语
结点的度、树的度:结点所拥有的子树的数目,而一棵树中拥有最多子树的结点的度就是该树的度
叶子结点、分支结点:下面没有/有任何分支的结点
兄弟结点:拥有同一个双亲结点
路径、路径长度:从树中某结点到另一个结点的边数即为路径长度
树的深度(高度):树的最大层数
树的宽度:层中最多数量的结点数
5.2.2 树的抽象数据类型定义
InitTree
输入:无
功能:初始化一棵树
输出:一个空树
DestroyTree
输入:无
功能:销毁一棵树
输出:释放该树占用的存储空间
PreOrder
输入:无
功能:前序遍历树
输出:树的前序遍历序列
PostOrder
输入:无
功能:后序遍历树
输出:树的后序遍历序列
LeverOrder
输入:无
功能:层序遍历树
输出::树的层序遍历序列
5.2.3 树的遍历操作
树的遍历操作指的是从根结点出发每个结点都可通过某种次序访问到,且有且仅访问一次
(1)先序遍历:先访问根节点,再访问左子树,最后访问右子树。
(2) 后序遍历:先左子树,再右子树,最后根节点。
(3)中序遍历:先左子树,再根节点,最后右子树。
(4)层序遍历:每一层从左到右访问每一个节点。
每一个子树遍历时依然按照此时的遍历顺序。
如下图:
先序遍历:FCADBEHGM
后序遍历:ABDCHMGEF
中序遍历:ACBDFHEMG
层序遍历:FCEADHGBM,层序遍历一般很少用。
5.3 树的存储结构
不仅要能表示结点本身的数据,还要能够表示树之间的父子关系
5.3.1 双亲表示法
根据树的特性,每个结点有且只有一个双亲结点。树中的结点可以用一维数组存储(通常按层序存储),结点不仅存储自身的信息,还要存储双亲结点在数组中的下标值
template
struct PNode
{
DataType data;
int Parent;
}
parent值为-1表示这个结点是根结点,没有双亲结点
5.3.2 孩子表示法
基于链表的存储方法,即列出每个结点的孩子结点的下标位置,每个结点再构成一个线性表,方便存储
struct CTNode //孩子结点
{
int child;
CTNode *next;
};
template
struct CBNode
{
DataType data;
CTNode *firstChild;
};
5.3.3 孩子兄弟表示法
又称二叉链表法,指的是当前结点除了存储自己的数据外,还要定义两个指针,一个指向这个结点的第一个孩子,一个指向右兄弟
template
struct TNode
{
DataType data;
TNode *firstChild,*rightSib;
}
想要找到某个结点的孩子或者右兄弟,只要找到第1个结点之后,再找k-1个结点即可
5.4 二叉树的逻辑结构
5.4.1 二叉树的定义
二叉树是n个结点的有限集合,由根结点和互不相交的左、右子树构成
二叉树特征:
①每个结点的度最大只为2
②二叉树有左右子树之分,不可以任意颠倒,哪怕只有左子树,都要说明它是左子树还是右子树
③并不是所有的二叉树的结点度都为2,如图所示,此时度为1,但它仍然是二叉树
④树的孩子只有序的区别,而二叉树的孩子必须有左右之分。
几种特殊的二叉树:
①斜树
只有左结点的二叉树称为左斜树,只有右结点的二叉树称为右斜树
左斜树和右斜树统称为斜树
斜树的特点:1.每一层只有一个结点 2.结点个数=深度
②满二叉树
结点把二叉树所有分支都占满了,即,除根结点和叶子结点以外的结点度都为2
③完全二叉树
深度为k的完全二叉树在第k-1层是满二叉树,且叶子结点只存在于其下两层,并且叶子结点集中在左侧。
度为1的结点只有一个,且只有左孩子
5.4.2 二叉树的基本性质
性质5-1 度为0的结点个数=度为2的结点个数+1
性质5-2 二叉树的第i层上最多有2的i-1次方个结点
性质5-3 一棵深度为k的二叉树,最多有2的k-1次方个结点
性质5-4 具有n个结点的完全二叉树深度为(以2为底n取对数)+1
性质5-5 具有n个结点的完全二叉树从1开始按层序编号,则有以下关系:
①i>1时,其双亲结点的编号为(i/2),否则说明这个结点是根结点,没有双亲
②2i<=n时,其左孩子的编号为(2i),否则说明i结点没有左孩子
③2i+1<=n时,其右孩子的编号为(2i+1),否则说明i结点没有右孩子
5.4.3 二叉树的抽象数据类型定义
InitBiTree
输入:无
功能:初始化一棵二叉树
输出:一个空的二叉树
CreatBiTree
输入:n个结点的数据及结点之间的关系
功能:建立一棵二叉树
输出:含有n个结点的二叉树
DestroyBiTree
输入:无
功能:销毁一棵二叉树
输出:释放二叉树占用的存储空间
PreOrder
输入:无
功能:前序遍历二叉树
输出:二叉树的前序遍历序列
InOrder
输入:无
功能:中序遍历二叉树
输出:二叉树的中序遍历序列
PostOrder
输入:无
功能:后序遍历二叉树
输出:二叉树的后序遍历序列
LevelOrder
输入:无
功能:层次遍历二叉树
输出:二叉树的层次遍历序列
5.4.4 二叉树的遍历操作
与树的遍历类似
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
确定一棵树:
前序,后序序列确定根结点
中序序列确定根结点的左右子树
5.5 二叉树的存储结构
5.5.1 顺序存储结构
用一维数组存储具有父子关系的树结点,从1开始按照层序关系给结点编号后放入下标为0开始的数组
缺点:浪费空间,因此存储完全二叉树的时候才适合用顺序存储结构
5.5.2 二叉链表
1.二叉链表的存储结构
二叉树一般用二叉链表进行存储,即每个链表结点除了存储树结点之外,还要存储这个结点的左、右孩子指针
template
struct BiNode
{
DataType data;
BiNode *lchild,*rchild;
}
2.二叉链表的实现
为了避免调用者对私有信息根结点root的调用,在private中定义了包含根结点的相关函数为私有函数
template
class BiTree
{
public:
BiTree() {root=Creat();} //构造函数,建立一棵二叉树
~BiTree() {Release(root);} //析构函数,释放各结点的存储空间
void PreOrder() {PreOrder(root);} //前序遍历二叉树
void InOrder() {InOrder(root);} //中序遍历二叉树
void PostOrder() {PostOrder(root);} //后序遍历二叉树
void LevelOrder(); //层序遍历二叉树
private:
BiNode *Creat(); //构造函数调用
void Release(BiNode* bt); //析构函数调用
void PreOrder(BiNode* bt); //前序遍历函数调用
void InOrder(BiNode* bt); //中序遍历函数调用
void PostOrder(BiNode* bt); //后序遍历函数调用
BiNode* root; //指向根结点的头指针
};
树是递归构造的,因此也用递归函数实现
(1)前序遍历
template
void BiTree ::PreOrder(BiNode* bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
cout << bt->data; //访问根结点bt的数据域
PreOrder(bt->lchild); //前序递归遍历bt的左子树
PreOrder(bt->rchild); //前序递归遍历bt的右子树
}
}
(2)中序遍历
template
void BiTree ::InOrder(BiNode* bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
InOrder(bt->lchild); //前序递归遍历bt的左子树
cout << bt->data; //访问根结点bt的数据域
InOrder(bt->rchild); //前序递归遍历bt的右子树
}
}
(3)后序遍历
template
void BiTree ::PostOrder(BiNode* bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
InOrder(bt->lchild); //前序递归遍历bt的左子树
InOrder(bt->rchild); //前序递归遍历bt的右子树
cout << bt->data; //访问根结点bt的数据域
}
}
(4)层次遍历
队列实现,为了方便,采取顺序队列实现,且假设其不会发生假溢出
template
void BiTree ::LeverOrder()
{
BiNode* Q[100], * q = nullptr; //顺序队列最多100个结点
int front = -1, rear = -1; //队列初始化
if (root == nullptr) return; //二叉树为空,算法结束
Q[++rear] = root; //根指针入队
while (front != rear) //当队列非空时
{
q = Q[++front]; //出队
cout << q->data;
if (q->lchild != nullptr) Q[++rear] = q->lchild;
if (q->rchild != nullptr) Q[++rear] = q->rchild;
}
}
(5)构造函数——建造二叉树
由于前序遍历,后序遍历,中序遍历中的任何一个都不能够确定一个二叉树,因此采用扩展二叉树的办法确定一棵二叉树。即为每个结点的空指针构造一个虚结点'#',一个扩展二叉树序列可以唯一确定一棵二叉树
template
BiNode* BiTree ::Creat(BiNode* bt)
{
char ch;
cout << "请输入扩展二叉树的前序遍历序列,每次输入一个字符:";
cin >> ch; //输入结点的数据信息,假设为字符
if (ch == '#') bt = nullptr; //建立一棵空树
else {
bt = new BiNode; bt->data = ch;
bt->lchild = Creat(bt->lchild); //递归建立左子树
bt->rchild = Creat(bt->rchild); //递归建立右子树
}
return bt;
}
(6)析构函数——销毁二叉树
后序遍历销毁二叉树,释放占用的空间
template
void BiTree ::Release(BiNode* bt)
{
if (bt == nullptr) return;
else {
Release(bt->lchild); //释放左子树
Release(bt->rchild); //释放右子树
delete bt; //释放根结点
}
}
3.二叉链表的使用
int main()
{
BiTree T; //定义对象变量T
cout << "该二叉树的前序遍历序列是:";
T.PreOrder();
cout << "\n该二叉树的中序遍历序列是:";
T.InOrder();
cout << "\n该二叉树的后序遍历序列是:";
T.PostOrder();
cout << "\n该二叉树的层序遍历序列是:";
T.LeverOrder();
return 0;
}
5.5.3 三叉链表
比二叉链表多一个指向双亲结点的指针,可直接访问某个结点的双亲
三叉链表相对于二叉链表来说,增加了空间开销
5.5.4 二叉链表源代码
#include
using namespace std;
template
struct BiNode
{
DataType data;
BiNode* lchild, * rchild;
};
template
class BiTree
{
public:
BiTree() { root = Creat(root); } //构造函数,建立一棵二叉树
~BiTree() { Release(root); } //析构函数,释放各结点的存储空间
void PreOrder() { PreOrder(root); } //前序遍历二叉树
void InOrder() { InOrder(root); } //中序遍历二叉树
void PostOrder() { PostOrder(root); } //后序遍历二叉树
void LeverOrder(); //层序遍历二叉树
private:
BiNode* Creat(BiNode* bt); //构造函数调用
void Release(BiNode* bt); //析构函数调用
void PreOrder(BiNode* bt); //前序遍历函数调用
void InOrder(BiNode* bt); //中序遍历函数调用
void PostOrder(BiNode* bt); //后序遍历函数调用
BiNode* root; //指向根结点的头指针
};
template
void BiTree ::PreOrder(BiNode* bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
cout << bt->data; //访问根结点bt的数据域
PreOrder(bt->lchild); //前序递归遍历bt的左子树
PreOrder(bt->rchild); //前序递归遍历bt的右子树
}
}
template
void BiTree ::InOrder(BiNode* bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
InOrder(bt->lchild); //前序递归遍历bt的左子树
cout << bt->data; //访问根结点bt的数据域
InOrder(bt->rchild); //前序递归遍历bt的右子树
}
}
template
void BiTree ::PostOrder(BiNode* bt)
{
if (bt == nullptr) return; //递归调用的结束条件
else {
InOrder(bt->lchild); //前序递归遍历bt的左子树
InOrder(bt->rchild); //前序递归遍历bt的右子树
cout << bt->data; //访问根结点bt的数据域
}
}
template
void BiTree ::LeverOrder()
{
BiNode* Q[100], * q = nullptr; //顺序队列最多100个结点
int front = -1, rear = -1; //队列初始化
if (root == nullptr) return; //二叉树为空,算法结束
Q[++rear] = root; //根指针入队
while (front != rear) //当队列非空时
{
q = Q[++front]; //出队
cout << q->data;
if (q->lchild != nullptr) Q[++rear] = q->lchild;
if (q->rchild != nullptr) Q[++rear] = q->rchild;
}
}
template
BiNode* BiTree ::Creat(BiNode* bt)
{
char ch;
cout << "请输入扩展二叉树的前序遍历序列,每次输入一个字符:";
cin >> ch; //输入结点的数据信息,假设为字符
if (ch == '#') bt = nullptr; //建立一棵空树
else {
bt = new BiNode; bt->data = ch;
bt->lchild = Creat(bt->lchild); //递归建立左子树
bt->rchild = Creat(bt->rchild); //递归建立右子树
}
return bt;
}
template
void BiTree ::Release(BiNode* bt)
{
if (bt == nullptr) return;
else {
Release(bt->lchild); //释放左子树
Release(bt->rchild); //释放右子树
delete bt; //释放根结点
}
}
int main()
{
BiTree T; //定义对象变量T
cout << "该二叉树的前序遍历序列是:";
T.PreOrder();
cout << "\n该二叉树的中序遍历序列是:";
T.InOrder();
cout << "\n该二叉树的后序遍历序列是:";
T.PostOrder();
cout << "\n该二叉树的层序遍历序列是:";
T.LeverOrder();
return 0;
}
5.6 森林
5.6.1 森林的逻辑结构
森林是由n棵树构成的集合
若增加一个根结点,则森林又变成了一棵树
若删除了树原本的根结点,则树又变成了森林
遍历森林的方式即依次遍历森林中的每一棵树
5.6.2 森林、树与二叉树的转换
树的孩子兄弟表示法在结构上等同于二叉链表的表示,即树的孩子兄弟表示法可以唯一地对应于一棵二叉树,两者可以相互转换
1.树转换为二叉树
①加线——为兄弟结点加一条连线
②去线——双亲结点只连接第一个孩子结点,把连接的跟其他孩子结点的连线去掉
③层次调整——将原先树的右结点变为二叉树的右孩子,因此根结点一定没有右子树
树的前序遍历序列对应二叉树的前序遍历序列
树的前序遍历:ABEFCDG
二叉树的前序:ABEFCDG
树的后序遍历序列对应二叉树的中序遍历序列
树的后序遍历:EFBCGDA
二叉树的中序:EFBCGDA
2.森林转换为二叉树
①将森林中的每棵树转换为二叉树,即兄弟结点变为二叉树的孩子
②将每棵树的根结点视为兄弟,为所有根结点连线
③根据二叉树的关系调整层次,连线的兄弟结点变成了二叉树的子结点
可以看出,转换后的二叉树有右子树,且根结点及其右分支的结点个数=原先森林中树的个数
森林的前序遍历序列对应二叉树的前序遍历序列
森林前序遍历:ABCDEFGHIJ
二叉树的前序:ABCDEFGHIJ
森林的后序遍历序列对应二叉树的中序遍历序列
森林后序遍历:BADEFCHJIG
二叉树的中序:BADEFCHJIG
3.二叉树转换为树或森林
树或森林都可以转换为二叉树,它们之间的区别是:树转换的二叉树无右子树,而森林转换的二叉树有右子树
二叉树转换为树或森林的方法:
①连线——若x是y的左孩子,则把x的右孩子,x的右孩子的右孩子,x的右孩子的右孩子的...都与y连线
②去线——把原二叉树的双亲结点跟右孩子的连线去掉
③调整树或者森林的层次
5.7 最优二叉树
5.7.1 哈夫曼算法
叶子结点的权值是对叶子结点赋予的一个具有实际意义的值,一般来说
带权路径长度(WPL)=(叶子结点的权值*从根结点到该结点的路径长度)+每个带权叶子结点累加
给定一组权值点可以构造出不同带权路径长度的树,其中,带权路径长度最小的二叉树为最优二叉树,也称哈夫曼树
要使一棵二叉树的带权路径长度最小,则要将权值越小的放在最远处,权值越大的放在根结点最近处
n个叶子结点的哈夫曼树经过n-1次分配后有n-1个分支结点,即一棵哈夫曼树一共有2n-1个结点
为了便于找到哈夫曼树中的最小权值等,设置数组保存哈夫曼树中的结点信息。通常将哈夫曼树的结点分成4个部分,即weight(存储结点权值),lchild(存储左孩子的下标值),rchild(存储右孩子的下标值),parent(存储双亲结点的下标值)
struct ElemType
{
int weight;
int parent,lchild,rchild;
}
先将哈夫曼树的权值结点存储到数组中,再将结合之后的新结点存储到数组中
#include
using namespace std;
struct ElemType
{
int weight; //假定权值为整数
int parent, lchild, rchild; //下标
};
class HuffmanTree
{
public:
HuffmanTree(int w[], int n); //w[]为存放权值的数组,n为权值的个数
HuffmanTree(); //构造哈夫曼树
void Print();
private:
ElemType* huffTree;
int num;
void Select(int n, int& i1, int& i2); //i1和i2是权值最小的两个结点
};
void HuffmanTree::Select(int n, int& i1, int& i2)
{
int i = 0, temp;
for(;i huffTree[i2].weight)
{
temp = i1;
i1 = i2;
i2 = temp;
}
for (i = i + 1; i < n; i++)
{
if (huffTree[i].parent == -1)
{
if (huffTree[i].weight < huffTree[i1].weight)
{
i2 = i1;
i1 = i;
}
else if (huffTree[i].weight < huffTree[i2].weight)
{
i2 = i;
}
}
}
}
HuffmanTree::HuffmanTree(int w[], int n)
{
int i, k, i1, i2;
huffTree = new ElemType[2 * n - 1];
num = n;
for (i = 0; i < 2 * num - 1; i++) //初始化,所有结点均没有双亲和孩子
{
huffTree[i].parent = -1;
huffTree[i].lchild = huffTree[i].rchild = -1;
}
for (i = 0; i < num; i++) //存储叶结点的权值
huffTree[i].weight = w[i];
for (k = num; k < 2 * num - 1; k++) //n-1次合并
{
Select(k, i1, i2); //权值最小的根结点下标是i1和i2
huffTree[k].weight = huffTree[i1].weight + huffTree[i2].weight;
huffTree[i1].parent = k;
huffTree[i2].parent = k;
huffTree[k].lchild = i1;
huffTree[k].rchild = i2;
}
}
void HuffmanTree::Print()
{
int i, k;
cout << "每个叶子结点到根结点的路径是:" << endl;
for (i = 0; i < num; i++)
{
cout << huffTree[i].weight;
k = huffTree[i].parent;
while (k != -1)
{
cout << "-->" << huffTree[k].weight;
k = huffTree[k].parent;
}
cout << endl;
}
}
int main()
{
int w[] = { 2,3,4,5,6 };
HuffmanTree T{ w,5 };
T.Print();
return 0;
}
运行结果:
5.7.2 哈夫曼编码
编码:将字符集的每个字符赋予二进制位串表示
等长编码:当每个字符出现频率相同的时候即使用相同长度的二进制编码表示它们,这样效率最高
不等长编码:当每个字符出现频率不同的时候用不同长度的二进制编码表示它们,频率高的使用较短编码,频率低的用较长编码表示,这样效率最高,也是文件压缩技术核心思想
前缀编码(前缀无歧义编码):当使用不等长编码时,要考虑解码的唯一性,即一个编码可能会同时表示多种不同的字符,要避免这种情况,即使用前缀不会与其他字符相冲突的编码
哈夫曼编码:处理最短的不等长编码方案。将哈夫曼树的左分支设为0,右分支设为1。从根结点到某结点中经过的路径中的'0'、'1'组成的序列便是该叶子结点对应字符的编码,即哈夫曼编码
哈尔曼树的叶子结点的平均深度即平均编码的长度,树的带权路径长度=(各个字符的码长*出现次数)+累加
5.8 扩展与提高
5.8.1 二叉树遍历的非递归算法
递归算法虽然简洁,但执行效率不高
1.前序遍历的非递归算法
2.中序遍历的非递归算法
#include
using namespace std;
template
struct BiNode
{
DataType data;
BiNode* lchild, * rchild;
};
template
class BiTree
{
public:
BiTree() { root = Creat(root); } //构造函数,建立一棵二叉树
~BiTree() { Release(root); } //析构函数,释放各结点的存储空间
void PreOrder(); //前序遍历二叉树
private:
BiNode* Creat(BiNode* bt); //构造函数调用
void Release(BiNode* bt); //析构函数调用
BiNode* root; //指向根结点的头指针
};
template
void BiTree ::PreOrder()
{
BiNode* bt = root;
BiNode* S[100]; //顺序栈,最多100个结点指针
int top = -1; //顺序栈初始化
while (bt != nullptr || top != -1) //两个条件都不成立才退出循环
{
while (bt != nullptr)
{
S[++top] = bt; //将根指针bt入栈
bt = bt->lchild;
}
if (top != -1) { //栈非空
bt = S[top--];
cout << bt->data;
bt = bt->rchild;
}
}
}
template
BiNode* BiTree ::Creat(BiNode* bt)
{
char ch;
cout << "请输入扩展二叉树的前序遍历序列,每次输入一个字符:";
cin >> ch; //输入结点的数据信息,假设为字符
if (ch == '#') bt = nullptr; //建立一棵空树
else {
bt = new BiNode; bt->data = ch;
bt->lchild = Creat(bt->lchild); //递归建立左子树
bt->rchild = Creat(bt->rchild); //递归建立右子树
}
return bt;
}
template
void BiTree ::Release(BiNode* bt)
{
if (bt == nullptr) return;
else {
Release(bt->lchild); //释放左子树
Release(bt->rchild); //释放右子树
delete bt; //释放根结点
}
}
int main()
{
BiTree T{ }; //定义对象变量T
cout << "该二叉树的中序遍历序列是:";
T.PreOrder();
return 0;
}
3.后序遍历的非递归算法
不懂
5.8.2 线索链表
1.线索链表的存储结构
树中空的指针域将其指向按某种遍历顺序的某结点的前驱或者后继结点,将指向前驱或者后继结点的指针称为线索,有线索的二叉链表称为线索二叉链表
在链表中,若左指针域为空,则存储该结点的前驱结点的地址;若右指针域为空,则存储该结点的后继结点的地址;
为了区别线索和孩子结点指针,每个结点增设标志位ltag和rtag
template
class ThrNode
{
DataType data;
int ltag,rtag;
ThrNode *lchild,*rchild;
};
由于二叉树有四种遍历顺序,因此线索链表也有四种,以中序线索链表为例:
2.线索链表的实现
不懂
5.9 应用实例
5.9.1 堆与优先队列
1.堆的定义
优先队列指的是按照某种优先级排列队列中的元素,优先级越高的元素出队越快,优先级相同的情况下按照先进先出的原则进行。可以先将元素排列好优先级再入队,也可以在队里面排列好再出队。
堆指的是具有特殊关系的完全二叉树。
堆 |
堆的分类 |
文字描述 |
数学关系 |
大根堆 |
每个根结点大于或等于左右孩子结点的堆 |
a[i]>=a[2i]&&a[i]>=a[2i+1] |
小根堆 |
每个根结点小于或等于左右孩子结点的堆 |
a[i]<=a[2i]&&a[i]<=a[2i+1] |
可知,堆的根结点一定是最大或者最小的元素。大根堆实现的队列为极大队列,小根堆实现的队列为极小队列
2.极大队列的存储结构
顺序存储结构存储堆,将堆按照层序排列从1开始编号,按编号从小到大的关系存储入一维数组
3.极大队列基本操作的实现
(1)入队操作
入队之后的结点需要与双亲结点进行大小比较,若结点值>双亲结点,则将两者位置互换。一直比较到成堆结束
template
void PriQueue::EnQueue(DataType x)
{
if (rear == QueueSize - 1) throw"上溢异常";
int temp, i;
i = rear++;
data[i] = x;
while (i / 2 > 0 && data[i / 2] < x)
{
temp = data[i];
data[i] = data[i / 2];
data[i / 2] = temp;
i = i / 2;
}
}
(2)出队操作
由于队列出队时根结点默认出队,为了维护堆的稳定性,根结点出列后将末尾结点移到根结点
第六章 图
6.1 引言
6.2 图的逻辑结构
6.2.1 图的定义和基本术语
顶点:图的数据元素
1.图的定义
图:由顶点和顶点间的边组成,一般表示为 G=(V,E)
无向边:两个顶点之间的边没有方向,用无序偶对表示为(vi,vj)
有向边/弧:两个顶点之间的边有方向,用有序偶对表示,其中,vi是弧尾,vj是弧头
无向图:任意两个顶点之间的边都是无向边
有向图:任意两个顶点之间的边都是有向边
带权图/网图:边带有权值的图
2.图的基本术语
(1)邻接、依附
在无向图中,若vi,vj之间存在(vi,vj),则说明vi与vj互为邻接点,且边(vi,vj)依附于点vi和vj
在有向图中,若vi,vj之间存在,则说明vi邻接到vj或vj邻接自vi,且边依附于顶点vi和vj,通常称vj是vi的邻接点
(2)顶点的度、入度、出度
在无向图中,顶点v的度指的是依附于该点的边的个数
在有向图中,顶点v的入度指的是以该点为弧头的边的个数,为ID(v),出度指的是以该点为弧尾的边的个数,为OD(v)
(3)无向完全图、有向完全图
在无向图中。任意两个顶点之间都有无向边,则称无向完全图,具有n个顶点的无向完全图中有n*(n-1)/2条边
在有向图中,任意两个顶点之间都有方向相反的两条弧,则称为有向完全图,具有n个顶点的有向完全图有n*(n-1)条边
(4)稀疏图、稠密图
边数很少的图叫做稀疏图,反之就是稠密图