数组、串与广义表
数组
std::vector
是 C++ 标准模板库(STL)中的一个动态数组容器,提供了丰富的成员函数来方便对数组进行操作。以下是一些常见的 std::vector
成员函数:
-
构造和赋值操作:
vector()
:构造函数,创建一个空的向量。vector(size_type n, const T& val = T())
:构造函数,创建包含 n 个元素,每个元素的值都是 val。vector(const vector& x)
:复制构造函数,创建一个与 x 同样的向量。vector(begin, end)
:构造函数,创建一个包含区间 [begin, end) 中元素的向量。operator=
:赋值操作符,用一个向量替换当前向量的内容。
-
访问元素:
operator[]
:访问指定位置的元素。at(index)
:访问指定位置的元素,并进行边界检查。front()
:返回第一个元素。back()
:返回最后一个元素。data()
:返回指向数组中的第一个元素的指针。
-
迭代器:
begin()
:返回指向第一个元素的迭代器。end()
:返回指向最后一个元素的迭代器。rbegin()
:返回指向最后一个元素的反向迭代器。rend()
:返回指向第一个元素的反向迭代器。
-
容量:
size()
:返回向量中元素的个数。max_size()
:返回向量可能包含的最大元素数。empty()
:检查向量是否为空。capacity()
:返回向量当前能容纳的元素数。reserve(n)
:请求向量容纳至少 n 个元素的空间。shrink_to_fit()
:请求移除多余的容量。
-
修改:
push_back(val)
:在向量的末尾添加一个元素。pop_back()
:删除向量的最后一个元素。insert(position, val)
:在指定位置插入一个元素。erase(position)
:删除指定位置的元素。clear()
:清空向量的内容。swap(x)
:交换两个向量的内容。
这里只是列举了一些常见的函数,std::vector
还有其他一些成员函数和操作符,具体可以查阅相关的C++标准库文档。
二维数组地址计算(行优先)
在C++中,二维数组在内存中是按照行主序(Row-major order)存储的。这意味着数组的每一行是顺序存储的,而每一列则是在内存中相邻的。
考虑一个int
类型的二维数组 arr[row][col]
,其中row
表示行数,col
表示列数。数组的地址计算可以通过以下公式来理解:
$\text{address} = \text{base_address} + (\text{row} \times \text{number_of_columns} + \text{col}) \times \text{element_size} $
其中:
- base_address \text{base\_address} base_address是数组的起始地址。
- row \text{row} row 是所需元素所在的行数。
- col \text{col} col 是所需元素所在的列数。
- number_of_columns \text{number\_of\_columns} number_of_columns 是数组的列数。
- element_size \text{element\_size} element_size 是数组中每个元素的大小(以字节为单位)。
这个公式可以用来计算数组中特定元素的地址。请注意,C++中数组的索引从0开始,所以如果想要访问数组中第一行第一列的元素,将 row \text{row} row 和 col \text{col} col 都设为0。
![image-20231113161256878](https://i-blog.csdnimg.cn/blog_migrate/a7ddbe4384413e6382a436e31f77fb16.png)
![image-20231113185933082](https://i-blog.csdnimg.cn/blog_migrate/f6e17ab6e19efa2efdc02f56d0c911a4.png)
稀疏矩阵
当矩阵中的非零元素<<矩阵的最大容量 成为稀疏矩阵
要求掌握稀疏矩阵的转置
#include <iostream>
const int defaultSize = 100;
template <class T>
struct Trituple {
int row, col; // 非零元素的行号、列号
T value; // 非零元素的值
Trituple<T>& operator=(Trituple<T>& x) {
row = x.row;
col = x.col;
value = x.value;
return *this;
}
};
template <class T>
class SparseMatrix {
public:
SparseMatrix(int maxSz = defaultSize);
~SparseMatrix() { delete[] elem; }
SparseMatrix<T>& operator=(SparseMatrix<T>& x);
void Transpose(SparseMatrix<T>& b);
void Add(SparseMatrix<T>& b, SparseMatrix<T>& c);
void Multiply(SparseMatrix<T>& b, SparseMatrix<T>& c);
friend std::ostream& operator<<(std::ostream& out, SparseMatrix<T>& M);
friend std::istream& operator>>(std::istream& in, SparseMatrix<T>& M);
private:
int Rows, Cols, Terms; // 矩阵行数、列数和非零元素数
Trituple<T>* elem; // 三元组表
//存放的是三元组的指针而不是每一个数据的值
int maxTerms; // 三元组表最大可容纳元素数
};
template <class T>
SparseMatrix<T>::SparseMatrix(int maxSz) : maxTerms(maxSz) {
elem = new Trituple<T>[maxSz];
if (elem == NULL) {
std::cerr << "存储分配错!" << std::endl;
exit(1);
}
Rows = Cols = Terms = 0;
}
template <class T>
SparseMatrix<T>& SparseMatrix<T>::operator=(SparseMatrix<T>& x) {
Rows = x.Rows;
Cols = x.Cols;
Terms = x.Terms;
maxTerms = x.maxTerms;
delete[] elem;
elem = new Trituple<T>[maxTerms];
if (elem == NULL) {
std::cerr << "存储分配错!" << std::endl;
exit(1);
}
for (int i = 0; i < Terms; ++i) {
elem[i] = x.elem[i];
}
return *this;
}
template <class T>
void SparseMatrix<T>::Transpose(SparseMatrix<T>& b) {
// 将稀疏矩阵 (*this) 转置,结果存储在稀疏矩阵 b 中,并通过函数返回。
// 设置矩阵 b 的行数、列数和非零元素数
b.Rows = Cols;
b.Cols = Rows;
b.Terms = Terms;
// 如果矩阵有非零元素,进行转置操作
if (Terms > 0) {
int k, i, CurrentB = 0; // 存放位置指针
// 按列号做 Cols 趟扫描
for (k = 0; k < Cols; ++k) {
// 在数组中找列号为 k 的三元组
for (i = 0; i < Terms; ++i) {
if (elem[i].col == k) {
// 将元素放入矩阵 b 的对应位置
b.elem[CurrentB].row = k;
b.elem[CurrentB].col = elem[i].row;
b.elem[CurrentB].value = elem[i].value;
CurrentB++; // 存放指针加 1
}
}
}
}
}
template <class T>
void SparseMatrix<T>::Add(SparseMatrix<T>& b, SparseMatrix<T>& c) {
// 实现矩阵相加
}
template <class T>
void SparseMatrix<T>::Multiply(SparseMatrix<T>& b, SparseMatrix<T>& c) {
// 实现矩阵相乘
}
template <class T>
std::ostream& operator<<(std::ostream& out, SparseMatrix<T>& M) {
// 输出流操作符重载
return out;
}
template <class T>
std::istream& operator>>(std::istream& in, SparseMatrix<T>& M) {
// 输入流操作符重载
return in;
}
快速转置:
template <class T>
void SparseMatrix<T>::FastTranspose(SparseMatrix<T>& b) {
// 对稀疏矩阵 (*this) 做快速转置,结果存储在矩阵 b 中,并通过函数返回
int* rowSize = new int[Cols]; // 辅助数组,统计各列非零元素个数
int* rowStart = new int[Cols]; // 辅助数组,预计转置后各行开始存放位置
// 初始化矩阵 b 的行数、列数和非零元素数
b.Rows = Cols;
b.Cols = Rows;
b.Terms = Terms;
if (Terms > 0) {
// 初始化 rowSize 数组为 0
for (int i = 0; i < Cols; ++i) {
rowSize[i] = 0;
}
// 统计每列非零元素个数
for (int i = 0; i < Terms; ++i) {
rowSize[elem[i].col]++;
}
// 计算转置后各行开始存放位置
rowStart[0] = 0;
for (int i = 1; i < Cols; ++i) {
rowStart[i] = rowStart[i - 1] + rowSize[i - 1];
}
// 从当前矩阵向矩阵 b 传送
for (int i = 0; i < Terms; ++i) {
int j = rowStart[elem[i].col]; // 第 i 个非零元素在矩阵 b 中应放的位置
b.elem[j].row = elem[i].col;
b.elem[j].col = elem[i].row;
b.elem[j].value = elem[i].value;
rowStart[elem[i].col]++;
}
}
// 释放辅助数组的内存
delete[] rowSize;
delete[] rowStart;
}
字符串
库函数
-
strcpy字符串复制
int strcpy(char *string1, char *string2);
函数复制字符串
string2
的内容到字符串string1
中。如果string1
本身原来有数据,则会被string2
的内容覆盖掉。如果string1
的长度大于string2
的长度,strcpy
将把string1
的前面部分覆盖上string2
的数据,而长度大于string2
的部分保留原来string1
的数据。例如,初始时
char str1[] = "word1", str2[] = "word2"
,调用strcpy(str1, str2)
后,str1 = "word2", str2 = "word2"
。 -
strncpy字符串部分复制
int strncpy(char *string1, char *string2, int n);
函数用字符串
string2
的前n
个字符覆盖字符串string1
的前n
个字符。如果string1
本身原来有数据,则会被string2
覆盖掉。例如,初始时
char str1[] = "Hello", str2[] = "World"
,调用strncpy(str1, str2, 2)
后,str1 = "Wollo", str2 = "World"
。 -
strcat字符串连接
int strcat(char *string1, char *string2);
函数连接字符串
string2
到string1
后面,存于string1
中。string2
中原来的内容保持不变。例如,初始时
char str1[] = "Tsing", str2[] = "hua"
,调用strcat(str1, str2)
后,str1 = "Tsinghua", str2 = "hua"
。 -
strncat将特定数量字符串连接到另一个字符串
int strncat(char *string1, char *string2, int n);
函数将字符串
string2
中前n
个字符连接到字符串string1
之后,存于string1
中,string2
中原来的内容不变。例如,初始时
char str1[] = "Tsing", str2[] = "hua"
,调用strncat(str1, str2, 2)
后,str1 = "Tsinghu", str2 = "hua"
。 -
_strdup预先配置内存,将字符串存入该内存里
char *_strdup(char *string1);
函数为字符串
string1
分配内存空间并将string1
存入其中,返回值为指向该内存开始地址的指针,数据类型为char
。例如,若定义
char *p, str[] = "Beijing"
,在调用p = _strdup(str)
后,在指针p
内得到分配给字符串str
的内存的开始地址。 -
strchr在给定字符串中搜寻指定字符
char *strchr(char *string1, char ch);
函数在字符串
string1
中搜索字符ch
并返回指向字符ch
的指针。若搜索失败则函数返回NULL
。例如,若定义
char *p, str[100] = "The Dog Barked at the Cat!"
,在调用p = strchr(_strdup(str), 'B')
后,在指针p
中得到字符 ‘B’ 的存储地址。 -
strcspn在给定字符串中搜寻某个指定字符第一次出现的位置
int strcspn(char *string1, char ch);
函数在字符串
string1
中搜索字符ch
并返回该字符在字符串中第一次出现的位置(从 0 开始计数)。例如,若定义
char str[100] = "The Dog Barked at the Cat!"
,在调用int d = strcspn(str, 'a')
后,在d
中的值为 9。若搜索失败,则函数返回字符串的长度。 -
strrchr在给定字符串中搜寻某个指定字符最后一次出现的地址
char *strrchr(char *string1, char ch);
函数在字符串
string1
中搜索字符ch
并返回指向该字符在字符串中最后一次出现地址的指针。若搜索失败,则函数返回NULL
。例如,若定义
char str[100] = "The Dog Barked at the Cat!"
,在调用char *add = strrchr(str, 'a')
后,在add
中得到字符 ‘a’ 最后一次出现的地址。 -
strpbrk在两个字符串中寻找首次共同出现的字符(String Pointer Break)
char *strpbrk(const char *string1, const char *string2);
函数在字符串
string1
和string2
中搜寻首次共同出现的字符,返回该字符在string1
中的地址。若找不到共同出现的字符,则函数返回NULL
。例如,若定义
char str1[] = "University", str2[] = "converse"
,则在调用char *p = strpbrk(str1, str2)
后,在指针p
中得到首先出现的共同字符 ‘n’ 在str1
中的地址。 -
strstr在两个字符串中寻找首次共同出现的共有子字符串
char *strstr(const char *string1, const char *string2);
函数在字符串
string1
中寻找和string2
匹配的子字符串,返回该子字符串第一个字符在string1
中的地址。若搜索失败下面是对提供的文本进行整理和修正,并以Markdown文档形式呈现:则函数返回
NULL
。例如,若定义
char str1[] = "University", str2[] = "ver"
,则在调用char *p = strstr(str1, str2)
后,在指针p
中得到首先匹配的共有子字符串 “ver” 在str1
中的地址。 -
strlen计算字符串的长度
int strlen(const char *string1);
函数计算字符串
string1
的长度,即字符串中字符的个数(串结束符\0
和串分界符"
不计)。例如,若定义字符串str = "Tsinghua"
,在调用int k = strlen(str)
后,k
中得到该字符串的长度 8。 -
_strnset在给定的字符串中按指定数目将若干字符置换为指定字符
char *_strnset(char *string1, char ch, int m);
函数按照指定的数目
m
,将字符串string1
中开始的m
个字符全部设定为ch
。例如,若定义字符串
str = "Tsinghua University"
,在调用char *p = _strnset(str, 'x', 9)
后,在字符串p
中将得到 “xxxxxxxxxUniversity”。 -
strcmp字符串比较大小
int strcmp(char *string1, char *string2);
函数比较字符串
string1
和string2
的大小。函数返回值小于 0、等于 0 或大于 0 分别表示string1 < string2
、string1 = string2
或string1 > string2
。例如,若调用
int k = strcmp("Joe", "Joseph")
后,k
中得到值小于 0。strcmp
函数是按照元素的字典序逐个比较的。在比较两个字符串时,它会从字符串的起始位置开始逐个比较对应位置的字符的 ASCII 值,直到遇到不同的字符、其中一个字符串结束,或者两个字符串完全相等。如果两个字符串的第一个字符相等,它会继续比较下一个字符,以此类推,直到发现不同的字符或者到达字符串结束的标志
\0
。比较时使用的是字符的 ASCII 值,因此可以理解为按照字典序逐个比较。
在 C++ 中,字符串类 std::string
并没有直接提供使用括号 ()
来提取子串的操作符重载。然而,你可以使用成员函数 substr
来实现提取子串的功能。
要求掌握求子串的健壮性处理
AString AString::operator()(int pos, int len) {
// 求子串
AString temp;
if (pos < 0 || pos + len - 1 > maxSize || len < 0) {
temp.curLength = 0;
temp.ch[0] = '\0';
} else {
if (pos + len - 1 > curLength) {
len = curLength - pos;
}
temp.curLength = len;
for (int i = 0, j = pos; i < len; i++, j++) {
temp.ch[i] = ch[j];
}
temp.ch[len] = '\0';
}
return temp;
}
#include <iostream>
#include <string>
int main() {
std::string originalString = "Hello, World!";
// 使用 substr 提取子串
std::string subString = originalString.substr(7, 5); // 从位置7开始提取长度为5的子串
std::cout << "Substring: " << subString << std::endl;
return 0;
}
在上面的例子中,substr
函数的第一个参数是子串的起始位置,第二个参数是子串的长度。你可以根据需要调整这两个参数来提取不同的子串。
字符串的模式匹配(KMP)
![image-20231114133921118](https://i-blog.csdnimg.cn/blog_migrate/63b44e56e57141c0d8855830c4c16f88.png)
这是一个基于KMP算法的字符串匹配实现,其中包括了计算 next
数组的函数 getNext
和在目标串中查找模式串的函数 KMP
。
下面是对这两个函数的简要解释:
1. getNext
函数
void AString::getNext(int next[]) {
int j = 0, k = -1, lengthP = curLength;
next[0] = -1;
while (j < lengthP) {
if (k == -1 || ch[j] == ch[k]) {
j++;
k++;
next[j] = k;
} else {
k = next[k];
}
}
}
这个函数用于计算模式串 pat
的 next
数组。next[j]
表示在模式串中,以 pat[j]
结尾的前缀子串与以模式串首字符开始的后缀子串的最大匹配长度。这个数组在KMP算法中用于优化匹配过程。
2. KMP
函数
int AString::KMP(AString& pat, int k) {
int posP = 0, posT = k;
int next[defaultSize];
pat.getNext(next);
int lengthP = pat.curLength;
int lengthT = curLength;
while (posP < lengthP && posT < lengthT) {
if (posP == -1 || pat.ch[posP] == ch[posT]) {
posP++;
posT++;
} else {
posP = next[posP];
}
}
if (posP == lengthP) {
return posT - lengthP; // 匹配成功,返回匹配位置的起始下标
} else {
return -1; // 匹配失败,返回-1
}
}
这个函数用于在目标串 *this
中从位置 k
开始寻找模式串 pat
的匹配位置。如果找到匹配,返回模式串在目标串中首字符的下标,否则返回 -1。在算法中,使用了 getNext
计算得到的 next
数组。
广义表
广义表(Generalized List)定义:
是一种扩展了线性表的数据结构,它允许元素可以是原子元素(单个数据项)或者子表(广义表本身)。广义表是一种递归定义的数据结构,其中每个元素可以是原子元素或者另一个广义表。
广义表的定义如下:
-
空表(Empty List):空表是广义表中不包含任何元素的情况,用
()
表示。 -
原子元素(Atom):原子元素是广义表中的基本单元,可以是任何数据类型的元素,如整数、字符、字符串等。
-
广义表(GList):广义表是由若干元素组成的有限序列,每个元素可以是原子元素或者是另一个广义表。
广义表的形式可以表示为:
L = () // 空表
| Atom // 原子元素
| (L1, L2, ..., Ln) // 广义表,由 n 个元素组成
广义表的性质:
-
递归性质:广义表的元素可以是原子元素或广义表,因此广义表是一个递归的数据结构。
-
表头和表尾:对于非空广义表
(L1, L2, ..., Ln)
,其第一个元素L1
称为表头,其余元素(L2, ..., Ln)
称为表尾。表头是一个原子元素或广义表,表尾是一个广义表。 -
原子表:只包含原子元素的广义表称为原子表。
-
子表:广义表的元素如果是一个广义表,那么它被称为广义表的子表。
-
长度:广义表的长度是指其包含的元素个数,对于空表,长度为 0。
-
深度:广义表的深度是指广义表中包含的最大嵌套层数。例如,
(1, (2, (3, 4)))
的深度为 3。 -
可递归:广义表自身就是自己的子表,比如表F
广义表的递归性质和灵活性使其成为一种强大的数据结构,广泛应用于人工智能、编程语言解释器等领域。
广义表的表示:
![image-20231114140739172](https://i-blog.csdnimg.cn/blog_migrate/380573c4e9bb35c6b7bd4d13c4771fbd.png)
广义表的链表中每个表结点有三个域,这三个域分别是:
-
标志域
utype
:用来标明该结点是什么类型的结点。utype = 0
时,表示是广义表专用的附加头结点。utype = 1
时,表示是原子结点,不考虑原子结点数据的不同类型。utype = 2
时,表示是子表结点。
-
信息域
info
:不同类型的结点在这个域中存放不同的内容。-
当
utype = 0
时,该信息域存放引用计数ref
。 -
引用计数是一种内存管理技术,用于追踪一个对象被引用的次数。在引用计数中,每个对象都有一个计数器,用于记录当前有多少个指针引用了该对象。当有新的指针引用该对象时,计数器加1;当指针不再引用该对象时,计数器减1。当计数器为0时,表示没有指针再引用该对象,可以安全地释放该对象的内存。
引用计数的优点是简单高效,可以迅速地判断对象是否可以被释放。但它也有一些缺点,比如无法处理循环引用的情况,可能导致内存泄漏。当两个或多个对象相互引用,它们的计数器永远不会降为零,即使它们不再被外部引用,这就导致了内存泄漏。
引用计数通常用于实现共享对象的内存管理,其中多个对象可以引用同一块内存,而引用计数记录了有多少个对象共享该内存。在C++中,智能指针(如
std::shared_ptr
)通常使用引用计数来管理内存。 -
当
utype = 1
时,该信息域存放数据值value
。 -
当
utype = 2
时,该信息域存放指向子表表头的指针hlink
。
-
-
尾指针域
tlink
:当utype = 0
时,该尾指针域存放指向该表表头元素结点的指针;当utype ≠ 0
时,该尾指针域存放同一层下一个表结点的地址。
这种表示方式允许在链表中灵活地存储广义表的结构,通过不同的标志和信息域来区分不同类型的结点,实现了对广义表的表示。
![image-20231114141734125](https://i-blog.csdnimg.cn/blog_migrate/fb96793c31c2e6a304108cfbd0bc7fc9.png)
这是一个 C++ 的广义表(Generalized List)的类定义。广义表是一种拓展了线性表的数据结构,其中元素可以是单个元素(原子元素)或子表(广义表)。以下是类定义的解释:
template <class T>
struct GenListNode {
int utype; // utype=0/1/2
GenListNode<T>* tlink; // 指向同一层下一结点的指针
union {
int ref; // utype=0,附加头结点,存放引用计数
T value; // utype=1,存放数据值,假设为字符型
GenListNode<T>* hlink; // utype=2,存放指向子表表头指针
} info;
GenListNode() : utype(0), tlink(NULL) {} // 构造函数
GenListNode(GenListNode<T>& R) // 复制构造函数
: utype(R.utype), tlink(R.tlink), info(R.info) {}
};
template <class T>
struct Items {//返回值的类定义
int utype; // utype=0/1/2
union {
int ref; // utype=0,附加头结点,存放引用计数(reference)
T value; // utype=1,存放数据值
GenListNode<T>* hlink; // utype=2,存放指向子表表头指针
} info;
Items() : utype(0), info.ref(0) {} // 构造函数
Items(Items R) : utype(R.utype), info(R.info) {} // 复制构造函数
};
template <class T>
struct nameltem {
T name; // 表名
GenListNode<T>* addr; // 该表头结点地址
};
template <class T>
class GenList {
public:
GenList(); // 构造函数
~GenList(); // 析构函数
bool Head(Items<T>& x); // 返回表头元素x
bool Tail(GenList<T>& It, nameltem<T> NT[], int& count); // 返回表尾It
GenListNode<T>* First(); // 返回第一个元素
GenListNode<T>* Next(GenListNode<T>* x); // 返回表元素x的直接后继元素
void Copy(GenList<T>& R, nameltem<T> NT[], int count); // 广义表的复制
int Length(); // 计算广义表的长度
int depth(); // 计算一个非递归表的深度
void CreateGenList(T in[], nameltem<T> NT[], int& count); // 从字符串描述in建立广义表
void PrintGenList(nameltem<T> NT[], int count); // 输出广义表
void Remove(T x); // 删除子表中所有的x
void DelsubGL(T name, nameltem<T> NT[], int count); // 删除表名为name的子表
private:
GenListNode<T>* first; // 广义表头指针
GenListNode<T>* Copy(GenListNode<T>* ls, nameltem<T> NT[], int count); // 复制无共享非递归表ls
int Length(GenListNode<T>* ls); // 求由ls指示的广义表的长度
int depth(GenListNode<T>* ls); // 计算非递归表的深度
void PrintGenList(GenListNode<T>* p, nameltem<T> NT[], int count);
void Remove(GenListNode<T>* ls, T x); // 删除子表中所有的x
void DelsubL(GenListNode<T>*& ls); // 删除ls子表
void Search(GenListNode<T>*& ls, T name, nameltem<T> NT[], int count); // 查找并删除表名为name的子表
friend bool equal(GenListNode<T>* s, GenListNode<T> t); // 判断表*s和表*t是否相等
};
广义表存储表示的特点:
-
所有表都带有附加头结点,包括空表。
-
结点的层次分明,同一层的表元素在存储表示中也在同一层。
-
最高一层的表结点个数(除附加头结点外)即为表的长度。
// 程序4.18:广义表类的部分成员函数的实现
template <class T>
GenList<T>::GenList() {
// 构造函数
first = new GenListNode<T>; // 建立附加头结点
assert(first != NULL);
}
template <class T>
bool GenList<T>::Head(Items<T>& x) {
// 若广义表非空,则通过 x 返回其第一个元素的值,否则函数没有定义
if (first->tlink == NULL) return false; // 空表,没有返回值可用
else {
// 非空表
x.utype = first->tlink->utype;
switch (x.utype) {
case 0:
x.info.ref = first->tlink->info.ref;
break;
case 1:
x.info.value = first->tlink->info.value;
break;
case 2:
x.info.hlink = first->tlink->info.hlink;
break;
}
return true; // 返回 true,表示返回表头的值
}
}
template <class T>
bool GenList<T>::Tail(GenList<T>& It, nameltem<T> NT[], int& count) {
// 若广义表非空,则通过 t 返回广义表除表头元素以外其他元素组成的表,否则函数没有定义
if (first->tlink == NULL) return false; // 空表
else {
// 非空表
It.first = new GenListNode<T>; // 创建表尾子表的头结点
It.first->utype = 0; // 设置附加头结点
It.first->info.ref = 0;
It.first->tlink = Copy(first->tlink, NT, count);
return true;
}
}
template <class T>
GenListNode<T>* GenList<T>::First() {
// 返回广义表的第一个元素(若表空,则返回一个特定的空值NULL)
return first->tlink;
}
template <class T>
GenListNode<T>* GenList<T>::Next(GenListNode<T> elem) {
// 返回表元素 elem 的直接后继元素
return elem->tlink;
}
这是一个复制广义表的C++程序片段,具体如下:
template <class T>
void GenList<T>::Copy(GenList<T>& R, nameItem<T> NT[], int count) {
// 共有函数,复制广义表,名址表NT[count]中的表名对应地址已换过
first = Copy(R.first, NT, count);
// 调用私有函数
}
template <class T>
GenListNode<T>* GenList<T>::Copy(GenListNode<T>* ls, nameltem<T> NT[], int count) {
// 私有函数,复制一个s指示的无共享子表的非递归表
GenListNode<T>* q, *s;
int i;
if (ls != NULL) {
q = new GenListNode<T>;
// 创建新表的当前结点q
q->utype = ls->utype;
// 复制结点类型
if (ls->utype == 0) {
// 修改名址表
for (i = 0; i < count; i++)
// 查表名
if (NT[i].addr == ls) break;
NT[i].addr = q;
// 让其地址指向新地址
switch (ls->utype) {
// 根据utype传送信息
case 0:
q->info.ref = ls->info.ref;
break;
// 附加头结点
case 1:
q->info.value = ls->info.value;
break;
// 原子结点
case 2:
q->info.hlink = Copy(ls->info.hlink, NT, count);
break; //
}
q->tlink = Copy(ls->tlink, NT, count);
// 处理同一层下一结点
} else {
q = NULL;
}
}
return q;
}
这段代码用于复制广义表,具体实现了一个公有函数 Copy
和一个私有函数 Copy
,后者用于递归复制无共享子表的非递归表。其中,名址表 NT
存储了广义表的名字和对应地址的关系,用于在复制时更新地址。
这是一个用于求广义表长度的 C++ 程序片段,具体如下:
template <class T>
int GenList<T>::Length() {
// 共有函数,求当前广义表的长度
return Length(first->tlink);
}
template <class T>
int GenList<T>::Length(GenListNode<T> *ls) {
// 私有函数,求以ls为头指针的广义表的长度
if (ls != NULL)
return 1 + Length(ls->tlink);
else
return 0;
}
这段代码实现了一个求广义表长度的算法,其中 Length()
是公有函数,它调用了私有函数 Length(GenListNode<T> *ls)
。私有函数通过递归方式计算广义表的长度。
以下是给定的 C++ 程序片段,实现了求广义表深度的算法:
template <class T>
int GenList<T>::depth() {
// 共有函数:计算一个非递归表的深度
return depth(first);
}
template <class T>
int GenList<T>::depth(GenListNode<T> *ls) {
// 私有函数:计算非递归广义表深度
if (ls->tlink == NULL)
return 1; // 空表,深度为1
GenListNode<T> *temp = ls->tlink;
int m = 0, n;
while (temp != NULL) {
// temp在广义表顶层横扫
if (temp->utype == 2) {
// 扫描到的结点utype为表结点
n = depth(temp->info.hlink); // 计算以该结点为头的广义表深度
if (m < n)
m = n; // 取最大深度
}
temp = temp->tlink;
}
return m + 1; // 返回深度
}
这段代码中,depth()
函数是公有函数,调用了私有函数 depth(GenListNode<T> *ls)
。私有函数通过递归方式计算非递归广义表的深度。
以下是给定的 C++ 程序片段,实现了在广义表中删除给定值相应结点的算法:
//非共享表,删除相应值
template <class T>
void GenList<T>::Remove(GenListNode<T> *ls, T x) {
if (ls->tlink != NULL) {
// 非空表
GenListNode<T> *p = ls->tlink;
// 找附加头结点后的第一个满足条件结点
while (p != NULL && p->utype == 1 && p->info.value == x) {
ls->tlink = p->tlink; // 重新链接
delete p; // 删除
p = ls->tlink; // 指向同层下一结点
}
if (p != NULL) {
if (p->utype == 2)
Remove(p->info.hlink, x); // 在子表中删除
Remove(p, x); // 在以p为表头的链表中递归删除
}
}
}
//共享表
template <class T>
void GenList<T>::DelsubL(GenListNode<T> *ls) {
// 私有函数:释放以1s为表头指针的广义表
ls->info.ref--; // 附加头结点的引用计数减1
if (ls->info.ref <= 0) {
// 如果减到0
GenListNode<T> *q;
while (ls->tlink != NULL) {
// 横扫表顶层
q = ls->tlink; // 到第一个结点
if (q->utype == 2) {
// 递归删除子表
DelsubL(q->info.hlink);
if (q->info.hlink->info.ref <= 0)
delete q->info.hlink; // 删除子表附加头结点
}
ls->tlink = q->tlink;
delete q;
}
delete ls;
}
}
这段代码中,Remove
函数用于在广义表中删除给定值相应的结点,而 DelsubL
函数是私有函数,用于释放以给定表头指针的广义表。在删除子表时,还考虑了引用计数的减少,只有当引用计数减到 0 时才会真正释放结点。