树
树状图是一种数据结构,它是由 n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
它具有以下的特点: 每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除 了根结点外,每个子结点可以分为多个不相交的子树;
二叉树
一个没有限制的线性表应用范围可能有限,但是我们对线性表进行一些限制就可 以衍生出非常有用的数据结构如栈、队列、优先队列等。
树也是一样,一个没有限制的树由于太灵活,控制起来比较复杂。如果对普通的树加上一些人为的限制,比如 节点只允许有两个子节点,这就是我们接下来要介绍的二叉树。
二叉树是一个每个结点最多只能有两个分支的树,左边的分支称之为左子树,右边的分支称之为右子树。
常见二叉树分类:
(1)完全二叉树 : 若设二叉树的高度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层有叶 子节点,并且叶子结点都是从左到右依次排布,这就是完全二叉树(堆就是完全二叉树)。
(2)满二叉树 : 除了叶结点外每一个结点都有左右子节点且叶子结点都处在最底层的二叉树。
(3)平衡二叉树 :又被称为 AVL 树,它是一颗空树或左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都 是一棵平衡二叉树。
(4)二叉搜索树 : 又称二叉查找树、二叉排序树(Binary Sort Tree)。
它是一颗空树或是满足下列性质的二叉树:
1)若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值;
2)若右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;
3)左、右子树也分别为二叉排序树。
(5)红黑树 : 是每个节点都带有颜色属性(颜色为红色或黑色)的自平衡二叉查找树,满足下列性质:
1)节点是红色或黑色;
2)根节点是黑色;
3)所有叶子节点都是黑色;
4)每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节 点。)
5)从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
二叉搜索树的算法实现
当我们要在一组数中要找到一个数,比如 26?你该怎么找?
当我们把数据进行排序(按照从小到大的顺序排列)后,再查找相应的这条记录?还是用上面的方法吗?
节点结构体的定义:
#define MAX_NODE 1024
#define isLess(a, b) (a<b)
#define isEqual(a, b) (a==b)
typedef int ElemType;
typedef struct _Bnode{
ElemType data; //元素类型
struct _Bnode *lchild, *rchild;//指向左右孩子节点
}Bnode, *Btree;
二叉搜索树插入节点
将要插入的结点 e,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上 操作直到找到一个空位置用于放置该新节点。
bool InsertBtree(Btree **root, Bnode *node){
Bnode *tmp = NULL;
Bnode *parent = NULL;
if(!node){
return false;
}else {//清空新节点的左右子树
node->lchild = NULL;
node->rchild = NULL;
}
if(*root){//存在根节点
tmp= *root;
}else{ //若该树为空树,则直接将 node 放置在根节点上
*root = node;
return true;
}
while(tmp != NULL){
parent = tmp;//保存父节点
printf("父节点: %d\n", parent->data);
if(isLess(node->data,tmp->data)){
tmp = tmp->lchild;
}else{
tmp = tmp->rchild;
}
}
if(isLess(node->data, parent->data)){
//找到空位置后,进行插入
parent->lchild = node;
}else{
parent->rchild = node;
}
return true;
}
二叉搜索树删除节点
将要删除的节点的值,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以 上操作直到找到一个节点的值等于删除的值,则将此节点删除。删除时有 4 中情况须分别处理:
/************************ *
查找二叉搜索树上最大的结点
*************************/
int findMax(Btree* root) {
assert(root!=NULL);
//方式一 采用递归
/*if(root->rchild==NULL){
return root->data;
}
return findMax(root->rchild); */
//方式二 采用循环
while(root->rchild){
root = root->rchild;
}
return root->data;
}
/************************ *
采用递归方式查找结点
*************************/
Btree* DeleteNode(Btree* root, int key){
if(root==NULL)return NULL;//没有找到删除节点
if(root->data > key) {
root->lchild = DeleteNode(root->lchild, key);
return root;
}
if(root->data < key) {
root->rchild = DeleteNode(root->rchild, key);
return root;
}
//删除节点不存在左右子节点,即为叶子节点,直接删除
if(root->lchild==NULL && root->rchild==NULL)
return NULL;
//删除节点只存在右子节点,直接用右子节点取代删除节点
if(root->lchild==NULL && root->rchild!=NULL)
return root->rchild;
//删除节点只存在左子节点,直接用左子节点取代删除节点
if(root->lchild!=NULL && root->rchild==NULL)
return root->lchild;
//删除节点存在左右子节点,直接用左子节点最大值取代删除节点
int val = findMax(root->lchild);
root->data=val;
root->lchild = DeleteNode(root->lchild,val);
return root;
}
二叉搜索树搜索
/************************ *
采用递归方式查找结点
*************************/
Bnode* queryByRec(Btree *root, ElemType e){
if (root == NULL || isEqual(root->data, e)){
return root;
} else if(isLess(e, root->data)) {
return queryByRec(root->lchild, e);
} else {
return queryByRec(root->rchild, e);
}
}
/** * 使用非递归方式查找结点 */
Bnode* queryByLoop(Bnode *root, int e){
while(root != NULL && !isEqual(root->data, e)){
if(isLess(e, root->data)){
root = root->lchild;
}else{
root = root->rchild;
}
}
return root;
}
二叉树的遍历
二叉树的遍历是指从根结点出发,按照某种次序依次访问所有结点,使得每个结点被当且访问一次。共分为四种方式:
前序遍历 - 先访问根节点,然后前序遍历左子树,再前序遍历右子树
上图前序遍历结果: 19 7 5 11 15 25 21 61
/************************ *
采用递归方式实现前序遍历
*************************/
void PreOrderRec(Btree *root) {
if (root == NULL) {
return;
}
printf("- %d -", root->data);
preOrderRec(root->lchild);
preOrderRec(root->rchild);
}
前序遍历 - 非递归方式实现:
首先申请一个新的栈,记为 stack; 将头结点 head 压入 stack 中; 每次从 stack 中弹出栈顶节点,记为 cur,然后打印 cur 值,如果 cur 右孩子不为空,则将右孩子压入栈中;如果 cur 的左 孩子不为空,将其压入 stack 中; 重复步骤 ,直到 stack 为空
/************************ *
借助栈实现前序遍历
*************************/
void PreOrder(Btree *root) {
Bnode cur ;
if (root == NULL) {
return;
}
SqStack stack;
InitStack(stack);
PushStack(stack, *root); //头节点先入栈
//栈为空,所有节点均已处理
while (!(IsEmpty(stack))) {
PopStack(stack, cur); //要遍历的节点
printf("- %d -", cur.data);
if (cur.rchild != NULL) {
PushStack(stack, *(cur.rchild));
//右子节点先入栈,后处理
}
if (cur.lchild != NULL) {
PushStack(stack, *(cur.lchild));
//左子节点后入栈,接下来先 处理
}
}
DestroyStack(stack);
}
中序遍历 - 先访问根节点的左子树,然后访问根节点,最后遍历右子树
后序遍历 - 从左到右,先叶子后节点的方式遍历访问左右子树,最后访问根节点
层序遍历 - 从根节点从上往下逐层遍历,在同一层,按从左到右的顺序对节点逐个访问
哈夫曼编码
哈夫曼(Huffman)编码算法是基于二叉树构建编码压缩结构的,它是数据压缩中经典的一种算 法。算法根据文本字符出现的频率,重新对字符进行编码。
编码实现
Huff.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <Windows.h>
#include <iostream>
#include <iomanip>
using namespace std;
#define MaxSize 1024 //队列的最大容量
typedef struct _Bnode {
char value;
int weight;
struct _Bnode *parent;
struct _Bnode *lchild;
struct _Bnode *rchild;
} Btree, Bnode; /* 树结点结构体 */
typedef Bnode *DataType; //任务队列中元素类型
typedef struct _QNode { //队列结点结构
/*每个节点的优先级,数值越大,优先级越高,
优先级相同, 取第一个节点 */
int priority;
DataType data;
struct _QNode *next;
}QNode;
typedef QNode * QueuePtr;
typedef struct Queue { //队头结构
int length; //队列的长度
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
//队列初始化,将队列初始化为空队列
void InitQueue(LinkQueue *LQ) {
if(!LQ) return ;
LQ->length = 0;
LQ->front = LQ->rear = NULL; //把对头和队尾指针同时置 0
}
//判断队列为空
int IsEmpty(LinkQueue *LQ) {
if(!LQ) return 0;
if (LQ->front == NULL) {
return 1;
}
return 0;
}
//判断队列是否为满
int IsFull(LinkQueue *LQ) {
if(!LQ) return 0;
if (LQ->length == MaxSize) {
return 1;
}
return 0;
}
//入队,将元素 data 插入到队列 LQ 中
int EnterQueue(LinkQueue *LQ,DataType data,int priority){
if(!LQ) return 0;
if(IsFull(LQ)){
cout<<"无法插入元素 "<<data<<", 队列已满!"<<endl;
return 0;
}
QNode *qNode = new QNode;
qNode->data = data;
qNode->priority = priority;
qNode->next = NULL;
if(IsEmpty(LQ)){//空队列
LQ->front = LQ->rear = qNode;
}else {
qNode->next = LQ->front; //在对头插入节点
LQ->front = qNode;
//LQ->rear->next =qNode;//在队尾插入节点 qNode
//LQ->rear = qNode; //队尾指向新插入的节点
}
LQ->length++;
return 1;
}
//出队,遍历队列,找到队列中优先级最高的元素 data 出队
int PopQueue(LinkQueue *LQ, DataType *data){
//保存当前已选举的最高优先级节 点上一个节点的指针地址。
QNode **prev = NULL, *prev_node=NULL;
QNode *last = NULL, *tmp = NULL;
if(!LQ || IsEmpty(LQ)){
cout<<"队列为空!"<<endl;
return 0;
}
if(!data) return 0;
//prev 指向队头 front 指针的地址
prev = &(LQ->front);
//printf("第一个节点的优先级: %d\n", (*prev)->priority);
last = LQ->front;
tmp = last->next;
while(tmp){
if(tmp->priority <(*prev)->priority){
//printf("抓到个更小优先级的节点[priority: %d]\n", tmp->priority);
prev = &(last->next);
prev_node= last;
}
last=tmp;
tmp=tmp->next;
}
*data = (*prev)->data;
tmp = *prev;
*prev = (*prev)->next;
delete tmp;
LQ->length--;
//接下来存在 2 种情况需要分别对待
//1.删除的是首节点,而且队列长度为零
if(LQ->length==0){
LQ->rear=NULL;
}
//2.删除的是尾部节点
if(prev_node&&prev_node->next==NULL){
LQ->rear=prev_node;
}
return 1;
}
//打印队列中的各元素
void PrintQueue(LinkQueue *LQ) {
QueuePtr tmp;
if(!LQ) return ;
if(LQ->front==NULL){
cout<<"队列为空!";
return ;
}
tmp = LQ->front;
while(tmp) {
cout<<setw(4)<<tmp->data->value<<"["<<tmp->priority<<"]";
tmp = tmp->next;
}
cout<<endl;
}
//获取队首元素,不出队
int GetHead(LinkQueue *LQ,DataType *data) {
if (!LQ || IsEmpty(LQ)) {
cout<<"队列为空!"<<endl;
return 0;
}
if(!data) return 0;
*data = LQ->front->data;
return 1;
}
//清空队列
void ClearQueue(LinkQueue *LQ) {
if(!LQ) return ;
while(LQ->front){
QueuePtr tmp = LQ->front->next;
delete LQ->front;
LQ->front = tmp;
}
LQ->front = LQ->rear = NULL;
LQ->length = 0;
}
//获取队列中元素的个数
int getLength(LinkQueue* LQ){
if(!LQ) return 0;
return LQ->length;
}
Huff.cpp
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include "Huff.h"
using namespace std;
void PreOrderRec(Btree *root);
/* 构造哈夫曼编码树 */
void HuffmanTree (Btree * &huff, int n) {
LinkQueue *LQ = new LinkQueue;
int i = 0;
//初始化队列
InitQueue(LQ);
/* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */
for (i=0; i<n; i++) {
Bnode * node = new Bnode;
cout<<"请输入第"<<i+1<<"个字符和出现频率: "<<endl;
cin>>node->value; //输入字符
cin>>node->weight ;//输入权值
node->parent =NULL;
node->lchild =NULL;
node->rchild =NULL;
EnterQueue(LQ, node, node->weight);
}
PrintQueue(LQ);
do{
Bnode *node1 = NULL;
Bnode *node2 = NULL;
if(!IsEmpty(LQ)){
PopQueue(LQ, &node1);
printf("第一个出队列的数:%c, 优先级: %d\n", node1->value, node1->weight);
}else {
break;
}
if(!IsEmpty(LQ)){
Bnode *node3 = new Bnode;
PopQueue(LQ, &node2);
printf("第二个出队列的数:%c, 优先级: %d\n", node2->value, node2->weight);
node3->lchild = node1;
node1->parent = node3;
node3->rchild = node2;
node2->parent = node3;
node3->value = ' ';
node3->weight=node1->weight+node2->weight;
printf("合并进队列的数:%c, 优先级: %d\n", node3->value, node3->weight);
EnterQueue(LQ,node3, node3->weight);
}else {
huff = node1;
break;
}
}while(1);
}
/************************ *
采用递归方式实现前序遍历
*************************/
void PreOrderRec(Btree *root) {
if (root == NULL) {
return;
}
printf("- %c -", root->value);
PreOrderRec(root->lchild);
PreOrderRec(root->rchild);
}
int main(void){
Btree * tree = NULL;
HuffmanTree(tree, 7);
PreOrderRec(tree);
system("pause");
return 0;
}
红黑树
至此我们的红黑树就构建完成,红黑树的查找同普通的二叉排序树