数据结构“二叉树“

✍前言:
        今天让我们来一起看一下让大家头疼的二叉树,当然了二叉树在数据结构中有着举足轻重的地位,所以让我们来由浅入深的了解一下它!

树的概念 :

        树是一个非线性的结构,为什么叫它树是因为它的样子就像是一个倒着的树,其中第一个节点就是我们常说的根节点。(根节点没有前驱节点)

        我们需要注意的是,树的子节点是独立的,他们之间不能有交集,否则就不是树!!!

如图:

        

树的相关概念: 

 二叉树图:

节点的度:一个节点有几个孩子节点,比如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();*/
	}
}

 以上就是全部代码,还有很多需要优化的地方,我之后也会继续改良,如有错误和不足的地方。欢迎大家指正!

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值