数据结构课本中的算法

 

摘自:数据结构(C语言版)(第2版) (严蔚敏 李冬梅 吴伟民)

绪论

代码示例

代码定义

//函数结果状态代码
#define OK 1
#define ERROR 0
#define OVERFLOW -2
//Status是函数返回值类型,其值是函数结果状态代码。
typedef int Status;

以复数为例,给出一个完整的抽象数据类型的定义、表示和实现。

定义部分

ADT Complex{
    数据对象 : D = {e1, e2 | e1, e2R, R是实数集}
    数据关系 : S = {<e1, e2> | e1是复数的实部, e2 是复数的虚部}
    基本操作 :
        Creat(&C, x, y)
        操作结果 : 构造复数C,
        其实部和虚部分别被赋予参数xy的值.
        GetReal(C)
        初始条件 : 复数C已存在.
        操作结果 : 返回复数C的实部值.
        GetImag(C)
        初始条件 : 复数C已存在.
        操作结果 : 返回复数C的虚部值.
        Add(C1, C2)
        初始条件 : C1,C2是复数.
        操作结果 : 返回两个复数C1C2的和.
        Sub(C1, C2)
        初始条件 : C1,C2是复数.
        操作结果 : 返回两个复数C1C2的差.
} ADT Complex

表示部分

typedef struct // 复数类型
{
    float Realpart;  // 实部
    float Imagepart; // 虚部
} Complex;

实现部分

#include<iostream>

struct Complex {  // 直接使用 struct 定义类型
    float Realpart;  // 实部
    float Imagepart; // 虚部
};

void Create(Complex &C, float x, float y) {
    // 构造一个复数
    C.Realpart = x;
    C.Imagepart = y;
}

float GetReal(const Complex &C) {  // 使用引用并添加 const 以避免不必要的复制,并表明不会修改参数
    // 取复数C=x+yi的实部
    return C.Realpart;
}

float GetImag(const Complex &C) {
    // 取复数C=x+yi的虚部
    return C.Imagepart;
}

Complex Add(const Complex &C1, const Complex &C2) {
    // 求两个复数C1和C2的和sum
    Complex sum;
    sum.Realpart = C1.Realpart + C2.Realpart;
    sum.Imagepart = C1.Imagepart + C2.Imagepart;
    return sum;
}

Complex Sub(const Complex &C1, const Complex &C2) {
    // 求两个复数C1和C2的差difference
    Complex difference;
    difference.Realpart = C1.Realpart - C2.Realpart;
    difference.Imagepart = C1.Imagepart - C2.Imagepart;
    return difference;
}

int main() {
    Complex a;  // 直接声明一个 Complex 对象
    Create(a, 1, 2);
    std::cout << "Real part: " << GetReal(a) << ", Imaginary part: " << GetImag(a) << std::endl;
    return 0;
}

算法示例

求两个 n 阶矩阵的乘积算法。

for (i = 1; i <= n; i++)     // 频度为n+1
    for (j = 1; j <= n; j++) // 频度为n*(n+1)
    {
        c[i][j] = 0;                               // 频度为n^2
        for (k = 1; k <= n; k++)                   // 频度为n^2*(n + 1)
            c[i][j] = c[i][j] + a[i][k] * b[k][j]; // 频度为n^3
    }

线性表

算法 2.1  顺序表的初始化

//- - - - - 顺序表的存储结构- - - - -
#define MAXSIZE 100 // 顺序表可能达到的最大长度
typedef struct
{
    ElemType *elem; // 存储空间的基地址
    int length;     // 当前长度
} SqList;           // 顺序表的结构类型为SqList

Status InitList(SqList &L)
{                                   // 构造一个空的顺序表L
    L.elem = new ElemType[MAXSIZE]; // 为顺序表分配一个大小为MAXSIZE的数组空间
    if (!L.elem) exit(OVERFLOW);     //存储分配失败退出
    L.length = 0;                   // 空表长度为0
    return OK;
}

算法 2.2  顺序表的取值

Status GetElem(SqList L, int i, ElemType &e)
{
    if (i < 1 || i > L.length)
        return ERROR;  // 判断i值是否合理,若不合理,返回ERROR
    e = L.elem[i - 1]; // elem[i-1]单元存储第i个数据元素
    return OK;
}

算法 2.3  顺序表的查找

int LocateElem(SqList L, ElemType e)
{ // 在顺序表L中查找值为e的数据元素,返回其序号
    for (i = 0; i < L.length; i++)
        if (L.elem[i] == e)
            return i + 1; // 查找成功,返回序号i+1
    return 0;             // 查找失败,返回0
}

算法 2.4  顺序表的插入

Status ListInsert(SqList &L, int i, ElemType e)
{ // 在顺序表L中第i个位置插入新的元素e,
    if ((i < 1) || (i > L.length + 1)) return ERROR; // i值不合法
    if (L.length == MAXSIZE) return ERROR;            // 当前存储空间已满
    for (j = L.length - 1; j >= i - 1; j--)
        L.elem[j + 1] = L.elem[j];  //插入位置及之后的元素后移
    L.elem[i - 1] = e;              //将新元素e放入第i个位置
    ++L.length;                     // 表长加1
    return OK;
}

算法 2.5  顺序表的删除

Status ListDelete(SqList &L, int i)
{ // 在顺序表L中删除第i个元素,i值的合法范围是1≤i≤L.length
    if ((i < 1) || (i > L.length))
        return ERROR; // i值不合法
    for (j = i; j <= L.length - 1; j++)
        L.elem[j - 1] = L.elem[j]; // 被删除元素之后的元素前移
    --L.length;                    // 表长减1
    return OK;
}

算法 2.6  单链表的初始化

typedef struct LNode
{
    ElemType data;      // 节点的数据域
    struct LNode *next; // 节点的指针域
} LNode, *LinkList;     // LinkList为指向结构体LNode的指针类型

Status InitList(LinkList &L)
{                   // 构造一个空的单链表L
    L = new LNode;  // 生成新节点作为头节点,用头指针L指向头节点
    L->next = NULL; // 头节点的指针域置空
    return OK;
}

算法 2.7  单链表的取值

Status GetElem(LinkList L, int i, ElemType &e)
{ // 在带头节点的单链表L中根据序号i获取元素的值,用e返回L中第i个数据元素的值
    p = L->next;
    j = 1;             // 初始化,p指向首元节点,计数器j初值赋为1
    while (p && j < i) // 顺链域向后查找,直到p为空或p指向第i个元素
    {
        p = p->next; // p指向下一个节点
        ++j;         // 计数器j相应加1
    }
    if (!p || j > i)
        return ERROR; // i值不合法i>n或i<=0
    e = p->data;      // 取第i个节点的数据域
    return OK;
}

算法 2.8  单链表的按值查找

LNode *LocateElem(LinkList LElemType e)
{                             // 在带头节点的单链表L中查找值为e的元素
    p = L->next;              // 初始化,p指向首元节点
    while (p && p->data != e) // 顺链域向后查找,直到p为空或p所指节点的数据域等于e
        p = p->next;          // p指向下一个节点
    return p;                 // 查找成功返回值为e的节点地址p,查找失败p为NULL
}

算法 2.9  单链表的插入

Status ListInsert(LinkList &L, int i, ElemType e)
{ // 在带头节点的单链表L中第i个位置插入值为e的新节点
    p = L;
    j = 0;
    while (p && (j < i - 1))
    {
        p = p->next;
        ++j;
    } // 查找第i − 1个节点,p指向该节点
    if (!p || j > i - 1)
        return ERROR;  // i>n+1或者i<1
    s = new LNode;     // 生成新节点*s
    s->data = e;       // 将节点*s的数据域置为e
    s->next = p->next; // 将节点*s的指针域指向节点ai
    p->next = s;       // 将节点*p的指针域指向节点*s
    return OK;
}

算法 2.10  单链表的删除

Status ListDelete(LinkList &L, int i)
{ // 在带头节点的单链表L中,删除第i个元素
    p = L;
    j = 0;
    while ((p->next) && (j < i - 1)) // 查找第i − 1个节点,p指向该节点
    {
        p = p->next;
        ++j;
    }
    if (!(p->next) || (j > i - 1))
        return ERROR;  // 当i>n或i<1时,删除位置不合理
    q = p->next;       // 临时保存被删节点的地址以备释放
    p->next = q->next; // 改变删除节点前驱节点的指针域
    delete q;          // 释放删除节点的空间
    return OK;
}

算法 2.11  前插法创建单链表

void CreateList_H(LinkList &L, int n)
{ // 逆位序输入n个元素的值,建立带表头节点的单链表L
    L = new LNode;
    L->next = NULL; // 先建立一个带头节点的空链表
    for (i = 0; i < n; ++i)
    {
        p = new LNode;  // 生成新节点*p
        cin >> p->data; // 输入元素值赋给新节点*p的数据域
        p->next = L->next;
        L->next = p; // 将新节点*p插入到头节点之后
    }
}

算法 2.12  后插法创建单链表

void CreateList_R(LinkList &L, int n)
{ // 正位序输入n个元素的值,建立带表头节点的单链表L
    L = new LNode;
    L->next = NULL; // 先建立一个带头节点的空链表
    r = L;          // 尾指针r指向头节点
    for (i = 0; i < n; ++i)
    {
        p = new LNode;  // 生成新节点
        cin >> p->data; // 输入元素值赋给新节点*p的数据域
        p->next = NULL;
        r->next = p; // 将新节点*p插入尾节点*r之后
        r = p;       // r指向新的尾节点*p
    }
}

算法 2.13  双向链表的插入

//- - - - - 双向链表的存储结构- - - - -
typedef struct DuLNode
{
    ElemType data;         // 数据域
    struct DuLNode *prior; // 指向直接前驱
    struct DuLNode *next;  // 指向直接后继
} DuLNode, *DuLinkList;

Status ListInsert_DuL(DuLinkList &L, int i, ElemType e)
{                                 // 在带头节点的双向链表L中第i个位置之前插入元素e
    if (!(p = GetElem_DuL(L, i))) // 在L中确定第i个元素的位置指针p
        return ERROR;             // p为NULL时,第i个元素不存在
    s = new DuLNode;              // 生成新节点*s
    s->data = e;                  // 将节点*s数据域置为e
    s->prior = p->prior;          // 将节点*s插入L中
    p->prior->next = s;
    s->next = p;
    p->prior = s;
    return OK;
}

算法 2.14  双向链表的删除

Status ListDelete_DuL(DuLinkList &L, int i)
{                                 // 删除带头节点的双向链表L中的第i个元素
    if (!(p = GetElem_DuL(L, i))) // 在L中确定第i个元素的位置指针p
        return ERROR;             // p为NULL时,第i个元素不存在
    p->prior->next = p->next;     // 修改被删节点的前驱节点的后继指针
    p->next->prior = p->prior;    // 修改被删节点的后继节点的前驱指针
    delete p;                     // 释放被删节点的空间
    return OK;
}

算法 2.15  线性表的合并

void MergeList(List &LA, List LB)
{ // 将所有在线性表LB中但不在LA中的数据元素插入LA中
    m = ListLength(LA);
    n = ListLength(LB); // 求线性表的长度
    for (i = 1; i <= n; i++)
    {
        GetElem(LB, i, e);          // 取LB中第i个数据元素赋给e
        if (!LocateElem(LA, e))     // LA中不存在和e相同的数据元素
            ListInsert(LA, ++m, e); // 将e插在LA的最后
    }
}

算法 2.16  顺序有序表的合并

voidMergeList_Sq(SqListLA, SqListLB, SqList &LC)
{ // 已知顺序有序表LA和LB的元素按值非递减排列
    // 归并LA和LB得到新的顺序有序表LC,LC的元素也按值非递减排列
    LC.length = LA.length + LB.length; // 新表长度为待合并两表的长度之和
    LC.elem = newElemType[LC.length];  // 为合并后的新表分配一个数组空间
    pc = LC.elem;                      // 指针pc指向新表的第一个元素
    pa = LA.elem;
    pb = LB.elem; // 指针pa和pb的初值分别指向两个表的第一个元素
    pa_last = LA.elem + LA.length - 1;         // 指针pa_last指向LA的最后一个元素
    pb_last = LB.elem + LB.length - 1;         // 指针pb_last指向LB的最后一个元素
    while ((pa <= pa_last) && (pb <= pb_last)) // 未达到LA和LB的表尾
    {
        if (*pa <= *pb)
            *pc++ = *pa++; // 依次摘取两表中值较小的节点插入LC的最后
        else
            *pc++ = *pb++;
    }
    while (pa <= pa_last)
        *pc++ = *pa++; // 已到达LB表尾,依次将LA的剩余元素插入LC的最后
    while (pb <= pb_last)
        *pc++ = *pb++; // 已到达LA表尾,依次将LB的剩余元素插入LC的最后
}

算法 2.17  链式有序表的合并

void MergeList_L(LinkList &LA, LinkList &LB, LinkList &LC)
{ // 已知单链表LA和LB的元素按值非递减排列
    // 归并LA和LB得到新的单链表LC,LC的元素也按值非递减排列
    pa = LA->next;
    pb = LB->next; // pa和pb的初值分别指向两个表的第一个节点
    LC = LA;       // 用LA的头节点作为LC的头节点
    pc = LC;       // pc的初值指向LC的头节点
    while (pa && pb)
    {                             // LA和LB均未到达表尾,依次“摘取”两表中值较小的节点插入到LC的最后
        if (pa->data <= pb->data) // 摘取pa所指节点
        {
            pc->next = pa; // 将pa所指节点链接到pc所指节点之后
            pc = pa;       // pc指向pa
            pa = pa->next; // pa指向下一节点
        }
        else // 摘取pb所指节点
        {
            pc->next = pb; // 将pb所指节点链接到pc所指节点之后
            pc = pb;       // pc指向pb
            pb = pb->next; // pb指向下一节点
        }
    } // while
    pc->next = pa ? pa : pb; // 将非空表的剩余段插入到pc所指节点之后
    delete LB;               // 释放LB的头节点
}

栈和队列

算法 3.1  顺序栈的初始化

//- - - - - 顺序栈的存储结构- - - - -
#define MAXSIZE 100 // 顺序栈存储空间的初始分配量
typedef struct
{
    SElemType *base; // 栈底指针
    SElemType *top;  // 栈顶指针
    int stacksize;   // 栈可用的最大容量
} SqStack;

Status InitStack(SqStack &S)
{                                    // 构造一个空栈S
    S.base = new SElemType[MAXSIZE]; // 为顺序栈动态分配一个最大容量为MAXSIZE的数组空间
    if (!S.base)
        exit(OVERFLOW);    // 存储分配失败
    S.top = S.base;        // top初始为base,空栈
    S.stacksize = MAXSIZE; // stacksize置为栈的最大容量MAXSIZE
    return OK;
}

算法 3.2  顺序栈的入栈

Status Push(SqStack &S, SElemType e)
{ // 插入元素e为新的栈顶元素
    if (S.top - S.base == S.stacksize)
        return ERROR; // 栈满
    *S.top++ = e;     // 将元素e压入栈顶,栈顶指针加1
    return OK;
}

算法 3.3  顺序栈的出栈

Status Pop(SqStack &S, SElemType &e)
{ // 删除S的栈顶元素,用e返回其值
    if (S.top == S.base)
        return ERROR; // 栈空
    e = *--S.top;     // 栈顶指针减1,将栈顶元素赋给e
    return OK;
}

算法 3.4  取顺序栈的栈顶元素

SElemType GetTop(SqStack S)
{                                  // 返回S的栈顶元素,不修改栈顶指针
    if (S.top != S.base)           // 栈非空
        return *(S.top - 1); // 返回栈顶元素的值,栈顶指针不变
}

算法 3.5  链栈的初始化

//- - - - - 链栈的存储结构- - - - -
typedef struct StackNode
{
    ElemType data;
    struct StackNode *next;
} StackNode, *LinkStack;

Status InitStack(LinkStack &S)
{ // 构造一个空栈S,栈顶指针置空
    S = NULL;
    return OK;
}

算法 3.6  链栈的入栈

Status Push(LinkStack &S, SElemType e)
{                      // 在栈顶插入元素e
    p = new StackNode; // 生成新节点
    p->data = e;       // 将新节点数据域置为e
    p->next = S;       // 将新节点插入栈顶
    S = p;             // 修改栈顶指针为p
    return OK;
}

算法 3.7  链栈的出栈

Status Pop(LinkStack &S, SElemType &e)
{ // 删除S的栈顶元素,用e返回其值
    if (S == NULL)
        return ERROR; // 栈空
    e = S->data;      // 将栈顶元素赋给e
    p = S;            // 用p临时保存栈顶元素空间,以备释放
    S = S->next;      // 修改栈顶指针
    delete p;         // 释放原栈顶元素的空间
    return OK;
}

算法 3.8  取链栈的栈顶元素

SElemType GetTop(LinkStack S)
{                       // 返回S的栈顶元素,不修改栈顶指针
    if (S != NULL)      // 栈非空
        return S->data; // 返回栈顶元素的值,栈顶指针不变
}

算法 3.9  遍历输出链表中各个节点的递归算法

void TraverseList(LinkList p)
{
    if (p == NULL)
        return; // 递归终止
    else
    {
        cout << p->data << endl; // 输出当前节点的数据域
        TraverseList(p->next);   // p指向后继节点继续递归
    }
}

算法 3.10 Hanoi 塔问题的递归算法

void Hanoi(int n, char A, char B, char C)
{ // 将塔座A上的n个圆盘按规则搬到塔座C上,塔座B作为辅助塔座
    if (n == 1)
        move(A, 1, C); // 将编号为1的圆盘从塔座A移到塔座C
    else
    {
        Hanoi(n - 1, A, C, B); // 将A上编号为1至n-1的圆盘移到塔座B,塔座C作为辅助塔座
        move(A, n, C);         // 将编号为n的圆盘从塔座A移到塔座C
        Hanoi(n - 1, B, A, C); // 将B上编号为1至n-1的圆盘移到塔座C,塔座A作为辅助塔座
    }
}

算法 3.11  循环队列的初始化

//- - - - - 队列的顺序存储结构- - - - -
#define MAXQSIZE 100 // 队列可能达到的最大长度
typedef struct
{
    QElemType *base; // 存储空间的基地址
    int front;       // 头指针
    int rear;        // 尾指针
} SqQueue;

Status InitQueue(SqQueue &Q)
{                                     // 构造一个空队列Q
    Q.base = new QElemType[MAXQSIZE]; // 为队列分配一个最大容量为MAXQSIZE的数组空间
    if (!Q.base)
        exit(OVERFLOW);   // 存储分配失败
    Q.front = Q.rear = 0; // 将头指针和尾指针置为0,队列为空
    return OK;
}

算法 3.12  求循环队列的长度

int QueueLength(SqQueue Q)
{ // 返回Q的元素个数,即队列的长度
    return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}

算法 3.13  循环队列的入队

Status EnQueue(SqQueue &Q, QElemType e)
{                                           // 插入元素e为Q的新的队尾元素
    if ((Q.rear + 1) % MAXQSIZE == Q.front) // 若尾指针在循环意义上加1后等于头指针,表明队满
        return ERROR;
    Q.base[Q.rear] = e;               // 新元素插入队尾
    Q.rear = (Q.rear + 1) % MAXQSIZE; // 队尾指针加1
    return OK;
}

算法 3.14  循环队列的出队

Status DeQueue(SqQueue &Q, QElemType &e)
{ // 删除Q的队头元素,用e返回其值
    if (Q.front == Q.rear)
        return ERROR;                   // 队空
    e = Q.base[Q.front];                // 保存队头元素
    Q.front = (Q.front + 1) % MAXQSIZE; // 队头指针加1
    return OK;
}

算法 3.15  取循环队列的队头元素

QElemType GetHead(SqQueue Q)
{                               // 返回Q的队头元素,不修改队头指针
    if (Q.front != Q.rear)      // 队列非空
        return Q.base[Q.front]; // 返回队头元素的值,队头指针不变
}

算法 3.16  链队的初始化

//- - - - - 队列的链式存储结构- - - - -
typedef struct QNode
{
    QElemType data;
    struct QNode *next;
} QNode, *QueuePtr;
typedef struct
{
    QueuePtr front; // 队头指针
    QueuePtr rear;  // 队尾指针
} LinkQueue;

Status InitQueue(LinkQueue &Q)
{                                 // 构造一个空队列Q
    Q.front = Q.rear = new QNode; // 生成新节点作为头节点,队头和队尾指针指向此节点
    Q.front->next = NULL;         // 头节点的指针域置空
    return OK;
}

算法 3.17  链队列的入队

Status EnQueue(LinkQueue &Q, QElemType e)
{                  // 插入元素e为Q的新的队尾元素
    p = new QNode; // 为入队元素分配节点空间,用指针p指向
    p->data = e;   // 将新节点数据域置为e
    p->next = NULL;
    Q.rear->next = p; // 将新节点插入队尾
    Q.rear = p;       // 修改队尾指针
    return OK;
}

算法 3.18  链队列的出队

Status DeQueue(LinkQueue &Q, QElemType &e)
{ // 删除Q的队头元素,用e返回其值
    if (Q.front == Q.rear)
        return ERROR;        // 若队列为空,则返回ERROR
    p = Q.front->next;       // p指向队头元素
    e = p->data;             // e保存队头元素的值
    Q.front->next = p->next; // 修改头节点的指针域
    if (Q.rear == p)
        Q.rear = Q.front; // 最后一个元素被删,队尾指针指向头节点
    delete p;             // 释放原队头元素的空间
    return OK;
}

算法 3.19  取链队的队头元素

QElemType GetHead(LinkQueue Q)
{                                   // 返回Q的队头元素,不修改队头指针
    if (Q.front != Q.rear)          // 队列非空
        return Q.front->next->data; // 返回队头元素的值,队头指针不变
}

算法 4.1 BF 算法

int Index_BF(SString S, SString T, int pos)
{ // 返回模式T在主串S中第pos个字符开始第一次出现的位置。若不存在,则返回0
    // 其中,T非空,1≤pos≤S.length
    i = pos;
    j = 1;                           // 初始化
    while (iS.length && jT.length) // 两个串均未比较到串尾
    {
        if (S.ch[i] == T.ch[j])
        {
            ++i;
            ++j;
        } // 继续比较后继字符
        else
        {
            // i是在主串中的位置,减去对比的长度j,等于本次没有比较,且i回退一格,所以+1是i回到原本的位置,+2是去下一个字符所在的位置
            i = i - j + 2;
            j = 1;
        } // 指针后退,重新开始匹配
    }

    if (j > T.length)           // 此时i,j在正确匹配的后一格,减去模式串长度=正确匹配时的第一个元素的位置
        return i - T.length;    // 匹配成功
    else
        return 0;               // 匹配失败
}

算法 4.2 KMP 算法

int Index_KMP(SString S, SString T, int pos)
{ // 利用模式串T的next函数求T在主串S中第pos个字符之后的位置
    // 其中,T非空,1≤pos≤S.length
    i = pos;
    j = 1;
    while (i <= S.length && j <= T.length) // 两个串均未比较到串尾
    {
        if (j == 0S.ch [i] == T.ch[j])
        {
            ++i;
            ++j;
        } // 继续比较后续字符
        else
            j = next[j]; // 模式串向右移动
    }
    if (j > T.length)
        return i - T.length; // 匹配成功
    else
        return 0; // 匹配失败
}

算法 4.3  计算 next 函数值

void get_next(SString T, int next[])
{ // 求模式串T的next函数值并将其存入数组next
    i = 1;
    next[1] = 0;
    j = 0;
    while (i < T.length)
    {
        if (j == 0T.ch [i] == T.ch[j])
        {
            ++i;
            ++j;
            next[i] = j;
        }
        else
            j = next[j];
    }
}

算法 4.4  计算 next 函数修正值

void get_nextval(SString T, int nextval[])
{ // 求模式串T的next函数修正值并将之存入数组nextval
    i = 1;
    nextval[1] = 0;
    j = 0;
    while (i < T.length)
    {
        if (j == 0T.ch [i] == T.ch[j])
        {
            ++i;
            ++j;
            if (T.ch[i] != T.ch[j])
                nextval[i] = j;
            else
                nextval[i] = nextval[j];
        }
        else
            j = nextval[j];
    }
}

算法 5.1  中序遍历的递归算法

void InOrderTraverse(BiTree T)
{          // 中序遍历二叉树T的递归算法
    if (T) // 若二叉树非空
    {
        InOrderTraverse(T->lchild); // 中序遍历左子树
        cout << T->data;            // 访问根节点
        InOrderTraverse(T->rchild); // 中序遍历右子树
    }
}

算法 5.2  中序遍历的非递归算法

void InOrderTraverse(BiTree T)
{ // 中序遍历二叉树T的非递归算法
    InitStack(S);
    p = T;
    q = new BiTNode;
    while (p || !StackEmpty(S))
    {
        if (p) // p非空
        {
            Push(S, p);    // 根指针进栈
            p = p->lchild; // 根指针进栈,遍历左子树
        }
        else // p为空
        {
            Pop(S, q);       // 退栈
            cout << q->data; // 访问根节点
            p = q->rchild;   // 遍历右子树
        }
    }
}

算法 5.3 按照先序遍历的顺序建立二叉链表

void CreateBiTree(BiTree &T)
{ // 按先序次序输入二叉树中节点的值(单字符),创建二叉链表表示的二叉树T
    cin >> ch;
    if (ch == '#')
        T = NULL; // 递归结束,建空树
    else          // 递归创建二叉树
    {
        T = new BiTNode;         // 生成根节点
        T->data = ch;            // 根节点数据域置为ch
        CreateBiTree(T->lchild); // 递归创建左子树
        CreateBiTree(T->rchild); // 递归创建右子树
    } // else
}

算法 5.4  复制二叉树

void Copy(BiTree T, BiTree &NewT)
{                  // 复制一棵和T完全相同的二叉树
    if (T == NULL) // 如果是空树,递归结束
    {
        NewT = NULL;
        return;
    }
    else
    {
        NewT = new BiTNode;
        NewT->data = T->data;          // 复制根节点
        Copy(T->lchild, NewT->lchild); // 递归复制左子树
        Copy(T->rchild, NewT->rchild); // 递归复制右子树

    } // else
}

算法 5.5  计算二叉树的深度

int Depth(BiTree T)
{ // 计算二叉树T的深度
    if (T == NULL)
        return 0; // 如果是空树,深度为0,递归结束
    else
    {
        m = Depth(T->lchild);      // 递归计算左子树的深度记为m
        n = Depth(T->rchild);      // 递归计算右子树的深度记为n
        if (m > n) return (m + 1); // 二叉树的深度为m与n的较大者加1
        else return (n + 1);
    }
}

算法 5.6  统计二叉树中节点的个数

int NodeCount(BiTree T)
{ // 统计二叉树T中节点的个数
    if (T == NULL)
        return 0; // 如果是空树,则节点个数为0,递归结束
    else
        return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
    // 否则节点个数为左子树的节点个数+右子树的节点个数+1
}

算法 5.7  以节点 p 为根的子树中序线索化

void InThreading(BiThrTree p)
{ // pre是全局变量,初始化时其右孩子指针为空,便于在树的最左点开始建线索
    if (p)
    {
        InThreading(p->lchild); // 左子树递归线索化
        if (!p->lchild)         // p的左孩子为空
        {
            p->LTag = 1;     // 给p加上左线索
            p->lchild = pre; // p的左孩子指针指向pre(前驱)
        } // if
        else
            p->LTag = 0;

        if (!pre->rchild) // pre的右孩子为空
        {
            pre->RTag = 1;   // 给pre加上右线索
            pre->rchild = p; // pre的右孩子指针指向p(后继)
        } // if
        else
            pre->RTag = 0;
        pre = p;                // 保持pre指向p
        InThreading(p->rchild); // 右子树递归线索化
    }
}

算法 5.8  带头节点的二叉树中序线索化

void InOrderThreading(BiThrTree &Thrt, BiThrTree T)
{                         // 中序遍历二叉树T,并将其中序线索化,Thrt指向头节点
    Thrt = new BiThrNode; // 建头节点
    Thrt->LTag = 0;       // 头节点没有前驱,Ltag一定为0,头节点有左孩子,若树非空,则其左孩子为树根
    Thrt->RTag = 1;       // 头节点的右孩子指针为右线索
    Thrt->rchild = Thrt;  // 初始化时右指针指向自己
    if (!T)
        Thrt->lchild = Thrt; // 若树为空,则左指针也指向自己
    else
    {
        Thrt->lchild = T;
        pre = Thrt;               // 头节点的左孩子指向根,pre初值指向头节点
        InThreading(T);     // 调用算法5.7,对以T为根的二叉树进行中序线索化
        pre->rchild = Thrt; // 算法5.7结束后,pre为最右节点,pre的右线索指向头节点
        pre->RTag = 1;
        Thrt->rchild = pre; // 头节点的右线索指向pre
    }
}

算法 5.9  遍历中序线索二叉树

void InOrderTraverse_Thr(BiThrTree T)
{ // T指向头节点,头节点的左链lchild指向根节点,可参见线索化算法5.8。
    // 中序遍历二叉线索树T的非递归算法,对每个数据元素直接输出
    p = T->lchild; // p指向根节点
    while (p != T) // 空树或遍历结束时,p==T
    {
        while (p->LTag == 0) p = p->lchild; // 沿左孩子向下
        cout << p->data;                    // 访问其左子树为空的节点
        while (p->RTag == 1 && p->rchild != T)
        {
            p = p->rchild;
            cout << p->data; // 沿右线索访问后继节点
        }
        p = p->rchild; // 转向p的右子树
    }
}

算法 5.10  构造哈夫曼树

void CreateHuffmanTree(HuffmanTree &HT, int n)
{ // 构造哈夫曼树HT
    if (n <= 1)
        return;
    m = 2 * n - 1;  // n个节点,n-1个间隔,共2n-1个节点
    HT = new HTNode[m + 1];  // 0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根节点
    for (i = 1; i <= m; ++i) // 将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0
    {
        HT[i].parent = 0;
        HT[i].lchild = 0;
        HT[i].rchild = 0;
    }
    for (i = 1; i <= n; ++i) // 输入前n个单元中叶子节点的权值
        cin >> HT[i].weight;
    /*- - - - - - - - - - -初始化工作结束,下面开始创建哈夫曼树- - - - - - - - - -*/
    for (i = n + 1; i <= m; ++i) 
    { // 通过n-1次的选择、删除、合并来创建哈夫曼树
        Select(HT, i - 1, s1, s2);
        // 在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的节点,并返回它们在HT中的序号s1和s2
        HT[s1].parent = i;
        HT[s2].parent = i;
        // 得到新节点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
        HT[i].lchild = s1;
        HT[i].rchild = s2;                            // s1,s2分别作为i的左右孩子
        HT[i].weight = HT[s1].weight + HT[s2].weight; // i的权值为左右孩子权值之和
    } // for
}

算法 5.11  根据哈夫曼树求哈夫曼编码

void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n)
{                            // 从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
    HC = new char *[n + 1];  // 分配存储n个字符编码的编码表空间
    cd = new char[n];        // 分配临时存放每个字符编码的动态数组空间
    cd[n  1] = '\0';        // 编码结束符
    for (i = 1; i <= n; ++i) // 逐个字符求哈夫曼编码
    {
        start = n - 1; // start开始时指向最后,即编码结束符位置
        c = i;
        f = HT[i].parent; // f指向节点c的双亲节点
        while (f != 0)    // 从叶子节点开始向上回溯,直到根节点
        {
            --start; // 回溯一次start向前指一个位置
            if (HT[f].lchild == c)
                cd[start] = '0'; // 节点c是f的左孩子,则生成代码0
            else
                cd[start] = '1'; // 节点c是f的右孩子,则生成代码1
            c = f;
            f = HT[f].parent; // 继续向上回溯
        } // 求出第i个字符的编码
        HC[i] = new char[n - start]; // 为第i个字符编码分配空间
        strcpy(HC[i], &cd[start]);   // 将求得的编码从临时空间cd复制到HC的当前行中
    } // for
    delete cd; // 释放临时空间
}

算法 6.1  采用邻接矩阵表示法创建无向网

//-----图的邻接矩阵存储表示-----
#define MaxInt 32767     // 表示极大值,即∞
#define MVNum 100        // 最大顶点数
typedef char VerTexType; // 假设顶点的数据类型为字符型
typedef int ArcType;     // 假设边的权值类型为整型
typedef struct
{
    VerTexType vexs[MVNum];     // 顶点表
    ArcType arcs[MVNum][MVNum]; // 邻接矩阵
    int vexnum, arcnum;         // 图的当前点数和边数
} AMGraph;

Status CreateUDN(AMGraph &G)
{                                  // 采用邻接矩阵表示法,创建无向网G
    cin >> G.vexnum >> G.arcnum;   // 输入总顶点数,总边数
    for (i = 0; i < G.vexnum; ++i) // 依次输入点的信息
        cin >> G.vexs[i];
    for (i = 0; i < G.vexnum; ++i) // 初始化邻接矩阵,边的权值均置为极大值MaxInt
        for (j = 0; j < G.vexnum; ++j)
            G.arcs[i][j] = MaxInt;
    for (k = 0; k < G.arcnum; ++k) // 构造邻接矩阵
    {
        cin >> v1 >> v2 >> w; // 输入一条边依附的顶点及权值
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);        // 确定v1和v2在G中的位置,即顶点数组的下标
        G.arcs[i][j] = w;            // 边<v1, v2>的权值置为w
        G.arcs[j][i] = G.arcs[i][j]; // 置<v1, v2>的对称边<v2, v1>的权值为w
    } // for
    return OK;
}

算法 6.2  采用邻接表表示法创建无向图

//- - - - -图的邻接表存储表示- - - - -
#define MVNum 100      // 最大顶点数
typedef struct ArcNode // 边节点
{
    int adjvex;              // 该边所指向的顶点的位置
    struct ArcNode *nextarc; // 指向下一条边的指针
    OtherInfo info;          // 和边相关的信息
} ArcNode;
typedef struct VNode // 顶点信息
{
    VerTexType data;
    ArcNode *firstarc;   // 指向第一条依附该顶点的边的指针
} VNode, AdjList[MVNum]; // AdjList表示邻接表类型
typedef struct           // 邻接表
{
    AdjList vertices;
    int vexnum, arcnum; // 图的当前顶点数和边数
} ALGraph;

Status CreateUDG(ALGraph &G)
{                                  // 采用邻接表表示法,创建无向图G
    cin >> G.vexnum >> G.arcnum;   // 输入总顶点数,总边数
    for (i = 0; i < G.vexnum; ++i) // 输入各点,构造表头节点表
    {
        cin >> G.vertices[i].data;     // 输入顶点值
        G.vertices[i].firstarc = NULL; // 初始化表头节点的指针域为NULL
    } // for
    for (k = 0; k < G.arcnum; ++k) // 输入各边,构造边表
    {
        cin >> v1 >> v2; // 输入一条边依附的两个顶点
        i = LocateVex(G, v1);
        j = LocateVex(G, v2);
        // 确定v1和v2在G中的位置,即顶点在G.vertices中的序号
        p1 = new ArcNode; // 生成一个新的边节点*p1
        p1->adjvex = j;   // 邻接点序号为j
        p1->nextarc = G.vertices[i].firstarc;
        G.vertices[i].firstarc = p1;
        // 将新节点*p1插入顶点vi的边表头部
        p2 = new ArcNode; // 生成另一个对称的新的边节点*p2
        p2->adjvex = i;   // 邻接点序号为i
        p2->nextarc = G.vertices[j].firstarc;
        G.vertices[j].firstarc = p2;
        // 将新节点*p2插入顶点vj的边表头部
    } // for
    return OK;
}

有向图的十字链表存储表示

//- - - - -有向图的十字链表存储表示- - - - -
#define MAX_VERTEX_NUM 20
typedef struct ArcBox
{
    int tailvex, headvex;         // 该弧的尾和头顶点的位置
    struct ArcBox *hlink, *tlink; // 分别为弧头相同和弧尾相同的弧的链域
    InfoType *info;               // 该弧相关信息的指针
} ArcBox;
typedef struct VexNode
{
    VertexType data;
    ArcBox *firstin, *firstout; // 分别指向该顶点第一条入弧和出弧
} VexNode;
typedef struct
{
    VexNode xlist[MAX_VERTEX_NUM]; // 表头向量
    int vexnum, arcnum;            // 有向图的当前顶点数和弧数
} OLGraph;

无向图的邻接多重表存储表示

//- - - - -无向图的邻接多重表存储表示- - - - -
#define MAX_VERTEX_NUM 20
typedef enum
{
    unvisited,
    visited
} VisitIf;
typedef struct EBox
{
    VisitIf mark;               // 访问标记
    int ivex, jvex;             // 该边依附的两个顶点的位置
    struct EBox *ilink, *jlink; // 分别指向依附这两个顶点的下一条边
    InfoType *info;             // 该边信息指针
} Ebox;
typedef struct VexBox
{
    VertexType data;
    EBox *firstedge; // 指向第一条依附该顶点的边
} VexBox;
typedef struct
{
    VexBox adjmulist[MAX_VERTEX_NUM];
    int vexnum, edgenum; // 无向图的当前顶点数和边数
} AMLGraph;

算法 6.3  深度优先搜索遍历连通图

bool visited[MVNum]; // 访问标志数组,其初值为“false”
void DFS(Graph G, int v)
{ // 从第v个顶点出发递归地深度优先遍历图G
    cout << v;
    visited[v] = true; // 访问第v个顶点,并置访问标志数组相应分量值为true
    for (w = FirstAdjVex(G, v); w >= 0; w = NextAdjVex(G, v, w))
        // 依次检查v的所有邻接点w ,FirstAdjVex(G, v)表示v的第一个邻接点
        // NextAdjVex(G,v,w)表示v相对于w的下一个邻接点,w≥0表示存在邻接点
        if (!visited[w])
            DFS(G, w); // 对v的尚未访问的邻接顶点w递归调用DFS()
}

算法 6.4  深度优先搜索遍历非连通图

void DFSTraverse(Graph G)
{ // 对非连通图G进行深度优先遍历
    for (v = 0; v < G.vexnum; ++v)
        visited[v] = false;        // 访问标志数组初始化
    for (v = 0; v < G.vexnum; ++v) // 循环调用算法6.3
        if (!visited[v])
            DFS(G, v); // 对尚未访问的顶点调用DFS
}

算法 6.5  采用邻接矩阵表示图的深度优先搜索遍历

void DFS _AM(AMGraph G, int v)
{ // 图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
    cout << v;
    visited[v] = true;             // 访问第v个顶点,并置访问标志数组相应分量值为true
    for (w = 0; w < G.vexnum; w++) // 依次检查邻接矩阵v所在的行
        if ((G.arcs[v][w] != 0) && (!visited[w]))
            DFS_AM(G, w);
    // G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS_AM()
}

算法 6.6  采用邻接表表示图的深度优先搜索遍历

void DFS_AL(ALGraph G, int v)
{ // 图G为邻接表类型,从第v个顶点出发深度优先搜索遍历图G
    cout << v;
    visited[v] = true;          // 访问第v个顶点,并置访问标志数组相应分量值为true
    p = G.vertices[v].firstarc; // p指向v的边链表的第一个边节点
    while (p != NULL)           // 边节点非空
    {
        w = p->adjvex; // 表示w是v的邻接点
        if (!visited[w])
            DFS_AL(G, w); // 如果w未访问,则递归调用DFS_AL()
        p = p->nextarc;   // p指向下一个边节点
    } // while
}

算法 6.7  广度优先搜索遍历连通图

void BFS(Graph G, int v)
{ // 按广度优先非递归遍历连通图G
    cout << v;
    visited[v] = true;     // 访问第v个顶点,并置访问标志数组相应分量值为true
    InitQueue(Q);          // 辅助队列Q初始化,置空
    EnQueue(Q, v);         // v入队
    while (!QueueEmpty(Q)) // 队列非空
    {
        DeQueue(Q, u); // 队头元素出队并置为u
        for (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w))
            // 依次检查u的所有邻接点w,FirstAdjVex(G,u)表示u的第一个邻接点
            // NextAdjVex(G,u,w)表示u相对于w的下一个邻接点,w≥0表示存在邻接点
            if (!visited[w]) // w为u的尚未访问的邻接顶点
            {
                cout << w;
                visited[w] = true; // 访问w,并置访问标志数组相应分量值为true
                EnQueue(Q, w);     // w入队
            } // if
    } // while
}

算法 6.8  普里姆算法

// 辅助数组的定义,用来记录从顶点集U到V-U的权值最小的边
struct
{
    VerTexType adjvex; // 最小边在U中的那个顶点
    ArcType lowcost;   // 最小边上的权值
} closedge[MVNum];

void MiniSpanTree_Prim(AMGraph G, VerTexType u)
{                                  // 无向网G以邻接矩阵形式存储,从顶点u出发构造G的最小生成树T,输出T的各条边
    k = LocateVex(G, u);           // k为顶点u的下标
    for (j = 0; j < G.vexnum; ++j) // 对V−U的每一个顶点初始化closedge[j]
        if (j != k)
            closedge[j] = {u, G.arcs[k][j]}; //{adjvex, lowcost}
    closedge[k].lowcost = 0;                 // 初始,U={u}
    for (i = 1; i < G.vexnum; ++i)
    { // 选择其余n-1个顶点,生成n-1条边(n=G.vexnum)
        k = Min(closedge);
        // 求出T的下一个节点 :第k个顶点,closedge[k]中存有当前最小边
        u0 = closedge[k].adjvex; // u0为最小边的一个顶点,u0∈U
        v0 = G.vexs[k];          // v0为最小边的另一个顶点,v0∈V − U
        cout << u0 << v0;        // 输出当前的最小边(u0, v0)
        closedge[k].lowcost = 0; // 第k个顶点并入U集
        for (j = 0; j < G.vexnum; ++j)
            if (G.arcs[k][j] < closedge[j].lowcost) // 新顶点并入U后重新选择最小边
                closedge[j] = {G.vexs[k], G.arcs[k][j]};
    } // for
}

算法 6.9  克鲁斯卡尔算法

// 辅助数组Edges的定义
struct
{
    VerTexType Head; // 边的始点
    VerTexType Tail; // 边的终点
    ArcType lowcost; // 边上的权值
} Edge[arcnum];

// 辅助数组Vexset的定义
int Vexset[MVNum];

void MiniSpanTree_ Kruskal(AMGraph G)
{                                  // 无向网G以邻接矩阵形式存储,构造G的最小生成树T,输出T的各条边
    Sort(Edge);                    // 将数组Edge中的元素按权值从小到大排序
    for (i = 0; i < G.vexnum; ++i) // 辅助数组,表示各顶点自成一个连通分量
        Vexset[i] = i;
    for (i = 0; i < G.arcnum; ++i) // 依次查看数组Edge中的边
    {
        v1 = LocateVex(G, Edge[i].Head); // v1为边的始点Head的下标
        v2 = LocateVex(G, Edge[i].Tail); // v2为边的终点Tail的下标
        vs1 = Vexset[v1];                // 获取边Edge[i]的始点所在的连通分量vs1
        vs2 = Vexset[v2];                // 获取边Edge[i]的终点所在的连通分量vs2
        if (vs1 != vs2)                  // 边的两个顶点分属不同的连通分量
        {
            cout << Edge[i].Head << Edge[i].Tail; // 输出此边
            for (j = 0; j < G.vexnum; ++j)        // 合并vs1和vs2两个分量,即两个集合统一编号
                if (Vexset[j] == vs2)
                    Vexset[j] = vs1; // 集合编号为vs2的都改为vs1
        } // if
    } // for
}

算法 6.10  迪杰斯特拉算法

void ShortestPath_DIJ(AMGraph G, int v0)
{                           // 用Dijkstra算法求有向网的v0顶点到其余顶点的最短路径
    n = G.vexnum;           // n为G中顶点的个数
    for (v = 0; v < n; ++v) // n个顶点依次初始化
    {
        S[v] = false;         // S初始为空集
        D[v] = G.arcs[v0][v]; // 将v0到各个终点的最短路径长度初始化为弧上的权值
        if (D[v] < MaxInt)
            Path[v] = v0; // 如果v0和v之间有弧,则将v的前驱置为v0
        else
            Path[v] = -1; // 如果v0和v之间无弧,则将v的前驱置为-1
    } // for
    S[v0] = true; // 将v0加入S
    D[v0] = 0;    // 源点到源点的距离为0
    /*--------初始化结束,开始主循环,每次求得v0到某个顶点v的最短路径,将v加到S集---------*/
    for (i = 1; i < n; ++i) // 对其余n − 1个顶点,依次进行计算
    {
        min = MaxInt;
        for (w = 0; w < n; ++w)
            if (!S[w] && D[w] < min)
            {
                v = w;
                min = D[w];
            }                   // 选择一条当前的最短路径,终点为v
        S[v] = true;            // 将v加入S
        for (w = 0; w < n; ++w) // 更新从v0出发到集合V − S上所有顶点的最短路径长度
        {
            if (!S[w] && (D[v] + G.arcs[v][w] < D[w]))
            {
                D[w] = D[v] + G.arcs[v][w]; // 更新D[w]
                Path[w] = v;                // 更改w的前驱为v
            } // if
        }
    } // for
}

算法 6.11  弗洛伊德算法

void ShortestPath_Floyd(AMGraph G)
{                                  // 用弗洛伊德算法求有向网G中各对顶点i和j之间的最短路径
    for (i = 0; i < G.vexnum; ++i) // 各对顶点之间初始已知路径及距离
        for (j = 0; j < G.vexnum; ++j)
        {
            D[i][j] = G.arcs[i][j];
            if (D[i][j] < MaxInt && i != j)
                Path[i][j] = i; // 如果i和j之间有弧,则将j的前驱置为i
            else
                Path[i][j] = -1; // 如果i和j之间无弧,则将j的前驱置为-1
        } // for
    for (k = 0; k < G.vexnum; ++k)
        for (i = 0; i < G.vexnum; ++i)
            for (j = 0; j < G.vexnum; ++j)
                if (D[i][k] + D[k][j] < D[i][j]) // 从i经k到j的一条路径更短
                {
                    D[i][j] = D[i][k] + D[k][j]; // 更新D[i][j]
                    Path[i][j] = Path[k][j];     // 更改j的前驱为k
                } // if
}

算法 6.12  拓扑排序

Status TopologicalSort(ALGraph G, int topo[])
{ // 有向图G采用邻接表作为存储结构
    // 若G无回路,则生成G的一个拓扑序列topo并返回OK,否则ERROR
    FindInDegree(G, indegree); // 求出各顶点的入度并存入数组indegree中
    InitStack(S);              // 栈S初始化为空
    for (i = 0; i < G.vexnum; ++i)
        if (!indegree[i])
            Push(S, i);    // 入度为0者进栈
    m = 0;                 // 对输出顶点计数,初始为0
    while (!StackEmpty(S)) // 栈S非空
    {
        Pop(S, i);                  // 使栈顶顶点vi出栈
        topo[m] = i;                // 将顶点vi保存在拓扑序列数组topo中
        ++m;                        // 对输出顶点计数
        p = G.vertices[i].firstarc; // p指向顶点vi的第一个邻接点
        while (p != NULL)
        {
            k = p->adjvex; // vk为vi的邻接点
            --indegree[k]; // vi的每个邻接点的入度减1
            if (indegree[k] == 0)
                Push(S, k); // 若入度减为0,则入栈
            p = p->nextarc; // p指向顶点vi下一个邻接节点
        } // while
    } // while
    if (m < G.vexnum)
        return ERROR; // 该有向图有回路
    else
        return OK;
}

算法 6.13  关键路径算法

Status CriticalPath(ALGraph G)
{ // G为邻接表存储的有向网,输出G的各项关键活动
    if (!TopologicalOrder(G, topo))
        return ERROR;
    // 调用拓扑排序算法,使拓扑序列保存在topo中,若调用失败,则存在有向环,返回ERROR
    n = G.vexnum;           // n为顶点个数
    for (i = 0; i < n; i++) // 给每个事件的最早发生时间置初值0
        ve[i] = 0;
    /*- - - - - - - - - - - 按拓扑次序求每个事件的最早发生时间 - - - - - - - - - -*/
    for (i = 0; i < n; i++)
    {
        k = topo[i];                // 取得拓扑序列中的顶点序号k
        p = G.vertices[k].firstarc; // p指向k的第一个邻接顶点
        while (p != NULL)
        {                                  // 依次更新k的所有邻接顶点的最早发生时间
            j = p->adjvex;                 // j为邻接顶点的序号
            if (ve[j] < ve[k] + p->weight) // 更新顶点j的最早发生时间ve[j]
                ve[j] = ve[k] + p->weight;
            p = p->nextarc; // p指向k的下一个邻接顶点
        } // while
    } // for
    for (i = 0; i < n; i++) // 给每个事件的最迟发生时间置初值ve[n-1]
        vl[i] = ve[n - 1];
    /*- - - - - - - - - - - - -按逆拓扑次序求每个事件的最迟发生时间- -- - - - - - - - - - - -*/
    for (i = n - 1; i >= 0; i--)
    {
        k = topo[i];                // 取得拓扑序列中的顶点序号k
        p = G.vertices[k].firstarc; // p指向k的第一个邻接顶点
        while (p != NULL)           // 根据k的邻接点,更新k的最迟发生时间
        {
            j = p->adjvex;                 // j为邻接顶点的序号
            if (vl[k] > vl[j] - p->weight) // 更新顶点k的最迟发生时间vl[k]
                vl[k] = vl[j] - p->weight;
            p = p->nextarc; // p指向k的下一个邻接顶点
        } // while
    } // for
    /*- - - - - - - - - - - - - -- -判断每一活动是否为关键活动- -- - - - - - - - - - - -*/
    for (i = 0; i < n; i++) // 每次循环针对vi为活动开始点的所有活动
    {
        p = G.vertices[i].firstarc; // p指向i的第一个邻接顶点
        while (p != NULL)
        {
            j = p->adjvex;         // j为i的邻接顶点的序号
            e = ve[i];             // 计算活动<vi, vj>的最早开始时间
            l = vl[j] - p->weight; // 计算活动<vi, vj>的最迟开始时间
            if (e == l)            // 若为关键活动,则输出<vi, vj>
                cout << G.vertices[i].data << G.vertices[j].data;
            p = p->nextarc; // p指向i的下一个邻接顶点
        } // while
    } // for
}

查找

数据元素定义

typedef struct
{
    KeyType key;        // 关键字域
    InfoType otherinfo; // 其他域
} ElemType;
typedef struct
{
    ElemType *R; // 存储空间基地址
    int length;  // 当前长度
} SSTable;

算法 7.1  顺序查找

int Search_Seq(SSTable ST, KeyType key)
{ // 在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0
    for (i = ST.length; i >= 1; --i)
        if (ST.R[i].key == key)
            return i; // 从后往前查找
    return 0;
}

算法 7.2  设置监视哨的顺序查找

int Search_Seq(SSTable ST, KeyType key)
{                      // 在顺序表ST中顺序查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0
    ST.R[0].key = key; // “监视哨”
    for (i = ST.length; ST.R[i].key != key; --i); // 从后往前找
    return i;
}

算法 7.3  折半查找

int Search_Bin(SSTable ST, KeyType key)
{ // 在有序表ST中折半查找其关键字等于key的数据元素。若找到,则函数值为该元素在表中的位置,否则为0
    low = 1;
    high = ST.length; // 置查找区间初值
    while (low <= high)
    {
        mid = (low + high) / 2;
        if (key == ST.R[mid].key)
            return mid; // 找到待查元素
        else if (key < ST.R[mid].key)
            high = mid - 1; // 继续在前一子表进行查找
        else
            low = mid + 1; // 继续在后一子表进行查找
    } // while
    return 0; // 表中不存在待查元素
}

算法 7.4  二叉排序树的递归查找

//- - - - -二叉排序树的二叉链表存储表示- - - - -
typedef struct
{
    KeyType key;        // 关键字项
    InfoType otherinfo; // 其他数据项
} ElemType;             // 每个节点的数据域的类型
typedef struct BSTNode
{
    ElemType data;                   // 每个节点的数据域包括关键字项和其他数据项
    struct BSTNode *lchild, *rchild; // 左右孩子指针
} BSTNode, *BSTree;

BSTree SearchBST(BSTree T, KeyType key)
{ // 在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素
    // 若查找成功,则返回指向该数据元素节点的指针,否则返回空指针
    if ((!T) || key == T->data.key)
        return T; // 查找结束
    else if (key < T->data.key)
        return SearchBST(T->lchild, key); // 在左子树中继续查找
    else
        return SearchBST(T->rchild, key); // 在右子树中继续查找
}

算法 7.5  二叉排序树的插入

void InsertBST(BSTree &T, ElemType e)
{ // 当二叉排序树T中不存在关键字等于e.key的数据元素时,则插入该元素
    if (!T)
    {                                 // 找到插入位置,递归结束
        S = new BSTNode;              // 生成新节点*S
        S->data = e;                  // 新节点*S的数据域置为e
        S->lchild = S->rchild = NULL; // 新节点*S作为叶子节点
        T = S;                        // 把新节点*S链接到已找到的插入位置
    }
    else if (e.key < T->data.key)
        InsertBST(T->lchild, e); // 将*S插入左子树
    else if (e.key > T->data.key)
        InsertBST(T->rchild, e); // 将*S插入右子树
}

算法 7.6  二叉排序树的创建

void CreatBST(BSTree &T)
{             // 依次读入关键字为key的节点,将相应节点插入二叉排序树T中
    T = NULL; // 将二叉排序树T初始化为空树
    cin >> e;
    while (e.key != ENDFLAG) // ENDFLAG为自定义常量,作为输入结束标志
    {
        InsertBST(T, e); // 将此节点插入二叉排序树T中
        cin >> e;
    }
}

算法 7.7  二叉排序树的删除

void DeleteBST(BSTree &T, KeyType key)
{ // 从二叉排序树T中删除关键字等于key的节点
    p = T;
    f = NULL; // 初始化
    /*------------下面的while循环从根开始查找关键字等于key的节点*p---------------*/
    while (p)
    {
        if (p->data.key == key)
            break; // 找到关键字等于key的节点*p,结束循环
        f = p;     //*f为*p的双亲节点
        if (p->data.key > key)
            p = p->lchild; // 在*p的左子树中继续查找
        else
            p = p->rchild; // 在*p的右子树中继续查找
    } // while
    if (!p)
        return; // 找不到被删节点则返回
    /*----考虑3种情况实现p所指子树内部的处理 :*p左右子树均不空、无右子树、无左子树---*/
    q = p;
    if ((p->lchild) && (p->rchild)) // 被删节点*p左右子树均不空
    {
        s = p->lchild;
        while (s->rchild) // 在*p的左子树中继续查找其前驱节点,即最右下节点
        {
            q = s;
            s = s->rchild; // 向右到尽头
        }
        p->data = s->data; // s指向被删节点的“前驱”
        if (q != p)
            q->rchild = s->lchild; // 重接*q的右子树
        else
            q->lchild = s->lchild; // 重接*q的左子树
        delete s;
        return;
    } // if
    else if (!p->rchild) // 被删节点*p无右子树,只需重接其左子树
    {
        p = p->lchild;
    } // else if
    else if (!p->lchild) // 被删节点*p无左子树,只需重接其右子树
    {
        p = p->rchild;
    } // else if
    /*-----------------将p所指的子树挂接到其双亲节点*f相应的位置----------------*/
    if (!f)
        T = p; // 被删节点为根节点
    else if (q == f->lchild)
        f->lchild = p; // 挂接到*f的左子树位置
    else
        f->rchild = p; // 挂接到*f的右子树位置
    delete q;
}

算法 7.8 B-树的查找

#define m 3 // B-树的阶,暂设为3
typedef struct BTNode
{
    int keynum;                // 节点中关键字的个数,即节点的大小
    struct BTNode *parent;     // 指向双亲节点
    KeyType K[m + 1];          // 关键字向量,0号单元未用
    struct BTNode *ptr[m + 1]; // 子树指针向量
    Record *recptr[m + 1];     // 记录指针向量,0号单元未用
} BTNode, *BTree;              // B-树节点和B-树的类型
typedef struct
{
    BTNode *pt; // 指向找到的节点
    int i;      // 1 ~ m,在节点中的关键字序号
    int tag;    // 1表示查找成功,0表示查找失败
} Result;       // B-树的查找结果类型

Result SearchBTree(BTree T, KeyType key)
{ // 在m阶B-树T上查找关键字key,返回结果(pt,i,tag)
    // 若查找成功,则特征值tag=1,指针pt所指节点中第i个关键字等于key
    // 否则特征值tag=0,等于key的关键字应插入在指针pt所指节点中第i个和第i+1个关键字之间
    p = T;
    q = NULL;
    found = FALSE;
    i = 0; // 初始化,p指向待查节点,q指向p的双亲
    while (p && !found)
    {
        i = Search(p, key);
        // 在p->K[1..keynum]中查找i,使得p->K[i]<=key<p->K[i+1]
        if (i > 0 && p->K[i] == key)
            found = TRUE; // 找到待查关键字
        else
        {
            q = p;
            p = p->ptr[i];
        }
    }
    if (found)
        return (p, i, l); // 查找成功
    else
        return (q, i, 0); // 查找不成功,返回key的插入位置信息
}

算法 7.9 B-树的插入

Status InsertBTree(BTree &T, KeyType key, BTree q, int i)
{ // 在m阶B-树T上节点*q的K[i]与K[i+1]之间插入关键字key
    // 若引起节点过大,则沿双亲链进行必要的节点分裂调整,使T仍是m阶B-树
    x = key;
    ap = NULL;
    finished = FALSE; // x表示新插入的关键字,ap为一个空指针
    while (q && !finished)
    {
        Insert(q, i, x, ap); // 将x和ap分别插入q->key[i+1]和q->ptr[i+1]
        if (q->keynum < m)
            finished = TRUE; // 插入完成
        else                 // 分裂节点
        {
            // s向上取整
            s = ceil((m + 1) / 2);
            split(q, s, ap);
            x = q->K[s];
            // 将q->K[s+1..m],q->ptr[s..m]和q->recptr[s+1..m]移入新节点*ap
            q = q->parent;
            if (q)
                i = Search(q, x); // 在双亲节点*q中查找x的插入位置
        } // else
    } // while
    if (!finished)            // T是空树(参数q初值为NULL)或者根节点已分裂为节点*q和*ap
        NewRoot(T, q, x, ap); // 生成含信息(T,x,ap)的新的根节点*T,原T和ap为子树指针
    return OK;
}

算法 7.10 散列表的查找

//- - - - -开放地址法散列表的存储表示- - - - -
#define m 20 // 散列表的表长
typedef struct
{
    KeyType key;        // 关键字项
    InfoType otherinfo; // 其他数据项
} HashTable[m];
#define NULLKEY 0 // 单元为空的标记
int SearchHash(HashTable HT, KeyType key)
{                // 在散列表HT中查找关键字为key的元素,若查找成功,返回散列表的单元标号,否则返回-1
    H0 = H(key); // 根据散列函数H(key)计算散列地址
    if (HT[H0].key == NULLKEY)
        return -1; // 若单元H0为空,则所查元素不存在
    else if (HT[H0].key == key)
        return H0; // 若单元H0中元素的关键字为key,则查找成功
    else
    {
        for (i = 1; i < m; ++i)
        {
            Hi = (H0 + i) % m; // 按照线性探测法计算下一个散列地址Hi
            if (HT[Hi].key == NULLKEY)
                return -1; // 若单元Hi为空,则所查元素不存在
            else if (HT[Hi].key == key)
                return Hi; // 若单元Hi中元素的关键字为key,则查找成功
        } // for
        return -1;
    } // else
}

排序

算法 8.1  直接插入排序

void InsertSort(SqList &L)
{ // 对顺序表L进行直接插入排序
    for (i = 2; i <= L.length; ++i)
        if (L.r[i].key < L.r[i - 1].key) // “<”,需将r[i]插入有序子表
        {
            L.r[0] = L.r[i];                              // 将待插入的记录暂存到监视哨中
            L.r[i] = L.r[i - 1];                          // r[i-1]后移
            for (j = i - 2; L.r[0].key < L.r[j].key; --j) // 从后向前寻找插入位置
                L.r[j + 1] = L.r[j];                      // 记录逐个后移,直到找到插入位置
            L.r[j + 1] = L.r[0];                          // 将r[0]即原r[i],插入正确位置
        } // if
}

算法 8.2  折半插入排序

void BInsertSort(SqList &L)
{ // 对顺序表L进行折半插入排序
    for (i = 2; i <= L.length; ++i)
    {
        L.r[0] = L.r[i]; // 将待插入的记录暂存到监视哨中
        low = 1;
        high = i - 1;       // 置查找区间初值
        while (low <= high) // 在r[low..high]中折半查找插入的位置
        {
            m = (low + high) / 2; // 折半
            if (L.r[0].key < L.r[m].key)
                high = m - 1; // 插入点在前一子表
            else
                low = m + 1; // 插入点在后一子表
        } // while
        for (j = i - 1; j >= high + 1; --j)
            L.r[j + 1] = L.r[j]; // 记录后移
        L.r[high + 1] = L.r[0];  // 将r[0]即原r[i],插入正确位置
    } // for
}

算法 8.3  希尔排序

void ShellInsert(SqList &L, int dk)
{ // 对顺序表L进行一趟增量是dk的希尔插入排序
    for (i = dk + 1; i <= L.length; ++i)
        if (L.r[i].key < L.r[i - dk].key) // 需将L.r[i]插入有序增量子表
        {
            L.r[0] = L.r[i]; // 暂存在r[0]中
            for (j = i - dk; j > 0 && L.r[0].key < L.r[j].key; j -= dk)
                L.r[j + dk] = L.r[j]; // 记录后移,直到找到插入位置
            L.r[j + dk] = L.r[0];     // 将r[0]即原r[i],插入正确位置
        } // if
}
void ShellSort(SqList &L, int dt[], int t)
{ // 按增量序列dt[0..t-1]对顺序表L进行t趟希尔排序
    for (k = 0; k < t; ++k)
        ShellInsert(L, dt[k]); // 一趟增量为dt[t]的希尔插入排序
}

算法 8.4  冒泡排序

void BubbleSort(SqList &L)
{ // 对顺序表L进行冒泡排序
    m = L.length - 1;
    flag = 1; // flag用来标记某一趟排序是否发生交换
    while ((m > 0) && (flag == 1))
    {
        flag = 0; // flag置为0,如果本趟排序没有发生交换,则不会执行下一趟排序
        for (j = 1; j <= m; j++)
        {
            if (L.r[j].key > L.r[j + 1].key)
            {
                flag = 1; // flag置为1,表示本趟排序发生了交换
                t = L.r[j];
                L.r[j] = L.r[j + 1];
                L.r[j + 1] = t; // 交换前后两个记录
            } // if
        }
        --m;
    } // while
} // BubbleSort

算法 8.5  快速排序

int Partition(SqList &L, int low, int high)
{                            // 对顺序表L中的子表r[low..high]进行一趟排序,返回枢轴位置
    L.r[0] = L.r[low];       // 用子表的第一个记录作为枢轴记录
    pivotkey = L.r[low].key; // 枢轴记录关键字保存在pivotkey中
    while (low < high)       // 从表的两端交替地向中间查找
    {
        while (low < high && L.r[high].key >= pivotkey)
            --high;
        L.r[low] = L.r[high]; // 将比枢轴记录小的记录移到低端
        while (low < high && L.r[low].key <= pivotkey)
            ++low;
        L.r[high] = L.r[low]; // 将比枢轴记录大的记录移到高端
    } // while
    L.r[low] = L.r[0]; // 枢轴记录到位
    return low;        // 返回枢轴位置
}
void QSort(SqList &L, int low, int high)
{ // 调用前置初值 :low=1; high=L.length;
    // 对顺序表L中的子表L.r[low..high]进行快速排序
    if (low < high)
    {                                       // 长度大于1
        pivotloc = Partition(L, low, high); // 将L.r[low..high]一分为二,pivotloc是枢轴位置
        QSort(L, low, pivotloc - 1);        // 对左子表递归排序
        QSort(L, pivotloc + 1, high);       // 对右子表递归排序
    }
}
void QuickSort(SqList &L)
{ // 对顺序表L进行快速排序
    QSort(L, 1, L.length);
}

算法 8.6  简单选择排序

void SelectSort(SqList &L)
{ // 对顺序表L进行简单选择排序
    for (i = 1; i < L.length; ++i)
    { // 在L.r[i..L.length] 中选择关键字最小的记录
        k = i;
        for (j = i + 1; j <= L.length; ++j)
            if (L.r[j].key < L.r[k].key)
                k = j; // k指向此趟排序中关键字最小的记录
        if (k != i)
        {
            t = L.r[i];
            L.r[i] = L.r[k];
            L.r[k] = t;
        } // 交换r[i]与r[k]
    } // for
}

算法 8.7  筛选法调整堆

void HeapAdjust(SqList &L, int s, int m)
{ // 假设r[s+1..m]已经是堆,将r[s..m]调整为以r[s]为根的大根堆
    rc = L.r[s];
    for (j = 2 * s; j <= m; j *= 2) // 沿key较大的孩子节点向下筛选
    {
        if (j < m && L.r[j].key < L.r[j + 1].key)
            ++j; // j为key较大的记录的下标
        if (rc.key >= L.r[j].key)
            break; // rc应插入在位置s上
        L.r[s] = L.r[j];
        s = j;
    } // for
    L.r[s] = rc; // 插入
}

算法 8.8  建初堆

void CreatHeap(SqList &L)
{ // 把无序序列L.r[1..n]建成大根堆
    n = L.length;
    for (i = n / 2; i > 0; --i)
        // 反复调用HeapAdjust
        HeapAdjust(L, i, n);
}

算法 8.9  堆排序

void HeapSort(SqList &L)
{                 // 对顺序表L进行堆排序
    CreatHeap(L); // 把无序序列L.r[1..L.length]建成大根堆
    for (i = L.length; i > 1; --i)
    {
        x = L.r[1]; // 将堆顶记录和当前未经排序子序列L.r[1..i]中最后一个记录互换
        L.r[1] = L.r[i];
        L.r[i] = x;
        HeapAdjust(L, 1, i - 1); // 将L.r[1..i-1]重新调整为大根堆
    } // for
}

算法 8.10  相邻两个有序子序列的归并

void Merge(RedType R[], RedType T[], int low, int mid, int high)
{ // 将有序表R[low..mid]和R[mid+1..high]归并为有序表T[low..high]
    i = low;
    j = mid + 1;
    k = low;
    while (i <= mid && j <= high) // 将R中的记录由小到大地并入T中
    {
        if (R[i].key <= R[j].key)
            T[k++] = R[i++];
        else
            T[k++] = R[j++];
    } // while
    while (i <= mid)
        T[k++] = R[i++]; // 将剩余的R[i..mid]复制到T中
    while (j <= high)
        T[k++] = R[j++]; // 将剩余的R[j..high]复制到T中
}

算法 8.11  归并排序

void MSort(RedType R[], RedType T[], int low, int high)
{ // R[low..high]归并排序后放入T[low..high]中
    if (low == high)
        T[low] = R[low];
    else
    {
        mid = (low + high) / 2;      // 将当前序列一分为二,求出分裂点mid
        MSort(R, S, low, mid);       // 对子序列R[low..mid]递归进行归并排序,结果放入S[low..mid]
        MSort(R, S, mid + 1, high);  // 对子序列R[mid+1..high]递归进行归并排序,结果放入S[mid+1..high]
        Merge(S, T, low, mid, high); // 将S[low..mid]和S[mid+1..high]归并到T[low..high]
    } // else
}
void MergeSort(SqList &L)
{ // 对顺序表L进行归并排序
    MSort(L.r, L.r, 1, L.length);
}

算法 8.12  基数排序

#define MAXNUM_KEY 8 // 关键字项数的最大值
#define RADIX 10     // 关键字基数,此时基数是十进制整数
#define MAX_SPACE 10000
typedef struct
{
    KeyType keys[MAXNUM_KEY]; // 关键字
    InfoType otheritems;      // 其他数据项
    int next ;
} SLCell ;  //静态链表的节点类型
typedef struct
{
    SLCell r[MAX_SPACE];   // 静态链表的可利用空间,r[0]为头节点
    int keynum;            // 记录的当前关键字个数
    int recnum;            // 静态链表的当前长度
} SLList;                  // 静态链表类型
typedef int ArrType[RADIX] // 数组类型

void Distribute(SLCell &r, int i, ArrType &f, ArrType &e)
{ // 静态链表L的r域中记录已按(keys[0], …, keys[i-1])有序
  //  本算法按第 i 个关键字 keys[i] 建立 RADIX 个子表,使同一子表中记录的keys[i] 相同
  // f[0..RADIX-1]和e[0..RADIX-1]分别指向各子表中第一个和最后一个记录
    for (j = 0; j < RADIX; ++j) f[j] = 0; // 各子表初始化为空表
    for (p = r[0].next; p; p = r[p].next)
    {
        j = ord(r[p].keys[i]); // ord将记录中第i个关键字映射到[0..RADIX-1]
        if (!f[j])
            f[j] = p;
        else
            r[e[j]].next = p;
        e[j] = p; // 将p所指的节点插入第j个子表中
    } // for
}

void Collect(SLCell &r, int i, ArrType f, ArrType e)
{ // 本算法按keys[i]自小至大地将f[0..RADIX-1]所指各子表依次链接成一个链表
    // e[0..RADIX-1]为各子表的尾指针
    for (j = 0; !f[j]; j = succ(j)); // 找第一个非空子表,succ()为求后继函数
    r[0].next = f[j];
    t = e[j]; // r[0].next指向第一个非空子表中第一个节点
    while (j < RADIX)
    {
        for (j = succ(j); j < RADIX - 1 && !f[j]; j = succ(j)); // 找下一个非空子表
        if (f[j])
        {
            r[t].next = f[j];
            t = e[j];
        } // 链接两个非空子表
    } // while
    r[t].next = 0; // t指向最后一个非空子表中的最后一个节点
}

void RadixSort(SLList &L)
{ // L是采用静态链表表示的顺序表
  // 对L进行基数排序,使得L成为按关键字自小到大的有序静态链表,L.r[0]为头节点
    for (i = 0; i < L.recnum; ++i)
        L.r[i].next = i + 1;
    L.r[L.recnum].next = 0; // 将L改造为静态链表
    for (i = 0; i < L.keynum; ++i)
    {                             // 按最低位优先依次对各关键字进行分配和收集
        Distribute(L.r, i, f, e); // 第i趟分配
        Collect(L.r, i, f, e);    // 第i趟收集
    } // for
}