• 《数据结构》 浙大(C语言)

  • 课程练习网站
  • 拼题A(Programming Teaching Assistant):https://pintia.cn/ 本课程的编程练习将在这里布置。
  • PAT(Programming Ability Test)官网:https://www.patest.cn/ 提供全部考试真题。
  • 学习方法:
  • 1、视频(B站),B站课程链接
  • 2、视频中习题,讨论,PPT(中国大学MOOC) 网课地址
  • 3、 笔记整理
  • 4、完成编程作业

######################################################

  • 数据结构(data structure): 是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法。
  • 数据结构:数据对象在计算机中的组织方式。(逻辑结构,物理存储结构)

1.1 数据结构

解决问题方法的效率,跟数据的组织方式有关

例1 关于空间使用

#include<stdio.h>// 传入一个正整数N
// 顺序打印1到N的全部整数void PrintN1(int N);
void PrintN2(int N);
// N = 100,1000,10000,1000000,......
int main() {int N;scanf("%d", &N);//PrintN1(N);PrintN2(N);return 0;
}// 方法一:循环实现
void PrintN1(int N) {int i;for (i = 1; i <= N; i++) {printf("%d\n", i);}return;
}//方法二:递归实现,占用空间大
void  PrintN2(int N) {if (N) {PrintN2(N - 1);printf("%d\n", N);}return;
}

解决问题方法的效率,跟空间的利用效率有关

  • 在VS里scanf报错解决方法:https://blog.csdn.net/bjxqmy/article/details/98251253

-------------------------------------------------------

例3

#include<stdio.h>
#include<math.h>// 例3: 计算给定多项式在给定点x处的值。
// f(x) = a0 + a1*x + a2*x^2+...+an*x^nint main() {//return 0;
}// 直观解法
double f1(int n, double a[], double x) {int i;double p = a[0];for (i = 1; i <= n; i++)p += (a[i] * pow(x, i));return p;
}// 提升解法 f(x)= a0 + x(a1 + x(...a(n-1)+x*an))  将x提取
double f2(int n, double a[], double x) {int i;double p = a[n];for (i = n; i > 0; i--)p = a[i - 1] + x * p;return p;
}
/*计算程序运行时长  模块
*/#include<stdio.h>
#include<time.h>clock_t start, stop;   //clock_t是clock()函数返回的类型变量
double duration;    // 记录被测函数运行时间,以秒为单位int main() {/*无需计时部分 */start = clock();  //开始计时MyFunction();stop = clock(); //结束计算/*无需计时部分 */duration = ((double)(stop - start)) / CLK_TCK;return 0;
}
/*计算程序运行时长  实例
*/#include<stdio.h>
#include<time.h>
#include<math.h>clock_t start, stop;   //clock_t是clock()函数返回的类型变量
double duration;    // 记录被测函数运行时间,以秒为单位#define MAXN 10    //多项式最大项数,即多项式阶数+1   #define MAXK 1e7   // 被测函数最大重复调用次数double f1(int n, double a[], double x);
double f2(int n, double a[], double x);int main() {int i;double a[MAXN];  //存储多项式的系数for (i = 0; i < MAXN; i++) a[i] = (double)i;start = clock();  //开始计时for (i = 0; i < MAXK; i++)    //重复调用函数以获得充分多的时钟打点数f1(MAXN - 1, a, 1.1);stop = clock(); //结束计算duration = ((double)(stop - start)) / CLK_TCK/MAXK;printf("ticks1 = %f\n", (double)(stop - start));printf("duration1 = %6.2e\n", duration);start = clock();  //开始计时for (i = 0; i < MAXK; i++)f2(MAXN - 1, a, 1.1);stop = clock(); //结束计算duration = ((double)(stop - start)) / CLK_TCK/MAXK;printf("ticks2 = %f\n", (double)(stop - start));printf("duration2 = %6.2e\n", duration);return 0;
}double f1(int n, double a[], double x) {int i;double p = a[0];for (int i = 1; i <= n; i++)p += (a[i] * pow(x, i));return p;
}double f2(int n, double a[], double x) {int i;double p = a[n];for (i = n; i > 0; i--)p = a[i - 1] + x * p;return p;
}

解决问题方法的效率,跟算法的巧妙程度有关。

讨论1.3

/*计算程序运行时长  实例
*/#include<stdio.h>
#include<time.h>
#include<math.h>clock_t start, stop;   //clock_t是clock()函数返回的类型变量
double duration;    // 记录被测函数运行时间,以秒为单位#define MAXN 101   //多项式最大项数,即多项式阶数+1   #define MAXK 1e7   // 被测函数最大重复调用次数double f1(int n, double a[], double x);
double f2(int n, double a[], double x);int main() {int i;double a[MAXN];  //存储多项式的系数a[0] = 1;for (i = 1; i < MAXN; i++) a[i] = (double)i;start = clock();  //开始计时for (i = 0; i < MAXK; i++)    //重复调用函数以获得充分多的时钟打点数f1(MAXN - 1, a, 1.1);stop = clock(); //结束计算duration = ((double)(stop - start)) / CLK_TCK/MAXK;printf("ticks1 = %f\n", (double)(stop - start));printf("duration1 = %6.2e\n", duration);start = clock();  //开始计时for (i = 0; i < MAXK; i++)f2(MAXN - 1, a, 1.1);stop = clock(); //结束计算duration = ((double)(stop - start)) / CLK_TCK/MAXK;printf("ticks2 = %f\n", (double)(stop - start));printf("duration2 = %6.2e\n", duration);return 0;
}//  f(x) = 1 + x + x^2/2 + ... + x^i/i + ... + x^100/100
double f1(int n, double a[], double x) {int i;double p = a[0];for (int i = 1; i <= n; i++)p +=  pow(x, i)/a[i] ;return p;
}double f2(int n, double a[], double x) {int i;double p = 1/a[n];for (i = n; i > 0; i--)p = 1/a[i - 1] + x * p;return p;
}

1.2 算法

  • 算法评价指标
  • 1、空间复杂度S(n) : 占用存储单元的长度
  • 2、时间复杂度T(n): 耗费时间长度
  • 都与输入数据规模有关

例3中:

double f1(int n, double a[], double x) {int i;double p = a[0];for (int i = 1; i <= n; i++)p += (a[i] * pow(x, i));return p;
}
  • 乘法次数为 (1 + 2+…+n) = (n^2 + n)/2
  • T(n) = C1×n^2 + C2×n
double f2(int n, double a[], double x) {int i;double p = a[n];for (i = n; i > 0; i--)p = a[i - 1] + x * p;return p;
}
  • T(n) = C·n
  • 最坏情况复杂度 T worst(n), 更关心这个指标
  • 平均复杂度 T avg(n)

复杂度的渐进表示法


不管以2还是10为底,都是相差常数倍。


复杂度分析小窍门

:最大连续子列和(4种方法)

算法1:

#include<stdio.h>int MaxSubseqSum1(int A[], int N)
{int ThisSum, MaxSum = 0;int i, j, k;for (i = 0; i < N; i++) { //i为子列左端位置for (j = i; j < N; j++) {//j为子列右端位置ThisSum = 0;  // A[i]到A[j]的子列和for (k = i; k <= j; k++)ThisSum += A[k];if (ThisSum > MaxSum)MaxSum = ThisSum;}}return MaxSum;
}int main() {int A[] = { 1,-2,3, 5, 6,100,-5 };int a = MaxSubseqSum1(A, 7);printf("%d", a);return 0;
}
  • T(N) = O(N^3)

算法2:

#include<stdio.h>int MaxSubseqSum2(int A[], int N)
{int ThisSum, MaxSum = 0;int i, j;for (i = 0; i < N; i++) { //i为子列左端位置ThisSum = 0;  // A[i]到A[j]的子列和for (j = i; j < N; j++) {//j为子列右端位置       ThisSum += A[j];  //对于相同的i,不同的j,只要在j-1次循环的基础上累加一项if (ThisSum > MaxSum)MaxSum = ThisSum;}}return MaxSum;
}int main() {int A[] = { 1,-2,3, 5, 6,100,-5 };int a = MaxSubseqSum2(A, 7);printf("%d", a);return 0;
}
  • T(N) = O(N^2)

算法3:分而治之

#include<stdio.h>int Max3(int A, int B, int C){//返回三个整数中的最大值return A > B ? A > C ? A : C : B > C ? B : C;
}int DivideAndConquer(int List[], int left, int right) {//分治法求List[left]到List[right]的最大子列和int MaxLeftSum, MaxRightSum;int MaxLeftBorderSum, MaxRightBorderSum; //存放跨分界线的结果int LeftBorderSum, RightBorderSum;int center, i;if (left == right) {//递归终止条件,子列只有一个数字if (List[left] > 0)  return List[left];else return 0;}//分center = (left + right) / 2;  //递归求两边子列的最大和MaxLeftSum = DivideAndConquer(List, left, center);MaxRightSum = DivideAndConquer(List, center + 1, right);//求跨分界线的最大子列和MaxLeftBorderSum = 0; LeftBorderSum = 0;for (i = center; i >= left; i--) {//从中线向左扫描LeftBorderSum += List[i];if (LeftBorderSum > MaxLeftBorderSum)MaxLeftBorderSum = LeftBorderSum;}MaxRightBorderSum = 0; RightBorderSum = 0;for (i = center + 1; i <= right; i++) { //从中线向右扫描RightBorderSum += List[i];if (RightBorderSum > MaxRightBorderSum)MaxRightBorderSum = RightBorderSum;}//返回治的结果return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum);
}int MaxSubseqSum3(int List[], int N)
{return DivideAndConquer(List,0,N-1);
}int main() {int A[] = { 1,-2,3, 5, 6,100,-5 };int a = MaxSubseqSum3(A, 7);printf("%d", a);return 0;
}

算法4: 在线处理

#include<stdio.h>int MaxSubseqSum4(int A[], int N)
{int i;int ThisSum =0, MaxSum = 0;for (i = 0; i < N; i++) { //i为子列左端位置ThisSum += A[i]; // 向右累加if (ThisSum > MaxSum)MaxSum = ThisSum;else if (ThisSum < 0)    //如果当前子列和为负ThisSum = 0;    //不可能使后面的部分和增大,抛弃}return MaxSum;
}int main() {int A[] = { 1,-2,3, 5, 6,100,-5 };int a = MaxSubseqSum4(A, 7);printf("%d", a);return 0;
}
  • T(N) = O(N)

2 线性结构

  • 结构数组 (系数,指数)

多项式问题

  • 数组实现:直接表示 或 只表示非零项
  • 链表实现 (系数,指数,指针域)

2.1 顺序存储

//链表结构存储非零项typedef struct PolyNode* Polynomial;
struct PolyNode {int coef;int expon;Polynomial ink;
};

线性表: 由同类型数据元素构成有序序列的线性结构。

6种基本操作:初始化,查找元素,返回位置,插入,删除,返回长度。

typedef struct LNode* List;
struct LNode {ElementType Data[MAXSIZE];int Last;
};
struct LNode L;
List PtrL;
  • 访问下标为i的元素: L.Data[i] 或 PtrL -> Data[i]
  • 线性表的长度: L.Last+1 或 PtrL -> Last + 1
/* 初始化*/
List MakeEmpty() {List PtrL;    PtrL = (List)malloc(sizeof(struct LNode));PtrL->Last = -1;return PtrL;
}
/*查找*/
int Find(List PtrL, ElementType X) {int i = 0;while (i <= PtrL->Last && PtrL->Data[i] != X)i++;if (i > PtrL->Last)  return -1;  //没找到else  return i;   //返回存储位置
}
/* 3、插入*/
void Inert(ElementType X, int i, List PtrL) {int j;if (PtrL->Last == MAXSIZE - 1) {printf(" 表满 ");return;}if (i < 1 || i>PtrL->Last + 2) {printf(" 位置不合法 ");return;}for (j = PtrL->Last; j >= i - 1; j--)PtrL->Data[j + 1] = PtrL->Data[j]; // 将ai~an倒序向后移动PtrL->Data[i - 1] = X;    //新元素插入PtrL->Last++;    // Last仍指向最后元素return;
}
  • 平均移动次数为n/2
  • 平均时间性能为 O(n)
// 删除void Delete(int i, List PtrL) {//下标从1开始int j;if (i<1 || i > PtrL->Last+1) {printf("位置%d不存在元素", i);return;}for (j = i; j <= PtrL->Last; j++)PtrL->Data[j - 1] = PtrL->Data[j];    //后面的元素依次前移PtrL->Last--;   /*Last仍指向最后元素*/return;
}
  • 平均移动次数为(n-1)/2
  • 平均时间性能为O(n)

-------------------------------------------
--------------------------------------------------------------------------

附录代码1:

typedef int Position;
typedef struct LNode* List;
struct LNode {ElementType Data[MAXSIZE];Position Last;
};/* 初始化*/
List MakeEmpty() {List L;    // PPT里L==> PtrLL = (List)malloc(sizeof(struct LNode));L->Last = -1;return L;
}/*查找*/#define ERROR -1Position Find(List L, ElementType X) {Position i = 0;while (i <= L->Last && L->Data[i] != X)i++;if (i > L->Last)  return ERROR;  //没找到else  return i;   //返回存储位置
}/* 插入 */bool Insert(List L, ElementType X, Position P) {//P从零开始Position i;if (L->Last == MAXSIZE - 1) {//表空间已满,不能插入printf("表满");return false;}if (P<0 || P > L->Last + 1) {printf("位置不合法");return false;}for (i = L->Last; i >= P; i--)L->Data[i + 1] = L->Data[i]; //将位置P及以后的元素向后移L->Data[P] = X;   //新元素插入L->Last++;     /* Last仍指向最后元素 */return true;
}// 删除bool Delete(List L, Position P) {//P下标从0开始Position i;if (P<0 || P > L->Last) {printf("位置%d不存在元素", P);return false;}for (i = P + 1; i <= L->Last; i++)L->Data[i - 1] = L->Data[i]; // 将后面的元素往前移动。L->Last--;   /*Last仍指向最后元素*/return true;}

2.2 链式存储

typedef struct LNode* List;
struct LNode {ElementType Data;List Next;
};
struct LNode L;
List PtrL;

1、求表长

/*求表长*/
int Length(List PtrL) {List p = PtrL; //p指向表的第一个结点int j = 0;while (p) {p = p->Next;j++;}return j;
}
  • 时间性能为O(n)
    2、查找
    (1)按序号查找
/*按序号查找*/
List FindKth(int K, List PtrL) {List p = PtrL;int i = 1;while (p != NULL && i < K) {p = p->Next;i++;}if (i == K) return p;else return NULL;
}

(2) 按值查找

/*按值查找*/
List Find(ElementTYpe X, List PtrL) {List p = PtrL;while (p != NULL && p->Data != X)p = p->Next;return p;
}

3、插入
(1)构造一个新结点,用s指向
(2)找到链表的第i-1个结点,用p指向
(3)修改指针,插入结点

/*插入操作*/List Insert(ElementType X, int i, List PtrL) {List p, s;if (i == 1) { /* 新节点插入在表头*/s = (List)malloc(sizeof(struct LNode));   //申请、填装结点s->Data = X;s->Next = PtrL;return s;}p = FindKth(i - 1, PtrL);    //查找第i-1个结点if (p == NULL) {printf(" 参数i错 ");return NULL;}else {s = (List)malloc(sizeof(struct LNode));s->Data = X;s->Next = p->Next;p->Next = s;return PtrL;}}

4、删除
(1)先找到链表的第i-1个结点,用p指向
(2)再用指针s指向要被删除的结点(p的下一个结点)
(3)然后修改指针,删除s所指结点
(4)最后释放s所指结点的空间

/*删除操作*/List Delete(int i, List PtrL) {List p, s;if (i == 1) { /* 要删除的是第一个结点*/s = PtrL;       /* s 指向第1个结点 */if (PtrL != NULL)  PtrL = PtrL->Next;   /* 从链表中删除 */else return NULL;free(s);                           // 释放被删除的结点return PtrL;}p = FindKth(i - 1, PtrL);    //查找第i-1个结点if (p == NULL) {    //前一个结点不存在,显然要删的也不存在printf("第%d个结点不存在", i - 1); return NULL;}else  if (p->Next == NULL) {// 要删的结点不存在printf("第%d个结点不存在", i);  return NULL;}else {s = p->Next;                     /* s指向第i个结点*/p->Next = s->Next;        /*从链表中删除*/free(s);                           /*释放被删除结点*/return PtrL;}
}

附件代码:

typedef struct LNode* PtrToLNode;struct LNode {ElementType Data;PtrToNode Next;
};typedef PtrToNode Position;
typedef PtrToNode List;/* 查找 */
#define EROOR NULLPosition Find(List L, ElementType X) {Position p = L;  //p指向L的第一个结点while (p && p->Data != X)p = p->Next;//return p;if (p)  return p;else return ERROR;
}bool Insert(List L, ElementType X, Position P) {//默认L有头结点Position tmp, pre;//查找P的前一个结点for (pre = L; pre && pre->Next != P; pre = pre->Next);if (pre == NULL) {//P所指的结点不在L中printf("插入位置参数错误\n");return false;}else {/* 找到P的前一个结点pre ,在P前插入新结点*/tmp = (Position)malloc(sizeof(struct LNode));tmp->Data = X;tmp->Next = P;pre->Next = tmp;return true;}
}/* 带头结点的删除 */
bool Delete(List L, Position P) {Position pre;//查找P的前一个结点for (pre = L; pre && pre->Next != P; pre = pre->Next);if (pre == NULL || P == NULL) {printf("删除位置参数错误\n");return false;}else {/* 找到了P的前一个结点pre,将P位置的结点删除*/pre->Next = p->Next;free(P);return true;}
}

----------------------------------------------------
----------------------------------------------------

广义表

  • 线性表:n个元素都是基本的单元素
  • 广义表:元素不仅可以是单元素也可以是另一个广义表。
/*广义表*/typedef struct GNode* GList;
struct GNode {int Tag;  /* 标志域: 0表示结点是单元素, 1表示结点是广义表*/union {     // 共用存储空间ElementType Data;GList SubList;}URegion;GList Next;
};

多重链表: 链表中的结点可能同时隶属于多个链

  • 包含两个指针域的链表不一定是多重链表,比如双向链表不是多重链表。
    -------------------------------------------、、、、、、、
  • 二维数组表示 稀疏矩阵(0很多),将造成大量的存储空间浪费。
  • 十字链表
  • 只存储矩阵非0元素项:
    结点的数据域:行坐标Row, 列坐标Col,数值Value
  • 两个指针域
    行指针 Right
    列指针 Down


P2.2 堆栈

  • 函数调用、递归、表达式求值。
    5种操作
  • 生成空堆栈
  • 判断堆栈是否已满
  • 压入
  • 是否为空
  • 删除并返回栈顶元素。
  • 后进先出
    栈的顺序存储
#define MaxSizetypedef struct SNode* Stack;
struct SNode {ElementType Data[MaxSize];int Top;
};
/* 入栈 */void Push(Stack PtrS, ElementType item) {if (PtrS->Top == MaxSize - 1) {printf("堆栈满"); return;}else {PtrS->Data[++(PtrL->Top )] = item;return;}
}
/* 出栈 */ElementType Pop(Stack PtrS) {if (PtrS->Top == -1) {printf("堆栈空");return ERROR;}else {return (PtrS->Data[(PtrS->Top)--]);}
}

例:用一个数组实现两个堆栈

/* 用一个数组实现两个堆栈 */#define MaxSize<存储数据元素的最大个数>
struct DStack {ElementType Data[MaxSize];int Top1;  //  堆栈1的栈顶指针int Top2;  // 堆栈2的栈顶指针
} S;S.Top1 = -1;
S.Top2 = MaxSize;void Push(struct DStack* PtrS, ElementType item, int Tag) {/* Tag 作为区分两个堆栈的标志,取值为1和2*/if (PtrS->Top2 - PtrS->Top1 == 1) {// 堆栈满printf("堆栈满"); return;}if (Tag == 1)  //对第一个堆栈操作PtrS->Data[++(PtrS->Top1)] == item;else                   // 对第二个堆栈操作PtrS->Data[--(PtrS->Top2)] = item;
}ElementType Pop(struct DStack* PtrS, int Tag) {if (Tag == 1) {//对第一个堆栈进行操作if (PtrS->Top1 == -1) {printf("堆栈1空"); return NULL;}else return PtrS->Data[(PtrS->Top1)--];}else {//对第二个堆栈操作if (PtrS->Top2 == MaxSize) {//堆栈2空printf("堆栈2空"); return NULL;}else return PtrS->Data[(PtrS->Top2)++];}
}

2.2.3堆栈的链式存储实现

typedef struct SNode* Stack;
struct SNode {ElementType Data;struct  SNode* Next;
};
/*  堆栈初始化 */Stack CreateStack() {//构建一个堆栈的头结点,返回指针Stack S;S = (Stack)malloc(sizeof(struct SNode));S->Next = NULL;return S;
}/* 判断堆栈 S是否为空 */
int IsEmpty(Stack S) {//若为空返回1return (S->Next == NULL);
}
/* 将元素item 压入堆栈S */
void Push(ElementType item, Stack S) {struct SNode* TmpCell;TmpCell = (struct SNode*)malloc(sizeof(struct SNode));TmpCell->Element = item;TmpCell->Next = S->Next;S->Next = TmpCell;
}/* 删除并返回堆栈S的栈顶元素*/
ElementType Pop(Stack S) {struct SNode* FirstCell;ElementType TopElem;if (IsEmpty(S)) {printf("堆栈空");  return NULL;}else {FirstCell = S->Next;S->Next = FirstCell->Next;TopElem = FirstCell->Element;free(FirstCell);return TopElem;}
}

附录代码

/* 附录代码 1*/typedef int Position;
struct SNode {ElementType* Data;   //存储元素的数组Position Top;        // 栈顶指针int MaxSize;       //堆栈最大容量
};typedef struct SNode* Stack;Stack CreateStack(int MaxSize) {Stack S = (Stack)malloc(sizeof(Struct SNode));S->Data = (ElementType*)malloc(MaxSize * sizeof(ElementType));S->Top = -1;S->MaxSize = MaxSize;return S;
}bool IsFull(Stack  S) {return (S->Top == S->MaxSize - 1);
}bool Push(Stack S, ElementType X) {if (IsFull(S)) {printf("堆栈满");return false;}else {S->Data[++(S->Top)] == X;return true;}
}bool IsEmpty(Stack S) {return (S->Top == -1);
}ElementType Pop(Stack S) {if (IsEmpty(S)) {printf("堆栈空");return ERROR;  //ERROR是ElementType的特殊值,标志错误}elsereturn (S->Data[(S->Top)--]);
}
/* 附录代码2*/
typedef struct SNode *PtrToSNode;struct SNode {ElementType Data;PtrToSNode Next;
};
typedef PtrToSNode Stack;Stack CreateStack() {//构建一个堆栈的头结点,返回该结点指针Stack S;S = (Stack)malloc(sizeof(struct SNode));S->Next = NULL;return S;}bool IsEmpty(Stack S) {//判断堆栈S是否为空,若是返回true,否则返回falsereturn (S->Next == NULL);
}bool Push(Stack S, ElementType X) {//将元素压入堆栈SPtrToSNode TmpCell;TmpCell = (PtrToSNode)malloc(sizeof(struct SNode));TmpCell->Data = X;TmpCell->Next = S->Next;S->Next = TmpCell;return true;
}ElementType Pop(Stack S) {//删除并返回堆栈S的栈顶元素PtrToSNode FirstCell;ElementType TopElem;if (IsEmpty(S)) {printf("堆栈空");return ERROR;}else {FirstCell = S->Next;TopElem = FirstCell->Data;S->Next = FirstCell->Next;free(FirstCell);return TopElem;}
}

2.3 队列

  • 只能一端插入,在另一端删除。
  • FIFO

队列的顺序存储实现

#define MaxSize <存储数据元素的最大个数>
struct QNode {ElementType Data[MaxSize];int rear;int front;
};
typedef struct QNode* Queue;

标记队列满的情况

(1) 使用额外标记,Size或者tag(最后为插入还是删除)
(2) 仅使用n-1个数组空间。
(1) 入队列

/* 入队列 */
void AddQ(Queue PtrQ, ElementType  item) {if ((PtrQ->rear + 1) % MaxSize == PtrQ->front) {printf("队列满");return;}PtrQ->rear = (PtrQ->rear + 1) % MaxSize;PtrQ->Data[PtrQ->rear] = item;
}
/* 出队列 */
ElementType DeleteQ(Queue PtrQ) {if (PtrQ->front == PtrQ->rear) {printf("队列空");return ERROR;}else {PtrQ->front = (PtrQ->front + 1) % MaxSize;return PtrQ->Data[PtrQ->front];}
}
  • Front和Rear的移动采用 加1取余法,体现了顺序存储的循环使用。

队列的链式存储实现

struct Node {ElementType Data;struct Node* Next;
};struct QNode {// 链队列结构struct Node* rear;   // 指向队尾struct Node* front;  //指向队头
};
typedef struct QNode* Queue;
Queue PtrQ;
/* 不带头结点的链式队列出队操作 */
ElementType DeleteQ(Queue PtrQ) {struct Node* FrontCell;ElementType FrontElem;if (PtrQ->front == NULL) {printf("队列空"); return ERROR;}FrontCell = PtrQ->front;if (PtrQ->front == PtrQ->rear) /* 若队列只有一个元素*/PtrQ->front = PtrQ->rear = NULL;   /* 删除后队列为空 */elsePtrQ->front = PtrQ->front->Next;FrontElem = FrontCell->Data;free(FrontCell);      /* 释放被删除结点的空间*/return FrontElem;}
讨论2.3 如何用两个堆栈模拟实现一个队列?

如何用两个堆栈模拟实现一个队列? 如果这两个堆栈的容量分别是m和n(m>n),你的方法能保证的队列容量是多少?

附件代码

/* 附件代码1 */typedef int Position;
struct QNode {ElementTYpe* Data;      //存储元素的数组Position Front, Rear;       // 队列的头、尾指针int MaxSize;                    // 队列最大容量
};typedef struct QNode* Queue;Queue CreateQueue(int MaxSize) {Queue Q = (Queue)malloc(sizeof(struct QNode));Q->Data = (ElementType*)malloc(MaxSize * sizeof(ElementType));Q->Front = Q->Rear = 0;Q->MaxSize = MaxSize;return Q;
}bool IsFull(Queue Q) {return  ((Q->Rear + 1) % Q->MaxSize == Q->Front);
}bool AddQ(Queue Q, ElementType X) {if (IsFull(Q)) {printf("队列满");return false;}else {Q->Rear = (Q->Rear + 1) % Q->MaxSize;Q->Data[Q->Rear] = X;return true;}
}bool IsEmpty(Queue Q) {return (Q->Front == Q->Rear);
}ElementType DeleteQ(Queue Q) {if (IsEmpty(Q)) {printf("队列空");return ERROR;}else {Q->Front = (Q->Front + 1) % Q->MaxSize;return Q->Data[Q->Front];}
}
/* 附件代码2 */
typedef struct Node* PtrToNode;
struct Node {  //队列中的结点ElementType Data;PtrToNode Next;
};typedef PtrToNode Position;struct QNode {Position Front, Rear;      /* 队列的头、尾指针*/int MaxSize;   //队列的最大容量
};
typedef struct QNode* Queue;bool IsEmpty(Queue Q) {return (Q->Front == NULL);
}ElementType DeleteQ(Queue Q) {Position FrontCell;ElementType FrontElem;if (IsEmpty(Q)) {printf("队列空");return ERROR;}else {FrontCell = Q->Front;if (Q->Front == Q->Rear)  /* 队列只有一个元素 */Q->Front = Q->Rear = NULL;           //删除后队列置为空elseQ->Front = Q->Front->Next;FrontElem = FrontCell->Data;free(FrontCell);   /* 释放被删除的结点空间 */return FrontElem;}
}

多项式加法 实践

算法思路:

/* 多项式加法 */struct PolyNode {int coef;  //系数int expon;  //指数struct PolyNode* link;   //指向下一个结点的指针
};
typedef struct PolyNode* Polynomial;
Polynomial P1, P2;  //两个多项式Polynomial PolyAdd(Polynomial P1, Polynomial P2) {Polynomial front, rear, temp;int sum;rear = (Polynomial)malloc(sizeof(struct PolyNode)); /* 为方便表头插入,先产生一个临时空结点作为结果多项式链表头*/front = rear;     /* 由front记录结果多项式链表头结点*/while(P1 && P2)  /* 当两个多项式都有非零项待处理时*/switch (Compare(P1->expon, P2->expon)) {case 1:                   //P1中的数据项指数较大Attach(P1->coef, P1->expon, &rear);P1 = P1->link;break;case -1:   //  P2大Attach(P2->coef, P2->expon, &rear);P2 = P2->link;break;case 0:  // 两者相同sum = P1->coef + P2->coef;if (sum) Attach(sum, P1->expon, &rear);P1 = P1->link;P2 = P2->link;break;}/* 将未处理完的另一个多项式的所有结点依次复制到结果多项式中去*/for (; P1; P1 = P1->link) Attach(P1->coef, P1->expon, &rear);for (; P2; P2 = P2->link) Attach(P2->coef, P2->expon, &rear);rear->link = NULL;temp = front;front = front->link;   /* 令front 指向结果多项式第一个非零项*/free(temp);              /* 释放临时空表头结点*/return front;
}void Attach(int c, int e, Polynomial* pRear) { //传递的是结点指针的地址Polynomial P;P = (Polynomial)malloc(sizeof(struct PolyNode)); //申请新结点P->coef = c;           //新结点赋值P->expon = e;P->link = NULL;(*pRear)->link = P;*pRear = P;
}

数组:

  • 编程简单,调试容易
  • 需要事先确定数组大小

链表:

  • 动态性强
  • 编程略为复杂、调试比较困难。
/* 数据结构设计*/
typedef struct PolyNode* Polynomial;
struct PolyNode {int coef;int expon;Polynomial link;
};

int main() {Polynomial P1, P2, PP, PS;P1 = ReadPoly();P2 = ReadPoly();PP = Mult(P1, P2);PrintPoly(PP);PS = Add(P1, P2);PrintPoly(PS);return 0;
}

Rear初值问题

  • Rear初值为NULL
  • Rear指向一个空结点
/* 读入多项式 */
void Attach(int c, int e, Polynomial* pRear) {Polynomial P;P = (Polynomial)malloc(sizeof(struct Polynomial)); //申请空结点P->coef = c;    //对新结点赋值P->expon = e;P->link = NULL;(*pRear)->link = P;*pRear = P;     /* 修改pRear值*/
}
/* 如何读入多项式*/
Polynomial ReadPoly() {Polynomial P, Rear, t;int c, e, N;scanf("%d", &N);P = (Polynomial)malloc(sizeof(struct PolyNode)); //链表头空结点P->link = NULL;Rear = P;while (N--) {scanf("%d %d", &c, &e);Attach(c, e, &Rear);         //将当前项插入多项式尾部}t = P; P = P->link; free(t);  // 删除临时生成的头结点return P;
}
/*  将两个多项式相加*/
Polynomial Add(Polynomial P1, Polynomial P2) {t1 = P1; t2 = P2;P = (Polynomial)malloc(sizeof(struct PolyNode)); P->link = NULL;Rear = P;while (t1 && t2) {if (t1->expon == t2->expon) {}else  if (t1->expon > t2->expon) {}else {}}t1 = t1->link;while (t1) {t2 = P2; Rear = P;while (t2) {e = t1->expon + t2->expon;c = t1->coef * t2->coef;t2 = t2->link;}t1 = t1->link;}while (t2) {Attach(t1->coef * t2->coef, t1->expon + t2->expon, &Rear);t2 = t2->link;}return P;
}Polynomial Mult(Polynomial P1, Polynomial P2) {Polynomial P, Rear, t1, t2, t;int c, e;if (!P1 || !P2)  return NULL;t1 = P1; t2 = P2;P = (Polynomial)malloc(sizeof(struct PolyNode)); P->link = NULL;Rear = P;while (t2) {  //先用P1的第一项乘以P2,得到PAttach(t1->coef * t2->coef, t1->expon + t2->expon, &Rear);t2 = t2->link;}t1 = t1->link;while (t1) {t2 = P2; Rear = P;while (t2) {e = t1->expon + t2->expon;c = t1->coef * t2->coef;while (Rear->link && Rear->link->expon > e)  // 得到的指数比当前的小,插在后面Rear = Rear->link;if (Rear->link && Rear->link->expon == e) {if (Rear->link->coef + c) //非零项Rear->link->coef += c;else {t = Rear->link;Rear->link = t->link;free(t);}}else {t = (Polynomial)malloc(sizeof(struct PolyNode));t->coef = c; t->expon = e;t->link = Rear->link;Rear->link = t; Rear = Rear->link;}t2 = t2->link;}t1 = t1->link;}t2 = P; P = P->link; free(t2);return P;
}/* 输出多项式*/
void PrintPoly(Polynomial P) {int flag = 0;if (!P) {printf("0 0\n"); return;}while (P) {if (!flag)flag = 1;elseprintf(" ");printf("%d %d", P->coef, P->expon);P = P->link;}printf("\n");
}

3. 树

  • 顺序查找 时间复杂度为O(n)
/* 静态查找
* 方法1:顺序查找
*/int SequentialSearch(StaticTable* Tb1, ElementType K) {//查找关键字为K的数据元素int i;Tb1->Element[0] = K;   // 建立哨兵for (i = Tb1->Length; Tb1->Element[i] != K; i--);return i;   //查找成功返回所在单元下标;不成功返回0
}typedef struct LNode* List;
struct LNode {ElementType Element[MaxSize];int Length;
};
  • 二分查找 时间复杂度O(logN)
/* 静态查找
* 方法2:二分查找(Binary Search)
* 条件: 有序 + 数组
*/int BinarySearch(List Tb1, ElementType K) {/* 在表Tb1中查找关键字为K的数据元素 */int left, right, mid, NotFound = -1;left = 1;      //初始左边界right = Tb1->Length;   // 初始右边界while (left <= right) {mid = (left + right) / 2;   //计算中间元素坐标if (K < Tb1->Element[mid])   right = mid - 1;else if (K > Tb1->Element[mid])  left = mid + 1;else return mid;             //查找成功,返回数据元素的下标}return NotFound; /* 查找不成功,返回-1*/
}
  • n个结点的判定树的深度为[log2(n)] + 1。
    一些概念:
    1、 结点的度(Degree):结点的子树个数
    2、树的度: 树的所有结点中最大的度数
    3、叶结点: 度为0的结点。
    4、祖先结点(Ancestor)
    5、子孙结点(Descendant)
    6、结点的层次(Level):根结点在第一层。

  • 儿子-兄弟表示法

  • 二叉树,5种基本形态

特殊二叉树:
1、斜二叉树(Skewed Binary Tree)
2、完美二叉树(Perfect Binary Tree)/满二叉树(Full Binary Tree)
3、 完全二叉树(Complete Binary Tree): 允许缺失后面的结点,对应于满二叉树的中间结点不允许缺失。

二叉树的几个重要性质:

* n0 = n2 + 1

重要操作: 是否为空,遍历,创建二叉树。

  • 顺序存储结构, 完全二叉树
  • 非根结点的父结点的序号是[i/2] (取整)
  • 左孩子为2i
  • 右孩子为 2i+1
  • 一般二叉树, 补空,但造成空间浪费。

链表存储

typedef struct TreeNode* BinTree;
Typedef BinTree Position;
struct TreeNode {ElementType Data;BinTree Left;BinTree Right;
};

3.3 二叉树的遍历

/* (1)先序遍历: 根结点  左子树  右子树*/void PreOrderTraversal(BinTree BT) {if (BT) {printf("%d", BT->Data);PreOrderTraversal(BT->Left);PreOrderTraversal(BT->Right);}
}
/* (2)中序遍历:左子树   根结点    右子树*/void InOrderTraversal(BinTree BT) {if (BT) {InOrderTraversal(BT->Left);printf("%d", BT->Data);InOrderTraversal(BT->Right);}
}
/* (3)后序遍历:左子树   右子树   根结点*/void PostOrderTraversal(BInTree BT) {if (BT) {PostOrderTraversal(BT->Left);PostOrderTraversal(BT->Right);printf("%d", BT->Data);}
}

堆栈实现二叉树的非递归遍历

/* 中序遍历非递归遍历算法 */void InOrderTraversal(BinTree BT) {BinTree T = BT;Stack S = CreateStack(MaxSize);  //创建并初始化堆栈Swhile (T || !IsEmpty(S)) {while (T) { /* 一直向左并将沿途结点压入堆栈*/Push(S, T);T = T->Left;}if (!IsEmpty(S)) {T = Pop(S);  /* 结点弹出堆栈*/printf("%d", T->Data); /* 访问(打印)结点*/T = T->Right;  //转向右子树}}
}
/* 先序遍历非递归遍历算法 */void InOrderTraversal(BinTree BT) {BinTree T = BT;Stack S = CreateStack(MaxSize);  //创建并初始化堆栈Swhile (T || !IsEmpty(S)) {while (T) { /* 一直向左并将沿途结点压入堆栈*/printf("%d", T->Data); /* 访问(打印)结点*/  //第一次碰到就访问Push(S, T);T = T->Left;}if (!IsEmpty(S)) {T = Pop(S);  /* 结点弹出堆栈*/T = T->Right;  //转向右子树}}
}

层序遍历

  • 保存暂时不访问的结点: 堆栈、队列。
    队列: 抛出根节点,队尾添加子结点
/* 二叉树  层序遍历 队列*/
void LevelOrderTraversal(BinTree BT) {Queue Q; BinTree T;if (!BT) return;   /* 若是空树,直接返回*/Q = CreateQueue(MaxSize);  /* 创建并初始化队列Q*/AddQ(Q, BT);while (!IsEmptyQ(Q)) {T = DeleteQ(Q);printf("%d\n", T->Data);  /* 访问取出队列的结点*/if (T->Left) AddQ(Q, T->Left);if (T->Right) AddQ(Q, T->Right);}}

例:输出二叉树中的叶子结点

/* 输出二叉树中的叶子结点 */
// 左右子树是否都为空void PreOrderPrintLeaves(BinTree BT) {if (BT) {if (!BT->Left && !BT->Right)printf("%d", BT->Data);PreOrderPrintLeaves(BT->Left);PreOrderPrintLeaves(BT->Right);}
}

例:求二叉树的高度

  • 需要知道左右子树的高度,因此修改后序遍历。
/* 求二叉树的高度 */
int PostOrderGetHeight(BinTree BT) {int HL, HR, MaxH;if (BT) {HL = PostOrderGetHeight(BT->Left); /* 求左子树的深度 */HR = PostOrderGetHeight(BT->Right); /* 求右子树的深度 */MaxH = (HL > HR) ? HL : HR;return (MaxH + 1);}else return 0;  //空树深度为0
}

由两种遍历序列确定二叉树

  • 必须要有中序遍历才行。

数的同构

  • 树T1可以通过若干次左右孩子互换就变成T2,则称这两棵树同构。
/* 二叉树表示  结构数组  静态链表*/#define MaxTree 10
#define ElementType char
# define Tree int
#define Null -1   // 表示空struct TreeNode {ElementType Element;Tree Left;Tree Right;
} T1[MaxTree], T2[MaxTree];

int main() {Tree R1, R2;R1 = BuildTree(T1);R2 = BuildTree(T2);if (Isomorphic(R1, R2))  printf("Yes\n");else printf("No\n");return 0;
}
  • T[i]中没有任何结点的left(cl) 和 right(cr)指向它,即为根结点。
/*  建二叉树 */
Tree BuildTree(struct TreeNode T[]) {scanf("%d\n", &N);if (N) {for (i = 0; i < N; i++)  check[i] = 0;  //一开始都是0for (i = 0; i < N; i++) {scanf("%c %c %c\n", &T[i].Element, &cl, &cr);if (cl != '-') { // 有指向T[i].Left = cl - '0';check[T[i].Left] = 1;  // 置1}else T[i].Left = Null;}for (i = 0; i < N; i++)if (!check[i]) break;  // 没有指向的,为根结点Root = i;}return Root;
}/* 判断两二叉树同构 */
int Isomorphic(Tree R1, Tree R2) {if ((R1 == Null) && (R2 == Null))return 1;   // 两个都为空if (((R1 == Null) && (R2 != Null)) || ((R1 != Null) && (R2 == Null)))return 0;     //其中一个为空if (T1[R1].Element != T2[R2].Element)return 0;if ((T1[R1].Left == Null) && (T2[R2].Left == Null))return Isomorphic(T1[R1].Right, T2[R2].Right);if (((T1[R1].Left != Null) && (T2[R2].Left != Null)) &&((T1[T1[R1].Left].Element) == (T2[T2[R2].Left].Element)))//不需要交换左右两边return (Isomorphic(T1[R1].Left, T2[R2].Left) &&Isomorphic(T1[R1].Right, T2[R2].Right));else  //需要交换左右两边return (Isomorphic(T1[R1].Left, T2[R2].Right) &&Isomorphic(T1[R1].Right, T2[R2].Left));
}

4.1 二叉搜索树

  • 二叉搜索树(BST, Binary Search Tree), 二叉排序树或二叉查找树

1、左子树的所有健值小于根结点的健值。
2、右子树的所有健值大于其根结点的健值。
3、左、右子树都是二叉搜索树。

二叉搜索树的查找

/* 二叉搜索树的查找*/
// 尾递归
Position Find(ElementType X, BinTree BST) {if (!BST) return NULL;  /* 查找失败*/if (X > BST->Data)return Find(X, BST->Right);  //找右子树else if (X < BST->Data)return Find(X, BST->Left);  /* 在左子树中继续查找*/elsereturn BST;     /* 查找成功,返回找到的结点的地址*/
}
// 改成迭代
Position IterFind(ElementType X, BinTree BST) {while (BST) {if (X > BST->Data)BST = BST->Right;else if (X < BST->Data)BST = BST->Left;elsereturn BST;}return NULL;
}

找最值

/* 搜索二叉树:查找最小元素的 递归 函数*/
Position FindMin(BinTree BST) {if (!BST) return NULL;  /* 空的二叉搜索树,返回NULL*/else if (!BST->Left)return BST;             /* 找到最左叶结点并返回*/elsereturn FindMin(BST->Left);   /* 沿左分支继续查找*/
}/* 搜索二叉树:查找最大元素的 迭代 函数*/
Position FindMax(BinTree BST) {if (BST)while (BST->Right) BST = BST->Right;  /* 沿右分支继续查找,直到最右叶结点*/return BST;
}

二叉搜索树的插入

/* 二叉搜索树的插入算法*/
BinTree Insert(ElementType X, BinTree BST) {if (!BST) { /* 原树为空,生成并返回一个结点的二叉搜索树*/BST = malloc(sizeof(struct TreeNode));BST->Data = X;BST->Left = BST->Right = NULL;}else /* 开始找要插入元素的位置*/if (X < BST->Data)BST->Left = Insert(X, BST->Left); /* 递归插入左子树*/else if (X > BST->Data)BST->Right = Insert(X, BST->Right); /* 递归插入右子树*/return BST;
}

二叉搜索树的 删除

三种情况:
1、叶结点
2、只有一个孩子
3、双结点:

  • 用另一结点替代被删除结点: 右子树中的最小元素或 左子树的最大元素。
/*  二叉搜索树的 删除*/BinTree Delete(ElementType X, BinTree BST) {Position Tmp;if (!BST) printf("要删除的元素未找到");else if (X < BST->Data)BST->Left = Delete(X, BST->Left);  //左子树递归删除else if (X > BST->Data)BST->Right = Delete(X, BST->Right); // 右子树递归删除else  /* 找到要删除的结点 */if (BST->Left && BST->Right) {/* 被删除结点有左右两个子结点*/Tmp = FindMin(BST->Right);  /* 在右子树中找最小的元素填充删除结点*/BST->Data = Tmp->Data;BST->Right = Delete(BST->Data, BST->Right); /* 在删除结点的右子树中删除最小元素*/}else {/* 被删除结点有一个或无子结点 */Tmp = BST;if (!BST->Left) /* 有右孩子或无子结点*/BST = BST->Right;else if (!BST->Right) /* 有左孩子或无子结点*/BST = BST->Left;free(Tmp);}return BST;
}

4.2 平衡二叉树

  • 平衡二叉树(Balanced Binary Tree)(AVL树)
    空树或者任一结点的左、右子树高度差的绝对值不超过1
  • RR旋转
  • LL旋转
  • LR旋转
  • RL旋转(右子树的左子树上)

补充代码

typedef struct AVLNode* Position;
typedef Position AVLTree;  /* AVL 树类型*/
struct AVLNode {ElementType Data;  /* 结点数据 */AVLtree Left;      /* 指向左子树 */AVLTree Right;   /* 指向右子树 */int Height;          /* 树高 */
};int Max(int a, int b) {return a > b ? a : b;
}AVLTree SingleLeftRotation(AVLTree A) {/* 将A与B做左单旋,更新A与B的高度,返回新的根结点B*/AVLTree B = A->Left;A->Left = B->Right;B->Right = A;A->Height = Max(GetHeight(A->Left), GetHeight(A->Right)) + 1;B->Height = Max(GetHeight(B->Left), A->Height) + 1;return B;
}AVLTree DoubleLeftRightRotation(AVLTree A) {/* 将A、B与C分别做两次单旋,返回新的根结点C*//* 将B与C做右单旋,C被返回*/A->Left = SingleRightRotation(A->Left);/* 将A与C做左单旋, C被返回*/return SingleLeftRotation(A);
}/* 对称的右单旋与右-左双旋    待实现*/
AVLTree Insert(AVLTree T, ElementType X) {/* 将X插入AVL树T中,并且返回调整后的AVL树*/if (!T) {/* 若插入空树,则新建包含一个结点的树*/T = (AVLTree)malloc(sizeof(struct AVLTree));T->Data = X;T->Height = 0;T->Right = T->Left = NULL;}else if (X < T->Data) {/* 插入T的左子树*/T->Left = Insert(T->Left, X);/*  如果需要左旋*/if (GetHeight(T->Left) - GetHeight(T->Right) == 2)if (X < T->Left->Data)T = SingleLeftRotation(T);  //左单旋elseT = DoubleLeftRightRotation(T);   /* 左-右双旋*/}else if (X > T->Data) {//插入T的右子树T->Right = Insert(T->Right, X);/* 如果需要右旋*/if (GetHeight(T->Left) - GetHeight(T->Right) == -2)if (X > T->Right->Data)T = SingleRightRotation(T);  /* 右单旋*/elseT = DoubleRightLeftRotation(T);   /* 右-左双旋*/}/* else  X == T-> Data, 无需插入*//*  更新树高 */T->Height = Max( GetHeight(T->Left), GetHeight(T->Right)) + 1;return T;
}

例: 是否同一棵二叉搜索树

/* *//* 1、搜索树表示*/
typedef struct TreeNode* Tree;
struct TreeNode {int v;Tree Left, Right;int flag;  // 标记是否被访问过
};/* 2、 建搜索树T */Tree NewNode(int V) {Tree T = (Tree)malloc(sizeof(struct TreeNode));T->v = V;T->Left = T->Right = NULL;T->flag = 0;return T;
}Tree Insert(Tree T, int V) {if (!T) T = NewNode(V);else {if (V > T->v)T->Right = Insert(T->Right, V);elseT->Left = Insert(T->Left, V);}return T;
}Tree MakeTree(int N) {Tree T;int i, V;scanf("%d", &V);T = NewNode(V);for (i = 1; i < N; i++) {scanf("%d", &V);T = Insert(T, V);}return T;
}/* 3、判别一序列是否与搜索树T一致 */
// 在树T中按顺序搜索序列中的每个数,如果某次搜索中遇到前面未出现过的结点,则不一致int check(Tree T, int V) {if (T->flag) {if (V < T->v) return check(T->Left, V);else if (V > T->v)   return check(T->Right, V);else return 0;}else {if (V == T->v) {T->flag = 1;return 1;}else  return 0;}
}int Judge(Tree T, int N) {int i, V, flag = 0;  /* flag: 0代表目前还一致,1代表已经不一致*/scanf("%d", &V);if (V != T->v)   flag = 1;else   T->flag = 1;for (i = 1; i < N; i++) {scanf("%d", &V);if ((!flag) && (!check(T, V)))   flag = 1;}if (flag)   return 0;else return 1;
}void ResetT(Tree T) {/* 清除T中各结点的flag标记*/if (T->Left) ResetT(T->Left);if (T->Right)  ResetT(T->Right);T->flag = 0;
}void FreeTree(Tree T) {/* 释放T的空间*/if (T->Left) FreeTree(T->Left);if (T->Right) FreeTree(T->Right);free(T);
}int main() {int N, L, i;Tree T;scanf("%d", &N);while (N) {scanf("%d", &L);T = MakeTree(N);for (i = 0; i < L; i++) {if (Judge(T, N))    printf("Yes\n");else printf("No\n");ResetT(T);     /* 清除T中的标记flag*/}FreeTree(T);scanf("%d", &N);}return 0;
}

5.1 堆


堆:

1、结构性: 用数组表示的完全二叉树
2、有序性:最大堆,最小堆。

  • 从根结点到任意结点路径上结点序列 有序。

主要操作:创建一个空堆,是否已满, 插入,是否为空, 返回最大。

/* 创建最大堆 */
typedef struct HeapStruct* MaxHeap;
struct HeapStruct {ElementType* Elements;  /* 存储堆元素的数组*/int size;    /* 堆的当前元素个数*/int Capacity;  /* 堆的最大容量 */
};MaxHeap Create(int MaxSize) {MaxHeap H = malloc(sizeof(struct HeapStruct));H->Elements = malloc((MaxSize + 1) * sizeof(ElementType)); //数组空间H->size = 0;H->Capacity = MaxSize;H->Elements[0] = MaxData; /* 定义“哨兵"为大于堆中所有可能元素的值,便于以后更快操作*/return H;
}
/*  堆操作: 插入*/
void Insert(MaxHeap H, ElementType item) {/* 将元素item 插入到最大堆H,其中H-> Elements[0] 已经定义为哨兵*/int i;if (IsFull(H)) {printf("最大堆已满");return;}i = ++H->Size;  /* i指向插入后堆中的最后一个元素的位置*/for (; H->Elements[i / 2] < item; i /= 2)H->Elements[i] = H->Elements[i / 2];  /* 向下过滤结点*/H->Elements[i] = item; /* 将item插入*/
}
/*  堆操作: 删除元素*/
ElementType DeleteMax(MaxHeap H) {/* 从最大堆H中取出健值为最大的元素,并删除一个结点*/int Parent, Child;ElementType MaxItem, temp;if (IsEmpty(H)) {printf("最大堆已为空");return;}MaxItem = H->Elements[1]; /* 取出根结点*/temp = H->Elements[H->Size--];for (Parent = 1; Parent * 2 <= H->size; Parent = Child) {Child = Parent * 2;if ((Child != H->Size) &&(H->Elements[Child] < H->Elements[Child + 1]))Child++;   /* Child指向左右子结点的较大者*/if (temp >= H->Elements[Child])  break;else /* 移动temp元素到下一层*/H->Elements[Parent] = H->Elements[Child];}H->Elements[Parent] = temp;return MaxItem;
}

其他:

typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {ElementType *Data; /* 存储元素的数组 */int Size;          /* 堆中当前元素个数 */int Capacity;      /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
typedef Heap MinHeap; /* 最小堆 */#define MAXDATA 1000  /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */MaxHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最大堆 */MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));H->Data = (ElementType *)malloc((MaxSize+1)*sizeof(ElementType));H->Size = 0;H->Capacity = MaxSize;H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/return H;
}bool IsFull( MaxHeap H )
{return (H->Size == H->Capacity);
}bool Insert( MaxHeap H, ElementType X )
{ /* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */int i;if ( IsFull(H) ) { printf("最大堆已满");return false;}i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */for ( ; H->Data[i/2] < X; i/=2 )H->Data[i] = H->Data[i/2]; /* 上滤X */H->Data[i] = X; /* 将X插入 */return true;
}#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */bool IsEmpty( MaxHeap H )
{return (H->Size == 0);
}ElementType DeleteMax( MaxHeap H )
{ /* 从最大堆H中取出键值为最大的元素,并删除一个结点 */int Parent, Child;ElementType MaxItem, X;if ( IsEmpty(H) ) {printf("最大堆已为空");return ERROR;}MaxItem = H->Data[1]; /* 取出根结点存放的最大值 *//* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 */for( Parent=1; Parent*2<=H->Size; Parent=Child ) {Child = Parent * 2;if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )Child++;  /* Child指向左右子结点的较大者 */if( X >= H->Data[Child] ) break; /* 找到了合适位置 */else  /* 下滤X */H->Data[Parent] = H->Data[Child];}H->Data[Parent] = X;return MaxItem;
} /*----------- 建造最大堆 -----------*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */int Parent, Child;ElementType X;X = H->Data[p]; /* 取出根结点存放的值 */for( Parent=p; Parent*2<=H->Size; Parent=Child ) {Child = Parent * 2;if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )Child++;  /* Child指向左右子结点的较大者 */if( X >= H->Data[Child] ) break; /* 找到了合适位置 */else  /* 下滤X */H->Data[Parent] = H->Data[Child];}H->Data[Parent] = X;
}void BuildHeap( MaxHeap H )
{ /* 调整H->Data[]中的元素,使满足最大堆的有序性  *//* 这里假设所有H->Size个元素已经存在H->Data[]中 */int i;/* 从最后一个结点的父节点开始,到根结点1 */for( i = H->Size/2; i>0; i-- )PercDown( H, i );
}
/* 附录代码 堆*/
typedef struct HNode* Heap;   /* 堆的类型定义*/struct HNode {ElementType* Data; /* 存储元素的数组*/int size;                     /* 堆中当前元素个数*/int Capacity;             /* 堆的最大容量 */
};typedef Heap MaxHeap;   /* 最大堆 */
typedef Heap MinHeap;   /* 最小堆*/#define MAXDATA  1000      /*  大于堆中所有可能元素的值*/MaxHeap CreateHeap(int MaxSize) {/* 创建容量为MaxSize的空的最大堆*/MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));H->Data = (ElementType*)malloc(MaxSize + 1) * sizeof(ElementType);H->Size = 0;H->Capacity = MaxSize; H->Data[0] = MAXDATA;    /* 定义 哨兵 为大于堆中所有可能元素的值*/return H;
}bool IsFull(MaxHeap H) {return (H->Size == H->Capacity);
}bool Insert(MaxHeap H, ElementType X) {/*  将元素X插入最大堆H, 其中H->Data[0] 已经定义为 哨兵*/int i;if (IsFull(H)) {printf("最大堆已满");return false;}i = ++H->Size;  /* i指向插入后 堆中的最后一个元素的位置*/for (; H->Data[i / 2] < X; i /= 2)H->Data[i] = H->Data[i / 2];   /* 上滤X*/H->Data[i] = X;   /*  将X插入*/return true;
}#define ERROR -1   /* 定义为堆中不可能出现的元素值*/bool IsEmpty(MaxHeap H) {return (H->size == 0);
}ElementType DeleteMax(MaxHeap H) {/* 从最大堆中取出健值为最大的元素,并删除一个结点*/int Parent, Child;ElementType MaxItem, X;if (IsEmpty(H)) {printf("最大堆已为空");return ERROR;}MaxItem = H->Data[1];  /* 取出根节点存放的最大值*//* 用最大堆中最后一个元素从根结点开始向上过滤下层结点*/X = H->Data[H->size--];   for (Parent = 1; Parent * 2 < H->Size; Parent = Child) {Child = Parent * 2;if ((Child != H->Size) && (H->Data[Child] < H->Data[Child + 1]))Child++;      /* Child指向左右结点中的较大者*/if (X >= H->Data[Child])   break;elseH->Data[Parent] = H->Data[Child];}H->Data[Parent] = X;return MaxItem;
}/*  创建最大堆 */
void PercDown(MaxHeap H, int p) {/* 下滤: 将H->Data[p]为根的子堆调整为最大堆*/int Parent, Child;ElementType X;X = H->Data[p]; /* 取出根结点存放的值*/for (Parent = p; Parent * 2 <= H->Size; Parent = Child) {Child = Parent * 2;if ((Child != H->Size) && (H->Data[Child] < H->Data[Child + 1]))Child++;if (X >= H->Data[Child])   break;elseH->Data[Parent] = H->Data[Child];}H->Data[Parent] = X;
}void BuildHeap(MaxHeap H) {/* 调整H->Data[]中元素,使满足最大堆的有序性*//* 这里假设所有H->Size个元素已经存在H-> Data[]中*/int i;/* 从最后一个结点的父结点开始,到根结点1*/for (i = H->Size / 2; i > 0; i--)PercDown(H, i);
}

P5.2 哈夫曼树和哈夫曼编码

  • 带权路径长度(WPL)
  • 最优二叉树或哈夫曼树:WPL最小的二叉树
/* 哈夫曼树创建 */
typedef struct TreeNode* HuffmanTree;
struct TreeNode {int Weight;HuffmanTree Left, Right;
};HuffmanTree Huffman(MinHeap H) {/* 假设H->Size个权值已经存在H->Elements[]->weight里*/int i; HuffmanTree T;BuildMinHeap(H);   /* 将H-> Elements[]按权值调整为最小堆*/for (i = 1; i < H->Size; i++) {/* 做H->Size-1次合并*/T = malloc(sizeof(struct TreeNode));  /* 建立新结点*/T->Left = DeleteMin(H);  /* 从最小堆中删除一个结点,作为新T的左子结点*/T->Right = DeleteMin(H); /* 从最小堆中删除一个结点,作为新T的右子结点*/T->Weight = T->Left->Weight + T->Right->Weight; /* 计算新权值*/Insert(H, T); /* 将新T插入最小堆*/}T = DeleteMin(H);return T;
}
  • 整体复杂度为O(NlogN)
    1、用二叉树进行编码:

(1)左右分支:0、1
(2)字符只在叶结点上

P5.3集合

  • 采用数组表示集合
typedef struct {ElementType Data;int Parent;
}SetType;
/* 集合运算: 查找某个元素所在的集合(用根结点表示)*/int Find(SetType S[], ElementType X) {/* 在数组S中查找值为X的元素所属的集合*//* MaxSize是全局变量,为数组S的最大长度*/int i;for (i = 0; i < MaxSize && S[i].Data != X; i++);if (i >= MaxSize)  return -1;  for (; S[i].Parent >= 0; i = S[i].Parent);return i;
}
/* 集合的并运算*/
/*1、分别找到X1和X2两个元素所在集合树的根节点
* 2、如果不同根,则将其中一个根结点的父结点指针设置成另一个根结点的数组下标
*/void Union(SetType S[], ElementType X1, ElementType X2) {int Root1, Root2;Root1 = Find(S, X1);Root2 = Find(S, X2);if (Root1 != Root2)  S[Root2].Parent = Root1;
}/* 为了改善合并以后的查找性能,应将小的集合合并到大的集合中*/

专场:堆中的路径

/* 堆的表示及其操作*/
#define MAXN 1001
#define MINH -10001int H[MAXN], size;void Create() {size = 0;H[0] = MINH;  /* 设置 岗哨*/
}void Insert(int X) {/* 将X插入H, 省略检查堆是否已满的代码*/int i;for (i = ++size; H[i / 2] > X; i /= 2)H[i] = H[i / 2];H[i] = X;
}int main() {int n, m, x, i, j;scanf("%d %d", &n, &m);Create();  /* 堆初始化*/for (i = 0; i < n; i++) {/* 以逐个插入的方式建堆*/scanf("%d", &x);Insert(x);}//查询for (i = 0; i < m; i++) {scanf("%d", &j);printf("%d", H[j]);while (j > 1) {//沿根方向输入各结点j /= 2;printf("%d", H[j]);}printf("\n");}return 0;
}

专场: 文件传输

  • 映射

结合的表示(常规)

/* 集合的表示(常规)*/
typedef struct {ElementType Data;int Parent;
}SetType;int Find(SetType S[], ElementType X) {int i;for (i = 0; i < MaxSize && S[i].Data != X; i++);if (i >= MaxSize)  return -1;for (; S[i].Parent >= 0; i = S[i].Parent);retutn i;
}

集合的简化表示

/* 集合的简化表示*/typedef int ElementType; /* 默认元素可以用非负整数表示*/
typedef int SetName;   /* 默认用根结点的下标作为集合名称*/typedef ElementType SetType[MaxSize];SetName Find(SetType S, ElementType X) {/* 默认集合元素全部初始化为-1*/for (; S[X] >= 0; X = S[X]);return X;
}void Union(SetType S, SetName Root1, SetName Root2) {/* 默认Root1和Root2是不同集合的根结点*/S[Root2] = Root1;
}

程序框架

/* 程序框架*/
int main() {SetType S;int n;char in;scanf("%d\n", &n);Initialization(S, n);do {scanf("%c", &in);switch (in) {case 'I': Input_connection(S); break;   // Union(Find)case 'C': Check_connection(S); break;   //Findcase 'S': check_network(S, n);  break;   // 数集合的根}} while (in != 'S');return 0;
}
void Input_connection(SetType S) {ElementType u, v;SetName Root1, Root2;scanf("%d %d\n", &u, &v);Root1 = Find(S, u - 1);Root2 = Find(S, v - 1);if (Root1 != Root2)Union(S, Root1, Root2);
}void Check_connection(SetType S) {ElementType u, v;SetName Root1, Root2;scanf("%d %d\n", &u, &v);Root1 = Find(S, u - 1);Root2 = Find(S, v - 1);if (Root1 == Root2)printf("yes\n");else printf("no\n");
}void Check_network(SetType S, int n) {int i, counter = 0;for (i = 0; i < n; i++) {if (S[i] < 0)  counter++;}if (counter == 1)printf("The network is connected.\n");elseprintf("There are %d components.\n", counter);
}

改进1:按秩归并

  • 把矮树贴到高树上 S[Root] = -树高
/* 按秩归并: 比树高*/
if (S[Root2] < S[Root1])S[Root1] = Root2;
else {if (S[Root1] == S[Root2])  S[Root1]--;  //值为负的,要增大,故为--S[Root2] = Root1;
}
  • 比规模
/* 按秩归并: 比规模(把小树贴到大树上)*/
void Union(SetType S, SetName Root1, SetName Root2) {if (S[Root2] < S[Root1]) {S[Root2] += S[Root1];S[Root1] = Root2;}else {S[Root1] += S[Root2];S[Root2] = Root1;}
}

改进2:路径压缩

/* 改进2: 路径压缩*/
SetName Find(SetType S, ElementType X) {if (S[X] < 0)   /* 找到集合的根*/return X;elsereturn S[X] = Find(S, S[X]);  //伪递归/*先找到根;把根变成X的父结点;再返回根。 */
}

__树_习题

  • Push的顺序为 先序遍历
  • Pop的顺序为 中序遍历

习题:中序+前序–>后序

void solve(int preL, int inL, int postL, int n) {if (n == 0)  return;  //没有右结点的情况if (n == 1) { post[postL] = pre[preL]; return; }root = pre[preL];post[postL + n - 1] = root;    //存根结点for (i = 0; i < n; i++)if (in[inL + i] == root)   break;  // 计算左右子树结点个数L = i; R = n - L - 1;solve(preL + 1, inL, postL, L);solve(preL + L + 1, inL + L + 1, postL + L, R);}

习题: 完全二叉搜索树

完全二叉树: 编号不缺失
二叉搜索树: 左<根<右

使用数组的原因:
1、完全二叉树,不浪费空间
2、层序遍历==直接顺序输出

先序遍历

void solve(int ALeft, int ARight, int TRoot)
{   //A的左端,右端, 结果数组/* 初始调用为 solve(0, N-1, 0) */n = ARight - ALeft + 1;  //序列长度if (n == 0) return;L = GetLeftLength(n);  /* 计算n个结点的树其左子树有多少个结点*/T[TRoot] = A[ALeft + L];LeftTRoot = TRoot * 2 + 1;   //下标从0开始RightTRoot = LeftTRoot + 1;solve(ALeft, ALeft + L - 1, LeftTRoot);solve(ALeft + L + 1, ARight, RightTRoot);
}/*  排序部分*/int compare(const void* a, const void* b)
{return *(int*)a - *(int*)b;
}#include<stdlib.h>
int main() {...qsort(A, N, sizeof(int), compare);
}

1、最优编码:总长度(WPL)最小
2、无歧义编码: 前缀码(数据仅存于叶子结点)
3、没有度为1的结点(满足1、2则必然有3)

/* 1、计算最优编码长度*/
MinHeap  H = CreateHeap(N); /* 创建一个空的,容量为N的最小堆*/
H = ReadData(N);  /* 将f[] 读入H->Data[]中*/
HuffmanTree T = Huffman(H); /* 建立Huffman树*/
int CodeLen = WPL(T, 0);int WPL(HuffmanTree T, int Depth) {if (!T->Left && !T->Right)return (Depth * T->Weight);elsereturn (WPL(T->Left, Depth + 1)+ WPL(T->Right, Depth + 1));
}/*2、 检查:  长度是否正确; 是否满足前缀码要求*/

__线性结构习题:单链表的逆转

  • 加一个头结点
/* 单链表的逆转*/
Ptr Reverse(Ptr head, int K) {new = head->next;old = new->next;while (cnt <K) {tmp = old->next;old->next = new;new = old; old = tmp;cnt++;}head->next->next = old;return new;
}

P6 图

  • 多对多
  • 图书馆、社交网络
  • 六度空间理论(Six Degrees of Separation)

线性表+ 树


无向图
有向图
网络: 带权重的图

  • 邻接矩阵G[N][N] : N个顶点从0到N-1编号
  • 1、对角线值为0:不允许自回路
  • 2、对称



适用于稠密图

  • 邻接表

/* 图的邻接矩阵表示*/#define MaxVertexNum 100    /* 最大顶点数设为100*/
#define INFINITY  65535        /* ∞设为双字节无符号整数的最大值65535*/
typedef int Vertex;                 /* 用顶点下标表示顶点,为整型*/
typedef int WeightType;       /* 边的权值设为整型*/
typedef char DataType;         /* 顶点存储的数据类型设为字符型 *//* 边的定义 */
typedef struct ENode* PtrToENode;
struct ENode {Vertex V1, V2;                 /* 有向边<V1,V2>*/WeightType Weight;     /* 权重*/
};
typedef PtrToENode Edge;/* 图结点的定义*/typedef struct GNode* PtrToGNode;  //指向该结点的指针
struct GNode {int Nv;     /* 顶点数*/int Ne;   /* 边数*/WeightType  G[MaxVertexNum][MaxVertexNum];   /* 邻接矩阵*/DataType Data[MaxVertexNum];            /* 存顶点的数据*//* 很多情况下,顶点无数据,此时Data[]可以不用出现*/
};
typedef PtrToGNode MGraph;     /* 以邻接矩阵存储的图类型*/MGraph CreateGraph(int VertexNum) {/* 初始化一个有VertexNum个顶点但没有边的图*/Vertex V, W; // 顶点,实际是整型MGraph Graph;Graph = (MGraph)malloc(sizeof(struct GNode));   /* 建立图*/Graph->Nv = VertexNum;Graph->Ne = 0;/* 初始化邻接矩阵:这里默认顶点编号从0开始,到(Graph->Nv-1)*/for (V = 0; V < Graph->Nv; V++)for (W = 0; W < Graph->Nv; W++)Graph->G[V][W] = INFINITY; /* 或0 */return Graph;
}void InsertEdge(MGraph Graph, Edge E) {/* 插入边<V1, V2>*/Graph->G[E->V1][E->V2] = E->Weight;/* 若是无向图,还要插入边<V2,V1>*/Graph->G[E->V2][E->V1] = E->Weight;
}MGraph BuildGraph() {MGraph Graph;Edge E;Vertex V;int Nv, i;scanf("%d", &Nv);    /* 读入顶点个数*/Graph = CreateGraph(Nv);     /* 初始化有Nv个顶点但没有边的图*/scanf("%d", &(Graph->Ne));    /* 读入边数*/if (Graph->Ne != 0) {/* 如果有边*/E = (Edge)malloc(sizeof(struct ENode));   /* 建立边结点*//* 读入边, 格式为 起点,终点, 权重,  插入邻接矩阵*/for (i = 0; i < Graph->Ne; i++) {scanf("%d %d %d", &E->V1, &E->V2, &E->Weight);/* 注意: 如果权重不是整型,Weight的读入格式要改*/InsertEdge(Graph, E);}}/* 如果顶点有数据,读入数据*/for (V = 0; V < Graph->Nv; V++)scanf("  %c", &(Graph->Data[V]));return Graph;
}
/* 图的邻接表 表示*/#define MaxVertexNum 100    /* 最大顶点数设为100*/typedef int Vertex;                 /* 用顶点下标表示顶点,为整型*/
typedef int WeightType;       /* 边的权值设为整型*/
typedef char DataType;         /* 顶点存储的数据类型设为字符型 *//* 边的定义 */
typedef struct ENode* PtrToENode;
struct ENode {Vertex V1, V2;                 /* 有向边<V1,V2>*/WeightType Weight;     /* 权重*/
};typedef PtrToENode Edge;/* 邻接点的定义 */
typedef struct AdjVNode* PtrToAdjVNode;
struct AdjVNode {Vertex AdjV;    /* 邻接点下标*/WeightType Weight; /* 边权重*/PtrToAdjVNode Next;     /* 指向下一个邻接点的指针*/
};/* 顶点表头 结点的定义*/
typedef struct Vnode {PtrToAdjVNode FirstEdge;    /* 边表头指针*/DataType Data;             /* 存顶点的数据*//* 很多情况下,顶点无数据,此时Data[]可以不用出现*/
}AdjList[MaxVertexNum];    /* AdjList是邻接表类型*//* 图结点的定义*/
typedef struct GNode *PtrToGNode;
struct GNode {int Nv;    /* 顶点数*/int Ne;   /*  边数 */AdjList G;   /* 邻接表*/
};
typedef PtrToGNode LGraph;   /* 以邻接表方式存储的图类型*/LGraph CreateGraph(int VertexNum) {/* 初始化一个有VertexNum个顶点但没有边的图*/Vertex V;LGraph Graph;Graph = (LGraph)malloc(sizeof(struct GNode));   /* 建立图*/Graph->Nv = VertexNum;Graph->Ne = 0;/* 初始化邻接表头指针:这里默认顶点编号从0开始,到(Graph->Nv-1)*/for (V = 0; V < Graph->Nv; V++)Graph->G[V].FirstEdge = NULL;return Graph;
}void InsertEdge(LGraph Graph, Edge E) {PtrToAdjVNode NewNode;/* 插入边<V1, V2>*//* 为V2建立新的邻接点 */NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));NewNode->AdjV = E->V2;NewNode->Weight = E->Weight;/* 将V2 插入V1 的表头*/NewNode->Next = Graph->G[E->V1].FirstEdge;Graph->G[E->V1].FirstEdge = NewNode;/* 若是无向图,还要插入边<V2,V1>*//*  为 V1建立新的邻接点*/NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));NewNode->AdjV = E->V1;NewNode->Weight = E->Weight;/* 将V1插入V2表头*/NewNode->Next = Graph->G[E->V2].FirstEdge;Graph->G[E->V2].FirstEdge = NewNode;
}LGraph BuildGraph() {LGraph Graph;Edge E;Vertex V;int Nv, i;scanf("%d", &Nv);    /* 读入顶点个数*/Graph = CreateGraph(Nv);     /* 初始化有Nv个顶点但没有边的图*/scanf("%d", &(Graph->Ne));    /* 读入边数*/if (Graph->Ne != 0) {/* 如果有边*/E = (Edge)malloc(sizeof(struct ENode));   /* 建立边结点*//* 读入边, 格式为 起点,终点, 权重,  插入邻接表*/for (i = 0; i < Graph->Ne; i++) {scanf("%d %d %d", &E->V1, &E->V2, &E->Weight);/* 注意: 如果权重不是整型,Weight的读入格式要改*/InsertEdge(Graph, E);}}/* 如果顶点有数据,读入数据*/for (V = 0; V < Graph->Nv; V++)scanf("  %c", &(Graph->G[V].Data));return Graph;
}

P5.2 图的遍历

深度优先搜索(Depth First Search, DFS)

广度优先搜索(Breadth First Search, BFS)


连通分量: 无向图的极大连通子图

  • 极大顶点数
  • 极大边数:包含子图 中 所有顶点相连的所有边

强联通
弱联通

/* 邻接表存储的图 _ DFS */
void Visit(Vertex V) {printf("正在访问顶点%d\n", V);
}/* Visit[] 为全局变量,已经初始化为false */
void DFS(LGraph Graph, Vertex V, void (*Visit)(Vertex)) {/* 以V为出发点对邻接表存储的图Graph进行DFS搜索*/PtrToAdjVNode W;Visit(V);   /* 访问第V个顶点*/Visited[V] = true;    /* 标记V已访问*/for (W = Graph->G[V].FirstEdge; W = W->Next)/* 对V的每个邻接点W->AdjV*/if (!Visited[W->AdjV])   /* 若W-> AdjV未被访问*/DFS(Graph, W->AdjV, Visit);    /* 递归访问 */
}
/* 邻接矩阵存储的图 _ BPS *//* IsEdge(Graph, V, W) 检查<V, W>是否图Graph中的一条边,即W是否V的邻接点*/
/* 此函数根据图的不同类型要做不同的实现,关键取决于对不同边的表示方法*//* 例如对有权图,如果不存在的边被初始化为INFINITY, 则函数实现如下:*/
bool IsEdge(MGraph Graph, Vertex V, Vertex W) {return Graph->G[V][W]<INFINITY ? true : false>;
}/* Visited[]  为全局变量,已经初始化为false*/
void BFS(MGraph Graph, Vertex S, void (*Visit)(Vertex)) {/* 以S为出发点对邻接矩阵存储的图Graph进行BFS搜索*/Queue Q;Vertex V, W;Q = CreateQueue(MaxSize); /* 创建空队列,MaxSize为外部定义的常数*//* 访问顶点S: 此处可根据具体访问需要改写 */Visit(S);Visited[S] = true;  /* 标记S已访问 */AddQ(Q, S); /* S入队列*/while (!IsEmpty(Q)) {V = DeleteQ(Q);   /* 弹出V */for(W=0; W< Graph -> Nv; W++) /* 对图中的每个顶点W *//*  若W是V的邻接点并且未访问过 */if (!Visited[W] && IsEdge(Graph, V, W)) {/* 访问顶点  */Visit(W);Visited[W] = true;  /* 标记W已访问*/AddQ(Q, W);    /* W入队列 */}}
}

习题: 007

/* 总体算法 */void ListComponents(Graph G) {for(each V in G)if (!visited[V]) {DFS(V);}
}void Save007(Graph G) {for (each V in G) {if (!visitedd[V] && FirstJump(V)) {answer = DFS(V);if (answer == YES)  break;}}if (answer == YES) output("YES");else output("No");
}void DFS(Vertex V) {visited[V] = true;if (IsSafe(V))  answer = YES;else {for(each W in G)if (!visited[W] && Jump(V, W)) {answer = DFS(W);if (answer == YES) break;}}return answer;
}

例: 六度空间

/* 六度空间 */
void SDS() {for (each V in G) {count = BFS(V);output(count / N);}
}int BFS(Vertex V) {visited[V] = true; count = 1;level = 0; last = V;Enqueue(V, Q);while (!IsEmpty(Q)) {V = Dequeue(Q);for(V 的每个邻接点W)if (!visited[W]) {visited[W] = true;Enqueue(W, Q); count++;tail = W;}if (V == last) {level++; last = tail;}if (level == 6) break;}return count;
}

专场:建图

  • 实际是P6.1节附录代码的解说
/* 建图: 邻接矩阵 */
int G[MAXN][MAXN], Nv, Ne;
void BuildGraph() {int i, j, v1, v2, w;scanf("%d", &Nv);/* CreateGraph*/for (i = 0; i < Nv; i++)for (j = 0; j < Nv; j++)G[i][j] = 0;   /* 或 INIFINITY*/scanf("%d", &Ne);for (i = 0; i < Ne; i++) {scanf("%d %d %d", &v1, &v2, &w);/* InsertEdge*/G[v1][v2] = w;G[v2][v1] = w;}
}

P7.1 最短路径问题

  • 最短路径(Shortest Path)
  • 源点(Source)
  • 终点(Destination)

无权图的单源最短路算法

/* 无权图的单源最短路算法*/
void Unweighted(Vertex S) {Enqueue(S, Q);while (!IsEmpty(Q)) {V = Dequeue(Q);for(V的每个邻接点W)if (dist[W] == -1) {//没被访问过dist[W] = dist[V] + 1;path[W] = V;Enqueue(W, Q);}}
}// T = O(|V| + |E|)
/*  邻链表存储 - 无权图的单源最短路算法*/
/* dist[] 和 path[] 全部初始化为-1  */void Unweighted(LGraph Graph, int dist[], int path[], Vertex S) {Queue Q;Vertex V;PtrToAdjVNode W;Q = CreateQueue(Graph->Nv);   /* 创建空队列, MaxSize为外部定义的常数*/dist[S] = 0;  /* 初始化源点*/AddQ(Q, S);while (!IsEmpty(Q)) {V = DeleteQ(Q);for(W = Graph-> G[V].FirstEdge; W; W = W-> W->Next)/* 对V的每个邻接点W->AdjV*/if (dist[W->AdjV] == -1) {//若W->AdjV未被访问过dist[W->AdjV] = dist[V] + 1;  /* W->AdjV 到S的距离更新*/path[W->AdjV] = V;     /* 将V记录在S到W->AdjV 的路径上*/AddQ(Q, W->AdjV);}}
}

有权图的单源最短路算法

  • Dijkstra算法
/* 有权图的单源最短路算法 */
void Dijkstra(Vertex S) {while (1) {V = 未收录顶点中的dist最小者;if (这样的V不存在)break;collected[V] = true;for(V的每个邻接点 W)if(collected[W] == false)if (dist[V] + E<v, w> < dist[W]) {dist[W] = dist[V] + E <V, W>;path[W] = V;}}
}
/*邻接矩阵存储 - 有权图的单源最短路算法 */Vertex FindMinDist(MGraph Graph, int dist[], int collected[]) {/* 返回未被收录顶点中dist最小者*/Vertex MinV, V;int MinDist = INFINITY;for (V = 0; V < Graph->Nv; V++) {if (collected[V] == false && dist[V] < MinDist) {/* 若V未被收录, 且dist[V]更小*/MinDist = dist[V]; /* 更新最小距离*/MinV = V;      /* 更新对应顶点*/}}if (MinDist < INFINITY) /* 若找到最小dist*/return MinV;       /* 返回对应顶点下标*/else   return ERROR;    /* 若这样的顶点不存在,返回错误标记*/
}bool Dijkstra(MGraph Graph, int dist[], int path[], Vertex S) {int collected[MaxVertexNum];Vertex V, W;/* 初始化: 此处默认邻接矩阵中不存在的边用INFINITY表示*/for (V = 0; V < Graph->Nv; V++) {dist[V] = Graph->G[S][V];if (dist[V] < INFINITY)path[V] = S;elsepath[V] = -1;collected[V] = false;}/* 先将起点收入集合*/dist[S] = 0;collected[S] = true;while (1) {/* V = 未被收录顶点中的dist最小者*/V = FindMinDist(Graph, dist, collected);if (V == ERROR)    /*这样的V不存在*/break;collected[V] = true;  /* 收录V*/for(W=0; W< Graph->Nv; W++)  /* 对图中的每个顶点W *//* 若W是V的邻接点并且未被收录*/if (collected[W] == false && Graph->G[V][W] < INFINITY) {if (Graph->G[V][W] < 0) /* 若有负边*/return false;     /* 不能正确解决, 返回错误标记*//* 若收录V使得dist[W]变小 */if (dist[V] + Graph-> G[V][W] < dist[W]) {dist[W] = dist[V] + Graph-> G[V][W];   /* 更新dist[W]*/path[W] = V;  /* 更新S到W的路径*/}}               }return true;
}

多源最短路算法

  • Floyd算法

/* 多源最短路径算法*/void Floyd() {for(i=0;i<N;i++)for (j = 0; j < N; j++) {D[i][j] = G[i][j];path[i][j] = -1;}for(k=0;k<N;k++)for(i=0;i<N;i++)for(j=0;j<N;j++)if (D[i][k] + D[k][j] < D[i][j]) {D[i][j] = D[i][k] + D[k][j];path[i][j] = k;}
}
// T = O(|V|^3)
/* 邻接矩阵存储 - 多源最短路径算法*/bool Floyd(MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum]) {Vertex i, j, k;/* 初始化*/for(i=0;i<Graph-> Nv;i++)for (j = 0; j < Graph-> Nv; j++) {D[i][j] = Graph->G[i][j];path[i][j] = -1;}for(k=0;k<Graph->Nv;k++)for(i=0;i<Graph-> Nv;i++)for(j=0;j<Graph->Nv;j++)if (D[i][k] + D[k][j] < D[i][j]) {D[i][j] = D[i][k] + D[k][j];if (i == j && D[i][j] < 0)  /* 若发现负值圈*/return false;path[i][j] = k;}return true;
}

例:哈利·波特的考试

  • 带哪个动物?
  • 找出所有最远路径, 带里面最小的那个。
/* 选择动物  */
void FindAnimal(MGraph Graph) {WeightType D[MaxVertexNum][MaxVertexNum], MaxDist, MinDist;Vertex Animal, i;Floyd(Graph, D);MinDist = INFINITY;for (i = 0; i < Graph->Nv; i++) {MaxDist = FindMaxDist(D, i, Graph->Nv);if (MaxDist == INFINITY) {// 说明有从i无法变出的动物printf("0\n");return;}if (MinDist > MaxDist) {//找到最长距离更小的动物MinDist = MaxDist; Animal = i + 1;  /* 更新距离,记录编号*/}}printf("%d %d\n", Animal, MinDist);
}weightType FindMaxDist(weightType D[][MaxVertexNum], Vertex i, int N) {weightType MaxDist;Vertex j;MaxDist = 0;for (j = 0; j < N; j++) {// 找到i到其他动物j的最长距离if (i != j && D[i][j] > MaxDist) {MaxDist = D[i][j];}}return MaxDist;
}
#define MaxVertexNum 100     /* 最大顶点数设为100*/
#define INFINITY  655535        /* ∞设为双字节无符号整数的最大值65535*/
typedef int Vertex;            /* 用顶点下标表示顶点,为整型*/
typedef int WeightType;    /*  边的权值设为整型*//* 边的定义*/
typedef struct ENode* PtrToENode;
struct ENode {Vertex V1, V2;     /* 有向边<V1, V2>*/WeightType weight;    /* 权重*/
};
typedef PtrToENode Edge;/*  图结点的定义 */
typedef struct GNode* PtrToGNode;
struct GNode {int Nv;    /*  顶点数*/int Ne;    /* 边数*/WeightType   G[MaxVertexNum][MaxVertexNum];   /* 邻接矩阵*/
};
typedef  PtrToGNode MGraph;  //  以邻接矩阵存储的图类型
MGRaph CreateGraph(int VertexNum) {/* 初始化一个有VertexNum个顶点但没有边的图*/Vertex V, W;MGraph Graph;Graph = (MGraph)malloc(sizeof(struct GNode));   /* 建立图*/Graph->Nv = VertexNum;Graph->Ne = 0;/* 初始化邻接矩阵*//* 这里默认顶点编号从0开始,到 (Graph->Nv - 1 )*/for (V = 0; V < Graph->Nv; V++) {for (W = 0; W < Graph->Nv; W++) {Graph->G[V][W] = INFINITY;}}return Graph;
}void InsertEdge(MGraph Graph, Edge E) {/* 插入边 <V1, V2>*/Graph->G[E->V1][E->V2] = E->weight;/* 若是无向图,还要插入边<V2, V1>*/Graph->G[E->V2][E->V1] = E->weight;
}
MGraph BuildGraph() {MGraph Graph;Edge E;int Nv, i;scanf("%d", &Nv);    /* 读入顶点个数*/Graph = CreateGraph(Nv);   /* 初始化有Nv个顶点但没有边的图 */scanf("%d", &(Graph->Ne));    /* 读入边数*/if (Graph->Ne != 0) {//如果有边E = (Edge)malloc(sizeof(struct ENode)); /* 建立边结点*//* 读入边, 格式为"起点 终点 权重", 插入邻接矩阵 */for(i=0; i< Graph->Ne;i++){scanf("%d %d %d", &E->V1, &E->V2, &E->weight);E->V1--; E->V2--;    /* 起始编号从0开始*/InsertEdge(Graph, E);}}return Graph;
}
void Floyd(MGraph Graph, WeightType D[][MaxVertexNum]) {Vertex i, j, k;/* 初始化 */for (i = 0; i < Graph->Nv; i++)for (j = 0; j < Graph->Nv; j++) {D[i][j] = Graph->G[i][j];}for (k = 0; k < Graph->Nv; k++) for(i=0;i<Graph->Nv; i++)for (j = 0; j < Graph->Nv; j++) {D[i][j] = D[i][k] + D[k][j];}
}

P8.1 最小生成树问题

  • Minimum Spanning Tree
  • 向生成树中任加一条边都一定构成回路
  • 最小生成树存在 <—> 图连通

贪心算法

/* Prim算法: 让一棵小树长大 */
/*  顶点到已生成树的最小距离 */
/*  适用于 稠密图    T = O(|V| ^2)*//* 邻接矩阵存储 - Prim 最小生成树法 */Vertex FindMinDist(MGraph Graph, WeightType dist[]) {/* 返回未被收录顶点中dist最小者 */Vertex MinV, V;WeightType MInDist = INFINITY;for (V = 0; V < Graph->Nv; V++){if (dist[V] != 0 && dist[V] < MinDist) {/* 若 V未被收录,且dist[V] 更小*/MinDist = dist[V];   /* 更新最小距离*/MinV = V;   /* 更新对应顶点*/}}if (MinDist < INFINITY) /* 若找到最小dist*/return MinV;    /* 返回赌赢的顶点下标*/else   return ERROR;    /* 若这样的顶点不存在,返回-1作为标记*/
}int Prim(MGraph Graph, LGraph MST) {/* 将最小生成树保存为邻接表存储的图MST, 返回最小权重和*/WeightType dist[MaxVertexNum], TotalWeight;Vertex parent[MaxVertexNum], V, W;int VCount;Edge E;/* 初始化, 默认初始点下标是0*/for (V = 0; V < Graph->Nv; V++) {/* 这里假设若V到W没有直接的边, 则Graph-> G[V][W]定义为INFINITY*/dist[V] = Graph->G[0][V];parent[V] = 0;     /* 暂且定义所有顶点的父结点都是初始点0*/}TotalWeight = 0;  /* 初始化权重和 */VCount = 0;        /* 初始化收录的顶点数*//*  创建包含所有顶点但没有边的图,  用邻接表版本*/MST = CreateGraph(Graph->Nv);E = (Edge)malloc(sizeof(struct ENode));    /* 建立空的边结点*//*  将初始点0收录进MST*/dist[0] = 0;VCount++;parent[0] = -1;    /* 当前树根是 0*/while (1) {V = FindMinDist(Graph, dist);/* V= 未被收录顶点中dist最小者*/if (V == ERROR)  /* 若这样的点不存在*/break;                 /* 算法结束 *//* 将V及相应的边<parent[V], V>收录进MST*/E->V1 = parent[V];E->V2 = V;E->Weight = dist[V];InsertEdge(MST, E);TotalWeight += dist[V];dist[V] = 0;VCount++;for (W = 0; W < Graph->Nv; W++) {/* 对图中的每个顶点*/if (dist[W] != 0 && Graph->G[V][W] < INFINITY) {/* 若W是V的邻接点并且未被收录 */if (Graph->G[V][W] < dist[W]) {/* 若收录V使dist[]变小*/dist[W] = Graph->G[V][W];  /* 更新 dist[W] */parent[W] = V;    /* 更新树*/}}}}if (VCount < Graph->Nv)   /*  MST中收录的顶点不到|V|个*/TotalWeight = ERROR;return TotalWeight;    /* 算法执行完毕, 返回最小权重和  或  错误标记*/
}

/*  KrusKal算法:  将森林合并成树*/
/* 邻接表存储 - Kruskal最小生成树算法 *//*   ------------ 顶点并查集定义 ------------ */
typedef Vertex ElementType;    /* 默认元素可以用非负数表示*/
typedef Vertex SetName;        /* 默认用根结点的下标作为集合名称 */
typedef ElementType SetType[MaxVertexNum];    /* 假设集合元素下标从0开始*/void InitializeVSet(SetType S, int N) {/* 初始化并查集 */ElementType X;for (X = 0; X < N; X++)   S[X] = -1;
}void Union(SetType S, SetName Root1, SetName Root2) {/* 这里默认Root1和Root2是不同集合的根结点*//* 保证小集合并入大集合*/if (S[Root2] < S[Root1]) {/* 如果集合2比较大 */S[Root2] += S[Root1];    /* 集合1并入集合2*/S[Root1] = Root2;}else {    /* 如果集合1比较大 */S[Root1] += S[Root2];    /* 集合2并入集合1*/S[Root2] = Root1;}
}SetName Find(SetType S, ElementType X) {/* 默认集合元素全部初始化为-1*/if (S[X] < 0)   /* 找到集合的根*/return X;elsereturn S[X] = Find(S, S[X]);  /* 路径压缩*/
}bool CheckCycle(SetType VSet, Vertex V1, Vertex V2) {/* 检查连接V1和V2的边是否在现有的最小生成子树中构成回路*/Vertex Root1, Root2;Root1 = Find(VSet, V1); /* 得到V1所属的连通集名称*/Root2 = Find(VSet, V2); /* 得到V2所属的连通集名称*/if (Root1 == Root2) /* 若V1 和V2已经连通, 则该边不能要*/return false;else {/* 否则该边可以被收集, 同时将V1和V2并入同一连通集*/Union(VSet, Root1, Root2);return true;}
}/* ------------------ 边的最小堆定义 ---------------------------*/
void PercDown(Edge ESet, int p, int N) {/* 将N个元素的边数组中ESet[p]为根的子堆调整为关于Weight的最小堆 */int Parent, Child;struct ENode X;X = ESet[p];  /* 取出根结点存放的值 */for (Parent = p; (Parent * 2 + 1) < N; Parent = Child) {Child = Parent * 2 + 1;if ((Child != N - 1) && ESet[Child].Weight > ESet[Child + 1].Weight)Child++;                  /* Child指向左右子结点中的较小点*/else    /* 下滤X */ESet[Parent] = ESet[Child];}ESet[Parent] = X;
}void InitializeESet(LGraph Graph, Edge ESet) {/* 将图的边存入数组ESet, 并且初始化为最小堆 */Vertex V;PtrToAdjVNode W;int ECount;/* 将图的边存入数组ESet*/ECount = 0;for (V = 0; V->Graph->Nv; V++) {for (W = Graph->G[V].FirstEdge; W: W = W->Next) {if (V < W->AdjV) {/* 避免重复录入无向图的边,只收V1 < V2的边*/ESet[ECount].V1 = V;ESet[ECount].V2 = W->AdjV;ESet[ECount++].Weight = W->Weight;}}}/*  初始化为最小堆 */for (ECount = Graph->Ne / 2; ECount >= 0; ECount--)PercDown(ESet, ECount, Graph->Ne);
}int GetEdge(Edge ESet, int CurrentSize) {/* 给定当前堆的大小CurrentSize, 将当前最小边位置弹出并调整堆*//* 将最小边与当前堆的最后一个位置的边交换 */Swap(&ESet[0], &ESet[CurrentSize - 1]);/* 将剩下的边继续调整成最小堆*/PercDown(ESet, 0, CurrentSize - 1);return CurrentSize - 1;   /* 返回最小边所在位置 */
}/*   */
int Kruskal(LGraph Graph, LGraph MST) {/* 将最小生成树保存为邻接表存储的图MST, 返回最小权重和*/WeightType TotalWeight;int ECount, NextEdge;SetType VSet; /* 顶点数据*/Edge ESet;  /* 边数组*/InitializeVSet(VSet, Graph->Nv);     /* 初始化 顶点 并查集*/ESet = (Edge)malloc(sizeof(struct ENode) * Graph->Ne);InitializeESet(Graph, ESet);  /*  初始化  边 的 最小堆 *//* 创建包含所有顶点但没有边的图,  使用邻接表*/MST = CreateGraph(Graph->Nv);TotalWeight = 0;      /*  初始化 权重和*/ECount = 0;       /* 初始化  收录的边数*/NextEdge = Graph->Ne;     /* 原始边 集 的规模 */while (ECount < Graph->Nv - 1) {/* 当收集的边不足以构成树时*/NextEdge = GetEdge(ESet, NextEdge);   /* 从边集 中得到最小边的位置*/if (NextEdge < 0)/* 边集已空*/break;/* 如果该边的加入不构成回路, 即两端结点不属于同一连通集 */if (CheckCycle(VSet, ESet[NextEdge].V1, ESet[NextEdge].V2) == true) {/* 将该边插入MST*/InsertEdge(MST, ESet + NextEdge);TotalWeight += ESet[NextEdge].Weight;   /* 累积权重*/ECount++;}}if (ECount < Graph->Nv - 1)TotalWeight = -1;      /* 设置错误标记,表示生成树不存在*/return TotalWeight;
}

P8.2 拓扑排序

  • AOV(Activity On Vertex)网络
  • 有向无环图(Directed Acyclic Graph ,DAG)
  • 输出没有前结点的结点(入度为0的点)



关键路径问题

  • AOE(Activity On Edge)
  • 由绝对不允许延误的活动组成的路径。
/*邻接表存储 -  拓扑排序 */bool TopSort(LGraph Graph, Vertex TopOrder[]) {/* 对Graph进行拓扑排序, TopOrder[]顺序存储排序后的顶点下标*/int Indegree[MaxVertexNum], cnt;Vertex V;PtrToAdjVNode W;Queue Q = CreateQueue(Graph->Nv);/*  初始化 Indegree[] */for (V = 0; V < Graph->Nv; V++) {Indegree[V] = 0;}/* 遍历图, 得到Indegree[] */for (V = 0; V < Graph->Nv; V++)for (W = Graph->G[V].FirstEdge; W: W = W->Next)Indegree[W->AdjV]++;    /* 对有向边<V, W-> AdjV>累计终点的入度*//* 将所有入度为0的顶点入列 */for (V = 0; V < Graph->Nv; V++) {if (Indegree[V] == 0)AddQ(Q, V);}/* 拓扑排序 */cnt = 0;while (!IsEmpty(Q)) {V = DeleteQ(Q);    /* 弹出一个入度为0的顶点*/TopOrder[cnt++] = V;    /* 将之存为结果序列的下一个元素 *//* 对V的每个邻接点W->AdjV */for (W = Graph->G[V].FirstEdge; W: W = W->Next)if (--Indegree[W->AdjV] == 0) /* 若删除V使得W->AdjV入度为0*/AddQ(Q, W->AdjV);    /* 则该顶点入列*/}if (cnt != Graph->Nv)return false;   /*  说明图中有回路,返回不成功标志*/elsereturn true;   }

题:旅游规划

类似问题:
1、最短路径 条数
2、边数最少的最短路

P9、排序

1、冒泡排序

  • 标记: 如果全程无交换,说明已经有序了,不用再进行后面的操作。

  • 大的时候才交换,保证稳定性。
/* 冒泡排序
* 最好情况: 顺序T = O(N)
* 最坏情况: 逆序T = O(N^2)
*/void Bubble_sort(ElementType A[], int N) {for (P = N - 1; P >= 0; P--) {flag = 0;for (i = 0; i < P; i++) {/* 一趟冒泡 */if (A[i] > A[i + 1]) {Swap(A[i], A[i + 1]);   /* 保证稳定性*/flag = 1;   /* 标识发生了变化*/}}if (flag == 0)   break;    /* 全程无交换*/}
}

2、插入排序

  • 抓牌
/* 插入排序(类似摸牌)*/
/*
* 最好情况: 顺序T = O(N)
* 最坏情况: 逆序T  = O(N ^ 2)
*/
void Insertion_Sort(ElementType A[], int N) {int P, i;ElementType Tmp;for (P = 1; P < N; P++) {Tmp = A[P]; /* 摸下一张牌 */for (i = P; i > 0 && A[i - 1] > Tmp; i++)A[i] = A[i - 1];     /* 移出空位*/A[i] = Tmp;   /* 新牌落位*/}
}
  • 交换2个相邻元素正好消去1个逆序对

3、希尔排序

1、定义增量序列 5-间隔,3-间隔, 1-间隔

  • 相邻元素互质

/* 希尔排序 */
void Shell_Sort(ElementType A[], int N) {for (D = N / 2; D > 0; D /= 2) { /* 希尔增量序列 */for (P = D; P < N; P++) {/* 插入排序*/Tmp = A[P];for (i = P; i >= D && A[i - D] > Tmp; i -= D)A[i] = A[i - D];A[i] = Tmp;}}
}
/* 希尔排序——附录代码 */
void Shell_Sort(ElementType A[], int N) {/* 希尔排序 - 用Sedgewick增量序列 */int Si, D, P, i;ElementType Tmp;int Sedgewick[] = { 929, 505,209, 109, 41, 19, 5, 1, 0 };for (D = Sedgewick[Si]; D > 0; D = Sedgewick[++Si]) { /* 初始的增量Sedgewick[Si]不能超过待排序列长度 */for (P = D; P < N; P++) {/* 插入排序*/Tmp = A[P];for (i = P; i >= D && A[i - D] > Tmp; i -= D)A[i] = A[i - D];A[i] = Tmp;}}
}

4、选择排序

5、堆排序

/* 堆排序 */
void Swap(ElementType* a, ElementType* b) {ElementType t = *a, * a = *b; *b = t;
}void PercDown(ElementType A[], int p, int N) {/* 将N个元素的数组中以A[p]为根的子堆调整为最大堆 */int Parent, Child;ElementType X;X = A[p];   /* 取出根结点存放的值 */for (Parent = p; (Parent * 2 + 1) < N; Parent = Child) {Child = Parent * 2 + 1;if ((Child != N - 1) && (A[Child] < A[Child + 1]))Child++;    /* Child指向左右结点的较大者 */if (X >= A[Child])  break;     /* 找到了合适位置*/else   /* 下滤X*/A[Parent] = A[Child];A[Parent] = X;}
}void HeapSort(ElementType A[], int N) {/* 堆排序 */int i;for (i = N / 2 - 1; i >= 0; i--)/* 建立最大堆*/PercDown(A, i, N);for (i = N - 1; i > 0; i--) {/* 删除最大堆顶*/Swap(&A[0], &A[i]);   PercDown(A, 0, i);}
}

6、归并排序

  • 稳定, 但需要额外空间
  • 适用于外排序
  • 时间复杂度 T(N) = O(N)
/* 有序子列的归并 */
/* L = 左边的起始位置, R = 右边起始位置, RightEnd = 右边终点位置 */void Merge(ElementType A[], ElementType TmpA[], int L, int R, int RightEnd) {LeftEnd = R - 1;    /* 左边终点位置。假设左右两列挨着 */Tmp = L;   /* 存放结果的数组  的初始位置 */NumElements = RightEnd - L + 1;        /* 计算元素 数量*/while (L <= LeftEnd && R <= RightEnd) {if (A[L] <= A[R])    TmpA[Tmp++] = A[L++];   //左边的序列小, else                        TmpA[Tmp++] = A[R++];}while (L <= LeftEnd)     /* 直接复制左边剩下的*/TmpA[Tmp++] = A[L++];while (R <= RightEnd)  /* 直接复制右边剩下的 */TmpA[Tmp++] = A[R++];/* 将排序好的临时数组的数据  复制到原数组里 */for (i = 0; i < NumElements; i++; RightEnd--)A[RightEnd] = TmpA[RightEnd];
}
/* 归并排序 */
/*  递归算法  分而治之 */void MSort(ElementType A[], ElementType TmpA[], int L, int RightEnd) {int Center;if (L < RightEnd) {Center = (L + RightEnd) / 2;MSort(A, TmpA, L, Center);MSort(A, TmpA, Center + 1, RightEnd);Merge(A, TmpA, L, Center + 1, RightEnd);}
}/*  T(N)= O(NlogN )*//*  统一函数接口 */
void Merge_sort(ElementType A[], int N) {ElementType* TmpA;TmpA = malloc(N * sizeof(ElementType));if (TmpA != NULL) {MSort(A, TmpA, 0, N - 1);free(TmpA);}else  Error("空间不足 ");
}
/*归并算法:  非递归算法  */
/* 空间复杂度:  O(N)*/
void Merge_pass(ElementType A[], ElementType TmpA[], int N, int length)/*length=当前有序子列的长度*/
{for (i = 0; i <= N - 2 * length; i += 2 * length)Merge1(A, TmpA, i, i + length, i + 2 * length - 1);if (i + length < N) /* 归并最后2个子列 */Merge1(A, TmpA, i, i + length, N - 1);else  /* 最后只剩下1个子列 */for (j = i; j < N; j++)   TmpA[j] = A[j];
}void Merge_sort(ElementType A[], int N) {ElementType* TmpA;TmpA = malloc(N * sizeof(ElementType));if (TmpA != NULL) {while (length < N) {Merge_pass(A, TmpA, N, length);length *= 2;Merge_pass(TmpA, A, N, length);length *= 2;}free(TmpA);}else   Error("空间不足 ");
}/* 稳定 */

附录代码

/* 归并排序  - 递归实现  *//* L = 左边的起始位置, R = 右边起始位置, RightEnd = 右边终点位置 */void Merge(ElementType A[], ElementType TmpA[], int L, int R, int RightEnd) {/* 将有序的A[L]~A[R-1]和A[R]~A[RightEnd] 归并成一个有序序列 */int LeftEnd, NumElements, Tmp;int i;LeftEnd = R - 1;    /* 左边终点位置。假设左右两列挨着 */Tmp = L;   /* 存放结果的数组  的初始位置 */NumElements = RightEnd - L + 1;        /* 计算元素 数量*/while (L <= LeftEnd && R <= RightEnd) {if (A[L] <= A[R])    TmpA[Tmp++] = A[L++];   //左边序列的元素小,将左边元素复制到TmpA else                        TmpA[Tmp++] = A[R++];}while (L <= LeftEnd)     /* 直接复制左边剩下的*/TmpA[Tmp++] = A[L++];while (R <= RightEnd)  /* 直接复制右边剩下的 */TmpA[Tmp++] = A[R++];/* 将排序好的临时数组的数据  复制到原数组里 */for (i = 0; i < NumElements; i++; RightEnd--)A[RightEnd] = TmpA[RightEnd];
}void MSort(ElementType A[], ElementType TmpA[], int L, int RightEnd) {/* 核心 递归排序函数 */int Center;if (L < RightEnd) {Center = (L + RightEnd) / 2;MSort(A, TmpA, L, Center);   /*  递归解决左边 */MSort(A, TmpA, Center + 1, RightEnd);  /*  递归解决右边*/Merge(A, TmpA, L, Center + 1, RightEnd);  /*  合并两段有序序列 */}
}void MergeSort(ElementType A[], int N) {/* 归并排序 */ElementType* TmpA;TmpA = (ElementType*)malloc(N * sizeof(ElementType));if (TmpA != NULL) {Msort(A, TmpA, 0, N - 1);free(TmpA);}else printf("  空间不足 ")
}
/*归并排序:  循环实现 */
/* Merge函数同递归版本*/
void Merge_pass(ElementType A[], ElementType TmpA[], int N, int length)/*length=当前有序子列的长度*/
{/* 两两归并相邻有序子列 */int i, j;for (i = 0; i <= N - 2 * length; i += 2 * length)Merge1(A, TmpA, i, i + length, i + 2 * length - 1);if (i + length < N) /* 归并最后2个子列 */Merge1(A, TmpA, i, i + length, N - 1);else  /* 最后只剩下1个子列 */for (j = i; j < N; j++)   TmpA[j] = A[j];
}void Merge_sort(ElementType A[], int N) {int length=1;  /*  初始化子序列长度 */ElementType* TmpA;TmpA = malloc(N * sizeof(ElementType));if (TmpA != NULL) {while (length < N) {Merge_pass(A, TmpA, N, length);length *= 2;Merge_pass(TmpA, A, N, length);length *= 2;}free(TmpA);}else   Error("空间不足 ");
}

7、快速排序

  • 分而治之
  • 大规模 用递归
  • 小规模 用插入排序

快排最好情况:每次正好中分, T(N) = O(NlogN)
T(N) = O(N^2)

/* ------------- 快速排序 ---------------------*//* 选主元*/
ElementType Median3(ElementType A[], int Left, int Right) {int Center = (Left + Right) / 2;if (A[Left] > A[Center])Swap(&A[Left], &A[Center]);if (A[Left] > A[Right])Swap(&A[Left] , &A[Right])if (A[Center] > A[Right])Swap(&A[Center], &A[Right]);/*  A[Left] <= A[Center] <= A[Right]*/Swap(&A[Center], &A[Right - 1]);      /* 将pivot藏到右边 *//* 只要考虑 A[Left + 1]... A[Right - 2]*/return A[Right - 1];    /* 返回 pivot */
}void Quicksort(ElementType A[], int Left, int Right) {if (Cutoff <= Right - Left) { /* 大规模用快排比较合适*/Pivot = Median3(A, Left, Right);i = Left; j = Right - 1;for (;;) {while (A[++i] < Pivot) {}while (A[--j] > Pivot) {}if (i < j)Swap(&A[i], &A[j]);else  break;}Swap(&A[i], &A[Right - 1]);Quicksort(A, Left, i - 1);Quicksort(A, i + 1, Right);}elseInsertion_Sort(A + Left, Right - Left + 1);  /* 小规模用插入排序比较合适*/
}/* 接口 */
void Quick_Sort(ElementType A[], int N) {Quicksort(A, 0, N - 1);
}

附录代码

/* ------------- 快速排序 ---------------------*//* 选主元*/
ElementType Median3(ElementType A[], int Left, int Right) {int Center = (Left + Right) / 2;if (A[Left] > A[Center])Swap(&A[Left], &A[Center]);if (A[Left] > A[Right])Swap(&A[Left] , &A[Right])if (A[Center] > A[Right])Swap(&A[Center], &A[Right]);/*  A[Left] <= A[Center] <= A[Right]*/Swap(&A[Center], &A[Right - 1]);      /* 将基准pivot藏到右边 *//* 只要考虑 A[Left + 1]... A[Right - 2]*/return A[Right - 1];    /* 返回基准 pivot */
}void Quicksort(ElementType A[], int Left, int Right) {/* 核心递归函数*/int Pivot, Cutoff, Low, High;if (Cutoff <= Right - Left) { /* 大规模用快排比较合适*/ /* 序列元素充分多,进入快排*/Pivot = Median3(A, Left, Right);   /* 选基准*/Low = Left;   High= Right - 1;while(1) { /*  将序列中比基准小的移到基准左边,大的移到右边*/while (A[++Low] < Pivot); // 左边比基准小的,不用管while (A[--High] > Pivot);if (Low< High)Swap(&A[Low], &A[High]);else  break;}Swap(&A[Low], &A[Right - 1]);  // 将基准换到正确的位置Quicksort(A, Left, Low - 1);   /* 递归解决左边 */Quicksort(A, Low + 1, Right);    /* 递归解决右边 */}elseInsertion_Sort(A + Left, Right - Left + 1);  /* 小规模用插入排序比较合适*//* 元素太少, 用简单排序*/
}/* 统一接口 */
void Quick_Sort(ElementType A[], int N) {Quicksort(A, 0, N - 1);
}

/* 快速排序 - 直接调用库函数 */#include<stdlib.h>/* ---------------  简单整数排序 ---------------------*/
int compare(const void* a, const void* b) {/* 比较两整数, 非降序排列 */return (*(int*)a - *(int*)b);
}/* 调用接口 */
qsort(A, N, sizeof(int), compare);/* --------- 一般情况下,对结构体Node中的某健值key排序-------------*/
struct Node {int key1, key2;
}A[MAXN];int compare2keys(const void* a, const void* b) {/* 比较两种健值: 按key1非升序排列;如果key1相等,则按key2非降序排列*/int k;if (((const struct Node*)a)->key1 < ((const struct Node*)b)->key1)k = 1;else if (((const struct Node*)a)->key1 > ((const struct Node*)b)->key1)k = -1;else {/* 如果key1相等 */if (((const struct Node*)a)->key2 < ((const struct Node*)b)->key2)k = -1;elsek = 1;}
}/*  调用接口 */
qsort(A, N, sizeof(struct Node), compare2keys);

8、表排序

  • table sort
  • table(指针数组)

9、(桶排序->基数排序)

桶排序

若数据很多, 桶排序不划算

  • 次位优先 (Least Significant Digit)
    T = O(P(N + B))
  • 稳定
    附录代码
/* 基数排序 - 次位优先 *//* 假设元素最多有MaxDigit个关键字, 基数全是同样的Radix*/
#define MaxDigit 4
#define Radix 10/* 桶元素结点 */
typedef struct Node* PtrToNode;
struct Node {int key;PtrToNode next;
};/* 桶头结点 */
struct HeadNode {PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];int GetDigit(int X, int D) {/* 默认次位D=1, 主位D <= MaxDigit*/int d, i;for (i = 1; i <= D; i++) {d = X % Radix;X /= Radix;}return d;
}void LSDRadixSort(ElementType A[], int N) {/* 基数排序 - 次位优先 */int D, Di, i;Bucket B;PtrToNode tmp, p, List = NULL;for (i = 0; i < Radix; i++)/* 初始化每个桶为空链表 */B[i].head = B[i].tail = NULL;for (i = 0; i < N; i++) {/* 将原始序列逆序 存入 初始链表List */tmp = (PtrToNode)malloc(sizeof(struct Node));tmp->key = A[i];tmp->next = List;List = tmp;}/* 下面开始排序 */for (D = 1; D <= MaxDigit; D++) {/* 对数据的每一位循环处理 *//* 下面是分配的过程 */p = List;while (p) {Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 *//* 从 List中摘除 */tmp = p; p = p->next;/* 插入B[Di] 号桶尾*/tmp->next = NULL;if (B[Di].head == NULL)B[Di].head = B[Di].tail = tmp;else {B[Di].tail->next = tmp;B[Di].tail = tmp;}}/* 收集 */List = NULL;for (Di = Radix - 1; Di >= 0; Di--) {/* 将每个桶的元素顺序收集入List */if (B[Di].head) {/* 如果桶不为空 *//* 整桶插入List表头 */B[Di].tail->next = List;List = B[Di].head;B[Di].head = B[Di].tail = NULL;    /* 清空桶 */}}}/* 将List倒入A[] 并释放空间 */for (i = 0; i < N; i++) {tmp = List;List = List->next;A[i] = tmp->key;free(tmp);}
}
/* 基数排序 - 主位优先 *//* 假设元素最多有MaxDigit个关键字, 基数全是同样的Radix*/
#define MaxDigit 4
#define Radix 10/* 桶元素结点 */
typedef struct Node* PtrToNode;
struct Node {int key;PtrToNode next;
};/* 桶头结点 */
struct HeadNode {PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];int GetDigit(int X, int D) {/* 默认次位D=1, 主位D <= MaxDigit*/int d, i;for (i = 1; i <= D; i++) {d = X % Radix;X /= Radix;}return d;
}void MSD(ElementType A[], int L, int R, int D) {/* 核心递归函数: 对A[L]..A[R]的第D位数进行排序 */int Di, i, j;Bucket B;PtrToNode tmp, p, List = NULL;if (D == 0) return;    /* 递归终止条件 */for (i = 0; i < Radix; i++)/* 初始化每个桶为空链表 */B[i].head = B[i].tail = NULL;for (i = L; i < R; i++) {/* 将原始序列逆序 存入 初始链表List */tmp = (PtrToNode)malloc(sizeof(struct Node));tmp->key = A[i];tmp->next = List;List = tmp;}/* 下面是分配的过程 */p = List;while (p) {Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 *//* 从 List中摘除 */tmp = p; p = p->next;/* 插入B[Di] 号桶尾*/if (B[Di].head == NULL)  B[Di].tail = tmp;tmp->next = B[Di].head;B[Di].head = tmp;}/* 收集 */i = j = L;   /* i, j 记录当前要处理的A[]的左右端下标 */for (Di = 0; Di < Radix; Di++) {/* 对于每个桶 */if (B[Di].head) {/* 将非空的桶整桶倒入A[], 递归排序 */p = B[Di].head;while (p) {tmp = p;p = p->next;A[j++] = tmp->key;free(tmp);}/* 递归对该桶数据排序, 位数减1*/MSD(A, i, j - 1, D - 1);i = j;     /* 为下一个桶对应的A[]左端 */}}}
/*  统一接口*/
void MSDRadixSort(ElementType A[], int N) {MSD(A, 0, N - 1, MaxDigit);
}

!!!排序综合比较

习题: 简单插入 VS 非递归的归并排序

插入排序: 前面有序, 后面无变化
归并排序:分段有序

最小N为4

P11、散列查找

插入:新变量定义
查找:变量的引用

散列表(哈希表)

数字关键字的散列函数构造

  • 1、 直接定址法: h(key) = a× key + b
  • 2、除留余数法: h(key) = key mod p
    (p一般取素数)
  • 3、数字分析法: h(key) = atoi(key + 7)(char *key)
  • 4、折叠法
  • 5、平方取中法
    方法4,5是希望最终结果由更多位数决定。

字符关键词的散列函数构造
1、ASCII码加和法
2、前3个字符移位法
3、移位法

Index Hash(const char* Key, int TableSize) {unsigned int h = 0;  /* 散列函数值, 初始化为0*/while (*Key != '\0') /* 位移映射*/h = (h << 5) + *Key++;return h % TableSize;
}

P11.3 冲突处理方法

1、换个位置:开放地址法
2、统一位置的冲突对象组织在一起:链地址法

开放地址法(Open Addressing)

(1) 线性探测(Linear Probing) 聚集

(2) 平方探测法(Quadratic Probing) 二次探测

  • 查找时可能存在循环跳转现象
/* 散列表:   平方探测法 */
typedef struct HashTbl* HashTable;struct HashTbl {int TableSize;Cell* TheCells;  /*  数组 */
}H;HashTable InitializeTable(int TableSize) {HashTable H;int i;if (TableSize < MinTableSize) { /* 数据太少, 不必用散列表*/Error("散列表太小");return NULL;}/* 分配散列表 */H = (HashTable)malloc(sizeof(struct HashTbl));if (H == NULL)FatalError("空间溢出");H->TableSize = NextPrime(TableSize);  /* 空间大小要 素数*//* 分配散列表Cells*/H->TheCells = (Cell*)malloc(sizeof(Cell) * H->TableSize);if (H->TheCells == NULL)FatalError("空间溢出!!!");/* 对是否有元素 做标记 */for (i = 0; i < H->TableSize; i++)/* 删除的元素 要做标记  避免查找出问题  */H->TheCells[i].Info = Empty;return H;
}
/* 基本操作 */
Position Find(ElementType Key, HashTable H)  /* 平方探测*/
{Position CurrentPos, NewPos;int CNum;  /* 记录冲突次数 */CNum = 0;NewPos = CurrentPos = Hash(Key, H->TableSize);while (H->TheCells[NewPos].Info != Empty &&H->TheCells[NewPos].Element != Key) {/* 字符串类型的关键词需要 strcmp 函数*/if (++CNum % 2) {/* 判断冲突的奇偶次 */NewPos = CurrentPos + (CNum + 1) / 2 * (CNum + 1) / 2; /*偶数  加  i^2 */while (NewPos -= H->TableSize)NewPos -= H->TableSize;}else {NewPos = CurrentPos - CNum / 2 * CNum / 2; /* 奇数  减 i^2*/while (NewPos < 0)NewPos += H->TableSize;}  }return NewPos;
}void Insert(ElementType Key, HashTable H) {/* 插入操作 */Position Pos;Pos = Find(Key, H);if (H->TheCell[Pos].Info != Legitimate) {/* 确认在此插入 */H->TheCells[Pos].Info = Legitimate;H->TheCells[Pos].Element = key;/* 字符串类型的关键词 需要 strcpy 函数 */}
}

(3) 双散列探测法(Double Hashing )

(4) 再散列(Rehashing)

  • 散列表元素太多
  • 加倍扩大散列表

分离链接法(Separate Chaining)

  • 将相应位置上冲突的所有关键词存储在同一个单链表
/* 分离链接法 */struct HashTbl {int TableSize;List TheLists;
}*H;typedef struct ListNode* Position, * List;
struct ListNode {ElementType Element;Position Next;
};typedef struct HashTbl* HashTable;
struct HashTal {int TableSize;List TheLists;
};Position Find(ElementType Key, HashTable H) {Positon P;int Pos;Pos = Hash(Key, H->TableSize); /* 初始散列位置*/P = H->TheLists[Pos].Next;    /* 获得链表头*/while (P != NULL && strcmp(P->Element, Key))  /*  遍历单向链表 */P = P->Next;return P;
}

附录代码

/* 附录1:*/#define MAXTABLESIZE 100000  /* 允许开辟的最大散列表长度 */
typedef int ElementType;   /* 关键词类型用   整型*/
typedef int Index;   /* 散列地址 类型 */
typedef Index Position;   /*  数据所在位置与散列地址 是同一类型 *//* 散列单元状态类型, 分别对应: 有合法元素、空单元、有已删除元素  */
typedef enum {Legitimate, Empty, Deleted} EntryType;typedef struct HashEntry Cell;     /* 散列表单元类型 */
struct HashEntry {ElementType Data;  /* 存放元素 */EntryType Info;     /*  单元状态 */
};typedef struct TblNode* HashTable;    /* 散列表类型 */
struct TblNode {   /* 散列表结点定义 */int TableSize;    /* 表的最大长度 */Cell* Cells;      /* 存放散列单元数据的数组  */
};int NextPrime(int N) {/* 返回 大于N且不超过MAXTABLESIZE的最小素数  */int i, p = (N % 2) ? N + 2 : N + 1; /* 从大于N的下一个奇数开始*/while (p <= MAXTABLESIZE) {for (i = (int)sqrt(p); i > 2; i--)if (!(p % i))   break;      /* p不是素数 */if (i == 2)   break;     /* for正常结束, 说明p是素数 */else p += 2;    /* 否则试探下一个奇数 */}return p;
}HashTable CreateTable(int TableSize) {HashTable H;int i;H = (HashTable)malloc(sizeof(struct TblNode));/* 保证散列表最大长度是素数 */H->TableSize = NextPrime(TableSize);/* 声明 单元数组 */H->Cells = (Cell*)malloc(H->TableSize * sizeof(Cell));/* 初始化单元状态为 "空单元 " */for (i = 0; i < H->TableSize; i++)H->Cells[i].Info = Empty;return H;
}
/* 附录2  */Position Find(HashTable H, ElementType Key) {Position CurrentPos, NewPos;int CNum = 0;    /*  记录冲突次数 */NewPos = CurrentPos = Hash(Key, H->TableSize);   /* 初始 散列位置 *//*  当该位置的单元非空, 并且不是要找的元素时, 发生冲突*/while (H->Cells[NewPos].Info != Empty && H->Cells[NewPos].Data != Key) {/* 字符串类型的关键词需要 strcmp 函数 *//* 统计1次冲突 , 并判断奇偶次*/if (++CNum % 2) {/* 奇数次冲突*/NewPos = CurrentPos + (CNum + 1) * (CNum + 1) / 4;  /* 增量为 + [(CNum+1)/2]^2*/if (NewPos >= H->TableSize)NewPos = NewPos % H->TableSize;   /* 调整为合法地址*/}else {  /* 偶数次冲突 */NewPos = CurrentPos - CNum * CNum / 4;  /* 增量为-(CNum/2)^2  */while (NewPos < 0)NewPos += H->TableSize;     /* 调整为合法地址 */}}return NewPos;     /* 此时NewPos或者是 Key的位置, 或者是一个空单元的位置(表示找不到)*/
}bool Insert(HashTable H, ElementType Key) {Position Pos = Find(H, Key);    /* 先检查Key是否已经存在 */if (H->Cells[Pos].Info != Legitimate) {/* 如果这个单元没有被占,说明Key可以插入在 此*/H->Cells[Pos].Info = Legitimate;H->Cells[Pos].Data = Key;/* 字符串类型的关键词需要 strcpy 函数!*/return true;}else {printf("健值已存在 ");return false;}
}
#define KEYLENGTH 15         /* 关键词 字符串 的最大长度*/
typedef char ElementType[KEYLENGTH + 1];    /* 关键词类型用字符串 */
typedef int Index;      /* 散列地址类型 *//* ------------------- 单链表  定义-----------------------*/
typedef struct LNode* PtrToLNode;
struct LNode {ElementType Data;PtrToLNode Next;
};
typedef PtrToLNode Position;
typedef PtrToLNode List;/* 散列表 */
typedef struct TblNode* HashTable;    /* 散列表类型 */
struct TblNode {/* 散列表结点 定义*/int TableSize;   /*  表的最大长度 */List Heads;       /* 指向链表头结点的数组 */
};HashTable CreateTable(int TableSize) {HashTable H;int i;H = (HashTable)malloc(sizeof(struct TblNode));/* 保证 散列表最大长度是素数*/H->TableSize = NextPrime(TableSize);/* 分配 链表头结点  数组*/H->Heads = (List)malloc(H->TableSize * sizeof(struct LNode));/* 初始化 表头结点 */for (i = 0; i < H->TableSize; i++) {H->Heads[i].Data[0] = '\0';H-> Heads[i].Next = NULL:}return H;
}Position Find(HashTable H, ElementType Key) {Position P;Index Pos;Pos = Hash(Key, H->TableSize);   /* 初始散列位置 */P = H->Heads[Pos].Next;     /* 从该链表的第1个结点开始 *//* 当未到表尾, 并且Key未找到 */while (P && strcmp(P->Data, Key))P = P->Next;return P;     /* 此时P或者指向找到的结点, 或者为NULL*/
}bool Insert(HashTable H, ElementType Key) {Position P, NewCell;Index Pos;P = Find(H, Key);if (!P) {/* 关键词未找到, 可以插入 */NewCell = (Position)malloc(sizeof(struct LNode));strcpy(NewCell->Data, Key);Pos = Hash(Key, H->TableSize);   /* 初始散列表 位置*//* 将NewCell插入为H-> Heads[Pos]链表的第一个结点 */NewCell->Next = H->Heads[Pos].Next;H->Heads[Pos].Next = NewCell;return true;}else { /*  关键词已存在 */printf("健值已存在 ");return false;}
}void DestroyTable(HashTable H) {int i;Position P, Tmp;/* 释放每个链表的结点 */for (i = 0; i < H->TableSize; i++) {P = H->Heads[i].Next;while (P) {Tmp = P->Next;free(P);P = Tmp;}}free(H->Heads); /* 释放头结点 数组 */free(H);   /* 释放散列表  结点*/
}

散列表性能分析

影响产生冲突多少的三个元素
1、散列函数是否均匀
2、处理冲突的方法
3、散列表的装填因子


合理的最大装入因子α应该不超过0.85 。



应用:文件中单词词频统计

散列表 查找 插入

/*  文件中单词词频统计  */
int main() {int TableSize = 10000;   /* 散列表的估计大小 */int wordcount = 0, length;HashTable H;ElementType word; FILE* fp;char doucunment[30] = "HarryPotter.txt"; /* 要被统计词频的文件名*/H = InitializeTable(TableSize); /* 建立散列表 */if ((fp = fopen(document, "r")) == NULL) FatalError("无法打开文件!\n");while (!feof(fp)) {length = GetAWord(fp, word);   /* 从文件中读取一个单词 */if (length > 3) {  /* 只考虑适当长度的单词 */wordcount++;   /* 统计文件中单词总数 */InsertAndCount(word, H);  /* 插到哈希表*/}}fclose(fp);printf("该文档共出现%d个有效单词, ", wordcount);Show(H, 10.0 / 100);  /* 显示词频前10%的所有单词 */DestroyTable(H); /* 销毁散列表 */return 0;
}

专场:电话聊天狂人

解法1-排序
解法2 - 直接映射
解法3- 散列

/*  电话聊天狂人  *//*------------------- main()-------------------*/
int main() {int N, i;ElementType Key;HashTable H;scanf("%d", &N);H = CreateTable(N * 2);  /* 创建一个散列表 */for (i = 0; i < N; i++) {scanf("%s", Key);   Insert(H, Key);scanf("%s", Key); Insert(H, Key);}ScanAndOutput(H);DestroyTable(H);return 0;
}/*----  输出 狂人 ----*/void ScanAndOutput(HashTable H) {int i,MaxCnt = PCnt = 0;ElementType MinPhone;List Ptr;MinPhone[0] = '\0';for (i = 0; i < H->TableSize; i++) {/*  扫描链表 */Ptr = H->Heads[i].Next;while (Ptr) {if (Ptr->Count > MaxCnt) {/* 更新最大通话次数*/MaxCnt = Ptr->Count;strcpy(MinPhone, Ptr->Data);PCnt = 1;}else if (Ptr->Count == MaxCnt) {PCnt++;  /* 狂人计数*/if (strcmp(MinPhone, Ptr->Data) > 0)  /* 比较 */strcpy(MinPhone, Ptr->Data);  /* 更新狂人的最小手机号码 */}Ptr = Ptr->Next;}printf("%s %d", MinPhone, MaxCnt);if (PCnt > 1)   printf("  %d", PCnt);printf("\n");}
}#define KEYLENGTH 11   /*  关键词字符串的最大长度 */typedef char ElementType[KEYLENGTH + 1];   /* 关键词类型用字符串*/
typedef int Index;   /* 散列地址类型 */typedef struct LNode* PtrToLNode;
struct LNode {ElementType Data;PtrToLNode Next;int Count;   /* 计数器 */
};
typedef PtrToLNode Position;
typedef PtrToLNode List;typedef struct TblNode* HashTable;
struct TblNode {/* 散列表结点  定义*/int TableSize;   /* 表的最大长度 */List Heads;
};#define MAXTABLESIZE  1000000
int NextPrime(int N) {/* 返回大于N且不超过MAXTABLESIZE的最小素数 */int i, p = (N % 2) ? N + 2 : N + 1; /* 从大于N的下一个奇数开始 */while (p <= MAXTABLESIZE) {for (i = (int)sqrt(p); i > 2; i--) if (!(p % i))  break;   /* p 不是素数 */if (i == 2)  break;    /* for正常结束, 说明p是素数 */else  p += 2;   }return p;
}HashTable CreateTable(int TableSize) {HashTable H;int i;H = (HashTable)malloc(sizeof(struct TblNode));H->TableSize = NextPrime(TableSize);H->Heads = (List)malloc(H->TableSize * sizeof(struct LNode));for (i = 0; i < H->TableSize; i++) {H->Heads[i].Data[0] = '\0'; H->Heads[i].Next = NULL;H->Heads[i].Count = 0;}return H;
}int Hash(int Key, int P) {  /* Key为整数 *//* 除留余数法  散列函数 */return Key % P;
}# define MAXD 5  /* 参与散列映射计算的字符个数 */
Position Find(HashTable H, ElementType Key) {Position P;Index Pos;/* 初始散列位置 */Pos = Hash(atoi(Key + KEYLENGTH - MAXD), H->TableSize);P = H->Heads[Pos].Next;   /*  从该链表的第1个结点  开始*/while (P && strcmp(P->Data, Key))   /* 当未到表尾,并且Key 未找到时*/P = P->Next; return P;    /* 此时P或者指向找到的结点, 或者为NULL*/
}bool Insert(HashTable H, ElementType Key) {Position P, NewCell;Index Pos;P = Find(H, Key);if (!P) { /* 关键词未找到, 可以插入 */NewCell = (Position)malloc(sizeof(struct LNode));strcpy(NewCell->Data, Key);NewCell->Count = 1;Pos = Hash(atoi(Key + KEYLENGTH - MAXD), H->TableSize);/* 将 NewCell 插入为H-> Heads[Pos]链表的第1个结点 */NewCell->Next = H->Heads[Pos].Next;H->Heads[Pos].Next = NewCell;return true;}else {/*  关键词已存在 */P->Count++;return false;}
}

习题

拓扑排序: 预处理的数据有很明显的先后关系

串的模式匹配

/* 匹配子串 */
/*  方法1: c 的库函数 strstr*/
//char *strstr(char*string, char *pattern)/
/* ------------  测试程序 -----------------------*/
#include<stdio.h>
#include<string.h>typedef char* Position;
#define NotFound NULL
int main() {char string[] = "This is a simple example.";char pattern[] = "simple";   //  samplePosition p = strstr(string, pattern);if (p == NotFound)   printf("Not Found.\n");else     printf("%s\n", p);return 0;
}/* T = O(n*m)*/
/* 改进: 从后面开始匹配   T= O(n)*//*------------------------------------------*/
/* 方法三 :  KMP 算法 */
/* T = O( n + m ):    String (len = n), Pattern (len = m) */#include<stdio.h>
#include<string.h>typedef int  Position;
#define NotFound -1int main() {char string[] = "This is a simple example.";char pattern[] = "simple";   //  samplePosition p = KMP(string, pattern);if (p == NotFound)   printf("Not Found.\n");else     printf("%s\n", string + p);return 0;
}Position KMP(char* string, char* pattern) {int n = strlen(string);    /* T = O(n)*/int m = strlen(pattern);  /* T = O(m)*/int s, p, * match;match = (int*)malloc(sizeof(int) * m);BuildMatch(pattern, match);s = p = 0;while (s < n && p < m) {   /* T = O(n)*/if (string[s] == pattern[p]) { s++; p++; }else if (p > 0) p = match[p - 1] + 1;  /* 判断已匹配部分 尾部和头部相同的部分,直接移动这么长*/else s++;}return (p == m) ? (s - m) : NotFound;
}/* T = O(n + m + Tm)*/void BuildMatch(char* pattern, int* match) {int i, j;int m = strlen(pattern);  /* T = O(m)*/match[0] = -1;for (j = 1; j < m; j++) { /* T = O(m)*/i = match[j - 1];while ((i >= 0) && (pattern[i + 1] != pattern[j]))i = match[i];if (pattern[i + 1] == pattern[j])match[j] = i + 1;else match[j] = -1;}
}/* Tm = O(m)*/

数据结构_浙大(C语言) 20211228-20220108相关推荐

  1. 数据结构c语言版袁和金答案,_数据结构_课程教学中的案例设计及应用_袁和金.pdf...

    _数据结构_课程教学中的案例设计及应用_袁和金 第 16 期 90 2013 年 8 月 25 日 Computer Education G642 袁和金 (华北电力大学 计算机系,河北 保定 071 ...

  2. python数据结构题目_《数据结构与算法Python语言描述》习题第二章第三题(python版)...

    ADT Rational: #定义有理数的抽象数据类型 Rational(self, int num, int den) #构造有理数num/den +(self, Rational r2) #求出本 ...

  3. java递归单链表查找中间元素_《数据结构与算法——C语言描述》答案 3.11 查找单链表中的特定元素(递归)...

    转载请注明出处:http://blog.csdn.net/xdz78 #include #include //查找单链表中的特定元素,<数据结构与算法--c语言描述> 3.11 答案 in ...

  4. 数据结构与算法python描述_数据结构与算法——Python语言描述.pdf

    数据结构与算法--Python语言描述.pdf 欢迎加入非盈利Python编学习交流程QQ群783462347,群里免费提供500+本Python书籍! 欢迎加入非盈利Python编程学习交流程QQ群 ...

  5. 数据结构python课后答案_数据结构与算法:Python语言描述 1~5章课后习题

    数据结构与算法:Python语言描述 1~5章课后习题 发布时间:2018-07-19 20:42, 浏览次数:1885 , 标签: Python MarkDown语法写的,不知道为啥上传到CSDN不 ...

  6. s数据结构替换子表java版_数据结构与算法分析Java语言描述(第3版) PDF和源码免费 下载...

    <数据结构与算法分析Java语言描述(第3版)>PDF和源码免费 下载 免积分下载 用户下载说明: 图书简介: 数据结构:Java语言描述(原书第3版)是国外数据结构与算法分析方面的经典教 ...

  7. 7-10 公路村村通 (最小生成树Prim算法) | PTA数据结构与算法——C语言实现

    公路村村通 非常直白的最小生成树问题. 原题链接:PTA | 程序设计类实验辅助教学平台 题目描述 现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通 ...

  8. 7-16 Sort with Swap(0, i) | PTA数据结构与算法——C语言实现

    2013年浙江大学免试研究生上机考试真题. 原题链接:PTA | 程序设计类实验辅助教学平台 题目描述 给定包含数字 {0, 1, 2,..., N−1} 的任一排列,很容易对它们进行升序排序. 但是 ...

  9. 让数组的左边全为奇数C语言,2015年全国计算机等级考试全真模拟考场_二级C语言试卷四.docx...

    2015年全国计算机等级考试全真模拟考场_二级C语言试卷四 全国计算机等级考试全真模拟试卷(4) 二级C (考试时间l20分钟,满分100分) 一.选择题(每小题1分,共40分) (1)算法是指( ) ...

最新文章

  1. 最近5年,诺贝尔化学奖都颁给了谁?
  2. 手把手教你看懂并理解Arduino PID控制库——调参改变
  3. 国际化困境(第二篇)
  4. 学习编程的25个“坑”,你踩到了吗?
  5. 【论文阅读】A Gentle Introduction to Graph Neural Networks [图神经网络入门](4)
  6. chrome浏览器中解决embed标签 loop=true 背景音乐无法循环的问题。
  7. 用Matlab来备份文件夹
  8. 微信支持环信_环信客户互动云v5.39已发布:支持微信小程序接入
  9. 10分钟教会你Apache Shiro
  10. html是什么型语言,HTML笔记
  11. 每当Xcode升级之后,都会导致原有的Xcode插件不能使用,解决办法
  12. OpenStack 认证服务 KeyStone部署(三)
  13. R语言---Ubuntu中R语言更新至R4.2.1和R包devtools下载
  14. 微型计算机控制技术扫描,微型计算机控制技术(双色)
  15. IDA Pro7.0使用技巧总结
  16. 正则表达式验证系统登录密码必须由字母数字和特殊符号组成
  17. 小王梦游记四----------隐形天使
  18. R12_专题知识总结提炼-AP模块
  19. arcgis api js调用天地图
  20. 零信任-易安联零信任介绍(11)

热门文章

  1. 魔法城堡计算机弹奏,魔法城堡钢琴块
  2. 历史上最牛的演讲—甲骨文总裁拉里埃里森在耶鲁大学的演讲
  3. vs2017编译使用jsoncpp
  4. Java数组缩减案例——成绩录入,已严格按照业务给出
  5. js:js中加载js文件
  6. “龙王宝”小程序,送水站老板轻松赚钱的神秘武器
  7. 商标45类分类表明细表_2017商标分类表第45类商标明细
  8. 相片墙个人相册图片墙HTML源码
  9. feko 2017安装教程
  10. 以太坊为什要使用幽灵协议?