持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情
手把手教学考研大纲范围内树定义,遍历,Huffman,并查集 22考研大纲数据结构要求的是C/C++,笔者以前使用的都是Java,对于C++还很欠缺, 如有什么建议或者不足欢迎大佬评论区或者私信指出 初心是用最简单的语言描述数据结构
Talk is cheap. Show me the code. 理论到处都有,代码加例题自己练习才能真的学会
二叉树顺序存储
//二叉树的顺序存储(类似于完全二叉树)
//其实就是数组 建议看看堆排序
#include "iostream"
#include "queue"
#include "stack"
#include "cmath"
#include <algorithm>
#define MAXSIZE 1000
using namespace std;
//顺序存储的二叉树本身就是一个数组
int tree[MAXSIZE] = {0}; //二叉树数组
bool isbool[MAXSIZE] = {0}; //判断当前结点是否有数据
queue<int> prqueue; //保存各个结点的下标 (下标队列)
int length = 0; //当前二叉树存在元素的长度
int depth = 0; //表示当前二叉树的深度
//此方法为,一层一层向下选择位置,找到位置后插入
bool treeInsert(int data) { //二叉树插入数据
int index = 0;
int tempdeep = 0; //二叉树深度的临时变量, 插入结点的时候检测深度有没有变化
while (index < MAXSIZE) { //判断index是否超过二叉树的容量了,(顺序存储本身是一个数组,方式下标超过数组长度发生异常)
if (!isbool[index]) { //如果当前结点不存在可以插入到当前结点
cout << "此节点为空,输入 1 (或者其他) 确认插入,输入 -2 返回上一结点,输入 -1 退出\n";
int temp;
cin >> temp; //输入上面描述的操作数
if (temp == -2) { //输入 -2 返回上一结点
if (index == 0) { //根节点无法返回上一结点
return false;
}
index = (index - 1) / 2; //完全二叉树特点,当前结点的父节点的关系
tempdeep--; //临时深度-1 向上走了一层
continue;
} else if (temp == -1) { //输入 -1 退出,不插入数据
return false;
} else { //输入其他确定插入
tree[index] = data; //把传进来的值插入二叉树的当前结点
isbool[index] = true; //记录当前结点有值
length++; //二叉树结点+1
tempdeep++; //临时深度+1
depth = max(depth, tempdeep); //看此次插入数据的深度是否比以往的深度要深
prqueue.push(index); //把当前点对应的下标放到队列中
return true;
}
}
//当前结点不为空(当前结点不能插入),选择左子树或右子树
int leftNode = index * 2 + 1, rightNode = index * 2 + 2; //左右子树的规律根据完全二叉树来的
// if (isbool[leftNode]) {
cout << "输入 1 选择左子树";
// }
// if (isbool[rightNode]) {
cout << "输入 2 选择右子树";
// }
if (index != 0) {
cout << "输入 3 返回上一结点";
}
cout << "输入 -1(或其他) 退出插入操作\n";
int selected;
cin >> selected; //输入操作
if (selected == 1) { //选择左子结点
index = leftNode;
tempdeep++;
} else if (selected == 2) { //选择柚子结点
index = rightNode;
tempdeep++;
} else if (selected == 3) {
if (index == 0) { //头结点不能返回上一结点
return false;
}
index = (index - 1) / 2; //返回父节点
tempdeep--;
} else {
return false;
}
}
}
bool treeDelete() { //删除结点
int index = 0; //当前结点下标
while (index < MAXSIZE) { //防止结点下标越界
if (!isbool[index]) { //当前结点为空
cout << "当前节点为空,不能删除,返回上一结点\n";
if (index == 0) { //如果是根节点为空,无法返回上一结点
return false;
}
index = (index - 1) / 2; //返回上一结点
}
int leftNode = index * 2 + 1, rightNode = index * 2 + 2; //父节点与子结点的关系
if (isbool[leftNode]) { //先判断是否存在左子树存在,才能提示
cout << "输入 1 选择左子树";
}
if (isbool[rightNode]) { //先判断是否存在右子树存在,才能提示
cout << "输入 2 选择右子树 ";
}
if (index != 0) { //不是父结点就能返回上一结点
cout << "输入 3 返回上一结点 ";
}
cout << "输入 4 删除当前结点 输入 -1(或其他) 退出删除操作\n";
int selected;
cin >> selected;
if (selected == 1 && isbool[leftNode]) { //选择左子树要判断左子树是否为空
index = leftNode;
} else if (selected == 2 && isbool[rightNode]) { //右子树同理
index = rightNode;
} else if (selected == 3) {
if (index == 0) { //头结点不能返回上一结点
return false;
}
index = (index - 1) / 2; //子结点与父节点的关系
} else if (selected == 4) { //删除当前结点
break;
} else { //输入 其他 退出删除
return false;
}
}
//删除结点采用BFS方法 (主要是如果删除某个结点要把他的子树都删除)
queue<int> q; //q(删除队列) 中存放要删除的结点
q.push(index); //把当前结点添加到删除队列
int i = 0;
while (!q.empty()) {
index = q.front(); //拿到第一位删除元素的下标
q.pop(); //把当前元素从队列弹出(上面已经拿到了此元素)
int leftNode = index * 2 + 1, rightNode = index * 2 + 2; //找到当前删除结点的 左子结点 和 右子结点
if (isbool[index]) { //如果存在当前结点才能删除
isbool[index] = false; //清除当前结点的存在状态
tree[index] = 0; //当前结点值清为0
int size = prqueue.size(); //获取 删除队列 的大小
for (int j = 0; j < size; j++) { //从 删除队列 中删除当前结点
if (prqueue.front() != index) { //如果 删除队列的头结点 不是当前结点
prqueue.push(prqueue.front()); //把队列的头结点在插入到 删除队列中(头结点存在,队列后面又插入一遍)
}
prqueue.pop(); //把队列头结点弹出(删除) ,如果头结点是当前结点那么上面就不会把头结点在插入到删除队列一遍
}
length--; //二叉树元素数量-1
if (isbool[leftNode]) { //如果存在左子结点 就把左子结点添加到删除队列
q.push(leftNode);
}
if (isbool[rightNode]) { //右节点也同理
q.push(rightNode);
}
}
}
if (prqueue.empty()) { //如果 二叉树存下标的队列 为空,深度为0
depth = 0;
return true;
}
int maxindex = -1; //找到除了删除的结点外最大的结点的位置
int size = prqueue.size(); //获得 二叉树存下标的队列的大小
for (int j = 0; j < size; j++) { //循环此队列,找到最大的下标
if (prqueue.front() != index) {
maxindex = max(prqueue.front(), maxindex);
prqueue.push(prqueue.front());
}
prqueue.pop();
}
//删除节点后,取位置的最大结点,重构深度
int sum = 1;
int start = 1;
int tempdeep = 1;
//根据完全二叉树的规律,重构当前二叉树的深度
//数量从1开始,下标从0开始, 判断的时候需要数量-1 < maxindex
while (sum - 1 < maxindex) { //一层一层的加,如果小于最大的下标就一直向下层加,一直加到>=maxindex 能放下当前二叉树
start *= 2; //start为每一层的数量
sum += start; //sum为数量总和
tempdeep++;
}
depth = tempdeep; //重新赋值深度
}
void treePrint() {
cout << "打印二叉树\n";
int index = 0;
//二叉树空格多找一找规律就能得出
for (int i = 0; i < depth; i++) {
//每一层前面的空格从上到下的规律是15 7 3 1 0
//不难发现规律就是 本层空格 就是 下层空格*2+1 底层一定是0个
int space = 0;
//depth是二叉树深度,也就是最底层是多少
for (int j = 0; j < (depth - i - 1); j++) { //底层循环0次,倒数第二层循环1次,层数靠上循环次数越多
space = space * 2 + 1;
}
for (int j = 0; j < space; j++) { //输出空格,输出space个
cout << " ";
}
for (int j = 0; j < pow(2,i); j++) {
cout << tree[index++];
for (int k = 0; k <space * 2 + 1; k++) { //字符和字符的空格数量为 2*space+1
cout << " ";
}
}
cout << "\n";
}
}
/*
* 1
* 1 1
* 1 1 1 1
* 1 1 1 1 1 1 1 1
*1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
* */
void treeClear() { //清除二叉树
int size = prqueue.size();
for (int i = 0; i < size; i++) { //循环下标队列中的每一个结点下标
int index = prqueue.front();
tree[index] = 0; //把二叉树当前结点的值清0
isbool[index] = false; //二叉树当前结点状态为false
prqueue.pop(); //清除完当前结点,从下标队列删除头结点
}
depth = 0; //二叉树深度为0
}
int treeDepth() { //返回二叉树的深度
return depth;
}
//先序遍历:先输出根结点,在输出左子结点,在输出右子结点
void treePreorderRecursionPrint(int index) { //先序遍历(递归)
if (isbool[index]) { //判断当前结点是否为空
cout << tree[index] << " "; //先输出结点,先找到的结点为根结点
treePreorderRecursionPrint(index * 2 + 1); //然后找左子结点
treePreorderRecursionPrint(index * 2 + 2); //找右子结点
}
}
void treePreorderPrint() { //先序遍历(非递归)
cout << "先序遍历:\n";
stack<int> nodes; //存放结点的栈,因为先序就是输出根节点,然后先找左下结点,在找右下结点
int index = 0; //从根结点压栈,一直到下面,出栈的时候是从下面到上面出栈
while (isbool[index] || !nodes.empty()) { //只要栈内还有结点或者当前结点不为空就要继续遍历
while (isbool[index]) { //当前结点不为空就输出当前结点,然后把当前结点压栈,指向左子结点
cout << tree[index] << " ";
nodes.push(tree[index]);
index = index * 2 + 1; //不断指向左子结点,直到左子结点为空为止
}
//上面循环结束,说明左子结点为空
if (!nodes.empty()) { //栈不为空,弹出一个元素,去找这个元素的右子结点
index = nodes.top(); //上面压栈进去的都是根结点,上面循环结束说明左子结点为空,
nodes.pop();
index = index * 2 + 2; //找这个结点的右子结点
}
}
cout << "\n";
}
//中序遍历:先输出左子结点,根结点,右子结点
//其实中序遍历和前序遍历是差不多的
void treeMiddleOrderRecursionPrint(int index) { //中序遍历(递归)
if (isbool[index]) { //当前结点不为空
treeMiddleOrderRecursionPrint(index * 2 + 1); //先找左子结点,把左子结点找完
cout << tree[index] << " "; //输出根结点
treeMiddleOrderRecursionPrint(index * 2 + 2); //找右子结点
}
}
void treeMiddleOrderPrint() { //中序遍历(非递归)
cout << "中序遍历\n";
stack<int> nodes; //用栈保存结点下标
int index = 0; //根节点下标
while (isbool[index] || !nodes.empty()) { //当前结点存在,或栈内的结点不为空就一直遍历
while (isbool[index]) { //只要当前结点存在,就压栈,压入的是根节点
nodes.push(index);
index = index * 2 + 1; //然后一直找左子结点,
}
//循环结束是左子节点为空
if (!nodes.empty()) { //栈内的结点是否为空
index = nodes.top();
nodes.pop();
cout << tree[index] << " "; //输出弹出的根节点
index = index * 2 + 2; //找根节点的右子结点
}
}
cout << "\n";
}
//后序遍历: 先输出左子结点,右子结点,根节点
//后序遍历在递归方式中是和前序中序差不多
//但是非递归情况下 就和前序中序有些不同
void treePostOrderRecursionPrint(int index) { //后序遍历 (递归)
if (isbool[index]) { //判断当前结点是否存在
treePostOrderRecursionPrint(index * 2 + 1); //先找左子结点
treePostOrderRecursionPrint(index * 2 + 2); //找右子结点
cout << tree[index] << " "; //左右子结点都输出后,才输出根节点
}
}
//当前结点压栈,一直向左子结点访问,一直压栈
//判断右结点是否存在,或者右节点是否被访问了
//如果右节点存在且没访问,那么就访问右节点
void treePostOrderPrint() { //后序遍历 (非递归)
cout << "后序遍历\n";
stack<int> nodes;
int index = 0; //当前结点下标
int lastindex = 0; //用此变量记录上一个访问的结点
while (index != -1 && isbool[index] || !nodes.empty()) { //当前结点存在并且左右结点都访问了(index=-1只有左右结点都被访问才会出现)
//或者nodes栈内结点不为空
while (isbool[index]) { //当前结点存在,压栈,不断找左子结点(压栈相当于压入的根结点)
nodes.push(index);
index = index * 2 + 1;
} //循环结束,左子结点为空
index = nodes.top(); //保存栈顶值,不出栈
if(!isbool[index * 2 + 2] || lastindex == index * 2 + 2) { //右子节点不存在 或者 右子结点上次被访问了
cout << tree[index] << " "; //证明左结点和右结点都访问过了,然后输出根结点(访问当前根结点)
nodes.pop();
lastindex = index; //记录当前被访问的结点
index = -1;
} else {
index = index * 2 + 2; //右结点没被访问,访问右结点
}
}
}
void treeFloorPrint() { //层序遍历 一层一层的输出
cout << "层序遍历\n";
int index = 0; //1 3 7 15 31
while (index < pow(2, depth) - 1) { //层序遍历每一层的数量都是上一层的二倍(完全二叉树)
if (isbool[index]) { //如果当前点存在,就可以输出当前结点
cout << tree[index] << " ";
}
index++;
}
}
int main() {
while (true) {
cout << "输入 1 添加结点 输入 2 输出结点 输出 其他 删除结点 \n";
int temp;
cin >> temp;
if (temp == 1) {
cout << "输入要插入的值\n";
cin >> temp;
treeInsert(temp);
} else if(temp == 2) {
// treePreorderRecursionPrint(0);
cout << "\n";
treePreorderPrint();
// treeMiddleOrderRecursionPrint(0);
cout << "\n";
treeMiddleOrderPrint();
// treePostOrderRecursionPrint(0);
treePostOrderPrint();
cout << "\n";
treeFloorPrint();
cout << "\n";
} else {
treeDelete();
}
treePrint();
}
return 0;
}