c++简单实现二叉树、AVL、最大最小堆、霍夫曼、集合(问题:malloc,typedef,指针)(代码只是大概思路不一定能运行)
前言
顺序查找
二分查找
树 n个结点构成集合 儿子兄弟表示法 所有结点结构相同省时省空间
二叉树
二叉树遍历 先序遍历 中序遍历 后序遍历 层序遍历
顺序存储结构 数组等实现的完全二叉树 他们的标号连续 结点父亲为i 左孩子为2i 右孩子为2i+1
一般的二叉树也可以补全结点为完全二叉树 但浪费空间
链表存储
typedef的使用
typedef的有无在c/c++ 中的区别 转载https://blog.csdn.net/rjw9999/article/details/6655821
在C中定义一个结构体类型要用typedef:
typedef struct Student
{
int a;
}Stu;
于是在声明变量的时候就可:Stu stu1;如果没有typedef就必须用struct Student stu1;来声明
但在c++里很简单,直接
struct Student
{
int a;
};
于是就定义了结构体类型Student,声明变量时直接Student stu2;
在c++中如果用typedef的话,又会造成区别:
struct Student
{
int a;
}stu1; //stu1是一个变量
typedef struct Student2
{
int a;
}stu2; //stu2是一个结构体类型 typedef相当于定义了一个别名
*/
stack的使用
转载https://blog.csdn.net/zichen_ziqi/article/details/80807989
队列的使用
转自:http://www.169it.com/article/2718050585107790752.html
malloc/new 的使用方法与区别
本文链接:https://blog.csdn.net/qq_26816591/article/details/52214313
c++ NULL/nullptr 的区别
转载https://www.cnblogs.com/Philip-Tell-Truth/p/6594632.html
NULL 相当于0会当作int不会当作void*来看待
二叉树
#include <iostream>
#include <string>
#include <algorithm>
#include <queue> //顺序队列
#include <stack> //先进后出
using namespace std;
struct Node
{
int value;
Node *left = NULL;
Node *right = NULL;
};
Node a;
二叉树遍历
二叉树遍历 先序遍历 先访问根结点 先序遍历左子树 先序遍历其右子树 根左右
void pre_traversal(Node *root)
{
if (root)
{
cout << root->value;
pre_traversal(root->left);
pre_traversal(root->right);
}
}
二叉树遍历 中序遍历 中序遍历左子树 访问根结点 中序遍历其右子树 左根右
void mid_traversal(Node *root)
{
if (root)
{
mid_traversal(root->left);
cout << root->value;
mid_traversal(root->right);
}
}
二叉树遍历 后序遍历 后序遍历左子树 后序遍历其右子树 访问根结点 左右根
void back_traversal(Node *root)
{
if (root)
{
back_traversal(root->left);
back_traversal(root->right);
cout << root->value;
}
}
二叉树非递归遍历 堆栈 中序遍历
void inorder_traversal(Node *root)
{
Node *temp = root;
stack<Node> s; // 创建队列
while (temp || !s.empty()) //栈不为空或当前结点不为空
{
while (temp)
{
s.push(*temp);
temp = temp->left;
} //当结点不为空时一直将左叶子结点插入
if (!s.empty())
{
temp = s.pop();
cout << temp->value;
temp = temp->right; //左根右
//当前节点没有右右节点则弹出栈顶元素父节点
}
}
}
层序遍历 遍历的本质就是如何将树的二维结构变为一维的线性结构
假如我访问了左儿子怎么才能访问到右儿子
需要一个存储结构存放暂时不访问的结点
依靠队列 将结点放入 之后进行抛出并将结点的左右儿子放进去一步步操作
void level_traversal(Node *root)
{
queue<Node> temp;
if (!root)
return; // 如果树为空返回
temp.push(*root);
while (!temp.empty())
{
Node *t = temp.pop;
cout << t->value;
if (t->left)
{
temp.push(*t->left);
}
if (t->right)
{
temp.push(*t->right);
}
}
}
二叉树结点/高度
输出二叉树的所有叶子结点
void child_traversal(Node *root)
{
if (root)
{
if (!root->left && !root->right)
cout << root->value;
pre_traversal(root->left);
pre_traversal(root->right);
}
}
求二叉树的高度
递归 max(Hl,Hr)+1 先求左子树的最大高度 再求右子树的最大高度 采用后序遍历
int max_height(Node *root)
{
int Hl, Hr, MaxH;
if (root)
{
Hl = max_height(root->left);
Hr = max_height(root->right);
MaxH = (Hl > Hr) ? Hl : Hr;
return (MaxH + 1);
}
else
return 0;
}
二元运算的树表示及遍历 (中缀表达式不准当没括号时)
两个遍历序列 前中 或 中后 可确定唯一二元式序列 但是前后不行
同构
左右子树交换后可以得到相同结构 数组结构表示二叉树 静态链表递归每层对比
struct Tree
{
int value;
int L;
int right;
} t1[size], t2[size];
void build()
{
Tree R1, R2;
R1 = build(); //首先找到根结点没有结点之乡的点为根结点
R2 = build();
if (check(R1, R2))
{
...
}
}
int check(Tree a, Tree b)
{
if ((a == NULL) && (b == NULL))
return 1;
if ((a == NULL) && (b != NULL))
|| ((a != NULL) && (b == NULL)) return 0;
if (/*判断ab两个树根结点是否相同*/)
return 0;
if (/*两个根结点左右位置不交换*/)
{ /*递归*/
}
else
{ /*递归*/
}
}
binary search tree 二叉搜索树 左小右大 递归查找 / 循环查找
//二叉搜索树 递归查找 NULL=0FALSE=0TRUE记得是非零值
Node* binary_find(int value, Node *root)
{
if (!root)
return NULL;
if (value > root->value)
{
binary_find(value, root->right);
}
else if (value < root->value)
{
binary_find(value, root->left);
}
else
return root;
}
Node* binary_find2(int value, Node *root)
{
while (root)
{
if (value > root->value)
{
root = root->right;
}
else if (value < root->value)
{
root = root->left;
}
else
return root;
}
return NULL;
}
//为了防止二叉搜索树一边倒 查找效率取决于树的高度
查找二叉搜索树的最大 最小值
//查找二叉搜索树的最大 最小值
Node* FindMin(Node *root)
{
if(!root) return NULL;
else if (!root->left) return root;
else return FindMin(root->left);
}
Node* FindMax(Node *root)
{
if(root)
{
while(root->right) root = root->right;
}
return root;
}
二叉搜索树的插入删除
//二叉搜索树的插入删除
void insert(int a,Node* root)//返回插入结点
{
if(!root)
{
root = (Node*)malloc(sizeof(Node));
root->value = a;
root->left = NULL;
root->right = NULL;
}
else
{
if(a<root->value)
{
return insert(a,root->left);
}
else if(a>root->value)
{
return insert(a,root->right);
}
}
}
//二叉树的使用 例如字典序
/*二叉树删除结点 用另一节点代替套被删除的结点 右子树的最小元素或者是
左子树的最大元素 这样的好处是那么这个节点一定不会有两个孩子*/
void delete_binary(int x,Node* root)
{
Node* temp;//用来存放替换的
if(!root) cout<<"所要删除的元素不存在";
else if(x<root->value) delete_binary(x, root->left);
else if(x>root->value) delete_binary(x, root->right);
else
{
if(root->left&&root->right) //左右孩子节点都存在时找右子树的最小节点
{
temp = FindMin(root->right);
root->value = temp->value;
delete_binary(temp->value,root->right); // 递归删除找到的替换的结点并找右子树最小的元素代替
}
else //只有一个孩子结点或者没有
{
temp = root;
if(!left)//有右孩子或无子结点
{
root=root->right;
}
else if(!right)//有左孩子或无子结点
{
root=root->left;
}
free(temp);
}
}
}
平衡二叉树
平衡二叉树 任意一个结点左右两个子树的高度差不超过1
当插入的结点在发现者右子树的右边因而叫RR插入需要RR旋转(右单旋)
当插入的结点在发现者左子树的左边因而叫LL插入需要LL旋转(左单旋)
当插入的结点在发现者左子树的右子树因而叫LR插入需要LR旋转(左右旋转)
当插入的结点在发现者右子树的左子树因而叫RL插入需要RL旋转(右左旋转)
AVL 树的实现
typedef struct AVLNode *Position;
typedef Position AVLTree; /* AVL树类型 */
struct AVLNode{
ElementType Data; /* 结点数据 */
AVLTree Left; /* 指向左子树 */
AVLTree Right; /* 指向右子树 */
int Height; /* 树高 */
};
int Max ( int a, int b )
{
return a > b ? a : b;
}
AVLTree SingleLeftRotation ( AVLTree A )
{ /* 注意:A必须有一个左子结点B */
/* 将A与B做左单旋,更新A与B的高度,返回新的根结点B */
AVLTree B = A->Left;
A->Left = B->Right;
B->Right = A;
A->Height = Max( GetHeight(A->Left), GetHeight(A->Right) ) + 1;
B->Height = Max( GetHeight(B->Left), A->Height ) + 1;
return B;
}
AVLTree DoubleLeftRightRotation ( AVLTree A )
{ /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */
/* 将A、B与C做两次单旋,返回新的根结点C */
/* 将B与C做右单旋,C被返回 */
A->Left = SingleRightRotation(A->Left);
/* 将A与C做左单旋,C被返回 */
return SingleLeftRotation(A);
}
/*************************************/
/* 对称的右单旋与右-左双旋请自己实现 */
/*************************************/
AVLTree Insert( AVLTree T, ElementType X )
{ /* 将X插入AVL树T中,并且返回调整后的AVL树 */
if ( !T ) { /* 若插入空树,则新建包含一个结点的树 */
T = (AVLTree)malloc(sizeof(struct AVLNode));
T->Data = X;
T->Height = 0;
T->Left = T->Right = NULL;
} /* if (插入空树) 结束 */
else if ( X < T->Data ) {
/* 插入T的左子树 */
T->Left = Insert( T->Left, X);
/* 如果需要左旋 */
if ( GetHeight(T->Left)-GetHeight(T->Right) == 2 )
if ( X < T->Left->Data )
T = SingleLeftRotation(T); /* 左单旋 */
else
T = DoubleLeftRightRotation(T); /* 左-右双旋 */
} /* else if (插入左子树) 结束 */
else if ( X > T->Data ) {
/* 插入T的右子树 */
T->Right = Insert( T->Right, X );
/* 如果需要右旋 */
if ( GetHeight(T->Left)-GetHeight(T->Right) == -2 )
if ( X > T->Right->Data )
T = SingleRightRotation(T); /* 右单旋 */
else
T = DoubleRightLeftRotation(T); /* 右-左双旋 */
} /* else if (插入右子树) 结束 */
/* else X == T->Data,无须插入 */
/* 别忘了更新树高 */
T->Height = Max( GetHeight(T->Left), GetHeight(T->Right) ) + 1;
return T;
}
堆heap 优先队列的实现
堆heap 优先队列的实现
数组实现 插入-元素总是插入到尾部 删除-查找最大胡哦最小关键字
数组中删去需要移动的元素 复杂度O(n)因为删去一个元素时需要将元素向前移
链表的实现 相对于数组来讲 删除结点 复杂度O(1) 指针指向下一个元素即可
有序数组 logn
二叉树叶子结点为最大值
堆 完全二叉树进行存储 结点为最大值/最小值 最大堆/最小堆
堆的构造
typedef struct HNode *Heap; /* 堆的类型定义 */
struct HNode {
int *Data; /* 存储元素的数组 */
int Size; /* 堆中当前元素个数 */
int Capacity; /* 堆的最大容量 */
};
typedef Heap MaxHeap; /* 最大堆 */
typedef Heap MinHeap; /* 最小堆 */
#define MAXDATA 1000 /* 该值应根据具体情况定义为大于堆中所有可能元素的值 */
MaxHeap CreateHeap( int MaxSize )
{ /* 创建容量为MaxSize的空的最大堆 */
MaxHeap H = (MaxHeap)malloc(sizeof(struct HNode));
H->Data = (int *)malloc((MaxSize+1)*sizeof(int));
H->Size = 0;
H->Capacity = MaxSize;
H->Data[0] = MAXDATA; /* 定义"哨兵"为大于堆中所有可能元素的值*/
return H;
}
bool IsFull( MaxHeap H )
{
return (H->Size == H->Capacity);
}
元素插入
/* 将元素X插入最大堆H,其中H->Data[0]已经定义为哨兵 */
bool Insert( MaxHeap H, int X )
{
int i;
if ( IsFull(H) ) {
printf("最大堆已满");
return false;
}
i = ++H->Size; /* i指向插入后堆中的最后一个元素的位置 */
for (i; H->Data[i/2] < X; i/=2 )
H->Data[i] = H->Data[i/2]; /* 上滤X */
H->Data[i] = X; /* 将X插入 */
return true; //插入到最后位置再与父元素减缓
}
#define ERROR -1 /* 错误标识应根据具体情况定义为堆中不可能出现的元素值 */
bool IsEmpty( MaxHeap H )
{
return (H->Size == 0);
}
删除元素
/* 从最大堆H中取出键值为最大的元素,并删除一个结点 */
int DeleteMax( MaxHeap H )
{
int Parent, Child;
int MaxItem, X;
if ( IsEmpty(H) ) {
printf("最大堆已为空");
return ERROR;
}
MaxItem = H->Data[1]; /* 取出根结点存放的最大值 */
/* 用最大堆中最后一个元素从根结点开始向上过滤下层结点 */
X = H->Data[H->Size--]; /* 注意当前堆的规模要减小 取出左后一个数 并且size变小 */
for( Parent=1; Parent*2<=H->Size; Parent=Child ) // 与子结点比较 并交换下滤
{
Child = Parent * 2;
if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = X;
return MaxItem;
}
建造最大堆
/*----------- 建造最大堆 -----------
可以考虑每次选出数组中最大的元素进行插入
也可以将整个数组构造好之后再重新整理顺序
*/
void PercDown( MaxHeap H, int p )
{ /* 下滤:将H中以H->Data[p]为根的子堆调整为最大堆 */
int Parent, Child;
int X;
X = H->Data[p]; /* 取出根结点存放的值 */
for( Parent=p; Parent*2<=H->Size; Parent=Child ) {
Child = Parent * 2;
if( (Child!=H->Size) && (H->Data[Child]<H->Data[Child+1]) )
Child++; /* Child指向左右子结点的较大者 */
if( X >= H->Data[Child] ) break; /* 找到了合适位置 */
else /* 下滤X */
H->Data[Parent] = H->Data[Child];
}
H->Data[Parent] = X;
}
void BuildHeap( MaxHeap H )
{ /* 调整H->Data[]中的元素,使满足最大堆的有序性 */
/* 这里假设所有H->Size个元素已经存在H->Data[]中 */
int i;
/* 从最后一个结点的父节点开始,到根结点1 */
for( i = H->Size/2; i>0; i-- )
PercDown( H, i );
}
哈夫曼树 依赖最小堆
哈夫曼树 依靠每个元素的概率来建树 带权路径最小
哈夫曼的构造 每次把权值最小的两棵二叉树合并
利用最小堆来实现选择数组中的最小元素效率要比排序算法要好
struct HuffmanTree
{
int weight;
HuffmanTree* Left;
HuffmanTree* Right;
};
HuffmanTree* huffman(MinHeap H)
{
int i;
HuffmanTree* temp;
BuildMinHeap(H);//构建最小堆
for(i=1;i<H->Size;i++)
{
temp = (HuffmanTree*)malloc(sizeof(HuffmanTree));
temp->Left = DeleteMin(H);
temp->Right = DeleteMin(H);
temp->weight = temp->Left->weight+ temp->Right->weight;
insert(H,temp);
}
temp = pop(H);//将最后值弹出
return temp;
}
集合相交
/*
集合相交 或者线路连通 那么若各结点相连
根节点相同则说明是交集或者是连通
数组中每个元素的类型描述
*/
typedef struct {
ElementType Data;
int parent;
}SetType;
/*循环遍历每个元素的parent 一层层向上遍历 若最后根结点相同 那么集合相同*/