数据结构丨树形结构

救命!我在老师讲【栈】和【队列】的课上不小心睡过去了..........是睡了很久的那种。。(感觉我的精神只能支撑一节半的课,每次到第二节课的后半节我就会倒下。...

不过今天讲的【树】有好好听了!(指听了1.5节)

Anyway,我们今天先来讲讲树!准确来说大标题是:树 形 结 构 。!(老师专门指出了大标题不是"树"。)

树形结构(Tree Structure)

树(Tree)形结构包括:树、森林(后续好像只有概念了,全转换成二叉树去了)、二叉树、哈夫曼树(这个我只会写用数组存的版本啊啊啊啊不好意思)...

树(Tree)

类似族谱(Family Tree)!(我当时可兴奋了XD。For Tangle Tower and Harry Potter

- (暂时不讨论自由树。有根)树的定义是递归的!

- 有一些表示方法,比如文氏图、平行线法(我喜欢这个)、...

- 有一些术语!(但基本可以猜到是什么意思)

结点(Node)、结点的度(Degree of Node)(我比较喜欢称之为宽度!)、树的度(Degree of Tree)(取最大的结点的度)、

叶子结点(Leaf Node)(终端结点)、分支结点(Branch Node)(非终端结点、内部结点)、

孩子(Child)、双亲(Parent)、祖先(Ancestor)(可以理解为继承过的所有结点)、子孙(Descendant)、兄弟(Brother)(同一双亲的孩子)、堂兄弟(Sibling)(草)

结点的层次(Level of Node)(根结点的层次规定为1)、树的深度(Depth of Tree)(高度)、

无序树(Unordered Tree)、有序树(Ordered Tree)、

森林(Forest):m(m≥0)棵树的集合。(老师指着一堆空气说这是森林wwww我真的要笑死。)

- 有一些基本的方法(函数)!比如插入、删除(从族谱除名(株连九族.....

树 to 二叉树:(我有空画个图示吧!文字描述得我想死(。写得想死看得也想死)

二叉树 to 树:

森林(Forest)

0个或一堆树。

森林 to 二叉树:把树根们当成同级的,按一般方法串起来。(有空画图示)

二叉树(Binary Tree)

二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单(又开始最简单了。。)且最重要(太好了!)的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树。

老师:我比较喜欢叫它异叉树。(用粤语读试试!

PS:度为2的树不一定是二叉树哦!二叉树是有序树,二叉树的左/右结点是有特殊意义的。

关于左结点和右结点分别代表什么,请听我慢慢道来!(暂时不知道怎么解释。。

左结点是连接(正常树中(父节点和第一个子节点)的结点,

右结点是连接(同级的子节点)的。(也就是说斜向右下角的对角线上的结点们在正常树中是兄弟关系!

特殊的二叉树们

正则二叉树:每个叉的分叉数都是2

平衡二叉树:平衡高度

理想平衡二叉树:倒数第二层是满的

满二叉树:完全满(金字塔的感觉)

完全二叉树:按顺序走的尽量满(可以用数组存)

存储结构

顺序结构(数组)(一般用在完全二叉树里)、链式结构(链表)

我流链式结构的结点Node:丨leftdataright

(哭死... 我又开始在大半夜写作业了。我真的很需要一个电子宠物在旁边陪我!!)

参考:

C/C++编程技术:数据结构与算法之二叉树(一个小时掌握)_哔哩哔哩_bilibili

二叉树的先序、中序、后序遍历C++_二叉树的先序,中序,后序遍历-CSDN博客】详细+好看。

完全二叉树的顺序储存结构(C++完整代码)_构造n节点的完全二叉树,先序输出-CSDN博客】完整代码可以用!

【纯干货】三分钟教会你遍历二叉树!学不会举报我!!_哔哩哔哩_bilibili】评论好牛:本质上是代码里面的访问顺序,左子树递归和右子树递归。先序是第一次访问节点,就直接写上,中序是第二次访问节点,后续是递归结束,最后离开这个节点的顺序。第一次线是第一次访问,过节点下的记号是第二次访问,过节点的右边记号是离开这个节点。

二叉树的创建和基本操作(详解)_建立二叉树-CSDN博客

碎碎念:

每一次写数据结构作业都觉得好无助。但是仔细想想,我已经无助过很多次了,加上我从来都不是一个人在学(不是指大家都要写这个作业,而是我的脑子里不止一个我自己。),所以没有什么好担心的!最起码不会死掉。

遍历

遍历方式:

先/前序遍历(PreOrder):根左右

中序遍历(InOrder):左根右

后序遍历(PostOrder):左右根

- 每一个三人组都按照同一个遍历方式进行

- 三种遍历方式又分别有递归的和非递归

- 先序遍历和中序遍历可以确定一棵二叉树。(后面写作业的时候我天天拿这俩来输出。)

递归遍历

递归遍历的实现是比较简单的!搞清楚顺序就好了。

先序遍历(PreOrder)(根左右)

// 先序遍历(根左右)递归
void BinaryTree::PreOrder(BinTreeNode* root)
{
    if (root != NULL) // root == NULL是递归终止条件
    {
        // 1. 输出
        cout << root->data << " ";
        // 2. 遍历左子树
        PreOrder(root->left);
        // 3. 遍历右子树
        PreOrder(root->right);
    }
}

中序遍历(InOrder)(左根右)

// 中序遍历(左根右)递归
void BinaryTree::InOrder(BinTreeNode* root)
{
    if (root != NULL) // root == NULL是递归终止条件
    {
        // 1. 遍历左子树
        InOrder(root->left);
        // 2. 输出
        cout << root->data << " ";
        // 3. 遍历右子树
        InOrder(root->right);
    }
}

后序遍历(PostOrder)(左右根)

// 后序遍历(左右根)递归
void BinaryTree::PostOrder(BinTreeNode* root)
{
    if (root != NULL) // root == NULL是递归终止条件
    {
        // 1. 遍历左子树
        PostOrder(root->left);
        // 2. 遍历右子树
        PostOrder(root->right);
        // 3. 输出
        cout << root->data << " ";
    }
}

非递归遍历(迭代遍历)

需要用到栈!

先序非递归遍历(PreOrder)(根左右)

小过程:

1. 定义一个移动指针(我们的劳模指针p),指向根部root

2. 沿着左边走,直到走到空的位置

(边走边打印,并记录下来(入栈))

3. 回退(出栈)(判断出栈元素是否存在右边)

4. 如果存在右边,重复步骤2、3

我的脑子:

p就是用来指你的路径的(不过回退了就删掉)

void InOrderByStack(BinTreeNode* root) // 中序遍历(左根右)迭代
{
	// 0. root == NULL 直接结束
	if (root == NULL)
	{
		return;
	}
	// 1. 定义一个移动指针(我们的劳模指针p),指向根部root
	struct BinTreeNode* p = root;
	struct BinTreeNode* stack[100];
	int stackTop = -1;

	while (stackTop != -1 || p != NULL) {
		// 2. 沿着左边走,直到走到空的位置
		while (p != NULL) {
			// 边走边打印
			cout << p->data << " ";
			// 并记录下来(入栈)
			stack[++stackTop] = p;
			p = p->left;
		}
		// 3. 回退(出栈)(判断出栈元素是否存在右边)
		if (stackTop != -1) {
			p = stack[stackTop--];
			p = p->right;
		}
	} // 4. 如果存在右边,重复步骤2、3
}

中序非递归遍历(InOrder)(左根右)

过程:

1. 创造p

2. 边走边记录(不用打印)

3. 回退,出栈,打印

后序非递归遍历(PostOrder)(左右根)

难!

平衡二叉树

暂时先写这么多!

又!学了好多东西(堆、哈夫曼树)。。。TT

堆(heap)

堆(heap)

完全二叉树:按顺序走的尽量满(可以用数组存)

大根堆(最大堆)

小根堆(最小堆)

向上调整、向下调整...

哈夫曼树(Huffman Tree)

(这个有实验作业!所以后续可能会讲。)

哈夫曼树(Huffman Tree)/ 最优二叉树。可以提高传送信息的效率!(编码最短?)

(树的)带权路径长度 WPL(Weighted Path Length):(数值 x 路径长度)的总和

- 只计算叶子结点

- 在哈夫曼编码中的意义:出现频率 x 编码长度

哈夫曼树的结点个数为:n = 2n' - 1 (n' 表示叶子节点)所以...大概可以开数组存?

参考:【赫夫曼编码树(图解+完整代码)_哈夫曼数编码_~在下小吴的博客-CSDN博客

【数据结构C/C++】C语言哈夫曼编码与解码实现细则(附代码以及详细实现解释)_哈夫曼编码代码_ZhangBlossom的博客-CSDN博客

hehe...【C/C++中函数参数传递的三种方式:值传递,指针传递,引用传递_引用传参-CSDN博客

哈夫曼编码

一般设定为左0右1。

参考:【【数据结构】构造哈夫曼树手写代码_哔哩哔哩_bilibili】【[数据结构] 哈夫曼树HuffmanTree、哈夫曼编码的c/c++语言实现_怎么使用c++实现使用哈夫曼树求哈夫曼编码-CSDN博客

哈夫曼树(C++实现)_哈夫曼树c++-CSDN博客

Emmmmmmm顺带一提,这个作业不知道怎么的就写完了。。(最起码哈夫曼编码的部分是纯手工的!现在被困在文件读写那块。。

二叉查找树(BST)

二叉查找树/二叉排序树(BST,Binary Search Tree)。

特征:左结点 < 根结点 < 右结点(中序遍历递增)

// 节点 BinTreeNode
struct Node {
	int data;    // 数据
	Node* left;  // 指向左孩子节点的指针
	Node* right; // 指向右孩子节点的指针

	Node() :left(NULL), right(NULL) {} // 不懂TT
	Node(int x, Node* l = NULL, Node* r = NULL, bool ava = true)
		: data(x), left(l), right(r) {} // 还是不懂TT
	// 我真的。。举三反一是有一套的hehehehe
	~Node() {}
};

// 二叉树 Binary Search Tree
class BSTree {
public:
	Node* root; // 根节点

	BSTree() {}  // 构造函数
	~BSTree() {} // 析构函数

	void PreOrder(Node* root); // 先序遍历(根左右)递归
	void InOrder(Node* root);  // 中序遍历(左根右)递归

	Node* Create(int n);          // 建立(在插入的基础上建立)
	Node* Insert(int x, Node* t); // 插入(递归)
	Node* Search(int x, Node* p); // 查找(递归)
};
// 插入(递归)
Node* BSTree::Insert(int x, Node* t) {
	// 树是空树
	if (t == NULL) {
		Node* p = new Node; // 建立根节点
		p->data = x;
		if (root == NULL) // 整个树都是空树
			root = p;
		return p;
	}

	if (x < t->data) {
		t->left = Insert(x, t->left);  // 插入到树的左子树
	}
	else if (x > t->data) {
		t->right = Insert(x, t->right); // 插入到树的右子树
	}
	// else if (x == t->data) // 重复
	return t; // t!!! 不是t->left/right呜呜呜呜呜呜卡了我好久!!
}
// 建立
Node* BSTree::Create(int n) {
	int x;
	root = NULL;
	for (int i = 0; i < n; i++) {
		cin >> x;
		if (i == 0)
			root = Insert(x, root);
		else
			Insert(x, root);
	}
	// cout << "root " << root << endl;
	return root;
}
// 查找(递归)
Node* BSTree::Search(int x, Node* p) {
    // 1. 判断空 - 没查到
	if (p == NULL) return NULL; // 是没查到的意思!相当于往下查查查发现查不动了
    // 2. 查到了
	if (x == p->data) {
		return p;
	}
    // 3. 不是上述情况,那我们继续查
	else if (x < p->data) return Search(x, p->left);  // 在左子树中查找
	else if (x > p->data) return Search(x, p->right); // 在右子树中查找
}
// 查找(非递归) // 空间复杂度更低
Node* BSTree::Search(int x) {
    Node* t = root;
    // 1. 判断空
	while (t != NULL) {
        // 2. 查到了
	    if (x == t->data) {
		    return t;
    	}
        // 3. 不是上述情况,那我们继续查
	    else if (x < t->data) t = t->left);  // 在左子树中查找
	    else if (x > t->data) t = t->right); // 在右子树中查找
    }
    return NULL; // 没找到
}

作业

救命。巨难无比。。

参考:

二叉树的前序、中序、后序遍历以及根据前序和中序或中序和后序生成二叉树_输入后序遍历序列和中序遍历序列以生成二叉树二叉链式结构,并采用前序遍历序列输_Karan_01的博客-CSDN博客

【二叉树构建与遍历2】后序遍历+中序遍历构建一个二叉树并输出先序遍历 C++实现-CSDN博客

【精选】C/C++数据结构之动态数组篇-CSDN博客

用递归法把二叉树的叶子结点按从左到右的顺序连成一个单链表_实现双向循环链表倒置的程序-CSDN博客

设计一个算法将二叉树的叶结点按从左到右的顺序连成一个单链表,表头指针为head,链接时用叶结点的右指针域来存放单链表指针-CSDN博客

新作业。难得想死。。

参考:

二叉搜索树(查找、插入、删除的讲解实现+图文并茂)_二叉搜索树删除_bang___bang_的博客-CSDN博客

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值