基本知识点名词见课本
树和二叉树的性质
1.任何一棵含有n(n>0)个结点的二叉树恰有n-1条边
2.深度为h的二叉树至多有2的h次方减1个结点(每层至多2的h-1次方个结点)
3.设二叉树的结点个数为n,深度是h,则[log2(n+1)]<=h<=n
4.完全二叉树的定义(对比满二叉树)
5.如果对一棵有n个结点的完全二叉树的结点,按层次次序编号(从上到下,从左到右),则对任一结点i,有下面结论:
(1)若i=1,则结点i为二叉树的根结点;若i>1,则结点[i/2]是父母结点
(2)若2i>n,则结点i无左子女;否则,结点2i是结点i的左子女
(3)若2i+1>n,则结点i无右子女;否则,结点2i+1是结点i的右子女。
6.具有n个结点的完全二叉树的深度是[log2(n)]+1
7.对于任一棵树,叶子结点个数比度数为2的结点个数多1
二叉树的存储方式
顺序存储
完全二叉树的顺序存储
非完全二叉树的顺序存储
此方式仅适用于所处理的二叉树与其对应的完全二叉树结点个数相差不多的二叉树,不然,即使对仅含n个点的单边二叉树都需要2的n次方减1个存储分量,这将造成空间浪费,我们下面引入链式存储来解决这个问题。
链式存储
二叉链式存储(后面都用这种方式):数据域、左孩子指针、右孩子指针
三叉链式存储:数据域、做孩子指针、右孩子指针、父母指针
二叉树和树、森林之间的互相转换
树转换为二叉树
(1)加线。在所有兄弟结点之间加一条连线。
(2)去线。树中的每个结点,只保留它与第一个孩子结点的连线,删除它与其它孩子结点之间的连线。
(3)层次调整。以树的根节点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。(注意第一个孩子是结点的左孩子,兄弟转换过来的孩子是结点的右孩子)
森林转换为二叉树
(1)把每棵树转换为二叉树。
(2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子,用线连接起来。
二叉树转换为树
是树转换为二叉树的逆过程。
(1)加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点…,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。
(2)去线。删除原二叉树中所有结点与其右孩子结点的连线。
(3)层次调整。
二叉树转换为森林
假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。
(1)从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除…。直到所有这些根节点与右孩子的连线都删除为止。
(2)将每棵分离后的二叉树转换为树。
二叉树的基本操作(创建、遍历、复制、求树深等)、堆排序
二叉树的遍历分为宽度遍历和深度遍历,深度遍历根据根被访问的顺序又分为先序遍历、中序遍历和后序遍历。
先序、后序、层序可以建立二叉树(且要添加虚空结点),中序不可以,因为中序的根不确定。例如序列:#A#B#C#D#,这个序列如果是中序,则B、C、D都有可能是根,会产生3棵树,不唯一。
如果不添加虚空结点的话,仅有先序、后序也确定不了一棵二叉树,但可以用先序和中序、后序和中序这两种方式来确定一棵二叉树。
以下面的二叉树为例
二叉树.h
/*
二叉树相关操作
二叉树遍历分为广度优先遍历(层序遍历)和深度优先遍历(先序、中序、后序)
*/
#include<iostream>
#include<string>
#include<cstdio>
#include<stack>
#include<queue>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef char elem_type;
//二叉树结点定义(二叉链式)
typedef struct binary_tree{
elem_type data; //数据域
struct binary_tree *lc,*rc; //指向左右孩子指针
}bi_node,*bi_tree;
void error_message(string s); //报错函数,并强制退出
void bi_tree_create(bi_tree &T); //先序创建二叉树
void visit(bi_tree T);//输出二叉树
void pre_order(bi_tree T);//先序遍历
void in_order(bi_tree T);//中序遍历
void post_order(bi_tree T);//后序遍历
void pre_order2(bi_tree T);//先序遍历(非递归)
void in_order2(bi_tree T);//中序遍历(非递归)
void post_order2(bi_tree T);//后序遍历(非递归)
void level_order(bi_tree T);//层次遍历(宽度优先遍历)
void bi_tree_depth1(bi_tree T,int h,int &depth);//先序遍历求树深
int bi_tree_depth2(bi_tree T);//后序遍历求树深
bi_tree copy_tree(bi_tree T);//复制二叉树
//堆排序
void heap_adjust(int a[], int i, int len);
void heap_sort(int a[], int len);
void error_message(string s){
cout<<s<<endl;
exit(-1);
}
//先序创建二叉树
void bi_tree_create(bi_tree &T){
char data;
cin>>data;
if (data == '#') { T = nullptr; }
else{
T=new bi_node;
if(!T){error_message("memory allocation failed");}
T->data=data;
bi_tree_create(T->lc);
bi_tree_create(T->rc);
}
}
//输出二叉树
void visit(bi_tree T){
if(T->data!='#') cout<<T->data;
}
//先序遍历
void pre_order(bi_tree T){
if(T){
visit(T);
pre_order(T->lc);
pre_order(T->rc);
}
}
//中序遍历
void in_order(bi_tree T){
if(T){
in_order(T->lc);
visit(T);
in_order(T->rc);
}
}
//后序遍历
void post_order(bi_tree T){
if(T){
post_order(T->lc);
post_order(T->rc);
visit(T);
}
}
//先序遍历(非递归)
void pre_order2(bi_tree T){
stack<bi_tree>sta;
bi_tree p=T;
while(p || !sta.empty()){
if(p){
sta.push(p);
cout<<p->data;
p=p->lc;
}
else{
p=sta.top();
sta.pop();
p=p->rc;
}
}
}
//中序遍历(非递归)
void in_order2(bi_tree T){
stack<bi_tree>sta;
bi_tree p=T;
while(p || !sta.empty()){
if(p){
sta.push(p);
p=p->lc;
}
else{
p=sta.top();
sta.pop();
cout<<p->data;
p=p->rc;
}
}
}
//后序遍历(非递归)
typedef struct{
bi_tree bitr;
char tag;
}bi_post_node,*bi_post_tree;
void post_order2(bi_tree T){
stack<bi_post_tree>sta;
bi_tree p=T;
bi_post_tree pp;
while(p || !sta.empty()){
while(p){
pp=new bi_post_node;
if(!pp){error_message("memory allocation failed");}
pp->bitr=p;
pp->tag='L';
sta.push(pp);
p=p->lc;
}
while(!sta.empty() && (sta.top())->tag=='R'){
pp=sta.top();
sta.pop();
cout<<pp->bitr->data;
}
if(!sta.empty()){
pp=sta.top();
pp->tag='R';
p=pp->bitr;
p=p->rc;
}
}
}
//层序遍历(宽度优先遍历)
void level_order(bi_tree T){
if (!T) return; //如果是空树,直接不遍历
bi_tree p=T;
queue<bi_tree>que;
que.push(T);
while(!que.empty()){
p=que.front();
cout<<p->data;
que.pop();
if(p->lc){que.push(p->lc);}
if(p->rc){que.push(p->rc);}
}
}
//利用先序遍历求树深,初始h=1,depth=0
void bi_tree_depth1(bi_tree T,int h,int &depth) {
if (T) {
if (h > depth) depth = h;
bi_tree_depth1(T->lc, h + 1, depth);
bi_tree_depth1(T->rc, h + 1, depth);
}
}
//利用后序遍历求树深,树深就是左子树和右子树中的比较深的树的深度
int bi_tree_depth2(bi_tree T) {
if (!T) return 0;
else {
int hl = bi_tree_depth2(T->lc);
int hr = bi_tree_depth2(T->rc);
if (hl > hr) return hl + 1;
else return hr + 1;
}
}
//二叉树后序遍历复制一棵树
bi_node *get_node(elem_type item, bi_node *lc, bi_node *rc) {
bi_node *p = new bi_node;
p->data = item;
p->lc = lc;
p->rc = rc;
return p;
}
bi_tree copy_tree(bi_tree T) {
if (!T) return nullptr;//复制空树
bi_node *p = nullptr, *q = nullptr;
if (T->lc) p = copy_tree(T->lc);
else p = nullptr;
if (T->rc) q = copy_tree(T->rc);
else q = nullptr;
bi_node *newnode = get_node(T->data, p, q);
return newnode;
}
//算术表达式求值(后序遍历),操作数都是叶子节点
//(该算法待研究)
//堆排序(本例用大顶堆实现)
//堆排序实质是一个完全二叉树(用数组表示)不断调整的过程
//构建堆(一个数组),调整堆,堆排序(交换堆顶和最后一个元素)
//i是待调整元素下标,len是待排序元素个数,数组下标从1开始
//调整堆(向大顶堆方向调整)
void heap_adjust(int a[], int i, int len) {
int child;
for (; 2 * i <=len; i = child) {
child = 2 * i;
if (child + 1 <=len && a[child + 1] > a[child])
child++; //选出两个孩子中的较大的那个
if (a[child] > a[i]) //如果父节点小于孩子,调整
swap(a[i], a[child]);
else
break; //等会堆排序过程,从下至上调整堆,如果孩子小于父亲,直接结束
}
}
//堆排序,不断交换堆顶与最后一个元素
//最后一个非叶子节点下标是len/2
void heap_sort(int a[], int len) {
int i;
//从最后一个非叶子节点开始调整
for (i = len / 2; i >=1; i--) {
heap_adjust(a, i, len);
}
for (i = len; i > 1; i--) {
swap(a[i], a[1]);
heap_adjust(a, 1, i - 1);
}
}
#include"二叉树.h"
int main()
{
bi_tree bitr = nullptr;
cout<<"先序创建一棵树: ";
bi_tree_create(bitr);
cout<<"先序遍历: ";pre_order(bitr);cout<<endl;
cout<<"中序遍历: ";in_order(bitr);cout<<endl;
cout<<"后序遍历: ";post_order(bitr);cout<<endl;
cout<<"先序遍历(非递归): ";pre_order2(bitr);cout<<endl;
cout<<"中序遍历(非递归): ";in_order2(bitr);cout<<endl;
cout<<"后序遍历(非递归): ";post_order2(bitr);cout<<endl;
cout<<"层次遍历: ";level_order(bitr);cout<<endl;
{int h = 1, depth = 0; bi_tree_depth1(bitr, h, depth); cout << "先序遍历求得树深: "<<depth<<endl; }//先序求树深
cout <<"后序遍历求得树深: "<< bi_tree_depth2(bitr) << endl;//后续求树深
bi_tree bitr2 = copy_tree(bitr); cout << "后序遍历复制该树: "; pre_order(bitr2); cout << endl;//后序遍历复制二叉树
//堆排序
cout << endl;int a[7] = { 0,1,4,2,23,12,9 }; cout << "序列:";
for (int i = 1; i <= 6; i++) cout << a[i] << " "; cout << endl; cout << "堆排序后:";
heap_sort(a, 6);for (int i = 1; i <= 6; i++) cout << a[i] << " "; cout << endl;
return 0;
}
二叉排序树
/*
二叉排序树:首先是二叉树。
任何节点的值一定大于其左子树中的每一个节点的值,并小于其右子树中的每一个节点的值。
二叉排序树的删除问题待研究,稍微有些复杂
*/
#include<iostream>
using namespace std;
typedef struct BST{
int e;
struct BST *lc, *rc;
}bst;
//查找
//查找最小元素的指针
bst *bst_search_min(bst *root) {
if (root == nullptr) return nullptr;
if (root->lc == nullptr) return root;
else return bst_search_min(root->lc);
}
//查找最大元素的指针
bst *bst_search_max(bst *root) {
if (root == nullptr) return nullptr;
if (root->rc == nullptr) return root;
else return bst_search_max(root->rc);
}
//查找指定元素的指针(递归版)
bst *bst_search1(bst *root, int e) {
if (root == nullptr) return nullptr;
if (e > root->e) return bst_search1(root->rc, e);
else if (e < root->e) return bst_search1(root->lc, e);
else return root;
}
//查找指定元素的指针(非递归版)
bst *bst_search2(bst *root, int e) {
bst *p = root;
while (p) {
if (p->e == e) return p;
p = (e > p->e) ? p->rc : p->lc;
}
return nullptr;
}
//插入
void bst_insert(bst *&root, int e) {
bst *p = new bst;
p->e = e;
p->lc = p->rc = nullptr;
//空树,直接作为根节点
if (root == nullptr) {
root = p;
return;
}
if (bst_search1(root, e) != nullptr) return;//已存在,不插入
bst *p1 = nullptr, *p2 = root;
while (p2) {
p1 = p2;
p2 = (e > p2->e) ? p2->rc : p2->lc;
}
if (e > p1->e) p1->rc = p;
else p1->lc = p;
}
//建立二叉排序树,注意下面的引用符号&
void bst_create(bst *&T, int a[], int n) {
for (int i = 0; i < n; i++)
bst_insert(T, a[i]);
}
//中序遍历二叉排序树,输出有序序列
void in_order(bst *T) {
if (T) {
in_order(T->lc);
cout << T->e << " ";
in_order(T->rc);
}
}
void destroy(bst *T) {
bst *tmp = T;
if (T) {
if (T->lc) { destroy(T->lc); T->lc = nullptr; }
if (T->rc) { destroy(T->rc); T->rc = nullptr; }
if (T) { delete T; T = nullptr; }
}
}
int main() {
int a[6] = { 3,1,44,33,2,5 };
bst *tree = nullptr;
cout << "序列:";
for (int i = 0; i < 6; i++) cout << a[i] << " "; cout << endl;
cout << "二叉排序树排序后: "; bst_create(tree, a, 6);
in_order(tree); cout << endl;
cout << "最大元素: "; bst*tmp1 = bst_search_min(tree); cout << tmp1->e << endl;
cout << "最小元素: "; bst *tmp2 = bst_search_max(tree); cout << tmp2->e<< endl;
cout << "查找元素33的指针: "; bst *tmp3 = bst_search1(tree, 33); cout << tmp3 << endl;
cout << "查找元素33的指针(非递归查找): "; bst *tmp4 = bst_search2(tree, 33); cout << tmp4 << endl;
destroy(tree);
return 0;
}
哈夫曼树及哈夫曼编码
过程:
/*
赫夫曼(Huffman)树是一种带权路径长度最短的二叉树。
从树根结点到该结点之间的路径长度与该结点上权的乘积称为结点的带权路径长度,
树中所有叶子结点的带权路径长度之和称为该树的带权路径长度(WPL)
赫夫曼(Huffman)树的特征:
1、当叶子上的权值均相同时,完全二叉树一定是最优二叉树。否则完全二叉树不一定是最优二叉树。
2、在最优二叉树中,权值越大的叶子离根越近。
3、最优二叉树的形态不唯一,但WPL最小。
4、哈夫曼树的结点的度数为 0 或 2, 没有度为 1 的结点。
5、包含 n 个叶子结点的哈夫曼树中共有 2n – 1 个结点
6、包含 n 棵树的森林要经过 n–1 次合并才能形成哈夫曼树,共产生 n–1 个新结点
*/
#include<iostream>
using namespace std;
typedef char* huffman_code;
typedef struct {
int weight;//叶子节点权值
int parent;
int lchild, rchild;
}huffman_node,*huffman_tree;
//选择两个parent为0,且weight最小的结点
//n为叶子结点的总数,s1,s2是两个指针,指向两个权值最小的结点的编号
void select(huffman_tree &ht, int n, int &s1, int &s2) {
int min_cnt;
//找出第一个单节点
for (int i = 1; i <= n; i++) {
if (ht[i].parent == 0) { min_cnt = i; break; }
}
//找出第一个权值最小的结点
for (int i = 1; i <= n; i++) {
if (ht[i].parent == 0 && ht[i].weight < ht[min_cnt].weight) {
min_cnt = i;
}
}
s1 = min_cnt;
//找出第二个单结点
for (int i = 1; i <= n; i++) {
if (ht[i].parent == 0 && i!=s1) { min_cnt = i; break; }
}
//找出权值第二小的结点
for (int i = 1; i <= n; i++) {
if (ht[i].parent == 0 && ht[i].weight < ht[min_cnt].weight && i!=s1) {
min_cnt = i;
}
}
s2 = min_cnt;
}
//创建哈夫曼树,并求出哈夫曼编码,w数组存放已知的权值
void huffman_create(huffman_tree &ht, int w[], int n) {
//n是叶子结点数目,n个结点,经过n-1次比较,共有2*n-1个结点
int m = 2 * n - 1;//m是哈夫曼树总的结点数目
ht = new huffman_node[m + 1];
//1-n号存放叶子结点,初始化叶子结点
for (int i = 1; i <= n; i++) {
ht[i].weight = w[i];
ht[i].lchild = ht[i].rchild = ht[i].parent = 0;
}
for (int i = n + 1; i <= m; i++) {
ht[i].weight = ht[i].parent = ht[i].lchild = ht[i].rchild = 0;
}
//创建非叶子结点,建立哈夫曼树
cout << "创建哈夫曼树过程如下:" << endl;
int s1, s2;
for (int i = n + 1; i <= m; i++) {
//在ht[1]到ht[i-1]的范围内选出两个权值最小的结点,其编号赋值给s1,s2
//下面用各结点的编号把各个结点联系起来
select(ht, i - 1, s1, s2);
ht[s1].parent = i;
ht[s2].parent = i;
ht[i].lchild = s1;
ht[i].rchild = s2;
//新结点i的权值
ht[i].weight = ht[s1].weight + ht[s2].weight;
cout << ht[i].weight<<"---(" <<ht[s1].weight<<", "<<ht[s2].weight<<")"<< endl;
}
}
//从n个叶子结点到根,逆向求出每个叶子结点对应的哈夫曼编码
void huffman_code_create(huffman_tree &ht, char **hc, int n) {
//分配n个编码的指针
hc = new char *[n + 1];
int st;//编码起始位置
//为一个结点分配编码空间,从右向左逐位编码
char *code = new char[n];
code[n - 1] = '\0';//以只有2个结点的树为例,只有一条边code[0]
//求n个叶子结点对应的哈夫曼编码
for (int i = 1; i <= n; i++) {
st = n - 1;//编码位置初始化(从右向左编码)
//从叶子一直追踪到根,求出每个叶子的哈夫曼编码
for (int p1 = i, p2 = ht[p1].parent; p2 != 0; p1 = p2, p2 = ht[p2].parent) {
if (ht[p2].lchild == p1) code[--st] = '0';//左走定为0
else code[--st] = '1';//右走定为1
}
//为每个节点的编码分配内存空间
hc[i] = new char[n - st];
strcpy(hc[i], &code[st]);
}
delete code;
//打印编码序列
cout << "哈夫曼编码如下:" << endl;
for (int i = 1; i <= n; i++) {
cout << ht[i].weight << "---" << hc[i] << endl;
}
}
int main()
{
huffman_tree ht;
huffman_code *hc=nullptr;
int n;
int weight;
cout << "请输入哈夫曼编码的结点数目:"; cin >> n;
int *w = new int[n + 1];
cout << "请分别输入" << n << "个结点的权值" << endl;
for (int i = 1; i <= n; i++) {
cout << "结点" << i << "的权:"; cin >> weight;
w[i] = weight;
}
huffman_create(ht, w, n);
huffman_code_create(ht, hc, n);
return 0;
}