用来表示具有结构层次的数据,应用:
软件工程技术:模块化技术
根:
子树:
在树中,每个元素都代表一个节点。
树的级: 根是一级,根的孩子是二级,一次往下,有三级,四级。。。
树的高度(深度): 树中级的个数
树中元素的度:一个元素的度指其孩子的个数,一棵树的度是其元素的度的最大值
定义:一棵二叉树t是有限个元素的集合,当二叉树非空时,其中有一个元素是根,余下的元素被划分为两颗二叉树,称为t的左子树和右子树。
二叉树和树的区别:
1. 每个元素恰好又两棵子树。
2. 元素的子树是有序的,有左右之分
3. 树的子树是无序的
4. 二叉树可以为空
二叉树的特性:
1. 一个二叉树有n个元素,则它有n-1条边。(除过根节点外,所有元素有且只有一个父节点)
2. 二叉树的高度为h,则他的元素个数x满足
3. 一棵二叉树有n个元素(n>0),则它的高度h满足
4. 高度为h的二叉树有个元素,则称为满二叉树
5 完全二叉树:
对于高度为h的满二叉树,
按照从左到右,从上到下的顺序编号,从1-2^h-1, 从二叉树中删除k个编号为2^h-i的元素(1<=i<=k), 所得到的二叉树被称为完全二叉树。
6. 假设一个完全二叉树的编号为i, 1<=i<=n,则有一下关系:
a. i=1, 则该元素为根节点,i>1,其父节点的编号为[1/2]
b. 2i>n, 则该元素没有左孩子,否则,左孩子的编号为2i
c 2i+1>n, 该元素无右孩子, 否则右孩子的编号为2i+1
1. 数组描述:
在数组描述中,二叉树中的元素按照其编号存储在数组的相应位置
可以看到,具有n个节点的二叉树,可能最多需要2^n个存储空间来存储(右斜二叉树),这样会浪费大量的存储空间,只有当二叉树缺少的元素数目较少时,才会用到数组描述
2.链表描述
用一个节点描述二叉树的一个元素,系欸但包含是两个指针(leftchild, rightchild)和数据域,先实现二叉树结点的数据描述
template
struct binaryTreeNode // 定义二叉树的节点
{
T element;
binaryTreeNode* leftchild; // 左子树
binaryTreeNode* rightchild; // 右子树
binaryTreeNode()
{
leftchild = NULL;
rightchild = NULL;
}
binaryTreeNode(T value)
{
element = value;
leftchild = NULL;
rightchild = NULL;
}
binaryTreeNode(T value, binaryTreeNode* theleftchild, binaryTreeNode* therightchild)
{
element = value;
leftchild - theleftchild;
rightchild = therightchild;
}
};
由于一个n个节点的树有n-1条边,所以值为NULL的指针个数为2*n-(n-1) = n+1个
二叉树常用操作:
有四种常用的二叉树遍历方法:
1.前序遍历
2.中序遍历
3.后序遍历
4 层次遍历
且者四种遍历方法的时间复杂度均为O(n)
二叉树的ADT:
// 二叉树的抽象基类
#ifndef BINARY_TREE_ABC_H
#define BINARY_TREE_ABC_H
template
class binaryTreeABC
{
public:
virtual bool empty() const=0;
virtual int size() const=0;
virtual void preOrder(void (*)(T*)) = 0; // 前序遍历
virtual void inOrder(void (*)(T*)) = 0; // 中序遍历
virtual void postOrder(void (*)(T*)) = 0; // 后序遍历
//virtual void levelOrder(void (*),(T*)) = 0; // 层级遍历
// void (*) (T*)是一种函数类型,其参数是T*, 返回值为void
};
#endif
二叉树的实现
binaryTree类的是实现:
#ifndef BINARY_TREE_H
#define BINARY_TREE_H
#include "E:\back_up\code\c_plus_code\binaryTree\external_file\binaryTreeABC.h"
#include
using namespace std;
template
struct binaryTreeNode // 定义二叉树的节点
{
T element;
binaryTreeNode* leftchild; // 左子树
binaryTreeNode* rightchild; // 右子树
binaryTreeNode()
{
leftchild = NULL;
rightchild = NULL;
}
binaryTreeNode(T value)
{
element = value;
leftchild = NULL;
rightchild = NULL;
}
binaryTreeNode(T value, binaryTreeNode* theleftchild, binaryTreeNode* therightchild)
{
element = value;
leftchild - theleftchild;
rightchild = therightchild;
}
};
template
class binaryTree : public binaryTreeABC >
{
private:
binaryTreeNode * root; // 根节点
int treeSize; // 节点个数
static void (*visit)(binaryTreeNode* t); // 访问函数 静态数据成员visit
// visit是一个函数指针, 返回值void, 参数 binaryTreeNode*
static void preOrder(binaryTreeNode* t);
static void inOrder(binaryTreeNode* t);
static void postOrder(binaryTreeNode* t);
//static void levelOrder(binaryTreeNode* t);
static void dispose(binaryTreeNode *t) // 删除一个节点
{
delete t;
}
public:
binaryTree(); // 构造方法
~binaryTree(); // 析构方法
bool empty() const
{
return treeSize==0;
}
int size() const
{
return treeSize;
}
void preOrder(void (*theVisit) (binaryTreeNode*)) // 公有的方法 函数签名()
{
visit = theVisit;
preOrder(root); // 这里调用的是私有的preOrder()函数
// 在实际的应用中,用户只能通过实例化的对象调用公有的方法,公有的方法内,调用了私有的方法
}
void inOrder(void (*theVisit) (binaryTreeNode*))
{
visit = theVisit;
inOrder(root);
}
void postOrder(void (*theVisit) (binaryTreeNode*))
{
visit = theVisit;
postOrder(root);
}
//void levelOrder(void (*) binaryTreeNode*);
void erase()
{
postOrder(dispose);
root = NULL;
treeSize = 0;
}
};
// 类的实现
template
binaryTree::binaryTree()
{
root = NULL;
treeSize = 0;
}
template
binaryTree::~binaryTree()
{
erase();
}
// 实现类中私有的方法
template
void binaryTree::preOrder(binaryTreeNode* t)
{
// 前序遍历
if(t!=NULL) // 通过递归的方法访问系欸但
{
visit(t);
preOrder(t->leftchild);
preOrder(t->rightchild);
}
}
template
void binaryTree::inOrder(binaryTreeNode* t)
{
// 中序遍历
if(t!=NULL)
{
//binaryTree::visit(t);
inOrder(t->leftchild);
visit(t);
inOrder(t->rightchild);
}
}
template
void binaryTree::postOrder(binaryTreeNode* t)
{
// 后序遍历
if(t!=NULL)
{
postOrder(t->leftchild);
postOrder(t->rightchild);
visit(t);
}
}
//template
//void binaryTree::levelOrder(void (*) (binaryTreeNode
对上面的二叉树的代码实现做了一些修改,问题已经改正:binaryTree.h文件
#ifndef BINARY_TREE_H
#define BINARY_TREE_H
// #include "E:\back_up\code\c_plus_code\binaryTree\external_file\binaryTreeABC.h"
#include
#include
using namespace std;
template
struct binaryTreeNode // 定义二叉树的节点
{
T element;
binaryTreeNode* leftchild; // 左子树
binaryTreeNode* rightchild; // 右子树
binaryTreeNode()
{
leftchild = NULL;
rightchild = NULL;
}
binaryTreeNode(T value)
{
element = value;
leftchild = NULL;
rightchild = NULL;
}
binaryTreeNode(T value, binaryTreeNode* theleftchild, binaryTreeNode* therightchild)
{
element = value;
leftchild = theleftchild;
rightchild = therightchild;
}
};
// 实现二叉树的类
template
class binaryTree
{
private:
binaryTreeNode* mroot; // 二叉树根节点
int treeSize; // 二叉树的节点个数
// 定义一些函数
int getHeight(binaryTreeNode* root); // 获取二叉树的高度;
void addNode(T value, int direction, binaryTreeNode*& root); // 给二叉树添加节点
void distory(binaryTreeNode*& root);
void preOrder(binaryTreeNode* root); // 前序遍历
void inOrder(binaryTreeNode* root); // 中序遍历
void postOrder(binaryTreeNode* root); // 后序遍历
public:
binaryTree(T rootValue); // 构造函数
~binaryTree(); // 析构函数
bool empty() const;
int size() const;
int getHeight();
void addNode(T value, int direction);
void distory();
void preOrder();
void inOrder();
void postOrder();
//void levelOrder(); // 二叉树的层次遍历需要用到队列,用于存储二叉树的
};
template
binaryTree::binaryTree(T rootValue) // 构造函数, 创建根节点
{
mroot = new binaryTreeNode(rootValue);
treeSize = 1;
}
template
binaryTree::~binaryTree()
{
distory(mroot);
//mroot = NULL;
treeSize = 0;
}
template
bool binaryTree::empty() const
{
return treeSize==0;
}
template
int binaryTree::size() const
{
return treeSize;
}
template
void binaryTree::addNode(T value, int direction, binaryTreeNode*& root)
{
// 添加一个节点
// 参数: value: 节点值 int:0/1 left or right root: 根节点 指针类型的引用
//cout << "AddNode " << value << " " << direction << endl;
if(direction == 0) // 左节点
{
if(root->leftchild == NULL) // 插入节点
{
root->leftchild = new binaryTreeNode(value); // 左孩子
treeSize++;
}
else if(root->rightchild == NULL) // 再判断右孩子
{
root->rightchild = new binaryTreeNode(value);
treeSize++;
}
else
{
addNode(value, direction, root->leftchild);
}
//treeSize++;
}
else
{
if(root->rightchild == NULL) // 右孩子
{
root->rightchild = new binaryTreeNode(value);
treeSize++;
}
else if(root->leftchild == NULL) // 再判断左孩子
{
root->leftchild = new binaryTreeNode(value);
treeSize++;
}
else
{
addNode(value, direction, root->rightchild);
}
//treeSize++;
}
//return root;
//cout << "Add Finished!" << endl;
}
template
void binaryTree::addNode(T value, int direction)
{
//cout << "test: " << mroot->leftchild << endl;
addNode(value, direction, mroot);
}
template
void binaryTree::distory(binaryTreeNode*& root) // 有参数的distory函数
{
if(root!=NULL)
{
distory(root->leftchild);
distory(root->rightchild);
delete root;
}
}
template
void binaryTree::distory()
{
distory(mroot);
}
template
void binaryTree::preOrder(binaryTreeNode* root) // 有参数的
{
if(root != NULL)
{
cout << root->element << " ";
preOrder(root->leftchild);
preOrder(root->rightchild);
}
}
template
void binaryTree::preOrder()
{
cout << "前序遍历:";
preOrder(mroot);
cout << endl;
}
template
void binaryTree::inOrder(binaryTreeNode* root)
{
if(root!=NULL)
{
inOrder(root->leftchild);
cout << root->element << " ";
inOrder(root->rightchild);
}
}
template
void binaryTree::inOrder()
{
cout << "中序遍历:";
inOrder(mroot);
cout << endl;
}
template
void binaryTree::postOrder(binaryTreeNode* root)
{
if(root!=NULL)
{
postOrder(root->leftchild);
postOrder(root->rightchild);
cout << root->element << " ";
}
}
template
void binaryTree::postOrder()
{
cout << "后序遍历:";
postOrder(mroot);
cout << endl;
}
template
int binaryTree::getHeight(binaryTreeNode* root) // 获取二叉树的高度
{
if(root == NULL)
{
return 0;
}
else
{
int dep_L = getHeight(root->leftchild); // 左部分
int dep_R = getHeight(root->rightchild); // 右部分
return (dep_L>dep_R)? dep_L+1: dep_R+1;
}
}
template
int binaryTree::getHeight()
{
return getHeight(mroot);
}
#endif
测试代码:
#include
#include
#include
#include "E:\back_up\code\c_plus_code\binaryTree\external_file\binarytree.h"
using namespace std;
int main()
{
binaryTree tree(0);
for(int i=1; i<=4; i++)
{
tree.addNode(i, 0);
}
for(int i=1; i<=4; i++)
{
tree.addNode(i, 1);
}
cout << "Tree size: " << tree.size() << endl;
tree.preOrder();
tree.inOrder();
tree.postOrder();
cout << "The tree height is: " << tree.getHeight() << endl;
return 0;
}
层次遍历中。需要从底层到顶层,从左到右进行遍历,所以需要用到队列。
关于队列的实现请参考:https://blog.csdn.net/zj1131190425/article/details/88090905
template
void binaryTree::levelOrder(binaryTreeNode* root)
{
queue*> q; // 队列,用于保存二叉树的节点
while(root!=NULL)
{
cout << root->element << " ";
if(root->leftchild!=NULL) // 保存当前节点的左右孩子
q.push(root->leftchild);
if(root->rightchild!=NULL)
q.push(root->rightchild);
try
{
root = q.front(); // 更新root的值
}
catch(queueEmptyException& ex)
{
return;
}
q.pop();
}
}
void binaryTree::levelOrder(binaryTreeNode* root)
{
levelOrder(mroot);
}
----------------------------------------------分割线-----------------------------------------------------
优先级队列:
与队列不同,优先级队列中的元素出队列顺序由元素的优先级决定。
实现优先级队列效率较高的数据结构是堆:堆是一颗完全二叉树,所以使用数组表示效率最高。优先级队列是一个或者多个元素的 集合,每个元素都有一个优先权。优先级队列的操作,push(), pop(), top().
最大优先级队列:查找删除从优先级最高的元素开始
最小优先级队列: 查找删除从优先级最低的元素开始
优先级相同的,按照任意顺序处理:
大(小)根树: 每个节点的值都大于(小于)其子节点的树
大根堆:一个大根堆既是大根树也是完全二叉树
小根堆:。。。
因为堆是完全二叉树,因此可以用数组进行描述
堆的插入和删除操作:
堆的插入和删除操作必须要保证堆的结构的不变(依然保持为大根树或者小根树),所以把新元素插入新的节点,需要沿着从新节点到根节点的路径,执行一趟起泡操作,将新元素与父节点的元素进行比较交换,直到后者大于或者等于前者为止。
堆的删除操作,删除的是根节点的元素,所以删除了根节点的元素后,需要对大根堆的布局进行重新调整。
详细介绍一下关于大根堆的删除,插入,和初始化操作:
插入操作:
例如,在a中所示的大根堆中插入元素21,因为大根堆一定是完全二叉树,所以插入的位置是一定的。需要进行的操作是:把新元素插入新的节点,需要沿着从新节点到根节点的路径,执行一趟起泡操作,将新元素与父节点的元素进行比较交换,直到后者大于或者等于前者为止。
例如:将1插入,则不会改变大根堆的结构,所以直接插入到元素2的左孩子位置
如果插入元素5,则会破坏大根堆的结构5>2,所以需要将元素2移动到其左孩子的位置,再将5插入到原来2的位置
如果插入的是30,则需要将2移动到其左孩子位置,将根节点20移动到2的位置,将30插入根节点。
插入的过程:
template
void maxheap::push(T theElement) // 在大堆根中插入元素
{
ensureLength(); // 先检测数组长度, 动态分配内存大小
int currentNode = heapSize+1; // 新节点的编号
while(currentNode!=1 && element[currentNode/2]
删除操作:
假设要对图12-3所示的大堆根执行删除操作:删除元素21,则大堆根的结构需要重新组织。将元素2取出,删除2所在的节点,得到一个完全二叉树,但此时根节点为空,且2不能放入根节点,所以需要把根节点的左右孩子中的较大者移到根节点。此时位置3为一个空位,且位置3没有左右孩子,所以可以将2插入到位置3.
假设接着继续删除,删除元素20后,大根堆的结构如图12-4(b)所示。此时需要调整元素的位置。在12-4(a)中,将元素10取出(始终是最后一个元素取出),根节点的左右孩子中较大的元素15放入根节点,元素14>10,所以14上移到位置2,元素10放入位置3.
删除过程:
template
void maxheap::pop() // 删除大堆根的根元素
{
if(heapSize==0)
{
throw heap_empty_exception(heapSize);
}
// 删除根元素
element[1].~T();
T last_element = element[heapSize--]; // 获取最后一个元素 // heapsize-1
// 重新建堆
// 寻找最后一个元素的插入位置
int currentNode = 1;
int child = 2; // currentNode的孩子
while(child<=heapSize) // 这里是child<=heapSize,即使剩余最后两个元素也需要重构最大堆
{
// 找到currentNOde的大孩子
if(child<=heapSize && element[child]=element[child]) // 找到插入的位置
{
break;
}
// 否则
element[currentNode] = element[child]; // 将大孩子网上移动
currentNode = child;
child *= 2;
}
element[currentNode] = last_element; // 插入位置
}
1.最大堆的实现, maxHeap.h文件:
// maxheap实现
// 因为最大堆是完全二叉树,
// 所以采用数组描述
#ifndef MAXHEAP_H
#define MAXHEAP_H
#include
#include
#include "E:\back_up\code\c_plus_code\binaryTree\external_file\heapEmptyException.h"
using namespace std;
template
class maxheap
{
private:
int arrayLength;
int heapSize;
T* element; // 堆中的元素
void ensureLength();
public:
maxheap(int length=10); // 构造函数
~maxheap();
bool empty() const;
int size() const;
void initilize(T *theHeap, int theSize); // 初始化一个非空的大堆根;
void push(T theElement); // 插入一个元素
T top(); // 返回根节点的元素
void pop(); // 删除根节点的元素
void print_heap();
};
template
maxheap::maxheap(int length)
{
heapSize = 0; // 堆中元素的个数
arrayLength = length;
element = new T[arrayLength+1]; // 堆中元素编号从位置1开始
}
template
maxheap::~maxheap() // 析构函数
{
delete [] element;
}
template
void maxheap::ensureLength()
{
// 动态分配内存的大小
if(heapSize>=arrayLength-1) // 编号原因,所以减一
{
T* old = element;
element = new T[2*arrayLength+1];
for(int i=1; i<=heapSize; i++)
{
element[i] = old[i];
}
delete [] old;
arrayLength = 2*arrayLength+1;
}
// 如果元素很少,也需要重新分配内存空间
if(heapSize>1 && heapSize
bool maxheap::empty() const
{
return heapSize==0;
}
template
int maxheap::size() const
{
return heapSize;
}
template
void maxheap::initilize(T* theHeap, int theSize) // 初始化非空的大堆根
{
delete [] element;
// element = theHeap;
heapSize = theSize; // 获取堆中元素的个数
ensureLength(); // 动态分配内存
for(int i=0; i=1; root--) // heapsize/2是最后一个元素的父节点
{
T root_element = element[root]; // 对应的父节点
int child = root*2; // 父节点的左孩子
while(child<=heapSize)
{
if(child=element[child]) // 根节点与大孩子比较
{
break; // 不用进行调整
}
// 否则需要调整位置 child/2是根节点的位置
element[child/2] = element[child];
child *= 2; // 为了跳出while循环
}
element[child/2] = root_element;
}
}
template
T maxheap::top()
{
return element[1]; // 返回大根堆的根;
}
template
void maxheap::push(T theElement) // 在大堆根中插入元素
{
ensureLength(); // 先检测数组长度, 动态分配内存大小
int currentNode = heapSize+1; // 新节点的编号
while(currentNode!=1 && element[currentNode/2]
void maxheap::pop() // 删除大堆根的根元素
{
if(heapSize==0)
{
throw heap_empty_exception(heapSize);
}
// 删除根元素
element[1].~T();
T last_element = element[heapSize--]; // 获取最后一个元素 // heapsize-1
// 重新建堆
// 寻找最后一个元素的插入位置
int currentNode = 1;
int child = 2; // currentNode的孩子
while(child<=heapSize) // 这里是child<=heapSize,即使剩余最后两个元素也需要重构最大堆
{
// 找到currentNOde的大孩子
if(child<=heapSize && element[child]=element[child]) // 找到插入的位置
{
break;
}
// 否则
element[currentNode] = element[child]; // 将大孩子网上移动
currentNode = child;
child *= 2;
}
element[currentNode] = last_element; // 插入位置
}
template
void maxheap::print_heap()
{
//int height = (int)(log(heapSize+1)/log(2));
cout << "The heap: ";
for(int i=1; i<=heapSize; i++)
{
cout << element[i] << " ";
}
cout << endl;
}
#endif
当最大堆在删除元素时,如果堆中没有元素,则抛出异常:这一功能由自定义的异常类heap_empty_exception实现:
heapEmptyException.h文件
#ifndef HEAP_EMPTY_EXCEPTION
#define HEAP_EMPTY_EXCEPTION
#include
#include
using namespace std;
class heap_empty_exception : public runtime_error
{
private:
int heap_size;
public:
heap_empty_exception(int heap_size):runtime_error("Heap is empty")
{
this->heap_size = heap_size;
}
void display_error()
{
cout << "The heap size is " << heap_size << endl;
}
};
#endif
测试代码:main.cpp
#include
#include
#include
#include
#include "E:\back_up\code\c_plus_code\binaryTree\external_file\binarytree.h"
#include "E:\back_up\code\c_plus_code\binaryTree\external_file\maxHeap.h"
using namespace std;
int main()
{
maxheap heap;
int a[] = {1,4,7,2,3,9,15,21,10,11,14};
heap.initilize(a, 11);
heap.print_heap();
heap.push(2);
heap.push(3);
heap.push(29);
heap.print_heap();
cout << "pop 操作:" << endl;
cout << "-------------------------------------------" << endl;
while(!heap.empty())
{
//cout << heap1.top() << " ";
heap.print_heap();
heap.pop();
}
cout << endl;
// 堆的应用:堆排序
cout << endl << "堆排序: " << endl;
maxheap heap1;
int b[] ={2,3,8,91,12,78,23,10,9,1,33,54};
heap1.initilize(b, 12);
heap1.print_heap();
cout << "-------------------------------------------" << endl;
cout << "排序结果: " << endl;
while(!heap1.empty())
{
cout << heap1.top() << " ";
heap1.pop();
}
cout << endl;
cout << endl << "-------------异常类测试------------------" << endl;
try
{
heap1.pop();
}
catch(heap_empty_exception& ex)
{
cout << ex.what() << endl;
ex.display_error();
}
return 0;
}
运行结果:
上述的堆是一种隐式数据结构,在数组中的存储是隐式的,虽然它的时间效率高,空间利用率高,但是这种数据结构没有存储树的结构信息,当涉及到其他一些应用的时候(两个优先级队列合并),就需要树的结构信息了。左高树就能满足这种需求。
一棵二叉树,如果让一部分特殊的节点代替树中的空子树,则称之为扩充二叉树。补充的节点称为外部节点,原有的节点称为内部节点。
定义s(x)是从内部节点x到其子树的外部节点所有路径中最短的一条,若x是外部节点,则s值为0.若x是内部节点,则:
s = min{s(L), s(R)}
左高树: 当且仅当任何内部节点x的左孩子值的s大于等于右孩子的s值。
堆可以实现n个元素的排序,所需的时间为O(nlogn),首先用n个待排序的元素来初始化一个大根堆,然后从堆中逐个删除元素,每次删除的都是最大的元素。初始化的时间为O(n),每次删除的时间为O(logn),因此总的时间为O(nlogn).
-----------------------------------------------------------------------------------------------------------------