数据结构与算法---树

数据结构可视化网址

构建二叉树

在这里插入图片描述

// C

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

typedef char T;


typedef struct TreeNode {
	T data;
	struct TreeNode* left;
	struct TreeNode* right;
}Node;


int main()
{
	int spaceSize = sizeof(Node);

	Node* a = (Node*)malloc(spaceSize);
	Node* b = (Node*)malloc(spaceSize);
	Node* c = (Node*)malloc(spaceSize);
	Node* d = (Node*)malloc(spaceSize);
	Node* e = (Node*)malloc(spaceSize);

	a->data = 'A';
	b->data = 'B';
	c->data = 'C';
	d->data = 'D';
	e->data = 'E';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->left = c->right = NULL;
	d->left = d->right = NULL;
	e->left = e->right = NULL;
	
	free(a);
	free(b);
	free(c);
	free(d);
	free(e);

	getchar();
	return 0;
}

遍历二叉树

0.基本概念

二叉树是一种特殊的树形数据结构,其中每个节点最多有两个子节点,通常称为左子节点和右子节点。

当我们遍历二叉树时,可以根据访问节点的顺序得到不同的遍历结果。

有以下四种遍历方式:

  1. 前序遍历
    • 访问根节点
    • 前序遍历左子树
    • 前序遍历右子树
    • 对于给定的二叉树,前序遍历的结果是一个节点序列,这个序列首先列出根节点,然后递归地列出左子树的所有节点,最后递归地列出右子树的所有节点
  2. 中序遍历
    • 中序遍历左子树
    • 访问根节点
    • 中序遍历右子树
    • 在二叉搜索树(BST)中,中序遍历的结果是一个有序的节点序列,因为它首先遍历最小的节点(在BST的左下方),然后遍历根节点,最后遍历最大的节点(在BST的右上方)
  3. 后序遍历
    • 后序遍历左子树
    • 后序遍历右子树
    • 访问根节点
    • 后序遍历的结果是一个节点序列,这个序列首先列出左子树的所有节点,然后列出右子树的所有节点,最后列出根节点
  4. 层序遍历
    • 从根节点开始
    • 访问当前层的所有节点
    • 对下一层的每个节点进行同样的操作
    • 层序遍历通常使用队列来实现,首先将根节点入,然后在循环中,出队一个节点并访问它,然后将它的子节点(如果存在)入队。这个过程一直持续到队列为空

1.前序遍历

在这里插入图片描述

  • 通过递归的方式实现
// C

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

typedef char T;


typedef struct TreeNode {
	T data;
	struct TreeNode* left;
	struct TreeNode* right;
}Node;


// 先序遍历
void preorder(Node* root)	// 参数:二叉树的根节点
{
	if (root == NULL)	return;
	printf("%c", root->data);
	preorder(root->left);
	preorder(root->right);
}


int main()
{
	int spaceSize = sizeof(Node);

	Node* a = (Node*)malloc(spaceSize);
	Node* b = (Node*)malloc(spaceSize);
	Node* c = (Node*)malloc(spaceSize);
	Node* d = (Node*)malloc(spaceSize);
	Node* e = (Node*)malloc(spaceSize);
	Node* f = (Node*)malloc(spaceSize);

	a->data = 'A';
	b->data = 'B';
	c->data = 'C';
	d->data = 'D';
	e->data = 'E';
	f->data = 'F';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->right = f;
	c->left = d->left = d->right = e->left = e->right = f->left = f->right = NULL;

	preorder(a);

	free(a);
	free(b);
	free(c);
	free(d);
	free(e);


	getchar();
	return 0;
}
  • 通过"循环+栈"的方式实现
// C

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

// 二叉树的节点
typedef char T;
typedef struct TreeNode {
	T data;
	struct TreeNode* left;
	struct TreeNode* right;
}TreeNode;


// 栈的节点
typedef struct StackNode {
	TreeNode* node;
	StackNode* next;
}StackNode;


// 栈的初始化
void initStack(StackNode* head)
{
	head->next = NULL;
}


// 入栈
bool pushStack(StackNode* head, TreeNode* node)
{
	StackNode* p = (StackNode*)malloc(sizeof(StackNode));
	if (p == NULL)	return false;

	p->node = node;
	p->next = head->next;
	head->next = p;

	return true;
}


// 出栈
TreeNode* popStack(StackNode* head)
{
	StackNode* temp = head->next;
	TreeNode* node = head->next->node;

	head->next = head->next->next;
	free(temp);

	return node;
}


// 栈是否为空
bool isEmpty(StackNode* head)
{
	return head->next == NULL;
}


// 先序遍历
void preorder(TreeNode* root)
{
	StackNode stack;		// 创建一个栈
	initStack(&stack);		// 初始化这个栈

	while (root || !isEmpty(&stack))		// 如果栈不为空,也会继续循环下去
	{
		// 先不断遍历左子树,直到没有为止
		while (root)
		{
			printf("%c", root->data);
			pushStack(&stack, root);		// 途中每经过一个节点,就将节点丢进栈中
			root = root->left;
		}

		// 处理右子树
		root = popStack(&stack);
		root = root->right;
	}
}

int main()
{
	int spaceSize = sizeof(TreeNode);

	TreeNode* a = (TreeNode*)malloc(spaceSize);
	TreeNode* b = (TreeNode*)malloc(spaceSize);
	TreeNode* c = (TreeNode*)malloc(spaceSize);
	TreeNode* d = (TreeNode*)malloc(spaceSize);
	TreeNode* e = (TreeNode*)malloc(spaceSize);
	TreeNode* f = (TreeNode*)malloc(spaceSize);

	a->data = 'A';
	b->data = 'B';
	c->data = 'C';
	d->data = 'D';
	e->data = 'E';
	f->data = 'F';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->right = f;
	c->left = d->left = d->right = e->left = e->right = f->left = f->right = NULL;

	preorder(a);	// 先序遍历

	free(a);
	free(b);
	free(c);
	free(d);
	free(e);


	getchar();
	return 0;
}

2.中序遍历

在这里插入图片描述

  • 通过递归的方式实现
// C

// 中序遍历
void inOrder(TreeNode* root)
{
	if (root == NULL) return;
	inOrder(root->left);		// 先完成全部左子树的遍历
	printf("%c", root->data);   // 等待左子树遍历完成之后再打印
	inOrder(root->right);		// 然后就是对右子树进行遍历
}
  • 通过"循环+栈"的方式实现
// C

// 中序遍历
void inOrder(TreeNode* root)
{
	StackNode stack;		// 创建一个栈
	initStack(&stack);		// 初始化这个栈

	while (root || !isEmpty(&stack))		// 如果栈不为空,也会继续循环下去
	{
		while (root)
		{
			pushStack(&stack, root);
			root = root->left;
		}
		root = popStack(&stack);
		printf("%c", root->data);   // 只需要将打印时机延后到左子树遍历完成
		root = root->right;
	}
}

3.后序遍历

// C

// 后序遍历
void postOrder(Node root){
    if(root == NULL) return;
    postOrder(root->left);
    postOrder(root->right);
    printf("%c", root->data);   // 时机延迟到最后
}

4.层序遍历

// C

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

typedef char E;


// 树的节点
struct TreeNode {
	E element;
	struct TreeNode* left;
	struct TreeNode* right;
	int flag;
};
typedef struct TreeNode* Node;


// 队列的节点
struct QueueNode {
	Node element;
	struct QueueNode* next;
};
typedef struct QueueNode* QNode;


// 队列
struct Queue {
	QNode front, rear;
};


// 初始化
bool initQueue(Queue* queue) {
	QNode node = (QNode)malloc(sizeof(struct QueueNode));
	if (node == NULL)   return false;

	queue->front = queue->rear = node;

	return true;
}


// 入队
bool offerQueue(Queue* queue, Node element) {
	QNode node = (QNode)malloc(sizeof(struct QueueNode));
	if (node == NULL) return false;

	node->element = element;
	queue->rear->next = node;
	queue->rear = node;

	return true;
}


// 出队
Node pollQueue(Queue* queue) {
	Node e = queue->front->next->element;
	QNode node = queue->front->next;

	queue->front->next = queue->front->next->next;
	if (queue->rear == node) queue->rear = queue->front;
	free(node);

	return e;
}


// 判断是否为空
bool isEmpty(Queue* queue) {
	return queue->front == queue->rear;
}


// 层序遍历
void levelOrder(Node root) {
	struct Queue queue;		// 创建一个队列
	initQueue(&queue);		// 初始化这个队列

	offerQueue(&queue, root);		// 先把根节点入队
	while (!isEmpty(&queue))		// 不断重复,直到队列空为止
	{
		Node node = pollQueue(&queue);		// 出队一个元素,打印值
		printf("%c", node->element);
		if (node->left)		// 如果存在左右孩子的话
			offerQueue(&queue, node->left);		// 记得将左右孩子入队,注意顺序,先左后右
		if (node->right)
			offerQueue(&queue, node->right);
	}
}


int main()
{
	int spaceSize = sizeof(TreeNode);

	TreeNode* a = (TreeNode*)malloc(spaceSize);
	TreeNode* b = (TreeNode*)malloc(spaceSize);
	TreeNode* c = (TreeNode*)malloc(spaceSize);
	TreeNode* d = (TreeNode*)malloc(spaceSize);
	TreeNode* e = (TreeNode*)malloc(spaceSize);
	TreeNode* f = (TreeNode*)malloc(spaceSize);

	a->element = 'A';
	b->element = 'B';
	c->element = 'C';
	d->element = 'D';
	e->element = 'E';
	f->element = 'F';

	a->left = b;
	a->right = c;
	b->left = d;
	b->right = e;
	c->right = f;
	c->left = d->left = d->right = e->left = e->right = f->left = f->right = NULL;

	levelOrder(a);	// 层序遍历

	free(a);
	free(b);
	free(c);
	free(d);
	free(e);


	getchar();
	return 0;
}

线索化二叉树

线索化概念

一棵二叉树的某些结点会存在NULL的情况,我们可以利用这些为NULL的指针,将其线索化为某一种顺序遍历的指向下一个按顺序的结点的指针,经过第一次遍历完成了线索化,以后我们再进行遍历的时候,就可以利用线索实现快速遍历。

  • 一棵经过线索化以后的二叉树的前序遍历顺序如下:

在这里插入图片描述

  • 一棵经过线索化以后的二叉树的中序遍历顺序如下:

在这里插入图片描述

  • 一棵经过线索化以后的二叉树的后序遍历顺序如下:

在这里插入图片描述

线索化规则

线索化的规则为:

  • 结点的左指针,指向其当前遍历顺序的前驱结点。
  • 结点的右指针,指向其当前遍历顺序的后继结点。

代码实现

// C

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

typedef char E;

// 树节点
typedef struct TreeNode {
	E element;
	struct TreeNode* left;
	struct TreeNode* right;
	struct TreeNode* parent;	// 指向双亲(父)结点 ------ 只是"后序遍历"需要用这个属性
	int leftTag, rightTag;		// 标志位,如果为1表示这一边指针指向的是线索,不为1就是正常的孩子结点
} *Node;


// 创建结点
Node createNode(E element) {
	Node node = (Node)malloc(sizeof(struct TreeNode));
	node->left = node->right = NULL;
	node->rightTag = node->leftTag = 0;
	node->element = element;
	return node;
}


// "前序遍历"线索化函数
Node pre1 = NULL;  // 这里我们需要一个pre1来保存后续结点的指向
void preOrderThreaded(Node root) {
	if (root == NULL) return;

	if (root->left == NULL) {   // 首先判断当前结点左边是否为NULL,如果是,那么指向上一个结点
		root->left = pre1;
		root->leftTag = 1;  // 记得修改标记
	}
	if (pre1 && pre1->right == NULL) {  // 然后是判断上一个结点的右边是否为NULL,如果是那么进行线索化,指向当前结点
		pre1->right = root;
		pre1->rightTag = 1;  // 记得修改标记
	}

	pre1 = root;   // 每遍历完一个,需要更新一下pre1,表示上一个遍历的结点

	if (root->leftTag == 0)   // 注意只有标志位是0才可以继续向下,否则就是线索了
		preOrderThreaded(root->left);
	if (root->rightTag == 0)
		preOrderThreaded(root->right);
}


// "前序遍历"一棵经过线索化的二叉树
void preOrder(Node root) {
	while (root) {
		printf("%c", root->element);   // 因为是前序遍历,所以直接按顺序打印就行了
		if (root->leftTag == 0)
			root = root->left;   // 如果是左孩子,那么就走左边
		else
			root = root->right;   // 如果左边指向的不是孩子,而是线索,那么就直接走右边,因为右边无论是线索还是孩子,都要往这边走了
	}
}


// "中序遍历"线索化函数
Node pre2 = NULL;  // 这里我们需要一个pre2来保存后续结点的指向
void inOrderThreaded(Node root) {
	if (root == NULL) return;
	if (root->leftTag == 0)
		inOrderThreaded(root->left);

	// ----------线索化 ---------
	if (root->left == NULL) {
		root->left = pre2;
		root->leftTag = 1;
	}
	if (pre2 && pre2->right == NULL) {
		pre2->right = root;
		pre2->rightTag = 1;
	}
	pre2 = root;
	// ----------线索化 ---------

	if (root->rightTag == 0)
		inOrderThreaded(root->right);
}


// "中序遍历"一棵经过线索化的二叉树
void inOrder(Node root) {

	while (root) {   // 因为中序遍历需要先完成左边,所以说要先走到最左边才行

		while (root && root->leftTag == 0)    // 如果左边一直都不是线索,那么就一直往左找,直到找到一个左边是线索的为止,表示到头了
			root = root->left;

		printf("%c", root->element);   // 到最左边了再打印,中序开始

		while (root && root->rightTag == 1) {   // 打印完就该右边了,右边如果是线索化之后的结果,表示是下一个结点,那么就一路向前,直到不是为止
			root = root->right;
			printf("%c", root->element);   // 注意按着线索往下就是中序的结果,所以说沿途需要打印
		}
		root = root->right;  // 最后继续从右结点开始,重复上述操作

	}
}


// "后序遍历"线索化函数
Node pre3 = NULL;  // 这里我们需要一个pre3来保存后续结点的指向
void postOrderThreaded(Node root) {

	if (root == NULL) return;
	if (root->leftTag == 0) {
		postOrderThreaded(root->left);
		if (root->left) root->left->parent = root;  // 左边完事之后,如果不为空,那么就设定父子关系
	}
	if (root->rightTag == 0) {
		postOrderThreaded(root->right);
		if (root->right) root->right->parent = root;   // 右边完事之后,如果不为空,那么就设定父子关系
	}

	// ------ 线索化 -------
	if (root->left == NULL) {
		root->left = pre3;
		root->leftTag = 1;
	}
	if (pre3 && pre3->right == NULL) {
		pre3->right = root;
		pre3->rightTag = 1;
	}
	pre3 = root;
	// ------ 线索化 -------
}


// "后序遍历"一棵经过线索化的二叉树
void postOrder(Node root) {

	Node last = NULL, node = root;  // 这里需要两个暂存指针,一个记录上一次遍历的结点,还有一个从root开始
	while (node) {
		while (node->left != last && node->leftTag == 0)    // 依然是从整棵树最左边结点开始,和前面一样,只不过这里加入了防无限循环机制,看到下面就知道了
			node = node->left;
		while (node && node->rightTag == 1) {   // 左边完了还有右边,如果右边是线索,那么直接一路向前,也是跟前面一样的
			printf("%c", node->element);   // 沿途打印
			last = node;
			node = node->right;
		}
		if (node == root && node->right == last) {
			// 上面的操作完成之后,那么当前结点左右就结束了,此时就要去寻找其兄弟结点了,我们可以
			// 直接通过parent拿到兄弟结点,但是如果当前结点是根结点,需要特殊处理,因为根结点没有父结点了
			printf("%c", node->element);
			return;   // 根节点一定是最后一个,所以说直接返回就完事
		}
		while (node && node->right == last) {    // 如果当前结点的右孩子就是上一个遍历的结点,那么一直向前就行
			printf("%c", node->element);   // 直接打印当前结点
			last = node;
			node = node->parent;
		}
		// 到这里只有一种情况了,是从左子树上来的,那么当前结点的右边要么是线索要么是右子树,所以直接向右就完事
		if (node && node->rightTag == 0) {  // 如果不是线索,那就先走右边,如果是,等到下一轮再说
			node = node->right;
		}
	}
}





int main() {
	// 一颗二叉树的所有节点
	Node a = createNode('A');
	Node b = createNode('B');
	Node c = createNode('C');
	Node d = createNode('D');
	Node e = createNode('E');

	// 节点相连,形成二叉树
	a->left = b;
	b->left = d;
	a->right = c;
	b->right = e;

	// 前序遍历
	preOrderThreaded(a);	// 线索化
	preOrder(a);			// 遍历

	// 中序遍历
	inOrderThreaded(a);		// 线索化
	inOrder(a);				// 遍历

	// 后序遍历
	postOrderThreaded(a);	// 线索化
	postOrder(a);			// 遍历

	// 释放内存空间
	free(a);
	free(b);
	free(c);
	free(d);
	free(e);

	// 程序暂停
	getchar();

	// 结束主函数
	return 0;
}

二叉查找树

二叉查找树也叫二叉搜索树或是二叉排序树,它具有一定的规则:

  • 左子树中所有结点的值,均小于其根结点的值
  • 右子树中所有结点的值,均大于其根结点的值
  • 二叉搜索树的子树也是二叉搜索树

在这里插入图片描述

平衡二叉树

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

typedef int E;

typedef struct TreeNode {
    E element;
    struct TreeNode * left;
    struct TreeNode * right;
    int height;   // 每个结点需要记录当前子树的高度,便于计算平衡因子
} * Node;


Node createNode(E element){
    Node node = malloc(sizeof(struct TreeNode));
    node->left = node->right = NULL;
    node->element = element;
    node->height = 1;   // 初始化时,高度写为1就可以了
    return node;
}


int max(int a, int b){
    return a > b ? a : b;
}


int getHeight(Node root){
    if(root == NULL) return 0;
    return root->height;
}


Node leftRotation(Node root){  		// 左旋操作,实际上就是把左边结点拿上来
    Node newRoot = root->right;   	// 先得到左边结点
    root->right = newRoot->left;   	// 将左边结点的左子树丢到原本根结点的右边去
    newRoot->left = root;			// 现在新的根结点左边就是原本的跟结点了

    root->height = max(getHeight(root->right), getHeight(root->left)) + 1;
    newRoot->height = max(getHeight(newRoot->right), getHeight(newRoot->left)) + 1;
    return newRoot;
}


Node rightRotation(Node root){
    Node newRoot = root->left;
    root->left = newRoot->right;
    newRoot->right = root;

    root->height = max(getHeight(root->right), getHeight(root->left)) + 1;
    newRoot->height = max(getHeight(newRoot->right), getHeight(newRoot->left)) + 1;
    return newRoot;
}


Node leftRightRotation(Node root){
    root->left = leftRotation(root->left);
    return rightRotation(root);
}


Node rightLeftRightRotation(Node root){
    root->right = rightRotation(root->right);
    return leftRotation(root);
}


Node insert(Node root, E element){
    if(root == NULL) {    					// 如果结点为NULL,说明找到了插入位置,直接创建新的就完事
        root = createNode(element);
    }else if(root->element > element) {   // 和二叉搜索树一样,判断大小,该走哪边走哪边,直到找到对应插入位置
        root->left = insert(root->left, element);
        if(getHeight(root->left) - getHeight(root->right) > 1) {   	// 插入完成之后,需要计算平衡因子,看看是否失衡
            if(root->left->element > element) 			// 接着需要判断一下是插入了左子树的左边还是右边,如果是左边那边说明是LL,如果是右边那说明是LR
                root = rightRotation(root);   // LL型得到左旋之后的结果,得到新的根结点
            else
                root = leftRightRotation(root);    // LR型得到先左旋再右旋之后的结果,得到新的根结点
        }
    }else if(root->element < element){
        root->right = insert(root->right, element);
        if(getHeight(root->left) - getHeight(root->right) < -1){
            if(root->right->element < element)
                root = leftRotation(root);
            else
                root = rightLeftRightRotation(root);
        }
    }
    // 前面的操作完成之后记得更新一下树高度
    root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
    return root;  	// 最后返回root到上一级
}

红黑树

B树

基本认识

B树 ( Balance Tree ),它是专门为磁盘数据读取设计的一种度为 m 的查找树(多用于数据库)

下面是一棵度为4的B树:

在这里插入图片描述

详细规则

  • 树中每个结点最多含有m个孩子(m >= 2)比如上面就是m为4的4阶B树,最多有4个孩子
  • 除根结点和叶子结点外,其它每个结点至少有⌈m/2⌉个孩子,同理键值数量至少有⌈m/2⌉-1个
  • 若根结点不是叶子结点,则至少有2个孩子
  • 所有叶子结点都出现在同一层
  • 一个结点的包含多种信息(P0,K1,P1,K2,…,Kn,Pn),其中P为指向子树的指针,K为键值(关键字)
  • Ki (i=1…n)为键值,也就是每个结点保存的值,且键值按顺序升序排序K(i-1)< Ki
  • Pi为指向子树的指针,且指针Pi指向的子树中所有结点的键值均小于Ki,但都大于K(i-1)
  • 键值的个数n必须满足: ⌈m/2⌉-1 <= n <= m-1

动画演示

https://www.cs.usfca.edu/~galles/visualization/BTree.html

B+树

基本认识

  • B+树是B树的一种变体,有着比B树更高的查询性能
  • B+树上的叶子结点存储关键字以及相应记录的地址,叶子结点以上各层作为索引使用

B+树的应用

在这里插入图片描述

动画演示

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

代码演示

https://www.cnblogs.com/yangj-Blog/p/12992124.html

哈夫曼树

基本认识

  • 哈夫曼树(Huffman Tree)又称最优二叉树,是n个带权叶子结点构成的二叉树中,带权路径长度(WPL)最小的二叉树。
  • 哈夫曼编码是一种压缩编码的编码算法,是基于哈夫曼树的一种编码方式。
  • 在计算机数据处理中,哈夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现机率的方法得到的,出现机率高的字母使用较短的编码,反之出现机率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而实现无损压缩数据

详细内容

https://zhuanlan.zhihu.com/p/415467000

基本认识

  • 堆 (heap) 是计算机科学中一类特殊的数据结构统称
  • 堆通常是一个可以被看做一棵树的数组对象
  • 堆总是满足下列性质:
    • 堆中某个节点的值总是不大于或不小于其父节点的值
    • 堆总是一棵完全二叉树

详细内容

https://blog.csdn.net/xiaomucgwlmx/article/details/103522410

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值