数据结构与算法 知识点整理

线性表

线性表的基本概念

线性表的定义:线性表是一个具有相同特性的数据元素的有限序列。

相同特性:所有元素属于同一数据类型

有限:数据元素个数是有限的

序列:数据元素由逻辑序号(逻辑位序)唯一确定,一个线性表中可以有相同值的元素

线性表的基本运算

1. 初始化线性表:构造一个空的线性表

2. 销毁线性表

3. 判断线性表是否为空

4. 求线性表的长度

5. 输出线性表

6. 求线性表中指定位置的数据元素

7. 定位查找特定值的数据元素

8. 插入

9. 删除

线性表的顺序存储结构

按逻辑顺序依次存储到存储器中一片连续的存储空间中。

顺序表类型定义

顺序表的运算实现

#include<stdio.h>
#include<stdlib.h>

#define ElemType char
#define MaxSize 100

typedef struct {
	ElemType data[MaxSize];
	int length;
} SqList;

//初始化顺序表
void InitList(SqList*& L) {
	L = (SqList*)malloc(sizeof(SqList));
	L->length = 0;
}

//整体创建顺序表
void CreateList(SqList*& L, ElemType a[], int n) {
	L = (SqList*)malloc(sizeof(SqList));
	for (int i = 0; i < n; i++) {
		L->data[i] = a[i];
	}
	L->length = n;
}

//销毁顺序表
void DestroyList(SqList*& L) {
	free(L);
}

//判断是否为空表
bool ListEmpty(SqList*& L) {
	return L->length == 0;
}

//求顺序表长度,直接返回length值即可
int ListLength(SqList*& L) {
	return L->length;
}

//输出顺序表
void DispList(SqList*& L) {
	if (ListEmpty(L)) return; //这样不会多打印一个换行符
	for (int i = 0; i < L->length; i++) {
		printf("%c ", L->data[i]);
	}
	putchar('\n');
}

//求第i个元素的值(i从1开始),放到e里面
bool GetElem(SqList*& L, int i, ElemType& e) {
	if (i < 1 || i > L->length) return false;
	e = L->data[i - 1];
	return true;
}

//查找第一个值域与e相等的元素的逻辑位序(从1开始),找不到就返回0
int LocateElem(SqList*& L, ElemType e) {
	int i = 0;
	while (i < L->length && L->data[i] != e) {
		++i;
	}
	if (i == L->length) return 0;
	else return i + 1;// 逻辑位序和物理位序相差1
}

// 在第i个位置(逻辑位序)插入新元素
bool InsertList(SqList*& L, int i, ElemType e) {
	// 检查一下输入的合法性
	if (i < 1 || i > L->length) return false;
	--i;// 把逻辑位序转化成物理位序,对于coder来说好操作;此后,i即物理位序
	// 把第i...length-1个元素都往后挪一位,把第i个位置腾出来
	for (int j = L->length - 1; j >= i; --j) {
		L->data[j + 1] = L->data[j];
	}
	L->data[i] = e;
	L->length += 1;// 注意顺序表的长度是我们需要去维护的!
	return true;
}

// 删除第i个位置的数据元素,返回给e
bool DeleteList(SqList*& L, int i, ElemType& e) {
	// 检查输入合法性
	if (i < 1 || i > L->length) return false;
	--i;
	e = L->data[i];
	for (int j = i + 1; j < L->length; j++) {
		L->data[j] = L->data[j - 1];
	}
	L->length += 1; // 差点又忘记维护了!呜呜呜
	return true;
}

顺序表算法设计(进阶)

1. 已知长度为n的线性表A采用顺序存储结构。设计一个时间复杂度为On、空间复杂度为O1的算法,该算法删除线性表中所有值为x的数据元素。

算法1:空间复用,还算好理解

// 算法1,空间复用,直接覆盖
void DelAl1(SqList*& L, ElemType x) {
	int k = 0;
	for (int i = 0; i < L->length; i++) {
		if (L->data[i] != x) {
			L->data[k] = L->data[i];
			++k;
		}
	}
	L->length = k;
}

算法2:稍微有点抽象了

这个题目的难点在于时间复杂度的要求,On也就是说我们只能遍历一遍,在这唯一的一次遍历中完成所有移位操作。但是我们一开始肯定会想,当我第一次遍历,面对一个元素的时候,我怎么知道要把这个元素前移几位呢?

事实上这是可知的!你仔细想想,如果这个元素前面有x个需要移除的元素,那你是不是把这个元素往前移x位即可?

注意,k计数的是“需要被移除”的元素个数!所以最后顺序表的长度为原长-k

// 算法2,有点抽象
void DelAl2(SqList*& L, ElemType x) {
	int k = 0;
	for (int i = 0; i < L->length; i++) {
		if (L->data[i] == x) ++k;
		else {
			L->data[i - k] = L->data[i];
		}
	}
	L->length -= k;
}

2. 设顺序表L有10个整数。设计一个算法,以第一个元素为分界线,将所有小于等于它的元素移到该元素的前面,将所有大于它的元素移到该元素的后面。

void MoveAl1(SqList*& L) {
	ElemType pivot = L->data[0];
	int i = 0, j = L->data[L->length - 1];
	while (i < j) {
		//i < j这个条件必须一直约束着
		while (i < j && L->data[i] <= pivot) {//小于等于的话,那你就一边玩去,没你事儿~
			++i;
		}
		while (i < j && L->data[i] > pivot) {
			++j;
		}
		if (i < j) {
			ElemType temp = L->data[i];
			L->data[i] = L->data[j];
			L->data[j] = temp;
		}
	}
	ElemType temp = L->data[0];
	L->data[0] = L->data[i];
	L->data[i] = temp;
}

线性表的链式存储结构

单链表:每个物理节点增加一个指向后继节点的指针域

双链表:每个物理节点增加一个指向后继节点的指针域和一个指向前驱节点的指针域

这里提出一个存储密度的概念:

单链表中节点类型LinkList的定义

typedef struct LNode{
	ElemType data;
	struct LNode* next;
} LinkList;

单链表建表——头插法、尾插法

头插法:

1. 从一个空表开始,创建一个头节点dummyHead。

2. 依次读取字符数组中的元素,生成新节点。

3. 将新节点插入到当前链表的表头上,直到结束为止。

(注意:链表的节点顺序与逻辑次序相反)

//头插法建表
void CreateListByHead(LinkList*& L, ElemType a[], int n) {
	L = (LinkList*)malloc(sizeof(LinkList));
	L->next = NULL;
	for (int i = 0; i < n; i++) {
		LNode* newNode = (LNode*)malloc(sizeof(LNode));
		newNode->data = a[i];
		newNode->next = L->next;
		L->next = newNode;
	}
}

尾插法:

1. 从一个空表开始,创建一个头节点dummyHead。

2. 依次读取字符数组a中的元素,生成新节点。

3. 将新节点插入到当前链表的表尾上,直到结束为止。

(链表的节点顺序与逻辑次序相同)

单链表的基本运算

单链表的算法设计

以查找为基础

1. 设计一个算法,删除一个单链表L中元素值最大的节点(假设最大值节点是唯一的)

// 删除最大节点
void DelMaxNode(LinkList*& L) {
	if (ListEmpty(L)) return;
	LNode* pre = L, * maxPre = L;
	while (pre->next != NULL) {
		if (pre->next->data > maxPre->next->data) {
			maxPre = pre;
		}
		pre = pre->next;
	}
	maxPre->next = maxPre->next->next; //不会出现空指针异常!因为maxPre后面一定是指着一个节点的!
}

2. 有一个带头节点的单链表L(至少有一个数据节点),设计一个算法使其元素递增有序排列。

我不懂

// 排序算法,我不懂
void SortList(LinkList*& L) {
	LNode* p = L->next, * pre;
	LNode* r = p->next;// r作为p放入后继,防止断链
	p->next = NULL; // 构造只有一个数据节点的有序表
	p = r;
	while (p != NULL) {
		r = p->next;// r防止断链,又接回去了?!
		pre = L;
		while (pre->next != NULL && pre->next->data < p->data) {
			pre = pre->next;
		}
		p->next = pre->next;
		pre->next = p;
		p = r;
	}
}

把链表转化成数组问题貌似可以,毕竟对数组进行排序的算法可多了。 

以建表为基础

1. 假设有一个带头节点的单链表L,设计一个算法将所有节点逆置。

用头插法不就是直接形成一个逆置链表了嘛!

// 逆置链表
void ReverseList(LinkList*& L) {
	LinkList* p = L->next, * q;
	L->next = NULL;
	while (p != NULL) {
		q = p->next;
		p->next = L->next;
		L->next = p;// 让p成为逆置链表的新的头节点
		p = q;
	}
}

双链表

双链表的基本概念

线性表基本算法在双链表中的实现

头插法建表

尾插法建表

栈的基本概念

栈的定义:只能在一端进行插入或删除操作的线性表

允许进行插入、删除操作的一端——栈顶

栈的顺序存储结构

顺序栈的基本运算实现

#include<stdio.h>
#include<stdlib.h>

#define ElemType int
#define MaxSize 10

typedef struct {
	ElemType data[MaxSize];
	int top;
} SqStack;

// 初始化栈,创建一个空栈
void InitStack(SqStack*& S) {
	S = (SqStack*)malloc(sizeof(SqStack));
	S->top = -1;
}

// 销毁栈
void DstroyStack(SqStack*& S) {
	free(S);
}

// 判断栈是否为空,若栈为空则返回true
bool StackEmpty(SqStack*& S) {
	return (S->top == -1)
}

// 进栈
bool PushStack(SqStack*& S, ElemType e) {
	// 顺序栈可能出现栈满的情况
	if (S->top == MaxSize - 1) {
		return false;
	}
	S->top++;
	S->data[S->top] = e;
	return true;
}

// 出栈
bool PopStack(SqStack*& S, ElemType& e) {
	if (StackEmpty(S)) {
		return false;
	}
	e = S->data[S->top];
	S->top--;
	return true;
}

// 取栈顶元素
bool GetTop(SqStack*& S, ElemType& e) {
	if (StackEmpty(S)) {
		return false;
	}
	e = S->data[S->top];
	return true;
}

顺序栈的算法设计

栈这种先进后出的思想可以用来解决“回文字符串”问题

1. 设计一个算法利用顺序栈判断一个字符串是否是对称串

// 判断回文字符串
bool SymmetryString(ElemType str[]) {
	SqStack* st;
	ElemType temp;
	InitStack(st);
	for (int i = 0; str[i]; i++) {
		PushStack(st, str[i]);
	}
	for (int i = 0; str[i]; i++) {
		PopStack(st, temp);
		if (temp != str[i]) {
			return false;
		}
	}
	DestroyStack(st);
	return true;
}

栈的链式存储结构

头插法,链表的真实头节点即栈顶

链式栈的基本运算实现

#include<stdio.h>
#include<stdlib.h>

#define ElemType char

typedef struct LinkNode {
	ElemType data;
	LinkNode* next;
} LkStack;

// 初始化栈
void InitStack(LkStack*& S) {
	S = (LkStack*)malloc(sizeof(LkStack));
	S->next == NULL;
}

// 销毁栈
void DestroyStack(LkStack*& S) {
	LinkNode* p = S->next, *q;
	while (p != NULL) {
		q = p->next;
		free(p);
		p = q;
	}
	free(S);
}

// 判断栈是否为空
bool StackEmpty(LkStack*& S) {
	return (S->next == NULL);
}

// 进栈
void PushStack(LkStack*& S, ElemType e) {
	LinkNode* newNode = (LinkNode*)malloc(sizeof(LinkNode));
	newNode->data = e;
	newNode->next = S->next;
	S->next = newNode;
}

// 出栈,出栈可能失败因此返回值设置为bool
bool PopStack(LkStack*& S, ElemType &e) {
	if (StackEmpty(S)) {
		return false;
	}
	e = S->next->data;
	LinkNode* temp = S->next;
	S->next = temp->next;
	free(temp);
	return true;
}

// 取栈顶元素
bool GetTop(LkStack*& S, ElemType &e) {
	if (StackEmpty(S)) {
		return false;
	}
	e = S->next->data;
	return true;
}

// 遍历栈
bool DispStack(LkStack*& S) {
	if (StackEmpty(S)) {
		return false;
	}
	LinkNode* p = S->next;
	while (p != NULL) {
		printf("%c\n", p->data);
		p = p->next;
	}
}

int main() {
	LkStack* st;
	InitStack(st);
	PushStack(st, 'a');
	DispStack(st);
	return 0;
}

链式栈的算法设计

1. 编写一个算法判断表达式的左右括号是否匹配(假设只含有左右圆括号)

//  判断表达式的左右括号是否匹配
bool IsMatch(char expr[]) {
	LkStack* st;
	ElemType e;
	InitStack(st);
	
	for (int i = 0; expr[i]; i++) {
		// 遇到左括号就把左括号入栈
		if (expr[i] == '(') {
			PushStack(st, expr[i]);
		}
		else if (expr[i] == ')') {
			if (GetTop(st, e)) {
				if (e == '(') {
					PopStack(st, e);
				}
				else {
					return false;
				}
			}
		}
	}
	// 如果遍历完整个字符串之后,栈刚好为空,则返回true
	if (StackEmpty(st)) {
		DestroyStack(st);
		return true;
	}
	else {
		DestroyStack(st);
		return false;
	}
}

需要注意的点是:你对几个栈的基本函数必须熟悉啊!返回值是多少?参数列表如何设定?

比如你用到PopStack这个函数,你是把Pop出去的那个元素的值放到e里面,你是这么些PopStack(st,e),然后你再对e这个值及逆行判断。

卧槽要是你直接写if(PopStack(st,e) == ’)’)不就寄了。

用栈tips

1. 如果空栈调用top()函数会引发未定义行为,所以在调用top函数(即想取栈顶元素前)一定要注意先确保栈不为空!

队列

队列是一种运算受限的线性表。

插入是从队尾插入(很合理,我们排队也是从队尾开始排队呢)

队列的顺序存储结构-顺序队

顺序队的基本算法实现

#include<stdio.h>
#include<stdlib.h>

#define ElemType char
#define MaxSize 10

typedef struct {
	ElemType data[MaxSize];
	int front, rear; // 定义队头和队尾
} SqQueue;

// 初始化队列
void InitQueue(SqQueue*& Q) {
	Q = (SqQueue*)malloc(sizeof(SqQueue));
	Q->front = Q->rear = -1;
}

// 销毁队列
void DestroyQueue(SqQueue*& Q) {
	free(Q);
}

// 判断队列是否为空
bool QueueEmpty(SqQueue*& Q) {
	return (Q->front == Q->rear);
}

// 进队列
bool InQueue(SqQueue*& Q, ElemType e) {
	// 顺序队要先判断是否队满
	if (Q->rear == MaxSize - 1) {
		return false;
	}
	Q->rear++;
	Q->data[Q->rear] = e;
	return true;
}

// 出队列
bool OutQueue(SqQueue*& Q, ElemType &e) {
	if (QueueEmpty(Q)) {
		return false;
	}
	Q->front++;
	e = Q->data[Q->front];
	return true;
}

环形顺序队

为什么rear需要+1,因为,你要进队啊!那你不得把指针往后挪一位。为什么要取余,因为是环形队列,这里有一个循环,尾接着头。

为什么front需要+1,因为front指向的是队头的前一个节点,你要把front+1之后才能确切指向队头。取余原因同理

#include<stdio.h>
#include<stdlib.h>

#define ElemType char
#define MaxSize 10

typedef struct {
	ElemType data[MaxSize];
	int front;
	int count; // 设置一个count保存元素个数
} CQueue;

// 初始化队列
void InitQueue(CQueue*& CQ) {
	CQ = (CQueue*)malloc(sizeof(CQueue));
	CQ->front = CQ->count = 0;
}

// 入队
bool InQueue(CQueue*& CQ, ElemType e) {
	if (CQ->count == MaxSize) {
		return false;
	}
	int rear = (CQ->front + CQ->count) % MaxSize;
	rear = (rear + 1) % MaxSize;// 加1是一定要加1的,环形队列是需要取余的。
	CQ->data[rear] = e;
	CQ ->count++;
	return true;
}

// 出队
bool OutQueue(CQueue*& CQ, ElemType& e) {
	// 这里不需要判断为空函数了,count直接保存该队列的元素值,注意维护!
	if (CQ->count == 0) {
		return false;
	}
	CQ->front = (CQ->front + 1) % MaxSize;
	e = CQ->data[CQ->front];
	CQ->count--;
	return true;
}

队列的链式存储结构-链式队

#include<stdio.h>
#include<stdlib.h>

#define ElemType char

typedef struct qNode {
	ElemType data;
	struct qNode* next;
} QNode;

typedef struct {
	QNode* front; // 指向链式队头节点
	QNode* rear; // 指向链式队尾节点
} LkQueue;

// 初始化
void InitQueue(LkQueue*& LQ) {
	LQ = (LkQueue*)malloc(sizeof(LkQueue));
	LQ->front = LQ->rear = NULL;
}

// 销毁
void DestroyQueue(LkQueue*& LQ) {
	QNode* p = LQ->front, *q;
	while (p != NULL) {
		q = p->next;
		free(p);
		p = q;
	}
	free(LQ);
}

// 判断队列是否为空
bool QueueEmpty(LkQueue*& LQ) {
	return (LQ->front == NULL);
}

// 进队
void InQueue(LkQueue*& LQ, ElemType e) {
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	newNode->data = e;
	newNode->next = NULL;
	if (QueueEmpty(LQ)) {
		LQ->front = LQ->rear = newNode;
	}
	else {
		LQ->rear->next = newNode;
		LQ->rear = newNode;
	}
}

bool OutQueue(LkQueue*& LQ, ElemType &e) {
	if (QueueEmpty(LQ)) {
		return false;
	}
	QNode* temp = LQ->front;
	LQ->front = temp->next;
	e = temp->data;
	// 如果只有一个节点,那要对rear也进行修改
	if (temp->next == NULL) {
		free(temp);
		LQ->front = LQ->rear = NULL;
	}
	else {
		LQ->front = temp->next;
		free(temp);
	}
	return true;
}

// 遍历
bool DispQueue(LkQueue*& LQ) {
	if (QueueEmpty(LQ)) {
		return false;
	}
	QNode* p = LQ->front;
	while (p != NULL) {
		printf("%c ", p->data);
		p = p->next;
	}
}

int main() {
	LkQueue* LQ;
	InitQueue(LQ);
	InQueue(LQ, 'a');
	InQueue(LQ, 'b');
	InQueue(LQ, 'c');
	ElemType e;
	OutQueue(LQ, e);
	DispQueue(LQ);
	return 0;
}

环形链式队

栈和队列的应用

1. 简单表达式求值

postfix后缀表达式计算

// 计算postfix表达式的值,不含括号
// 如果是操作数就入栈,如果是操作符就弹出两个操作数,计算之后再把结果入栈
void CalExpr(char expr[]) {
	LkStack* st;
	ElemType e1, e2;
	int op1, op2, res = 0;
	InitStack(st);

	for (int i = 0; expr[i]; i++) {
		if (isdigit(expr[i])) {
			PushStack(st, expr[i]);
		}
		else {
			PopStack(st, e2);
			PopStack(st, e1);
			op1 = e1 - '0';
			op2 = e2 - '0';
			switch (expr[i]) {
			case '+':
				res = op1 + op2;
				break;
			case '-':
				res = op1 - op2;
				break;
			case '*':
				res = op1 * op2;
				break;
			case '/':
				res = op1 / op2;
				break;
			}
			PushStack(st, res + '0');
		}
	}
	printf("表达式的值为:%d\n", res);
}

好的,那么问题来了,我们如何将infix转换为postfix呢?而且有的infix是含括号的,我们要想办法把括号去掉。

infix——>postfix

不含括号

这里我犯了个错误,这是源于我对GetTop函数的不熟悉。GetTop函数是有可能会失败的,就是你栈已经拿空了,你再拿就拿不出来了。但是我这里的写法,e是一直有值的。所以会造成死循环。

我这样写,就相当于把GetTop函数的返回值考虑进去了。如果该函数返回false了,说明栈为空了。拿完了退出循环即可!

完整代码如下:

// 判断运算符优先级
int Prece(char ch) {
	if (ch == '+' || ch == '-') return 1;
	else if (ch == '*' || ch == '/') return 2;
	else return 0;
}

// 中缀表达式转后缀表达式
void InfixToPostfix(char* infix, char* postfix) {
	LkStack* st;
	InitStack(st);
	int p = 0;
	ElemType e;

	for (int i = 0; infix[i]; i++) {
		if (isdigit(infix[i])) {
			postfix[p++] = infix[i];
		}
		else {
			while(GetTop(st, e) && (Prece(e) >= Prece(infix[i]))) {
				PopStack(st, e);
				postfix[p++] = e;
			}
			PushStack(st, infix[i]);
		}
	}

	while (!StackEmpty(st)) {
		PopStack(st, e);
		postfix[p++] = e;
	}
	printf("%s\n", postfix);
	DestroyStack(st);
}

有括号

解读一下这一串伪代码:

1. 遇到操作数,直接输入postfix数组

2. 遇到左括号open bracket,直接入栈

3. 遇到操作符(非右括号),进行优先级判断(同上),把栈里该拿的先拿走,拿完放postfix数组里

4. 遇到的右括号close bracket,把栈里的东西拿走,直到遇见左括号。再把左括号弹出。

右括号不需要入栈哦!

完整代码如下:

// 判断运算符优先级
int Prece(char ch) {
	if (ch == '+' || ch == '-') return 1;
	else if (ch == '*' || ch == '/') return 2;
	else return 0;
}

// 中缀表达式转后缀表达式
void InfixToPostfix(char* infix, char* postfix) {
	LkStack* st;
	InitStack(st);
	int p = 0;
	ElemType e;

	for (int i = 0; infix[i]; i++) {
		if (isdigit(infix[i])) {
			postfix[p++] = infix[i];
		}
		else if (infix[i] == '(') {
			PushStack(st, infix[i]);
		}
		else if (infix[i] == ')') {
			while (GetTop(st, e) && e != '(') {
				PopStack(st, e);
				postfix[p++] = e;
			}
			PopStack(st, e); // 把左括号弹出。对于合法表达式来说,此时栈顶肯定是左括号
		}
		else if(infix[i] != ')'){
			while(GetTop(st, e) && (Prece(e) >= Prece(infix[i]))) {
				PopStack(st, e);
				postfix[p++] = e;
			}
			PushStack(st, infix[i]);
		}
	}

	while (!StackEmpty(st)) {
		PopStack(st, e);
		postfix[p++] = e;
	}
	printf("%s\n", postfix);
	DestroyStack(st);
}

递归

递归的定义

在定义一个过程或函数时,出现直接或者间接调用自己的成分,称之为递归。

何时使用递归

1. 定义是递归的

例如求n!和Fibonacci数列

2. 数据结构是递归的

链表、树

3. 问题的求解方法是递归的

Hanoi问题

递归问题的算法设计

1. 求数组元素中的最小值

树的性质

性质1:树中的节点数等于所有节点的度数+1(这个很好理解啊,这个1就是根节点)

这也很好理解,等比数列~,每一个节点都最多有m条分支

这就是等比数列求和了。

树的基本运算

1. 查找满足某种特定关系的节点,如查找当前节点的双亲节点等

2. 插入或删除某个节点,如在树的当前节点上插入一个新节点或删除当前节点的第i个孩子节点等

3. 遍历树中每个节点(先根、后根、层次遍历,其中先根和后根遍历算法都是递归的)

树的存储结构

双亲存储结构

孩子链存储结构

孩子兄弟链存储结构

二叉树的概念

二叉树性质

二叉树的链式存储结构

二叉链的基本运算

#include<stdio.h>
#include<stdlib.h>

#define ElemType char
#define MaxSize 100

typedef struct node {
	ElemType data;
	struct node* lchild, *rchild;
} BTNode;

// 创建二叉树
void CreateBTree(BTNode*& BT, char* str) {
	// 先创建一个顺序栈
	BTNode* BTst[MaxSize] = {}, * newNode = NULL;
	BT = NULL; // 二叉树初始根节点为NULL
	int top = -1; //
	int flag = 0; // flag为0时,表明此时处理的是左子树;flag为1时,为右子树

	for (int i = 0; str[i]; i++) {
		switch (str[i]) {
		case '(': // 此处开始处理左子树,有新的节点,把它入栈
			BTst[++top] = newNode;
			flag = 0;
			break;
		case ',': // 此处处理右子树
			flag = 1;
			break;
		case ')': // 该根节点处理完成,可以出栈了
			top--;
			break;
		default:
			newNode = (BTNode*)malloc(sizeof(BTNode));
			newNode->data = str[i];
			newNode->lchild = newNode->rchild = NULL; // 创建完新节点之后的初始化是多么重要!
			if (BT == NULL) {
				BT = newNode;
			}
			else if (flag == 0) { // 上面新生成的节点是栈顶的节点的左子树
				BTst[top]->lchild = newNode;
			}
			else if(flag == 1){
				BTst[top]->rchild = newNode;
			}
		}
	}	
}

// 销毁二叉树,采用递归方法
void DestroyBTree(BTNode*& BT) {
	// 什么时候去free当前处理的节点BT?这里这个写法,在递归里,BT就是当前处理节点的意思,而不是真实头节点
	if (BT == NULL) {
		free(BT);
	}
	else {
		DestroyBTree(BT->lchild);
		DestroyBTree(BT->rchild);
		free(BT);
	}
}

// 查找特定值的节点FindNode
BTNode* FindNode(BTNode*& BT, ElemType e) {
	BTNode* p;
	// 一定要考虑空树的情况啊!(这跟前面考虑空栈啥的不一样嘛)
	if (BT == NULL) {
		return NULL;
	}
	else if (BT->data == e) {
		return BT;
	}
	else {
		p = FindNode(BT->lchild, e);
		if (p != NULL) return p;
		else return FindNode(BT->rchild, e);
	}
}

// 求高度
int BTreeDepth(BTNode*& BT) {
	int lDepth = 0, rDepth = 0;
	if (BT == NULL) return 0;
	else {
		lDepth = BTreeDepth(BT->lchild);
		rDepth = BTreeDepth(BT->rchild);
		if (lDepth > rDepth) return lDepth + 1;
		else return rDepth + 1;
	}
}

// 输出二叉链
void DispBTree(BTNode*& BT) {
	if (BT == NULL) {
		return;
	}
	printf("%c", BT->data);
	if (BT->lchild != NULL || BT->rchild != NULL) {
		putchar('(');
		DispBTree(BT->lchild);
		if (BT->rchild != NULL) {
			putchar(',');
		}
		DispBTree(BT->rchild);
		putchar(')');
	}
}

int main() {
	BTNode* t;
	char str[MaxSize] = {};
	scanf("%s", str);
	CreateBTree(t, str);
	DispBTree(t);
	return 0;
}

二叉树的节点计算

 二叉树遍历算法

先中后序

// 先序遍历二叉树
void PreOrder(BTNode*& BT) {
	if (BT != NULL) {
		printf("%c", BT->data);
		PreOrder(BT->lchild);
		PreOrder(BT->rchild);
	}
}

// 中序遍历二叉树
void InOrder(BTNode*& BT) {
	if (BT != NULL) {
		InOrder(BT->lchild);
		printf("%c", BT->data);
		InOrder(BT->rchild);
	}
}

// 后序遍历二叉树
void PostOrder(BTNode*& BT) {
	if (BT != NULL) {
		PostOrder(BT->lchild);
		PostOrder(BT->rchild);
		printf("%c", BT->data);
	}
}

层次遍历

// 层次遍历二叉树
// 为什么考虑用队列?因为这是一个先进先出的问题
void LevelOrder(BTNode*& BT) {
	if (BT == NULL) {
		return;
	}
	BTNode* p = NULL;
	BTNode* qu[MaxSize] = {};
	int front = 0, rear = 0; // 初始化很关键哦!
	rear++;
	qu[rear] = BT; // 把根节点先入队

	while (front != rear) {
		front = (front + 1) % MaxSize;
		p = qu[front]; // 把队头出队
		printf("%c", p->data); // 访问该节点,该节点访问完成
		if (p->lchild != NULL) {
			rear = (rear + 1) % MaxSize;
			qu[rear] = p->lchild;
		}
		if (p->rchild != NULL) {
			rear = (rear + 1) % MaxSize;
			qu[rear] = p->rchild;
		}
	}
}

二叉树遍历的应用

1. 设计一个算法,计算给定二叉树的节点个数

2. 二叉树复制算法,建立一颗新二叉树和原二叉树一模一样

 仍然运用了递归的思想。

3. 设计一个算法求二叉树中给定节点的层次(节点值唯一)

其实我感觉这个思路很6,把所求隐含在函数的参数列表里,这样可以在层层递归时实现”存储信息“的功能,需要的时候直接返回即可。 

二叉树的构造

先序+中序

代码如下:

中序+后序 

代码如下:

哈夫曼树

哈夫曼树的定义

设二叉树具有n个带权值的叶节点,那么从根节点到各个叶节点的路径长度与相应节点权值的乘积的和,叫做二叉树的带权路径长度。

具有最小带权路径长度的二叉树称为哈夫曼树(最优树)

构建哈夫曼树

构建哈夫曼树的原则:权值越大的叶节点越远离根节点,权值越小的叶节点越靠近根节点。

哈夫曼编码

规定哈夫曼树中的左分支为0,右分支为1,则从根节点到每个叶节点所经过的分支对应的0和1组成的序列便为该节点对应字符的编码。这样的编码称为哈夫曼编码。(哈夫曼编码属于二进制编码)

在一组字符的哈夫曼编码中,不可能出现一个字符的哈夫曼编码是另一个字符哈夫曼编码的前缀!

哈夫曼树的特点

  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值