数据结构-数组、串与广义表

数组、串与广义表

数组

std::vector 是 C++ 标准模板库(STL)中的一个动态数组容器,提供了丰富的成员函数来方便对数组进行操作。以下是一些常见的 std::vector 成员函数:

  1. 构造和赋值操作:

    • vector():构造函数,创建一个空的向量。
    • vector(size_type n, const T& val = T()):构造函数,创建包含 n 个元素,每个元素的值都是 val。
    • vector(const vector& x):复制构造函数,创建一个与 x 同样的向量。
    • vector(begin, end):构造函数,创建一个包含区间 [begin, end) 中元素的向量。
    • operator=:赋值操作符,用一个向量替换当前向量的内容。
  2. 访问元素:

    • operator[]:访问指定位置的元素。
    • at(index):访问指定位置的元素,并进行边界检查。
    • front():返回第一个元素。
    • back():返回最后一个元素。
    • data():返回指向数组中的第一个元素的指针。
  3. 迭代器:

    • begin():返回指向第一个元素的迭代器。
    • end():返回指向最后一个元素的迭代器。
    • rbegin():返回指向最后一个元素的反向迭代器。
    • rend():返回指向第一个元素的反向迭代器。
  4. 容量:

    • size():返回向量中元素的个数。
    • max_size():返回向量可能包含的最大元素数。
    • empty():检查向量是否为空。
    • capacity():返回向量当前能容纳的元素数。
    • reserve(n):请求向量容纳至少 n 个元素的空间。
    • shrink_to_fit():请求移除多余的容量。
  5. 修改:

    • 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 image-20231113185933082

稀疏矩阵

当矩阵中的非零元素<<矩阵的最大容量 成为稀疏矩阵

要求掌握稀疏矩阵的转置

#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;
}

字符串

库函数

  1. 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"

  2. 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"

  3. strcat字符串连接

    int strcat(char *string1, char *string2);
    

    函数连接字符串 string2string1 后面,存于 string1 中。string2 中原来的内容保持不变。

    例如,初始时 char str1[] = "Tsing", str2[] = "hua",调用 strcat(str1, str2) 后,str1 = "Tsinghua", str2 = "hua"

  4. 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"

  5. _strdup预先配置内存,将字符串存入该内存里

    char *_strdup(char *string1);
    

    函数为字符串 string1 分配内存空间并将 string1 存入其中,返回值为指向该内存开始地址的指针,数据类型为 char

    例如,若定义 char *p, str[] = "Beijing",在调用 p = _strdup(str) 后,在指针 p 内得到分配给字符串 str 的内存的开始地址。

  6. 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’ 的存储地址。

  7. 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。若搜索失败,则函数返回字符串的长度。

  8. 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’ 最后一次出现的地址。

  9. strpbrk在两个字符串中寻找首次共同出现的字符(String Pointer Break)

    char *strpbrk(const char *string1, const char *string2);
    

    函数在字符串 string1string2 中搜寻首次共同出现的字符,返回该字符在 string1 中的地址。若找不到共同出现的字符,则函数返回 NULL

    例如,若定义 char str1[] = "University", str2[] = "converse",则在调用 char *p = strpbrk(str1, str2) 后,在指针 p 中得到首先出现的共同字符 ‘n’ 在 str1 中的地址。

  10. 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 中的地址。

  11. strlen计算字符串的长度

    int strlen(const char *string1);
    

    函数计算字符串 string1 的长度,即字符串中字符的个数(串结束符 \0 和串分界符 " 不计)。例如,若定义字符串 str = "Tsinghua",在调用 int k = strlen(str) 后,k 中得到该字符串的长度 8。

  12. _strnset在给定的字符串中按指定数目将若干字符置换为指定字符

    char *_strnset(char *string1, char ch, int m);
    

    函数按照指定的数目 m,将字符串 string1 中开始的 m 个字符全部设定为 ch

    例如,若定义字符串 str = "Tsinghua University",在调用 char *p = _strnset(str, 'x', 9) 后,在字符串 p 中将得到 “xxxxxxxxxUniversity”。

  13. strcmp字符串比较大小

    int strcmp(char *string1, char *string2);
    

    函数比较字符串 string1string2 的大小。函数返回值小于 0、等于 0 或大于 0 分别表示 string1 < string2string1 = string2string1 > 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

这是一个基于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];
        }
    }
}

这个函数用于计算模式串 patnext 数组。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)定义:

是一种扩展了线性表的数据结构,它允许元素可以是原子元素(单个数据项)或者子表(广义表本身)。广义表是一种递归定义的数据结构,其中每个元素可以是原子元素或者另一个广义表。

广义表的定义如下:

  1. 空表(Empty List):空表是广义表中不包含任何元素的情况,用 () 表示。

  2. 原子元素(Atom):原子元素是广义表中的基本单元,可以是任何数据类型的元素,如整数、字符、字符串等。

  3. 广义表(GList):广义表是由若干元素组成的有限序列,每个元素可以是原子元素或者是另一个广义表。

广义表的形式可以表示为:

L = ()                    // 空表
   | Atom                 // 原子元素
   | (L1, L2, ..., Ln)    // 广义表,由 n 个元素组成

广义表的性质:

  1. 递归性质:广义表的元素可以是原子元素或广义表,因此广义表是一个递归的数据结构。

  2. 表头和表尾:对于非空广义表 (L1, L2, ..., Ln),其第一个元素 L1 称为表头,其余元素 (L2, ..., Ln) 称为表尾。表头是一个原子元素或广义表,表尾是一个广义表。

  3. 原子表:只包含原子元素的广义表称为原子表。

  4. 子表:广义表的元素如果是一个广义表,那么它被称为广义表的子表。

  5. 长度:广义表的长度是指其包含的元素个数,对于空表,长度为 0。

  6. 深度:广义表的深度是指广义表中包含的最大嵌套层数。例如,(1, (2, (3, 4))) 的深度为 3。

  7. 可递归:广义表自身就是自己的子表,比如表F

广义表的递归性质和灵活性使其成为一种强大的数据结构,广泛应用于人工智能、编程语言解释器等领域。

image-20231114140429211

广义表的表示:

image-20231114140739172

广义表的链表中每个表结点有三个域,这三个域分别是:

  1. 标志域 utype:用来标明该结点是什么类型的结点。

    • utype = 0 时,表示是广义表专用的附加头结点。
    • utype = 1 时,表示是原子结点,不考虑原子结点数据的不同类型。
    • utype = 2 时,表示是子表结点。
  2. 信息域 info:不同类型的结点在这个域中存放不同的内容。

    • utype = 0 时,该信息域存放引用计数 ref

    • 引用计数是一种内存管理技术,用于追踪一个对象被引用的次数。在引用计数中,每个对象都有一个计数器,用于记录当前有多少个指针引用了该对象。当有新的指针引用该对象时,计数器加1;当指针不再引用该对象时,计数器减1。当计数器为0时,表示没有指针再引用该对象,可以安全地释放该对象的内存。

      引用计数的优点是简单高效,可以迅速地判断对象是否可以被释放。但它也有一些缺点,比如无法处理循环引用的情况,可能导致内存泄漏。当两个或多个对象相互引用,它们的计数器永远不会降为零,即使它们不再被外部引用,这就导致了内存泄漏。

      引用计数通常用于实现共享对象的内存管理,其中多个对象可以引用同一块内存,而引用计数记录了有多少个对象共享该内存。在C++中,智能指针(如std::shared_ptr)通常使用引用计数来管理内存。

    • utype = 1 时,该信息域存放数据值 value

    • utype = 2 时,该信息域存放指向子表表头的指针 hlink

  3. 尾指针域 tlink:当 utype = 0 时,该尾指针域存放指向该表表头元素结点的指针;当 utype ≠ 0 时,该尾指针域存放同一层下一个表结点的地址。

这种表示方式允许在链表中灵活地存储广义表的结构,通过不同的标志和信息域来区分不同类型的结点,实现了对广义表的表示。

image-20231114141734125

image-20231114140429211image-20231114141753743

这是一个 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是否相等
};

广义表存储表示的特点:

  1. 所有表都带有附加头结点,包括空表。

  2. 结点的层次分明,同一层的表元素在存储表示中也在同一层。

  3. 最高一层的表结点个数(除附加头结点外)即为表的长度。

// 程序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 时才会真正释放结点。

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值