✍前言:
今天让我们来一起看一下让大家头疼的二叉树,当然了二叉树在数据结构中有着举足轻重的地位,所以让我们来由浅入深的了解一下它!
树的概念 :
树是一个非线性的结构,为什么叫它树是因为它的样子就像是一个倒着的树,其中第一个节点就是我们常说的根节点。(根节点没有前驱节点)
我们需要注意的是,树的子节点是独立的,他们之间不能有交集,否则就不是树!!!
如图:
树的相关概念:
二叉树图:
节点的度:一个节点有几个孩子节点,比如1节点的度是2;
叶子节点:叶子节点是度为0的节点,比如6,7,8,9,10都是叶子节点;
分支节点:度不为0的节点,比如5号节点;
父节点:一个节点含有子节点,那么它就是子节点的父节点,比如5就是10的父节点;
兄弟节点:具有相同父节点的节点被称为兄弟节点,比如8,9节点就互为兄弟节点;
树的度:一个树的度就是树中度最大节点的度,比如这个树的度就是2;
节点的层次:第一层为1,第二曾为2,一次类推,注意的是有的树第一层是0;
树的高度:树的高度就是最大的层次,此树的高度为4;
堂兄弟节点:同一层的节点就是堂兄弟节点;
节点的祖先:从节点一直到根上的节点都可以称为祖先,比如5是10的祖先,1是所有的祖先;
子孙:以某节点为根的子树中任一节点都称为该节点的子孙,所有节点都是1的子孙;
森林:由N(N>0)个不相交的树就可以构成森林;
二叉树的相关概念:
1.二叉树的是节点的集合
2.空树
3.一个根节点和一个左子树和右子树组成
4.节点的度不可以大于2
特殊二叉树:
满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是 2^k-1,则它就是满二叉树。
完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
二叉树的性质:
1.若规定二叉树的根节点层数为1,那么第i层的节点数最多为2^(i-1)个节点
2.若规定二叉树的根节点层数为1,那么数深度为h的树最多节点为2^h-1;
3.对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有 n2= n0+1;
4.若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= log2(n+1);
5.对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为i的结点有:
a: 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点;
b:若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子;
c:若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子;
二叉树的存储结构:
顺序结构:
顺序结构其实就是用数组去存储节点,但是只是适用于完全二叉树,其他的不太适用,因为数组在逻辑和物理结构上都是连续的,如果不是完全二叉树,那么会产生很多的空间浪费。应用上一般是堆会采取这样的结构。
链式结构:
链式结构是用指针链接类似链表那样,但是这里采用的是二叉链和三叉链,这里应用的比较多,比如说是AVL树,红黑树等.
堆:
堆其实就是一个特殊的完全二叉树,把数据用数组的方式存储起来,再组合成一个大堆或者小堆。
堆的性质:
1.堆是一个完全二叉树;
2.堆的根节点最大的是大堆,最小的是小堆
3.大堆的父节点永远大于子节点,小堆反之;
堆的实现:
1.堆的向下调整算法:
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。
2堆的创建:
作为一个数组结构,我们可以从最后一个非叶子节点的子树开始调整,一直调节到根节点的树。
3.堆的时间复杂度:
因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
O(N);
4.堆的插入:
每次插入的时候都需要向上调整,调整的时间复杂度为logN
4.堆的删除:
每次删除的时候都需要首先把首元素和尾元素交换之后向下调整,调整的时间复杂度为logN
5.代码
heap.h
#pragma once
#include <iostream>
#include <vector>
#include <utility>
namespace rzj
{
template <class T>
struct less
{
bool operator()(const T& x,const T& y)
{
return x < y;
}
};
template <class T>
struct greater
{
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template <class T,class compare = less<T>>
class Heap
{
public:
void push(const T& key)
{
_hp.push_back(key);
_end = _hp.size();
AdjustUp();
}
void pushCreate(const T& key)
{
_hp.push_back(key);
_end = _hp.size();
}
void justupdown()
{
int last = (_end - 1 - 1) / 2;
while (last >= 0)
{
Adjustdown(last,_end);
last--;
}
}
void AdjustUp()
{
compare com;
size_t child = _hp.size() - 1;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_hp[child],_hp[parent]))//小堆
{
std::swap(_hp[child], _hp[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
void Adjustdown(int parent,int end)
{
compare com;
int child = parent * 2 + 1;
while (child < end)
{
if (child+1<end && com(_hp[child+1],_hp[child]))
{
child += 1;
}
if (com(_hp[child],_hp[parent]))
{
std::swap(_hp[child], _hp[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void pop()
{
std::swap(_hp[0], _hp[_hp.size() - 1]);
_hp.pop_back();
_end = _hp.size();
Adjustdown(0,_end);
}
void Heapsort()
{
while (_end>0)
{
std::swap(_hp[0], _hp[_end-1]);
Adjustdown(0,_end-1);
_end--;
}
}
void HeapTopK(int k)
{
FILE* fd = fopen("file.txt", "r");
Heap* hp = new Heap;
for (int i = 0; i < k; i++)
{
int r = 0;
fscanf(fd, "%d", &r);
hp->push(r);
}
_hp = hp->_hp;
int x = 0;
while (fscanf(fd, "%d", &x) != EOF)
{
if (x > _hp[0])
{
_hp[0] = x;
Adjustdown(0, k);
}
}
for (int i = 0; i < k; i++)
{
std::cout << _hp[i] << std::endl;
}
}
private:
void _AdjustUp(int k)
{
compare com;
size_t child = k - 1;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_hp[child], _hp[parent]))//小堆
{
std::swap(_hp[child], _hp[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
std::vector<T> _hp;
size_t _end = _hp.size();
};
}
test.cpp
#include "Heap.h"
int main()
{
rzj::Heap<int> p;
std::vector<int> v = { 8,1,3,6,4,7,9,5,2 };
for (auto e : v)
{
p.push(e);
}
p.pop();
p.pop();
return 0;
}
6.堆排序
建堆:
升序——建大堆
降序——建小堆
如果我们要用堆排序派排一段升序,我们就要建一个大堆,因为堆顶的数据永远是最大的,每一次把最大的数选出来后放到最后一个并与堆断开链接,依次就会把最大的放到最后,第二大的数字放到倒数第二个位置,最后就会形成一个升序顶顶顶排序序列。
7.TOPK问题
TOPK问题在实践中的用途还是比较多的,就比如在一万个数据中在最大的100的数,就要用到TOPK。
当我们求最大的100个数的时候我们就要建一个100个数的小堆,只要比堆顶(堆顶的数是100个数中最小的一个)大我们就把此数与堆顶替换,再向下调整,最后当一万个数都比较完的时候 这个堆中堆顶数就是这些数据中最大的100个数、
链式二叉树
1.链式二叉树的结构
链式二叉树的结构底层是类似与链表结构的结构,并非是数组,由根节点,左子树,右子树,且二叉数的结构定义是递归式的。
注意:AVL树,红黑树的底层结构都是搜索二叉树(三叉链)
2.二叉树的遍历
1.二叉树的遍历分为前序遍历,中序遍历,后续遍历,层序遍历
前序遍历:根节点,左子树,右子树。
中序遍历:左子树,根节点,右子树。
中序遍历:左子树,右子树,根节点。
层序遍历:先访问第一次,第二次依次遍历,需要用一个队列的结构辅助。
3.代码
Tree.cpp
#pragma once
#include <iostream>
#include <vector>
namespace rzj
{
template <class T>
struct TreeNode
{
TreeNode(const T& t)
:_val(t)
, _left(nullptr)
,_right(nullptr)
,_parents(nullptr)
{
}
T _val;
TreeNode* _left;
TreeNode* _right;
TreeNode* _parents;
};
template <class T>
class Tree
{
typedef TreeNode<T> Node;
public:
bool insert(const T& t)
{
if (_root == nullptr)
{
_root = new Node(t);
return true;
}
Node* cur = _root;
Node* parent = _root;
while (cur)
{
if (cur->_val > t)
{
parent =cur;
cur = cur->_left;
}
else if (cur->_val < t)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(t);
cur->_parents = parent;
if (parent->_val < t)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
void inorder()
{
_inorder(_root);
}
void preorder()
{
_preorder(_root);
}
void postorder()
{
_postorder(_root);
}
size_t heigh()
{
return _heigh(_root);
}
size_t treesize()
{
return _treesize(_root);
}
size_t levelK(int k)
{
return _levelK(_root,k);
}
size_t leafsize()
{
return _leafsize(_root);
}
private:
size_t _leafsize(Node* root)
{
if (root == nullptr)
{
return 0;
}
if (root->_left == nullptr && root->_right == nullptr)
{
return 1;
}
else
{
return _leafsize(root->_left) + _leafsize(root->_right);
}
}
size_t _levelK(Node* root,int k)
{
if (root == nullptr)
{
return 0;
}
if (k == 1)
{
return 1;
}
return _levelK(root->_left, k - 1) + _levelK(root->_right, k - 1);
}
size_t _treesize(Node* root)
{
if (root == nullptr)
{
return 0;
}
return _treesize(root->_left) + _treesize(root->_right) + 1;
}
size_t _heigh(Node* root)
{
if (root == nullptr)
{
return 0;
}
int lefth = _heigh(root->_left);
int righth = _heigh(root->_right);
return 1 + (lefth > righth ? lefth : righth);
}
void _inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_inorder(root->_left);
std::cout << root->_val << " ";
_inorder(root->_right);
}
void _preorder(Node* root)
{
if (root == nullptr)
{
return;
}
std::cout << root->_val << " ";
_preorder(root->_left);
_preorder(root->_right);
}
void _postorder(Node* root)
{
if (root == nullptr)
{
return;
}
_postorder(root->_left);
_postorder(root->_right);
std::cout << root->_val << " ";
}
Node* _root = nullptr;
};
void test()
{
std::vector<int> v = { 5,4,8,7,3,6,1,32,9 };
Tree<int> tr;
for (auto e : v)
{
tr.insert(e);
}
std::cout << tr.heigh() << std::endl;
std::cout << tr.treesize() << std::endl;
std::cout << tr.leafsize() << std::endl;
std::cout << tr.levelK(1) << std::endl;
/*tr.inorder();
std::cout << std::endl;
tr.preorder();
std::cout << std::endl;
tr.postorder();*/
}
}
以上就是全部代码,还有很多需要优化的地方,我之后也会继续改良,如有错误和不足的地方。欢迎大家指正!