复习要点

第一章 绪论

数据结构:
广义定义:数据结构是一门研究非数值计算的程序设计问题中计算机的操作对像以及它们之间的关系和操作等的学科。
狭义定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。

数据元素:
数据元素是数据的基本单位,在计算机中通常作为一个整体进行考虑和处理。有时一个数据元素可以由若干个数据项组成,数据项是数据元素不可分割的最小单位。

数据类型:
按“值”的不同特性可分为:
原子类型:基本类型(整型,实型,字符型,枚举型),指针类型和空类型
结构类型:值由若干成分按某种结构组成
抽象数据类型(ADT):原子类型,固定聚合类型,可变聚合类型

逻辑结构(线性、非线性)
复习提要(20级) - 图1

存储结构(顺序、链式)
顺序存储:存储的物理位置相邻。(p.s. 物理位置即信息在计算机中的位置。)
链接存储:存储的物理位置未必相邻,通过记录相邻元素的物理位置来找到相邻元素。
索引存储
散列存储

时间复杂度(大O表示法)、语句执行频度计算
复习提要(20级) - 图2

  • 时间复杂度计算(单个循环体)
    • 直接关注循环体的执行次数,设为k
  • 时间复杂度计算(多个循环体)
    • 两个运算规则:乘法规则,加法规则。

第二章 线性表

线性表的定义及特点:
定义:线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列
特点:线性表中第一个元素称为表头元素;最后一个元素称为表尾元素。除第一个元素外,每个元素有且仅有一个直接前驱。除最后一个元素外,每个元素有且仅有一个直接后继
顺序存储:查找快,增删慢 链表:查找慢,增删快

线性表的顺序存储及基本操作(插入、删除等)

  1. /*
  2. * 顺序表结构
  3. *
  4. * 注:elem在使用前需要先为其分配内存,且元素从elem[0]处开始存储
  5. */
  6. typedef struct {
  7. ElemType* elem; // 顺序表存储空间的基址(指向顺序表所占内存的起始位置)
  8. int length; // 当前顺序表长度(包含多少元素)
  9. int listsize; // 当前分配的存储容量(可以存储多少元素)
  10. } SqList;
  11. /*
  12. * ████████ 算法2.4 ████████
  13. *
  14. * 插入
  15. *
  16. * 向顺序表第i个位置上插入e,插入成功则返回OK,否则返回ERROR。
  17. *
  18. *【备注】
  19. * 教材中i的含义是元素位置,从1开始计数
  20. */
  21. Status ListInsert(SqList* L, int i, ElemType e) {
  22. ElemType* newbase;
  23. ElemType* p, * q;
  24. // 确保顺序表结构存在
  25. if(L == NULL || (*L).elem == NULL) {
  26. return ERROR;
  27. }
  28. // i值越界
  29. if(i < 1 || i > (*L).length + 1) {
  30. return ERROR;
  31. }
  32. // 若存储空间已满,则增加新空间
  33. if((*L).length >= (*L).listsize) {
  34. // 基于现有空间扩容
  35. newbase = (ElemType*) realloc((*L).elem, ((*L).listsize + LISTINCREMENT) * sizeof(ElemType));
  36. if(newbase == NULL) {
  37. // 存储内存失败
  38. exit(OVERFLOW);
  39. }
  40. // 新基址
  41. (*L).elem = newbase;
  42. // 存的存储空间
  43. (*L).listsize += LISTINCREMENT;
  44. }
  45. // q为插入位置
  46. q = &(*L).elem[i - 1];
  47. // 1.右移元素,腾出位置
  48. for(p = &(*L).elem[(*L).length - 1]; p >= q; --p) {
  49. *(p + 1) = *p;
  50. }
  51. // 2.插入e
  52. *q = e;
  53. // 3.表长增1
  54. (*L).length++;
  55. return OK;
  56. }
  57. /*
  58. * ████████ 算法2.5 ████████
  59. *
  60. * 删除
  61. *
  62. * 删除顺序表第i个位置上的元素,并将被删除元素存储到e中。
  63. * 删除成功则返回OK,否则返回ERROR。
  64. *
  65. *【备注】
  66. * 教材中i的含义是元素位置,从1开始计数
  67. */
  68. Status ListDelete(SqList* L, int i, ElemType* e) {
  69. ElemType* p, * q;
  70. // 确保顺序表结构存在
  71. if(L == NULL || (*L).elem == NULL) {
  72. return ERROR;
  73. }
  74. // i值越界
  75. if(i < 1 || i > (*L).length) {
  76. return ERROR;
  77. }
  78. // p为被删除元素的位置
  79. p = &(*L).elem[i - 1];
  80. // 1.获取被删除元素
  81. *e = *p;
  82. // 表尾元素位置
  83. q = (*L).elem + (*L).length - 1;
  84. // 2.左移元素,被删除元素的位置上会有新元素进来
  85. for(++p; p <= q; ++p) {
  86. *(p - 1) = *p;
  87. }
  88. // 3.表长减1
  89. (*L).length--;
  90. return OK;
  91. }
  • 链表的基本操作及应用(插入、删除、查找等) ```c /
    • 单链表结构 *
    • 注:这里的单链表存在头结点 / typedef struct LNode { ElemType data; // 数据结点 struct LNode next; // 指向下一个结点的指针 } LNode;

/*

  • ████████ 算法2.9 ████████ *
  • 插入 *
  • 向链表第i个位置上插入e,插入成功则返回OK,否则返回ERROR。 【备注】
  • 教材中i的含义是元素位置,从1开始计数 */ Status ListInsert(LinkList L, int i, ElemType e) { LinkList p, s; int j;

    // 确保链表存 if(L == NULL) {

    1. return ERROR;

    }

    p = L; j = 0;

    // 寻找第i-1个结点,且保证该结点本身不为NULL while(p != NULL && j < i - 1) {

    1. p = p->next;
    2. ++j;

    }

    // 如果遍历到头了,或者i的值不合规(比如i<=0),说明没找到合乎目标的结点 if(p == NULL || j > i - 1) {

    1. return ERROR;

    }

    // 生成新结点 s = (LinkList) malloc(sizeof(LNode)); if(s == NULL) {

    1. exit(OVERFLOW);

    } s->data = e; s->next = p->next; p->next = s;

    return OK; }

/*

  • ████████ 算法2.10 ████████ *
  • 删除 *
  • 删除链表第i个位置上的元素,并将被删除元素存储到e中。
  • 删除成功则返回OK,否则返回ERROR。 【备注】
  • 教材中i的含义是元素位置,从1开始计数 / Status ListDelete(LinkList L, int i, ElemType e) { LinkList p, q; int j;

    // 确保链表存在且不为空表 if(L == NULL || L->next == NULL) {

    1. return ERROR;

    }

    p = L; j = 0;

    // 寻找第i-1个结点,且保证该结点的后继不为NULL while(p->next != NULL && j < i - 1) {

    1. p = p->next;
    2. ++j;

    }

    // 如果遍历到头了,或者i的值不合规(比如i<=0),说明没找到合乎目标的结点 if(p->next == NULL || j > i - 1) {

    1. return ERROR;

    }

    // 删除第i个结点 q = p->next; p->next = q->next; *e = q->data; free(q);

    return OK; }

/*

  • 查找 *
  • 返回链表中首个与e满足Compare关系的元素位序。
  • 如果不存在这样的元素,则返回0。 【备注】
  • 元素e是Compare函数第二个形参 */ int LocateElem(LinkList L, ElemType e, Status(Compare)(ElemType, ElemType)) { int i; LinkList p;

    // 确保链表存在且不为空表 if(L == NULL || L->next == NULL) {

    1. return 0;

    }

    i = 1; // i的初值为第1个元素的位序 p = L->next; // p的初值为第1个元素的指针

    while(p != NULL && !Compare(p->data, e)) {

    1. i++;
    2. p = p->next;

    }

    if(p != NULL) {

    1. return i;

    } else {

    1. return 0;

    } }

  1. ```c
  2. /*
  3. * 静态链表结构
  4. *
  5. * 注:静态链表依托于一个数组,该数组包含了已占用空间和空闲空间
  6. */
  7. typedef struct SLinkNode {
  8. ElemType data;
  9. int cur; // cur是游标,做指针用,用来链接下一个结点(区别于数组下标)
  10. } SLinkList[MAXSIZE]; // 链表空间类型
  11. /*
  12. * 插入
  13. *
  14. * 向静态链表第i个位置上插入e,插入成功则返回OK,否则返回ERROR。
  15. *
  16. *【备注】
  17. * 教材中i的含义是元素位置,从1开始计数
  18. */
  19. Status ListInsert(SLinkList space, int S, int i, ElemType e) {
  20. int p, s;
  21. int j;
  22. // 确保静态链表存在
  23. if(S == 0) {
  24. return ERROR;
  25. }
  26. p = S;
  27. j = 0;
  28. // 寻找第i-1个结点,且保证该结点本身存在
  29. while(p != 0 && j < i - 1) {
  30. p = space[p].cur;
  31. ++j;
  32. }
  33. // 如果遍历到头了,或者i的值不合规(比如i<=0),说明没找到合乎目标的结点
  34. if(p == 0 || j > i - 1) {
  35. return ERROR;
  36. }
  37. // 生成新结点
  38. s = Malloc(space);
  39. space[s].data = e;
  40. space[s].cur = space[p].cur;
  41. space[p].cur = s;
  42. return OK;
  43. }
  44. /*
  45. * 删除
  46. *
  47. * 删除静态链表第i个位置上的元素,并将被删除元素存储到e中。
  48. * 删除成功则返回OK,否则返回ERROR。
  49. *
  50. *【备注】
  51. * 教材中i的含义是元素位置,从1开始计数
  52. */
  53. Status ListDelete(SLinkList space, int S, int i, ElemType* e) {
  54. int p, q;
  55. int j;
  56. // 确保静态链表存在
  57. if(S == 0) {
  58. return ERROR;
  59. }
  60. p = S;
  61. j = 0;
  62. // 寻找第i-1个结点,且保证该结点的后继存在
  63. while(space[p].cur != 0 && j < i - 1) {
  64. p = space[p].cur;
  65. ++j;
  66. }
  67. // 如果遍历到头了,或者i的值不合规(比如i<=0),说明没找到合乎目标的结点
  68. if(space[p].cur == 0 || j > i - 1) {
  69. return ERROR;
  70. }
  71. // 删除第i个结点
  72. q = space[p].cur;
  73. space[p].cur = space[q].cur;
  74. *e = space[q].data;
  75. Free(space, q);
  76. return OK;
  77. }
  78. /*
  79. * ████████ 算法2.13 ████████
  80. *
  81. * 查找
  82. *
  83. * 返回静态链表中首个与e满足Compare关系的元素位序。
  84. * 如果不存在这样的元素,则返回0。
  85. *
  86. *【备注】
  87. * 1.元素e是Compare函数第二个形参
  88. * 2.这里的实现与教材上的算法2.13不相同,原因参见顶部的“注意”信息
  89. */
  90. int LocateElem(SLinkList space, int S, ElemType e, Status(Compare)(ElemType, ElemType)) {
  91. int i;
  92. int p;
  93. // 确保静态链表存在且不为空
  94. if(S == 0 || space[S].cur == 0) {
  95. return 0;
  96. }
  97. i = 1; // i的初值为第1个元素的位序
  98. p = space[S].cur; // p的初值为第1个元素的索引
  99. while(p != 0 && !Compare(space[p].data, e)) {
  100. i++;
  101. p = space[p].cur;
  102. }
  103. if(p != 0) {
  104. return i;
  105. } else {
  106. return 0;
  107. }
  108. }
  1. /*
  2. * 双向循环链表结构
  3. *
  4. * 注:这里的双向循环链表存在头结点
  5. */
  6. typedef struct DuLNode {
  7. ElemType data;
  8. struct DuLNode* prior; // 前驱
  9. struct DuLNode* next; // 后继
  10. } DuLNode;
  11. /*
  12. * ████████ 算法2.18 ████████
  13. *
  14. * 插入
  15. *
  16. * 向双向循环链表第i个位置上插入e,插入成功则返回OK,否则返回ERROR。
  17. *
  18. *【备注】
  19. * 教材中i的含义是元素位置,从1开始计数
  20. */
  21. Status ListInsert(DuLinkList L, int i, ElemType e) {
  22. DuLinkList p, s;
  23. // 确保双向循环链表存在(但可能为空表)
  24. if(L == NULL) {
  25. return ERROR;
  26. }
  27. // 查找第i个结点位置(引用)
  28. if((p = GetElemP(L, i)) == NULL) {
  29. return ERROR;
  30. }
  31. // 创建新结点
  32. s = (DuLinkList) malloc(sizeof(DuLNode));
  33. if(s == NULL) {
  34. exit(OVERFLOW);
  35. }
  36. s->data = e;
  37. // 将s插入到p的前面,称为第i个结点
  38. s->prior = p->prior;
  39. p->prior->next = s;
  40. s->next = p;
  41. p->prior = s;
  42. return OK;
  43. }
  44. /*
  45. * ████████ 算法2.19 ████████
  46. *
  47. * 删除
  48. *
  49. * 删除双向循环链表第i个位置上的元素,并将被删除元素存储到e中。
  50. * 删除成功则返回OK,否则返回ERROR。
  51. *
  52. *【备注】
  53. * 教材中i的含义是元素位置,从1开始计数
  54. */
  55. Status ListDelete(DuLinkList L, int i, ElemType* e) {
  56. DuLinkList p;
  57. // 确保双向循环链表存在
  58. if(L == NULL || L->next == L || L->prior == L) {
  59. return ERROR;
  60. }
  61. // 查找第i个结点位置(引用)
  62. if((p = GetElemP(L, i)) == NULL) {
  63. return ERROR;
  64. }
  65. // 如果p==L,说明待删除元素是第len+1个元素,不合规
  66. if(p == L) {
  67. return ERROR;
  68. }
  69. *e = p->data;
  70. // 移除p结点
  71. p->prior->next = p->next;
  72. p->next->prior = p->prior;
  73. free(p);
  74. return OK;
  75. }
  76. /*
  77. * 查找
  78. *
  79. * 返回双向循环链表中首个与e满足Compare关系的元素位序。
  80. * 如果不存在这样的元素,则返回0。
  81. *
  82. *【备注】
  83. * 元素e是Compare函数第二个形参
  84. */
  85. int LocateElem(DuLinkList L, ElemType e, Status(Compare)(ElemType, ElemType)) {
  86. int i;
  87. DuLinkList p;
  88. // 确保双向循环链表存在
  89. if(L == NULL || L->next == L || L->prior == L) {
  90. return 0;
  91. }
  92. i = 1; // i的初值为第1个元素的位序
  93. p = L->next; // p的初值为第1个元素的指针
  94. while(p != L && !Compare(p->data, e)) {
  95. i++;
  96. p = p->next;
  97. }
  98. if(p != L) {
  99. return i;
  100. } else {
  101. return 0;
  102. }
  103. }

第三章 栈和队列

栈的定义及特点
栈(Stack):只允许在一端进行插入或删除操作的线性表。
特点:1.栈是受限的线性表,所以自然具有线性关系。2.栈中元素后进去的必然先出来,即后进先出LIFO(Last In First Out)

栈的表示及实现(进栈、出栈、为空或满的判定条件)

  1. typedef struct
  2. {
  3. SElemType data[MAXSIZE];
  4. int top; /* 用于栈顶指针 */
  5. }SqStack;
  6. /* 插入元素e为新的栈顶元素 */
  7. Status Push(SqStack *S,SElemType e)
  8. {
  9. if(S->top == MAXSIZE -1) /* 栈满 */
  10. {
  11. return ERROR;
  12. }
  13. S->top++; /* 栈顶指针增加一 */
  14. S->data[S->top]=e; /* 将新插入元素赋值给栈顶空间 */
  15. return OK;
  16. }
  17. /* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
  18. Status Pop(SqStack *S,SElemType *e)
  19. {
  20. if(S->top==-1)
  21. return ERROR;
  22. *e=S->data[S->top]; /* 将要删除的栈顶元素赋值给e */
  23. S->top--; /* 栈顶指针减一 */
  24. return OK;
  25. }

栈空:S->top==-1
栈满:S->top == MAXSIZE -1

  1. // 顺序栈元素结构
  2. typedef struct {
  3. SElemType* base; // 栈底指针
  4. SElemType* top; // 栈顶指针
  5. int stacksize; // 当前已分配的存储空间,以元素为单位
  6. } SqStack;
  7. /*
  8. * 入栈
  9. *
  10. * 将元素e压入到栈顶。
  11. */
  12. Status Push(SqStack* S, SElemType e) {
  13. if(S == NULL || (*S).base == NULL) {
  14. return ERROR;
  15. }
  16. // 栈满时,追加存储空间
  17. if((*S).top - (*S).base >= (*S).stacksize) {
  18. (*S).base = (SElemType*) realloc((*S).base, ((*S).stacksize + STACKINCREMENT) * sizeof(SElemType));
  19. if((*S).base == NULL) {
  20. exit(OVERFLOW); // 存储分配失败
  21. }
  22. (*S).top = (*S).base + (*S).stacksize;
  23. (*S).stacksize += STACKINCREMENT;
  24. }
  25. // 进栈先赋值,栈顶指针再自增
  26. *(S->top++) = e;
  27. return OK;
  28. }
  29. /*
  30. * 出栈
  31. *
  32. * 将栈顶元素弹出,并用e接收。
  33. */
  34. Status Pop(SqStack* S, SElemType* e) {
  35. if(S == NULL || (*S).base == NULL) {
  36. return ERROR;
  37. }
  38. if((*S).top == (*S).base) {
  39. return ERROR;
  40. }
  41. // 出栈栈顶指针先递减,再赋值
  42. *e = *(--(*S).top);
  43. return OK;
  44. }

栈空:(*S).top == (*S).base
栈满:(*S).top - (*S).base >= (*S).stacksize

队列的定义及特点
队列是只允许在一端进行插入,而在另一端进行删除的线性表
先进入队列的元素必然先离开队列,即先进先出(First In First Out)简称FIFO

循环队列(入列、出列、元素个数)

  1. // 队列元素结构
  2. typedef struct QNode {
  3. QElemType data;
  4. struct QNode* next;
  5. } QNode, * QueuePtr;
  6. // 队列结构
  7. typedef struct {
  8. QueuePtr front; // 队头指针
  9. QueuePtr rear; // 队尾指针
  10. } LinkQueue; // 队列的链式存储表示
  11. /*
  12. * 入队
  13. *
  14. * 将元素e添加到队列尾部。
  15. */
  16. Status EnQueue(LinkQueue* Q, QElemType e) {
  17. QueuePtr p;
  18. if(Q == NULL || (*Q).front == NULL) {
  19. return ERROR;
  20. }
  21. p = (QueuePtr) malloc(sizeof(QNode));
  22. if(!p) {
  23. exit(OVERFLOW);
  24. }
  25. p->data = e;
  26. p->next = NULL;
  27. (*Q).rear->next = p;
  28. (*Q).rear = p;
  29. return OK;
  30. }
  31. /*
  32. * 出队
  33. *
  34. * 移除队列头部的元素,将其存储到e中。
  35. */
  36. Status DeQueue(LinkQueue* Q, QElemType* e) {
  37. QueuePtr p;
  38. if(Q == NULL || (*Q).front == NULL || (*Q).front == (*Q).rear) {
  39. return ERROR;
  40. }
  41. p = (*Q).front->next;
  42. *e = p->data;
  43. (*Q).front->next = p->next;
  44. if((*Q).rear == p) {
  45. (*Q).rear = (*Q).front;
  46. }
  47. free(p);
  48. return OK;
  49. }
  50. /*
  51. * 计数
  52. *
  53. * 返回链队包含的有效元素的数量。
  54. */
  55. int QueueLength(LinkQueue Q) {
  56. int count = 0;
  57. QueuePtr p = Q.front;
  58. while(p != Q.rear) {
  59. count++;
  60. p = p->next;
  61. }
  62. return count;
  63. }
  1. // 循环队列的顺序存储结构
  2. typedef struct {
  3. QElemType* base; // 动态分配存储空间
  4. int front; // 头指针,若队列不空,指向队头元素
  5. int rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
  6. } SqQueue;
  7. /*
  8. * 入队
  9. *
  10. * 将元素e添加到队列尾部。
  11. */
  12. Status EnQueue(SqQueue* Q, QElemType e) {
  13. if(Q == NULL || (*Q).base == NULL) {
  14. return ERROR;
  15. }
  16. // 队列满的标志(会浪费一个空间来区分队列空和队列满)
  17. if(((*Q).rear + 1) % MAXQSIZE == (*Q).front) {
  18. return ERROR;
  19. }
  20. // 入队
  21. (*Q).base[(*Q).rear] = e;
  22. // 尾指针前进
  23. (*Q).rear = ((*Q).rear + 1) % MAXQSIZE;
  24. return OK;
  25. }
  26. /*
  27. * 出队
  28. *
  29. * 移除队列头部的元素,将其存储到e中。
  30. */
  31. Status DeQueue(SqQueue* Q, QElemType* e) {
  32. if(Q == NULL || (*Q).base == NULL) {
  33. return ERROR;
  34. }
  35. // 队列空的标志
  36. if((*Q).front == (*Q).rear) {
  37. return ERROR;
  38. }
  39. // 出队
  40. *e = (*Q).base[(*Q).front];
  41. // 头指针前进
  42. (*Q).front = ((*Q).front + 1) % MAXQSIZE;
  43. return OK;
  44. }
  45. /*
  46. * 计数
  47. *
  48. * 返回循环顺序队列包含的有效元素的数量。
  49. */
  50. int QueueLength(SqQueue Q) {
  51. if(Q.base == NULL) {
  52. return 0;
  53. }
  54. // 队列长度
  55. return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
  56. }

第四章 串

串的定义及基本操作
由零个或多个字符组成的有限序列。

  1. typedef unsigned char SString[MAXSTRLEN + 1]; // 0号单元存放串的长度
  2. /*
  3. * ████████ 算法4.3 ████████
  4. *
  5. * 求子串
  6. *
  7. * 用Sub返回S[pos, pos+len-1]。
  8. * 返回值指示是否截取成功。
  9. *
  10. *【注】
  11. * 该操作属于最小操作子集
  12. */
  13. Status SubString(SString Sub, SString S, int pos, int len) {
  14. int i;
  15. if(pos < 1 || pos > S[0] || len < 0 || pos + len - 1 > S[0]) {
  16. return ERROR;
  17. }
  18. // 复制元素
  19. for(i = 1; i <= len; i++) {
  20. Sub[i] = S[pos + i - 1];
  21. }
  22. // 确定新长度
  23. Sub[0] = len;
  24. return OK;
  25. }
  26. /*
  27. * ████████ 算法4.1 ████████
  28. *
  29. * 查找
  30. *
  31. * 从pos处开始搜索模式串T在主串S中首次出现的位置,如果不存在,则返回0。
  32. * 如果查找成功,返回匹配的位置。
  33. *
  34. *【注】
  35. * 1.此实现需要依赖串的最小操作子集
  36. * 2.该实现比较低效
  37. */
  38. int Index_1(SString S, SString T, int pos) {
  39. int i, n, m; // 记录S和T的长度
  40. SString sub;
  41. /*
  42. * 失败情形提前处理
  43. * 这里与教材写法略微不同
  44. */
  45. if(pos < 1 || pos > S[0] || StrEmpty(T)) {
  46. return 0;
  47. }
  48. n = StrLength(S);
  49. m = StrLength(T);
  50. i = pos;
  51. // 保证长度不越界
  52. while(i <= n - m + 1) {
  53. // 获取S[i, i+m-1]
  54. SubString(sub, S, i, m);
  55. // 如果子串与模式串不匹配,则需要继续推进
  56. if(StrCompare(sub, T) != 0) {
  57. ++i;
  58. } else {
  59. return i;
  60. }
  61. }
  62. return 0;
  63. }
  64. /*
  65. * ████████ 算法4.5 ████████
  66. *
  67. * 查找
  68. *
  69. * 从pos处开始搜索模式串T在主串S中首次出现的位置,如果不存在,则返回0。
  70. * 如果查找成功,返回匹配的位置。
  71. *
  72. *【注】
  73. * 1.此实现不依赖串的最小操作子集
  74. * 2.该实现比较低效
  75. */
  76. int Index_2(SString S, SString T, int pos) {
  77. int i = pos;
  78. int j = 1;
  79. if(pos < 1 || pos > S[0] || StrEmpty(T)) {
  80. return 0;
  81. }
  82. while(i <= S[0] && j <= T[0]) {
  83. // 遇到相同字符,则继续比较后继字符
  84. if(S[i] == T[j]) {
  85. i++;
  86. j++;
  87. // 遇到不同的字符,则游标需要回退,重新比较
  88. } else {
  89. i = i - (j - 1) + 1; // j-1代表徒劳地前进了j-1个元素,在第j个元素上功亏一篑
  90. j = 1; // 游标j回到串T的第一个位置
  91. }
  92. }
  93. // 增加了一个T[0]>0的判断
  94. if(j > T[0] && T[0] > 0) { // T不为空串
  95. return i - T[0]; // 匹配成功
  96. } else {
  97. return 0;
  98. }
  99. }
  100. /*
  101. * 插入
  102. *
  103. * 将串T插入到主串S的pos位置处。
  104. */
  105. Status StrInsert(SString S, int pos, SString T) {
  106. int i;
  107. if(pos < 1 || pos > S[0] + 1 || S[0] + T[0] > MAXSTRLEN) {
  108. return ERROR;
  109. }
  110. // 如果待插入的串为空,则提前返回
  111. if(StrEmpty(T)) {
  112. return OK;
  113. }
  114. // 在S中腾出位置,为插入T做准备
  115. for(i = S[0]; i >= pos; i--) {
  116. // 从后向前遍历,将前面的元素挪到后面
  117. S[i + T[0]] = S[i];
  118. }
  119. // 将串T插入在S中腾出的位置上
  120. for(i = pos; i <= pos + T[0] - 1; i++) {
  121. S[i] = T[i - pos + 1];
  122. }
  123. // 长度增加
  124. S[0] += T[0];
  125. return OK;
  126. }
  127. /*
  128. * 删除
  129. *
  130. * 删除S[pos, pos+len-1]。
  131. */
  132. Status StrDelete(SString S, int pos, int len) {
  133. int i;
  134. if(pos < 1 || pos + len - 1 > S[0] || len < 0) {
  135. return ERROR;
  136. }
  137. // 如果待删除的长度为0,则提前返回
  138. if(len == 0) {
  139. return OK;
  140. }
  141. // 把后面的元素挪到前面,覆盖掉被删除的元素
  142. for(i = pos + len; i <= S[0]; i++) {
  143. S[i - len] = S[i];
  144. }
  145. // 长度减少
  146. S[0] -= len;
  147. return OK;
  148. }
  149. /*
  150. * 比较
  151. *
  152. * 比较串S和串T,返回比较结果。
  153. *
  154. *【注】
  155. * 该操作属于最小操作子集
  156. */
  157. int StrCompare(SString S, SString T) {
  158. int i = 1;
  159. while(i <= S[0] && i <= T[0]) {
  160. // 遇到不同的字符时,比较其大小
  161. if(S[i] != T[i]) {
  162. return S[i] - T[i];
  163. }
  164. i++;
  165. }
  166. return S[0] - T[0];
  167. }
  168. /*
  169. * 复制
  170. *
  171. * 将串S复制到串T。
  172. */
  173. Status StrCopy(SString T, SString S) {
  174. int i;
  175. // 连同长度信息一起复制
  176. for(i = 0; i <= S[0]; i++) {
  177. T[i] = S[i];
  178. }
  179. return OK;
  180. }
  181. /*
  182. * 替换
  183. *
  184. * 用V替换主串S中出现的所有与T相等的且不重叠的子串。
  185. *
  186. *【注】
  187. * 1.该操作依赖最小操作子集
  188. * 2.该实现比较低效
  189. */
  190. Status Replace(SString S, SString T, SString V) {
  191. int i;
  192. if(StrEmpty(S) || StrEmpty(T)) {
  193. return ERROR;
  194. }
  195. // 在主串S中寻找模式串T第一次出现的位置
  196. i = Index_2(S, T, 1);
  197. // 如果存在匹配的字符串,且可以被完全替换(替换后不溢出)
  198. while(i != 0 && S[0] - T[0] + V[0] <= MAXSTRLEN) {
  199. StrDelete(S, i, StrLength(T)); // 从S中删除T
  200. StrInsert(S, i, V); // 向S中插入V
  201. i += StrLength(V); // i切换到下一个位置
  202. i = Index_2(S, T, i); // 查找下一个匹配的字符串
  203. }
  204. if(i == 0) { // S中的T已全部被替换
  205. return OK;
  206. } else { // S中尚有T,但是V已经插不进去了
  207. return ERROR;
  208. }
  209. }
  210. /*
  211. * ████████ 算法4.2 ████████
  212. *
  213. * 串联接
  214. *
  215. * 联接S1和S2,并存储到T中返回。如果联接后的长度溢出,则只保留未溢出的部分。
  216. * 返回值表示联接后的串是否完整。
  217. *
  218. *【注】
  219. * 该操作属于最小操作子集
  220. */
  221. Status Concat(SString T, SString S1, SString S2) {
  222. int i;
  223. int uncut; // 新串是否完整
  224. // 完全不需要裁剪
  225. if(S1[0] + S2[0] <= MAXSTRLEN) {
  226. // 复制S1到T中
  227. for(i = 1; i <= S1[0]; i++) {
  228. T[i] = S1[i];
  229. }
  230. // 复制S2到T中
  231. for(i = S1[0] + 1; i <= S1[0] + S2[0]; i++) {
  232. T[i] = S2[i - S1[0]];
  233. }
  234. // 设置新长度
  235. T[0] = S1[0] + S2[0];
  236. // 未裁剪,完整
  237. uncut = TRUE;
  238. // 需要裁剪S2
  239. } else if(S1[0] <= MAXSTRLEN) {
  240. // 复制S1到T中
  241. for(i = 1; i <= S1[0]; i++) {
  242. T[i] = S1[i];
  243. }
  244. // 将S2的一部分复制到T中
  245. for(i = S1[0] + 1; i <= MAXSTRLEN; i++) {
  246. T[i] = S2[i - S1[0]];
  247. }
  248. // 设置新长度
  249. T[0] = MAXSTRLEN;
  250. uncut = FALSE;
  251. // 只需要复制S1的一部分
  252. } else {
  253. // 连同长度信息一起复制
  254. for(i = 0; i <= MAXSTRLEN; i++) {
  255. T[i] = S1[i];
  256. }
  257. uncut = FALSE;
  258. }
  259. return uncut;
  260. }
  1. /*
  2. * 串的堆存储表示
  3. *
  4. * 注:有效元素从ch的0号单元开始存储
  5. */
  6. typedef struct {
  7. char* ch; // 若是非空串,则按串长分配存储区,否则ch为NULL
  8. int length;
  9. } HString;
  10. /*
  11. * 求子串
  12. *
  13. * 用Sub返回S[pos, pos+len-1]。
  14. * 返回值指示是否截取成功。
  15. *
  16. *【注】
  17. * 该操作属于最小操作子集
  18. */
  19. Status SubString(HString* Sub, HString S, int pos, int len) {
  20. int i;
  21. if(pos < 1 || pos > S.length || len < 0 || pos + len - 1 > S.length) {
  22. return ERROR;
  23. }
  24. // 如果是截取0个字符,不需要分配空间
  25. if(len == 0) {
  26. (*Sub).ch = NULL;
  27. (*Sub).length = 0;
  28. return OK;
  29. }
  30. (*Sub).ch = (char*) malloc(len * sizeof(char));
  31. if(!(*Sub).ch) {
  32. exit(OVERFLOW);
  33. }
  34. // 复制元素
  35. for(i = 0; i < len; i++) {
  36. (*Sub).ch[i] = S.ch[i + pos - 1];
  37. }
  38. // 确定新长度
  39. (*Sub).length = len;
  40. return OK;
  41. }
  42. /*
  43. * 查找
  44. *
  45. * 从pos处开始搜索模式串T在主串S中首次出现的位置,如果不存在,则返回0。
  46. * 如果查找成功,返回匹配的位置。
  47. *
  48. *【注】
  49. * 1.此实现需要依赖串的最小操作子集
  50. * 2.该实现比较低效
  51. */
  52. int Index(HString S, HString T, int pos) {
  53. int i, s, t;
  54. HString sub;
  55. if(pos < 1 || pos > S.length || StrEmpty(T)) {
  56. return 0;
  57. }
  58. s = S.length;
  59. t = T.length;
  60. i = pos;
  61. // 保证长度不越界
  62. while(i + t - 1 <= s) {
  63. // 获取S[i, i+t-1]
  64. SubString(&sub, S, i, t);
  65. // 如果子串与模式串不匹配,则需要继续推进
  66. if(StrCompare(sub, T) != 0) {
  67. ++i;
  68. } else {
  69. return i;
  70. }
  71. }
  72. return 0;
  73. }
  74. /*
  75. * ████████ 算法4.4 ████████
  76. *
  77. * 插入
  78. *
  79. * 将串T插入到主串S的pos位置处。
  80. */
  81. Status StrInsert(HString* S, int pos, HString T) {
  82. int i;
  83. if(pos < 1 || pos > (*S).length + 1) {
  84. return ERROR;
  85. }
  86. // 如果待插入的串为空,则提前返回
  87. if(StrEmpty(T)) {
  88. return OK;
  89. }
  90. // 分配新空间,会将旧元素一起复制过去
  91. (*S).ch = (char*) realloc((*S).ch, ((*S).length + T.length) * sizeof(char));
  92. if(!(*S).ch) {
  93. exit(OVERFLOW);
  94. }
  95. // 在S中腾出位置,为插入T做准备
  96. for(i = (*S).length - 1; i >= pos - 1; i--) {
  97. // 从后向前遍历,将前面的元素挪到后面
  98. (*S).ch[i + T.length] = (*S).ch[i];
  99. }
  100. // 将串T插入在S中腾出的位置上
  101. for(i = pos - 1; i <= pos + T.length - 2; i++) {
  102. (*S).ch[i] = T.ch[i - pos + 1];
  103. }
  104. // 长度增加
  105. (*S).length += T.length;
  106. return OK;
  107. }
  108. /*
  109. * 删除
  110. *
  111. * 删除S[pos, pos+len-1]。
  112. */
  113. Status StrDelete(HString* S, int pos, int len) {
  114. int i;
  115. if(pos < 1 || pos + len - 1 > (*S).length || len < 0) {
  116. return ERROR;
  117. }
  118. // 如果待删除的长度为0,则提前返回
  119. if(len == 0) {
  120. return OK;
  121. }
  122. // 把后面的元素挪到前面,覆盖掉被删除的元素
  123. for(i = pos + len - 1; i <= (*S).length - 1; i++) {
  124. (*S).ch[i - len] = (*S).ch[i];
  125. }
  126. // 长度减少
  127. (*S).length -= len;
  128. // 缩减分配的空间(如果长度减少为0,这里会返回空指针)
  129. (*S).ch = (char*) realloc((*S).ch, (*S).length * sizeof(char));
  130. return OK;
  131. }
  132. /*
  133. * 比较
  134. *
  135. * 比较串S和串T,返回比较结果。
  136. *
  137. *【注】
  138. * 该操作属于最小操作子集
  139. */
  140. Status StrCompare(HString S, HString T) {
  141. int i;
  142. for(i = 0; i < S.length && i < T.length; i++) {
  143. // 遇到不同的字符时,比较其大小
  144. if(S.ch[i] != T.ch[i]) {
  145. return S.ch[i] - T.ch[i];
  146. }
  147. }
  148. return S.length - T.length;
  149. }
  150. /*
  151. * 复制
  152. *
  153. * 将串S复制到串T。
  154. */
  155. Status StrCopy(HString* T, HString S) {
  156. int i;
  157. if(StrEmpty(S)) {
  158. (*T).ch = NULL;
  159. (*T).length = 0;
  160. } else {
  161. // 分配空间
  162. (*T).ch = (char*) malloc(S.length * sizeof(char));
  163. if(!(*T).ch) {
  164. exit(OVERFLOW);
  165. }
  166. // 复制元素
  167. for(i = 0; i < S.length; i++) {
  168. (*T).ch[i] = S.ch[i];
  169. }
  170. // 复制长度信息
  171. (*T).length = S.length;
  172. }
  173. return OK;
  174. }
  175. /*
  176. * 替换
  177. *
  178. * 用V替换主串S中出现的所有与T相等的且不重叠的子串。
  179. *
  180. *【注】
  181. * 1.该操作依赖最小操作子集
  182. * 2.该实现比较低效
  183. */
  184. Status Replace(HString* S, HString T, HString V) {
  185. int i;
  186. if(StrEmpty(*S) || StrEmpty(T)) {
  187. return ERROR;
  188. }
  189. // 在主串S中寻找模式串T第一次出现的位置
  190. i = Index(*S, T, 1);
  191. // 如果存在匹配的字符串
  192. while(i != 0) {
  193. StrDelete(S, i, StrLength(T)); // 从S中删除T
  194. StrInsert(S, i, V); // 向S中插入V
  195. i += StrLength(V); // i切换到下一个位置
  196. i = Index(*S, T, i); // 查找下一个匹配的字符串
  197. }
  198. return OK;
  199. }
  200. /*
  201. * 串联接
  202. *
  203. * 联接S1和S2,并存储到T中返回。如果联接后的长度溢出,则只保留未溢出的部分。
  204. * 返回值表示联接后的串是否完整。
  205. * 堆串的空间被认为是无限的,因此这里总是返回TRUE,指示串不会被裁剪。
  206. *
  207. *【注】
  208. * 该操作属于最小操作子集
  209. */
  210. Status Concat(HString* T, HString S1, HString S2) {
  211. int i;
  212. // 确定新长度
  213. (*T).length = S1.length + S2.length;
  214. // 分配空间
  215. (*T).ch = (char*) malloc((*T).length * sizeof(char));
  216. if(!(*T).ch) {
  217. exit(OVERFLOW);
  218. }
  219. // 先把S1的内容拷贝出来
  220. for(i = 0; i < S1.length; i++) {
  221. (*T).ch[i] = S1.ch[i];
  222. }
  223. // 再拷贝S2的内容
  224. for(i = 0; i < S2.length; i++) {
  225. (*T).ch[S1.length + i] = S2.ch[i];
  226. }
  227. return TRUE;
  228. }

串的模式匹配(next[j]值求解)
复习提要(20级) - 图3:其值 = 第复习提要(20级) - 图4位字符前面复习提要(20级) - 图5位字符组成的子串的前后缀重合字符数+1

  1. #include "SString.h"
  2. /*
  3. * ████████ 算法4.6 ████████
  4. *
  5. * 查找
  6. *
  7. * 从pos处开始搜索模式串T在主串S中首次出现的位置,如果不存在,则返回0。
  8. * 如果查找成功,返回匹配的位置。
  9. *
  10. *【注】
  11. * 1.该实现用到了KMP算法,是一种比较高效的字符串匹配方式
  12. * 2.教材中没有next参数
  13. */
  14. int Index_KMP(SString S, SString T, int pos, int next[]) {
  15. int i = pos;
  16. int j = 1;
  17. if(pos < 1) {
  18. return 0;
  19. }
  20. // 比较字符串
  21. while(i <= S[0] && j <= T[0]) {
  22. /*
  23. * 两种情形:
  24. * 1.在模式串的第一个字符处就失配
  25. * 2.主串和模式串处的字符相等
  26. */
  27. if(j == 0 || S[i] == T[j]) {
  28. i++;
  29. j++;
  30. } else {
  31. // 失配时回到前一个适当的位置
  32. j = next[j];
  33. }
  34. }
  35. if(j > T[0]) {
  36. // 匹配成功,返回匹配位置
  37. return i - T[0];
  38. } else {
  39. // 匹配失败
  40. return 0;
  41. }
  42. }
  43. /*
  44. * ████████ 算法4.7 ████████
  45. *
  46. * 计算模式串的“失配数组”,用于KMP算法。
  47. */
  48. void get_next(SString T, int next[]) {
  49. int i = 1;
  50. int j = 0;
  51. // 模式串第一个字符处失配时,模式串需要从头比较,主串需要前进到下一个位置比较
  52. next[1] = 0;
  53. // 遍历模式串上的字符
  54. while(i < T[0]) {
  55. if(j == 0 || T[i] == T[j]) {
  56. i++;
  57. j++;
  58. next[i] = j;
  59. } else {
  60. j = next[j];
  61. }
  62. }
  63. }
  64. /*
  65. * ████████ 算法4.8 ████████
  66. *
  67. * 计算模式串的“失配数组”,用于KMP算法。
  68. * 这是一个优化后的版本,效率较算法4.7有所提高。
  69. */
  70. void get_nextval(SString T, int nextval[]) {
  71. int i = 1;
  72. int j = 0;
  73. // 模式串第一个字符处失配时,模式串需要从头比较,主串需要前进到下一个位置比较
  74. nextval[1] = 0;
  75. // 遍历模式串上的字符
  76. while(i < T[0]) {
  77. if(j==0 || T[i] == T[j]) {
  78. i++;
  79. j++;
  80. if(T[i] != T[j]) {
  81. nextval[i] = j;
  82. } else {
  83. nextval[i] = nextval[j];
  84. }
  85. } else {
  86. j = nextval[j];
  87. }
  88. }
  89. }

KMP

第五章 数组

数组元素地址的计算
1)按行序存储

如果按行序存储,怎么找到复习提要(20级) - 图6的存储位置呢?

先看看存储复习提要(20级) - 图7之前,前面已经存储了多少个元素,如图所示。
image.png
从图可以看出,在复习提要(20级) - 图9之前一共有i×n+j个元素,如果每个元素占用L字节,那么共需要(i×n+j)×L字节,只需要用基地址加上这些字节就可以得到复习提要(20级) - 图10的存储地址了。按行序存储,复习提要(20级) - 图11的存储地址为:
image.png
复习提要(20级) - 图13表示第一个元素的存储地址,即基地址,复习提要(20级) - 图14表示复习提要(20级) - 图15的存储地址。

2)按列序存储

如果按列序存储,怎么找到复习提要(20级) - 图16的存储位置呢?

先看看存储复习提要(20级) - 图17之前,前面已经存储了多少个元素,如图所示。
image.png
从图可以看出,在复习提要(20级) - 图19之前一共有j×m+i个元素,如果每个元素占用L字节,那么共需要(j×m+i)×L字节,只需要用基地址加上这些字节就可以得到复习提要(20级) - 图20的存储地址了。按列序存储,复习提要(20级) - 图21的存储地址为:
image.png
复习提要(20级) - 图23表示第一个元素的存储地址,即基地址,复习提要(20级) - 图24表示aij的存储地址。

注意:如果二维数组的下标是从1开始的,那么情形就变了。 先看看存储复习提要(20级) - 图25之前,前面已经存储了多少个元素,如图所示。 image.png 从图可以看出,行数和个数都少1,在aij之前一共有(i−1)×n+j−1个元素,如果每个元素占用L字节,那么共需要((i−1)×n+j−1)×L字节,只需要用基地址加上这些字节就可以得到复习提要(20级) - 图27的存储地址了。如果二维数组下标从1开始,按行序存储,复习提要(20级) - 图28的存储地址为: image.png

存储地址计算秘籍:
复习提要(20级) - 图30的存储地址等于第一个元素的存储地址,加上前面的元素个数乘以每个元素占用的字节数。计算公式为:
image.png

稀疏矩阵的压缩存储

  1. /* 三元组类型定义,主要用来存储非零元 */
  2. typedef struct {
  3. int i, j; // 该三元组非零元的行下标和列下标
  4. ElemType e;
  5. } Triple;
  6. /* 三元组稀疏矩阵类型定义 */
  7. typedef struct {
  8. Triple data[MAXSIZE + 1]; // 非零元三元组表,data[0]未用
  9. int mu, nu, tu; // 矩阵的行数、列数和非零元个数
  10. } TSMatrix;
  11. /*
  12. * 创建稀疏矩阵M
  13. *
  14. *
  15. *【备注】
  16. *
  17. * 教材中默认从控制台读取数据。
  18. * 这里为了方便测试,避免每次运行都手动输入数据,
  19. * 因而允许选择从预设的文件path中读取测试数据。
  20. *
  21. * 如果需要从控制台读取数据,则path为NULL或者为空串,
  22. * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
  23. */
  24. Status CreateSMatrix(TSMatrix* M, char* path) {
  25. int k;
  26. FILE* fp;
  27. int readFromConsole; // 是否从控制台读取数据
  28. // 如果没有文件路径信息,则从控制台读取输入
  29. readFromConsole = path == NULL || strcmp(path, "") == 0;
  30. // 如果没有文件路径信息,则从控制台读取输入
  31. if(readFromConsole) {
  32. printf("请输入行数:");
  33. scanf("%d", &((*M).mu));
  34. printf("请输入列数:");
  35. scanf("%d", &((*M).nu));
  36. printf("请输入非零元素个数:");
  37. scanf("%d", &((*M).tu));
  38. printf("请输入%d个三元组信息\n", (*M).tu);
  39. for(k = 1; k <= (*M).tu; k++) {
  40. printf("第%2d组:", k);
  41. scanf("%d%d%d", &((*M).data[k].i), &((*M).data[k].j), &((*M).data[k].e));
  42. }
  43. } else {
  44. fp = fopen(path, "r");
  45. ReadData(fp, "%d%d%d", &((*M).mu), &((*M).nu), &((*M).tu));
  46. for(k = 1; k <= (*M).tu; k++) {
  47. ReadData(fp, "%d%d%d", &((*M).data[k].i), &((*M).data[k].j), &((*M).data[k].e));
  48. }
  49. fclose(fp);
  50. }
  51. return OK;
  52. }
  53. /*
  54. * 矩阵复制
  55. *
  56. * 创建一个新矩阵T,该矩阵包含了从矩阵M中包含的数据。
  57. */
  58. Status CopySMatrix(TSMatrix M, TSMatrix* T) {
  59. (*T) = M; // 结构体之间可以直接复制,即使内部包含数组也可以
  60. return OK;
  61. }
  62. /*
  63. * 矩阵加法
  64. *
  65. * Q = M + N。
  66. */
  67. Status AddSMatrix(TSMatrix M, TSMatrix N, TSMatrix* Q) {
  68. int m, n, k;
  69. if(M.mu != N.mu || M.nu != N.nu) {
  70. printf("两矩阵的行数、列数不满足相加条件!!\n");
  71. return ERROR;
  72. }
  73. // 初始化Q
  74. (*Q).mu = M.mu;
  75. (*Q).nu = M.nu;
  76. (*Q).tu = 0;
  77. m = n = k = 1;
  78. // 依次遍历M与N的三元组
  79. while(m <= M.tu && n <= N.tu) {
  80. // M中的三元组行下标较小
  81. if(M.data[m].i < N.data[n].i) {
  82. (*Q).data[k] = M.data[m];
  83. m++;
  84. // N中的三元组行下标较小
  85. } else if(M.data[m].i > N.data[n].i) {
  86. (*Q).data[k] = N.data[n];
  87. n++;
  88. // M与N中的三元组行下标一致,需要进一步比较列坐标
  89. } else {
  90. // M中的三元组列下标较小
  91. if(M.data[m].j < N.data[n].j) {
  92. (*Q).data[k] = M.data[m];
  93. m++;
  94. // N中的三元组列下标较小
  95. } else if(M.data[m].j > N.data[n].j) {
  96. (*Q).data[k] = N.data[n];
  97. n++;
  98. // M与N中的三元组列下标一致,需要进行加法运算
  99. } else {
  100. // 值已经加为0的话,不需要存储该元素
  101. if((M.data[m].e + N.data[n].e) == 0) {
  102. m++;
  103. n++;
  104. continue;
  105. } else {
  106. (*Q).data[k].i = M.data[m].i;
  107. (*Q).data[k].j = M.data[m].j;
  108. (*Q).data[k].e = M.data[m].e + N.data[n].e;
  109. m++;
  110. n++;
  111. }
  112. }
  113. }
  114. k++;
  115. (*Q).tu++;
  116. }
  117. // 遍历M中剩余的三元组
  118. while(m <= M.tu) {
  119. (*Q).data[k] = M.data[m];
  120. m++;
  121. k++;
  122. (*Q).tu++;
  123. }
  124. // 遍历N中剩余的三元组
  125. while(n <= N.tu) {
  126. (*Q).data[k] = N.data[n];
  127. n++;
  128. k++;
  129. (*Q).tu++;
  130. }
  131. return OK;
  132. }
  133. /*
  134. * 矩阵减法
  135. *
  136. * Q = M - N。
  137. */
  138. Status SubSMatrix(TSMatrix M, TSMatrix N, TSMatrix* Q) {
  139. int m, n, k;
  140. if(M.mu != N.mu || M.nu != N.nu) {
  141. printf("两矩阵的行数、列数不满足相减条件!!\n");
  142. return ERROR;
  143. }
  144. // 初始化Q
  145. (*Q).mu = M.mu;
  146. (*Q).nu = M.nu;
  147. (*Q).tu = 0;
  148. m = n = k = 1;
  149. // 依次遍历M与N的三元组
  150. while(m <= M.tu && n <= N.tu) {
  151. // M中的三元组行下标较小
  152. if(M.data[m].i < N.data[n].i) {
  153. (*Q).data[k] = M.data[m];
  154. m++;
  155. // N中的三元组行下标较小
  156. } else if(M.data[m].i > N.data[n].i) {
  157. (*Q).data[k].i = N.data[n].i;
  158. (*Q).data[k].j = N.data[n].j;
  159. (*Q).data[k].e = -N.data[n].e; // 由于是相减,所以要对元素值取相反数
  160. n++;
  161. // M与N中的三元组行下标一致,需要进一步比较列坐标
  162. } else {
  163. // M中的三元组列下标较小
  164. if(M.data[m].j < N.data[n].j) {
  165. (*Q).data[k] = M.data[m];
  166. m++;
  167. // N中的三元组列下标较小
  168. } else if(M.data[m].j > N.data[n].j) {
  169. (*Q).data[k].i = N.data[n].i;
  170. (*Q).data[k].j = N.data[n].j;
  171. (*Q).data[k].e = -N.data[n].e; // 由于是相减,所以要对元素值取相反数
  172. n++;
  173. // M与N中的三元组列下标一致,需要进行减法运算
  174. } else {
  175. // 值已经减为0的话,不需要存储该元素
  176. if((M.data[m].e - N.data[n].e) == 0) {
  177. m++;
  178. n++;
  179. continue;
  180. } else {
  181. (*Q).data[k].i = M.data[m].i;
  182. (*Q).data[k].j = M.data[m].j;
  183. (*Q).data[k].e = M.data[m].e - N.data[n].e;
  184. m++;
  185. n++;
  186. }
  187. }
  188. }
  189. k++;
  190. (*Q).tu++;
  191. }
  192. // 遍历M中剩余的三元组
  193. while(m <= M.tu) {
  194. (*Q).data[k] = M.data[m];
  195. m++;
  196. k++;
  197. (*Q).tu++;
  198. }
  199. // 遍历N中剩余的三元组
  200. while(n <= N.tu) {
  201. (*Q).data[k].i = N.data[n].i;
  202. (*Q).data[k].j = N.data[n].j;
  203. (*Q).data[k].e = -N.data[n].e;
  204. n++;
  205. k++;
  206. (*Q).tu++;
  207. }
  208. return OK;
  209. }
  210. /*
  211. * 矩阵乘法
  212. *
  213. * Q = M * N,这里实现的是传统矩阵乘法。
  214. */
  215. Status MultSMatrix(TSMatrix M, TSMatrix N, TSMatrix* Q) {
  216. int m, n, i, j, k;
  217. ElemType c, c1, c2;
  218. // M的列数需要等于N的行数
  219. if(M.nu != N.mu) {
  220. printf("两矩阵的行数、列数不满足相乘条件!!\n");
  221. return ERROR;
  222. }
  223. // 初始化Q
  224. (*Q).mu = M.mu;
  225. (*Q).nu = N.nu;
  226. (*Q).tu = 0;
  227. // 如果存在零矩阵
  228. if(M.tu * N.tu == 0) {
  229. return OK;
  230. }
  231. // 遍历矩阵M的行
  232. for(i = 1; i <= M.mu; i++) {
  233. // 遍历矩阵N的列
  234. for(j = 1; j <= N.nu; j++) {
  235. c = 0;
  236. for(k = 1; k <= M.nu; k++) {
  237. // 记录M[i][k]的值
  238. c1 = 0;
  239. // 依次寻找位于指定位置的M三元组
  240. for(m = 1; m <= M.tu; m++) {
  241. if(M.data[m].i == i && M.data[m].j == k) {
  242. c1 = M.data[m].e;
  243. break;
  244. }
  245. }
  246. // 记录N[k][j]的值
  247. c2 = 0;
  248. //依次寻找位于指定位置的N三元组
  249. for(n = 1; n <= N.tu; n++) {
  250. if(N.data[n].i == k && N.data[n].j == j) {
  251. c2 = N.data[n].e;
  252. break;
  253. }
  254. }
  255. // 计算Q[i][j]的值
  256. if(c1 && c2) {
  257. c += c1 * c2;
  258. }
  259. }
  260. // 如果计算结果不为0,则进行存储
  261. if(c != 0) {
  262. (*Q).tu++;
  263. (*Q).data[(*Q).tu].i = i;
  264. (*Q).data[(*Q).tu].j = j;
  265. (*Q).data[(*Q).tu].e = c;
  266. }
  267. }
  268. }
  269. return OK;
  270. }
  271. /*
  272. * ████████ 算法5.1 ████████
  273. *
  274. * 矩阵转置
  275. */
  276. Status TransposeSMatrix(TSMatrix M, TSMatrix* T) {
  277. int p, q, col;
  278. (*T).mu = M.nu;
  279. (*T).nu = M.mu;
  280. (*T).tu = M.tu;
  281. if((*T).tu != 0) {
  282. q = 1; // q用于T中非零元的计数
  283. // col代表M的列,T的行
  284. for(col = 1; col <= M.nu; ++col) {
  285. // 在M中查找第j列的元素,依次将其转置到T中
  286. for(p = 1; p <= M.tu; ++p) {
  287. if(M.data[p].j == col) {
  288. (*T).data[q].i = M.data[p].j; // M的列变为T的行
  289. (*T).data[q].j = M.data[p].i; // M的行变为T的列
  290. (*T).data[q].e = M.data[p].e; // 每个三元组值不变
  291. ++q;
  292. }
  293. }
  294. }
  295. }
  296. return OK;
  297. }
  298. /*
  299. * ████████ 算法5.2 ████████
  300. *
  301. * 矩阵快速转置
  302. */
  303. Status FastTransposeSMatrix(TSMatrix M, TSMatrix* T) {
  304. int col, t, p, q;
  305. int* num; // num[col] 表示M第col列中非零元的个数
  306. int* copt; // copt[col]表示M第col列第一个非零元在转置后矩阵中的位置
  307. (*T).mu = M.nu;
  308. (*T).nu = M.mu;
  309. (*T).tu = M.tu;
  310. // 提前返回
  311. if((*T).tu == 0) {
  312. return ERROR;
  313. }
  314. num = (int*) malloc((M.nu + 1) * sizeof(int));
  315. copt = (int*) malloc((M.nu + 1) * sizeof(int));
  316. // 初始化数组num
  317. for(col = 1; col <= M.nu; ++col) {
  318. num[col] = 0;
  319. }
  320. // 统计M中的非零元,统计每列非零元的个数
  321. for(t = 1; t <= M.tu; ++t) {
  322. num[M.data[t].j]++;
  323. }
  324. // 第1列第1个非零元总是位于转置后矩阵中的首位
  325. copt[1] = 1;
  326. // 计算各列第1个非零元在转置矩阵中的位置
  327. for(col = 2; col <= M.nu; ++col) {
  328. copt[col] = copt[col - 1] + num[col - 1];
  329. }
  330. // 依次扫描M中的三元组
  331. for(p = 1; p <= M.tu; ++p) {
  332. col = M.data[p].j; // 计算当前非零元所处的列
  333. q = copt[col]; // 计算当前非零元在转置矩阵中的位置
  334. (*T).data[q].i = M.data[p].j;
  335. (*T).data[q].j = M.data[p].i;
  336. (*T).data[q].e = M.data[p].e;
  337. ++copt[col]; // 再遇到此列元素时,其在转置矩阵中的位置应当增一(该步骤很重要)
  338. }
  339. return OK;
  340. }
  341. /*
  342. * 输出矩阵
  343. */
  344. void PrintSMatrix(TSMatrix M) {
  345. int r, c;
  346. int k = 1;
  347. for(r = 1; r <= M.mu; r++) {
  348. for(c = 1; c <= M.nu; c++) {
  349. if(r == M.data[k].i && c == M.data[k].j) {
  350. printf("%3d ", M.data[k].e);
  351. k++;
  352. } else {
  353. printf("%3d ", 0);
  354. }
  355. }
  356. printf("\n");
  357. }
  358. }
  1. /* 三元组类型定义,主要用来存储非零元 */
  2. typedef struct {
  3. int i, j; // 该非零元的行下标和列下标
  4. ElemType e;
  5. } Triple;
  6. /* 行逻辑链接的稀疏矩阵类型定义 */
  7. typedef struct {
  8. Triple data[MAXSIZE + 1]; // 非零元三元组表,data[0]未用
  9. int rpos[MAXRC + 1]; // 各行第一个非零元在三元组表中的位置表,rpos[0]未用
  10. int mu, nu, tu; // 矩阵的行数、列数和非零元个数
  11. } RLSMatrix;
  12. /*
  13. * 创建稀疏矩阵M
  14. *
  15. *
  16. *【备注】
  17. *
  18. * 教材中默认从控制台读取数据。
  19. * 这里为了方便测试,避免每次运行都手动输入数据,
  20. * 因而允许选择从预设的文件path中读取测试数据。
  21. *
  22. * 如果需要从控制台读取数据,则path为NULL或者为空串,
  23. * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
  24. */
  25. Status CreateSMatrix(RLSMatrix* M, char* path) {
  26. int k;
  27. FILE* fp;
  28. int readFromConsole; // 是否从控制台读取数据
  29. // 如果没有文件路径信息,则从控制台读取输入
  30. readFromConsole = path == NULL || strcmp(path, "") == 0;
  31. // 如果没有文件路径信息,则从控制台读取输入
  32. if(readFromConsole) {
  33. printf("请输入行数:");
  34. scanf("%d", &((*M).mu));
  35. printf("请输入列数:");
  36. scanf("%d", &((*M).nu));
  37. printf("请输入非零元素个数:");
  38. scanf("%d", &((*M).tu));
  39. printf("请输入%d个三元组信息\n", (*M).tu);
  40. for(k = 1; k <= (*M).tu; k++) {
  41. printf("第%2d组:", k);
  42. scanf("%d%d%d", &((*M).data[k].i), &((*M).data[k].j), &((*M).data[k].e));
  43. }
  44. } else {
  45. fp = fopen(path, "r");
  46. ReadData(fp, "%d%d%d", &((*M).mu), &((*M).nu), &((*M).tu));
  47. for(k = 1; k <= (*M).tu; k++) {
  48. ReadData(fp, "%d%d%d", &((*M).data[k].i), &((*M).data[k].j), &((*M).data[k].e));
  49. }
  50. fclose(fp);
  51. }
  52. // 为rpos数组赋值
  53. AssignRpos(M);
  54. return OK;
  55. }
  56. /*
  57. * 矩阵加法
  58. *
  59. * Q = M + N。
  60. */
  61. Status AddSMatrix(RLSMatrix M, RLSMatrix N, RLSMatrix* Q) {
  62. int m, n, k;
  63. if(M.mu != N.mu || M.nu != N.nu) {
  64. printf("两矩阵的行数、列数不满足相加条件!!\n");
  65. return ERROR;
  66. }
  67. // 初始化Q的行列信息
  68. (*Q).mu = M.mu;
  69. (*Q).nu = M.nu;
  70. (*Q).tu = 0;
  71. m = n = k = 1;
  72. // 依次遍历M与N的三元组
  73. while(m <= M.tu && n <= N.tu) {
  74. // M中的三元组行下标较小
  75. if(M.data[m].i < N.data[n].i) {
  76. (*Q).data[k] = M.data[m];
  77. m++;
  78. // N中的三元组行下标较小
  79. } else if(M.data[m].i > N.data[n].i) {
  80. (*Q).data[k] = N.data[n];
  81. n++;
  82. // M与N中的三元组行下标一致,需要进一步比较列坐标
  83. } else {
  84. // M中的三元组列下标较小
  85. if(M.data[m].j < N.data[n].j) {
  86. (*Q).data[k] = M.data[m];
  87. m++;
  88. // N中的三元组列下标较小
  89. } else if(M.data[m].j > N.data[n].j) {
  90. (*Q).data[k] = N.data[n];
  91. n++;
  92. // M与N中的三元组列下标一致,需要进行加法运算
  93. } else {
  94. // 值已经加为0的话,不需要存储该元素
  95. if((M.data[m].e + N.data[n].e) == 0) {
  96. m++;
  97. n++;
  98. continue;
  99. } else {
  100. (*Q).data[k].i = M.data[m].i;
  101. (*Q).data[k].j = M.data[m].j;
  102. (*Q).data[k].e = M.data[m].e + N.data[n].e;
  103. m++;
  104. n++;
  105. }
  106. }
  107. }
  108. k++;
  109. (*Q).tu++;
  110. }
  111. // 遍历M中剩余的三元组
  112. while(m <= M.tu) {
  113. (*Q).data[k] = M.data[m];
  114. m++;
  115. k++;
  116. (*Q).tu++;
  117. }
  118. // 遍历N中剩余的三元组
  119. while(n <= N.tu) {
  120. (*Q).data[k] = N.data[n];
  121. n++;
  122. k++;
  123. (*Q).tu++;
  124. }
  125. // 为rpos数组赋值
  126. AssignRpos(Q);
  127. return OK;
  128. }
  129. /*
  130. * 矩阵减法
  131. *
  132. * Q = M - N。
  133. */
  134. Status SubSMatrix(RLSMatrix M, RLSMatrix N, RLSMatrix* Q) {
  135. int m, n, k;
  136. if(M.mu != N.mu || M.nu != N.nu) {
  137. printf("两矩阵的行数、列数不满足相减条件!!\n");
  138. return ERROR;
  139. }
  140. // 初始化Q的行列信息
  141. (*Q).mu = M.mu;
  142. (*Q).nu = M.nu;
  143. (*Q).tu = 0;
  144. m = n = k = 1;
  145. // 依次遍历M与N的三元组
  146. while(m <= M.tu && n <= N.tu) {
  147. // M中的三元组行下标较小
  148. if(M.data[m].i < N.data[n].i) {
  149. (*Q).data[k] = M.data[m];
  150. m++;
  151. // N中的三元组行下标较小
  152. } else if(M.data[m].i > N.data[n].i) {
  153. (*Q).data[k].i = N.data[n].i;
  154. (*Q).data[k].j = N.data[n].j;
  155. (*Q).data[k].e = -N.data[n].e; // 由于是相减,所以要对元素值取相反数
  156. n++;
  157. // M与N中的三元组行下标一致,需要进一步比较列坐标
  158. } else {
  159. // M中的三元组列下标较小
  160. if(M.data[m].j < N.data[n].j) {
  161. (*Q).data[k] = M.data[m];
  162. m++;
  163. // N中的三元组列下标较小
  164. } else if(M.data[m].j > N.data[n].j) {
  165. (*Q).data[k].i = N.data[n].i;
  166. (*Q).data[k].j = N.data[n].j;
  167. (*Q).data[k].e = -N.data[n].e; // 由于是相减,所以要对元素值取相反数
  168. n++;
  169. // M与N中的三元组列下标一致,需要进行减法运算
  170. } else {
  171. // 值已经减为0的话,不需要存储该元素
  172. if((M.data[m].e - N.data[n].e) == 0) {
  173. m++;
  174. n++;
  175. continue;
  176. } else {
  177. (*Q).data[k].i = M.data[m].i;
  178. (*Q).data[k].j = M.data[m].j;
  179. (*Q).data[k].e = M.data[m].e - N.data[n].e;
  180. m++;
  181. n++;
  182. }
  183. }
  184. }
  185. k++;
  186. (*Q).tu++;
  187. }
  188. // 遍历M中剩余的三元组
  189. while(m <= M.tu) {
  190. (*Q).data[k] = M.data[m];
  191. m++;
  192. k++;
  193. (*Q).tu++;
  194. }
  195. // 遍历N中剩余的三元组
  196. while(n <= N.tu) {
  197. (*Q).data[k].i = N.data[n].i;
  198. (*Q).data[k].j = N.data[n].j;
  199. (*Q).data[k].e = -N.data[n].e;
  200. n++;
  201. k++;
  202. (*Q).tu++;
  203. }
  204. // 为rpos数组赋值
  205. AssignRpos(Q);
  206. return OK;
  207. }
  208. /*
  209. * ████████ 算法5.3 ████████
  210. *
  211. * 矩阵乘法
  212. *
  213. * Q = M * N。
  214. */
  215. Status MultSMatrix(RLSMatrix M, RLSMatrix N, RLSMatrix* Q) {
  216. int arow, p, tp;
  217. int brow, q, tq;
  218. int ccol;
  219. int* ctemp; // Q中各行元素值累加器,ctemp[0]单元弃用
  220. int i;
  221. // M的列数需要等于N的行数
  222. if(M.nu != N.mu) {
  223. printf("两矩阵的行数、列数不满足相乘条件!!\n");
  224. return ERROR;
  225. }
  226. // 初始化Q的行列信息
  227. (*Q).mu = M.mu;
  228. (*Q).nu = N.nu;
  229. (*Q).tu = 0;
  230. // 如果存在零矩阵
  231. if(M.tu * N.tu == 0) {
  232. return OK;
  233. }
  234. ctemp = (int*) malloc((N.nu + 1) * sizeof(int));
  235. // 处理M的每一行
  236. for(arow = 1; arow <= M.mu; ++arow) {
  237. // 初始化Q中行元素值计数器
  238. for(i = 0; i <= N.nu; ++i) {
  239. ctemp[i] = 0;
  240. }
  241. // tp指向M当前行的下一行第一个非零元位置
  242. if(arow < M.mu) {
  243. tp = M.rpos[arow + 1];
  244. } else {
  245. tp = M.tu + 1;
  246. }
  247. // 遍历M中arow行的所有非零元
  248. for(p = M.rpos[arow]; p < tp; ++p) {
  249. // 获取该非零元在N中的行号
  250. brow = M.data[p].j;
  251. // tq指向N当前行的下一行第一个非零元位置
  252. if(brow < N.mu) {
  253. tq = N.rpos[brow + 1];
  254. } else {
  255. tq = N.tu + 1;
  256. }
  257. // 遍历N中brow行的所有非零元
  258. for(q = N.rpos[brow]; q < tq; ++q) {
  259. // 乘积元素在Q中的列号
  260. ccol = N.data[q].j;
  261. // 累加乘积
  262. ctemp[ccol] += M.data[p].e * N.data[q].e;
  263. }
  264. }
  265. /*
  266. * 至此,Q中第arow行元素已求出
  267. */
  268. // 遍历计算后的乘积,选取非零元存入Q中
  269. for(ccol = 1; ccol <= (*Q).nu; ++ccol) {
  270. // 若Q中第arow行ccol列元素不为0
  271. if(ctemp[ccol]) {
  272. ++(*Q).tu;
  273. // 非零元个数超出限制
  274. if((*Q).tu > MAXSIZE) {
  275. return ERROR;
  276. }
  277. (*Q).data[(*Q).tu].i = arow;
  278. (*Q).data[(*Q).tu].j = ccol;
  279. (*Q).data[(*Q).tu].e = ctemp[ccol];
  280. }
  281. }
  282. }
  283. // 为rpos数组赋值
  284. AssignRpos(Q);
  285. return OK;
  286. }
  287. /*
  288. * 矩阵转置
  289. */
  290. Status TransposeSMatrix(RLSMatrix M, RLSMatrix* T) {
  291. int p, q, col;
  292. (*T).mu = M.nu;
  293. (*T).nu = M.mu;
  294. (*T).tu = M.tu;
  295. if((*T).tu) {
  296. q = 1; // q用于T中非零元的计数
  297. // col代表M的列,T的行
  298. for(col = 1; col <= M.nu; ++col) {
  299. // 在M中查找第j列的元素,依次将其转置到T中
  300. for(p = 1; p <= M.tu; ++p) {
  301. if(M.data[p].j == col) {
  302. (*T).data[q].i = M.data[p].j; // M的列变为T的行
  303. (*T).data[q].j = M.data[p].i; // M的行变为T的列
  304. (*T).data[q].e = M.data[p].e; // 每个三元组值不变
  305. ++q;
  306. }
  307. }
  308. }
  309. }
  310. // 为rpos数组赋值
  311. AssignRpos(T);
  312. return OK;
  313. }
  314. /*
  315. * 矩阵快速转置
  316. */
  317. Status FastTransposeSMatrix(RLSMatrix M, RLSMatrix* T) {
  318. int col, t, p, q;
  319. int* num; // num[col] 表示M第col列中非零元的个数
  320. int* copt; // copt[col]表示M第col列第一个非零元在转置后矩阵中的位置
  321. (*T).mu = M.nu;
  322. (*T).nu = M.mu;
  323. (*T).tu = M.tu;
  324. // 提前返回
  325. if((*T).tu == 0) {
  326. return ERROR;
  327. }
  328. num = (int*) malloc((M.nu + 1) * sizeof(int));
  329. copt = (int*) malloc((M.nu + 1) * sizeof(int));
  330. // 初始化数组num
  331. for(col = 1; col <= M.nu; ++col) {
  332. num[col] = 0;
  333. }
  334. // 统计M中的非零元,统计每列非零元的个数
  335. for(t = 1; t <= M.tu; ++t) {
  336. num[M.data[t].j]++;
  337. }
  338. // 第1列第1个非零元总是位于转置后矩阵中的首位
  339. copt[1] = 1;
  340. // 计算各列第1个非零元在转置矩阵中的位置
  341. for(col = 2; col <= M.nu; ++col) {
  342. copt[col] = copt[col - 1] + num[col - 1];
  343. }
  344. // 依次扫描M中的三元组
  345. for(p = 1; p <= M.tu; ++p) {
  346. col = M.data[p].j; // 计算当前非零元所处的列
  347. q = copt[col]; // 计算当前非零元在转置矩阵中的位置
  348. (*T).data[q].i = M.data[p].j;
  349. (*T).data[q].j = M.data[p].i;
  350. (*T).data[q].e = M.data[p].e;
  351. ++copt[col]; // 再遇到此列元素时,其在转置矩阵中的位置应当增一(该步骤很重要)
  352. }
  353. // 为rpos数组赋值
  354. AssignRpos(T);
  355. return OK;
  356. }
  357. /*
  358. * 输出矩阵
  359. */
  360. void PrintSMatrix(RLSMatrix M) {
  361. int r, c;
  362. int k = 1;
  363. for(r = 1; r <= M.mu; ++r) {
  364. for(c = 1; c <= M.nu; ++c) {
  365. if(r == M.data[k].i && c == M.data[k].j) {
  366. printf("%3d ", M.data[k].e);
  367. k++;
  368. } else {
  369. printf("%3d ", 0);
  370. }
  371. }
  372. printf("\n");
  373. }
  374. printf("rpos = ");
  375. for(k = 1; k <= M.mu; ++k) {
  376. printf("%d ", M.rpos[k]);
  377. }
  378. printf("\n");
  379. }
  380. // 为rpos数组赋值
  381. static void AssignRpos(RLSMatrix* M) {
  382. int k, m;
  383. // 初始化数组rpos
  384. for(k = 0; k <= MAXRC; ++k) {
  385. (*M).rpos[k] = 0;
  386. }
  387. for(k = 1; k <= (*M).tu; k++) {
  388. m = (*M).data[k].i; // 当前三元组元素在矩阵中的行位置
  389. // 记录每行第一个非零元的在三元组表中的位置
  390. if((*M).rpos[m] == 0) {
  391. (*M).rpos[m] = k; // 只会在当前行有非零元的情况下记录
  392. }
  393. }
  394. // 处理那些没有非零元的行
  395. for(k = (*M).mu; k >= 1; k--) {
  396. // 如果当前行没有非零元,则此处会直接取用下一行的参数
  397. if((*M).rpos[k] == 0) {
  398. // 如果是最后一行无非零元,因为已经不存在下一行了,所以需特殊处理
  399. if(k == (*M).mu) {
  400. (*M).rpos[k] = (*M).tu + 1;
  401. } else {
  402. (*M).rpos[k] = (*M).rpos[k + 1];
  403. }
  404. }
  405. }
  406. }
  1. /* 非零元类型定义 */
  2. typedef struct OLNode {
  3. int i, j; // 该非零元的行下标和列下标
  4. ElemType e;
  5. struct OLNode* right; // 该非零元所在的行表的后继链域
  6. struct OLNode* down; // 该非零元所在的列表的后继链域
  7. } OLNode, * OLink;
  8. /* 十字链表类型定义 */
  9. typedef struct {
  10. OLink* rhead; // 行链表头指针
  11. OLink* chead; // 列链表头指针
  12. int mu, nu, tu; // 矩阵的行数、列数和非零元个数
  13. } CrossList;
  14. /*
  15. * ████████ 算法5.4 ████████
  16. *
  17. * 创建稀疏矩阵M
  18. *
  19. *
  20. *【备注】
  21. *
  22. * 教材中默认从控制台读取数据。
  23. * 这里为了方便测试,避免每次运行都手动输入数据,
  24. * 因而允许选择从预设的文件path中读取测试数据。
  25. *
  26. * 如果需要从控制台读取数据,则path为NULL或者为空串,
  27. * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
  28. */
  29. Status CreateSMatrix(CrossList* M, char* path) {
  30. int i, j, k;
  31. OLNode* p, * q;
  32. FILE* fp;
  33. int readFromConsole; // 是否从控制台读取数据
  34. // 如果没有文件路径信息,则从控制台读取输入
  35. readFromConsole = path == NULL || strcmp(path, "") == 0;
  36. if(readFromConsole) {
  37. printf("请输入行数:");
  38. scanf("%d", &((*M).mu));
  39. printf("请输入列数:");
  40. scanf("%d", &((*M).nu));
  41. printf("请输入非零元素个数:");
  42. scanf("%d", &((*M).tu));
  43. printf("请输入%d个三元组信息\n", (*M).tu);
  44. } else {
  45. fp = fopen(path, "r");
  46. ReadData(fp, "%d%d%d", &((*M).mu), &((*M).nu), &((*M).tu));
  47. }
  48. // 创建行链(类似行索引,0号单元弃用)
  49. (*M).rhead = (OLink*) malloc(((*M).mu + 1) * sizeof(OLink));
  50. if((*M).rhead == NULL) {
  51. exit(OVERFLOW);
  52. }
  53. // 创建列链(类似列索引,0号单元弃用)
  54. (*M).chead = (OLink*) malloc(((*M).nu + 1) * sizeof(OLink));
  55. if((*M).chead == NULL) {
  56. exit(OVERFLOW);
  57. }
  58. // 初始化行链索引为NULL
  59. for(k = 0; k <= (*M).mu; ++k) {
  60. (*M).rhead[k] = NULL;
  61. }
  62. // 初始化列链索引为NULL
  63. for(k = 0; k <= (*M).nu; ++k) {
  64. (*M).chead[k] = NULL;
  65. }
  66. // 依次录入非零元
  67. for(k = 1; k <= (*M).tu; ++k) {
  68. // 创建三元组结点
  69. p = (OLNode*) malloc(sizeof(OLNode));
  70. if(p == NULL) {
  71. exit(OVERFLOW);
  72. }
  73. if(readFromConsole) {
  74. printf("第%2d组:", k);
  75. scanf("%d%d%d", &i, &j, &(p->e));
  76. } else {
  77. ReadData(fp, "%d%d%d", &i, &j, &(p->e));
  78. }
  79. p->i = i; // 行号
  80. p->j = j; // 列号
  81. p->right = p->down = NULL;
  82. /*
  83. * 开始行的插入
  84. */
  85. // 如果该行还没有元素,或已有元素均位于该元素右侧,则可以直接插入
  86. if((*M).rhead[i] == NULL || (*M).rhead[i]->j > j) {
  87. // 定位行表中的插入位置
  88. p->right = (*M).rhead[i];
  89. (*M).rhead[i] = p;
  90. } else {
  91. // 寻找插入位置的前一个位置
  92. for(q = (*M).rhead[i]; (q->right) && (q->right->j < j); q = q->right) {
  93. }
  94. if(q->j == p->j || ((q->right) && q->right->j == p->j)) {
  95. printf("此位置已被占用!!\n");
  96. exit(ERROR);
  97. }
  98. p->right = q->right;
  99. q->right = p;
  100. }
  101. /*
  102. * 开始列的插入
  103. */
  104. // 如果该列还没有元素,或已有元素均位于该元素下侧,则可以直接插入
  105. if((*M).chead[j] == NULL || (*M).chead[j]->i > i) {
  106. // 定位列表中的插入位置
  107. p->down = (*M).chead[j];
  108. (*M).chead[j] = p;
  109. } else {
  110. // 寻找插入位置的前一个位置
  111. for(q = (*M).chead[j]; (q->down) && (q->down->i < i); q = q->down) {
  112. }
  113. if(q->i == p->i || ((q->down) && q->down->i == p->i)) {
  114. printf("此位置已被占用!!\n");
  115. exit(ERROR);
  116. }
  117. p->down = q->down;
  118. q->down = p;
  119. }
  120. }
  121. if(!readFromConsole) {
  122. fclose(fp);
  123. }
  124. return OK;
  125. }
  126. /*
  127. * 矩阵复制
  128. *
  129. * 创建一个新矩阵T,该矩阵包含了从矩阵M中包含的数据。
  130. */
  131. Status CopySMatrix(CrossList M, CrossList* T) {
  132. int k;
  133. OLNode* p, * q, * r, * l;
  134. if(T == NULL) {
  135. return ERROR;
  136. }
  137. // 复制行列信息
  138. (*T).mu = M.mu;
  139. (*T).nu = M.nu;
  140. (*T).tu = M.tu;
  141. // 创建行链(类似行索引,0号单元弃用)
  142. (*T).rhead = (OLink*) malloc(((*T).mu + 1) * sizeof(OLink));
  143. if((*T).rhead == NULL) {
  144. exit(OVERFLOW);
  145. }
  146. // 创建列链(类似列索引,0号单元弃用)
  147. (*T).chead = (OLink*) malloc(((*T).nu + 1) * sizeof(OLink));
  148. if((*T).chead == NULL) {
  149. exit(OVERFLOW);
  150. }
  151. // 初始化行链索引为NULL
  152. for(k = 0; k <= (*T).mu; ++k) { //初始化行列头指针向量为空
  153. (*T).rhead[k] = NULL;
  154. }
  155. // 初始化列链索引为NULL
  156. for(k = 0; k <= (*T).nu; ++k) {
  157. (*T).chead[k] = NULL;
  158. }
  159. // 按行扫描,依次复制非零元
  160. for(k = 1; k <= M.mu; ++k) {
  161. q = M.rhead[k];
  162. // 如果当前行没有元素,直接跳过
  163. if(q == NULL) {
  164. continue;
  165. }
  166. r = NULL;
  167. while(q != NULL) {
  168. // 创建三元组结点
  169. p = (OLNode*) malloc(sizeof(OLNode));
  170. if(p == NULL) {
  171. exit(OVERFLOW);
  172. }
  173. // 为结点赋值
  174. p->i = q->i;
  175. p->j = q->j;
  176. p->e = q->e;
  177. p->right = p->down = NULL;
  178. /*
  179. * 开始行的插入
  180. */
  181. if(r == NULL) {
  182. (*T).rhead[q->i] = p;
  183. } else {
  184. r->right = p;
  185. }
  186. // r指向当前行新插入的结点
  187. r = p;
  188. /*
  189. * 开始列的插入
  190. */
  191. // 在列链中寻找插入位置
  192. if((*T).chead[q->j] == NULL || (*T).chead[q->j]->i > q->i) {
  193. r->down = (*T).chead[q->j];
  194. (*T).chead[q->j] = r;
  195. } else {
  196. // 寻找插入位置的前一个位置
  197. for(l = (*T).chead[q->j]; (l->down) && (l->down->i < q->i); l = l->down) {
  198. }
  199. r->down = l->down;
  200. l->down = r;
  201. }
  202. q = q->right;
  203. }
  204. }
  205. return OK;
  206. }
  207. /*
  208. * 矩阵加法
  209. *
  210. * Q = M + N。
  211. */
  212. Status AddSMatrix(CrossList M, CrossList N, CrossList* Q) {
  213. int i;
  214. OLNode* pm, * pn, * p, * r, * l;
  215. if(M.mu != N.mu || M.nu != N.nu) {
  216. printf("两矩阵的行数、列数不满足相加条件!!\n");
  217. return ERROR;
  218. }
  219. // 初始化Q的行列信息
  220. Q->mu = M.mu;
  221. Q->nu = M.nu;
  222. Q->tu = 0;
  223. // 创建行链(类似行索引,0号单元弃用)
  224. Q->rhead = (OLink*) malloc((Q->mu + 1) * sizeof(OLink));
  225. if(!Q->rhead) {
  226. exit(OVERFLOW);
  227. }
  228. // 创建列链(类似列索引,0号单元弃用)
  229. Q->chead = (OLink*) malloc((Q->nu + 1) * sizeof(OLink));
  230. if(!Q->chead) {
  231. exit(OVERFLOW);
  232. }
  233. // 初始化行链索引为NULL
  234. for(i = 0; i <= Q->mu; ++i) {
  235. Q->rhead[i] = NULL;
  236. }
  237. // 初始化列链索引为NULL
  238. for(i = 0; i <= Q->nu; ++i) {
  239. Q->chead[i] = NULL;
  240. }
  241. // 从第一行往下遍历
  242. for(i = 1; i <= M.mu; ++i) {
  243. pm = M.rhead[i];
  244. pn = N.rhead[i];
  245. // 如果M与N的当前行中均有未处理的非零元
  246. while(pm != NULL && pn != NULL) {
  247. // 处理特殊情形
  248. if(pm->j == pn->j && pm->e + pn->e == 0) {
  249. pm = pm->right;
  250. pn = pn->right;
  251. continue;
  252. }
  253. // 创建结点
  254. p = (OLNode*) malloc(sizeof(OLNode));
  255. if(!p) {
  256. exit(OVERFLOW);
  257. }
  258. // M中的三元组列下标较小
  259. if(pm->j < pn->j) {
  260. p->i = pm->i;
  261. p->j = pm->j;
  262. p->e = pm->e;
  263. pm = pm->right;
  264. // N中的三元组列下标较小
  265. } else if(pm->j > pn->j) {
  266. p->i = pn->i;
  267. p->j = pn->j;
  268. p->e = pn->e;
  269. pn = pn->right;
  270. // M与N中的三元组列下标一致,需要进行加法运算
  271. } else {
  272. p->i = pm->i;
  273. p->j = pm->j;
  274. p->e = pm->e + pn->e;
  275. pm = pm->right;
  276. pn = pn->right;
  277. }
  278. p->right = p->down = NULL;
  279. Q->tu++; // Q中非零元个数增一
  280. /*
  281. * 开始行的插入
  282. */
  283. if(Q->rhead[p->i] == NULL) {
  284. Q->rhead[p->i] = p;
  285. } else {
  286. r->right = p;
  287. }
  288. // r指向当前行新插入的结点
  289. r = p;
  290. /*
  291. * 开始列的插入
  292. */
  293. // 在列链中寻找插入位置
  294. if(Q->chead[p->j] == NULL || Q->chead[p->j]->i > p->i) {
  295. r->down = Q->chead[p->j];
  296. Q->chead[p->j] = r;
  297. } else {
  298. // 寻找插入位置的前一个位置
  299. for(l = Q->chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  300. }
  301. r->down = l->down;
  302. l->down = r;
  303. }
  304. }
  305. // 如果M的当前行中仍有未处理的非零元
  306. while(pm != NULL) {
  307. p = (OLNode*) malloc(sizeof(OLNode));
  308. if(p == NULL) {
  309. exit(OVERFLOW);
  310. }
  311. p->i = pm->i;
  312. p->j = pm->j;
  313. p->e = pm->e;
  314. p->right = p->down = NULL;
  315. Q->tu++; // Q中非零元个数增一
  316. if(Q->rhead[p->i] == NULL) {
  317. Q->rhead[p->i] = p;
  318. } else {
  319. r->right = p;
  320. }
  321. // r指向当前行新插入的结点
  322. r = p;
  323. // 在列链中寻找插入位置
  324. if(Q->chead[p->j] == NULL || Q->chead[p->j]->i > p->i) {
  325. r->down = Q->chead[p->j];
  326. Q->chead[p->j] = r;
  327. } else {
  328. // 寻找插入位置的前一个位置
  329. for(l = Q->chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  330. }
  331. r->down = l->down;
  332. l->down = r;
  333. }
  334. pm = pm->right;
  335. }
  336. // 如果N的当前行中仍有未处理的非零元
  337. while(pn != NULL) {
  338. p = (OLNode*) malloc(sizeof(OLNode));
  339. if(p == NULL) {
  340. exit(OVERFLOW);
  341. }
  342. p->i = pn->i;
  343. p->j = pn->j;
  344. p->e = pn->e;
  345. p->right = p->down = NULL;
  346. Q->tu++; // Q中非零元个数增一
  347. if(Q->rhead[p->i] == NULL) {
  348. Q->rhead[p->i] = p;
  349. } else {
  350. r->right = p;
  351. }
  352. // r指向当前行新插入的结点
  353. r = p;
  354. // 在列链中寻找插入位置
  355. if(Q->chead[p->j] == NULL || Q->chead[p->j]->i > p->i) {
  356. r->down = Q->chead[p->j];
  357. Q->chead[p->j] = r;
  358. } else {
  359. // 寻找插入位置的前一个位置
  360. for(l = Q->chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  361. }
  362. r->down = l->down;
  363. l->down = r;
  364. }
  365. pn = pn->right;
  366. }
  367. }
  368. return OK;
  369. }
  370. /*
  371. * 矩阵减法
  372. *
  373. * Q = M - N。
  374. */
  375. Status SubSMatrix(CrossList M, CrossList N, CrossList* Q) {
  376. int i;
  377. OLNode* pm, * pn, * p, * r, * l;
  378. if(M.mu != N.mu || M.nu != N.nu) {
  379. printf("两矩阵的行数、列数不满足相减条件!!\n");
  380. return ERROR;
  381. }
  382. // 初始化Q的行列信息
  383. Q->mu = M.mu;
  384. Q->nu = M.nu;
  385. Q->tu = 0;
  386. // 创建行链(类似行索引,0号单元弃用)
  387. Q->rhead = (OLink*) malloc((Q->mu + 1) * sizeof(OLink));
  388. if(!Q->rhead) {
  389. exit(OVERFLOW);
  390. }
  391. // 创建列链(类似列索引,0号单元弃用)
  392. Q->chead = (OLink*) malloc((Q->nu + 1) * sizeof(OLink));
  393. if(!Q->chead) {
  394. exit(OVERFLOW);
  395. }
  396. // 初始化行链索引为NULL
  397. for(i = 0; i <= Q->mu; ++i) {
  398. Q->rhead[i] = NULL;
  399. }
  400. // 初始化列链索引为NULL
  401. for(i = 0; i <= Q->nu; ++i) {
  402. Q->chead[i] = NULL;
  403. }
  404. // 从第一行往下遍历
  405. for(i = 1; i <= M.mu; ++i) {
  406. pm = M.rhead[i];
  407. pn = N.rhead[i];
  408. // 如果M与N的当前行中均有未处理的非零元
  409. while(pm != NULL && pn != NULL) {
  410. // 处理特殊情形
  411. if(pm->j == pn->j && pm->e - pn->e == 0) {
  412. pm = pm->right;
  413. pn = pn->right;
  414. continue;
  415. }
  416. // 创建结点
  417. p = (OLNode*) malloc(sizeof(OLNode));
  418. if(!p) {
  419. exit(OVERFLOW);
  420. }
  421. // M中的三元组列下标较小
  422. if(pm->j < pn->j) {
  423. p->i = pm->i;
  424. p->j = pm->j;
  425. p->e = pm->e;
  426. pm = pm->right;
  427. // N中的三元组列下标较小
  428. } else if(pm->j > pn->j) {
  429. p->i = pn->i;
  430. p->j = pn->j;
  431. p->e = -pn->e; // 加负号
  432. pn = pn->right;
  433. // M与N中的三元组列下标一致,需要进行减法运算
  434. } else {
  435. p->i = pm->i;
  436. p->j = pm->j;
  437. p->e = pm->e - pn->e;
  438. pm = pm->right;
  439. pn = pn->right;
  440. }
  441. p->right = p->down = NULL;
  442. Q->tu++; // Q中非零元个数增一
  443. /*
  444. * 开始行的插入
  445. */
  446. if(Q->rhead[p->i] == NULL) {
  447. Q->rhead[p->i] = p;
  448. } else {
  449. r->right = p;
  450. }
  451. // r指向当前行新插入的结点
  452. r = p;
  453. /*
  454. * 开始列的插入
  455. */
  456. // 在列链中寻找插入位置
  457. if(Q->chead[p->j] == NULL || Q->chead[p->j]->i > p->i) {
  458. r->down = Q->chead[p->j];
  459. Q->chead[p->j] = r;
  460. } else {
  461. //寻找插入位置的前一个位置
  462. for(l = Q->chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  463. }
  464. r->down = l->down;
  465. l->down = r;
  466. }
  467. }
  468. // 如果M的当前行中仍有未处理的非零元
  469. while(pm != NULL) {
  470. p = (OLNode*) malloc(sizeof(OLNode));
  471. if(!p) {
  472. exit(OVERFLOW);
  473. }
  474. p->i = pm->i;
  475. p->j = pm->j;
  476. p->e = pm->e;
  477. p->right = p->down = NULL;
  478. Q->tu++; // Q中非零元个数增一
  479. if(Q->rhead[p->i] == NULL) {
  480. Q->rhead[p->i] = p;
  481. } else {
  482. r->right = p;
  483. }
  484. // r指向当前行新插入的结点
  485. r = p;
  486. // 在列链中寻找插入位置
  487. if(Q->chead[p->j] == NULL || Q->chead[p->j]->i > p->i) {
  488. r->down = Q->chead[p->j];
  489. Q->chead[p->j] = r;
  490. } else {
  491. // 寻找插入位置的前一个位置
  492. for(l = Q->chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  493. }
  494. r->down = l->down;
  495. l->down = r;
  496. }
  497. pm = pm->right;
  498. }
  499. // 如果N的当前行中仍有未处理的非零元
  500. while(pn != NULL) {
  501. p = (OLNode*) malloc(sizeof(OLNode));
  502. if(!p) {
  503. exit(OVERFLOW);
  504. }
  505. p->i = pn->i;
  506. p->j = pn->j;
  507. p->e = -pn->e; // 加负号
  508. p->right = p->down = NULL;
  509. Q->tu++; // Q中非零元个数增一
  510. if(Q->rhead[p->i] == NULL) {
  511. Q->rhead[p->i] = p;
  512. } else {
  513. r->right = p;
  514. }
  515. // r指向当前行新插入的结点
  516. r = p;
  517. // 在列链中寻找插入位置
  518. if(Q->chead[p->j] == NULL || Q->chead[p->j]->i > p->i) {
  519. r->down = Q->chead[p->j];
  520. Q->chead[p->j] = r;
  521. } else {
  522. // 寻找插入位置的前一个位置
  523. for(l = Q->chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  524. }
  525. r->down = l->down;
  526. l->down = r;
  527. }
  528. pn = pn->right;
  529. }
  530. }
  531. return OK;
  532. }
  533. /*
  534. * 矩阵乘法
  535. *
  536. * Q = M * N。
  537. */
  538. Status MultSMatrix(CrossList M, CrossList N, CrossList* Q) {
  539. int m_row, n_col, i;
  540. ElemType e;
  541. OLNode* pm, * pn, * p, * r, * l;
  542. // M的列数需要等于N的行数
  543. if(M.nu != N.mu) {
  544. printf("两矩阵的行数、列数不满足相乘条件!!\n");
  545. return ERROR;
  546. }
  547. // 初始化Q的行列信息
  548. Q->mu = M.mu;
  549. Q->nu = N.nu;
  550. Q->tu = 0;
  551. // 创建行链(类似行索引,0号单元弃用)
  552. Q->rhead = (OLink*) malloc((Q->mu + 1) * sizeof(OLink));
  553. if(!Q->rhead) {
  554. exit(OVERFLOW);
  555. }
  556. // 创建列链(类似列索引,0号单元弃用)
  557. Q->chead = (OLink*) malloc((Q->nu + 1) * sizeof(OLink));
  558. if(!Q->chead) {
  559. exit(OVERFLOW);
  560. }
  561. // 初始化行链索引为NULL
  562. for(i = 0; i <= Q->mu; ++i) {
  563. Q->rhead[i] = NULL;
  564. }
  565. // 初始化列链索引为NULL
  566. for(i = 0; i <= Q->nu; ++i) {
  567. Q->chead[i] = NULL;
  568. }
  569. // Q是非零矩阵
  570. if(M.tu * N.tu) {
  571. for(m_row = 1; m_row <= M.mu; ++m_row) {
  572. for(n_col = 1; n_col <= N.nu; ++n_col) {
  573. pm = M.rhead[m_row];
  574. pn = N.chead[n_col];
  575. e = 0;
  576. // M的行与N的列相乘
  577. while(pm && pn) {
  578. if(pm->j < pn->i) {
  579. pm = pm->right;
  580. } else if(pm->j > pn->i) {
  581. pn = pn->down;
  582. } else {
  583. e += pm->e * pn->e;
  584. pm = pm->right;
  585. pn = pn->down;
  586. }
  587. }
  588. if(e == 0) {
  589. continue;
  590. }
  591. p = (OLNode*) malloc(sizeof(OLNode));
  592. if(!p) {
  593. exit(OVERFLOW);
  594. }
  595. // 为结点赋值
  596. p->i = M.rhead[m_row]->i;
  597. p->j = N.chead[n_col]->j;
  598. p->e = e;
  599. p->right = p->down = NULL;
  600. Q->tu++; // Q中非零元个数增一
  601. if(Q->rhead[p->i] == NULL) {
  602. Q->rhead[p->i] = p;
  603. } else {
  604. r->right = p;
  605. }
  606. // r指向当前行新插入的结点
  607. r = p;
  608. // 在列链中寻找插入位置
  609. if(Q->chead[p->j] == NULL || Q->chead[p->j]->i > p->i) {
  610. r->down = Q->chead[p->j];
  611. Q->chead[p->j] = r;
  612. } else {
  613. // 寻找插入位置的前一个位置
  614. for(l = Q->chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  615. }
  616. r->down = l->down;
  617. l->down = r;
  618. }
  619. }
  620. }
  621. }
  622. return OK;
  623. }
  624. /*
  625. * 矩阵转置
  626. */
  627. Status TransposeSMatrix(CrossList M, CrossList* T) {
  628. int i;
  629. OLNode* p, * q, * r, * l;
  630. // 初始化Q的行列信息
  631. (*T).mu = M.nu;
  632. (*T).nu = M.mu;
  633. (*T).tu = M.tu;
  634. // 创建行链(类似行索引,0号单元弃用)
  635. (*T).rhead = (OLink*) malloc(((*T).mu + 1) * sizeof(OLink));
  636. if(!(*T).rhead) {
  637. exit(OVERFLOW);
  638. }
  639. // 创建列链(类似列索引,0号单元弃用)
  640. (*T).chead = (OLink*) malloc(((*T).nu + 1) * sizeof(OLink));
  641. if(!(*T).chead) {
  642. exit(OVERFLOW);
  643. }
  644. // 初始化行链索引为NULL
  645. for(i = 0; i <= (*T).mu; ++i) {
  646. (*T).rhead[i] = NULL;
  647. }
  648. // 初始化列链索引为NULL
  649. for(i = 0; i <= (*T).nu; ++i) {
  650. (*T).chead[i] = NULL;
  651. }
  652. // 零矩阵
  653. if(!(*T).tu) {
  654. return OK;
  655. }
  656. // 按列扫描
  657. for(i = 1; i <= M.nu; ++i) {
  658. q = M.chead[i];
  659. // 如果当前行没有元素,直接跳过
  660. if(q == NULL) {
  661. continue;
  662. }
  663. while(q != NULL) {
  664. // 创建三元组结点
  665. p = (OLNode*) malloc(sizeof(OLNode));
  666. if(!p) {
  667. exit(OVERFLOW);
  668. }
  669. // 为结点赋值,行变列,列变行
  670. p->i = q->j;
  671. p->j = q->i;
  672. p->e = q->e;
  673. p->right = p->down = NULL;
  674. /*
  675. * 开始行的插入
  676. */
  677. if((*T).rhead[p->i] == NULL) {
  678. (*T).rhead[p->i] = p;
  679. } else {
  680. r->right = p;
  681. }
  682. // r指向当前行新插入的结点
  683. r = p;
  684. /*
  685. * 开始列的插入
  686. */
  687. // 在列链中寻找插入位置
  688. if((*T).chead[p->j] == NULL || (*T).chead[p->j]->i > p->i) {
  689. r->down = (*T).chead[p->j];
  690. (*T).chead[p->j] = r;
  691. } else {
  692. // 寻找插入位置的前一个位置
  693. for(l = (*T).chead[p->j]; (l->down) && (l->down->i < p->i); l = l->down) {
  694. }
  695. r->down = l->down;
  696. l->down = r;
  697. }
  698. q = q->down;
  699. }
  700. }
  701. return OK;
  702. }
  703. /*
  704. * 输出矩阵
  705. */
  706. void PrintSMatrix(CrossList M) {
  707. int i, j;
  708. OLNode* p;
  709. for(i = 1; i <= M.mu; ++i) {
  710. p = M.rhead[i];
  711. for(j = 1; j <= M.nu; ++j) {
  712. if(p && p->j == j) {
  713. printf("%3d ", p->e);
  714. p = p->right;
  715. } else {
  716. printf("%3d ", 0);
  717. }
  718. }
  719. printf("\n");
  720. }
  721. }

广义表的定义(深度、长度)

  1. /* 广义表(头尾链表存储表示)类型定义 */
  2. typedef struct GLNode {
  3. ElemTag tag; // 公共标记,用于区分原子结点和表结点
  4. // 原子结点和表结点的联合部分
  5. union {
  6. AtomType atom; // atom是原子结点的值域,AtomType由用户定义
  7. struct {
  8. struct GLNode* hp; // 指向表头
  9. struct GLNode* tp; // 指向表尾
  10. } ptr; // 表结点的指针域
  11. } Node;
  12. } GLNode;
  13. /*
  14. * ████████ 算法5.7 ████████
  15. *
  16. * 创建
  17. *
  18. * 由字符串S创建广义表L。
  19. */
  20. Status CreateGList(GList* L, SString S) {
  21. SString emp; // 代表空广义表的字符串
  22. SString hsub, sub;
  23. GList p, q;
  24. if(L == NULL) {
  25. return ERROR;
  26. }
  27. // 清理字符串S中的空白,包括清理不可打印字符和清理空格
  28. ClearBlank(S);
  29. if(StrEmpty(S)) {
  30. return ERROR;
  31. }
  32. StrAssign(emp, "()");
  33. /*
  34. * 如果输入串为(),则代表需要创建空的广义表
  35. *
  36. *【注】
  37. * 教材这里的代码是有问题的。
  38. * StrCompare的返回值指示的是两个字符串的大小,而不是指示两个字符串是否相等。
  39. * 如果给定的S与()相等,返回值应当是0。
  40. */
  41. if(!StrCompare(S, emp)) {
  42. *L = NULL;
  43. } else {
  44. *L = (GList) malloc(sizeof(GLNode));
  45. if(*L == NULL) {
  46. exit(OVERFLOW);
  47. }
  48. // 创建原子
  49. if(StrLength(S) == 1) {
  50. (*L)->tag = Atom;
  51. (*L)->Node.atom = S[1];
  52. } else {
  53. (*L)->tag = List;
  54. p = *L;
  55. // 去掉最外层括号
  56. SubString(sub, S, 2, StrLength(S) - 2);
  57. // 重复建n个子表
  58. do {
  59. // 从sub中分离出表头串hsub,分离完成后,sub也会发生变化
  60. sever(hsub, sub);
  61. // 递归创建广义表
  62. CreateGList(&(p->Node.ptr.hp), hsub);
  63. q = p;
  64. // 如果表尾不为空,需要继续处理表尾
  65. if(!StrEmpty(sub)) {
  66. p = (GList) malloc(sizeof(GLNode));
  67. if(p == NULL) {
  68. exit(OVERFLOW);
  69. }
  70. p->tag = List;
  71. q->Node.ptr.tp = p;
  72. }
  73. } while(!StrEmpty(sub));
  74. q->Node.ptr.tp = NULL;
  75. }
  76. }
  77. return OK;
  78. }
  79. /*
  80. * ████████ 算法5.6 ████████
  81. *
  82. * 复制
  83. *
  84. * 由广义表L复制得到广义表T。
  85. */
  86. Status CopyGList(GList* T, GList L) {
  87. if(T == NULL) {
  88. return ERROR;
  89. }
  90. if(L == NULL) {
  91. *T = NULL;
  92. } else {
  93. // 新建广义表结点
  94. *T = (GList) malloc(sizeof(GLNode));
  95. if(*T == NULL) {
  96. exit(OVERFLOW);
  97. }
  98. (*T)->tag = L->tag;
  99. // 复制单原子
  100. if(L->tag == Atom) {
  101. (*T)->Node.atom = L->Node.atom;
  102. // 复制表头和表尾
  103. } else {
  104. CopyGList(&((*T)->Node.ptr.hp), L->Node.ptr.hp);
  105. CopyGList(&((*T)->Node.ptr.tp), L->Node.ptr.tp);
  106. }
  107. }
  108. return OK;
  109. }
  110. /*
  111. * ████████ 算法5.5 ████████
  112. *
  113. * 深度
  114. *
  115. * 返回广义表的深度
  116. */
  117. int GListDepth(GList L) {
  118. int max, deep;
  119. GList p;
  120. // 空表深度为1
  121. if(L == NULL) {
  122. return 1;
  123. }
  124. // 原子深度为0
  125. if(L->tag == Atom) {
  126. return 0;
  127. }
  128. // 递归求子表深度
  129. for(max = 0, p = L; p != NULL; p = p->Node.ptr.tp) {
  130. deep = GListDepth(p->Node.ptr.hp);
  131. if(deep > max) {
  132. max = deep;
  133. }
  134. }
  135. // 非空表的深度是各子元素最大深度加一
  136. return max + 1;
  137. }
  1. /* 广义表(扩展线性链表存储表示)类型定义 */
  2. typedef struct GLNode {
  3. ElemTag tag; // 公共标记,用于区分原子结点和表结点
  4. // 原子结点和表结点的联合部分
  5. union
  6. {
  7. AtomType atom; // atom是原子结点的值域,AtomType由用户定义
  8. struct GLNode* hp; // 指向表头
  9. } Node;
  10. struct GLNode* tp; // 指向表尾
  11. } GLNode;
  12. /*
  13. * 创建
  14. *
  15. * 由字符串S创建广义表L。
  16. */
  17. Status CreateGList(GList* L, SString S) {
  18. SString emp, hsub, sub, tmp;
  19. if(L == NULL) {
  20. return ERROR;
  21. }
  22. // 清理字符串S中的空白,包括清理不可打印字符和清理空格
  23. ClearBlank(S);
  24. if(StrEmpty(S)) {
  25. return ERROR;
  26. }
  27. // 复制是为了不破坏S
  28. StrCopy(sub, S);
  29. /*
  30. * 初次执行到此时,带着最外层的括号
  31. * 再次执行到此时,已经脱去了外层括号
  32. */
  33. sever(hsub, sub);
  34. *L = (GList) malloc(sizeof(GLNode));
  35. if(*L == NULL) {
  36. exit(OVERFLOW);
  37. }
  38. StrAssign(emp, "()");
  39. if(!StrCompare(hsub, emp)) {
  40. (*L)->tag = List;
  41. (*L)->Node.hp = NULL;
  42. } else {
  43. if(StrLength(hsub) == 1) {
  44. (*L)->tag = Atom;
  45. (*L)->Node.atom = hsub[1];
  46. } else {
  47. (*L)->tag = List;
  48. SubString(tmp, hsub, 2, StrLength(hsub) - 2);
  49. CreateGList(&((*L)->Node.hp), tmp);
  50. }
  51. }
  52. // 如果不存在队尾,则退出本层递归
  53. if(StrEmpty(sub)) {
  54. (*L)->tp = NULL;
  55. } else {
  56. // 继续递归求表尾
  57. CreateGList(&((*L)->tp), sub);
  58. }
  59. return OK;
  60. }
  61. /*
  62. * 深度
  63. *
  64. * 返回广义表的深度
  65. */
  66. int GListDepth(GList L) {
  67. int max, deep;
  68. GList p;
  69. max = 0;
  70. // 广义表不存在
  71. if(L == NULL) {
  72. return -1;
  73. }
  74. // 空表深度为1
  75. if(L->tag == List && !L->Node.hp) {
  76. return 1;
  77. }
  78. // 原子深度为0
  79. if(L->tag == Atom) {
  80. return 0;
  81. }
  82. // 递归求子表深度
  83. for(p = L->Node.hp; p != NULL; p = p->tp) {
  84. // 求以p为头指针的子表深度
  85. deep = GListDepth(p);
  86. if(deep > max) {
  87. max = deep;
  88. }
  89. }
  90. return max + 1;
  91. }

求表头Head、表尾Tail操作

  1. /*
  2. * 表头
  3. */
  4. GList GetHead(GList L) {
  5. GList p;
  6. // 空表无表头,这里不能返回NULL,不然分不清是失败了还是返回了空表
  7. if(L == NULL) {
  8. exit(ERROR);
  9. }
  10. CopyGList(&p, L->Node.ptr.hp);
  11. return p;
  12. }
  13. /*
  14. * 表尾
  15. */
  16. GList GetTail(GList L) {
  17. GList p;
  18. // 空表无表尾,这里不能返回NULL,不然分不清是失败了还是返回了空表
  19. if(L == NULL) {
  20. exit(ERROR);
  21. }
  22. CopyGList(&p, L->Node.ptr.tp);
  23. return p;
  24. }
  1. /*
  2. * 表头
  3. */
  4. GList GetHead(GList L) {
  5. GList p, q;
  6. // 广义表不存在或广义表为空表,无法获取表头
  7. if(L == NULL || L->Node.hp == NULL) {
  8. return NULL;
  9. }
  10. q = L->Node.hp->tp; // 临时保存L的表尾信息
  11. L->Node.hp->tp = NULL; // 截去L的表尾部分
  12. CopyGList(&p, L->Node.hp); // 复制表头信息(已屏蔽表尾)
  13. L->Node.hp->tp = q; // 恢复L的表尾信息
  14. return p;
  15. }
  16. /*
  17. * 表尾
  18. */
  19. GList GetTail(GList L) {
  20. GList p, q;
  21. // 广义表不存在或广义表为空表,无法获取表尾
  22. if(L == NULL || L->Node.hp == NULL) {
  23. return NULL;
  24. }
  25. q = L->Node.hp; // 临时保存L的表头信息
  26. L->Node.hp = q->tp; // 摘下L的表头部分
  27. CopyGList(&p, L); // 复制表尾信息(已屏蔽表头)
  28. q->tp = L->Node.hp; // 恢复L的表头信息
  29. L->Node.hp = q;
  30. return p;
  31. }

第六章 树

树、二叉树的定义
(tree)是n(n>=0)个节点的有限集合
1)有且仅有一个称为根的节点;
2)除根节点以外,其余节点可分为m(m>0)个互不相交的有限集复习提要(20级) - 图32,其中每一个集合本身又是一棵树,并且称为根的子树(subtree)。

二叉树(binary tree)是n(n>=0)个节点构成的集合,它或为空树(n=0),或满足以下两个条件:
1)有且仅有一个称为根的节点;
2)除根节点以外,其余节点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身都是二叉树。

二叉树的性质

  1. 非空二叉树上叶子结点数等于度为2的结点数加1
  2. 非空二叉树上第复习提要(20级) - 图33层上至多有复习提要(20级) - 图34个结点(K≥1)
  3. 深度度为复习提要(20级) - 图35的二叉树至多有复习提要(20级) - 图36个结点(H≥1)
  4. 具有复习提要(20级) - 图37个节点的完全二叉树的深度必为复习提要(20级) - 图38复习提要(20级) - 图39

完全二叉树和满二叉树
满二叉树:一颗深度度为复习提要(20级) - 图40且有复习提要(20级) - 图41个结点的二叉树(H≥1)


  • 二叉树的三种遍历(先序、中序、后序) __*

先序DLR(根左右)
1)访问根节点;
2)先序遍历左子树;
3)先序遍历右子树。

中序LDR(左中右)
1)中序遍历左子树;
2)访问根节点;
3)中序遍历右子树。

后序LRD(左右中)
1)后序遍历左子树;
2)后序遍历右子树;
3)访问根节点。

  1. typedef TElemType SqBiTree[MAX_TREE_SIZE];
  2. // 先序遍历的内部实现
  3. static Status PreTraverse(SqBiTree T, Status(Visit)(TElemType), int i) {
  4. // 越界
  5. if(i >= MAX_TREE_SIZE) {
  6. return ERROR;
  7. }
  8. if(T[i]) {
  9. if(Visit(T[i])) {
  10. if(PreTraverse(T, Visit, 2 * i + 1)) {
  11. if(PreTraverse(T, Visit, 2 * i + 2)) {
  12. return OK;
  13. }
  14. }
  15. }
  16. return ERROR;
  17. // 遇到空树则无需继续计算
  18. } else {
  19. return OK;
  20. }
  21. }
  22. // 中序遍历的内部实现
  23. static Status InTraverse(SqBiTree T, Status(Visit)(TElemType), int i) {
  24. // 越界
  25. if(i >= MAX_TREE_SIZE) {
  26. return ERROR;
  27. }
  28. if(T[i]) {
  29. if(InTraverse(T, Visit, 2 * i + 1)) {
  30. if(Visit(T[i])) {
  31. if(InTraverse(T, Visit, 2 * i + 2)) {
  32. return OK;
  33. }
  34. }
  35. }
  36. return ERROR;
  37. // 遇到空树则无需继续计算
  38. } else {
  39. return OK;
  40. }
  41. }
  42. // 后序遍历的内部实现
  43. static Status PostTraverse(SqBiTree T, Status(Visit)(TElemType), int i) {
  44. // 越界
  45. if(i >= MAX_TREE_SIZE) {
  46. return ERROR;
  47. }
  48. if(T[i]) {
  49. if(PostTraverse(T, Visit, 2 * i + 1)) {
  50. if(PostTraverse(T, Visit, 2 * i + 2)) {
  51. if(Visit(T[i])) {
  52. return OK;
  53. }
  54. }
  55. }
  56. return ERROR;
  57. // 遇到空树则无需继续计算
  58. } else {
  59. return OK;
  60. }
  61. }
  1. /* 二叉树结点定义 */
  2. typedef struct BiTNode {
  3. TElemType data; // 结点元素
  4. struct BiTNode* lchild; // 左孩子指针
  5. struct BiTNode* rchild; // 右孩子指针
  6. } BiTNode;
  7. /*
  8. * ████████ 算法6.4 ████████
  9. *
  10. * 创建
  11. *
  12. * 按照预设的定义来创建二叉树。
  13. * 这里约定使用【先序序列】来创建二叉树。
  14. *
  15. *
  16. *【备注】
  17. *
  18. * 教材中默认从控制台读取数据。
  19. * 这里为了方便测试,避免每次运行都手动输入数据,
  20. * 因而允许选择从预设的文件path中读取测试数据。
  21. *
  22. * 如果需要从控制台读取数据,则path为NULL或者为空串,
  23. * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
  24. */
  25. Status CreateBiTree(BiTree* T, char* path) {
  26. FILE* fp;
  27. int readFromConsole; // 是否从控制台读取数据
  28. // 如果没有文件路径信息,则从控制台读取输入
  29. readFromConsole = path == NULL || strcmp(path, "") == 0;
  30. if(readFromConsole) {
  31. printf("请输入二叉树的先序序列,如果没有子结点,使用^代替:");
  32. CreateTree(T, NULL);
  33. } else {
  34. // 打开文件,准备读取测试数据
  35. fp = fopen(path, "r");
  36. if(fp == NULL) {
  37. return ERROR;
  38. }
  39. CreateTree(T, fp);
  40. fclose(fp);
  41. }
  42. return OK;
  43. }
  44. /*
  45. * 判空
  46. *
  47. * 判断二叉树是否为空树。
  48. */
  49. Status BiTreeEmpty(BiTree T) {
  50. return T == NULL ? TRUE : FALSE;
  51. }
  52. /*
  53. * 树深
  54. *
  55. * 返回二叉树的深度(层数)。
  56. */
  57. int BiTreeDepth(BiTree T) {
  58. int LD, RD;
  59. if(T == NULL) {
  60. return 0; // 空树深度为0
  61. } else {
  62. LD = BiTreeDepth(T->lchild); // 求左子树深度
  63. RD = BiTreeDepth(T->rchild); // 求右子树深度
  64. return (LD >= RD ? LD : RD) + 1;
  65. }
  66. }
  67. /*
  68. * 取值
  69. *
  70. * 返回二叉树中指定结点的值。
  71. */
  72. TElemType Value(BiTree T, TElemType e) {
  73. BiTree p;
  74. // 遇到空树则无需继续计算
  75. if(BiTreeEmpty(T)) {
  76. return '\0';
  77. }
  78. // 获取结点e的指针
  79. p = EPtr(T, e);
  80. // 如果没有找到元素e
  81. if(p == NULL) {
  82. return '\0';
  83. } else {
  84. return p->data;
  85. }
  86. }
  87. /*
  88. * 赋值
  89. *
  90. * 为二叉树指定的结点赋值。
  91. */
  92. Status Assign(BiTree T, TElemType e, TElemType value) {
  93. BiTree p;
  94. // 遇到空树则无需继续计算
  95. if(BiTreeEmpty(T)) {
  96. return ERROR;
  97. }
  98. // 获取结点e的指针
  99. p = EPtr(T, e);
  100. // 如果没有找到元素e
  101. if(p == NULL) {
  102. return ERROR;
  103. } else {
  104. // 进行赋值
  105. p->data = value;
  106. return OK;
  107. }
  108. }
  109. /*
  110. * 根
  111. *
  112. * 返回二叉树的根结点。
  113. */
  114. TElemType Root(BiTree T) {
  115. // 遇到空树则无需继续计算
  116. if(BiTreeEmpty(T)) {
  117. return '\0';
  118. }
  119. return T->data;
  120. }
  121. /*
  122. * 双亲
  123. *
  124. * 返回二叉树中结点e的双亲结点。
  125. */
  126. TElemType Parent(BiTree T, TElemType e) {
  127. BiTree p;
  128. // 遇到空树则无需继续计算
  129. if(BiTreeEmpty(T)) {
  130. return '\0';
  131. }
  132. // 获取结点e的双亲结点的指针
  133. p = PPtr(T, e);
  134. // 如果没有找到元素e的双亲
  135. if(p == NULL) {
  136. return '\0';
  137. } else {
  138. return p->data;
  139. }
  140. }
  141. /*
  142. * 左孩子
  143. *
  144. * 返回二叉树中结点e的左孩子结点。
  145. */
  146. TElemType LeftChild(BiTree T, TElemType e) {
  147. BiTree p;
  148. // 遇到空树则无需继续计算
  149. if(BiTreeEmpty(T)) {
  150. return '\0';
  151. }
  152. // 获取结点e的指针
  153. p = EPtr(T, e);
  154. // 如果找到了元素e
  155. if(p != NULL && p->lchild != NULL) {
  156. return p->lchild->data;
  157. }
  158. return '\0';
  159. }
  160. /*
  161. * 右孩子
  162. *
  163. * 返回二叉树中结点e的右孩子结点。
  164. */
  165. TElemType RightChild(BiTree T, TElemType e) {
  166. BiTree p;
  167. // 遇到空树则无需继续计算
  168. if(BiTreeEmpty(T)) {
  169. return '\0';
  170. }
  171. // 获取结点e的指针
  172. p = EPtr(T, e);
  173. // 如果找到了元素e
  174. if(p != NULL && p->rchild != NULL) {
  175. return p->rchild->data;
  176. }
  177. return '\0';
  178. }
  179. /*
  180. * 左兄弟
  181. *
  182. * 返回二叉树中结点e的左兄弟结点。
  183. */
  184. TElemType LeftSibling(BiTree T, TElemType e) {
  185. BiTree p;
  186. // 遇到空树则无需继续计算
  187. if(BiTreeEmpty(T)) {
  188. return '\0';
  189. }
  190. // 获取结点e的双亲结点的指针
  191. p = PPtr(T, e);
  192. // 如果找到了元素e的双亲
  193. if(p != NULL && p->lchild != NULL) {
  194. return p->lchild->data;
  195. }
  196. return '\0';
  197. }
  198. /*
  199. * 右兄弟
  200. *
  201. * 返回二叉树中结点e的右兄弟结点。
  202. */
  203. TElemType RightSibling(BiTree T, TElemType e) {
  204. BiTree p;
  205. // 遇到空树则无需继续计算
  206. if(BiTreeEmpty(T)) {
  207. return '\0';
  208. }
  209. // 获取结点e的双亲结点的指针
  210. p = PPtr(T, e);
  211. // 如果找到了元素e的双亲
  212. if(p != NULL && p->rchild != NULL) {
  213. return p->rchild->data;
  214. }
  215. return '\0';
  216. }
  217. /*
  218. * 插入
  219. *
  220. * 已知c为与T不相交的非空二叉树,且c的右子树为空,
  221. * 根据LR的取值(0或1),将c插入为T中结点p的左子树/右子树,
  222. * 并且,将p结点原有的左子树/右子树嫁接为二叉树c的右子树。
  223. */
  224. Status InsertChild(BiTree T, TElemType p, int LR, BiTree c) {
  225. BiTree p_ptr;
  226. // 如果待插入的树为空树则无需继续计算
  227. if(BiTreeEmpty(c)) {
  228. return ERROR;
  229. }
  230. // 获取结点p的指针
  231. p_ptr = EPtr(T, p);
  232. // 如果p结点不存在,则返回错误提示
  233. if(p_ptr == NULL) {
  234. return ERROR;
  235. }
  236. // 将c插入为p的左子树
  237. if(LR==0) {
  238. // 如果p处存在左子树,则摘下p的左子树,插入为c的右子树
  239. if(p_ptr->lchild!=NULL) {
  240. c->rchild = p_ptr->lchild;
  241. }
  242. p_ptr->lchild = c;
  243. } else {
  244. // 如果p处存在右子树,则摘下p的右子树,插入为c的右子树
  245. if(p_ptr->rchild!=NULL) {
  246. c->rchild = p_ptr->rchild;
  247. }
  248. p_ptr->rchild = c;
  249. }
  250. return OK;
  251. }
  252. /*
  253. * 删除
  254. *
  255. * 根据LR的取值(0或1),删除结点p的左子树/右子树。
  256. */
  257. Status DeleteChild(BiTree T, TElemType p, int LR) {
  258. BiTree p_ptr;
  259. // 遇到空树则无需继续计算
  260. if(BiTreeEmpty(T)) {
  261. return ERROR;
  262. }
  263. // 获取结点p的指针
  264. p_ptr = EPtr(T, p);
  265. // 如果p结点不存在,则返回错误提示
  266. if(p_ptr == NULL) {
  267. return ERROR;
  268. }
  269. // 如果需要删除p的左子树
  270. if(LR == 0) {
  271. ClearBiTree(&(p_ptr->lchild));
  272. // 如果需要删除p的右子树
  273. } else {
  274. ClearBiTree(&(p_ptr->rchild));
  275. }
  276. return OK;
  277. }
  278. /*
  279. * ████████ 算法6.1 ████████
  280. *
  281. * 先序遍历
  282. */
  283. Status PreOrderTraverse(BiTree T, Status(Visit)(TElemType)) {
  284. Status status;
  285. status = PreTraverse(T, Visit);
  286. printf("\n");
  287. return status;
  288. }
  289. /*
  290. * 中序遍历
  291. */
  292. Status InOrderTraverse(BiTree T, Status(Visit)(TElemType)) {
  293. Status status;
  294. status = InTraverse(T, Visit);
  295. printf("\n");
  296. return status;
  297. }
  298. /*
  299. * ████████ 算法6.2 ████████
  300. *
  301. * 中序遍历
  302. *
  303. *【注】
  304. * 非递归算法
  305. */
  306. Status InOrderTraverse_2(BiTree T, Status(Visit)(TElemType)) {
  307. SqStack S;
  308. BiTree p;
  309. InitStack(&S);
  310. Push(&S, T); // 根指针入栈
  311. while(!StackEmpty(S)) {
  312. // 向左走到尽头
  313. while(GetTop(S, &p) && p != NULL) {
  314. Push(&S, p->lchild);
  315. }
  316. Pop(&S, &p); // 空指针退栈
  317. if(!StackEmpty(S)) {
  318. // 访问结点
  319. Pop(&S, &p);
  320. if(!Visit(p->data)) {
  321. return ERROR;
  322. }
  323. // 向右一步
  324. Push(&S, p->rchild);
  325. }
  326. }
  327. printf("\n");
  328. return OK;
  329. }
  330. /*
  331. * ████████ 算法6.3 ████████
  332. *
  333. * 中序遍历
  334. *
  335. *【注】
  336. * 非递归算法
  337. */
  338. Status InOrderTraverse_3(BiTree T, Status(Visit)(TElemType)) {
  339. SqStack S;
  340. BiTree p;
  341. InitStack(&S);
  342. p = T;
  343. while(p != NULL || !StackEmpty(S)) {
  344. if(p != NULL) {
  345. Push(&S, p); // 根指针进栈
  346. p = p->lchild; // 遍历左子树
  347. } else {
  348. // 访问结点
  349. Pop(&S, &p);
  350. if(!Visit(p->data)) {
  351. return ERROR;
  352. }
  353. p = p->rchild;
  354. }
  355. }
  356. printf("\n");
  357. return OK;
  358. }
  359. /*
  360. * 后序遍历
  361. */
  362. Status PostOrderTraverse(BiTree T, Status(Visit)(TElemType)) {
  363. Status status;
  364. status = PostTraverse(T, Visit);
  365. printf("\n");
  366. return status;
  367. }
  368. /*
  369. * 层序遍历
  370. */
  371. Status LevelOrderTraverse(BiTree T, Status(Visit)(TElemType)) {
  372. LinkQueue Q;
  373. BiTree e;
  374. // 二叉树为空
  375. if(T == NULL) {
  376. printf("\n");
  377. return OK;
  378. }
  379. // 借助队列实现层序遍历
  380. InitQueue(&Q);
  381. // 根指针入队
  382. EnQueue(&Q, T);
  383. // 一直循环,直到队列为空
  384. while(!QueueEmpty(Q)) {
  385. DeQueue(&Q, &e);
  386. // 访问元素
  387. if(!Visit(e->data)) {
  388. return ERROR;
  389. }
  390. // 左孩子入队
  391. if(e->lchild != NULL) {
  392. EnQueue(&Q, e->lchild);
  393. }
  394. // 右孩子入队
  395. if(e->rchild != NULL) {
  396. EnQueue(&Q, e->rchild);
  397. }
  398. }
  399. printf("\n");
  400. return OK;
  401. }
  402. /*━━━━━━━━━━━━━━━━━━━━━━ 仅限内部使用的函数 ━━━━━━━━━━━━━━━━━━━━━━*/
  403. // 创建二叉树的内部函数
  404. static void CreateTree(BiTree* T, FILE* fp) {
  405. char ch;
  406. // 读取当前结点的值
  407. if(fp == NULL) {
  408. scanf("%c", &ch);
  409. } else {
  410. ReadData(fp, "%c", &ch);
  411. }
  412. if(ch == '^') {
  413. *T = NULL;
  414. } else {
  415. // 生成根结点
  416. *T = (BiTree) malloc(sizeof(BiTNode));
  417. if(!(*T)) {
  418. exit(OVERFLOW);
  419. }
  420. (*T)->data = ch;
  421. CreateTree(&((*T)->lchild), fp); // 创建左子树
  422. CreateTree(&((*T)->rchild), fp); // 创建右子树
  423. }
  424. }
  425. // 返回指向二叉树结点e的指针
  426. static BiTree EPtr(BiTree T, TElemType e) {
  427. BiTree pl, pr;
  428. if(T == NULL) {
  429. return NULL;
  430. }
  431. // 如果找到了目标结点,直接返回其指针
  432. if(T->data == e) {
  433. return T;
  434. }
  435. // 在左子树中查找e
  436. pl = EPtr(T->lchild, e);
  437. if(pl != NULL) {
  438. return pl;
  439. }
  440. // 在右子树中查找e
  441. pr = EPtr(T->rchild, e);
  442. if(pr != NULL) {
  443. return pr;
  444. }
  445. return NULL;
  446. }
  447. // 返回指向二叉树结点e的双亲结点的指针
  448. static BiTree PPtr(BiTree T, TElemType e) {
  449. BiTree pl, pr;
  450. if(T == NULL || T->data == e) {
  451. return NULL;
  452. }
  453. // e是T的左孩子
  454. if(T->lchild != NULL && T->lchild->data == e) {
  455. return T;
  456. }
  457. // e是T的右孩子
  458. if(T->rchild != NULL && T->rchild->data == e) {
  459. return T;
  460. }
  461. // 在左子树中查找e
  462. pl = PPtr(T->lchild, e);
  463. if(pl != NULL) {
  464. return pl;
  465. }
  466. // 在右子树中查找e
  467. pr = PPtr(T->rchild, e);
  468. if(pr != NULL) {
  469. return pr;
  470. }
  471. return NULL;
  472. }
  473. // 先序遍历的内部实现
  474. static Status PreTraverse(BiTree T, Status(Visit)(TElemType)) {
  475. if(T) {
  476. if(Visit(T->data)) {
  477. if(PreTraverse(T->lchild, Visit)) {
  478. if(PreTraverse(T->rchild, Visit)) {
  479. return OK;
  480. }
  481. }
  482. }
  483. return ERROR;
  484. // 遇到空树则无需继续计算
  485. } else {
  486. return OK;
  487. }
  488. }
  489. // 中序遍历的内部实现
  490. static Status InTraverse(BiTree T, Status(Visit)(TElemType)) {
  491. if(T) {
  492. if(InTraverse(T->lchild, Visit)) {
  493. if(Visit(T->data)) {
  494. if(InTraverse(T->rchild, Visit)) {
  495. return OK;
  496. }
  497. }
  498. }
  499. return ERROR;
  500. // 遇到空树则无需继续计算
  501. } else {
  502. return OK;
  503. }
  504. }
  505. // 后序遍历的内部实现
  506. static Status PostTraverse(BiTree T, Status(Visit)(TElemType)) {
  507. if(T) {
  508. if(PostTraverse(T->lchild, Visit)) {
  509. if(PostTraverse(T->rchild, Visit)) {
  510. if(Visit(T->data)) {
  511. return OK;
  512. }
  513. }
  514. }
  515. return ERROR;
  516. // 遇到空树则无需继续计算
  517. } else {
  518. return OK;
  519. }
  520. }
  521. /*━━━━━━━━━━━━━━━━━━━━━━ 图形化输出 ━━━━━━━━━━━━━━━━━━━━━━*/
  522. // 以图形化形式输出当前结构,仅限内部测试使用
  523. void PrintTree(BiTree T) {
  524. int level, width;
  525. int i, j, k, w;
  526. int begin;
  527. int distance;
  528. TElemType** tmp;
  529. LinkQueue Q;
  530. BiTree e;
  531. // 遇到空树则无需继续计算
  532. if(BiTreeEmpty(T)) {
  533. printf("\n");
  534. return;
  535. }
  536. level = BiTreeDepth(T); // (完全)二叉树结构高度
  537. width = (int)pow(2, level)-1; // (完全)二叉树结构宽度
  538. // 动态创建行
  539. tmp = (TElemType**)malloc(level* sizeof(TElemType*));
  540. // 动态创建列
  541. for(i = 0; i < level; i++) {
  542. tmp[i] = (TElemType*)malloc(width* sizeof(TElemType));
  543. // 初始化内存值为空字符
  544. memset(tmp[i], '\0', width);
  545. }
  546. // 借助队列实现层序遍历
  547. InitQueue(&Q);
  548. EnQueue(&Q, T);
  549. // 遍历树中所有元素,将其安排到二维数组tmp中合适的位置
  550. for(i = 0; i < level; i++) {
  551. w = (int) pow(2, i); // 二叉树当前层的宽度
  552. distance = width / w; // 二叉树当前层的元素间隔
  553. begin = width / (int) pow(2, i + 1); // 二叉树当前层首个元素之前的空格数
  554. for(k = 0; k < w; k++) {
  555. DeQueue(&Q, &e);
  556. if(e == NULL) {
  557. EnQueue(&Q, NULL);
  558. EnQueue(&Q, NULL);
  559. } else {
  560. j = begin + k * (1 + distance);
  561. tmp[i][j] = e->data;
  562. // 左孩子入队
  563. EnQueue(&Q, e->lchild);
  564. // 右孩子入队
  565. EnQueue(&Q, e->rchild);
  566. }
  567. }
  568. }
  569. for(i = 0; i < level; i++) {
  570. for(j = 0; j < width; j++) {
  571. if(tmp[i][j] != '\0') {
  572. printf("%c", tmp[i][j]);
  573. } else {
  574. printf(" ");
  575. }
  576. }
  577. printf("\n");
  578. }
  579. }

线索二叉树 __*

/* 线索二叉树结点定义 */
typedef struct BiThrNode {
    TElemType data;             // 结点元素
    struct BiThrNode* lchild;   // 左孩子指针
    struct BiThrNode* rchild;   // 右孩子指针
    PointerTag LTag;            // 左指针标记
    PointerTag RTag;            // 右指针标记

    struct BiThrNode* parent;   // 双亲结点指针,仅在非递归后序遍历后序后继线索二叉树时使用
} BiThrNode;

/*
 * 创建
 *
 * 按照预设的定义来创建二叉树。
 * 这里约定使用【先序序列】来创建二叉树。
 *
 *
 *【备注】
 *
 * 教材中默认从控制台读取数据。
 * 这里为了方便测试,避免每次运行都手动输入数据,
 * 因而允许选择从预设的文件path中读取测试数据。
 *
 * 如果需要从控制台读取数据,则path为NULL或者为空串,
 * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
 */
Status CreateBiTree(BiThrTree* T, char* path) {
    FILE* fp;
    int readFromConsole;    // 是否从控制台读取数据

    // 如果没有文件路径信息,则从控制台读取输入
    readFromConsole = path == NULL || strcmp(path, "") == 0;

    if(readFromConsole) {
        printf("请输入二叉树的先序序列,如果没有子结点,使用^代替:");
        CreateTree(T, NULL);
    } else {
        // 打开文件,准备读取测试数据
        fp = fopen(path, "r");
        if(fp == NULL) {
            return ERROR;
        }
        CreateTree(T, fp);
        fclose(fp);
    }

    return OK;
}

/*
 * ████████ 算法6.6 ████████
 *
 * 中序遍历二叉树T,并将其全线索化为线索二叉树Thrt。
 * 注:这里的线索包括前驱线索与后继线索。
 */
Status InOrderThreading(BiThrTree* Thrt, BiThrTree T) {
    // 建立头结点
    *Thrt = (BiThrTree) malloc(sizeof(BiThrNode));
    if(!*Thrt) {
        exit(OVERFLOW);
    }

    (*Thrt)->data = '\0';

    (*Thrt)->LTag = Link;       // 左孩子,需要指向二叉树的根结点
    (*Thrt)->RTag = Thread;     // 右指针,需要指向中序序列最后一个元素,以便逆中序遍历线索二叉树

    (*Thrt)->rchild = *Thrt;

    // 若二叉树为空,则左指针回指
    if(!T) {
        (*Thrt)->lchild = *Thrt;
    } else {
        (*Thrt)->lchild = T;    // 指向二叉树头结点
        pre = *Thrt;            // 记录前驱信息,初始化为线索二叉树头结点

        InTheading(T);          // 中序遍历,以进行中序线索化

        pre->rchild = *Thrt;    // 最后一个结点指回线索二叉树头结点
        pre->RTag = Thread;     // 最后一个结点线索化
        (*Thrt)->rchild = pre;  // 头结点指向最后一个结点,建立双向联系
    }

    return OK;

}

/*
 * ████████ 算法6.5 ████████
 *
 * 中序遍历中序全线索二叉树(非递归算法)。
 *
 * 注:该方法可以验证后继线索是否正确
 */
Status InOrderTraverse_Thr(BiThrTree T, Status(Visit)(TElemType)) {
    BiThrTree p = T->lchild;    // p指向二叉树根结点(不同于线索二叉树的头结点)

    // 空树或遍历结束时,p==T
    while(p != T) {
        // 如果存在左孩子,则持续向左访问
        while(p->LTag == Link) {
            p = p->lchild;
        }

        // 访问左子树为空的结点(最左边)
        if(!Visit(p->data)) {
            return ERROR;
        }

        // 如果存在后继线索(即没有右子树)
        while(p->RTag == Thread && p->rchild != T) {
            p = p->rchild;   // 将p指向其后继
            Visit(p->data);  // 访问后继结点
        }

        // 访问右子树
        p = p->rchild;
    }

    printf("\n");

    return OK;
}


/*━━━━━━━━━━━━━━━━━━━━━━ 仅限内部使用的函数 ━━━━━━━━━━━━━━━━━━━━━━*/

// 创建二叉树的内部函数
static void CreateTree(BiThrTree* T, FILE* fp) {
    char ch;

    // 读取当前结点的值
    if(fp == NULL) {
        scanf("%c", &ch);
    } else {
        ReadData(fp, "%c", &ch);
    }

    if(ch == '^') {
        *T = NULL;
    } else {
        // 生成根结点
        *T = (BiThrTree) malloc(sizeof(BiThrNode));
        if(!(*T)) {
            exit(OVERFLOW);
        }
        (*T)->data = ch;
        CreateTree(&((*T)->lchild), fp); // 创建左子树
        CreateTree(&((*T)->rchild), fp); // 创建右子树
    }
}

/*
 * ████████ 算法6.7 ████████
 *
 * 中序全线索化的内部实现
 */
static void InTheading(BiThrTree p) {
    if(p == NULL) {
        return;
    }

    InTheading(p->lchild);  // 线索化左子树

    // 如果当前结点的左子树为空,则需要建立前驱线索
    if(!p->lchild) {
        p->LTag = Thread;
        p->lchild = pre;

        /*
         * 如果左子树不为空,添加左孩子标记。
         * 教材中缺少这一步骤,这会导致出现一些幽灵BUG。
         * 这里的Link枚举值是零,如果编译器在动态分配内存后恰好把该标记初始化为0,
         * 那么效果跟手动设置Link是一样的。但如果编译器没有初始化零值,那么就会出BUG。
         */
    } else {
        p->LTag = Link;
    }

    // 如果前驱结点的右子树为空,则为前驱结点建立后继线索
    if(!pre->rchild) {
        pre->RTag = Thread;
        pre->rchild = p;

        /*
         * 如果右子树不为空,添加右孩子标记。
         * 教材中缺少这一步骤,这会导致出现一些幽灵BUG,理由同上。
         */
    } else {
        pre->RTag = Link;
    }

    pre = p;                // pre向前挪一步

    InTheading(p->rchild);  // 线索化右子树
}


/*━━━━━━━━━━━━━━━━━━━━━━ 辅助函数 ━━━━━━━━━━━━━━━━━━━━━━*/

/*
 * 逆中序遍历中序全线索二叉树(非递归算法)。
 *
 * 注:该方法可以验证前驱线索是否正确
 */
Status InOrderTraverse_Thr_Inverse(BiThrTree T, Status(Visit)(TElemType)) {
    BiThrTree p = T->rchild;    // p指向二叉树中序遍历的最后一个结点,这也相当于是T的左子树上中序遍历的最后一个结点

    // 空树或遍历结束时,p==T
    while(p != T) {
        // 访问结点p,该结点是某棵左子树上中序遍历的最后一个结点
        if(!Visit(p->data)) {
            return ERROR;
        }

        // 如果存在前驱线索(即没有左子树)
        while(p->LTag == Thread && p->lchild != T) {
            p = p->lchild;   // 将p指向其前驱
            Visit(p->data);  // 访问前驱结点
        }

        // 向左前进一步,可能遇到左子树,也可能遇到终点T
        p = p->lchild;

        // 如果没有遇到终点,即存在左子树,则找到该左子树上中序遍历的最后一个结点
        if(p != T) {
            while(p->RTag == Link) {
                p = p->rchild;
            }
        }
    }

    printf("\n");

    return OK;
}
  • 二叉树和森林的转换__ * ```c / (双亲)树的结点定义 / typedef struct PTNode { TElemType data; int parent; // 双亲位置域 } PTNode;

/*

  • (双亲)树类型定义 【注】
  • 1.树中结点在nodes中”紧邻”存储,没有空隙
  • 2.树根r可能出现在nodes的任意位置
  • 3.除根结点外,其他结点依次按层序顺着根结点往下排列(这一点与教材图示可能会有区别)
  • 4.nodes数组是循环使用的(这一点教材未提到)
  • 5.这里假设nodes空间是足够大的,可以视需求将其改为动态分配存储 */ typedef struct { PTNode nodes[MAX_TREE_SIZE]; // 存储树中结点 int r; // 树根位置(索引) int n; // 树的结点数 } PTree;

/ 树中某个结点的信息 / typedef struct{ int row; // 当前结点所处的行 int col; // 当前结点所处的列 int childIndex; // 当前结点是第几个孩子 int firstChild; // 当前结点的第一个孩子在树中的索引 int lastChild; // 当前结点的最后一个孩子在树中的索引 } Pos;

```c
/* 孩子结点定义 */
typedef struct CTNode {
    int child;              // 该孩子在树中的索引
    struct CTNode* next;    // 指向下一个孩子
} CTNode;

/* 指向孩子结点的指针 */
typedef CTNode* ChildPtr;

/* (双亲)树的结点定义 */
typedef struct {
    int parent;             // 双亲位置域
    TElemType data;         // 当前结点
    ChildPtr firstchild;    // 孩子链表头指针
} CTBox;

/*
 * (双亲)树类型定义
 *
 *【注】
 * 1.树中结点在nodes中"紧邻"存储,没有空隙
 * 2.树根r可能出现在nodes的任意位置
 * 3.除根结点外,其他结点依次按层序顺着根结点往下排列(这一点与教材图示可能会有区别)
 * 4.nodes数组是循环使用的(这一点教材未提到)
 * 5.这里假设nodes空间是足够大的,可以视需求将其改为动态分配存储
 */
typedef struct {
    CTBox nodes[MAX_TREE_SIZE]; // 存储树中结点
    int r;  // 树根位置(索引)
    int n;  // 树的结点数
} CTree;
/* (孩子-兄弟)树的结点定义 */
typedef struct CSNode {
    TElemType data;
    struct CSNode* firstchild;  // 指向长子
    struct CSNode* nextsibling; // 指向右兄弟
} CSNode;

/* (孩子-兄弟)树类型定义 */
typedef CSNode* CSTree;
  • 二叉树的基本操作(遍历、查找等)__* ```c /*
    • 二叉树类型定义,0号单元存储根结点。 【注】
    • 在二叉树的顺序结构中,其元素是按照完全顺序二叉树的层序序列排列的。 */ typedef TElemType SqBiTree[MAX_TREE_SIZE];

// 先序遍历的内部实现 static Status PreTraverse(SqBiTree T, Status(Visit)(TElemType), int i) { // 越界 if(i >= MAX_TREE_SIZE) { return ERROR; }

if(T[i]) {
    if(Visit(T[i])) {
        if(PreTraverse(T, Visit, 2 * i + 1)) {
            if(PreTraverse(T, Visit, 2 * i + 2)) {
                return OK;
            }
        }
    }

    return ERROR;

    // 遇到空树则无需继续计算
} else {
    return OK;
}

}

// 中序遍历的内部实现 static Status InTraverse(SqBiTree T, Status(Visit)(TElemType), int i) { // 越界 if(i >= MAX_TREE_SIZE) { return ERROR; }

if(T[i]) {
    if(InTraverse(T, Visit, 2 * i + 1)) {
        if(Visit(T[i])) {
            if(InTraverse(T, Visit, 2 * i + 2)) {
                return OK;
            }
        }

    }

    return ERROR;

    // 遇到空树则无需继续计算
} else {
    return OK;
}

}

// 后序遍历的内部实现 static Status PostTraverse(SqBiTree T, Status(Visit)(TElemType), int i) { // 越界 if(i >= MAX_TREE_SIZE) { return ERROR; }

if(T[i]) {
    if(PostTraverse(T, Visit, 2 * i + 1)) {
        if(PostTraverse(T, Visit, 2 * i + 2)) {
            if(Visit(T[i])) {
                return OK;
            }

        }
    }

    return ERROR;

    // 遇到空树则无需继续计算
} else {
    return OK;
}

}

/*

  • 取值 *
  • 返回二叉树中指定结点的值。 */ TElemType Value(SqBiTree T, TElemType e) { int index;

    // 遇到空树则无需继续计算 if(BiTreeEmpty(T)) {

     return '\0';
    

    }

    // 获取结点e的索引 index = EIndex(T, e, 0);

    // 如果没有找到元素e if(index == -1) {

     return '\0';
    

    } else {

     return T[index];
    

    } }

// 返回二叉树结点e的索引号,i是结点p的索引号 static int EIndex(SqBiTree T, TElemType e, int i) { int cl, cr;

// 已经越界
if(i >= MAX_TREE_SIZE) {
    return -1;
}

// e的值不合规
if(e == '\0') {
    return -1;
}

// 找到了元素e
if(T[i] == e) {
    return i;
}

// 在左子树中查找
cl = EIndex(T, e, 2 * i + 1);
if(cl != -1) {
    return cl;
}

// 在右子树中查找
cr = EIndex(T, e, 2 * i + 2);
if(cr != -1) {
    return cr;
}

return -1;

}

```c
/* 二叉树结点定义 */
typedef struct BiTNode {
    TElemType data;             // 结点元素
    struct BiTNode* lchild;     // 左孩子指针
    struct BiTNode* rchild;     // 右孩子指针
} BiTNode;

/* 指向二叉树结点的指针 */
typedef BiTNode* BiTree;

// 先序遍历的内部实现
static Status PreTraverse(BiTree T, Status(Visit)(TElemType)) {
    if(T) {
        if(Visit(T->data)) {
            if(PreTraverse(T->lchild, Visit)) {
                if(PreTraverse(T->rchild, Visit)) {
                    return OK;
                }
            }
        }

        return ERROR;

        // 遇到空树则无需继续计算
    } else {
        return OK;
    }
}

// 中序遍历的内部实现
static Status InTraverse(BiTree T, Status(Visit)(TElemType)) {
    if(T) {
        if(InTraverse(T->lchild, Visit)) {
            if(Visit(T->data)) {
                if(InTraverse(T->rchild, Visit)) {
                    return OK;
                }
            }
        }

        return ERROR;

        // 遇到空树则无需继续计算
    } else {
        return OK;
    }
}

// 后序遍历的内部实现
static Status PostTraverse(BiTree T, Status(Visit)(TElemType)) {
    if(T) {
        if(PostTraverse(T->lchild, Visit)) {
            if(PostTraverse(T->rchild, Visit)) {
                if(Visit(T->data)) {
                    return OK;
                }
            }
        }

        return ERROR;

        // 遇到空树则无需继续计算
    } else {
        return OK;
    }
}

// 返回指向二叉树结点e的指针
static BiTree EPtr(BiTree T, TElemType e) {
    BiTree pl, pr;

    if(T == NULL) {
        return NULL;
    }

    // 如果找到了目标结点,直接返回其指针
    if(T->data == e) {
        return T;
    }

    // 在左子树中查找e
    pl = EPtr(T->lchild, e);
    if(pl != NULL) {
        return pl;
    }

    // 在右子树中查找e
    pr = EPtr(T->rchild, e);
    if(pr != NULL) {
        return pr;
    }

    return NULL;
}

Huffman树构造(带权路径长度)

/* 赫夫曼树结点定义,是一种双亲存储结构 */
typedef struct {
    unsigned int weight;    // 权值
    unsigned int parent;    // 双亲位置
    unsigned int lchild;    // 左孩子位置
    unsigned int rchild;    // 右孩子位置
} HTNode;

/*
 * 赫夫曼树类型定义
 *
 *【注】
 * 1.0号单元的weight域指示赫夫曼树的结点数量
 * 2.其存储空间会动态分配
 */
typedef HTNode* HuffmanTree;

/*
 * 赫夫曼编码表的类型定义,包含了多个字符的赫夫曼编码
 *
 *【注】
 * 1.0号单元是弃用的
 * 2.存储空间需要动态分配
 */
typedef char** HuffmanCode;

/*
 * 初始化环境
 *
 * 主要用来初始化权值信息。
 *
 *
 *【备注】
 *
 * 教材中默认从控制台读取数据。
 * 这里为了方便测试,避免每次运行都手动输入数据,
 * 因而允许选择从预设的文件path中读取测试数据。
 *
 * 如果需要从控制台读取数据,则path为NULL或者为空串,
 * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
 */
Status InitEnvironment(int** w, int* n, char* path) {
    FILE* fp;
    int readFromConsole;    // 是否从控制台读取数据

    // 如果没有文件路径信息,则从控制台读取输入
    readFromConsole = path == NULL || strcmp(path, "") == 0;

    if(readFromConsole) {
        printf("请输入赫夫曼树结点信息...\n");
        Init(w, n, NULL);
    } else {
        // 打开文件,准备读取测试数据
        fp = fopen(path, "r");
        if(fp == NULL) {
            return ERROR;
        }
        Init(w, n, fp);
        fclose(fp);
    }

    return OK;
}

/*
 * ████████ 算法6.12 ████████
 *
 * 编码
 * 1.根据指定的n个权值信息w来创建赫夫曼树HT。
 * 2.由赫夫曼树HT逆序计算赫夫曼编码值HC。
 *
 *【注】
 * 该算法与【算法6.13】作用一致。
 */
Status HuffmanCodeing_1(HuffmanTree* HT, HuffmanCode* HC, int* w, int n) {
    int m, i;
    HuffmanTree p;
    int s1, s2;
    char* cd;
    int start, c;
    unsigned int f;

    if(n <= 1) {
        return ERROR;
    }

    // 计算赫夫曼树结点数
    m = 2 * n - 1;

    *HT = (HuffmanTree) malloc((m + 1) * sizeof(HTNode));   // 0号单元未使用,但其weight域记录了原始的权值数量
    (*HT)[0].weight = n;

    // 记录权值信息(注意p应当指向索引1处,因为0号单元弃用了,而教材中错误地指向了0号单元)
    for(p = (*HT) + 1, i = 1; i <= n; ++i, ++p, ++w) {
        (*p).weight = *w;
        (*p).parent = 0;
        (*p).lchild = 0;
        (*p).rchild = 0;
    }

    // 后面的部分需要计算
    for(; i <= m; ++i, ++p) {
        (*p).weight = 0;
        (*p).parent = 0;
        (*p).lchild = 0;
        (*p).rchild = 0;
    }

    // 建赫夫曼树
    for(i = n + 1; i <= m; ++i) {
        // 在HT[1,i-1]中选择parent为0(未加入树),且weight最小的两个结点,其序号分别为s1和s2
        Select(*HT, i - 1, &s1, &s2);

        (*HT)[s1].parent = i;
        (*HT)[s2].parent = i;
        (*HT)[i].lchild = s1;
        (*HT)[i].rchild = s2;
        (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
    }


    /* 从叶子到根逆向求每个字符的赫夫曼编码 */

    (*HC) = (HuffmanCode) malloc((n + 1) * sizeof(char*));  // 分配n个字符编码的头指针向量(0号单元弃用)
    cd = (char*) malloc(n * sizeof(char));  // 分配求编码的工作空间(每个编码长度最大为n-1)
    cd[n - 1] = '\0';   // 编码结束符

    // 逐个字符求赫夫曼编码
    for(i = 1; i <= n; ++i) {
        start = n - 1;    // 编码结束符位置

        // 从叶子到根逆向求编码
        for(c = i, f = (*HT)[i].parent; f != 0; c = f, f = (*HT)[f].parent) {
            if((*HT)[f].lchild == c) {
                cd[--start] = '0';
            } else {
                cd[--start] = '1';
            }
        }

        (*HC)[i] = (char*) malloc((n - start) * sizeof(char));  // 为第i个字符编码分配空间
        strcpy((*HC)[i], &cd[start]);   // 从cd复制编码(串)到HC
    }

    free(cd);   // 释放工作空间

    return OK;
}

/*
 * ████████ 算法6.13 ████████
 *
 * 编码
 * 1.根据指定的n个权值信息w来创建赫夫曼树HT。
 * 2.先序遍历赫夫曼树HT计算赫夫曼编码值HC。
 *
 *【注】
 * 该算法与【算法6.12】作用一致。
 */
Status HuffmanCodeing_2(HuffmanTree* HT, HuffmanCode* HC, int* w, int n) {
    int m, i;
    HuffmanTree p;
    int s1, s2;
    unsigned int r;
    int cdlen;
    char* cd;
    HuffmanTree H;  // HT的一个副本

    if(n <= 1) {
        return ERROR;
    }

    // 计算赫夫曼树结点数
    m = 2 * n - 1;

    *HT = (HuffmanTree) malloc((m + 1) * sizeof(HTNode));   // 0号单元未使用,但其weight域记录了原始的权值数量
    (*HT)[0].weight = n;

    // 记录权值信息(注意p应当指向索引1处,因为0号单元弃用了,而教材中错误地指向了0号单元)
    for(p = (*HT) + 1, i = 1; i <= n; ++i, ++p, ++w) {
        (*p).weight = *w;
        (*p).parent = 0;
        (*p).lchild = 0;
        (*p).rchild = 0;
    }

    // 后面的部分需要计算
    for(; i <= m; ++i, ++p) {
        (*p).weight = 0;
        (*p).parent = 0;
        (*p).lchild = 0;
        (*p).rchild = 0;
    }

    // 建赫夫曼树
    for(i = n + 1; i <= m; ++i) {
        // 在HT[1,i-1]中选择parent为0(未加入树),且weight最小的两个结点,其序号分别为s1和s2
        Select(*HT, i - 1, &s1, &s2);

        (*HT)[s1].parent = i;
        (*HT)[s2].parent = i;
        (*HT)[i].lchild = s1;
        (*HT)[i].rchild = s2;
        (*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
    }


    /* 无栈非递归遍历赫夫曼树,求赫夫曼编码 */

    (*HC) = (HuffmanCode) malloc((n + 1) * sizeof(char*));  // 分配n个字符编码的头指针向量(0号单元弃用)
    cd = (char*) malloc(n * sizeof(char));  // 分配求编码的工作空间(每个编码长度最大为n-1)

    r = m;
    cdlen = 0;

    // 后续再赫夫曼树的副本上进行操作
    H = (HuffmanTree) malloc((m + 1) * sizeof(HTNode));
    for(i = 0; i <= m; ++i) {
        H[i] = (*HT)[i];
        H[i].weight = 0;
    }

    while(r != 0) {
        // 第一次访问此结点,需要向左遍历
        if(H[r].weight == 0) {
            H[r].weight = 1;

            // 存在左孩子
            if(H[r].lchild != 0) {
                // 向左走一步
                r = H[r].lchild;
                cd[cdlen++] = '0';

                // 不存在左孩子,也不存在右孩子,即遇到了叶子结点
            } else if(H[r].rchild == 0) {
                (*HC)[r] = (char*) malloc((cdlen + 1) * sizeof(char));
                cd[cdlen] = '\0';
                strcpy((*HC)[r], cd);   // 复制编码串
            }

            // 第二次访问此结点,需要向右遍历
        } else if(H[r].weight == 1) {
            H[r].weight = 2;

            if(H[r].rchild != 0) {
                r = H[r].rchild;
                cd[cdlen++] = '1';
            }

            // 第三次访问,需要回退到父结点
        } else {
            r = H[r].parent;    // 退回到父结点
            cdlen--;                // 编码长度减1
        }
    }

    return OK;
}

/*
 * 解码
 * 根据给定的n个赫夫曼编码HC,计算其代表的权值。
 */
Status HuffmanDecoding(HuffmanTree HT, HuffmanCode HC, int** w, int n) {
    int i, j, k;
    int r;
    char* s;

    if(n <= 0) {
        return ERROR;
    }

    (*w) = (int*) malloc(n * sizeof(int));

    // 根结点位置
    r = 2 * n - 1;

    for(i = 1; i <= n; i++) {
        s = HC[i];

        k = r;

        // 从根结点往下找
        for(j = 0; j < strlen(s); j++) {
            if(s[j] == '0') {
                k = HT[k].lchild;   // 向左
            } else if(s[j] == '1') {
                k = HT[k].rchild;   // 向右
            } else {
                return ERROR;
            }
        }

        (*w)[i - 1] = HT[k].weight;
    }

    return OK;
}


/*━━━━━━━━━━━━━━━━━━━━━━ 仅限内部使用的函数 ━━━━━━━━━━━━━━━━━━━━━━*/

// 初始化环境的内部实现
static Status Init(int** w, int* n, FILE* fp) {
    int i;

    if(fp == NULL) {
        printf("请输入赫夫曼树叶子结点数量:");
        scanf("%d", n);

        if(*n <= 0) {
            *w = NULL;
            return ERROR;
        }

        *w = (int*) malloc((*n) * sizeof(int));

        printf("请输入 %d 个权值信息:", *n);
        for(i = 0; i < *n; i++) {
            scanf("%d", *w + i);
        }
    } else {
        // 录入元素数量
        ReadData(fp, "%d", n);

        if(*n <= 0) {
            *w = NULL;
            return ERROR;
        }

        *w = (int*) malloc((*n) * sizeof(int));

        // 录入结点权值信息
        for(i = 0; i < *n; i++) {
            ReadData(fp, "%d", *w + i);
        }
    }

    return OK;
}

// 在赫夫曼树结点[1...end]中依次选出权值最小且未编入树的两个结点的序号s1、s2。
static void Select(HuffmanTree HT, int end, int* s1, int* s2) {
    int i;
    int w1, w2;

    w1 = w2 = INT_MAX;

    for(i = 1; i <= end; i++) {
        // 忽略已经加入树的结点
        if(HT[i].parent != 0) {
            continue;
        }

        if(HT[i].weight < w1) {
            *s2 = *s1;
            w2 = w1;

            *s1 = i;
            w1 = HT[i].weight;
        } else if(HT[i].weight >= w1 && HT[i].weight < w2) {
            *s2 = i;
            w2 = HT[i].weight;
        } else {
            // HT[i].weight>=w2
        }
    }
}


/*━━━━━━━━━━━━━━━━━━━━━━ 图形化输出 ━━━━━━━━━━━━━━━━━━━━━━*/

// 打印赫夫曼树结构。
void PrintHuffmanTree(HuffmanTree HT) {
    int i;

    printf("+-------+--------+--------+--------+--------+\n");
    printf("| index | weight | parent | lchild | rchild |\n");
    printf("+-------+--------+--------+--------+--------+\n");
    printf("|  %3d  |  %4d  |        |        |        |\n", 0, HT[0].weight);
    printf("+-------+--------+--------+--------+--------+\n");
    for(i = 1; i <= 2 * HT[0].weight - 1; i++) {
        printf("|  %3d  |  %4d  |   %2d   |   %2d   |   %2d   |\n", i, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild);
    }
    printf("+-------+--------+--------+--------+--------+\n");
}

// 打印赫夫曼编码。
void PrintHuffmanCode(HuffmanTree HT, HuffmanCode HC) {
    int i;

    printf("+-------+--------+-------------\n");
    printf("| index | weight | HuffmanCode \n");
    printf("+-------+--------+-------------\n");
    printf("|  %3d  |  %4d  | \n", 0, HT[0].weight);
    printf("+-------+--------+-------------\n");
    for(i = 1; i <= HT[0].weight; i++) {
        printf("|  %3d  |  %4d  -> %s\n", i, HT[i].weight, HC[i]);
    }
    printf("+-------+--------+-------------\n");
}

// 打印赫夫曼编码对应的权值信息。
void PrintWeight(HuffmanCode HC, int* w, int n) {
    int i;

    printf("+-------+-------------+--------+\n");
    printf("| index | HuffmanCode | weight |\n");
    printf("+-------+-------------+--------+\n");
    for(i = 1; i <= n; i++) {
        printf("|  %3d  | %11s |  %4d  |\n", i, HC[i], w[i - 1]);
    }
    printf("+-------+-------------+--------+\n");
}

第七章 图

图的定义(度、入度、初度)
复习提要(20级) - 图42由顶点集复习提要(20级) - 图43和边集E组成,记为复习提要(20级) - 图44#card=math&code=G%3D%28V%EF%BC%8CE%29&id=VWSyu)

  1. 复习提要(20级) - 图45#card=math&code=V%28G%29&id=jD9rp)表示图复习提要(20级) - 图46中顶点的有限非空集。用复习提要(20级) - 图47表示图复习提要(20级) - 图48中顶点的个数,也称为图复习提要(20级) - 图49的阶
  2. 复习提要(20级) - 图50#card=math&code=E%28G%29&id=XJr05)表示图复习提要(20级) - 图51中顶点之间的关系(边)集合。用复习提要(20级) - 图52表示图复习提要(20级) - 图53中边的条数。

度:以该顶点为一个端点的边数目
无向图中顶点复习提要(20级) - 图54的度是指依附于该顶点的边的条数,记为复习提要(20级) - 图55

有向图中顶点复习提要(20级) - 图56的度分为出度入度
入度(ID)是以顶点复习提要(20级) - 图57为终点的有向边的数目
出度(OD)是以顶点复习提要(20级) - 图58为起点的有向边的数目

  • 图的存储结构(邻接矩阵、邻接表)__*

邻接矩阵

// 图的类型
typedef enum {
    DG,     // 0-有向图
    DN,     // 1-有向网(带权值)
    UDG,    // 2-无向图
    UDN     // 3-无向网(带权值)
} GraphKind;

/*
 * 顶点关系类型
 *
 * 在无权图中,该值通常为0或1,表示两顶点是否直接连通;
 * 在有权图中,该值通常为权值。
 */
typedef int VRType;

// 边的相关附加信息
typedef struct {
    // 如果有的话,后续会添加相应的属性
} InfoType;

// 边的类型,每条边上可能有附加信息info
typedef struct ArcCell {
    VRType adj;  // 顶点关系,在有权图跟无权图中的含义不同
    InfoType* info; // 边的附加信息,通常忽略
} ArcCell;

/* 图/网的数组(邻接矩阵)存储表示类型定义 */
typedef struct {
    VertexType vexs[MAX_VERTEX_NUM];               // 顶点向量
    ArcCell arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM];  // 邻接矩阵
    int vexnum, arcnum;                            // 图/网的顶点数和弧数
    GraphKind kind;                                // 图的类型标志
} MGraph;

/*
 * ████████ 算法7.1 ████████
 *
 * 创建图/表
 *
 *【备注】
 *
 * 教材中默认从控制台读取数据。
 * 这里为了方便测试,避免每次运行都手动输入数据,
 * 因而允许选择从预设的文件path中读取测试数据。
 *
 * 如果需要从控制台读取数据,则path为NULL,或path[kind]为""。
 * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
 */
Status CreateGraph(MGraph* G, char* path[]) {
    int readFromConsole;    // 是否从控制台读取数据
    int kind;
    Status flag;

    printf("请输入图的类型(0-有向图 │ 1-有向网 │ 2-无向图 │ 3-无向网):");
    scanf("%d", &kind);

    // 类型不合规
    if(kind < 0 || kind > 3) {
        return ERROR;
    }

    // 如果没有文件路径信息,则从控制台读取输入
    readFromConsole = (path == NULL) || strcmp(path[kind], "") == 0;

    // 需要从文件读取
    if(readFromConsole) {
        (*G).kind = kind;   // 记录图/网的类型
    } else {
        // 打开文件,准备读取测试数据
        fp = fopen(path[kind], "r");
        if(fp == NULL) {
            return ERROR;
        }

        // 录入图的类型
        ReadData(fp, "%d", &((*G).kind));
    }

    // 随机创建有向图/网或无向图/网的一种
    switch((*G).kind) {
        case DG:
            flag = CreateDG(G);
            break;
        case DN:
            flag = CreateDN(G);
            break;
        case UDG:
            flag = CreateUDG(G);
            break;
        case UDN:
            flag = CreateUDN(G);
            break;
        default:
            flag = ERROR;
            break;
    }

    if(fp != NULL) {
        fclose(fp);
        fp = NULL;
    }

    return flag;
}

/*
 * 构造有向图
 */
static Status CreateDG(MGraph* G) {
    int i, j, k;
    ArcCell arcs = {0, NULL};   // 有向图每条边的初始值
    VertexType v1, v2;

    if(fp == NULL) {
        printf("请输入有向图的顶点数:");
        scanf("%d", &((*G).vexnum));
        printf("请输入有向图的弧数:");
        scanf("%d", &((*G).arcnum));
        printf("该有向图的弧上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", (*G).vexnum);
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vexs[i]));
        }
    } else {
        ReadData(fp, "%d", &((*G).vexnum)); // 录入顶点数
        ReadData(fp, "%d", &((*G).arcnum)); // 录入弧数
        ReadData(fp, "%d", &IncInfo);       // 判断弧上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vexs[i]));
        }
    }

    // 初始化有向图的邻接矩阵
    for(i = 0; i < (*G).vexnum; i++) {
        for(j = 0; j < (*G).vexnum; j++) {
            (*G).arcs[i][j] = arcs;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && (*G).arcnum != 0) {
        printf("请为有向图依次录入 %d 条弧的信息,顶点之间用空格隔开:\n", (*G).arcnum);
    }

    // 录入弧的信息
    for(k = 0; k < (*G).arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条弧:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        i = LocateVex(*G, v1);  // 获取顶点v1在顶点集中的位置
        j = LocateVex(*G, v2);  // 获取顶点v2在顶点集中的位置

        // 将指定的顶点关系设置为1,指示这两个顶点是直接连接的(注:这里没有验证下标是否越界)
        (*G).arcs[i][j].adj = 1;

        // 如果需要录入弧的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &((*G).arcs[i][j].info));
        }
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造有向网
 */
static Status CreateDN(MGraph* G) {
    int i, j, k;
    ArcCell arcs = {INFINITE, NULL};    // 有向网每条弧的初始值
    VertexType v1, v2;
    VRType w;

    if(fp == NULL) {
        printf("请输入有向网的顶点数:");
        scanf("%d", &((*G).vexnum));
        printf("请输入有向网的弧数:");
        scanf("%d", &((*G).arcnum));
        printf("该有向网的弧上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", (*G).vexnum);
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vexs[i]));
        }
    } else {
        ReadData(fp, "%d", &((*G).vexnum)); // 录入顶点数
        ReadData(fp, "%d", &((*G).arcnum)); // 录入弧数
        ReadData(fp, "%d", &IncInfo);       // 判断弧上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vexs[i]));
        }
    }

    // 初始化有向网的邻接矩阵
    for(i = 0; i < (*G).vexnum; i++) {
        for(j = 0; j < (*G).vexnum; j++) {
            (*G).arcs[i][j] = arcs;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && (*G).arcnum != 0) {
        printf("请为有向网依次录入 %d 条弧(带权值)的信息,顶点及权值之间用空格隔开:\n", (*G).arcnum);
    }

    // 录入弧的信息
    for(k = 0; k < (*G).arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条弧及其权值:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
            scanf("%d", &w);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c%d", &v1, &v2, &w);
        }

        i = LocateVex(*G, v1);  // 获取顶点v1在顶点集中的位置
        j = LocateVex(*G, v2);  // 获取顶点v2在顶点集中的位置

        // 在指定的顶点关系上记录权值(注:这里没有验证下标是否越界)
        (*G).arcs[i][j].adj = w;

        // 如果需要录入弧的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &((*G).arcs[i][j].info));
        }
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造无向图
 */
static Status CreateUDG(MGraph* G) {
    int i, j, k;
    ArcCell arcs = {0, NULL};   // 无向图每条边的初始值
    VertexType v1, v2;

    if(fp == NULL) {
        printf("请输入无向图的顶点数:");
        scanf("%d", &((*G).vexnum));
        printf("请输入无向图的边数:");
        scanf("%d", &((*G).arcnum));
        printf("该无向图的边上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", (*G).vexnum);
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vexs[i]));
        }
    } else {
        ReadData(fp, "%d", &((*G).vexnum)); // 录入顶点数
        ReadData(fp, "%d", &((*G).arcnum)); // 录入边数
        ReadData(fp, "%d", &IncInfo);       // 判断边上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vexs[i]));
        }
    }

    // 初始化无向图的邻接矩阵
    for(i = 0; i < (*G).vexnum; i++) {
        for(j = 0; j < (*G).vexnum; j++) {
            (*G).arcs[i][j] = arcs;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && (*G).arcnum != 0) {
        printf("请为无向图依次录入 %d 条边的信息,顶点之间用空格隔开:\n", (*G).arcnum);
    }

    // 录入边的信息
    for(k = 0; k < (*G).arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        i = LocateVex(*G, v1);  // 获取顶点v1在顶点集中的位置
        j = LocateVex(*G, v2);  // 获取顶点v2在顶点集中的位置

        // 将指定的顶点关系设置为1,指示这两个顶点是直接连接的(注:这里没有验证下标是否越界)
        (*G).arcs[i][j].adj = 1;

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &((*G).arcs[i][j].info));
        }

        // 填充对称点
        (*G).arcs[j][i] = (*G).arcs[i][j];
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * ████████ 算法7.2 ████████
 *
 * 构造无向网
 */
static Status CreateUDN(MGraph* G) {
    int i, j, k;
    ArcCell arcs = {INFINITE, NULL};    // 无向网每条边的初始值
    VertexType v1, v2;
    VRType w;

    if(fp == NULL) {
        printf("请输入无向网的顶点数:");
        scanf("%d", &((*G).vexnum));
        printf("请输入无向网的边数:");
        scanf("%d", &((*G).arcnum));
        printf("该无向网的边上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", (*G).vexnum);
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vexs[i]));
        }
    } else {
        ReadData(fp, "%d", &((*G).vexnum)); // 录入顶点数
        ReadData(fp, "%d", &((*G).arcnum)); // 录入边数
        ReadData(fp, "%d", &IncInfo);       // 判断边上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < (*G).vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vexs[i]));
        }
    }

    // 初始化无向网的邻接矩阵
    for(i = 0; i < (*G).vexnum; i++) {
        for(j = 0; j < (*G).vexnum; j++) {
            (*G).arcs[i][j] = arcs;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && (*G).arcnum != 0) {
        printf("请为无向网依次录入 %d 条边(带权值)的信息,顶点及权值之间用空格隔开:\n", (*G).arcnum);
    }

    // 录入边的信息
    for(k = 0; k < (*G).arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边及其权值:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
            scanf("%d", &w);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c%d", &v1, &v2, &w);
        }

        i = LocateVex(*G, v1);  // 获取顶点v1在顶点集中的位置
        j = LocateVex(*G, v2);  // 获取顶点v2在顶点集中的位置

        // 在指定的顶点关系上记录权值(注:这里没有验证下标是否越界)
        (*G).arcs[i][j].adj = w;

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &((*G).arcs[i][j].info));
        }

        // 填充对称点
        (*G).arcs[j][i] = (*G).arcs[i][j];
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 录入边/弧的相关附加信息
 */
static void Input(MGraph G, InfoType** info) {
    //录入边/弧的信息,本测试涉及到的边/弧默认无其他附加信息
}

/*
 * 销毁
 *
 * 邻接矩阵存储的图无需释放内存,只需重置相关遍历即可。
 */
Status DestroyGraph(MGraph* G) {
    (*G).vexnum = 0;
    (*G).arcnum = 0;
    IncInfo = 0;

    return OK;
}

/*
 * 查找
 *
 * 返回顶点u在图/网中的位置
 */
int LocateVex(MGraph G, VertexType u) {
    int i;

    for(i = 0; i < G.vexnum; i++) {
        if(G.vexs[i] == u) {
            return i;
        }
    }

    return -1;
}

/*
 * 取值
 *
 * 返回索引v处的顶点值
 */
VertexType GetVex(MGraph G, int v) {
    if(v < 0 || v >= G.vexnum) {
        return '\0';    // 指定的顶点不存在
    }

    return G.vexs[v];
}

/*
 * 赋值
 *
 * 将顶点v赋值为value
 */
Status PutVex(MGraph* G, VertexType v, VertexType value) {
    int k;

    // 首先需要判断该顶点是否存在
    k = LocateVex((*G), v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    (*G).vexs[k] = value;

    return OK;
}

/*
 * 首个邻接点
 *
 * 返回顶点v的首个邻接点
 */
int FirstAdjVex(MGraph G, VertexType v) {
    int kv, j;
    VRType adj;

    kv = LocateVex(G, v);
    if(kv == -1) {
        return -1;    // 指定的顶点不存在
    }

    // 确定一个非连通标记
    if(G.kind == DG || G.kind == UDG) {
        adj = 0;            // 图
    } else if(G.kind == DN || G.kind == UDN) {
        adj = INFINITE;     // 网
    } else {
        return -1;
    }

    // 从头开始查找
    for(j = 0; j < G.vexnum; j++) {
        // 找到与v直接连接的顶点
        if(G.arcs[kv][j].adj != adj) {
            return j;
        }
    }

    return -1;
}

/*
 * 下一个邻接点
 *
 * 返回顶点v的(相对于w的)下一个邻接点
 */
int NextAdjVex(MGraph G, VertexType v, VertexType w) {
    int kv, kw, j;
    VRType adj;

    kv = LocateVex(G, v);
    if(kv == -1) {
        return -1;    // 指定的顶点不存在
    }

    kw = LocateVex(G, w);
    if(kw == -1) {
        return -1;    // 指定的顶点不存在
    }

    // 确定一个非连通标记
    if(G.kind == DG || G.kind == UDG) {
        adj = 0;        // 图
    } else if(G.kind == DN || G.kind == UDN) {
        adj = INFINITE; // 网
    } else {
        return -1;
    }

    // 从顶点w后开始查找
    for(j = kw + 1; j < G.vexnum; j++) {
        // 找到与v直接连接的顶点
        if(G.arcs[kv][j].adj != adj) {
            return j;
        }
    }

    return -1;
}

/*
 * 插入顶点
 *
 * 将指定的顶点v追加到顶点集中,未建立该顶点与其他顶点的关系
 */
Status InsertVex(MGraph* G, VertexType v) {
    int i, k;
    VRType adj;

    // 顶点数过多
    if((*G).vexnum == MAX_VERTEX_NUM) {
        return ERROR;
    }

    // 首先需要判断该顶点是否存在
    k = LocateVex(*G, v);
    if(k >= 0) {
        return ERROR;    // 指定的顶点存在时,无需重复添加
    }

    // 确定一个非连通标记
    if((*G).kind == DG || (*G).kind == UDG) {
        adj = 0;        // 图
    } else if((*G).kind == DN || (*G).kind == UDN) {
        adj = INFINITE; // 网
    } else {
        return ERROR;
    }

    (*G).vexs[(*G).vexnum] = v;
    (*G).vexnum++;

    for(i = 0; i < (*G).vexnum; i++) {
        (*G).arcs[i][(*G).vexnum - 1].adj = adj;
        (*G).arcs[(*G).vexnum - 1][i].adj = adj;
    }

    return OK;
}

/*
 * 删除顶点
 *
 * 从顶点集中删除指定的顶点v,注意需要更新相关的顶点关系
 */
Status DeleteVex(MGraph* G, VertexType v) {
    int i, j, k;
    VRType adj;

    k = LocateVex(*G, v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 确定一个非连通标记
    if((*G).kind == DG || (*G).kind == UDG) {
        adj = 0;        // 图
    } else if((*G).kind == DN || (*G).kind == UDN) {
        adj = INFINITE; // 网
    } else {
        return ERROR;
    }

    // 更新边/弧的数量
    for(i = 0; i < (*G).vexnum; i++) {
        // 如果存在从顶点v出发的边,则边的数量减一
        if((*G).arcs[k][i].adj != adj) {
            (*G).arcnum--;
        }

        // 如果这是有向的图/网,依然需要更新边/弧的数量
        if((*G).kind == DG || (*G).kind == DN) {
            // 如果存在到达顶点v的边,则边的数量减一
            if((*G).arcs[i][k].adj != adj) {
                (*G).arcnum--;
            }
        }
    }

    // 将邻接矩阵中的顶点关系左移
    for(j = k + 1; j < (*G).vexnum; j++) {
        for(i = 0; i < (*G).vexnum; i++) {
            (*G).arcs[i][j - 1] = (*G).arcs[i][j];    // 右边的列挪到左边的列
        }
    }

    // 将邻接矩阵中的顶点关系上移
    for(i = k + 1; i < (*G).vexnum; i++) {
        // 注,由于上面进行左移的关系,所以这里的j是小于(*G).vexnum - 1
        for(j = 0; j < (*G).vexnum - 1; j++) {
            (*G).arcs[i - 1][j] = (*G).arcs[i][j];    // 下一行挪到上一行
        }
    }

    // 将该顶点从顶点集中移除
    for(i = k + 1; i < (*G).vexnum; i++) {
        (*G).vexs[i - 1] = (*G).vexs[i];
    }

    // 顶点数减一
    (*G).vexnum--;

    return OK;
}

/*
 * 插入边/弧<v, w>
 *
 * 如果当前图/网是无向的,则插入一条弧需要增加两个顶点关系,但弧的数量只增一。
 *
 * 对于图来说,可以在可变参数中列出边/弧的附加信息;
 * 对于网来说,可以在可变参数中依次列出边/弧的权值以及附加信息。
 */
Status InsertArc(MGraph* G, VertexType v, VertexType w, ...) {
    int kv, kw;
    VRType adj;                 // 顶点关系
    Boolean overlay = FALSE;    // 是否为覆盖添加
    InfoType* info = NULL;      // 边/弧的附加信息
    va_list ap;

    kv = LocateVex(*G, v);
    if(kv == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    kw = LocateVex(*G, w);
    if(kw == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 拒绝环
    if(kv == kw) {
        return ERROR;
    }

    /* 确定一个顶点关系 */

    // 对于图来说,连通关系用1表示
    if((*G).kind == DG || (*G).kind == UDG) {
        adj = 1;

        // 如果边/弧上存在附加信息
        if(IncInfo) {
            va_start(ap, w);                // 在w后查询首个可变参数
            info = va_arg(ap, InfoType*);   // 获取附加信息
            va_end(ap);
        }

        overlay = (*G).arcs[kv][kw].adj != 0;

        // 对于网来说,此处需要从可变参数中获取权值信息
    } else if((*G).kind == DN || (*G).kind == UDN) {
        va_start(ap, w);    // 在w后查询首个可变参数

        adj = va_arg(ap, VRType);   // 获取权值信息

        // 如果边/弧上存在附加信息
        if(IncInfo) {
            info = va_arg(ap, InfoType*);   // 获取附加信息
        }

        va_end(ap);

        overlay = (*G).arcs[kv][kw].adj != INFINITE;
    } else {
        return ERROR;
    }

    (*G).arcs[kv][kw].adj = adj;    // 记录顶点关系

    // 如果边/弧上存在附加信息,则记录附加关系
    if(IncInfo) {
        (*G).arcs[kv][kw].info = info;
    }

    // 如果是无向图/网,需要考虑对称性
    if((*G).kind == UDG || (*G).kind == UDN) {
        (*G).arcs[kw][kv] = (*G).arcs[kv][kw];
    }

    // 在非覆盖的情形下,才考虑更新边/弧的数量
    if(!overlay) {
        (*G).arcnum++;  // 不论有向无向,边/弧数只增一
    }

    return OK;
}

/*
 * 删除边/弧
 *
 * 此删除只是更新边/弧的连通关系
 */
Status DeleteArc(MGraph* G, VertexType v, VertexType w) {
    int kv, kw;
    VRType adj;
    Boolean found = FALSE;  // 是否存在待删除的边/弧

    kv = LocateVex(*G, v);
    if(kv == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    kw = LocateVex(*G, w);
    if(kw == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 确定一个非连通标记
    if((*G).kind == DG || (*G).kind == UDG) {
        adj = 0;        // 图

        found = (*G).arcs[kv][kw].adj != 0;
    } else if((*G).kind == DN || (*G).kind == UDN) {
        adj = INFINITE; // 网

        found = (*G).arcs[kv][kw].adj != INFINITE;
    } else {
        return ERROR;
    }

    // 标记这两个顶点已断开连接
    (*G).arcs[kv][kw].adj = adj;

    // 如果是无向图/网,需要考虑对称性
    if((*G).kind == UDG || (*G).kind == UDN) {
        (*G).arcs[kw][kv] = (*G).arcs[kv][kw];
    }

    // 在找到了指定的弧时,才考虑更新边/弧的数量
    if(found) {
        (*G).arcnum--;  // 不论有向无向,边/弧数只减一
    }

    return OK;
}

/*
 * ████████ 算法7.4 ████████
 *
 * 深度优先遍历(此处借助递归实现)
 */
void DFSTraverse(MGraph G, Status(Visit)(VertexType)) {
    int v;

    // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
    VisitFunc = Visit;

    // 访问标志数组初始化
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 此处需要遍历的原因是并不能保证所有顶点都连接在了一起
    for(v = 0; v < G.vexnum; v++) {
        if(!visited[v]) {
            DFS(G, v);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * ████████ 算法7.5 ████████
 *
 * 深度优先遍历核心函数
 */
static void DFS(MGraph G, int v) {
    int w;

    // 从第v个顶点出发递归地深度优先遍历图G
    visited[v] = TRUE;

    // 访问第v个顶点
    VisitFunc(G.vexs[v]);

    for(w = FirstAdjVex(G, G.vexs[v]);
        w >= 0;
        w = NextAdjVex(G, G.vexs[v], G.vexs[w])) {
        if(!visited[w]) {
            DFS(G, w);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * ████████ 算法7.6 ████████
 *
 * 广度优先遍历(此处借助队列实现)
 */
void BFSTraverse(MGraph G, Status(Visit)(VertexType)) {
    int v, w;
    LinkQueue Q;
    QElemType u;

    // 初始化为未访问
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 置空辅助队列
    InitQueue(&Q);

    for(v = 0; v < G.vexnum; v++) {
        // 如果该顶点已访问过,则直接忽略
        if(visited[v]) {
            continue;
        }

        // 标记该顶点已访问
        visited[v] = TRUE;

        // 访问顶点
        Visit(G.vexs[v]);

        EnQueue(&Q, v);

        while(!QueueEmpty(Q)) {
            DeQueue(&Q, &u);

            // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
            for(w = FirstAdjVex(G, G.vexs[u]);
                w >= 0;
                w = NextAdjVex(G, G.vexs[u], G.vexs[w])) {
                if(!visited[w]) {
                    visited[w] = TRUE;
                    Visit(G.vexs[w]);
                    EnQueue(&Q, w);
                }
            }
        }
    }
}

/*
 * 以图形化形式输出当前结构
 *
 * 注:在图/网中,使用"-"来表示两顶点不直接连通
 */
void PrintGraph(MGraph G) {
    int i, j;

    if(G.vexnum == 0) {
        printf("空图,无需打印!\n");
        return;
    }

    printf("当前图/网包含 %2d 个顶点, %2d 条边/弧...\n", G.vexnum, G.arcnum);

    printf("  ");
    for(i = 0; i < G.vexnum; i++) {
        printf("  %c", G.vexs[i]);
    }
    printf("\n");

    for(i = 0; i < G.vexnum; i++) {
        printf("%c ", G.vexs[i]);

        for(j = 0; j < G.vexnum; j++) {
            if(((G.kind == DG || G.kind == UDG) && G.arcs[i][j].adj == 0) || ((G.kind == DN || G.kind == UDN) && G.arcs[i][j].adj == INFINITE)) {
                printf("  -");
            } else {
                printf("%3d", G.arcs[i][j].adj);
            }
        }

        printf("\n");
    }
}

邻接表

// 图的类型
typedef enum {
    DG,     // 0-有向图
    DN,     // 1-有向网(带权值)
    UDG,    // 2-无向图
    UDN     // 3-无向网(带权值)
} GraphKind;

// 顶点类型
typedef char VertexType;

// 边/弧的相关附加信息
typedef struct {
    /*
     * 注:
     * 教材中给出的结构只考虑了无权图,而没考虑有权图(网)。
     * 这里为了把“网”的情形也考虑进去,特在附加信息中增加了"权重"属性。
     */
    int weight;
} InfoType;

/* 边/弧结点 */
typedef struct ArcNode {
    int adjvex;                 // 该弧所指向的顶点的位置
    struct ArcNode* nextarc;    // 指向下一条弧的指针
    InfoType* info;             // 弧的附加信息,通常忽略
} ArcNode;

// 每个链表的头结点
typedef struct VNode {
    VertexType data;    // 顶点信息
    ArcNode* firstarc;  // 指向第一条依附该顶点的弧的指针
} VNode;

/* 图的邻接表存储表示类型定义 */
typedef struct {
    VNode vertices[MAX_VERTEX_NUM]; // 邻接表
    int vexnum, arcnum;             // 图/网的顶点数和弧数
    GraphKind kind;                 // 图的类型标志
} ALGraph;

/*
 * IncInfo指示该图/网的边/弧上是否存在附加信息。
 * 如果其值不为0,则表示无附加信息,否则,表示存在附加信息。
 */
Boolean IncInfo = FALSE;

// 访问标志数组,记录访问过的顶点
static Boolean visited[MAX_VERTEX_NUM];

// 函数变量
static Status (* VisitFunc)(VertexType e);


/*
 * 创建
 *
 *【备注】
 *
 * 教材中默认从控制台读取数据。
 * 这里为了方便测试,避免每次运行都手动输入数据,
 * 因而允许选择从预设的文件path中读取测试数据。
 *
 * 如果需要从控制台读取数据,则path为NULL,或path[kind]为""。
 * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
 */
Status CreateGraph(ALGraph* G, char* path[]) {
    int readFromConsole;    // 是否从控制台读取数据
    int kind;
    Status flag;

    printf("请输入图的类型(0-有向图 │ 1-有向网 │ 2-无向图 │ 3-无向网):");
    scanf("%d", &kind);

    // 类型不合规
    if(kind < 0 || kind > 3) {
        return ERROR;
    }

    // 如果没有文件路径信息,则从控制台读取输入
    readFromConsole = (path == NULL) || strcmp(path[kind], "") == 0;

    // 需要从文件读取
    if(readFromConsole) {
        (*G).kind = kind;   // 记录图/网的类型
    } else {
        // 打开文件,准备读取测试数据
        fp = fopen(path[kind], "r");
        if(fp == NULL) {
            return ERROR;
        }

        // 录入图的类型
        ReadData(fp, "%d", &((*G).kind));
    }

    // 随机创建有向图/网或无向图/网的一种
    switch((*G).kind) {
        case DG:
            flag = CreateDG(G);
            break;
        case DN:
            flag = CreateDN(G);
            break;
        case UDG:
            flag = CreateUDG(G);
            break;
        case UDN:
            flag = CreateUDN(G);
            break;
        default:
            flag = ERROR;
            break;
    }

    if(fp != NULL) {
        fclose(fp);
        fp = NULL;
    }

    return flag;
}

/*
 * 构造有向图
 */
static Status CreateDG(ALGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入有向图的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入有向图的弧数:");
        scanf("%d", &arcnum);
        printf("该有向图的弧上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum);    // 录入顶点数
        ReadData(fp, "%d", &arcnum);    // 录入弧数
        ReadData(fp, "%d", &IncInfo);   // 判断弧上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为有向图依次录入 %d 条弧的信息,顶点之间用空格隔开:\n", arcnum);
    }

    // 录入弧的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条弧:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入弧的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &info);
        }

        // 插入弧<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造有向网
 */
static Status CreateDN(ALGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入有向网的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入有向网的弧数:");
        scanf("%d", &arcnum);
        printf("该有向网的弧上必须包含其他附加信息,因为此处的权值需要存储到附加信息中...\n");
        IncInfo = 1;

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入弧数
        ReadData(fp, "%d", &IncInfo);// 判断弧上是否包含附加信息(此处应当包含)
        IncInfo = 1;    // 强制将权值录入到附加信息中

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为有向网依次录入 %d 条弧(带权值)的信息,顶点及权值之间用空格隔开:\n", arcnum);
    }

    // 录入弧的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条弧及其权值:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入弧的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息(此处需要录入网的权值)
            Input(*G, &info);
        }

        // 插入弧<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造无向图
 */
static Status CreateUDG(ALGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入无向图的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入无向图的边数:");
        scanf("%d", &arcnum);
        printf("该无向图的边上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入边数
        ReadData(fp, "%d", &IncInfo);// 判断边上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为无向图依次录入 %d 条边的信息,顶点之间用空格隔开:\n", arcnum);
    }

    // 录入边的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &info);

        }

        // 插入边<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造无向网
 */
static Status CreateUDN(ALGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入无向网的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入无向网的边数:");
        scanf("%d", &arcnum);
        printf("该无向网的边上必须包含其他附加信息,因为此处的权值需要存储到附加信息中...\n");
        IncInfo = 1;

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入边数
        ReadData(fp, "%d", &IncInfo);// 判断边上是否包含附加信息
        IncInfo = 1;    // 强制将权值录入到附加信息中

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).vertices[i].data));
            (*G).vertices[i].firstarc = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为无向网依次录入 %d 条边(带权值)的信息,顶点及权值之间用空格隔开:\n", arcnum);
    }

    // 录入边的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边及其权值:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息(此处需要录入网的权值)
            Input(*G, &info);
        }

        // 插入边<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 录入边/弧的相关附加信息
 */
static void Input(ALGraph G, InfoType** info) {
    int weight;

    // 在"网"的情形下需要录入权值信息
    if(G.kind == DN || G.kind == UDN) {
        *info = (InfoType*) malloc(sizeof(InfoType));

        if(fp == NULL) {
            scanf("%d", &weight);
        } else {
            ReadData(fp, "%d", &weight);
        }

        (*info)->weight = weight;
    }
}

/*
 * 销毁
 *
 * 邻接表存储的图需要释放内存。
 */
Status DestroyGraph(ALGraph* G) {
    int k;
    ArcNode* pre, * r;

    for(k = 0; k < G->vexnum; k++) {
        r = G->vertices[k].firstarc;

        while(r != NULL) {
            pre = r;
            r = r->nextarc;
            free(pre);
        }

        G->vertices[k].firstarc = NULL;
    }

    (*G).vexnum = 0;
    (*G).arcnum = 0;
    IncInfo = 0;

    return OK;
}

/*
 * 查找
 *
 * 返回顶点u在图/网中的位置
 */
int LocateVex(ALGraph G, VertexType u) {
    int i;

    for(i = 0; i < G.vexnum; i++) {
        if(G.vertices[i].data == u) {
            return i;
        }
    }

    return -1;
}

/*
 * 取值
 *
 * 返回索引v处的顶点值
 */
VertexType GetVex(ALGraph G, int v) {
    if(v < 0 || v >= G.vexnum) {
        return '\0';    // 指定的顶点不存在
    }

    return G.vertices[v].data;
}

/*
 * 赋值
 *
 * 将顶点v赋值为value
 */
Status PutVex(ALGraph* G, VertexType v, VertexType value) {
    int k;

    // 首先需要判断该顶点是否存在
    k = LocateVex((*G), v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 替换头结点
    (*G).vertices[k].data = value;

    /* 链表中的元素存储的是顶点的位置,所以无需遍历链表来替换目标值 */

    return OK;
}

/*
 * 首个邻接点
 *
 * 返回顶点v的首个邻接点
 */
int FirstAdjVex(ALGraph G, VertexType v) {
    int k;
    ArcNode* r;

    // 首先需要判断该顶点是否存在
    k = LocateVex(G, v);
    if(k == -1) {
        return -1;    // 指定的顶点不存在
    }

    r = G.vertices[k].firstarc;
    if(r == NULL) {
        return -1;
    } else {
        return r->adjvex;
    }
}

/*
 * 下一个邻接点
 *
 * 返回顶点v的(相对于w的)下一个邻接点
 */
int NextAdjVex(ALGraph G, VertexType v, VertexType w) {
    int kv, kw;
    ArcNode* r;

    // 首先需要判断该顶点是否存在
    kv = LocateVex(G, v);
    if(kv == -1) {
        return -1;    // 指定的顶点不存在
    }

    // 首先需要判断该顶点是否存在
    kw = LocateVex(G, w);
    if(kw == -1) {
        return -1;    // 指定的顶点不存在
    }

    r = G.vertices[kv].firstarc;
    if(r == NULL) {
        return -1;  // 链表为空
    }

    // 在链表中查找w
    while(r != NULL && r->adjvex < kw) {
        r = r->nextarc;
    }

    // 如果没找到w
    if(r == NULL) {
        return -1;
    }

    // 如果找到了w,但是w后面没有别的顶点,那么也无法返回邻接点
    if(r->adjvex == kw && r->nextarc != NULL) {
        return r->nextarc->adjvex;
    }

    return -1;
}

/*
 * 插入顶点
 *
 * 将指定的顶点v追加到顶点集中,未建立该顶点与其他顶点的关系
 */
Status InsertVex(ALGraph* G, VertexType v) {
    int k;

    // 顶点数过多
    if((*G).vexnum == MAX_VERTEX_NUM) {
        return ERROR;
    }

    // 首先需要判断该顶点是否存在
    k = LocateVex(*G, v);
    if(k >= 0) {
        return ERROR;    // 指定的顶点存在时,无需重复添加
    }

    G->vertices[(*G).vexnum].data = v;
    G->vertices[(*G).vexnum].firstarc = NULL;

    (*G).vexnum++;

    return OK;
}

/*
 * 删除顶点
 *
 * 从顶点集中删除指定的顶点v,注意需要更新相关的顶点关系
 */
Status DeleteVex(ALGraph* G, VertexType v) {
    int k, i;
    ArcNode* pre, * r;

    // 首先需要判断该顶点是否存在
    k = LocateVex(*G, v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 找到以结点v出发的链表,释放该链表上所有结点
    r = G->vertices[k].firstarc;
    while(r != NULL) {
        pre = r;
        r = r->nextarc;
        free(pre);

        (*G).arcnum--;
    }

    G->vertices[k].firstarc = NULL;

    // 遍历其它所有链表,删除那些指向顶点v的弧;而且,下标超过k的顶点,其下标值需要递减
    for(i = 0; i < G->vexnum; i++) {
        pre = NULL;
        r = G->vertices[i].firstarc;
        while(r != NULL && r->adjvex < k) {
            pre = r;
            r = r->nextarc;
        }

        // 链表上所有顶点的下标均小于k
        if(r == NULL) {
            continue;
        }

        if(r->adjvex == k) {
            // 从开头删掉结点v
            if(pre == NULL) {
                G->vertices[i].firstarc = r->nextarc;

                // 从中间某个位置删掉结点v
            } else {
                pre->nextarc = r->nextarc;
            }

            free(r);

            // 如果这是有向的图/网,依然需要递减边/弧的数量
            if((*G).kind == DG || (*G).kind == DN) {
                (*G).arcnum--;
            }
        }

        // 再次确定r的位置
        if(pre == NULL) {
            r = G->vertices[i].firstarc;
        } else {
            r = pre->nextarc;
        }

        // 下标超过k的顶点,需要递减其下标
        while(r != NULL && r->adjvex > k) {
            r->adjvex -= 1;
            r = r->nextarc;
        }
    }

    // 顶点集前移
    for(i = k + 1; i < (*G).vexnum; i++) {
        G->vertices[i - 1] = G->vertices[i];
    }

    // 顶点数递减
    (*G).vexnum--;

    return OK;
}

/*
 * 构造一个边/弧结点(仅限内部使用)
 */
static ArcNode* newArcNodePtr(int adjvex, ArcNode* nextarc, InfoType* info) {
    ArcNode* p = (ArcNode*) malloc(sizeof(ArcNode));
    if(!p) {
        exit(OVERFLOW);
    }

    p->adjvex = adjvex;

    p->nextarc = nextarc;

    p->info = info;

    return p;
}

/*
 * 插入边/弧<v, w>
 *
 * 如果当前图/网是无向的,则插入一条弧需要增加两个顶点关系,但弧的数量只增一。
 * 对于图/网来说,可以在可变参数中列出边/弧的附加信息。
 *
 * 注:此处接收的参数与MGraph有些不一样:网的附加信息中包含了各条边/弧的权值。
 */
Status InsertArc(ALGraph* G, VertexType v, VertexType w, ...) {
    int tail, head, k, count;
    ArcNode* r;
    ArcNode* pre;
    Boolean overlay = FALSE;    // 是否为覆盖添加
    InfoType* info = NULL;      // 边/弧的附加信息
    va_list ap;

    tail = LocateVex(*G, v); // 获取顶点v在顶点集中的位置
    if(tail == -1) {
        return ERROR;  // 指定的顶点不存在
    }

    head = LocateVex(*G, w); // 获取顶点w在顶点集中的位置
    if(head == -1) {
        return ERROR;  // 指定的顶点不存在
    }

    // 拒绝环
    if(tail == head) {
        return ERROR;
    }

    // 如果边/弧上存在附加信息
    if(IncInfo) {
        va_start(ap, w);                // 在w后查询首个可变参数
        info = va_arg(ap, InfoType*);   // 获取附加信息
        va_end(ap);
    }

    /* 接下来,需要查找合适的插入位置 */

    for(count = 0; count < 2; count++) {
        pre = NULL;
        // 指向以tail为尾的首条边/弧
        r = G->vertices[tail].firstarc;
        while(r != NULL && r->adjvex < head) {
            pre = r;
            r = r->nextarc;
        }

        // 遇到了相同位置的结点
        if(r != NULL && r->adjvex == head) {
            r->info = info; // 复用该结点
            overlay = TRUE; // 发生了覆盖
        } else {
            if(pre == NULL) {
                G->vertices[tail].firstarc = newArcNodePtr(head, r, info);
            } else {
                pre->nextarc = newArcNodePtr(head, r, info);
            }
        }

        // 如果当前图/网是无向的,需要考虑对称性
        if((G->kind == UDG || G->kind == UDN) && tail != head) {
            // 颠倒i和j
            k = tail;
            tail = head;
            head = k;
        } else {
            break;  // 如果是有向的,可以结束了
        }
    }

    // 在非覆盖的情形下,才考虑更新边/弧的数量
    if(!overlay) {
        (*G).arcnum++;  // 不论有向无向,边/弧数只增一
    }

    return OK;
}

/*
 * 删除边/弧<v, w>
 */
Status DeleteArc(ALGraph* G, VertexType v, VertexType w) {
    int tail, head, k, count;
    ArcNode* r;
    ArcNode* pre;

    tail = LocateVex(*G, v);
    if(tail == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    head = LocateVex(*G, w);
    if(head == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    for(count = 0; count < 2; count++) {
        pre = NULL;
        // 在当前链表中找到待删除的边/弧
        r = G->vertices[tail].firstarc;
        while(r != NULL && r->adjvex < head) {
            pre = r;
            r = r->nextarc;
        }

        // 找到了待删除的边/弧
        if(r != NULL && r->adjvex == head) {
            if(pre == NULL) {
                G->vertices[tail].firstarc = r->nextarc;
            } else {
                pre->nextarc = r->nextarc;
            }

            free(r);
        } else {
            return ERROR; // 没找到
        }

        // 如果当前图/网是无向的,需要考虑对称性
        if((G->kind == UDG || G->kind == UDN) && tail != head) {
            // 颠倒tail和head
            k = tail;
            tail = head;
            head = k;
        } else {
            break;  // 如果是有向的,可以结束了
        }
    }

    (*G).arcnum--;  // 不论有向无向,边/弧数只减一

    return OK;
}

/*
 * 深度优先遍历(此处借助递归实现)
 */
void DFSTraverse(ALGraph G, Status(Visit)(VertexType)) {
    int v;

    // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
    VisitFunc = Visit;

    // 访问标志数组初始化
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 此处需要遍历的原因是并不能保证所有顶点都连接在了一起
    for(v = 0; v < G.vexnum; v++) {
        if(!visited[v]) {
            DFS(G, v);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 深度优先遍历核心函数
 */
static void DFS(ALGraph G, int v) {
    int w;

    // 从第v个顶点出发递归地深度优先遍历图G
    visited[v] = TRUE;

    // 访问第v个顶点
    VisitFunc(G.vertices[v].data);

    for(w = FirstAdjVex(G, G.vertices[v].data);
        w >= 0;
        w = NextAdjVex(G, G.vertices[v].data, G.vertices[w].data)) {
        if(!visited[w]) {
            DFS(G, w);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 广度优先遍历(此处借助队列实现)
 */
void BFSTraverse(ALGraph G, Status(Visit)(VertexType)) {
    int v, w;
    LinkQueue Q;
    QElemType u;

    // 初始化为未访问
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 置空辅助队列
    InitQueue(&Q);

    for(v = 0; v < G.vexnum; v++) {
        // 如果该顶点已访问过,则直接忽略
        if(visited[v]) {
            continue;
        }

        // 标记该顶点已访问
        visited[v] = TRUE;

        // 访问顶点
        Visit(G.vertices[v].data);

        EnQueue(&Q, v);

        while(!QueueEmpty(Q)) {
            DeQueue(&Q, &u);

            // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
            for(w = FirstAdjVex(G, G.vertices[u].data);
                w >= 0;
                w = NextAdjVex(G, G.vertices[u].data, G.vertices[w].data)) {
                if(!visited[w]) {
                    visited[w] = TRUE;
                    Visit(G.vertices[w].data);
                    EnQueue(&Q, w);
                }
            }
        }
    }
}

/*
 * 以图形化形式输出当前结构
 */
void PrintGraph(ALGraph G) {
    int i;
    ArcNode* p;

    if(G.vexnum == 0) {
        printf("空图,无需打印!\n");
        return;
    }

    printf("当前图/网包含 %2d 个顶点, %2d 条边/弧...\n", G.vexnum, G.arcnum);

    for(i = 0; i < G.vexnum; i++) {
        printf("%c ===> ", G.vertices[i].data);

        p = G.vertices[i].firstarc;
        while(p != NULL) {
            if(IncInfo == 0) {
                printf("%c ", G.vertices[p->adjvex].data);

                // 对于网,会从其附加信息中获取到权值
            } else {
                printf("%c[%2d] ", G.vertices[p->adjvex].data, p->info->weight);
            }

            p = p->nextarc;

            if(p != NULL) {
                printf("- ");
            }
        }

        printf("\n");
    }
}
// 图的类型
typedef enum {
    DG,     // 0-有向图
    DN,     // 1-有向网(带权值)
    UDG,    // 2-无向图
    UDN     // 3-无向网(带权值)
} GraphKind;

// 顶点类型
typedef char VertexType;

// 边/弧的相关附加信息
typedef struct {
    /*
     * 注:
     * 教材中给出的结构只考虑了无权图,而没考虑有权图(网)。
     * 这里为了把“网”的情形也考虑进去,特在附加信息中增加了"权重"属性。
     */
    int weight;
} InfoType;

/* 边/弧结点 */
typedef struct ArcBox {
    int tailvex;    // 弧头顶点位置
    int headvex;    // 弧尾顶点位置
    struct ArcBox* hlink;  // 指向下一个拥有相同弧头的弧
    struct ArcBox* tlink;  // 指向下一个拥有相同弧尾的弧
    InfoType* info;  // 该弧的相关附加信息
} ArcBox;

// 每个横向链表的头结点
typedef struct VexNode {
    VertexType data;    // 顶点
    ArcBox* firstin;    // 指向该顶点的第一条入弧
    ArcBox* firstout;   // 指向该顶点的第一条出弧
} VexNode;

/* 图的十字链表存储表示类型定义 */
typedef struct {
    VexNode xlist[MAX_VERTEX_NUM];  // 表头向量
    int vexnum, arcnum;             // 顶点数和弧数
    GraphKind kind;                 // 图的类型标志
} OLGraph;


// 边/弧上是否存在附加信息
extern Boolean IncInfo;

// 录入数据的源文件;fp为null时,说明需要从控制台录入
static FILE* fp = NULL;

/*
 * IncInfo指示该图的边上是否存在附加信息。
 * 如果其值不为0,则表示无附加信息,否则,表示存在附加信息。
 */
Boolean IncInfo = FALSE;

// 访问标志数组,记录访问过的顶点
static Boolean visited[MAX_VERTEX_NUM];

// 函数变量
static Status (* VisitFunc)(VertexType e);


/*
 * 创建
 *
 *【备注】
 *
 * 教材中默认从控制台读取数据。
 * 这里为了方便测试,避免每次运行都手动输入数据,
 * 因而允许选择从预设的文件path中读取测试数据。
 *
 * 如果需要从控制台读取数据,则path为NULL,或path[kind]为""。
 * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
 */
Status CreateGraph(OLGraph* G, char* path[]) {
    int readFromConsole;    // 是否从控制台读取数据
    int kind;
    Status flag;

    printf("请输入图的类型(0-有向图 │ 1-有向网 │ 2-无向图 │ 3-无向网):");
    scanf("%d", &kind);

    // 类型不合规
    if(kind < 0 || kind > 3) {
        return ERROR;
    }

    // 如果没有文件路径信息,则从控制台读取输入
    readFromConsole = (path == NULL) || strcmp(path[kind], "") == 0;

    // 需要从文件读取
    if(readFromConsole) {
        (*G).kind = kind;   // 记录图/网的类型
    } else {
        // 打开文件,准备读取测试数据
        fp = fopen(path[kind], "r");
        if(fp == NULL) {
            return ERROR;
        }

        // 录入图的类型
        ReadData(fp, "%d", &((*G).kind));
    }

    // 随机创建有向图/网或无向图/网的一种
    switch((*G).kind) {
        case DG:
            flag = CreateDG(G);
            break;
        case DN:
            flag = CreateDN(G);
            break;
        case UDG:
            flag = CreateUDG(G);
            break;
        case UDN:
            flag = CreateUDN(G);
            break;
        default:
            flag = ERROR;
            break;
    }

    if(fp != NULL) {
        fclose(fp);
        fp = NULL;
    }

    return flag;
}

/*
 * ████████ 算法7.3 ████████
 *
 * 构造有向图
 *
 * 注:
 * 教材中使用了头插法来插入弧,这种做法的唯一优点是插入方便,
 * 但是在涉及到删除或查找时,效率较低;
 * 而且,"头插法"依赖输入的顺序,如果输入的边/弧是乱序的,
 * 则最终构造出的图/网中的边/弧也是无序的。
 *
 * 为了克服以上缺点,这里改用"升序"插入法,保证插入的弧是"升序"的。
 * 但同时,这样做会使"插入"算法变得较为复杂。
 */
static Status CreateDG(OLGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入有向图的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入有向图的弧数:");
        scanf("%d", &arcnum);
        printf("该有向图的弧上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            scanf("%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum);    // 录入顶点数
        ReadData(fp, "%d", &arcnum);    // 录入弧数
        ReadData(fp, "%d", &IncInfo);   // 判断弧上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为有向图依次录入 %d 条弧的信息,顶点之间用空格隔开:\n", arcnum);
    }

    // 录入弧的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条弧:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入弧的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &info);
        }

        // 插入弧<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造有向网
 */
static Status CreateDN(OLGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入有向图的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入有向网的弧数:");
        scanf("%d", &arcnum);
        printf("该有向网的弧上必须包含其他附加信息,因为此处的权值需要存储到附加信息中...\n");
        IncInfo = 1;

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入弧数
        ReadData(fp, "%d", &IncInfo);// 判断弧上是否包含附加信息(此处应当包含)
        IncInfo = 1;    // 强制将权值录入到附加信息中

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为有向网依次录入 %d 条弧(带权值)的信息,顶点及权值之间用空格隔开:\n", arcnum);
    }

    // 录入弧的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条弧及其权值:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入弧的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息(此处需要录入网的权值)
            Input(*G, &info);
        }

        // 插入弧<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造无向图
 */
static Status CreateUDG(OLGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入无向图的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入无向图的边数:");
        scanf("%d", &arcnum);
        printf("该无向图的边上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入边数
        ReadData(fp, "%d", &IncInfo);// 判断边上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为无向图依次录入 %d 条边的信息,顶点之间用空格隔开:\n", arcnum);
    }

    // 录入边的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &info);
        }

        // 插入边<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造无向网
 */
static Status CreateUDN(OLGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).arcnum = 0;

    if(fp == NULL) {
        printf("请输入无向网的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入无向网的边数:");
        scanf("%d", &arcnum);
        printf("该无向网的边上必须包含其他附加信息,因为此处的权值需要存储到附加信息中...\n");
        IncInfo = 1;

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入边数
        ReadData(fp, "%d", &IncInfo);// 判断边上是否包含附加信息
        IncInfo = 1;    // 强制将权值录入到附加信息中

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).xlist[i].data));
            (*G).xlist[i].firstin = NULL;
            (*G).xlist[i].firstout = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为无向网依次录入 %d 条边(带权值)的信息,顶点及权值之间用空格隔开:\n", arcnum);
    }

    // 录入边的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边及其权值:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息(此处需要录入网的权值)
            Input(*G, &info);
        }

        // 插入边<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 录入弧的相关附加信息
 */
static void Input(OLGraph G, InfoType** info) {
    int weight;

    // 在"网"的情形下需要录入权值信息
    if(G.kind == DN || G.kind == UDN) {
        *info = (InfoType*) malloc(sizeof(InfoType));

        if(fp == NULL) {
            scanf("%d", &weight);
        } else {
            ReadData(fp, "%d", &weight);
        }

        (*info)->weight = weight;
    }
}

/*
 * 销毁
 *
 * 邻接表存储的图需要释放内存。
 */
Status DestroyGraph(OLGraph* G) {
    int k;
    ArcBox* pre, * r;

    for(k = 0; k < G->vexnum; k++) {
        r = G->xlist[k].firstout;

        while(r != NULL) {
            pre = r;
            r = r->tlink;
            free(pre);
        }

        G->xlist[k].firstin = NULL;
        G->xlist[k].firstout = NULL;
    }

    (*G).vexnum = 0;
    (*G).arcnum = 0;
    IncInfo = 0;

    return OK;
}

/*
 * 查找
 *
 * 返回顶点u在图/网中的位置
 */
int LocateVex(OLGraph G, VertexType u) {
    int i;

    for(i = 0; i < G.vexnum; i++) {
        if(G.xlist[i].data == u) {
            return i;
        }
    }

    return -1;
}

/*
 * 取值
 *
 * 返回索引v处的顶点值
 */
VertexType GetVex(OLGraph G, int v) {
    if(v < 0 || v >= G.vexnum) {
        return '\0';    // 指定的顶点不存在
    }

    return G.xlist[v].data;
}

/*
 * 赋值
 *
 * 将顶点v赋值为value
 */
Status PutVex(OLGraph* G, VertexType v, VertexType value) {
    int k;

    // 首先需要判断该顶点是否存在
    k = LocateVex((*G), v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 替换头结点
    (*G).xlist[k].data = value;

    /* 链表中的元素存储的是顶点的位置,所以无需遍历链表来替换目标值 */

    return OK;
}

/*
 * 首个邻接点
 *
 * 返回顶点v的首个邻接点
 */
int FirstAdjVex(OLGraph G, VertexType v) {
    int k;
    ArcBox* r;

    // 首先需要判断该顶点是否存在
    k = LocateVex(G, v);
    if(k == -1) {
        return -1;    // 指定的顶点不存在
    }

    r = G.xlist[k].firstout;
    if(r == NULL) {
        return -1;
    } else {
        return r->headvex;
    }
}

/*
 * 下一个邻接点
 *
 * 返回顶点v的(相对于w的)下一个邻接点
 */
int NextAdjVex(OLGraph G, VertexType v, VertexType w) {
    int kv, kw;
    ArcBox* r;

    // 首先需要判断该顶点是否存在
    kv = LocateVex(G, v);
    if(kv == -1) {
        return -1;    // 指定的顶点不存在
    }

    // 首先需要判断该顶点是否存在
    kw = LocateVex(G, w);
    if(kw == -1) {
        return -1;    // 指定的顶点不存在
    }

    r = G.xlist[kv].firstout;
    if(r == NULL) {
        return -1;  // 链表为空
    }

    // 在链表中查找w
    while(r != NULL && r->headvex < kw) {
        r = r->tlink;
    }

    // 如果没找到w
    if(r == NULL) {
        return -1;
    }

    // 如果找到了w,但是w后面没有别的顶点,那么也无法返回邻接点
    if(r->headvex == kw && r->tlink != NULL) {
        return r->tlink->headvex;
    }

    return -1;
}

/*
 * 插入顶点
 *
 * 将指定的顶点v追加到顶点集中,未建立该顶点与其他顶点的关系
 */
Status InsertVex(OLGraph* G, VertexType v) {
    int k;

    // 顶点数过多
    if((*G).vexnum == MAX_VERTEX_NUM) {
        return ERROR;
    }

    // 首先需要判断该顶点是否存在
    k = LocateVex(*G, v);
    if(k >= 0) {
        return ERROR;    // 指定的顶点存在时,无需重复添加
    }

    G->xlist[(*G).vexnum].data = v;
    G->xlist[(*G).vexnum].firstin = NULL;
    G->xlist[(*G).vexnum].firstout = NULL;

    (*G).vexnum++;

    return OK;
}

/*
 * 删除顶点
 *
 * 从顶点集中删除指定的顶点v,注意需要更新相关的顶点关系
 */
Status DeleteVex(OLGraph* G, VertexType v) {
    int k, i;
    ArcBox* pre, * r;
    ArcBox* pre2, * r2;

    // 首先需要判断该顶点是否存在
    k = LocateVex(*G, v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 找到以结点v出发的链表,释放该链表上所有结点
    r = G->xlist[k].firstout;
    while(r != NULL) {
        pre = r;
        r = r->tlink;

        // 在纵向链表上查找pre的位置
        pre2 = NULL;
        r2 = G->xlist[pre->headvex].firstin;   // 至此,r2一定不为NULL
        while(r2 != NULL && r2->tailvex != k) {
            pre2 = r2;
            r2 = r2->hlink;
        }

        // 至此,r2一定指向了待删除的弧
        if(pre2 == NULL) {
            G->xlist[pre->headvex].firstin = r2->hlink;
        } else {
            pre2->hlink = r2->hlink;
        }

        free(pre);

        (*G).arcnum--;
    }

    G->xlist[k].firstout = NULL;

    // 遍历其它所有链表,删除那些指向顶点v的弧;而且,下标超过k的顶点,其下标值需要递减
    for(i = 0; i < G->vexnum; i++) {
        pre = NULL;
        r = G->xlist[i].firstout;
        while(r != NULL && r->headvex < k) {
            if(r->tailvex > k) {
                r->tailvex -= 1;
            }
            pre = r;
            r = r->tlink;
        }

        // 链表上所有顶点的下标均小于k
        if(r == NULL) {
            continue;
        }

        if(r->headvex == k) {
            // 从开头删掉结点v
            if(pre == NULL) {
                G->xlist[i].firstout = r->tlink;

                // 从中间某个位置删掉结点v
            } else {
                pre->tlink = r->tlink;
            }

            // 在纵向链表上查找r的位置
            pre2 = NULL;
            r2 = G->xlist[k].firstin;   // 至此,r2一定不为NULL
            while(r2 != NULL && r2->tailvex != r->tailvex) {
                pre2 = r2;
                r2 = r2->hlink;
            }

            // 至此,r2一定指向了待删除的弧
            if(pre2 == NULL) {
                G->xlist[k].firstin = r2->hlink;
            } else {
                pre2->hlink = r2->hlink;
            }

            free(r);

            // 如果这是有向的图/网,依然需要递减边/弧的数量
            if((*G).kind == DG || (*G).kind == DN) {
                (*G).arcnum--;
            }
        }

        // 再次确定r的位置
        if(pre == NULL) {
            r = G->xlist[i].firstout;
        } else {
            r = pre->tlink;
        }

        // 下标超过k的顶点,需要递减其下标
        while(r != NULL) {
            if(r->tailvex > k) {
                r->tailvex -= 1;
            }

            if(r->headvex > k) {
                r->headvex -= 1;
            }

            r = r->tlink;
        }
    }

    // 顶点集前移
    for(i = k + 1; i < (*G).vexnum; i++) {
        G->xlist[i - 1] = G->xlist[i];
    }

    // 顶点数递减
    (*G).vexnum--;

    return OK;
}

/*
 * 构造一个边/弧结点(仅限内部使用)
 */
static ArcBox* newArcBoxPtr(int tailvex, int headvex, ArcBox* hlink, ArcBox* tlink, InfoType* info) {
    ArcBox* p = (ArcBox*) malloc(sizeof(ArcBox));
    if(!p) {
        exit(OVERFLOW);
    }

    p->tailvex = tailvex;
    p->headvex = headvex;

    p->hlink = hlink;
    p->tlink = tlink;

    p->info = info;

    return p;
}

/*
 * 插入边/弧<v, w>
 *
 * 如果当前图/网是无向的,则插入一条弧需要增加两个顶点关系,但弧的数量只增一。
 * 对于图/网来说,可以在可变参数中列出边/弧的附加信息。
 *
 * 注:此处接收的参数与MGraph有些不一样:网的附加信息中包含了各条边/弧的权值。
 */
Status InsertArc(OLGraph* G, VertexType v, VertexType w, ...) {
    int tail, head, k, count;
    ArcBox* p;
    ArcBox* pre;
    ArcBox* r;
    Boolean overlay = FALSE;   // 是否为覆盖添加
    InfoType* info = NULL;     // 边/弧的附加信息
    va_list ap;

    tail = LocateVex(*G, v); // 获取顶点v在顶点集中的位置
    if(tail == -1) {
        return ERROR;  // 指定的顶点不存在
    }

    head = LocateVex(*G, w); // 获取顶点w在顶点集中的位置
    if(head == -1) {
        return ERROR;  // 指定的顶点不存在
    }

    // 拒绝环
    if(tail == head) {
        return ERROR;
    }

    // 如果边/弧上存在附加信息
    if(IncInfo) {
        va_start(ap, w);                // 在w后查询首个可变参数
        info = va_arg(ap, InfoType*);   // 获取附加信息
        va_end(ap);
    }

    /* 接下来,需要查找合适的插入位置 */

    for(count = 0; count < 2; count++) {

        // 在横向链表上查找合适的插入位置
        pre = NULL;
        // 指向以tail为尾的首条边/弧
        r = G->xlist[tail].firstout;
        while(r != NULL && r->headvex < head) {
            pre = r;
            r = r->tlink;
        }

        // 遇到了相同位置的结点
        if(r != NULL && r->headvex == head) {
            r->info = info; // 复用该结点
            overlay = TRUE; // 发生了覆盖
        } else {
            p = newArcBoxPtr(tail, head, NULL, r, info);

            if(pre == NULL) {
                G->xlist[tail].firstout = p;
            } else {
                pre->tlink = p;
            }
        }

        // 如果没有发生覆盖,说明插入了新结点,此时需要考虑其在纵向链表上的位置
        if(overlay == FALSE) {
            // 在纵向链表上查找合适的插入位置
            pre = NULL;
            // 指向以head为头的首条边/弧
            r = G->xlist[head].firstin;
            while(r != NULL && r->tailvex < tail) {
                pre = r;
                r = r->hlink;
            }

            // 遇到了相同位置的结点
            if(r != NULL && r->tailvex == tail) {
                // 不会执行到这里,因为如果发生了覆盖,前面进不来
            } else {
                /* 至此,结点p已经存在了 */

                if(pre == NULL) {
                    p->hlink = G->xlist[head].firstin;
                    G->xlist[head].firstin = p;
                } else {
                    p->hlink = pre->hlink;
                    pre->hlink = p;
                }
            }
        }

        // 如果当前图/网是无向的,需要考虑对称性
        if((G->kind == UDG || G->kind == UDN) && tail != head) {
            // 颠倒i和j
            k = tail;
            tail = head;
            head = k;
        } else {
            break;  // 如果是有向的,可以结束了
        }
    }

    // 在非覆盖的情形下,才考虑更新边/弧的数量
    if(!overlay) {
        (*G).arcnum++;  // 不论有向无向,边/弧数只增一
    }

    return OK;
}

/*
 * 删除边/弧<v, w>
 */
Status DeleteArc(OLGraph* G, VertexType v, VertexType w) {
    int tail, head, k, count;
    ArcBox* pre, * r;
    ArcBox* pre2, * r2;

    tail = LocateVex(*G, v);
    if(tail == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    head = LocateVex(*G, w);
    if(head == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    for(count = 0; count < 2; count++) {
        pre = NULL;
        // 在横向链表中找到待删除的边/弧
        r = G->xlist[tail].firstout;
        while(r != NULL && r->headvex < head) {
            pre = r;
            r = r->tlink;
        }

        // 找到了待删除的边/弧
        if(r != NULL && r->headvex == head) {
            if(pre == NULL) {
                G->xlist[tail].firstout = r->tlink;
            } else {
                pre->tlink = r->tlink;
            }

            // 在纵向链表上查找r的位置
            pre2 = NULL;
            r2 = G->xlist[head].firstin;   // 至此,r2一定不为NULL
            while(r2 != NULL && r2->tailvex != r->tailvex) {
                pre2 = r2;
                r2 = r2->hlink;
            }

            // 至此,r2一定指向了待删除的弧
            if(pre2 == NULL) {
                G->xlist[head].firstin = r2->hlink;
            } else {
                pre2->hlink = r2->hlink;
            }

            free(r);
        } else {
            return ERROR; // 没找到
        }

        // 如果当前图/网是无向的,需要考虑对称性
        if((G->kind == UDG || G->kind == UDN) && tail != head) {
            // 颠倒tail和head
            k = tail;
            tail = head;
            head = k;
        } else {
            break;  // 如果是有向的,可以结束了
        }
    }

    (*G).arcnum--;  // 不论有向无向,边/弧数只减一

    return OK;
}

/*
 * 深度优先遍历(此处借助递归实现)
 */
void DFSTraverse(OLGraph G, Status(Visit)(VertexType)) {
    int v;

    // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
    VisitFunc = Visit;

    // 访问标志数组初始化
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 此处需要遍历的原因是并不能保证所有顶点都连接在了一起
    for(v = 0; v < G.vexnum; v++) {
        if(!visited[v]) {
            DFS(G, v);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 深度优先遍历核心函数
 */
static void DFS(OLGraph G, int v) {
    int w;

    // 从第v个顶点出发递归地深度优先遍历图G
    visited[v] = TRUE;

    // 访问第v个顶点
    VisitFunc(G.xlist[v].data);

    for(w = FirstAdjVex(G, G.xlist[v].data);
        w >= 0;
        w = NextAdjVex(G, G.xlist[v].data, G.xlist[w].data)) {
        if(!visited[w]) {
            DFS(G, w);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 广度优先遍历(此处借助队列实现)
 */
void BFSTraverse(OLGraph G, Status(Visit)(VertexType)) {
    int v, w;
    LinkQueue Q;
    QElemType u;

    // 初始化为未访问
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 置空辅助队列
    InitQueue(&Q);

    for(v = 0; v < G.vexnum; v++) {
        // 如果该顶点已访问过,则直接忽略
        if(visited[v]) {
            continue;
        }

        // 标记该顶点已访问
        visited[v] = TRUE;

        // 访问顶点
        Visit(G.xlist[v].data);

        EnQueue(&Q, v);

        while(!QueueEmpty(Q)) {
            DeQueue(&Q, &u);

            // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
            for(w = FirstAdjVex(G, G.xlist[u].data);
                w >= 0;
                w = NextAdjVex(G, G.xlist[u].data, G.xlist[w].data)) {
                if(!visited[w]) {
                    visited[w] = TRUE;
                    Visit(G.xlist[w].data);
                    EnQueue(&Q, w);
                }
            }
        }
    }
}

/*
 * 以图形化形式输出当前结构
 */
void PrintGraph(OLGraph G) {
    int i, head;
    ArcBox* p;

    if(G.vexnum == 0) {
        printf("空图,无需打印!\n");
        return;
    }

    printf("当前图/网包含 %2d 个顶点, %2d 条边/弧...\n", G.vexnum, G.arcnum);

    for(i = 0; i < G.vexnum; i++) {
        printf("%c ===> ", G.xlist[i].data);

        head = 0;
        p = G.xlist[i].firstout;

        while(p != NULL) {
            if(head < p->headvex) {
                if(IncInfo == 0) {
                    printf("      ");

                    // 对于网,会从其附加信息中获取到权值
                } else {
                    printf("          ");
                }
            } else {
                if(IncInfo == 0) {
                    printf("<%c, %c>", G.xlist[p->tailvex].data, G.xlist[p->headvex].data);

                    // 对于网,会从其附加信息中获取到权值
                } else {
                    printf("<%c, %c, %2d>", G.xlist[p->tailvex].data, G.xlist[p->headvex].data, p->info->weight);
                }

                p = p->tlink;
            }

            head++;

            if(p != NULL) {
                printf("  ");
            }
        }

        printf("\n");
    }
}
// 图的类型
// 图的类型
typedef enum {
    DG,     // 0-有向图;此处不支持
    DN,     // 1-有向网(带权值);此处不支持
    UDG,    // 2-无向图
    UDN     // 3-无向网(带权值)
} GraphKind;


/* 无向图(邻接多重表)类型定义 */
typedef enum {
    unvisit, visit
} VisitIf;

// 顶点类型
typedef char VertexType;

// 边的相关附加信息
typedef struct {
    /*
     * 注:
     * 教材中给出的结构只考虑了无权图,而没考虑有权图(网)。
     * 这里为了把“网”的情形也考虑进去,特在附加信息中增加了"权重"属性。
     */
    int weight;
} InfoType;

/* 边结点 */
typedef struct EBox {
    VisitIf mark;       // 访问标记
    int ivex;           // 该边依附的两个顶点的位置
    int jvex;           // 该边依附的两个顶点的位置
    struct EBox* ilink; // 分别指向依附这两个顶点的下一条边
    struct EBox* jlink; // 分别指向依附这两个顶点的下一条边
    InfoType* info;
} EBox;

// 每个链表的头结点
typedef struct VexBox {
    VertexType data;    // 顶点信息
    EBox* firstedge;    // 指向第一条依附该顶点的边的指针
} VexBox;

/* 图的邻接多重表存储表示类型定义 */
typedef struct {
    VexBox adjmulist[MAX_VERTEX_NUM];   // 表头向量
    int vexnum, edgenum;                // 图/网的顶点数和边数
    GraphKind kind;                     // 图的类型标志
} AMLGraph;


// 边/弧上是否存在附加信息
extern Boolean IncInfo;

// 录入数据的源文件;fp为null时,说明需要从控制台录入
static FILE* fp = NULL;

/*
 * IncInfo指示该图的边上是否存在附加信息。
 * 如果其值不为0,则表示无附加信息,否则,表示存在附加信息。
 */
Boolean IncInfo = FALSE;

// 访问标志数组,记录访问过的顶点
static Boolean visited[MAX_VERTEX_NUM];

// 函数变量
static Status (* VisitFunc)(VertexType e);


/*
 * 创建
 *
 *【备注】
 *
 * 教材中默认从控制台读取数据。
 * 这里为了方便测试,避免每次运行都手动输入数据,
 * 因而允许选择从预设的文件path中读取测试数据。
 *
 * 如果需要从控制台读取数据,则path为NULL,或path[kind]为""。
 * 如果需要从文件中读取数据,则需要在path中填写文件名信息。
 */
Status CreateGraph(AMLGraph* G, char* path[]) {
    int readFromConsole;    // 是否从控制台读取数据
    int kind;
    Status flag;

    printf("请输入图的类型(2-无向图 │ 3-无向网):");
    scanf("%d", &kind);

    // 类型不合规(只接受无向的图/网)
    if(kind < 2 || kind > 3) {
        return ERROR;
    }

    // 如果没有文件路径信息,则从控制台读取输入
    readFromConsole = (path == NULL) || strcmp(path[kind], "") == 0;

    // 需要从文件读取
    if(readFromConsole) {
        (*G).kind = kind;   // 记录图/网的类型
    } else {
        // 打开文件,准备读取测试数据
        fp = fopen(path[kind], "r");
        if(fp == NULL) {
            return ERROR;
        }

        // 录入图的类型
        ReadData(fp, "%d", &((*G).kind));
    }

    // 随机创建无向图或无向网的一种
    switch((*G).kind) {
        case UDG:
            flag = CreateUDG(G);
            break;
        case UDN:
            flag = CreateUDN(G);
            break;
        default:
            flag = ERROR;
            break;
    }

    if(fp != NULL) {
        fclose(fp);
        fp = NULL;
    }

    return flag;
}

/*
 * 构造无向图
 */
static Status CreateUDG(AMLGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).edgenum = 0;

    if(fp == NULL) {
        printf("请输入无向图的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入无向图的边数:");
        scanf("%d", &arcnum);
        printf("该无向图的边上是否包含其他附加信息(0-不包含│1-包含):");
        scanf("%d", &IncInfo);

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).adjmulist[i].data));
            (*G).adjmulist[i].firstedge = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入边数
        ReadData(fp, "%d", &IncInfo);// 判断边上是否包含附加信息

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).adjmulist[i].data));
            (*G).adjmulist[i].firstedge = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为无向图依次录入 %d 条边的信息,顶点之间用空格隔开:\n", arcnum);
    }

    // 录入边的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息
            Input(*G, &info);
        }

        // 插入边<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 构造无向网
 */
static Status CreateUDN(AMLGraph* G) {
    int i, k;
    int vexnum, arcnum;
    VertexType v1, v2;
    InfoType* info = NULL;

    (*G).vexnum = (*G).edgenum = 0;

    if(fp == NULL) {
        printf("请输入无向网的顶点数:");
        scanf("%d", &vexnum);
        printf("请输入无向网的边数:");
        scanf("%d", &arcnum);
        printf("该无向网的边上必须包含其他附加信息,因为此处的权值需要存储到附加信息中...\n");
        IncInfo = 1;

        // 录入顶点集
        printf("请录入 %d 个顶点,不同顶点之间用空格隔开:", vexnum);
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(stdin);
            scanf("%c", &((*G).adjmulist[i].data));
            (*G).adjmulist[i].firstedge = NULL;
            (*G).vexnum++;
        }
    } else {
        ReadData(fp, "%d", &vexnum); // 录入顶点数
        ReadData(fp, "%d", &arcnum); // 录入边数
        ReadData(fp, "%d", &IncInfo);// 判断边上是否包含附加信息
        IncInfo = 1;    // 强制将权值录入到附加信息中

        // 录入顶点集
        for(i = 0; i < vexnum; i++) {
            // 跳过空白,寻找下一个"可读"符号
            skipBlank(fp);
            ReadData(fp, "%c", &((*G).adjmulist[i].data));
            (*G).adjmulist[i].firstedge = NULL;
            (*G).vexnum++;
        }
    }

    // 仅在控制台录入信息时输出此提示
    if(fp == NULL && arcnum != 0) {
        printf("请为无向网依次录入 %d 条边(带权值)的信息,顶点及权值之间用空格隔开:\n", arcnum);
    }

    // 录入边的信息
    for(k = 0; k < arcnum; k++) {
        if(fp == NULL) {
            printf("第 %2d 条边及其权值:", k + 1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v1);
            skipBlank(stdin);   // 跳过空白,寻找下一个可读符号
            scanf("%c", &v2);
        } else {
            // 跳过空白,寻找下一个可读符号
            skipBlank(fp);
            ReadData(fp, "%c%c", &v1, &v2);
        }

        // 如果需要录入边的其他附加信息
        if(IncInfo) {
            // 最后录入附加信息(此处需要录入网的权值)
            Input(*G, &info);
        }

        // 插入边<v1, v2>
        InsertArc(G, v1, v2, info);
    }

    // 从文件中读取数据时,最后其实应当判断一下是否读到了足够的信息
    return OK;
}

/*
 * 录入边的相关附加信息
 */
static void Input(AMLGraph G, InfoType** info) {
    int weight;

    // 在"网"的情形下需要录入权值信息
    if(G.kind == UDN) {
        *info = (InfoType*) malloc(sizeof(InfoType));

        if(fp == NULL) {
            scanf("%d", &weight);
        } else {
            ReadData(fp, "%d", &weight);
        }

        (*info)->weight = weight;
    }
}

/*
 * 销毁
 *
 * 邻接表存储的图需要释放内存。
 */
Status DestroyGraph(AMLGraph* G) {
    int k, other;
    EBox* p, * r;

    // 逆序遍历
    for(k = G->vexnum-1; k >=0; k--) {
        p = NULL;
        r = G->adjmulist[k].firstedge;

        // 删除ivex处为k的边,保留jvex处为k的边
        while(r != NULL) {
            if(r->ivex == k) {
                other = r->jvex;
            } else if(r->jvex==k) {
                other = r->ivex;
            } else {
                // 不会至此
            }

            // 暂时保存这条边
            if(other<k) {
                p = r;

                if(r->ivex == k) {
                    r = r->ilink;
                } else if(r->jvex==k) {
                    r = r->jlink;
                } else {
                    // 不会至此
                }

                // 删除这条边
            } else {
                if(p==NULL) {
                    if(r->ivex == k) {
                        G->adjmulist[k].firstedge = r->ilink;
                    } else if(r->jvex==k) {
                        G->adjmulist[k].firstedge = r->jlink;
                    } else {
                        // 不会至此
                    }

                    free(r);
                    r = G->adjmulist[k].firstedge;
                } else {
                    if(p->ivex==k) {
                        if(r->ivex == k) {
                            p->ilink = r->ilink;
                        } else if(r->jvex==k) {
                            p->ilink = r->jlink;
                        } else {
                            // 不会至此
                        }

                        free(r);
                        r = p->ilink;
                    } else if(p->jvex==k) {
                        if(r->ivex == k) {
                            p->jlink = r->ilink;
                        } else if(r->jvex==k) {
                            p->jlink = r->jlink;
                        } else {
                            // 不会至此
                        }

                        free(r);
                        r = p->jlink;
                    } else {
                        // 不会至此
                    }
                }
            }
        }
    }

    (*G).vexnum = 0;
    (*G).edgenum = 0;
    IncInfo = 0;

    return OK;
}

/*
 * 查找
 *
 * 返回顶点u在图/网中的位置
 */
int LocateVex(AMLGraph G, VertexType u) {
    int i;

    for(i = 0; i < G.vexnum; i++) {
        if(G.adjmulist[i].data == u) {
            return i;
        }
    }

    return -1;
}

/*
 * 取值
 *
 * 返回索引v处的顶点值
 */
VertexType GetVex(AMLGraph G, int v) {
    if(v < 0 || v >= G.vexnum) {
        return '\0';    // 指定的顶点不存在
    }

    return G.adjmulist[v].data;
}

/*
 * 赋值
 *
 * 将顶点v赋值为value
 */
Status PutVex(AMLGraph* G, VertexType v, VertexType value) {
    int k;

    // 首先需要判断该顶点是否存在
    k = LocateVex((*G), v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    // 替换头结点
    (*G).adjmulist[k].data = value;

    /* 链表中的元素存储的是顶点的位置,所以无需遍历链表来替换目标值 */

    return OK;
}

/*
 * 首个邻接点
 *
 * 返回顶点v的首个邻接点
 */
int FirstAdjVex(AMLGraph G, VertexType v) {
    int k;
    EBox* r;

    // 首先需要判断该顶点是否存在
    k = LocateVex(G, v);
    if(k == -1) {
        return -1;    // 指定的顶点不存在
    }

    r = G.adjmulist[k].firstedge;
    if(r == NULL) {
        return -1;
    } else {
        if(r->ivex == k) {
            return r->jvex;
        } else if(r->jvex==k) {
            return r->ivex;
        } else {
            return -1;  // 不会至此
        }
    }
}

/*
 * 下一个邻接点
 *
 * 返回顶点v的(相对于w的)下一个邻接点
 */
int NextAdjVex(AMLGraph G, VertexType v, VertexType w) {
    int kv, kw;
    EBox* r;

    // 首先需要判断该顶点是否存在
    kv = LocateVex(G, v);
    if(kv == -1) {
        return -1;    // 指定的顶点不存在
    }

    // 首先需要判断该顶点是否存在
    kw = LocateVex(G, w);
    if(kw == -1) {
        return -1;    // 指定的顶点不存在
    }

    r = G.adjmulist[kv].firstedge;
    if(r == NULL) {
        return -1;    // 链表为空
    }

    // 在链表中查找w
    while(r != NULL) {
        if(r->ivex == kv && r->jvex < kw) {
            r = r->ilink;
        } else if(r->jvex == kv && r->ivex < kw) {
            r = r->jlink;
        } else {
            break;
        }
    }

    // 如果没找到w
    if(r == NULL) {
        return -1;
    }

    // 如果找到了w,但是w后面没有别的顶点,那么也无法返回邻接点
    if(r->ivex == kv && r->jvex == kw && r->ilink!=NULL) {
        r = r->ilink;
    } else if(r->jvex == kv && r->ivex == kw && r->jlink!=NULL) {
        r = r->jlink;
    } else {
        return -1;
    }

    // 向相邻的边中获取到相邻顶点
    if(r->ivex==kv) {
        return r->jvex;
    } else if(r->jvex==kv) {
        return r->ivex;
    } else {
        return -1;  // 不会至此
    }
}

/*
 * 插入顶点
 *
 * 将指定的顶点v追加到顶点集中,未建立该顶点与其他顶点的关系
 */
Status InsertVex(AMLGraph* G, VertexType v) {
    int k;

    // 顶点数过多
    if((*G).vexnum == MAX_VERTEX_NUM) {
        return ERROR;
    }

    // 首先需要判断该顶点是否存在
    k = LocateVex(*G, v);
    if(k >= 0) {
        return ERROR;    // 指定的顶点存在时,无需重复添加
    }

    G->adjmulist[(*G).vexnum].data = v;
    G->adjmulist[(*G).vexnum].firstedge = NULL;

    (*G).vexnum++;

    return OK;
}

/*
 * 删除顶点
 *
 * 从顶点集中删除指定的顶点v,注意需要更新相关的顶点关系
 */
Status DeleteVex(AMLGraph* G, VertexType v) {
    int i, k, other;
    EBox* p;
    EBox* pre, * r;

    // 首先需要判断该顶点是否存在
    k = LocateVex(*G, v);
    if(k == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    while((p = G->adjmulist[k].firstedge) != NULL) {
        if(p->ivex == k) {
            G->adjmulist[k].firstedge = p->ilink;
            other = p->jvex;
        } else if(p->jvex == k) {
            G->adjmulist[k].firstedge = p->jlink;
            other = p->ivex;
        } else {
            // 没有其他可能
        }

        pre = NULL;
        r = G->adjmulist[other].firstedge;

        // 查找边<other, k>,肯定能找到
        while(r != NULL) {
            if(r->ivex == other && r->jvex < k) {
                pre = r;
                r = r->ilink;
            } else if(r->jvex == other && r->ivex < k) {
                pre = r;
                r = r->jlink;
            } else {
                break;
            }
        }

        if(r != NULL && r->ivex == other && r->jvex == k) {
            if(pre == NULL) {
                G->adjmulist[other].firstedge = r->ilink;
            } else {
                if(pre->ivex == other) {
                    pre->ilink = r->ilink;
                } else if(pre->jvex == other) {
                    pre->jlink = r->ilink;
                } else {
                    // 没有其他可能
                }
            }
        } else if(r != NULL && r->jvex == other && r->ivex == k) {
            if(pre == NULL) {
                G->adjmulist[other].firstedge = r->jlink;
            } else {
                if(pre->ivex == other) {
                    pre->ilink = r->jlink;
                } else if(pre->jvex == other) {
                    pre->jlink = r->jlink;
                } else {
                    // 没有其他可能
                }
            }
        } else {
            // 不会至此,因为肯定能找到
        }

        free(p);

        (*G).edgenum--;  // 边数减一
    }

    // 下标超过k的顶点,需要递减其下标
    for(i = k + 1; i < (*G).vexnum; i++) {
        r = G->adjmulist[i].firstedge;

        while(r!=NULL){
            if(r->ivex==i) {
                r->ivex--;
                r = r->ilink;
            } else if(r->jvex==i) {
                r->jvex--;
                r = r->jlink;
            } else {
                // 不会至此
            }
        }
    }

    // 顶点集前移
    for(i = k + 1; i < (*G).vexnum; i++) {
        G->adjmulist[i - 1] = G->adjmulist[i];
    }

    // 顶点数递减
    (*G).vexnum--;

    return OK;
}

/*
 * 构造一个边结点(仅限内部使用)
 */
static EBox* newEBoxPtr(VisitIf mark, int ivex, int jvex, EBox* ilink, EBox* jlink, InfoType* info) {
    EBox* p = (EBox*) malloc(sizeof(EBox));
    if(!p) {
        exit(OVERFLOW);
    }

    p->mark = mark;

    p->ivex = ivex;
    p->jvex = jvex;

    p->ilink = ilink;
    p->jlink = jlink;

    p->info = info;

    return p;
}

/*
 * 插入边<v, w>
 *
 * 当前图/网是无向的,且由于特殊的结构,使得插入一条边时只需要增加一对顶点关系,边的数量依然增一。
 * 对于图/网来说,可以在可变参数中列出边的附加信息。
 *
 * 注:此处接收的参数与MGraph有些不一样:网的附加信息中包含了各条边的权值。
 */
Status InsertArc(AMLGraph* G, VertexType v, VertexType w, ...) {
    int tail, head, k;
    EBox* p;
    EBox* pre;
    EBox* r;
    Boolean overlay = FALSE;   // 是否为覆盖添加
    InfoType* info = NULL;     // 边的附加信息
    va_list ap;

    tail = LocateVex(*G, v); // 获取顶点v在顶点集中的位置
    if(tail == -1) {
        return ERROR;  // 指定的顶点不存在
    }

    head = LocateVex(*G, w); // 获取顶点w在顶点集中的位置
    if(head == -1) {
        return ERROR;  // 指定的顶点不存在
    }

    // 拒绝环
    if(tail == head) {
        return ERROR;
    }

    // 如果边上存在附加信息
    if(IncInfo) {
        va_start(ap, w);                // 在w后查询首个可变参数
        info = va_arg(ap, InfoType*);   // 获取附加信息
        va_end(ap);
    }

    /* 接下来,需要查找合适的插入位置 */

    pre = NULL;
    r = G->adjmulist[tail].firstedge;

    while(r != NULL) {
        if(r->ivex == tail && r->jvex < head) {
            pre = r;
            r = r->ilink;
        } else if(r->jvex == tail && r->ivex < head) {
            pre = r;
            r = r->jlink;
        } else {
            break;
        }
    }

    if(r != NULL && r->ivex == tail && r->jvex == head) {
        r->info = info; // 复用该结点
        overlay = TRUE; // 发生了覆盖
    } else if(r != NULL && r->jvex == tail && r->ivex == head) {
        r->info = info; // 复用该结点
        overlay = TRUE; // 发生了覆盖
    } else {
        p = newEBoxPtr(unvisit, tail, head, r, NULL, info);

        if(pre == NULL) {
            G->adjmulist[tail].firstedge = p;
        } else {
            if(pre->ivex == tail) {
                pre->ilink = p;
            } else if(pre->jvex == tail) {
                pre->jlink = p;
            } else {
                // 没有其他可能
            }
        }
    }

    if(!overlay) {
        pre = NULL;
        r = G->adjmulist[head].firstedge;

        while(r != NULL) {
            if(r->ivex == head && r->jvex < tail) {
                pre = r;
                r = r->ilink;
            } else if(r->jvex == head && r->ivex < tail) {
                pre = r;
                r = r->jlink;
            } else {
                break;
            }
        }

        p->jlink = r;

        if(pre == NULL) {
            G->adjmulist[head].firstedge = p;
        } else {
            if(pre->ivex == head) {
                pre->ilink = p;
            } else if(pre->jvex == head) {
                pre->jlink = p;
            } else {
                // 没有其他可能
            }
        }
    }

    (*G).edgenum++;  // 边数增一

    return OK;
}

/*
 * 删除边<v, w>
 */
Status DeleteArc(AMLGraph* G, VertexType v, VertexType w) {
    int tail, head;
    EBox* r;
    EBox* pre;

    tail = LocateVex(*G, v);
    if(tail == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    head = LocateVex(*G, w);
    if(head == -1) {
        return ERROR;    // 指定的顶点不存在
    }

    pre = NULL;
    r = G->adjmulist[tail].firstedge;

    while(r != NULL) {
        if(r->ivex == tail && r->jvex < head) {
            pre = r;
            r = r->ilink;
        } else if(r->jvex == tail && r->ivex < head) {
            pre = r;
            r = r->jlink;
        } else {
            break;
        }
    }

    if(r != NULL && r->ivex == tail && r->jvex == head) {
        if(pre == NULL) {
            G->adjmulist[tail].firstedge = r->ilink;
        } else {
            if(pre->ivex == tail) {
                pre->ilink = r->ilink;
            } else if(pre->jvex == tail) {
                pre->jlink = r->ilink;
            } else {
                // 没有其他可能
            }
        }
    } else if(r != NULL && r->jvex == tail && r->ivex == head) {
        if(pre == NULL) {
            G->adjmulist[tail].firstedge = r->jlink;
        } else {
            if(pre->ivex == tail) {
                pre->ilink = r->jlink;
            } else if(pre->jvex == tail) {
                pre->jlink = r->jlink;
            } else {
                // 没有其他可能
            }
        }
    } else {
        return ERROR;   // 未找到
    }


    pre = NULL;
    r = G->adjmulist[head].firstedge;

    while(r != NULL) {
        if(r->ivex == head && r->jvex < tail) {
            pre = r;
            r = r->ilink;
        } else if(r->jvex == head && r->ivex < tail) {
            pre = r;
            r = r->jlink;
        } else {
            break;
        }
    }

    if(r != NULL && r->ivex == head && r->jvex == tail) {
        if(pre == NULL) {
            G->adjmulist[head].firstedge = r->ilink;
        } else {
            if(pre->ivex == head) {
                pre->ilink = r->ilink;
            } else if(pre->jvex == head) {
                pre->jlink = r->ilink;
            } else {
                // 没有其他可能
            }
        }
    } else if(r != NULL && r->jvex == head && r->ivex == tail) {
        if(pre == NULL) {
            G->adjmulist[head].firstedge = r->jlink;
        } else {
            if(pre->ivex == head) {
                pre->ilink = r->jlink;
            } else if(pre->jvex == head) {
                pre->jlink = r->jlink;
            } else {
                // 没有其他可能
            }
        }
    } else {
        // 不会至此,因为前面找到了,此处肯定也能找到
    }

    free(r); // 释放内存

    (*G).edgenum--;  // 边数减一

    return OK;
}

/*
 * 深度优先遍历(此处借助递归实现)
 */
void DFSTraverse(AMLGraph G, Status(Visit)(VertexType)) {
    int v;

    // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
    VisitFunc = Visit;

    // 访问标志数组初始化
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 此处需要遍历的原因是并不能保证所有顶点都连接在了一起
    for(v = 0; v < G.vexnum; v++) {
        if(!visited[v]) {
            DFS(G, v);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 深度优先遍历核心函数
 */
static void DFS(AMLGraph G, int v) {
    int w;

    // 从第v个顶点出发递归地深度优先遍历图G
    visited[v] = TRUE;

    // 访问第v个顶点
    VisitFunc(G.adjmulist[v].data);

    for(w = FirstAdjVex(G, G.adjmulist[v].data);
        w >= 0;
        w = NextAdjVex(G, G.adjmulist[v].data, G.adjmulist[w].data)) {
        if(!visited[w]) {
            DFS(G, w);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 广度优先遍历(此处借助队列实现)
 */
void BFSTraverse(AMLGraph G, Status(Visit)(VertexType)) {
    int v, w;
    LinkQueue Q;
    QElemType u;

    // 初始化为未访问
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 置空辅助队列
    InitQueue(&Q);

    for(v = 0; v < G.vexnum; v++) {
        // 如果该顶点已访问过,则直接忽略
        if(visited[v]) {
            continue;
        }

        // 标记该顶点已访问
        visited[v] = TRUE;

        // 访问顶点
        Visit(G.adjmulist[v].data);

        EnQueue(&Q, v);

        while(!QueueEmpty(Q)) {
            DeQueue(&Q, &u);

            // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
            for(w = FirstAdjVex(G, G.adjmulist[u].data);
                w >= 0;
                w = NextAdjVex(G, G.adjmulist[u].data, G.adjmulist[w].data)) {
                if(!visited[w]) {
                    visited[w] = TRUE;
                    Visit(G.adjmulist[w].data);
                    EnQueue(&Q, w);
                }
            }
        }
    }
}

/*
 * 以图形化形式输出当前结构
 */
void PrintGraph(AMLGraph G) {
    int i, cur, pos;
    EBox* p;

    if(G.vexnum == 0) {
        printf("空图,无需打印!\n");
        return;
    }

    printf("当前图/网包含 %2d 个顶点, %2d 条边...\n", G.vexnum, G.edgenum);

    for(i = 0; i < G.vexnum; i++) {
        printf("%c ===> ", G.adjmulist[i].data);

        cur = 0;
        p = G.adjmulist[i].firstedge;

        while(p != NULL) {
            if(p->ivex == i) {
                pos = p->jvex;
            } else {
                pos = p->ivex;
            }

            if(cur < pos) {
                if(IncInfo == 0) {
                    printf(" ");

                    // 对于网,会从其附加信息中获取到权值
                } else {
                    printf("     ");
                }
            } else {
                if(IncInfo == 0) {
                    printf("%c", G.adjmulist[pos].data);

                    // 对于网,会从其附加信息中获取到权值
                } else {
                    printf("%c[%2d]", G.adjmulist[pos].data, p->info->weight);
                }

                if(p->ivex == i) {
                    p = p->ilink;
                } else {
                    p = p->jlink;
                }
            }

            cur++;

            if(p != NULL) {
                printf("  ");
            }
        }

        printf("\n");
    }
}
  • 图的深度遍历、广度遍历(基于邻接矩阵、邻接表)__*

邻接矩阵

/*
 * ████████ 算法7.4 ████████
 *
 * 深度优先遍历(此处借助递归实现)
 */
void DFSTraverse(MGraph G, Status(Visit)(VertexType)) {
    int v;

    // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
    VisitFunc = Visit;

    // 访问标志数组初始化
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 此处需要遍历的原因是并不能保证所有顶点都连接在了一起
    for(v = 0; v < G.vexnum; v++) {
        if(!visited[v]) {
            DFS(G, v);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * ████████ 算法7.5 ████████
 *
 * 深度优先遍历核心函数
 */
static void DFS(MGraph G, int v) {
    int w;

    // 从第v个顶点出发递归地深度优先遍历图G
    visited[v] = TRUE;

    // 访问第v个顶点
    VisitFunc(G.vexs[v]);

    for(w = FirstAdjVex(G, G.vexs[v]);
        w >= 0;
        w = NextAdjVex(G, G.vexs[v], G.vexs[w])) {
        if(!visited[w]) {
            DFS(G, w);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * ████████ 算法7.6 ████████
 *
 * 广度优先遍历(此处借助队列实现)
 */
void BFSTraverse(MGraph G, Status(Visit)(VertexType)) {
    int v, w;
    LinkQueue Q;
    QElemType u;

    // 初始化为未访问
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 置空辅助队列
    InitQueue(&Q);

    for(v = 0; v < G.vexnum; v++) {
        // 如果该顶点已访问过,则直接忽略
        if(visited[v]) {
            continue;
        }

        // 标记该顶点已访问
        visited[v] = TRUE;

        // 访问顶点
        Visit(G.vexs[v]);

        EnQueue(&Q, v);

        while(!QueueEmpty(Q)) {
            DeQueue(&Q, &u);

            // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
            for(w = FirstAdjVex(G, G.vexs[u]);
                w >= 0;
                w = NextAdjVex(G, G.vexs[u], G.vexs[w])) {
                if(!visited[w]) {
                    visited[w] = TRUE;
                    Visit(G.vexs[w]);
                    EnQueue(&Q, w);
                }
            }
        }
    }
}

邻接表

/*
 * 深度优先遍历(此处借助递归实现)
 */
void DFSTraverse(ALGraph G, Status(Visit)(VertexType)) {
    int v;

    // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
    VisitFunc = Visit;

    // 访问标志数组初始化
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 此处需要遍历的原因是并不能保证所有顶点都连接在了一起
    for(v = 0; v < G.vexnum; v++) {
        if(!visited[v]) {
            DFS(G, v);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 深度优先遍历核心函数
 */
static void DFS(ALGraph G, int v) {
    int w;

    // 从第v个顶点出发递归地深度优先遍历图G
    visited[v] = TRUE;

    // 访问第v个顶点
    VisitFunc(G.vertices[v].data);

    for(w = FirstAdjVex(G, G.vertices[v].data);
        w >= 0;
        w = NextAdjVex(G, G.vertices[v].data, G.vertices[w].data)) {
        if(!visited[w]) {
            DFS(G, w);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 广度优先遍历(此处借助队列实现)
 */
void BFSTraverse(ALGraph G, Status(Visit)(VertexType)) {
    int v, w;
    LinkQueue Q;
    QElemType u;

    // 初始化为未访问
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 置空辅助队列
    InitQueue(&Q);

    for(v = 0; v < G.vexnum; v++) {
        // 如果该顶点已访问过,则直接忽略
        if(visited[v]) {
            continue;
        }

        // 标记该顶点已访问
        visited[v] = TRUE;

        // 访问顶点
        Visit(G.vertices[v].data);

        EnQueue(&Q, v);

        while(!QueueEmpty(Q)) {
            DeQueue(&Q, &u);

            // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
            for(w = FirstAdjVex(G, G.vertices[u].data);
                w >= 0;
                w = NextAdjVex(G, G.vertices[u].data, G.vertices[w].data)) {
                if(!visited[w]) {
                    visited[w] = TRUE;
                    Visit(G.vertices[w].data);
                    EnQueue(&Q, w);
                }
            }
        }
    }
}
/*
 * 深度优先遍历(此处借助递归实现)
 */
void DFSTraverse(OLGraph G, Status(Visit)(VertexType)) {
    int v;

    // 使用全局变量VisitFunc,使得DFS不必设置函数指针参数
    VisitFunc = Visit;

    // 访问标志数组初始化
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 此处需要遍历的原因是并不能保证所有顶点都连接在了一起
    for(v = 0; v < G.vexnum; v++) {
        if(!visited[v]) {
            DFS(G, v);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 深度优先遍历核心函数
 */
static void DFS(OLGraph G, int v) {
    int w;

    // 从第v个顶点出发递归地深度优先遍历图G
    visited[v] = TRUE;

    // 访问第v个顶点
    VisitFunc(G.xlist[v].data);

    for(w = FirstAdjVex(G, G.xlist[v].data);
        w >= 0;
        w = NextAdjVex(G, G.xlist[v].data, G.xlist[w].data)) {
        if(!visited[w]) {
            DFS(G, w);  // 对尚未访问的顶点调用DFS
        }
    }
}

/*
 * 广度优先遍历(此处借助队列实现)
 */
void BFSTraverse(OLGraph G, Status(Visit)(VertexType)) {
    int v, w;
    LinkQueue Q;
    QElemType u;

    // 初始化为未访问
    for(v = 0; v < G.vexnum; v++) {
        visited[v] = FALSE;
    }

    // 置空辅助队列
    InitQueue(&Q);

    for(v = 0; v < G.vexnum; v++) {
        // 如果该顶点已访问过,则直接忽略
        if(visited[v]) {
            continue;
        }

        // 标记该顶点已访问
        visited[v] = TRUE;

        // 访问顶点
        Visit(G.xlist[v].data);

        EnQueue(&Q, v);

        while(!QueueEmpty(Q)) {
            DeQueue(&Q, &u);

            // 先集中访问顶点v的邻接顶点,随后再访问邻接顶点的邻接顶点
            for(w = FirstAdjVex(G, G.xlist[u].data);
                w >= 0;
                w = NextAdjVex(G, G.xlist[u].data, G.xlist[w].data)) {
                if(!visited[w]) {
                    visited[w] = TRUE;
                    Visit(G.xlist[w].data);
                    EnQueue(&Q, w);
                }
            }
        }
    }
}

拓扑排序

/*
 * ████████ 算法7.12 ████████
 *
 * 如果有向图G无回路,则输出它的一个拓扑序列并返回OK;否则,返回FALSE。
 * 拓扑序列通常不唯一,但是必须保证某些关键节点的先后次序。
 *
 * 注:与教材不同之处在于,此处增加了一个topo数组做参数。
 *   对于求出的拓扑序列,不会直接输出,而是会缓存到topo中。
 *   这样的做的目的是后续的习题中会有多处用到该拓扑序列。
 */
Status TopologicalSort(ALGraph G, int topo[MAX_VERTEX_NUM]) {
    int i, k, count;
    int indegree[MAX_VERTEX_NUM];
    SqStack S;
    ArcNode* p;

    // 对各顶点求入度
    FindInDegree(G, indegree);

    // 初始化零入度顶点栈
    InitStack(&S);

    // 建立入度为0的顶点栈
    for(i = 0; i < G.vexnum; i++) {
        // 将入度为0的顶点添加到栈中
        if(indegree[i] == 0) {
            Push(&S, i);
        }
    }

    // 对拓扑序列中的顶点计数
    count = 0;

    // 遍历顶点栈
    while(!StackEmpty(S)) {
        // 获取一个零入度顶点
        Pop(&S, &i);

        // 将各顶点的序号暂存起来
        topo[count++] = i;

        // 遍历i号顶点的邻接点
        for(p = G.vertices[i].firstarc; p != NULL; p = p->nextarc) {
            // 获取顶点序号
            k = p->adjvex;

            /*
             * 将i号顶点每个邻接点的入度减一,这相当于切段i号顶点到其它邻接点的联系。
             * 如果出现了新的入度为0的顶点,继续将其入栈。
             */
            if((--indegree[k]) == 0) {
                Push(&S, k);
            }
        }
    }

    // 如果遇到了回路,则返回ERROR
    if(count < G.vexnum) {
        return ERROR;
    } else {
        return OK;
    }
}

/*
 * 计算各顶点的入度
 */
static void FindInDegree(ALGraph G, int indegree[MAX_VERTEX_NUM]) {
    int i;
    ArcNode* p;

    // 初始化所有顶点的入度为0
    for(i = 0; i < G.vexnum; i++) {
        indegree[i] = 0;
    }

    // 遍历所有顶点
    for(i = 0; i < G.vexnum; i++) {
        // 指向该顶点的首个邻接点
        p = G.vertices[i].firstarc;

        // 遍历该顶点的所有邻接点,统计各顶点的入度
        while(p != NULL) {
            indegree[p->adjvex]++;
            p = p->nextarc;
        }
    }
}

最小生成树

/*
 * 普里姆算法中用到的辅助数组,
 * 用来记录从顶点子集U到顶点子集V-U的代价最小的边
 */
static struct {
    VertexType adjvex;      // 顶点子集U中的顶点
    VRType lowcost;         // 顶点子集V-U到当前顶点的边的权值
} closedge[MAX_VERTEX_NUM]; // 辅助数组


/*
 * ████████ 算法7.9 ████████
 *
 * 普里姆算法
 *
 * 从第u个顶点出发构造无向网G的最小生成树T,输出T的各条边。
 * 该算法的耗时部分是对顶点的遍历,与网中的边数无关,因为适用于边比较稠密的网
 *
 * 注:预设图的权值均大于0,允许调整
 */
void MinSpanTree_PRIM(MGraph G, VertexType u) {
    int i, j, k;

    // 返回顶点u在无向网中的位置
    k = LocateVex(G, u);

    // 辅助数组初始化,将顶点u加入了顶点子集U
    for(j = 0; j < G.vexnum; j++) {
        if(j != k) {
            closedge[j].adjvex = u;
            closedge[j].lowcost = G.arcs[k][j].adj;
        }
    }

    // 赋值为0意味着顶点k已进入顶点子集U
    closedge[k].lowcost = 0;

    // 选择其余G.vexnum-1个顶点
    for(i = 1; i < G.vexnum; i++) {
        // 从顶点子集V-U中选出下一个候选顶点以便后续加入到最小生成树
        k = minimum(G);

        // 打印顶点和边的信息
        printf("%c --%d-- %c\n", closedge[k].adjvex, closedge[k].lowcost, G.vexs[k]);

        // 将顶点k加入到顶点子集U
        closedge[k].lowcost = 0;

        // 新顶点进入顶点子集U后,需要更新顶点子集U与顶点子集V-U的边的信息
        for(j = 0; j < G.vexnum; j++) {
            if(G.arcs[k][j].adj < closedge[j].lowcost) {
                closedge[j].adjvex = G.vexs[k];
                closedge[j].lowcost = G.arcs[k][j].adj;
            }
        }
    }
}

/*
 * 从顶点子集V-U中选出下一个候选顶点以便后续加入到最小生成树
 *
 * 选择顶点子集U到顶点子集V-U的权值最小的边,
 * 并返回该边在顶点子集V-U中那头的端点,
 * 该端点后续会加入到顶点子集U中,成为最小生成树的新结点。
 *
 * 注:形参与教材中给出的模板有所不同
 */
static int minimum(MGraph G) {
    int i, k = -1;
    int min = INT_MAX;

    // 从权值不为0的边中选择拥有最小权值的边
    for(i = 0; i < G.vexnum; i++) {
        if(closedge[i].lowcost != 0 && closedge[i].lowcost < min) {
            min = closedge[i].lowcost;
            k = i;
        }
    }

    return k;
}

/*
 * 克鲁斯卡尔算法
 *
 * 从第u个顶点出发构造无向网G的最小生成树T,输出T的各条边
 * 该算法的耗时部分是对边的遍历,与网中的顶点无关,因为适用于边比较稀疏的网
 *
 * 可以改进之处:对已有边排序时可以采用第9章介绍的堆排序
 */
void MinSpanTree_KRUSKAL(MGraph G) {
    int i, j, k;
    int s1, s2;

    // 边集
    struct Edge {
        int v1;         // 顶点1的下标
        int v2;         // 顶点2的下标
        VRType adj;     // 权值
    } * edges, tmp;

    // 记录已经加入到最小生成树的顶点集,这里使用集合的目的是快速判断候选顶点是否会造成环路
    MFSet S;
    Relation relation;

    // 边集的容量就是边的数量
    edges = (struct Edge*)malloc(G.arcnum* sizeof(struct Edge));

    // 对边计数
    k = 0;

    // 获取所有的边
    for(i = 0; i < G.vexnum; i++) {
        // 由于网是无向的,所以只遍历一半的边就可以
        for(j = 0; j <= i; j++) {
            // 只对有效的边进行统计
            if(G.arcs[i][j].adj != INFINITE) {
                edges[k].v1 = i;
                edges[k].v2 = j;
                edges[k].adj = G.arcs[i][j].adj;
                k++;
            }
        }
    }

    // 根据权值从小到大对边进行排序,这里只是简单使用了效率较低的冒泡排序
    for(i = 0; i < G.arcnum - 1; i++) {
        for(j = 0; j < G.arcnum - i - 1; j++) {
            // 每轮遍历都将权值大的往后挪
            if(edges[j].adj > edges[j + 1].adj) {
                tmp = edges[j];
                edges[j] = edges[j + 1];
                edges[j + 1] = tmp;
            }
        }
    }

    // 初始化顶点集合
    initial_mfset(&S, G.vexnum);

    // 从边集中依次选择权值最小,且不构成环路的边加入到最小生成树
    for(i = 0; i < G.arcnum; i++) {
        s1 = find_mfset(S, edges[i].v1);
        s2 = find_mfset(S, edges[i].v2);

        // 如果这两个端点位于同一个集合,则跳过该条边
        if(s1 == s2) {
            continue;
        }

        // 构造二元关系
        relation.n = 1;
        relation.pairs[0].i = edges[i].v1;
        relation.pairs[0].j = edges[i].v2;

        build_mfset(&S, relation);

        // 打印顶点和边的信息
        printf("%c --%d-- %c\n", GetVex(G, edges[i].v1), edges[i].adj, GetVex(G, edges[i].v2));
    }
}

关键活动

/* 全局变量 */
static int ve[MAX_VERTEX_NUM];  // 各事件(顶点)的最早发生时间(越早越好)
static int vl[MAX_VERTEX_NUM];  // 各事件(顶点)的最晚发生时间(越晚越好)

/*
 * ████████ 算法7.14 ████████
 *
 * 计算有向网G的关键路径
 *
 * 注:有向网顶点序列中最后一个顶点必须为事件终点
 */
Status CriticalPath(ALGraph G) {
    int i, j, k;
    ArcNode* p;
    SqStack T;  // 拓扑序列顶点栈
    int dut;    // 活动持续时间
    int ee, el;
    char tag;
    int count;

    int path[MAX_VERTEX_NUM];           // 存储拓扑序列,跟T中存储的信息一致
    Boolean P[MAX_VERTEX_NUM]= {FALSE}; // 存储关键路径信息,只是为了最后的展示

    // 如果有向网存在回路,则无法计算其拓扑序列,进而也无法得出该有向网的关键路径
    if(!TopologicalOrder(G, &T)) {
        printf("异常退出:给定的有向网中存在回路,故无法生成完整的拓扑序列...\n");
        return ERROR;
    } else {
        printf("有向网的拓扑序列构建成功...\n");
        // 复制拓扑序列到path中以便后续输出
        for(i=0; i<StackLength(T); i++) {
            path[i] = T.base[i];
        }
    }

    // 初始化各事件的最晚发生时间为所有事件中最晚出现的那个最早发生时间(拓扑序列上最后一个事件的最早发生时间)
    for(i = 0; i < G.vexnum; i++) {
        vl[i] = ve[G.vexnum-1]; // ve[G.vexnum-1]代表终点的最早发生时间
    }

    // 逆序遍历拓扑序列顶点栈(将栈中顶点出栈即可达到逆序的目的)
    while(!StackEmpty(T)) {
        // 获取一个顶点
        Pop(&T, &j);

        // 遍历j顶点的邻接点
        for(p = G.vertices[j].firstarc; p != NULL; p = p->nextarc) {
            k = p->adjvex;              // 顶点j的邻接点序号
            dut = (*(p->info)).weight;  // j->k的权值,即事件j->k的持续时间

            // 将事件j的最晚发生时间提前
            if(vl[k] - dut < vl[j]) {
                vl[j] = vl[k] - dut;
            }
        }
    }

    count = 0;

    printf("计算有向网的关键路径信息...\n");

    // 遍历所有顶点
    for(j = 0; j < G.vexnum; j++) {
        // 遍历j顶点的邻接点
        for(p = G.vertices[j].firstarc; p != NULL; p = p->nextarc) {
            k = p->adjvex;
            dut = (*(p->info)).weight;

            ee = ve[j];         // 事件j的最早发生时间
            el = vl[k] - dut;   // 事件j的最晚发生时间

            // 用*标记关键路径
            tag = (ee == el) ? '*' : ' ';

            printf("%c-%c  a%-2d=%2d  (%2d, %2d)  %c\n", G.vertices[j].data, G.vertices[k].data, count++, dut, ee, el, tag);

            // 记录哪些顶点加入到了关键路径中
            if(tag=='*') {
                P[j] = TRUE;
                P[k] = TRUE;
            }
        }
    }


    /*
     * 注:教材中没有后续步骤
     * 此处增加这些步骤是为了更直观地显示关键路径信息
     */
    printf("\n");
    printf("当前有向网的关键路径为:");
    for(i = 0; i < G.vexnum; i++) {
        if(P[path[i]] == TRUE) {
            printf("%c ", G.vertices[path[i]].data);
        }
    }
    printf("\n");

    return OK;
}

/*
 * ████████ 算法7.13 ████████
 *
 * 如果有向网G无回路,则寻找它的一个拓扑序列存储到拓扑序列栈T中,并返回OK;否则,返回FALSE。
 * 拓扑序列通常不唯一,但是必须保证某些关键节点的先后次序。
 */
static Status TopologicalOrder(ALGraph G, SqStack* T) {
    int i, j, k, count;
    int indegree[MAX_VERTEX_NUM + 1];
    SqStack S;
    ArcNode* p;

    // 对各顶点求入度
    FindInDegree(G, indegree);

    // 初始化零入度顶点栈
    InitStack(&S);

    // 初始化拓扑序列顶点栈
    InitStack(T);

    // 建立入度为0的顶点栈
    for(i = 0; i < G.vexnum; i++) {
        // 将入度为0的顶点添加到栈中
        if(indegree[i] == 0) {
            Push(&S, i);
        }

        // 初始化零入度顶点(事件)的最早发生时间为0
        ve[i] = 0;
    }

    // 对拓扑序列中的顶点计数
    count = 0;

    // 遍历顶点栈
    while(!StackEmpty(S)) {
        // 获取一个零入度顶点
        Pop(&S, &j);

        // 将该顶点加入到拓扑序列顶点栈中
        Push(T, j);

        count++;

        // 遍历i号顶点的邻接点
        for(p = G.vertices[j].firstarc; p != NULL; p = p->nextarc) {
            // 将j号顶点每个邻接点的入度减一,这相当于切段j号顶点到其它邻接点的联系
            k = p->adjvex;

            // 如果出现了新的入度为0的顶点,继续将其入栈
            if((--indegree[k]) == 0) {
                Push(&S, k);
            }

            // 计算邻接点事件的最早发生时间
            if(ve[j] + (*(p->info)).weight > ve[k]) {
                ve[k] = ve[j] + (*(p->info)).weight;
            }
        }
    }

    // 如果遇到了回路,则返回ERROR
    if(count < G.vexnum) {
        return ERROR;
    } else {
        return OK;
    }
}

/*
 * 计算各顶点的入度
 */
static void FindInDegree(ALGraph G, int indegree[MAX_VERTEX_NUM]) {
    int i;
    ArcNode* p;

    // 初始化所有顶点的入度为0
    for(i = 0; i < G.vexnum; i++) {
        indegree[i] = 0;
    }

    // 遍历所有顶点
    for(i = 0; i < G.vexnum; i++) {
        // 指向该顶点的首个邻接点
        p = G.vertices[i].firstarc;

        // 遍历该顶点的所有邻接点,统计各顶点的入度
        while(p != NULL) {
            indegree[p->adjvex]++;
            p = p->nextarc;
        }
    }
}
/*
 * ████████ 算法7.15 ████████
 *
 * 迪杰斯特拉(Dijkstra)算法求单源最短路径
 *
 * @param G  待遍历的图。
 * @param v0 单源最短路径的起点。
 * @param P  存储从v0到其余各顶点的最短路径信息。
 *           P[j][k]==TRUE代表从v0->j的最短路径会经过顶点k;否则,代表不经过。
 *           注:
 *           1.该数组只反映了顶点的存在性,而未反映顶点在路径上的次序。
 *           2.如果两顶点之间有多条最短路径,只会选取一条
 * @param D  存储从v0到其余各顶点的最短路径代价,在图中,该代价是步长和,在网中,该代价是权值和。
 */
void ShortestPath_DIJ(MGraph G, int v0, PathMatrix_DIJ P, ShortPathTable_DIJ D) {
    int v, w, i, j, min;
    Status final[MAX_VERTEX_NUM];   // 用来标记当前结点是否已经访问过,即是否已加入最短路径

    /*
     * 预设已访问过的顶点集为S,未访问过的顶点集为V-S
     * 初始时,顶点集S为空,而顶点集V-S为所有顶点的集合V
     */
    for(v = 0; v < G.vexnum; v++) {
        // 初始时,所有顶点均未访问过
        final[v] = FALSE;

        // 初始化v0->v的路径代价
        D[v] = G.arcs[v0][v].adj;

        // 预设从v0->v不需要经过顶点w,即设空路径
        for(w = 0; w < G.vexnum; w++) {
            P[v][w] = FALSE;
        }

        // 如果是网,只走此分支(教材只处理了此种情形)
        if(G.kind == DN || G.kind == UDN) {
            // 如果v0->v直接连通
            if(D[v] < INFINITE) {
                P[v][v0] = TRUE;    // 指示v0->v必将经过顶点v0(端点一)
                P[v][v] = TRUE;     // 指示v0->v必将经过顶点v(端点二)
            }
        }

        // 如果是图,只走此分支
        if(G.kind == DG || G.kind == UDG) {
            // 如果v0到v直接连通
            if(D[v] != 0) {
                P[v][v0] = TRUE;    // 指示v0->v必将经过顶点v0(端点一)
                P[v][v] = TRUE;     // 指示v0->v必将经过顶点v(端点二)
            }
        }
    }

    // 初始化,v0进入顶点集S
    D[v0] = 0;
    final[v0] = TRUE;

    /*
     * 开始主循环,遍历其余G1.vexnum-1个顶点。
     * 每次求得v0到某个顶点的最短路径,则将该顶点从顶点集V-S中挪到顶点集S中
     */
    for(i = 1; i < G.vexnum; i++) {
        /*
         * 每次进入选取最近顶点之前,都应当将v重置为一个正常顶点序号之外的值
         * (教材中无此步骤,需要补上,否则会进行很多次无效的循环)
         */
        v = -1;

        // 记录各顶点与顶点v0的最短距离
        min = INFINITE;

        // 遍历顶点集V-S(所有未加入路径的顶点),从中选出距离v0更近的顶点
        for(w = 0; w < G.vexnum; w++) {
            // 如果w顶点在V-S中(未加入最短路径)
            if(!final[w]) {
                // 对于图来说,距离为0意味着两顶点不连通
                if((G.kind == DG || G.kind == UDG) && D[w] == 0) {
                    continue;
                }

                // 如果v0到w的距离D[w]小于当前预设的最小距离,则需要更新顶点和距离信息
                if(D[w] < min) {
                    v = w;      // 记下未访问的顶点中距离v0更近的顶点,或者可以理解为记下顶点集V-S中距离顶点集S更近的顶点
                    min = D[w]; // 记下更近的距离
                }
            }
        }

        // 找到了更近的顶点
        if(v != -1) {
            // 将顶点集V-S中当前距离v0顶点最近的v加入S集
            final[v] = TRUE;
        } else {
            // 如果没找到更近的顶点,说明顶点v0与V-S中的剩余顶点没有连接,此时可以直接结束程序了
            return;
        }

        // 以上述的顶点v做跳板,更新顶点v0到V-S集中其余点顶点的最短路径
        for(w = 0; w < G.vexnum; w++) {
            // 如果是网,只走此分支(教材只处理了此种情形)
            if(G.kind == DN || G.kind == UDN) {
                /*
                 * 为了避免溢出,需要确保G.arcs[v][w].adj不为INFINITE。
                 * 此处其实没必要判断min!=INFINITE,因为如果没找到更近的顶点时,上面就提前返回了。
                 * (教材的伪码中未处理溢出,故需要自行添加)
                 */
                if(min != INFINITE && G.arcs[v][w].adj != INFINITE) {
                    // 更新顶点集S到顶点集V-S的最近距离
                    if(!final[w] && (min + G.arcs[v][w].adj < D[w])) {
                        // 更新距离:由于顶点v0->w经由v后会获得更短的距离,故此处需要记下该距离
                        D[w] = min + G.arcs[v][w].adj;

                        /* 更新路径:P[w] = P[v] + [w] */

                        // 先将v0->w的路径更新为v0->j的路径
                        for(j = 0; j < G.vexnum; j++) {
                            P[w][j] = P[v][j];
                        }

                        // 再将w添加到v0->w的路径中
                        P[w][w] = TRUE;
                    }
                }
            }

            // 如果是图,只走此分支
            if(G.kind == DG || G.kind == UDG) {
                // 更新顶点集S到顶点集V-S的最近距离
                if(!final[w] && G.arcs[v][w].adj != 0 && D[w] == 0) {
                    // 更新距离:由于顶点v0->w经由v后会获得更短的距离,故此处需要记下该距离
                    D[w] = D[v] + 1;    // 图中相邻顶点步长为1

                    /* 更新路径:P[w] = P[v] + [w] */

                    // 先将v0->w的路径更新为v0->j的路径
                    for(j = 0; j < G.vexnum; j++) {
                        P[w][j] = P[v][j];
                    }

                    // 再将w添加到v0->w的路径中
                    P[w][w] = TRUE;
                }
            }
        }
    }
}

/*
 * 打印使用迪杰斯特拉(Dijkstra)算法求取的单源最短路径
 */
void PrintPath_DIJ(MGraph G, int v0, PathMatrix_DIJ P, ShortPathTable_DIJ D) {
    int j;

    // 遍历所有路径
    for(j = 0; j < G.vexnum; j++) {
        if(P[j][v0] == FALSE) {
            printf("%c 到 %c 之间没有通路\n", GetVex(G, v0), GetVex(G, j));
            continue;
        }

        printf("%c 到 %c 之间的最短距离为%2d,其最短路径为:", GetVex(G, v0), GetVex(G, j), D[j]);

        /*
         * 将v0->j之间的无序顶点排列成一条有序路径后打印
         * P[i]指示v0->j这条路径上经过的顶点
         */
        PrintPath(G, v0, j, P[j]);
    }
}

/*
 * ████████ 算法7.16 ████████
 *
 * 弗洛伊德(Floyd)算法求多源最短路径
 *
 * @param G  待遍历的图。
 * @param P  存储各对顶点之间的最短路径信息。
 *           P[i][j][k]==TRUE代表从i->j的最短路径会经过顶点k;否则,代表不经过。
 *           注:该数组只反映了顶点的存在性,而未反映顶点在路径上的次序。
 * @param D  存储各对顶点之间的最短路径代价,在图中,该代价是步长和,在网中,该代价是权值和。
 */
void ShortestPath_FLOYD(MGraph G, PathMatrix_FLOYD P, ShortPathTable_FLOYD D) {
    int i, v, w, u;

    // 对各顶点之间初始已知路径及距离
    for(v = 0; v < G.vexnum; v++) {
        for(w = 0; w < G.vexnum; w++) {
            // 获取<v,w>的边/弧信息
            D[v][w] = G.arcs[v][w].adj;

            // 初始化路径v->w为空路径
            for(u = 0; u < G.vexnum; u++) {
                P[v][w][u] = FALSE;
            }

            // 如果是网,只走此分支(教材只处理了此种情形)
            if(G.kind == DN || G.kind == UDN) {
                // 如果v0->v直接连通
                if(D[v][w] < INFINITE) {
                    P[v][w][v] = TRUE;  // 指示v->w必将经过顶点v(端点一)
                    P[v][w][w] = TRUE;  // 指示v->w必将经过顶点w(端点二)
                }
            }

            // 如果是图,只走此分支
            if(G.kind == DG || G.kind == UDG) {
                // 如果v0->v直接连通
                if(D[v][w] != 0) {
                    P[v][w][v] = TRUE;  // 指示v->w必将经过顶点v(端点一)
                    P[v][w][w] = TRUE;  // 指示v->w必将经过顶点w(端点二)
                }
            }
        }
    }

    for(u = 0; u < G.vexnum; u++) {
        for(v = 0; v < G.vexnum; v++) {
            for(w = 0; w < G.vexnum; w++) {
                // 对于同一顶点,将其视为不连通
                if(v == w) {
                    continue;
                }

                // 如果是网,只走此分支(教材只处理了此种情形)
                if(G.kind == DN || G.kind == UDN) {
                    // 需要先判断权值信息,防止溢出
                    if(D[v][u] != INFINITE && D[u][w] != INFINITE) {
                        // 从v经u到w(v->u->w)的一条路径更短
                        if(D[v][u] + D[u][w] < D[v][w]) {
                            // 更新权值
                            D[v][w] = D[v][u] + D[u][w];

                            // 更新路径
                            for(i = 0; i < G.vexnum; i++) {
                                P[v][w][i] = P[v][u][i] || P[u][w][i];
                            }
                        }
                    }
                }

                // 如果是图,只走此分支
                if(G.kind == DG || G.kind == UDG) {
                    // 需要先判断连接信息
                    if(D[v][u] != 0 && D[u][w] != 0) {
                        // 从v经u到w(v->u->w)的一条路径更短
                        if(D[v][w] == 0 || D[v][u] + D[u][w] < D[v][w]) {
                            // 更新权值
                            D[v][w] = D[v][u] + D[u][w];

                            // 更新路径
                            for(i = 0; i < G.vexnum; i++) {
                                P[v][w][i] = P[v][u][i] || P[u][w][i];
                            }
                        }
                    }
                }
            }
        }
    }
}

/*
 * 打印使用弗洛伊德(Floyd)算法求取的单源最短路径
 */
void PrintPath_FLOYD(MGraph G, PathMatrix_FLOYD P, ShortPathTable_FLOYD D) {
    int i, j;

    // 遍历所有路径
    for(i = 0; i < G.vexnum; i++) {
        for(j = 0; j < G.vexnum; j++) {
            if(P[i][j][i] == FALSE) {
                printf("%c 到 %c 之间没有通路\n", GetVex(G, i), GetVex(G, j));
                continue;
            }

            printf("%c 到 %c 之间的最短距离为%2d,其最短路径为:", GetVex(G, i), GetVex(G, j), D[i][j]);

            /*
             * 将i->j之间的无序顶点排列成一条有序路径后打印
             * P[i][j]指示i->j这条路径上经过的顶点
             */
            PrintPath(G, i, j, P[i][j]);
        }
    }
}

/*
 * 打印从a到b的路径,该路径的顶点信息位于P中
 */
static void PrintPath(MGraph G, int a, int b, Boolean P[MAX_VERTEX_NUM]) {
    int i;
    int vexs[MAX_VERTEX_NUM + 1];
    int path[MAX_VERTEX_NUM + 1];

    vexs[0] = 0;

    // 获取v0->i这条途径上的所有顶点,可能是无序的
    for(i = 0; i < G.vexnum; i++) {
        if(P[i] == TRUE) {
            vexs[0]++;
            vexs[vexs[0]] = i;
        }
    }

    // 只有两个顶点的情形下快速处理
    if(vexs[0] == 2) {
        path[0] = 2;
        path[1] = a;
        path[2] = b;
        goto print;
    }

    // 只有三个顶点的情形下快速处理
    if(vexs[0] == 3) {
        path[0] = 3;
        path[1] = a;
        for(i = 1; i <= vexs[0]; i++) {
            if(vexs[i] != a && vexs[i] != b) {
                path[2] = vexs[i];
                break;
            }
        }
        path[3] = b;
        goto print;
    }

    path[0] = 1;
    path[1] = a;

    for(i = 1; i <= vexs[0]; i++) {
        if(vexs[i] == a) {
            vexs[i] = -1;
            break;
        }
    }

    Find(G, a, b, vexs, path);

print:
    // 打印排列好的路径
    for(i = 1; i <= path[0]; i++) {
        printf("%c ", GetVex(G, path[i]));
    }

    printf("\n");
}

/*
 * 利用广度预先搜索找到一条从pre到b的路径,该路径上路过的所有顶点位于vexs中,找出的路径存储到path中
 */
static Status Find(MGraph G, int pre, int b, int vexs[MAX_VERTEX_NUM + 1], int path[MAX_VERTEX_NUM + 1]) {
    int i;
    int tmp;
    Status s;

    for(i = 1; i <= vexs[0]; i++) {
        // 已加入到路径
        if(vexs[i] == -1) {
            continue;
        }

        // 不连通
        if(((G.kind == DN || G.kind == UDN) && G.arcs[pre][vexs[i]].adj == INFINITE)
        || ((G.kind == DG || G.kind == UDG) && G.arcs[pre][vexs[i]].adj == 0)) {
            continue;
        }

        path[0]++;
        path[path[0]] = vexs[i];

        // 遇到终点
        if(vexs[i] == b) {
            // 找到了最短路径
            if(path[0] == vexs[0]) {
                return OK;
            } else {
                path[0]--;
                continue;
            }
        }

        tmp = vexs[i];
        vexs[i] = -1;

        s = Find(G, tmp, b, vexs, path);

        if(s == OK) {
            return OK;
        } else {
            // 恢复该顶点,以便后续重新搜索
            vexs[i] = tmp;
            path[0]--;
        }
    }

    return ERROR;
}

第九章 查找

折半查找、顺序查找

/* 折半查找 */
int Binary_Search(int *a,int n,int key)
{
    int low,high,mid;
    low=1;    /* 定义最低下标为记录首位 */
    high=n;    /* 定义最高下标为记录末位 */
    while(low<=high)
    {
        mid=(low+high)/2;    /* 折半 */
        if (key<a[mid])        /* 若查找值比中值小 */
            high=mid-1;        /* 最高下标调整到中位下标小一位 */
        else if (key>a[mid])/* 若查找值比中值大 */
            low=mid+1;        /* 最低下标调整到中位下标大一位 */
        else
        {
            return mid;        /* 若相等则说明mid即为查找到的位置 */
        }

    }
    return 0;
}

/* 无哨兵顺序查找,a为数组,n为要查找的数组个数,key为要查找的关键字 */
int Sequential_Search(int *a,int n,int key)
{
    int i;
    for(i=1;i<=n;i++)
    {
        if (a[i]==key)
            return i;
    }
    return 0;
}
/* 有哨兵顺序查找 */
int Sequential_Search2(int *a,int n,int key)
{
    int i;
    a[0]=key;
    i=n;
    while(a[i]!=key)
    {
        i--;
    }
    return i;
}

哈希表(线性探测法解决冲突)

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

#define MAXSIZE 100 /* 存储空间初始分配量 */

#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12 /* 定义散列表长为数组的长度 */
#define NULLKEY -32768 

typedef int Status;    /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ 

typedef struct
{
   int *elem; /* 数据元素存储基址,动态分配数组 */
   int count; /*  当前数据元素个数 */
}HashTable;

int m=0; /* 散列表表长,全局变量 */

/* 初始化散列表 */
Status InitHashTable(HashTable *H)
{
    int i;
    m=HASHSIZE;
    H->count=m;
    H->elem=(int *)malloc(m*sizeof(int));
    for(i=0;i<m;i++)
        H->elem[i]=NULLKEY; 
    return OK;
}

/* 散列函数 */
int Hash(int key)
{
    return key % m; /* 除留余数法 */
}

/* 插入关键字进散列表 */
void InsertHash(HashTable *H,int key)
{
    int addr = Hash(key); /* 求散列地址 */
    while (H->elem[addr] != NULLKEY) /* 如果不为空,则冲突 */
    {
        addr = (addr+1) % m; /* 开放定址法的线性探测 */
    }
    H->elem[addr] = key; /* 直到有空位后插入关键字 */
}

/* 散列表查找关键字 */
Status SearchHash(HashTable H,int key,int *addr)
{
    *addr = Hash(key);  /* 求散列地址 */
    while(H.elem[*addr] != key) /* 如果不为空,则冲突 */
    {
        *addr = (*addr+1) % m; /* 开放定址法的线性探测 */
        if (H.elem[*addr] == NULLKEY || *addr == Hash(key)) /* 如果循环回到原点 */
            return UNSUCCESS;    /* 则说明关键字不存在 */
    }
    return SUCCESS;
}
  • 二叉排序树(平均查找长度)__* ```c / 二叉树的二叉链表结点结构定义 / typedef struct BiTNode / 结点结构 / { int data; / 结点数据 / struct BiTNode lchild, rchild; / 左右孩子指针 / } BiTNode, *BiTree;

/ 递归查找二叉排序树T中是否存在key, / / 指针f指向T的双亲,其初始调用值为NULL / / 若查找成功,则指针p指向该数据元素结点,并返回TRUE / / 否则指针p指向查找路径上访问的最后一个结点并返回FALSE / Status SearchBST(BiTree T, int key, BiTree f, BiTree p) {
if (!T) /
查找不成功 / { p = f;
return FALSE; } else if (key==T->data) / 查找成功 / { p = T;
return TRUE; } else if (keydata) return SearchBST(T->lchild, key, T, p); /
在左子树中继续查找 / else
return SearchBST(T->rchild, key, T, p); /
在右子树中继续查找 */ }

/ 当二叉排序树T中不存在关键字等于key的数据元素时, / / 插入key并返回TRUE,否则返回FALSE / Status InsertBST(BiTree T, int key) {
BiTree p,s; if (!SearchBST(
T, key, NULL, &p)) / 查找不成功 / { s = (BiTree)malloc(sizeof(BiTNode)); s->data = key;
s->lchild = s->rchild = NULL;
if (!p) T = s; / 插入s为新的根结点 / else if (keydata) p->lchild = s; / 插入s为左孩子 / else p->rchild = s; / 插入s为右孩子 / return TRUE; } else return FALSE; / 树中已有关键字相同的结点,不再插入 */ }

/ 从二叉排序树中删除结点p,并重接它的左或右子树。 / Status Delete(BiTree p) { BiTree q,s; if((p)->rchild==NULL) / 右子树空则只需重接它的左子树(待删结点是叶子也走此分支) / { q=p; p=(p)->lchild; free(q); } else if((p)->lchild==NULL) / 只需重接它的右子树 / { q=p; p=(p)->rchild; free(q); } else / 左右子树均不空 / { q=p; s=(p)->lchild; while(s->rchild) / 转左,然后向右到尽头(找待删结点的前驱) / { q=s; s=s->rchild; } (p)->data=s->data; / s指向被删结点的直接前驱(将被删结点前驱的值取代被删结点的值) / if(q!=p) q->rchild=s->lchild; / 重接q的右子树 / else q->lchild=s->lchild; / 重接q的左子树 */ free(s); } return TRUE; }

/ 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点, / / 并返回TRUE;否则返回FALSE。 / Status DeleteBST(BiTree T,int key) { if(!T) / 不存在关键字等于key的数据元素 / return FALSE; else { if (key==(T)->data) / 找到关键字等于key的数据元素 / return Delete(T); else if (key<(T)->data) return DeleteBST(&(T)->lchild,key); else return DeleteBST(&(T)->rchild,key);

}

}



**_AVL树(平衡因子)_**
```c
/* 二叉树的二叉链表结点结构定义 */
typedef  struct BiTNode    /* 结点结构 */
{
    int data;    /* 结点数据 */
    int bf; /*  结点的平衡因子 */ 
    struct BiTNode *lchild, *rchild;    /* 左右孩子指针 */
} BiTNode, *BiTree;


/* 对以p为根的二叉排序树作右旋处理, */
/* 处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点 */
void R_Rotate(BiTree *P)
{ 
    BiTree L;
    L=(*P)->lchild; /*  L指向P的左子树根结点 */ 
    (*P)->lchild=L->rchild; /*  L的右子树挂接为P的左子树 */ 
    L->rchild=(*P);
    *P=L; /*  P指向新的根结点 */ 
}

/* 对以P为根的二叉排序树作左旋处理, */
/* 处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0  */
void L_Rotate(BiTree *P)
{ 
    BiTree R;
    R=(*P)->rchild; /*  R指向P的右子树根结点 */ 
    (*P)->rchild=R->lchild; /* R的左子树挂接为P的右子树 */ 
    R->lchild=(*P);
    *P=R; /*  P指向新的根结点 */ 
}

#define LH +1 /*  左高 */ 
#define EH 0  /*  等高 */ 
#define RH -1 /*  右高 */ 

/*  对以指针T所指结点为根的二叉树作左平衡旋转处理 */
/*  本算法结束时,指针T指向新的根结点 */
void LeftBalance(BiTree *T)
{ 
    BiTree L,Lr;
    L=(*T)->lchild; /*  L指向T的左子树根结点 */ 
    switch(L->bf)
    { /*  检查T的左子树的平衡度,并作相应平衡处理 */ 
         case LH: /*  新结点插入在T的左孩子的左子树上,要作单右旋处理 */ 
            (*T)->bf=L->bf=EH;
            R_Rotate(T);
            break;
         case RH: /*  新结点插入在T的左孩子的右子树上,要作双旋处理 */ 
            Lr=L->rchild; /*  Lr指向T的左孩子的右子树根 */ 
            switch(Lr->bf)
            { /*  修改T及其左孩子的平衡因子 */ 
                case LH: (*T)->bf=RH;
                         L->bf=EH;
                         break;
                case EH: (*T)->bf=L->bf=EH;
                         break;
                case RH: (*T)->bf=EH;
                         L->bf=LH;
                         break;
            }
            Lr->bf=EH;
            L_Rotate(&(*T)->lchild); /*  对T的左子树作左旋平衡处理 */ 
            R_Rotate(T); /*  对T作右旋平衡处理 */ 
    }
}

/*  对以指针T所指结点为根的二叉树作右平衡旋转处理, */ 
/*  本算法结束时,指针T指向新的根结点 */ 
void RightBalance(BiTree *T)
{ 
    BiTree R,Rl;
    R=(*T)->rchild; /*  R指向T的右子树根结点 */ 
    switch(R->bf)
    { /*  检查T的右子树的平衡度,并作相应平衡处理 */ 
     case RH: /*  新结点插入在T的右孩子的右子树上,要作单左旋处理 */ 
              (*T)->bf=R->bf=EH;
              L_Rotate(T);
              break;
     case LH: /*  新结点插入在T的右孩子的左子树上,要作双旋处理 */ 
              Rl=R->lchild;             /*  Rl指向T的右孩子的左子树根 */ 
              switch(Rl->bf)
              {                         /*  修改T及其右孩子的平衡因子 */ 
                case RH: (*T)->bf=LH;
                         R->bf=EH;
                         break;
                case EH: (*T)->bf=R->bf=EH;
                         break;
                case LH: (*T)->bf=EH;
                         R->bf=RH;
                         break;
              }
              Rl->bf=EH;
              R_Rotate(&(*T)->rchild); /*  对T的右子树作右旋平衡处理 */ 
              L_Rotate(T); /*  对T作左旋平衡处理 */ 
    }
}

/*  若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 */ 
/*  数据元素为e的新结点,并返回1,否则返回0。若因插入而使二叉排序树 */ 
/*  失去平衡,则作平衡旋转处理,布尔变量taller反映T长高与否。 */
Status InsertAVL(BiTree *T,int e,Status *taller)
{  
    if(!*T)
    { /*  插入新结点,树“长高”,置taller为TRUE */ 
         *T=(BiTree)malloc(sizeof(BiTNode));
         (*T)->data=e; (*T)->lchild=(*T)->rchild=NULL; (*T)->bf=EH;
         *taller=TRUE;
    }
    else
    {
        if (e==(*T)->data)
        { /*  树中已存在和e有相同关键字的结点则不再插入 */ 
            *taller=FALSE; return FALSE;
        }
        if (e<(*T)->data)
        { /*  应继续在T的左子树中进行搜索 */ 
            if(!InsertAVL(&(*T)->lchild,e,taller)) /*  未插入 */ 
                return FALSE;
            if(*taller) /*   已插入到T的左子树中且左子树“长高” */ 
                switch((*T)->bf) /*  检查T的平衡度 */ 
                {
                    case LH: /*  原本左子树比右子树高,需要作左平衡处理 */ 
                            LeftBalance(T);    *taller=FALSE; break;
                    case EH: /*  原本左、右子树等高,现因左子树增高而使树增高 */ 
                            (*T)->bf=LH; *taller=TRUE; break;
                    case RH: /*  原本右子树比左子树高,现左、右子树等高 */  
                            (*T)->bf=EH; *taller=FALSE; break;
                }
        }
        else
        { /*  应继续在T的右子树中进行搜索 */ 
            if(!InsertAVL(&(*T)->rchild,e,taller)) /*  未插入 */ 
                return FALSE;
            if(*taller) /*  已插入到T的右子树且右子树“长高” */ 
                switch((*T)->bf) /*  检查T的平衡度 */ 
                {
                    case LH: /*  原本左子树比右子树高,现左、右子树等高 */ 
                            (*T)->bf=EH; *taller=FALSE;    break;
                    case EH: /*  原本左、右子树等高,现因右子树增高而使树增高  */
                            (*T)->bf=RH; *taller=TRUE; break;
                    case RH: /*  原本右子树比左子树高,需要作右平衡处理 */ 
                            RightBalance(T); *taller=FALSE; break;
                }
        }
    }
    return TRUE;
}

第十章 排序

  • 冒泡排序 __* ```c / 对顺序表L作交换排序(冒泡排序初级版) / void BubbleSort0(SqList *L) { int i,j; for(i=1;ilength;i++) {
      for(j=i+1;j<=L->length;j++)
      {
          if(L->r[i]>L->r[j])
          {
               swap(L,i,j);/* 交换L->r[i]与L->r[j]的值 */
          }
      }
    
    } }

/ 对顺序表L作冒泡排序 / void BubbleSort(SqList L) { int i,j; for(i=1;ilength;i++) { for(j=L->length-1;j>=i;j—) / 注意j是从后往前循环 / { if(L->r[j]>L->r[j+1]) / 若前者大于后者(注意这里与上一算法的差异)/ { swap(L,j,j+1);/ 交换L->r[j]与L->r[j+1]的值 */ } } } }

/ 对顺序表L作改进冒泡算法 / void BubbleSort2(SqList L) { int i,j; Status flag=TRUE; / flag用来作为标记 / for(i=1;ilength && flag;i++) / 若flag为true说明有过数据交换,否则停止循环 / { flag=FALSE; / 初始为False / for(j=L->length-1;j>=i;j—) { if(L->r[j]>L->r[j+1]) { swap(L,j,j+1); / 交换L->r[j]与L->r[j+1]的值 / flag=TRUE; / 如果有数据交换,则flag为true */ } } } }


- [x] **_快速排序 __*_**
```c
/* 快速排序******************************** */

/* 交换顺序表L中子表的记录,使枢轴记录到位,并返回其所在位置 */
/* 此时在它之前(后)的记录均不大(小)于它。 */
int Partition(SqList *L,int low,int high)
{ 
    int pivotkey;

    pivotkey=L->r[low]; /* 用子表的第一个记录作枢轴记录 */
    while(low<high) /*  从表的两端交替地向中间扫描 */
    { 
         while(low<high&&L->r[high]>=pivotkey)
            high--;
         swap(L,low,high);/* 将比枢轴记录小的记录交换到低端 */
         while(low<high&&L->r[low]<=pivotkey)
            low++;
         swap(L,low,high);/* 将比枢轴记录大的记录交换到高端 */
    }
    return low; /* 返回枢轴所在位置 */
}

/* 对顺序表L中的子序列L->r[low..high]作快速排序 */
void QSort(SqList *L,int low,int high)
{ 
    int pivot;
    if(low<high)
    {
            pivot=Partition(L,low,high); /*  将L->r[low..high]一分为二,算出枢轴值pivot */
            QSort(L,low,pivot-1);        /*  对低子表递归排序 */
            QSort(L,pivot+1,high);        /*  对高子表递归排序 */
    }
}


/* 改进后快速排序******************************** */

/* 快速排序优化算法 */
int Partition1(SqList *L,int low,int high)
{ 
    int pivotkey;

    int m = low + (high - low) / 2; /* 计算数组中间的元素的下标 */  
    if (L->r[low]>L->r[high])            
        swap(L,low,high);    /* 交换左端与右端数据,保证左端较小 */
    if (L->r[m]>L->r[high])
        swap(L,high,m);        /* 交换中间与右端数据,保证中间较小 */
    if (L->r[m]>L->r[low])
        swap(L,m,low);        /* 交换中间与左端数据,保证左端较小 */

    pivotkey=L->r[low]; /* 用子表的第一个记录作枢轴记录 */
    L->r[0]=pivotkey;  /* 将枢轴关键字备份到L->r[0] */
    while(low<high) /*  从表的两端交替地向中间扫描 */
    { 
         while(low<high&&L->r[high]>=pivotkey)
            high--;
         L->r[low]=L->r[high];
         while(low<high&&L->r[low]<=pivotkey)
            low++;
         L->r[high]=L->r[low];
    }
    L->r[low]=L->r[0];
    return low; /* 返回枢轴所在位置 */
}

void QSort1(SqList *L,int low,int high)
{ 
    int pivot;
    if((high-low)>MAX_LENGTH_INSERT_SORT)
    {
        pivot=Partition1(L,low,high); /*  将L->r[low..high]一分为二,算出枢轴值pivot */
        QSort1(L,low,pivot-1);        /*  对低子表递归排序 */
        QSort1(L,pivot+1,high);        /*  对高子表递归排序 */
    }
    else
        InsertSort(L);
}

/* 对顺序表L作快速排序 */
void QuickSort1(SqList *L)
{ 
    QSort1(L,1,L->length);
}

/* 尾递归 */
void QSort2(SqList *L,int low,int high)
{ 
    int pivot;
    if((high-low)>MAX_LENGTH_INSERT_SORT)
    {
        while(low<high)
        {
            pivot=Partition1(L,low,high); /*  将L->r[low..high]一分为二,算出枢轴值pivot */
            QSort2(L,low,pivot-1);        /*  对低子表递归排序 */
            low=pivot+1;    /* 尾递归 */
        }
    }
    else
        InsertSort(L);
}

/* 对顺序表L作快速排序(尾递归) */
void QuickSort2(SqList *L)
{ 
    QSort2(L,1,L->length);
}

/* **************************************** */

直接插入排序

/* 对顺序表L作直接插入排序 */
void InsertSort(SqList *L)
{ 
    int i,j;
    for(i=2;i<=L->length;i++)
    {
        if (L->r[i]<L->r[i-1]) /* 需将L->r[i]插入有序子表 */
        {
            L->r[0]=L->r[i]; /* 设置哨兵 */
            for(j=i-1;L->r[j]>L->r[0];j--)
                L->r[j+1]=L->r[j]; /* 记录后移 */
            L->r[j+1]=L->r[0]; /* 插入到正确位置 */
        }
    }
}

简单选择排序

/* 对顺序表L作简单选择排序 */
void SelectSort(SqList *L)
{
    int i,j,min;
    for(i=1;i<L->length;i++)
    { 
        min = i;                        /* 将当前下标定义为最小值下标 */
        for (j = i+1;j<=L->length;j++)/* 循环之后的数据 */
        {
            if (L->r[min]>L->r[j])    /* 如果有小于当前最小值的关键字 */
                min = j;                /* 将此关键字的下标赋值给min */
        }
        if(i!=min)                        /* 若min不等于i,说明找到最小值,交换 */
            swap(L,i,min);                /* 交换L->r[i]与L->r[min]的值 */
    }
}
  • 排序方法的稳定性 __*