【学习笔记】数据结构算法文档(类C语言)

0、类C语言代码说明

// 函数结果状态代码
#define OK 1
#define ERROR 0
#define OVERFLOW -2// 函数返回值类型(返回函数结果状态代码)
typedef int Status;// 用户自定义数据元素类型 ElemType
typedef xxx ElemType;// C++ 引用(示例)
void swap(int &x, int &y);  // 把引用作为参数(传入地址)// C++ 动态内存(示例)
double* pvalue = NULL; // 初始化为空指针
pvalue = new double;  // 为指针变量请求内存
delete pvalue;  // 释放之前分配的内存

1、线性表

1.1 线性表的顺序表示和实现

1.1.1 线性表的顺序存储表示

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

1.1.2 顺序表中基本操作的实现

1.1.2.1 初始化
/* 返回值为Status(状态码) */
Status InitList(SqList &L) {  // 构造一个空的顺序表L.elem = new ElemType[MAXSIZE]; //分配一个MAXSIZE大小的数组空间if(!L.elem) {  // 检查自由存储区是否有可用空间分配内存exit(OVERFLOW);  // 若存储分配失败则退出}L.length = 0;  // 设空表长度为0return OK;
}// 当不需要动态分配的内存时,可以销毁或释放
1.1.2.2 取值
/* 返回值为Status(状态码) */
Status GetElem(SqList L, int i, ElemType &e) {if(i < 1 || i > L.length) {  // 判断i值是否合理(1 ≤ i ≤ n)return ERROR;  // 若不合理则返回ERROR}e = L.elem[i-1];  // 将第i个数据元素赋值给参数ereturn OK;
}// 顺序表取值算法的时间复杂度为 O(1)
1.1.2.3 查找
/* 返回值为int(整数) */
int LocateElem(SqList L, ElemType e) {for(i=0; i<L.length; i++) {if(L.elem[i] == e) {return i+1;  // 查找成功,返回序号i+1}}return 0;  // 查找失败,返回0
}// 顺序表按值查找算法的平均时间复杂度为 O(n)
1.1.2.4 插入
/* 返回值为Status(状态码) */
Status ListInsert(SqList &L, int i, ElemType e) {if(i<1 || i>L.length+1) {  // 判断i值是否合法(1 ≤ i ≤ n+1)return ERROR;  // 若不合法则返回ERROR}if(L.length == MAXSIZE) {  // 判断内存是否已满(达到最大长度)return ERROR;  // 若已满则返回ERROR}for(j=L.length-1; j>=i-1; j--) {  // 从最后一个到第i个元素L.elem[j+1] = L.elem[j];  // 依次后移一个位置}L.elem[i-1] = e;  // 将e放入第i个元素位置++L.length;  // 表长加1return OK;
}// 顺序表插入算法的平均时间复杂度为 O(n)
1.1.2.5 删除
/* 返回值为Status(状态码) */
Status ListDelete(SqList &L, int i) {if(i<1 || i>L.length) {  // 判断i值是否合法(1 ≤ i ≤ n)return ERROR;  // 若不合法则返回ERROR}for(j=i; j<=L.length-1; j++) {  // 从第i+1个元素到最后一个L.elem[j-1] = L.elem[j];  // 依次前移一个位置}--L.length;  // 表长减1return OK;
}// 顺序表删除算法的平均时间复杂度为 O(n)
1.1.2.6 计数
/* 返回值为int(整数) */
int ListLength(SqList L) {return L.length;  // 直接返回L的length属性值
}// 顺序表计数算法的平均时间复杂度为 O(1)

1.2 线性表的链式表示和实现

1.2.1 单链表的定义和表示

// 单链表可由头指针唯一确定,可用结构指针来描述单链表的存储结构
typedef struct LNode {ElemType data;  // 数据域datastruct LNode *next;  // 指针域next(指向结点LNode的指针)
} LNode, *LinkList;
LinkList L;  // L表示头指针
LNode *p;  // p表示结点指针,*p表示结点
★ 关于结点
/* 等价的定义形式(变量含义不同) */
LinkList p;  // p为指示结点的指针变量(结点地址)
LNode *p;  // *p为结点变量(结点名称)/* 通过指针变量访问结点分量 */
p->data;  // 指针p指示的结点*p的数据域
p->next;  // 指针p指示的结点*p的指针域(后继结点的地址)/* 通过结点变量访问结点分量 */
(*p).data;  // 结点*p的数据域
(*p).next;  // 结点*p的指针域(后继结点的地址)
*((*p).next);  // 结点*p的后继结点

1.2.2 单链表基本操作的实现

1.2.2.1 初始化
/* 返回值为Status(状态码) */
Status InitList(LinkList &L) {  // 构造一个空的单链表L = new LNode;  // 生成新结点作为头结点L->next = NULL;  // 用头指针L指向头结点,并将头结点指针域置空return OK;
}
1.2.2.2 取值
/* 返回值为Status(状态码) */
Status GetElem(LinkList L, int i, ElemType &e) {p = L->next;  // 初始化p为头指针指向的首元结点j = 1;  // 计数器j赋值1while(p && j<i) {  // 当p不为空且j<i时循环操作p = p->next;  // p依次顺链向下访问(p更新为当前结点)++j;  // 计数器依次加1}if(!p || j>i) {  // 若p为空或i>n或i≤0,则i不合法return ERROR;}e = p->data;  // 参数e保存第i个元素(当前p)的数据域return OK;
}// 单链表取值算法的平均时间复杂度为 O(n)
1.2.2.3 查找
/* 返回值为LNode(结点) */
LNode *LocateElem(LinkList L, ElemType e) {p = L->next;  // 初始化p为头指针指向的首元结点while(p && p->data != e) {  // 当p不为空且p数据域不为e时循环操作p = p->next;  // p依次顺链向下访问(p更新为当前结点)}return p;  // 返回p结点值
}// 单链表查找算法的平均时间复杂度为 O(n)
1.2.2.4 插入
/* 返回值为Status(状态码) */
Status ListInsert(LinkList &L, int i, ElemType e) {p = L;j = 0;/* 合法插入位置有 n+1 个 */while(p && j<i-1) {  // 查找第i-1个结点p = p->next;  // p更新为当前结点(第i-1个)++j;}if(!p || j>i-1) {  // 若p为空或i>n+1或i<1,则i不合法return ERROR;}s = new LNode;  // 生产新结点*ss->data = e;  // *s的数据域赋为es->next = p->next;  // *s的指针域赋为p的指针域(指向第i个)p->next = s;  // 结点*P的指针域指向*sreturn OK;
}// 单链表插入算法的平均时间复杂度为 O(n)
1.2.2.5 删除
/* 返回值为Status(状态码) */
Status ListDelete(LinkList &L, int i) {p = L;j = 0;/* 合法删除位置有 n 个 */while(p->next && j<i-1) {  // 查找第i-1个结点p = p->next;  // p更新为当前结点(第i-1个)++j;}if(!(p->next) || j>i-1) {  // 若p指针域为空或i>n或i<1,则i不合法return ERROR;}LNode *q;  // 引入一个新指针qq = p->next;  // q临时保存被删结点的地址(第i个)p->next = q->next;  // 修改p指针域(指向第i+1个)delete q;  // 释放被删结点的空间return OK;
}// 单链表删除算法的时间复杂度为 O(n)
1.2.2.6 前插法创建单链表
/* 无返回值void */
void CreateList_H(LinkList &L, int n) {L = new LNode;  // 生成头结点*L(头指针L)L->next = NULL;  // 建立带头结点的空链表for(i=0; i<n; ++i) {p = new LNode;  // 生成新结点*p(指针p)cin >> p->data;  // 提取用户输入的值并存储在*p的数据域中p->next = L->next;  // *p的指针域赋为*L的指针域(指向后置结点)L->next = p;  // *L指针域(头指针)指向*p}
}// 前插法创建单链表算法的时间复杂度为 O(n)
1.2.2.7 后插法创建单链表
/* 无返回值void */
void CreateList_R(LinkList &L, int n) {L = new LNode;  // 生成头结点*L(头指针L)L->next = NULL;  // 建立带头结点的空链表LNode *r = L;  // 引入尾指针r指向头结点*Lfor(i=0; i<n; ++i) {p = new LNode;  // 生成新结点*pcin >> p->data;  // 提取用户输入的值并存储在p的数据域中p->next = NULL;  // *p的指针域赋为NULL(作为尾结点)r->next = p;  // 尾指针r(*r的指针域)指向*pr = p;  // 尾指针r更新到尾结点*p}
}// 后插法创建单链表算法的时间复杂度为 O(n)

1.2.3 循环链表

// 合并两个单向循环链表// 合并两个单向循环链表算法的时间复杂度为 O(1)

1.2.4 双向链表

1.2.4.1 双向链表的存储结构
typedef struct DuLNode {ElemType data;  // 数据域struct DuLNode *prior;  // 指向直接前驱struct DuLNode *next;  // 指向直接后继
} DuLNode, *DuLinkList;
1.2.4.2 双向链表的查找
/* 返回值为DuLNode(结点) */
DuLNode *GetElem_DuL(DuLinkList L, int i) {if(i < 1) {return ERROR;}p = new DuLNode;p = L->next;for (j=1; j<i; j++) {p = p->next;}return p;
}
1.2.4.3 双向链表的插入
/* 返回值为Status(状态码) */
Status ListInsert_DuL(DuLinkList &L, int i, ElemType e) {if(!(p = GetElem_DuL(L, i))) {  // 查找第i个元素指针,赋值给preturn ERROR;  // 判断p是否为空}s = new DuLNode;  // 生成新结点*s(指针s)s->data = e;  // s的数据域赋为es->prior = p->prior;  // s的前驱赋为p的前驱(指向第i-1个)p->prior->next = s;  // s的前驱的后继指向ss->next = p;  // s的后继指向p(第i个)p->prior = s;  // p的前驱指向sreturn OK;
}// 双向链表插入算法的时间复杂度为 O(n)
1.2.4.4 双向链表的删除
/* 返回值为Status(状态码) */
Status ListDelete_DuL(LinkList &L, int i) {if(!(p = GetElem_DuL(L, i))) {  // 查找第i个元素指针,赋值给preturn ERROR;  // 判断p是否为空}p->prior->next = p->next;  // p的前驱的后继赋为p的后继(指向i+1)p->next->prior = p->prior; // p的后继的前驱赋为p的前驱(指向i-1)delete p;  // 释放被删结点*p的空间return OK;
}// 双向链表删除算法的时间复杂度为 O(n)

1.3 线性表的应用

1.3.1 线性表的合并

问题:已知两个集合A和B,求一个新的集合A=A∪B。

/* 无返回值void */
void MergeList(List &LA, List LB) {m = ListLength(LA);  // 获取LA表长赋给mn = ListLength(LB);  // 获取LB表长赋给nfor(i=1; i<=n; i++) {  // 遍历LBGetElem(LB, i, e);  // 依次取LB中第i个元素的值(保存在e中)if(!LocateElem(LA, e)) {  // 判断能否在LA中查找到与e相等的元素ListInsert(LA, ++m, e);  // 若找不到则将e插入LA的++m位置}}
}// 线性表的合并算法的时间复杂度 O(m·n)

1.3.2 有序表的合并

问题:已知两个有序集合A和B,数据元素按值非递减有序排列,求一个新的集合C=A∪B,使集合C中的数据元素仍按值非递减有序排列。

1.3.2.1 顺序有序表的合并
/* 无返回值void */
void MergeList_Sq(SqList LA, SqList LB, SqList &LC) {LC.length = LA.length + LB.length;  // 新表长为两表长之和LC.elem = new ElemType[LC.length];  // 为LC请求数组空间SqList *pc, *pa, *pb, *pa_last, *pb_last;  // 引入辅助指针pc = LC.elem;  // *pc指向LC第一个元素pa = LA.elem;  // *pa指向LA第一个元素(初值)pb = LB.elem;  // *pb指向LB第一个元素(初值)pa_last = LA.elem + LA.length - 1;  // *pa_last指向LA最后元素pb_last = LB.elem + LB.length - 1;  // *pb_last指向LB最后元素/* 要求非递减排列,故依次取较小值从头到尾插入LC */while(pa<=pa_last && pb<=pb_last) {  // 若*pa、*pb均未到达表尾if(*pa <= *pb) {  // 依次比较取更小元素插入LC,指针同时向后移动*pc++ = *pa++;} else {*pc++ = *pb++;}}while(pa <= pa_last) {  // 若*pa已到达表尾*pc++ = *pa++;  // 依次将LA剩余元素插入,指针同时向后移动}while(pb <= pb_last) {  // 若*pb已到达表尾*pc++ = *pb++;  // 依次将LB剩余元素插入,指针同时向后移动}
}// 合并顺序有序表算法的时间复杂度 O(m+n),空间复杂度 O(m+n)
1.3.2.2 链式有序表的合并
/* 无返回值void */
void MergeList_L(LinkList &LA, LinkList &LB, LinkList &LC) {LNode *pa, *pb, *pc;  // 引入辅助指针pa、pb、pcpa = LA->next;  // pa指向LA首元结点(初值)pb = LB->next;  // pb指向LB首元结点(初值)LC = LA;  // LC头指针赋为LA头指针pc = LC;  // pc赋为LC头指针while(pa && pb) {  // 当pa、pb都不为空if(pa->data <= pb->data) {  // 若*pa数据值 ≤ *pb数据值(取pa)pc->next = pa;  // *pc指针域链接到*papc = pa;  //  pc移动到papa = pa->next;  //  pa移动到下一个位置} else {  // 若 *pa数据值 > *pb数据值(取pb)pc->next = pb;  // *pc指针域链接到*pbpc = pb;  //  pc移动到pbpb = pb->next;  //  pb移动到下一个位置}}pc->next = pa?pa:pb;  // 非空表的剩余结点一快链接到pc之后delete LB;  // 释放*LB头结点
}// 合并链式有序表算法的时间复杂度 O(m+n),空间复杂度 O(1)

1.4 案例分析与实现

1.4.1 一元多项式的运算

/* 多项式的顺序存储结构 */
#define MAXSIZE 100  // 多项式可能到达的最大长度
typedef struct {  // 多项式非零项的定义float coef;  // 系数int expn;  // 指数
} Polynomial;typedef struct {Polynomial *elem;  // 存储空间的基地址int length;  // 多项式中当前项的个数
} SqList;  // 多项式的顺序存储结构类型为SqList

1.4.2 稀疏多项式的运算

1.4.2.1 多项式的链式存储结构
typedef struct PNode {float coef;  // 系数(数据域)int expn;  // 指数(数据域)struct PNode *next;  // 指针域
} PNode, *Polynomial;
1.4.2.2 多项式的创建
/* 无返回值void */
void CreatePolyn(Polynomial &P, int n) {P = new PNode;  // 生成头指针P(头结点*P)P->next = NULL;  // 建立带头结点的空链表PNode *s, *pre, *q;  // 引入辅助指针s、pre、qfor(i=1; i<=n; ++i) {  // 依次输入n个非零项,每次操作如下:s = new PNode;  // 生成新结点cin >> s->coef >> s->expn;  // 用户输入系数和指数pre = P;  // pre赋为头指针(保存q的前驱)q = P->next;  // q初始化,指向首元结点while(q && (q->expn < s->expn)) {  // 若q不为空,比较q、s指数pre = q;  // pre移动到qq = q->next;  // q移动到下一个位置}s->next = q;  // s指针域链接到qpre->next = s;  // pre指针域链接到s}
}// 合并链式有序表算法的时间复杂度为 O(n²)
1.4.2.3 多项式的相加
/* 无返回值void */
void CreatePolyn(Polynomial &P, int n) {P = new PNode;  // 生成头指针P(头结点*P)P->next = NULL;  // 建立带头结点的空链表PNode *s, *pre, *q;  // 引入辅助指针s、pre、qfor(i=1; i<=n; ++i) {  // 依次输入n个非零项,每次操作如下:s = new PNode;  // 生成新结点cin >> s->coef >> s->expn;  // 用户输入系数和指数pre = P;  // pre赋为头指针(保存q的前驱)q = P->next;  // q初始化,指向首元结点while(q && (q->expn < s->expn)) {  // 若q不为空,比较q、s指数pre = q;  // pre移动到qq = q->next;  // q移动到下一个位置}s->next = q;  // s指针域链接到qpre->next = s;  // pre指针域链接到s}
}// 合并链式有序表算法的时间复杂度为 O(n²)

1.4.3 图书信息管理系统

/* 图书表的顺序存储结构 */
#define MAXSIZE 10000  // 图书表可能到达的最大长度
typedef struct {  // 图书信息的定义char no[20];  // 图书ISBNchar name[50];  // 图书名称float price;  // 图书价格
} Book;typedef struct {Book *elem;  // 存储空间的基地址int length;  // 图书表中当前图书的个数
} SqList;  // 图书表的顺序存储结构类型为SqListSqList L;  // 定义SqList类型的变量L

2、栈和队列

2.1 栈的表示和操作的实现

2.1.1 顺序栈的表示和实现

2.1.1.1 顺序栈的存储结构
#define MAXSIZE 100  // 顺序栈存储空间的初始分配量
typedef struct {SElemType *base;  // 栈底指针(始终指向栈底,为NULL则栈不存在)SElemType *top;  // 栈顶指针(初值指向栈底,插入增1,删除减1)int stacksize;  // 栈可用的最大容量
} SqStack;
2.1.1.2 初始化
/* 返回值为Status(状态码) */
Status InitStack(SqStack &S) {  // 构造一个空栈SS.base = new SElemType[MAXSIZE];  // 为顺序栈动态分配数组空间if(!S.base) {  // 若存储分配失败exit(OVERFLOW);}S.top = S.base;  // top初值为base(空栈)S.stacksize = MAXSIZE;  // 栈可用的最大容量为MAXSIZEreturn OK;
}
2.1.1.3 入栈
/* 返回值为Status(状态码) */
Status Push(SqStack &S, SElemType e) {  // 插入元素e为新的栈顶元素if(S.top - S.base == S.stacksize) {  // 若栈空间已满return ERROR;}*S.top++ = e;  // 元素e压入栈顶,栈顶指针加1return OK;
}
2.1.1.4 出栈
/* 返回值为Status(状态码) */
Status Pop(SqStack &S, SElemType &e) {  // 删除S栈顶元素,用e返回if(S.top == S.base) {  // 若栈为空return ERROR;}*S.top++ = e;  // 栈顶指针减1,将栈顶元素赋值给ereturn OK;
}
2.1.1.5 取栈顶元素
/* 返回值为SElemType(栈元素) */
SElemType GetTop(SqStack S) {  // 返回S的栈顶元素if(S.top != S.base) {  // 若栈不为空return *(S.top - 1);  // 返回栈顶元素的值}
}
2.1.1.6 判断栈空
/* 返回值为bool(是否为空栈) */
bool StackEmpty(S) {if(S.top == S.base) {return true;  // 若栈空,返回true} else {return false;  // 若栈非空,返回false}
}

2.1.2 链栈的表示和实现

2.1.2.1 链栈的存储结构
typedef struct StackNode {SElemType data;struct StackNode *next;
} StackNode, *LinkStack;
2.1.2.2 初始化
/* 返回值为Status(状态码) */
Status InitStack(LinkStack &S) {  // 构造一个空栈SS = NULL;  // 将栈顶指针S置空return OK;
}
2.1.2.3 入栈
/* 返回值为Status(状态码) */
Status Push(LinkStack &S, SElemType e) {  // 在栈顶插入元素ep = new StackNode;  // 生成新结点p->data = e;  // 将新结点数据域置为ep->next = S;  // 将新结点指针域链接到原栈顶元素S = p;  // 将栈顶指针移动到preturn OK;
}
2.1.2.4 出栈
/* 返回值为Status(状态码) */
Status Pop(LinkStack &S, SElemType &e) {  // 删除S栈顶元素,用e返回if(S == NULL) {  // 若栈为空return ERROR;}e = S->data;  // 将栈顶元素(结点*S)的值赋给eStackNode *p;  // 引入辅助指针pp = S;  // 将栈顶元素(结点*S)赋给*pS = S->next;  // 将栈顶指针S移动到后继结点(新的栈顶元素)delete p;  // 释放该出栈元素的空间return OK;
}
2.1.2.5 取栈顶元素
/* 返回值为SElemType(栈元素) */
SElemType GetTop(LinkStack S) {  // 返回S的栈顶元素if(S != NULL) {  // 若栈不为空return S->data;  // 返回栈顶元素(结点*S)的值}
}

2.2 栈与递归

2.2.1 采用递归算法解决的问题

2.2.1.1 定义是递归的
/* 阶乘函数 */
long Fact(long n) {if(n == 0) {  // 最后一次调用终止递归return 1;} else {  // 以参数执行递归调用,依次返回值return n*Fact(n-1);}
}/* 二阶斐波那契数列 */
long Fib(long n) {if(n==1 || n==2) {return 1;} else {return Fib(n-1)+Fib(n-2);}
}
2.2.1.2 数据结构是递归的
/* 遍历输出链表中各个结点的递归算法 */
void TraverseList(LinkList p) {if(p==NULL) {  // 递归终止return;} else {cout << p->data << endl;  // 输出当前结点的数据域TraverseList(p->next);  // 指针p移动到后继结点继续递归}
}/* 若递归终止只执行return,可简化为 */
void TraverseList(LinkList p) {if(p) {cout << p->data << endl;  // 输出当前结点的数据域TraverseList(p->next);  // 指针p移动到后继结点继续递归}
}
2.2.1.3 问题的解法是递归的
/* 汉诺塔问题的递归算法 */
int m = 0;
void move(char A, int n, char C) {  // 对搬动进行计数cout << ++m << "," << n << "," << A << "," << C << endl;
}// 将塔A上的n个圆盘按规则移动到C上,B做辅助塔
void Hanoi(int n, char A, char B, char C) {if(n==1) {move(A, 1, C);  // 将1号圆盘从A移动到C} else {Hanoi(n-1, A, B, C);  // 将A上1到n-1号圆盘移动到B,C做辅助塔move(A, n, C);  // 将1号圆盘从A移到CHanoi(n-1, B, A, C);  // 将B上1到n-1号圆盘移动到C,A做辅助塔}
}

2.2.2 递归过程与递归工作栈

/* 阶乘函数Fact(4)递归过程中递归工作栈和活动记录的使用 */
void main() {long n;  // 调用Fact(4)时记录进栈n = Fact(4);  // 返回地址RetLoc1在赋值语句
}long Fact(long n) {long temp;if(n == 0) {  // 最后一次调用终止递归return 1;  // 活动记录退栈} else {  // 以参数执行递归调用,依次返回值temp = n*Fact(n-1);  // 活动记录进栈}  // 返回地址RetLoc2在计算语句return temp;  // 活动记录退栈
}

2.3 队列的表示和操作的实现

2.3.1 循环队列——队列的顺序表示和实现

2.3.1.1 队列的顺序存储结构
#define MAXQSIZE 100  // 队列可能达到的最大长度
typedef struct {QElemType *base;  // 存储空间的基地址int front;  // 头指针int rear;  // 尾指针
} SqQueue;
2.3.1.2 初始化
/* 返回值为Status(状态码) */
Status InitQueue(SqQueue &Q) {  // 构造一个空队列QQ.base = new QElemType[MAXQSIZE];  // 为队列分配一个数组空间if(!Q.base) {  // 若存储分配失败exit(OVERFLOW);}Q.front = Q.rear = 0;  // 头指针和尾指针设为0,队列为空return OK;
}
2.3.1.3 求队列长度
/* 返回值为int(队列长度) */
int QueueLength(SqQueue Q) {  // 返回Q的元素个数return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
2.3.1.4 入队
/* 返回值为Status(状态码) */
Status EnQueue(SqQueue &Q, QElemType e) {  // 插入e为新的队尾元素if((Q.rear+1)%MAXQSIZE == Q.front) {  // 若尾指针加1等于头指针return ERROR;  // 队满}Q.base[Q.rear] = e;  // 新元素插入队尾Q.rear = (Q.rear + 1) % MAXQSIZE;  // 尾指针移动到下一个位置return OK;
}
2.3.1.5 出队
/* 返回值为Status(状态码) */
Status DeQueue(SqQueue &Q, QElemType &e) {  // 删除队头元素并返回if(Q.rear == Q.front) {  // 若队空return ERROR;}e = Q.base[Q.front];  // 保存队头元素到eQ.front = (Q.front + 1) % MAXQSIZE;  // 头指针移动到下一个位置return OK;
}
2.3.1.6 取队头元素
/* 返回值为QElemType(队列元素) */
QElemType GetHead(SqQueue Q) {  // 返回Q的队头元素,不修改头指针if(Q.front != Q.rear) {  // 若队列非空return Q.base[Q.front];  // 返回队头元素的值,头指针不变}
}
2.3.1.7 判断队空
/* 返回值为bool(是否为空队列) */
bool QueueEmpty(Q) {if(Q.front == Q.rear) {return true;  // 若队空,返回true} else {return false;  // 若队非空,返回false}
}

2.3.2 链队——队列的链式表示和实现

2.3.2.1 队列的链式存储结构
typedef struct QNode {QElemType data;struct QNode *next;
} QNode, *QueuePtr;typedef struct {QueuePtr front;  // 队头指针QueuePtr rear;  // 队尾指针
} LinkQueue;
2.3.2.2 初始化
/* 返回值为Status(状态码) */
Status InitQueue(LinkQueue &Q) {  // 构造一个空队列QQ.front = Q.rear = new QNode;  // 生成头结点,头尾指针指向头结点Q.front->next = NULL;  // 头结点的指针域置空return OK;
}
2.3.2.3 入队
/* 返回值为Status(状态码) */
Status EnQueue(LinkQueue &Q, QElemType e) { // 插入e为新的队尾元素p = new QNode;  // 动态分配结点空间给新结点*pp->data = e;  // p的数据域赋值ep->next = NULL;  // p的指针域置空Q.rear->next = p;  // 尾指针链接到pQ.rear = p;  // 尾指针移动到preturn OK;
}
2.3.2.4 出队
/* 返回值为Status(状态码) */
Status DeQueue(LinkQueue &Q, QElemType &e) { // 删除队头元素并返回if(Q.front == Q.rear) {  // 若队列为空return ERROR;}p = Q.front->next;  // p置为头结点指针域(队头元素)e = p->data;  // e保存队头元素的值Q.front->next = p->next; //头结点指针域置为p指针域(队头的下一个)if(Q.rear == p) {  // 若p到达队尾(最后一个元素被删)Q.rear = Q.front;  // 尾指针移动到头结点}delete p;  // 释放原队头元素的空间return OK;
}
2.3.2.5 取队头元素
/* 返回值为QElemType(队列元素) */
QElemType GetHead(LinkQueue Q) {  // 返回Q的队头元素,不修改头指针if(Q.front != Q.rear) {  // 若队列非空return Q.front->next->data;  // 返回队头元素的值,头指针不变}
}

2.4 案例分析与实现

2.4.1 数制的转换

/* 无返回值void */
void conversion(int N) {  // 非负十进制数转为八进制数,打印输出InitStack(S);  // 初始化空栈Swhile(N) {  // 当N非零时,循环以下操作(短除法)Push(S, N%8);  // 将N与8取模运算的余数入栈N = N/8;  // N更新为N除以8的商}while(!StackEmpty(S)) {  // 当栈非空时,循环以下操作(先进后出)Pop(S, e);  // 将栈顶元素e出栈cout << e;  // 输出e}
}

2.4.2 括号匹配的检验

/* 返回值为Status(状态码) */
Status matching() {  //检验以#结尾的表达式中括号是否匹配,返回布尔值InitStack(S);  // 初始化空栈Sflag = 1;  // 设置一个flag标记匹配结果(初值为真)cin >> ch;  // 开始读取字符while(ch!='#' && flag) {  // 当字符不是#且标记为真时,循环操作:switch(ch) {  // 匹配字符值case '[':case '(':Push(S, ch);  // 若是左括号[或(,则将其入栈break;case ')':  // 若是右括号)if(!StackEmpty(S) && GetTop(S)=='(') {Pop(S, x);  // 若栈非空且栈顶元素为(,将(出栈(匹配正确)} else {flag = 0;  // 否则标记为假(匹配错误)}break;case ']':  // 若是右括号]if(!StackEmpty(S) && GetTop(S)=='[') {Pop(S, x);  // 若栈非空且栈顶元素为[,将[出栈(匹配正确)} else {flag = 0;  // 否则标记为假(匹配错误)}break;}cin >> ch;  // 读取下一个字符}if(StackEmpty(S) && flag) {return true;  // 若栈空且标记为真,则匹配成功,返回true} else {return false;  // 否则匹配失败,返回false}
}

2.4.3 表达式求值

/* 返回值为char(字符) */
char EvalExpr() {  // 算术表达式求值的算符优先法,设置两个工作栈InitStack(OPTR);  // 初始化空栈OPTR(寄存运算符)InitStack(OPND);  // 初始化空栈OPND(寄存操作数或运算结果)Push(OPTR, '#');  // 先将表达式的起始符#入栈cin >> ch;  // 开始读取字符while(ch!='#' || GetTop(OPTR)!='#') {  // 未到结束符时循环操作:if(!In(ch)) {  // 若ch不是运算符Push(OPND, ch);  // 将ch入栈OPNDcin >> ch;  // 读取下一个字符} else {  // 若ch是运算符switch(Precede(GetTop(OPTR), ch)) {  // 匹配优先级case '<':  // 若OPTR栈顶元素(运算符)优先级小于ch(运算符)Push(OPTR, ch);  // 将ch入栈OPTRcin >> ch;  // 读取下一个字符break;case '>':  // 若OPTR栈顶元素(运算符)优先级大于ch(运算符)Pop(OPTR, theta);  // 将OPTR栈顶元素(运算符)出栈Pop(OPND, b);  // 将OPND栈顶元素(操作数)出栈Pop(OPND, a);  // 将OPND栈顶元素(操作数)出栈Push(OPND, Operate(a, theta, b));  //将运算结果入栈OPNDbreak;case '=':  // 若OPTR栈顶元素与ch匹配括号()Pop(OPTR, x);  // 将OPTR栈顶元素(出栈cin >> ch;  // 读取下一个字符break;}}}return GetTop(OPND);  // 最终,OPND栈顶元素即为表达式求值结果
}/* 判断字符是否为运算符,返回布尔值 */
bool In(char ch) {if(ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')' || ch == '#') {return true;} else {return false;}
}/* 比较栈顶元素与ch的运算优先级,返回><= */
char Precede(char top, char ch) {char operators[6] = {'(', '*', '/', '+', '-', ')', '#'};int ti, ci;for(i=0; i<6; i++) {if(top == operators[i]) {ti = i;}if(ch == operators[i]) {ci = i;}}if(top == '(') {if(ch == ')') {return '=';} else {return '<';}} else {if(ti < ci) {return '>';} else {return '<';}}
}/* 根据运算符+操作数返回运算结果(一位数) */
int Operate(char opr, int a, int b) {switch(opr) {case '+':return a+b;case '-':return a-b;case '*':return a*b;case '/':return a/b;}
}// 上述算法中操作数只能为一位数,因为使用的OPND为字符栈;若要进行多位数运算,需将OPND改为数栈(读取的数字字符拼成数后再入栈)

2.4.4 舞伴问题

/* 某个舞者的信息 */
typedef struct {char name[20];  // 姓名char sex;  // 性别(男性'M' 女性'F')
}Person;  // 定义队列元素的类型/* 队列的顺序存储结构 */
#define MAXQSIZE 100  // 队列可能达到的最大长度
typedef struct {Person *base;  // 存储空间的基地址int front;  // 头指针int rear;  // 尾指针
}SqQueue;
SqQueue Mdancers, Fdancers;  // 声明男队和女队/* 舞伴问题:数组dancer存放所有舞者信息,num为人数 */
void DancerPartner(Person dancer[], int num) {InitQueue(Mdancers);  // 初始化队列MdancersInitQueue(Fdancers);  // 初始化队列Fdancersfor(i=0; i<num; i++) {  // 遍历数组dancerp = dancer[i];  // 获取某个舞者信息if(p.sex == 'F') {EnQueue(Fdancers, p);  // 若舞者性别为女,则进入女队} else {EnQueue(Mdancers, p);  // 否则进入男队}}cout << "Partners are:\n";while(!QueueEmpty(Fdancers) && !QueueEmpty(Mdancers)) {DeQueue(Fdancers, p);  // 两队都非空时,依次将女队头元素出队cout << p.name << " ";  // 依次输出女伴姓名DeQueue(Mdancers, p);  // 两队都非空时,依次将男队头元素出队cout << p.name << endl;  // 依次输出男伴姓名}if(!QueueEmpty(Fdancers)) {  // 结伴完成后,若女队还剩人p = GetHead(Fdancers);  // 获取女队头元素cout << "Next round Female leader is: " << p.name << endl;} else if(!QueueEmpty(Mdancers)) {  // 若男队还剩人p = GetHead(Mdancers);  // 获取男队头元素cout << "Next round Male leader is: " << p.name << endl;}
}

3、串、数组和广义表

3.1 串

3.1.1 串的存储结构

3.1.1.1 串的定长顺序存储结构
#define MAXLEN 255  // 串的最大长度(静态定义)
typedef struct {char ch[MAXLEN+1];  // 存储串的一维数组(每个分量存储一个字符)int length;  // 串的长度(下标0的分量闲置不用)
} SString;
3.1.1.2 串的堆式顺序存储结构
typedef struct {char *ch;  // 若非空串,则按串长分配存储区,否则ch为NULLint length;  // 串的长度(下标0的分量闲置不用)
} HString;
3.1.1.3 串的链式存储结构
#define CHUNKSIZE 80  // 自定义的块大小
typedef struct Chunk{char ch[CHUNKSIZE];struct Chunk *next;
} Chunk;typedef struct {Chunk *head, *tail;  // 头指针和尾指针int length;  // 串的长度
} LString;

3.1.2 串的模式匹配算法

3.1.2.1 BF算法
/* 返回值为int(子串位置) */
int Index_BF(SString S, SString T, int pos) {  // 从pos位开始查找i = pos;  // 主串当前待匹配的字符位置(初值为pos)j = 1;  // 子串当前待匹配的字符位置(初值为1)while(i<=S.length && j<=T.length) {  // 当S,T未完成比较,循环:if(S.ch[i] == T.ch[j]) {  // 若 S中第i个字符==T中第j个字符++i;  // 指针i+1(继续比较下一位)++j;  // 指针j+1(继续比较下一位)} else {  // 否则i = i-j+2;  // 将i退回本次起始位置的下一个位置(重新开始匹配)j = 1;  // 将j退回第1个位置(重新开始匹配)}}if(j > T.length) {return i-T.length;  // 匹配成功,返回匹配到的子串位置} else {return 0;  // 匹配失败,返回0}
}
3.1.2.2 KMP算法
/* 返回值为int(子串位置) */
int Index_KMP(SString S, SString T, int pos) {  //从pos位开始查找i = pos;  // 主串当前待匹配的字符位置(初值为pos)j = 1;  // 子串当前待匹配的字符位置(初值为1)while(i<=S.length && j<=T.length) {  // 当S,T未完成比较,循环:if(j==0 || S.ch[i]==T.ch[j]) {  // 若子串归零或比较相等++i;  // 指针i+1(继续比较下一位)++j;  // 指针j+1(继续比较下一位)} else {j = next[j];  // 否则子串向右移动,将next[j]位置与i进行比较}}if(j > T.length) {return i-T.length;  // 匹配成功,返回匹配到的子串位置} else {return 0;  // 匹配失败,返回0}
}// 计算next函数值(求模式串中每个字符的退回位置,存入数组next[])
void get_next(SString T, int next[]) {i = 1;  // 以模式T为主串(i初值为1)next[1] = 0;  // 令next[1] = 0(函数定义)j = 0;  // 以模式T为子串(j初值为0)while(i < T.length) {  // 当比较未完成时,循环以下操作:if(j==0 || T.ch[i]==T.ch[j]) {  // 若子串归零或比较相等++i;  // 指针i+1(继续比较下一位)++j;  // 指针j+1(继续比较下一位)next[i] = j;  // 第i位的退回位置为j} else {j = next[j];  // 否则j退回到next[j]}}
}// 计算next函数的修正值
void get_nextval(SString T, int nextval[]) {i = 1;  // 以模式T为主串(i初值为1)nextval[1] = 0;  // 令nextval[1] = 0(函数定义)j = 0;  // 以模式T为子串(j初值为0)while(i < T.length) {  // 当比较未完成时,循环以下操作:if(j==0 || T.ch[i]==T.ch[j]) {  // 若子串归零或比较相等++i;  // 指针i+1(继续比较下一位)++j;  // 指针j+1(继续比较下一位)if(T.ch[i] != T.ch[j]) {  // 若下一位比较不等nextval[i] = j;  // 第i位的退回位置为j} else {nextval[i] = nextval[j];  // 否则第i位退回到第j位的nextval}} else {j = nextval[j];  // 否则j退回到nextval[j]}}
}

3.2 广义表

3.2.1 广义表的存储结构

/* 广义表的头尾链式存储表示 */
typedef enum {ATOM, LIST} ElemTag;  //ATOM==0即原子 LIST==1即子表
typedef struct GLNode {ElemTag tag;  // 标志域tag,用于区分原子结点和表结点union {AtomType atom;  // 原子结点,值域atomstruct {struct GLNode *hp, *tp;} ptr;  // 表结点的指针域:ptr.hp指向表头,ptr.tp指向表尾};
} *GList;  // 广义表类型为GList

3.3 案例分析与实现

3.3.1 病毒感染检测

/* 无返回值void */
void Virus_detection() {  // 利用BF算法实现病毒检测ifstream inFile("病毒感染检测输入数据.txt");  // 打开并读取文件ofstream outFile("病毒感染检测输出结果.txt");  // 创建并写入文件inFile >> num;  // 从文件读取待检测的任务数numwhile(num--) {  // 依次从num行向前进行以下操作:inFile >> Virus.ch+1;  // 读取病毒DNA(后移1位,下标1开始存放)inFile >> Patient.ch+1; //读取患者DNA(后移1位,下标1开始存放)Vir = Virus.ch;  // 病毒DNA保存到Virflag = 0;  // 标记是否匹配,初值为0(未匹配)m = Virus.length;  // 病毒DNA长度mfor(i=m+1,j=1; j<=m; j++) {Virus.ch[i++] = Virus.ch[j];  // 病毒字符串长度扩大到2m}Virus.ch[2*m+1] = '\0';  // 添加结束符\0for(i=0; i<m; i++) {  // 依次获取每个长度为m的模式串for(j=1; j<=m; j++) {temp.ch[j] = Virus.ch[i+j];  // 存入数组temp}temp.ch[m+1] = '\0';  // 添加结束符\0flag = Index_BF(Patient, temp, 1);  // 模式匹配并标记结果if(flag) {break;  // 匹配成功则退出循环}}if(flag) {  // 匹配成功outFile << Vir+1 << " " << Patient.ch+1 << " " << "YES" << endl;} else {  // 匹配失败outFile << Vir+1 << " " << Patient.ch+1 << " " << "No" << endl;}}
}

4、树和二叉树

4.1 二叉树

4.1.1 二叉树的存储结构

4.1.1.1 顺序存储结构
#define MAXTSIZE 100  // 二叉树的最大结点数
typedef TElemType SqBiTree[MAXTSIZE];  // 0号单元存储根结点
SqBiTree bt;
4.1.1.2 链式存储结构
/* 二叉树的二叉链表存储表示 */
typedef struct BiTNode {TElemType data;  // 结点数据域struct BiTNode *lchild, *rchild;  // 左右孩子指针
} BiTNode, *BiTree;

4.1.2 遍历二叉树

4.1.2.1 中序遍历的递归算法
/* 无返回值void */
void InOrderTraverse(BiTree T) {  // 中序遍历二叉树T的递归算法if(T) {  // 若T非空InOrderTraverse(T->lchild);  // 中序遍历左子树cout << T->data;  // 访问根结点InOrderTraverse(T->rchild);  // 中序遍历右子树}
}// 改变输出语句的顺序,便可实现先序遍历和后序遍历
4.1.2.2 中序遍历的非递归算法
/* 无返回值void */
void InOrderTraverse(BiTree T) {  // 中序遍历二叉树T的非递归算法InitStack(S);  // 初始化栈Sp = T;  // 头指针p(指向根结点)q = new BiTNode;  // 指针q(指向当前结点)while(p || !StackEmpty(S)) {  // 当p非空或栈非空时,循环操作:if(p) {  // 若头指针p非空Push(S, p);  // 将p入栈p = p->lchild;  // p移动到左孩子结点(遍历左子树)} else {  // 若头指针p为空Pop(S, q);  // 将栈顶元素q出栈(退回上一层)cout << q->data;  // 输出q的数据域p = q->rchild;  // p移动到q的右孩子结点(遍历右子树)}}
}// 改变输出语句的顺序,便可实现先序遍历和后序遍历
4.1.2.3 创建二叉树的存储结构——二叉链表
/* 无返回值void */
void CreateBiTree(BiTree &T) {  // 按先序输入结点值,创建二叉链表cin >> ch;  // 读取一个输入字符if(ch == '#') {  // 若字符为#T = NULL;  // T置空(递归结束)} else {  // 若字符不为#T = new BiTNode;  // 生成根结点(指针T)T->data = ch;  // 根结点数据域赋为chCreateBiTree(T->lchild);  // 递归创建左子树CreateBiTree(T->rchild);  // 递归创建右子树}
}
4.1.2.4 复制二叉树
/* 无返回值void */
void Copy(BiTree T, BiTree &newT) {  //复刻一棵与T完全相同的二叉树if(T == NULL) {  // 若T为空newT = NULL;  // newT置空return;  // 递归结束} else {  // 若T非空newT = new BiTNode;  // 生成newT根结点newT->data = T->data;  // 将T数据域复制到newT数据域Copy(T->lchild, newT->lchild);  // 递归复制左子树Copy(T->rchild, newT->rchild);  // 递归复制右子树}
}
4.1.2.5 计算二叉树的深度
/* 返回值为int(深度) */
int Depth(BiTree T) {  // 计算二叉树T的深度if(T == NULL) {  // 若T为空return 0;  // 深度为0(递归结束)} else {  // 若T非空m = Depth(T->lchild);  // 递归计算左子树深度n = Depth(T->rchild);  // 递归计算右子树深度if(l > r) {  // 返回数值更大的深度return m+1;} else {return n+1;}}
}
4.1.2.6 统计二叉树中结点的个数
/* 返回值为int(个数) */
int NodeCount(BiTree T) {  // 统计二叉树T中结点的个数if(T == NULL) {  // 若T为空return 0;  // 结点数为0(递归结束)} else {  // 若T非空,递归获取左子树+右子树+1(结点总数)并返回return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;}
}

4.1.3 线索二叉树

4.1.3.1 线索二叉树的基本概念
/* 二叉树的二叉线索存储表示 */
typedef struct BiThrNode {TElemType data;struct BiThrNode *lchild, *rchild;  // 左右孩子指针int LTag, RTag;  // 左右标志
} BiThrNode, *BiThrTree;
4.1.3.2 构造线索二叉树
BiThrNode *pre;  // 指针pre始终指向刚访问过的结点(记录访问顺序)
pre->rchild = NULL;  // 初始化时其右孩子为空(从左开始建立线索)/* 带头结点的二叉树中序线索化 */
void InOrderThreading(BiThrTree &Thrt, BiThrTree T) {Thrt = new BiThrNode;  // 生成头结点ThrtThrt->lTag = 0;  // 头结点的左标志改为0(左孩子)Thrt->rTag = 1;  // 头结点的右标志改为1(后继)Thrt->rchild = Thrt;  // 头结点的右指针指向自己(初值)if(!T) {  // 若T为空Thrt->lchild = Thrt;  // 头结点的左指针也指向自己} else {  // 若T非空Thrt->lchild = T;  // 头结点的左指针指向根结点pre = Thrt;  // pre指向头结点(初值)InThreading(T);  // 调用InThreading对以T为根的二叉树进行线索化pre->rchild = Thrt;  // pre已移动到最后,令其右指针指向头结点pre->rTag = 1;  // pre右标志改为1(后继)Thrt->rchild = pre;  // 头结点右指针指向pre}
}/* 以结点p为根的子树中序线索化 */
void InThreading(BiThrTree p) {if(p) {InThreading(p->lchild);  // p的左子树递归线索化if(!(p->lchild)) {  // 若p的左孩子为空p->lTag = 1;  // 左标志改为1p->lchild = pre;  // 左指针指向pre(p的前驱)} else {  // 若p的左孩子非空p->lTag = 0;  // 左标志改为0(左指针指向左孩子)}if(!(pre->rchild)) {  // 若pre的右孩子为空pre->rTag = 1;  // 右标志改为1pre->rchild = p;  // 右指针指向p(pre的后继)} else {  // 若pre的右孩子非空pre->rTag = 0;  // 右标志改为0(右指针指向右孩子)}pre = p;  // 将pre移动到p(访问记录)InThreading(p->rchild);  // p的右子树递归线索化}
}
4.1.3.3 遍历线索二叉树
/* 无返回值void */
void InOrderTraverse_Thr(BiThrTree H) {  // 遍历中序线索二叉树p = H->lchild;  // p指示头结点H的左孩子(根结点)while(p != H) {  // 当p不指示H时,循环操作:while(p->lTag == 0) {  //当p的左标志为0(左孩子)时,循环操作:p = p->lchild;  // p移动到左孩子}cout << p->data;  // 输出p的数据域while(p->rTag == 1  // 当p的右标志为1且右指针不指向H时&&p->rchild != H) {p = p->rchild;  // p移动到后继cout << p->data;  // 输出p的数据域}p = p->rchild;  // p移动到右子树}
}

4.2 树和森林

4.2.1 树的存储结构

/* 树的二叉链表(孩子-兄弟)存储表示 */
typedef struct CSNode {ElemType data;struct CSNode *firstchild, *nextsibling;
} CSNode, *CSTree;

4.3 哈夫曼树及其应用

4.3.1 哈夫曼树的存储表示

typedef struct {int weight;  // 结点的权值int parent, lchild, rchild;  // 结点的双亲、左孩子、右孩子的下标
} HTNode, *HuffmanTree;  // 动态分配数组存储哈夫曼树

4.3.2 构造哈夫曼树

/* 无返回值void */
void CreateHaffmanTree(HuffmanTree &HT, int n) { //构造哈夫曼树HTif(n <= 1) {  // 若根结点少于两个return;  // 退出}/* 初始化 */m = 2*n - 1;  // 哈夫曼树的结点总数m=2n-1HT = new HTNode[m+1];  // 0号弃用,故分配m+1个单元(下标1 ~ m)for(i=1; i<=m; ++i) {  // 初始化每个结点的双亲、左右孩子的下标为0HT[i].parent = 0;HT[i].lchild = 0;HT[i].rchild = 0;}for(i=1; i<=n; ++i) {  // 前n个单元存储叶子结点(下标1 ~ n)cin >> HT[i].weight;  // 输入每个叶子结点的权值}/* 创建树 */for(i=n+1; i<=m; ++i) {  // 后n-1个单元存储根结点(下标n-1 ~ m)Select(HT, i-1, s1, s2);  // 选取权值最小的两个叶子/根结点HT[s1].parent = i;  // 令HT[s1]双亲为HT[i]HT[s2].parent = i;  // 令HT[s2]双亲为HT[i]HT[i].lchild = s1;  // 令HT[i]左孩子为HT[s1]HT[i].rchild = s2;  // 令HT[i]右孩子为HT[s2]HT[i].weight = HT[s1].weight + HT[s2].weight;  // 令HT[i]的权值为HT[s1]和HT[s2]的权值之和}
}/* 在叶子/根结点中选取两个双亲为0且权值最小的结点,返回其下标 */
int count;
struct Root {int index;int weight;
};
void Select(HTNode arr[], int len, int s1, int s2) {/* 提取双亲为0的结点(下标+权值) */count = 0;struct Root Roots[len+1];for(i=1; i<=len; i++) {if(arr[i].parent == 0) {count++;roots[count-1].index = i;roots[count-1].weight = arr[i].weight;}}/* 插入排序算法 */int i,j,temp;for(i=1; i<len; i++) {temp = roots[i].weight;for(j=i; j>0 && roots[j-1].weight>temp; j--) {roots[j] = roots[j-1];}roots[j].weight = temp;}/* 返回结点下标 */s1 = roots[0].index;s2 = roots[1].index;return;
}

4.3.3 哈夫曼编码

4.3.3.1 哈夫曼编码表的存储表示
/* 动态分配数组存储哈夫曼编码表 */
typedef char **HuffmanCode;  // 0号单元弃用,从1号单元开始存储
4.3.3.2 根据哈夫曼树求哈夫曼编码
/* 无返回值void */
void CreateHaffmanCode(HuffmanTree HT, HuffmanCode &HC, int n) {HC = new char*[n+1];  // 给指针数组HC分配n+1个单元(存储n个字符)cd = new char[n];  // 临时存放编码的动态数组空间cd(字符串)cd[n-1] = '\0';  // cd最后一位存放结束符for(i=1; i<=n; ++i) {  // 从树HT的1号单元开始逐个字符求编码:start = n-1;  // 记录当前编码临时存放在cd的下标(初值为末位)c = i;  // 记录树HT当前结点的下标f = HT[i].parent;  // 记录树HT当前结点双亲的下标while(f != 0) {  // 当f不为0(当前结点有双亲),循环操作:--start;  // start向前移动一位if(HT[f].lchild == c) {  // 若当前结点为双亲的左孩子cd[start] = '0';  // 临时存放0} else {cd[start] = '1';  // 临时存放1}c = f;  // c移动到双亲结点(当前结点)f = HT[f].parent;  // f移动到c的双亲结点(当前结点的双亲)}HC[i] = new char[n-start];  // 为当前字符编码表(指针)分配空间strcpy(HC[i], &cd[start]);  // 令HC当前下标的指针链到cd的首地址}delete cd;  // 释放临时空间
}

4.4 案例分析与实现

4.4.1 利用二叉树求解表达式的值

4.4.1.1 表达式树的创建
/* 无返回值void */
void InitExpTree() {  // 表达式树的创建算法InitStack(OPTR);  // 初始化空栈OPTR(暂存运算符)InitStack(EXPT);  // 初始化空栈EXPT(暂存已建好的树的根结点)Push(OPTR, '#');  // 先将表达式的起始符#入栈cin >> ch;  // 开始读取字符while(ch!='#' || GetTop(OPTR)!='#') { //当未到结束符,循环操作:if(!In(ch)) {  // 若ch不是运算符CreateExpTree(T, NULL, NULL, ch); //创建只有根结点ch的二叉树Push(EXPT, T);  // 将根结点T入栈EXPTcin >> ch;  // 读取下一个字符} else {  // 若ch是运算符switch(Precede(GetTop(OPTR), ch)) {  // 匹配运算符优先级case '<':  // 若OPTR栈顶元素(运算符)优先级小于ch(运算符)Push(OPTR, ch);  // 将ch入栈OPTRcin >> ch;  // 读取下一个字符break;case '>':  // 若OPTR栈顶元素(运算符)优先级大于ch(运算符)Pop(OPTR, theta);  // 将OPTR栈顶元素(运算符)出栈Pop(EXPT, b);  // 将EXPT栈顶元素(操作数)出栈Pop(EXPT, a);  // 将EXPT栈顶元素(操作数)出栈CreateExpTree(T, a, b, theta);  // 以theta为根、a为左子树、b为右子树创建二叉树Push(EXPT, T);  // 将根结点T入栈EXPTbreak;case '=':  // 若OPTR栈顶元素与ch匹配括号()Pop(OPTR, x);  // 将OPTR栈顶元素(出栈cin >> ch;  // 读取下一个字符break;}}}
}/* 判断字符是否为运算符,返回布尔值 */
bool In(char ch) {if(ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')' || ch == '#') {return true;} else {return false;}
}/* 由两个操作数(左右子树)+一个运算符(根)创建二叉树 */
void CreateExpTree(BiTree &T, BiTree a, BiTree b, char theta) {T = new BiTNode;T->data = theta;T->lchild = a;T->rchild = b;
}/* 比较栈顶元素与ch的运算优先级,返回><= */
char Precede(char top, char ch) {char operators[6] = {'(', '*', '/', '+', '-', ')', '#'};int ti, ci;for(i=0; i<6; i++) {if(top == operators[i]) {ti = i;}if(ch == operators[i]) {ci = i;}}if(top == '(') {if(ch == ')') {return '=';} else {return '<';}} else {if(ti < ci) {return '>';} else {return '<';}}
}
4.4.1.2 表达式树的求值
/* 返回值为int(表达式的值) */
int EvaluateExpTree(BiTree T) {  // 遍历表达式树进行表达式求值lvalue = rvalue = 0;  // 标记左右操作数(初值为0)if(T->lchild == NULL && T->rchild == NULL) {return T->data - '0';  // 若结点为叶子(操作数),返回数值} else {  // 若结点非叶子(运算符)lvalue = EvaluateExpTree(T->lchild);  // 递归计算左子树rvalue = EvaluateExpTree(T->rchild);  // 递归计算右子树return GetValue(T->data, lvalue, rvalue); //返回当前计算结果}
}/* 根据运算符+操作数返回运算结果(一位数) */
int GetValue(char opr, int a, int b) {switch(opr) {case '+':return a+b;case '-':return a-b;case '*':return a*b;case '/':return a/b;}
}

5、图

5.1 图的存储结构

5.1.1 邻接矩阵

5.1.1.1 邻接矩阵表示法
/* 图的邻接矩阵存储表示 */
#define MaxInt 32767  // 定义极大值∞
#define MVNum 100  // 定义最大顶点数
typedef char VerTexType;  // 设顶点的数据类型为字符型char
typedef int ArcType;  // 设边的权值的数据类型为整型int
typedef struct {VerTexType vexs[MVNum];  // 顶点的列表(一维数组)ArcType arcs[MVNum][MVNum];  // 邻接矩阵(二维数组)int vexnum, arcnum;  // 图的当前顶点数和边数
} AMGraph;  // 构造图AMGraph
5.1.1.2 采用邻接矩阵表示法创建无向网
/* 返回值为Status(状态码) */
Status CreateUDN(AMGraph &G) {  // 采用邻接矩阵表示法创建无向网Gcin >> G.vexnum >> G.arcnum;  // 输入总顶点数、总边数for(i=0; i<G.vexnum; i++) {  // 按总顶点数循环(顶点列表):cin >> G.vexs[i];  // 输入每个顶点的信息,初始化顶点列表}for(i=0; i<G.vexnum; i++) {  // 按总顶点数循环(邻接矩阵-行):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);  // 获取v1在G中的位置i(顶点列表中v1的下标)j = LocateVex(G, v2);  // 获取v2在G中的位置j(顶点列表中v2的下标)G.arcs[i][j] = w;  // 将第i行第j列的权值置为wG.arcs[j][i] = G.arcs[i][j];  // 第j行第i列的权值也为w(无向网为对称矩阵)}return OK;
}/* 返回值为int(数组下标) */
int LocateVex(AMGraph G, char v) {  // 获取顶点v在图G中的位置(顶点列表已知)for(i=0; i<G.vexnum; i++) {if(v == G.vexs[i]) {return i;}}
}

5.1.2 邻接表

5.1.2.1 邻接表表示法
/* 图的邻接表存储表示 */
#define MVNum 100  // 定义最大顶点数
typedef struct ArcNode {int adjvex;  // 邻接点域(邻接点的下标)struct ArcNode *nextarc;  // 链域(指向下一个边结点)ArcType info;  // 数据域(边的权值)
} ArcNode;  // 构造边结点ArcNode
typedef struct VNode {VerTexType data;  // 数据域(顶点信息)ArcNode *firstarc;  // 链域(指向链表中第一个边结点)
} VNode, AdjList[MVNum];  // 构造表头结点VNode、表头结点表AdjList
typedef struct {AdjList vertices;  // 声明表头结点表变量verticesint vexnum, arcnum;  // 图的当前顶点数和边数
} ALGraph;  // 构造图ALGraph
5.1.2.2 采用邻接表表示法创建无向图
/* 返回值为Status(状态码) */
Status CreateUDG(ALGraph &G) {  // 采用邻接表表示法创建无向网Gcin >> G.nevnum >> G.arcnum;  // 输入总顶点数、总边数for(i=0; i<G.vexnum; i++) {  // 按总顶点数循环(表头结点表):cin >> G.vertices[i].data;  // 输入每个顶点的信息G.vertices[i].firstarc = NULL;  // 初始化所有链域为空}for(k=0; k<G.arcnum; k++) {  // 按总边数循环,构造邻接表:cin >> v1 >> v2;  // 依次输入一条边的两个顶点i = LocateVex(G, v1);  // 获取v1在G中的位置i(顶点列表中v1的下标)j = LocateVex(G, v2);  // 获取v2在G中的位置j(顶点列表中v2的下标)p1 = new ArcNode;  // 生成边结点*p1p1->adjvex = j;  // *p1的邻接点域存放v2下标j(v1的邻接点)p1->nextarc = G.vertices[i].firstarc;  // *p1的链域同步为v1的链域(插入第一个边结点之前)G.vertices[i].firstarc = p1;  // v1的链域指向*p1(成为第一个边结点)p2 = new ArcNode;  // 生成边结点*p2p2->adjvex = i;  // *p2的邻接点域存放v1下标i(v2的邻接点)p2->nextarc = G.vertices[j].firstarc;  // *p2的链域同步为v2的链域(插入第一个边结点之前)G.vertices[j].firstarc = p2;  // v2的链域指向*p2(成为第一个边结点)}return OK;
}/* 返回值为int(数组下标) */
int LocateVex(ALGraph G, char v) {  // 获取顶点v在图G中的位置(表头结点表已知)for(i=0; i<G.vexnum; i++) {if(v == G.vertices[i].data) {return i;}}
}

5.1.3 十字链表

/* 有向图的十字链表存储表示 */
#define MAX_VERTEX_NUM 20  // 定义最大顶点数
typedef struct ArcBox {int tailvex, headvex;  // 尾域(弧尾顶点下标)、头域(弧头顶点下标)struct ArcBox *hlink, *tlink;  // 链域(指向弧头/弧尾相同的下一条弧)InfoType *info;  // 数据域(指向弧的相关信息)
} ArcBox;  // 构造弧结点ArcBox
typedef struct VexNode {VertexType data;  // 数据域(顶点信息)ArcBox *firstin, *firstout;  // 链域(指向以该顶点为弧头/弧尾的第一个弧结点)
} VexNode;  // 构造顶点结点VexNode
typedef struct {VexNode xList[MAX_VERTEX_NUM];  // 声明表头结点表xListint vexnum, arcnum;  // 图的当前顶点数和弧数
} OLGraph;  // 构造图OLGraph

5.1.4 邻接多重表

/* 无向图的邻接多重表存储表示 */
#define MAX_VERTEX_NUM 20  // 定义最大顶点数
typedef enum {unvisited, visited  // 0, 1
} VisitIf;  // 定义枚举VisitIf
typedef struct EBox {VisitIf mark;  // 标志域(是否已被访问过)int ivex, jvex;  // 边依附的两个顶点的下标struct EBox *ilink, *jlink;  // 指向下一条依附于顶点ivex/jvex的边(边结点)InfoType *info;  // 指向边的相关信息
} EBox;  // 构造边结点EBox
typedef struct VexBox {VertexType data;  // 顶点信息EBox *firstedge;  // 指向依附于顶点的第一条边(边结点)
} VexBox;  // 构造顶点结点VexBox
typedef struct {VexBox adjmulist[MAX_VERTEX_NUM];  // 声明表头结点表adjmulistint vexnum, arcnum;  // 图的当前顶点数和边数
} AMLGraph;  // 构造图AMLGraph

5.2 图的遍历

5.2.1 深度优先搜索

5.2.1.1 深度优先搜索遍历连通图
bool visited[MVNum];  // 定义数组visited,存储访问标记/* 采用邻接矩阵表示图的深度优先搜索遍历 */
void DFS_AM(AMGraph G, int v) {  // 从邻接矩阵G的第v个顶点出发进行遍历(递归)cout << v;  // 输出v(下标)visited[v] = true;  // 标记第v个顶点为已访问for(w=0; w<G.vexnum; w++) {  // 按总顶点数循环,在第v行依次检查:if((G.arcs[v][w] != 0) && (!visited[w])) {  // 若w为v的邻接点且w未被访问过:DFS_AM(G, w);  // 递归调用(从w出发)}}
}/* 采用邻接表表示图的深度优先搜索遍历 */
void DFS_AL(ALGraph G, int v) {  // 从邻接表G的第v个顶点出发进行遍历(递归)cout << v;  // 输出v(下标)visited[v] = true;  // 标记第v个顶点为已访问p = G.vertices[v].firstarc;  // 指针p同步为v链域指向的结点(v的第一个邻接点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):w = p->adjvex;  // 获取邻接点下标w(p的邻接点域)if(!visited[w]) {  // 若w未被访问过:DFS_AL(G, w);  // 递归调用(从w出发)}p = p->nextarc;  // 指针p移动到下一个边结点}
}
5.2.1.2 深度优先搜索遍历非连通图
bool visited[MVNum];  // 定义数组visited,存储访问标记
void DFSTraverse(Graph G) {  // 对非连通图G进行深度优先搜索遍历for(v=0; v<G.vexnum; v++) {  // 按总顶点数循环:visited[v] = false;  // 初始化标记数组为全部未访问}for(v=0; v<G.vexnum; v++) {  // 按总顶点数循环:if(!visited[v]) {  // 若v未被访问过:DFS_xx(G, v);  // 调用遍历连通图的算法(从v出发)}}
}

5.2.2 广度优先搜索

5.2.2.1 广度优先搜索遍历连通图
/* 引入辅助的数据结构 */
typedef struct {QElemType *base;int front;int rear;
} SqQueue;  // 构造队列SqQueue
bool visited[MVNum];  // 定义数组visited,存储访问标记/* 采用邻接矩阵表示图的广度优先搜索遍历 */
void BFS_AM(AMGraph G, int v) {  // 从邻接矩阵G的第v个顶点出发进行遍历cout << v;  // 输出v(下标)visited[v] = true;  // 标记第v个顶点为已访问InitQueue(Q);  // 初始化队列QEnQueue(Q, v);  // 将顶点v入队while(!QueueEmpty(Q)) {  // 当队列非空(未访问完毕):DeQueue(Q, u);  // 将队头元素出队,存为ufor(w=0; w<G.vexnum; w++) {  // 按总顶点数循环,在第u行依次检查:if((G.arcs[u][w] != 0) && (!visited[w])) {  // 若w为u的邻接点且w未被访问过:cout << w;  // 输出w(下标)visited[w] = true;  // 标记第w个顶点为已访问EnQueue(Q, w);  // 将顶点w入队}}}
}/* 采用邻接表表示图的广度优先搜索遍历 */
void BFS_AL(ALGraph G, int v) {  // 从邻接表G的第v个顶点出发进行遍历cout << v;  // 输出v(下标)visited[v] = true;  // 标记第v个顶点为已访问InitQueue(Q);  // 初始化队列QEnQueue(Q, v);  // 将顶点v入队while(!QueueEmpty(Q)) {  // 当队列非空(未访问完毕):DeQueue(Q, u);  // 将队头元素出队,存为up = G.vertices[u].firstarc;  // 指针p同步为u链域指向的结点(u的第一个邻接点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):w = p->adjvex;  // 获取邻接点下标w(p的邻接点域)if(!visited[w]) {  // 若w未被访问过:cout << w;  // 输出w(下标)visited[w] = true;  // 标记第w个顶点为已访问EnQueue(Q, w);  // 将顶点w入队}p = p->nextarc;  // 指针p移动到下一个边结点}}
}
5.2.2.2 广度优先搜索遍历非连通图
bool visited[MVNum];  // 定义数组visited,存储访问标记
void BFSTraverse(Graph G) {  // 对非连通图G进行广度优先搜索遍历for(v=0; v<G.vexnum; v++) {  // 按总顶点数循环:visited[v] = false;  // 初始化标记数组为全部未访问}for(v=0; v<G.vexnum; v++) {  // 按总顶点数循环:if(!visited[v]) {  // 若v未被访问过:BFS_xx(G, v);  // 调用遍历连通图的算法(从v出发)}}
}

5.3 图的应用

5.3.1 最小生成树

5.3.1.1 普里姆算法
/* 定义结构体数组,记录从U到V-U中选取的权值最小的边 */
struct Aux {VertexType adjvex;  // 最小边在U中的顶点ArcType lowcost;  // 最小边上的权值
} closedge[MVNum];  // 构造数组closedge/* 以邻接矩阵表示无向网G,从顶点u出发构造最小生成树T,输出T的各条边 */
void MiniSpanTree_Prim(AMGraph G, VertexType u) {k = LocateVex(G, u);  // 获取顶点u在G中的下标kfor(j=0; j<G.vexnum; j++) {  // 按总顶点数循环,在第k行依次检查:if(j != k) {  // 排除顶点u本身:closedge[j] = {u, G.arcs[k][j]};  // 将u关联的边以{adjvex, lowcost}格式依次存入数组中}}closedge[k].lowcost = 0;  // 在数组中将u-u权值置空,顶点u自动归入U中for(i=1; i<G.vexnum; i++) {  // 按剩余顶点数循环(n-1):k = Min(closedge, G);  // 获取数组中权值最小的边的下标ku0 = closedge[k].adjvex;  // 权值最小的边在U中的顶点u0(u0∈U)v0 = G.vexs[k];  // 另一个顶点v0(矩阵第k列即顶点列表中第k个顶点,v0∈V-U)cout << u0 << v0;  // 输出当前边(u0, v0)closedge[k].lowcost = 0;  // 数组中将u0-v0权值置空,顶点u0、v0自动归入U中for(j=0; j<G.vexnum; j++) {  // 按总顶点数二级循环,在第k行依次检查(从v0出发):if(G.arcs[k][j] < closedge[j].lowcost) {  // 若边k-j的权值小于数组中u-j的权值:closedge[j] = {G.vexs[k], G.arcs[k][j]};  // 将数组中边u-j替换为边k-j}}}
}/* 返回值为int(数组下标) */
int Min(AMGraph G, Aux closedge[]) {  // 获取数组中权值最小的边的下标min = MaxInt;k = 0;for(i=1; i<G.vexnum; i++) {if((closedge[i].lowcost != 0) && (closedge[i].lowcost < min)) {min = closedge[i].lowcost;k = i;}}return k;
}
5.3.1.2 克鲁斯卡尔算法
/* 引入辅助数组 */
struct Aux {VertexType Head;  // 边的第一个顶点VertexType Tail;  // 边的第二个顶点ArcType lowcost;  // 边上的权值
} Edge[arcnum];  // 构造数组Edge,存储边的信息
int Vexset[MVNum];  // 定义数组Vexset,记录每个顶点所在的连通分量/* 以邻接矩阵表示无向网G,构造最小生成树T,输出T的各条边 */
void MiniSpanTree_Kruskal(AMGraph G) {Sort(Edge);  // 初始化数组Edge并按权值从小到大排序for(i=0; i<G.vexnum; i++) {  // 按总顶点数循环:Vexset[i] = i;  // 每个顶点为一个连通分量,用各自下标区分}for(i=0; i<G.arcnum; i++) {  // 按总边数循环:v1 = LocateVex(G, Edge[i].Head);  // 获取第i条边的第一个顶点下标v2 = LocateVex(G, Edge[i].Tail);  // 获取第i条边的第二个顶点下标vs1 = Vexset[v1];  // 获取顶点v1所在连通分量vs1vs2 = Vexset[v2];  // 获取顶点v2所在连通分量vs2if(vs1 != vs2) {  // 若两顶点不在同一连通分量:cout << Edge[i].Head << Edge[i].Tail;  // 输出边(v1, v2)for(j=0; j<G.vexnum; j++) {  // 按总顶点数二级循环:if(Vexset[j] == vs2) {  // 若第j个顶点在连通分量vs2上:Vexset[j] = vs1;  // 合并到连通分量vs1上(边i已将两个分量连成一个)}}}}
}

5.3.2 最短路径

5.3.2.1 迪杰斯特拉算法
/* 引入辅助数组 */
bool S[MVNum];  // 记录从源点v0到终点vi是否已被确定最短路径长度
int Path[MVNum];  // 记录从源点v0到终点vi的当前最短路径上,vi的直接前驱顶点下标
ArcType D[arcnum];  // 记录从源点v0到终点vi的当前最短路径长度(弧上的权值)/* 以邻接矩阵表示有向网G,求第v0个顶点(源点)到其余顶点的最短路径 */
void ShortestPath_DIJ(AMGraph G, int v0) {n = G.vexnum;  // 总顶点数nfor(v=0; v<n; v++) {  // 循环n次,在第v0行依次检查:S[v] = false;  // 初始化S每项为false(未确定最短路径)D[v] = G.arcs[v0][v];  // 初始化D每项为v0-v的权值(当前最短路径)if(D[v] < MaxInt) {  // 若v0-v权值小于∞(有弧):Path[v] = v0;  // Path中顶点v的直接前驱顶点为v0} else {Path[v] = -1;  // 否则,v的直接前驱为-1}}S[v0] = true;  // v0-v0已确定最短路径D[v0] = 0;  // v0-v0路径长度为0for(i=1; i<n; i++) {  // 循环n-1次,依次计算其余顶点:min = MaxInt;  // min初值为∞(循环重置)for(w=0; w<n; w++) {  // 二级循环n次,选择v0-?中最短的路径:if(!S[w] && (D[w] < min)) {  // 若w未确定,且v0-w权值比min更小(循环更新):v = w;  // v更新为当前下标min = D[w];  // min更新为当前权值}}S[v] = true;  // v0-v已确定最短路径for(w=0; w<n; w++) {  // 二级循环n次,以v为中转点选择v0-?中最短的路径:if(!S[w] && (D[v] + G.arcs[v][w] < D[w])) {  // 若w未确定,且(v0-v)+(v-w)权值比v0-w更小(循环更新):D[w] = D[v] + G.arcs[v][w];  // v0-w路径长度更新为(v0-v)+(v-w)权值Path[w] = v;  // Path中w的直接前驱顶点更新为v}}}
}
5.3.2.2 弗洛伊德算法
/* 引入辅助数组 */
int Path[MVNum][MVNum];  // 最短路径上顶点vj的前一个顶点的下标
ArcType D[MVNum][MVNum];  // 记录顶点vi到vj之间的最短路径长度/* 以邻接矩阵表示有向网G,求各对顶点i和j之间的最短路径 */
void ShortestPath_Floyd(AMGraph G) {for(i=0; i<G.vexnum; i++) {  // 按总顶点数循环(矩阵第i行):for(j=0; j<G.vexnum; j++) {  // 按总顶点数二级循环(矩阵第j列):D[i][j] = G.arcs[i][j];  // 获取各对顶点之间初始的路径(权值)if((D[i][j] < MaxInt) && (i != j)) {  // 若i-j权值小于∞且非顶点本身(有弧):Path[i][j] = i;  // Path中顶点j的直接前驱顶点为i} else {Path[i][j] = -1;  // 否则,j的直接前驱为-1}}}for(k=0; k<G.vexnum; k++) {  // 按总顶点数循环(加入中转点k再进行比较):for(i=0; i<G.vexnum; i++) {  // 按总顶点数二级循环(第i行):for(j=0; j<G.vexnum; j++) {  // 按总顶点数三级循环(第j列):if(D[i][k] + D[k][j] < D[i][j]) {  // 若(i-k)+(k-j)权值比i-j更小(循环更新):D[i][j] = D[i][k] + D[k][j]  // i-j路径长度更新为(i-k)+(k-j)权值Path[i][j] = Path[k][j];  // Path中j的直接前驱更新为k}}}}
}

5.3.3 拓扑排序

/* 引入辅助的数据结构 */
typedef struct {SElemType *base;SElemType *top;int stacksize;
} SqStack;  // 构造栈SqStack
int indegree[MVNum];  // 存放各顶点入度,没有前驱的顶点入度为0,已输出顶点的后继顶点入度-1
int topo[MVNum];  // 记录拓扑序列的顶点序号/* 以邻接表表示有向图G,若G无环则生成一个拓扑序列topo[]并返回OK,否则返回ERROR */
Status TopologicalSort(ALGraph G, int topo[]) {FindInDegree(G, indgree);  // 获取各顶点的入度InitStack(S);  // 初始化栈Sfor(i=0; i<G.vexnum; i++) {  // 按总顶点数循环:if(indgree[i] == 0) {  // 若顶点i的入度为0(无前驱):Push(S, i);  // 将i入栈}}m = 0;  // 对输出顶点进行计数(初值为0)while(!StackEmpty(S)) {  // 当栈非空(后进先出):Pop(S, i);  // 将栈顶元素出栈,存为itopo[m] = i;  // 记录拓扑序列(第m个为i)++m;  // 输出顶点计数+1p = G.vertices[i].firstarc;  // 指针p同步为第i个顶点链域指向的结点(i的第一个边结点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):k = p->adjvex;  // 获取边结点的邻接点域值k(顶点下标)--indegree[k];  // indegree对应顶点的入度-1(删除顶点k和以它为尾的弧)if(indgree[k] == 0) {  // 若k入度减为0:Push(S, k);  // 将k入栈}p = p->nextarc;  // 指针p移动到下一个边结点}}if(m < G.vexnum) {  // 若输出顶点数<总顶点数:return ERROR;  // 则图中有环} else {return OK;}
}/* 以邻接表表示有向图G,求G中各顶点的入度,存入数组indgree */
void FindInDegree(ALGraph G, int indgree[]) {for(i=0; i<G.vexnum; i++) {  // 按总顶点数循环:indgree[i] = 0;  // 初始化indgree中每个顶点入度为0}for(i=0; i<G.vexnum; i++) {  // 按总顶点数循环(表头结点表):p = G.vertices[i].firstarc;  // 指针p同步为第i个顶点链域指向的结点(i的第一个边结点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):++indgree[p->adjvex];  // 边结点的邻接点域值作为顶点下标,indgree中对应顶点的入度+1p = p->nextarc;  // 指针p移动到下一个边结点}}
}

5.3.4 关键路径

/* 引入辅助数组 */
ArcType ve[MVNum];  // 事件vi的最早发生时间
ArcType vl[MVNum];  // 事件vi的最迟发生时间
int topo[MVNum];  // 记录拓扑序列的顶点序号/* 以邻接表表示有向网G,输出G的各项关键活动,返回值为Status(状态码) */
Status CriticalPath(ALGraph G) {if(!TopologicalSort(G, topo)) {  // 调用拓扑排序算法,获取拓扑序列topo;若调用失败(网中有环):return ERROR;  // 返回ERROR}n = G.vexnum;  // 总顶点数nfor(i=0; i<n; i++) {  // 循环n次:ve[i] = 0;  // 初始化每个事件的最早发生时间为0}for(i=0; i<n; i++) {  // 循环n次,按拓扑顺序求每个事件的最早发生时间:k = topo[i];  // 获取顶点vi的下标kp = G.vertices[k].firstarc;  // 指针p移动到第k个顶点链域指向的结点(vi的第一个边结点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):j = p->adjvex;  // 获取边结点的邻接点域值j(顶点下标)if(ve[j] < ve[k] + p->info) {  // 若j最早发生时间 < (k最早发生时间 + k-j权值):ve[j] = ve[k] + p->info;  // j最早发生时间更新为(k最早发生时间 + k-j权值)}p = p->nextarc;  // 指针p移动到下一个边结点}}for(i=0; i<n; i++) {  // 循环n次:vl[i] = ve[n-1];  // 根据ve的值初始化每个事件的最迟发生时间}for(i=n-1; i>=0; i--) {  // 循环n次,按拓扑逆序求每个事件的最迟发生时间:k = topo[i];  // 获取顶点vi的下标kp = G.vertices[k].firstarc;  // 指针p移动到第k个顶点链域指向的结点(vi的第一个边结点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):j = p->adjvex;  // 获取边结点的邻接点域值j(顶点下标)if(vl[k] > vl[j] - p->info) {  // 若k最迟发生时间 > (j最迟发生时间 - k-j权值):vl[k] = vl[j] - p->info; // k最迟发生时间更新为(j最迟发生时间 - k-j权值)}p = p->nextarc;  // 指针p移动到下一个边结点}}for(i=0; i<n; i++) {  // 循环n次,依次检查vi为头的弧是否为关键活动:p = G.vertices[i].firstarc;  // 指针p移动到第i个顶点链域指向的结点(vi的第一个边结点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):j = p->adjvex;  // 获取边结点的邻接点域值j(顶点下标)e = ve[i];  // 获取活动<vi, vj>的最早开始时间e(事件i的最早发生时间)l = vl[j] - p->info;  // 获取活动<vi, vj>的最晚开始时间l(事件j的最迟发生时间 - i-j权值)if(e == l) {  // 若<vi, vj>是关键活动:cout << G.vertices[i].data << G.vertices[j].data;  // 输出<vi, vj>}p = p->nextarc;  // 指针p移动到下一个边结点}}
}

5.4 案例分析与实现

5.4.1 六度空间理论

/* 引入辅助的数据结构 */
typedef struct {QElemType *base;int front;int rear;
} SqQueue;  // 构造队列SqQueue
bool visited[MVNum];  // 存储顶点访问标记/* 邻接表存储无向图G,通过广度优先搜索遍历G来验证六度空间理论 */
void SixDegree_BFS(ALGraph G, int start) {  // 指定start为起始点Visit_Num = 0;  // 记录路径长度在7以内的顶点个数(初值为0)visited[start] = true;  // 标记第start个顶点为已访问InitQueue(Q);  // 初始化队列QEnQueue(Q, start);  // 将顶点start入队level[0] = 1;  // 记录第一步入队的顶点数为1(从start开始走)for(len=1; len<=6 && !QueueEmpty(Q); len++) {  // 队列非空时循环6次(统计七步之内能找到的所有顶点数):for(i=0; i<level[len-1]; i++) {  // 按上一步入队的顶点数循环(继续走一步):DeQueue(Q, u);  // 将队头元素出队,存为up = G.vertices[u].firstarc;  // 指针p指示u链域指向的结点(u的第一个邻接点)while(p != NULL) {  // 当p不为空(顺着边表依次检查):w = p->adjvex;  // 获取邻接点下标w(p的邻接点域)if(!visited[w]) {  // 若w未被访问过:visited[w] = true;  // 标记第w个顶点为已访问Visit_Num++;  // 七步之内找到的顶点个数+1level[len]++;  // 当前这一步找到的顶点个数+1EnQueue(Q, w);  // 将顶点w入队(作为下一步的起点)}p = p->nextarc;  // 指针p移动到下一个边结点}}cout << (Visit_Num / G.vexnum) * 100;  // 每走一步输出一次百分比}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/99668.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年中国助消化药物行业现状分析:消化不良患者逐年上升,提升需求量[图]

助消化药物主要分为促胃动力药物、消化酶抑制剂、胃酸抑制药物和消食剂4种类型。促胃动力药物的作用机制是通过增强胃肠道平滑肌动力促进胃酸分泌&#xff0c;从而达到助消化的目的&#xff0c;临床常用药物包括多潘立酮、莫沙必利、西沙比利等。 助消化药物分类 资料来源&…

Observability:使用 OpenTelemetry 对 Node.js 应用程序进行自动检测

作者&#xff1a;Bahubali Shetti DevOps 和 SRE 团队正在改变软件开发的流程。 DevOps 工程师专注于高效的软件应用程序和服务交付&#xff0c;而 SRE 团队是确保可靠性、可扩展性和性能的关键。 这些团队必须依赖全栈可观察性解决方案&#xff0c;使他们能够管理和监控系统&a…

Django开发之进阶篇

Django进阶篇 一、Django学习之模板二、Django学习之中间件默认中间件自定义中间件 三、Django学习之ORM定义模型类生成数据库表操作数据库添加查询修改删除 一、Django学习之模板 在 Django 中&#xff0c;模板&#xff08;Template&#xff09;是用于生成动态 HTML&#xff…

【架构】研发高可用架构和系统设计经验

研发高可用架构和系统设计经验 从研发规范层面、应用服务层面、存储层面、产品层面、运维部署层面、异常应急层面这六大层面去剖析一个高可用的系统需要有哪些关键的设计和考虑。 一、高可用架构和系统设计思想 1.可用性和高可用概念 可用性是一个可以量化的指标,计算的公…

Java 8遍历Map的方式

1、使用entrySet()和stream()方法结合遍历Map Map<String, String> map new HashMap<>();map.put("A001", "zhangsan");map.put("A002", "lisi");map.entrySet().stream().forEach(entry -> {String key entry.getKe…

自动拟人对话机器人在客户服务方面起了什么作用?

在当今数字时代&#xff0c;企业不断寻求创新的方法来提升客户服务体验。随着科技的不断进步和消费者期望的提升&#xff0c;传统的客户服务方式逐渐无法满足现代消费者的需求。因此&#xff0c;许多企业正在积极探索利用新兴技术来改进客户服务&#xff0c;自动拟人对话机器人…

LuatOS-SOC接口文档(air780E)-- gmssl - 国密算法

sm.sm2encrypt(pkx,pky,data)# sm2算法加密 参数 传入值类型 解释 string 公钥x,必选 string 公钥y,必选 string 待计算的数据,必选,最长255字节 返回值 返回值类型 解释 string 加密后的字符串, 原样输出,未经HEX转换 例子 local originStr "encryptio…

新增一个timestamp.html 页面 --chatGPT

问&#xff1a;新增一个timestamp.html 页面&#xff0c;页面实现日期和时间戳 互转功能 gpt: 要创建一个 timestamp.html 页面&#xff0c;用于实现日期和时间戳的互转功能&#xff0c;可以按照以下步骤操作&#xff1a; 1. 创建一个名为 timestamp.html 的 HTML 文件&…

阿里云轻量应用服务器流量价格表(计费/免费说明)

阿里云轻量应用服务器套餐有的限制月流量&#xff0c;有的不限制月流量&#xff0c;限制每月流量的套餐&#xff0c;如果自带的免费月流量包用完了&#xff0c;流量超额部分需要另外支付流量费&#xff0c;阿里云百科aliyunbaike.com分享阿里云轻量应用服务器月流量超额收费价格…

phpstorm不提示$this->request,不提示Controller父类的方法

![在这里插入图片描述](https://img-blog.csdnimg.cn/d55799a22b724099930eb7fb67260a12.png 最后 保存就可以了

浅谈风力发电场集中监控系统解决方案

作为清洁能源之一&#xff0c;风力发电场近几年装机容量快速增长。8月17日&#xff0c;国家能源局发布1-7月份全国电力工业统计数据。截至7月底&#xff0c;全国累计发电装机容量约27.4亿千瓦&#xff0c;同比增长11.5%。其中&#xff0c;太阳能发电装机容量约4.9亿千瓦&#x…

【JavaEE】_tomcat的安装与简单使用

目录 1. 安装tomcat 1.1 下载tomcat并解压缩 1.2 启动tomcat 1.3 访问tomcat欢迎页面 2. tomcat简单使用&#xff1a;部署前端代码 3. 基于tomcat的网站后端开发 tomcat是一个HTTP服务器&#xff0c;HTTP协议就是HTTP客户端与HTTP服务器之间通信使用的协议。 其中HTTP客…

管理Linux的联网

1. RHEL9版本特点 在RHEL7版本中&#xff0c;同时支持network.service和NetworkManager.service&#xff08;简称NM&#xff09;。 在RHEL8上默认只能通过NM进行网络配置&#xff0c;包括动态ip和静态ip,若不开启NM&#xff0c;否则无法使用网络 RHEL8依然支持network.service&…

【Spring Boot】SpringBoot 单元测试

SpringBoot 单元测试 一. 什么是单元测试二. 单元测试的好处三. Spring Boot 单元测试单元测试的实现步骤 一. 什么是单元测试 单元测试&#xff08;unit testing&#xff09;&#xff0c;是指对软件中的最⼩可测试单元进⾏检查和验证的过程就叫单元测试。 二. 单元测试的好处…

Kubernetes革命:云原生时代的应用编排和自动化

文章目录 什么是Kubernetes以及为何它备受欢迎&#xff1f;云原生应用和K8s的关系Kubernetes的核心概念&#xff1a;Pods、Services、ReplicaSets等部署、扩展和管理应用程序的自动化容器编排的演进&#xff1a;Docker到Kubernetes实际用例&#xff1a;企业如何受益于K8s的应用…

【SpringCloud】微服务技术栈入门6 - RestClient深入

目录 RestClient定义索引安装与配置测试类插入索引索引的 CRUD批量导入文档 RestClient 定义索引 引入对应 sql 后&#xff0c;需要添加 sql 对应的 es 索引 下面是根据 sql 结构来构建的索引树&#xff0c;我们需要插入到 es 里面&#xff0c;在这里先不要在 devtools 中实现…

【工作记录】css3 grid布局笔记

概述 Grid 布局即网格布局&#xff0c;是一种新的 CSS 布局模型&#xff0c;比较擅长将一个页面划分为几个主要区域&#xff0c;以及定义这些区域的大小、位置、层次等关系。号称是最强大的的 CSS 布局方案&#xff0c;是目前唯一一种 CSS 二维布局 参数配置说明 属性说明可…

Ubuntu 18.04 OpenCV3.4.5 + OpenCV3.4.5 Contrib 编译

目录 1 依赖安装 2 下载opencv3.4.5及opencv3.4.5 contrib版本 3 编译opencv3.4.5 opencv3.4.5_contrib及遇到的问题 1 依赖安装 首先安装编译工具CMake&#xff0c;命令安装即可&#xff1a; sudo apt install cmake 安装Eigen&#xff1a; sudo apt-get install libeigen3-…

重启redis的步骤

要重启 Redis&#xff0c;需要使用以下步骤&#xff1a; 登录到您的服务器&#xff1a;使用 SSH 或其他远程访问方式登录到托管 Redis 的服务器。 停止 Redis 服务器&#xff1a;您可以使用以下命令停止 Redis 服务器&#xff1a; redis-cli shutdown 这将向 Redis 服务器发送…

计算机的体系与结构

文章目录 前言一、冯诺依曼体系二、现代计算机的结构总结 前言 今天给大家介绍计算机的体系和结构&#xff0c;分为两个板块&#xff1a;冯诺依曼体系和现代计算机的结构。 一、冯诺依曼体系 冯诺依曼体系是将程序指令和数据一起存储的计算机设计概念结构。 冯诺依曼体系可以…