290 lines
9.0 KiB
C++
290 lines
9.0 KiB
C++
/**
|
||
* File: print_utils.hpp
|
||
* Created Time: 2021-12-19
|
||
* Author: krahets (krahets@163.com), msk397 (machangxinq@gmail.com), LoneRanger(836253168@qq.com)
|
||
*/
|
||
|
||
#pragma once
|
||
|
||
#include "list_node.hpp"
|
||
#include "tree_node.hpp"
|
||
#include <climits>
|
||
#include <iostream>
|
||
#include <sstream>
|
||
#include <string>
|
||
|
||
/* Find an element in a vector */ // 在一个 vector 中查找元素
|
||
template <typename T>
|
||
int vecFind(const vector<T> &vec, T ele)
|
||
{
|
||
int j = INT_MAX;
|
||
for (int i = 0; i < vec.size(); i++)
|
||
{
|
||
if (vec[i] == ele) // 当找到元素时,返回元素的下标
|
||
{
|
||
j = i;
|
||
}
|
||
}
|
||
return j;
|
||
}
|
||
|
||
/* Concatenate a vector with a delim */ // 将一个 vector 用指定的分隔符连接成字符串
|
||
template <typename T>
|
||
string strJoin(const string &delim, const T &vec) // delim 是分隔符,vec 是 vector(使用&vec表示传递的是引用, 而不是拷贝, 以提高效率)
|
||
{
|
||
ostringstream s; // 创建一个输出流对象
|
||
for (const auto &i : vec)
|
||
{
|
||
if (&i != &vec[0])
|
||
{
|
||
s << delim; // 将分隔符添加到输出流对象中
|
||
}
|
||
s << i; // 将元素添加到输出流对象中
|
||
}
|
||
return s.str(); // .str() 方法是输出流对象的成员函数,用于返回输出流对象的字符串表示
|
||
}
|
||
|
||
/* Repeat a string for n times */ // 将一个字符串重复 n 次
|
||
string strRepeat(string str, int n)
|
||
{
|
||
ostringstream os; // 创建一个输出流对象
|
||
for (int i = 0; i < n; i++)
|
||
os << str; // 将字符串重复 n 次
|
||
return os.str(); // 返回输出流对象的字符串表示
|
||
}
|
||
|
||
/* Print an Array */ // 打印一个数组
|
||
template <typename T>
|
||
void printArray(T *arr, int n) // arr 是数组的首地址,n 是数组的长度
|
||
{
|
||
cout << "[";
|
||
for (int i = 0; i < n - 1; i++)
|
||
{
|
||
cout << arr[i] << ", "; // 逐个打印数组元素
|
||
}
|
||
if (n >= 1) // 当数组长度大于等于 1 时
|
||
cout << arr[n - 1] << "]" << endl; // 打印数组最后一个元素 并 附加一个 ']' 符号
|
||
else
|
||
cout << "]" << endl; // 当数组长度为 0 时,直接打印一个 ']' 符号
|
||
}
|
||
|
||
/* Get the Vector String object */ // 获取一个 vector 的字符串表示
|
||
template <typename T>
|
||
string getVectorString(vector<T> &list) // list 是 vector
|
||
{
|
||
return "[" + strJoin(", ", list) + "]"; // 使用 strJoin 函数将 vector 用逗号连接成字符串,并在字符串两端添加 '[' 和 ']'
|
||
}
|
||
|
||
/* Print a vector */ // 打印一个 vector
|
||
template <typename T>
|
||
void printVector(vector<T> list)
|
||
{
|
||
cout << getVectorString(list) << '\n'; // 使用 getVectorString 函数获取 vector 的字符串表示,并打印
|
||
}
|
||
|
||
/* Print a vector matrix */ // 打印一个 vector 矩阵
|
||
template <typename T>
|
||
void printVectorMatrix(vector<vector<T>> &matrix) // matrix 是一个二维 vector, 其中每个元素都是一个 vector, 用于表示矩阵 (数组的数组)
|
||
{
|
||
cout << "[" << '\n';
|
||
for (vector<T> &list : matrix) // 遍历 matrix 中的每一个 vector 元素
|
||
cout << " " + getVectorString(list) + "," << '\n'; // 使用 getVectorString 函数获取 vector 的字符串表示,并打印
|
||
cout << "]" << '\n';
|
||
}
|
||
|
||
/* Print a linked list */ // 打印一个链表
|
||
void printLinkedList(ListNode *head)
|
||
{
|
||
vector<int> list; // 创建一个 vector 用于存储链表的元素
|
||
while (head != nullptr) // 遍历链表: 当链表的指针不为空时, 继续遍历
|
||
{
|
||
list.push_back(head->val); // 将链表的元素添加到 vector 中
|
||
head = head->next; // 将指针指向链表的下一个元素
|
||
}
|
||
|
||
cout << strJoin(" -> ", list) << '\n'; // 使用 strJoin 函数将 vector 用箭头连接成字符串,并打印
|
||
}
|
||
|
||
/**
|
||
* This tree printer is borrowed from TECHIE DELIGHT // 该树打印器是从 TECHIE DELIGHT 借用的
|
||
* https://www.techiedelight.com/c-program-print-binary-tree/
|
||
*/
|
||
struct Trunk // 树干结构体
|
||
{
|
||
Trunk *prev; // 指向上一个树干
|
||
string str; // 树干的字符串表示
|
||
Trunk(Trunk *prev, string str) // 构造函数 (cpp 中的 struct 也可以有构造函数)
|
||
{
|
||
this->prev = prev;
|
||
this->str = str;
|
||
}
|
||
};
|
||
|
||
/* Helper function to print branches of the binary tree */ // 用于打印二叉树的辅助函数
|
||
void showTrunks(Trunk *p)
|
||
{
|
||
if (p == nullptr) // 当树干为空时,直接返回
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 递归调用的顺序与打印的顺序相反
|
||
showTrunks(p->prev); // 递归调用 showTrunks 函数
|
||
cout << p->str; // 打印树干的字符串表示
|
||
}
|
||
|
||
/* Print a binary tree */ // 打印一个二叉树
|
||
void printTree(TreeNode *root, Trunk *prev, bool isRight)
|
||
{
|
||
if (root == nullptr)
|
||
{
|
||
return;
|
||
}
|
||
|
||
string prev_str = " "; // 初始化 prev_str 为空格
|
||
Trunk trunk(prev, prev_str); // 创建一个树干对象
|
||
|
||
printTree(root->right, &trunk, true); // 递归调用 printTree 函数,打印右子树
|
||
|
||
if (!prev) // 当树干为空时
|
||
{
|
||
trunk.str = "———"; // 树干的字符串表示为 "———"
|
||
}
|
||
else if (isRight) // 当树干不为空且是右子树时
|
||
{
|
||
trunk.str = "/———"; // 树干的字符串表示为 "/———"
|
||
prev_str = " |"; // prev_str 为 " |"
|
||
}
|
||
else // 当树干不为空且不是右子树时
|
||
{
|
||
trunk.str = "\\———"; // 树干的字符串表示为 "\———"
|
||
prev->str = prev_str; // prev_str 为 prev->str
|
||
}
|
||
|
||
showTrunks(&trunk); // 调用 showTrunks 函数,打印树干
|
||
cout << " " << root->val << endl; // 打印节点的值
|
||
|
||
if (prev) // 当树干不为空时
|
||
{
|
||
prev->str = prev_str; // prev->str 为 prev_str
|
||
}
|
||
trunk.str = " |"; // 树干的字符串表示为 " |"
|
||
|
||
printTree(root->left, &trunk, false); // 递归调用 printTree 函数,打印左子树
|
||
}
|
||
|
||
/* The interface of the tree printer */ // 二叉树打印器的接口 (函数重载: 目的是为了方便调用)
|
||
void printTree(TreeNode *root)
|
||
{
|
||
printTree(root, nullptr, false); // 调用 printTree 函数 (重载版本)
|
||
}
|
||
|
||
/* Print a stack */ // 打印一个栈
|
||
template <typename T>
|
||
void printStack(stack<T> stk)
|
||
{
|
||
// Reverse the input stack // 反转输入的栈
|
||
stack<T> tmp; // 创建一个临时栈
|
||
while (!stk.empty()) // 当输入的栈不为空时 (遍历输入的栈)
|
||
{
|
||
tmp.push(stk.top()); // 将输入的栈的元素逐个添加到临时栈中
|
||
stk.pop(); // 弹出输入的栈的元素
|
||
}
|
||
|
||
// Generate the string to print // 生成要打印的字符串
|
||
ostringstream s; // 创建一个输出流对象
|
||
bool flag = true; // 创建一个标志位 (默认为 true), 用来标记是否是第一个元素
|
||
while (!tmp.empty())
|
||
{
|
||
if (flag)
|
||
{
|
||
s << tmp.top(); // 当标志位为 true 时,将栈顶元素添加到输出流对象中
|
||
flag = false; // 将标志位设置为 false
|
||
}
|
||
else // 当标志位为 false 时
|
||
s << ", " << tmp.top(); // 将栈顶元素添加到输出流对象中
|
||
tmp.pop(); // 弹出栈顶元素
|
||
}
|
||
cout << "[" + s.str() + "]" << '\n'; // 使用输出流对象的字符串表示,并打印
|
||
}
|
||
|
||
/* Print a queue */ // 打印一个队列
|
||
template <typename T>
|
||
void printQueue(queue<T> queue) // queue 是队列
|
||
{
|
||
// Generate the string to print // 生成要打印的字符串
|
||
ostringstream s;
|
||
bool flag = true;
|
||
while (!queue.empty())
|
||
{
|
||
if (flag)
|
||
{
|
||
s << queue.front(); // 当标志位为 true 时,将队列的队首元素添加到输出流对象中 (true 表示是第一个元素)
|
||
flag = false;
|
||
}
|
||
else
|
||
s << ", " << queue.front(); // 当标志位为 false 时,将队列的队首元素前面添加一个逗号,并添加到输出流对象中
|
||
queue.pop(); // 弹出队首元素
|
||
}
|
||
cout << "[" + s.str() + "]" << '\n'; // 使用输出流对象的字符串表示,并打印
|
||
}
|
||
|
||
/* Print a deque */ // 打印一个双端队列
|
||
template <typename T>
|
||
void printDeque(deque<T> deque) // deque 是双端队列(念做 deck)
|
||
{
|
||
// Generate the string to print // 生成要打印的字符串
|
||
ostringstream s;
|
||
bool flag = true;
|
||
while (!deque.empty()) // 当双端队列不为空时
|
||
{
|
||
if (flag)
|
||
{
|
||
s << deque.front();
|
||
flag = false;
|
||
}
|
||
else
|
||
s << ", " << deque.front();
|
||
deque.pop_front(); // 弹出队首元素
|
||
}
|
||
cout << "[" + s.str() + "]" << '\n';
|
||
}
|
||
|
||
/* Print a HashMap */ // 打印一个哈希表
|
||
// 定义模板参数 TKey 和 TValue ,用于指定键值对的类型
|
||
template <typename TKey, typename TValue>
|
||
void printHashMap(unordered_map<TKey, TValue> map) // map 是哈希表
|
||
{
|
||
for (auto kv : map) // auto 关键字用于自动推导变量的类型
|
||
{
|
||
cout << kv.first << " -> " << kv.second << '\n'; // .first 表示键,.second 表示值
|
||
}
|
||
}
|
||
|
||
/* Expose the underlying storage of the priority_queue container */ // 暴露优先队列容器的底层存储
|
||
template <typename T, typename S, typename C>
|
||
S &Container(priority_queue<T, S, C> &pq) // pq 是优先队列
|
||
{
|
||
struct HackedQueue : private priority_queue<T, S, C> // 创建一个私有继承的优先队列
|
||
{
|
||
static S &Container(priority_queue<T, S, C> &pq) // 创建一个静态成员函数 Container
|
||
{
|
||
return pq.*&HackedQueue::c; // 返回优先队列的底层存储
|
||
}
|
||
};
|
||
return HackedQueue::Container(pq); // 调用静态成员函数 Container
|
||
}
|
||
|
||
/* Print a Heap (PriorityQueue) */ // 打印一个堆(优先队列)
|
||
template <typename T, typename S, typename C>
|
||
void printHeap(priority_queue<T, S, C> &heap) // heap 是优先队列
|
||
{
|
||
vector<T> vec = Container(heap); // 获取优先队列的底层存储
|
||
cout << "堆的数组表示:";
|
||
printVector(vec);
|
||
cout << "堆的树状表示:" << endl;
|
||
TreeNode *root = vectorToTree(vec); // 新建一个二叉树 root,将优先队列的底层存储转换为二叉树
|
||
printTree(root);
|
||
freeMemoryTree(root);
|
||
}
|