数据结构_浙大(C语言) 20211228-20220108

  • 《数据结构》 浙大(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^n


int 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==> PtrL
	L = (List)malloc(sizeof(struct LNode));
	L->Last = -1;

	return L;
}


/*查找*/

#define ERROR -1

Position 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 NULL

Position 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 MaxSize

typedef 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) {//若为空返回1
	return (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的特殊值,标志错误
	}
	else
		return (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,否则返回false
	return (S->Next == NULL);
}

bool Push(Stack S, ElementType X) {//将元素压入堆栈S
	PtrToSNode 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;   /* 删除后队列为空 */
	else
		PtrQ->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;           //删除后队列置为空
		else
			Q->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,得到P
		Attach(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;
		else
			printf(" ");
		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);  //创建并初始化堆栈S
	while (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);  //创建并初始化堆栈S
	while (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;  //一开始都是0
		for (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);  /* 在左子树中继续查找*/
	else
		return BST;     /* 查找成功,返回找到的结点的地址*/
}
// 改成迭代
Position IterFind(ElementType X, BinTree BST) {
	while (BST) {
		if (X > BST->Data)
			BST = BST->Right;
		else if (X < BST->Data)
			BST = BST->Left;
		else
			return BST;
	}
	return NULL;
}

找最值

/* 搜索二叉树:查找最小元素的 递归 函数*/
Position FindMin(BinTree BST) {
	if (!BST) return NULL;  /* 空的二叉搜索树,返回NULL*/
	else if (!BST->Left)
		return BST;             /* 找到最左叶结点并返回*/
	else
		return 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);  //左单旋
			else
				T = 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);  /* 右单旋*/
			else
				T = 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);
		else
			T->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;
		else
			H->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;
		else
			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);
}

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 -10001

int 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;   //Find
		case '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");
	else
		printf("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;
	else
		return 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);
	else
		return (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;
		else
			path[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;
	else
		return 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;   /*  说明图中有回路,返回不成功标志*/
	else
		return 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);
	}
	else
		Insertion_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);    /* 递归解决右边 */
	}
	else
		Insertion_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;
		else
			k = 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";   //  sample
	Position 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 -1

int main() {
	char string[] = "This is a simple example.";
	char pattern[] = "simple";   //  sample
	Position 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)*/
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值