绘制二叉树
在学习二叉树的时候,二叉树里的数据不容易直观地体现出来,因为它的结构比较特殊,不能很好发挥 print 大法
我写了一个比较简单实用的 Windows 窗口来绘制二叉树,用 GDI 函数绘制的,如果觉得太粗糙可以很容易修改成 GDI+

使用也比较简单, 只需要写一个回调函数用来填充 NodeInfo 结构体就好了
struct NodeInfo
{
void* left;
void* right;
COLORREF ColorBorder;
COLORREF ColorFill;
COLORREF ColorLabel;
TCHAR Label[256];
};
比如有这样一个二叉根节点, 只需要填充好 NodeInfo 的左右节点和显示的文本就好了,还可以根据节点内容设置字体颜色、边框颜色和填充颜色
在绘制的时候会遍历整个二叉树,会为每个节点调用一次回调函数
回调函数的第一个参数是 DisplayBinaryTree 方法设置的回调上下文参数, 第二个参数是当前二叉树节点指针,第三个参数是当前二叉树节点指针在整个树的层级, 第四个参数是输出参数 NodeInfo 指针,回调函数返回前必须要设置好 left ,right 和 Label 字段。如果使用了哨兵,如果当前节点的子节点是哨兵,必须把对应的 left 或者right 设置为空指针,
struct node
{
node* left;
node* right;
int key;
};
void NodeInfoCallback(void* context, void* node_, int hierarchy, NodeInfo* nodeInfo)
{
auto x = (node*)(node_);
nodeInfo->left = x->left; // 设置左节点
nodeInfo->right = x->right; // 设置右节点
_stprintf_s(nodeInfo->Label, L"%d", x->key); // 需要显示的文本
//nodeInfo->ColorFill = RGB(255, 0, 0); // 填充色
//nodeInfo->ColorLabel = RGB(0, 0, 0); // 文本颜色
//nodeInfo->ColorBorder = RGB(0,128 ,128); // 边框颜色
}
写好回调函数后, 可以实例化一个 TreeWindow 对象调用 DisplayBinaryTree 方法,传入根节点指针,回调和一个可选的回调上下文参数
TreeWindow wnd;
wnd.DisplayBinaryTree(root, NodeInfoCallback, nullptr);
TreeWindow 窗口类代码
#include <atlbase.h>
#include <atlwin.h>
struct NodeInfo
{
void* left;
void* right;
COLORREF ColorBorder;
COLORREF ColorFill;
COLORREF ColorLabel;
TCHAR Label[256];
};
class TreeWindow : public CWindowImpl<TreeWindow, CWindow, CWinTraits<WS_OVERLAPPEDWINDOW> >
{
public:
TreeWindow()
{
_thread.Attach(AtlCreateThread<TreeWindow>(&TreeWindow::_threadCallback, this));
while (!(const_cast<volatile HWND&>(m_hWnd)))
{
Sleep(100);
}
}
~TreeWindow()
{
PostMessage(WM_QUIT);
::WaitForSingleObject(_thread, INFINITE);
_thread.Close();
}
BEGIN_MSG_MAP(TreeWindow)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
END_MSG_MAP()
void DisplayBinaryTree(void* tree, void (*nodeInfoCallback)(void*, void*, int, NodeInfo*), void* context)
{
ATLASSERT(tree);
ATLASSERT(nodeInfoCallback);
if (tree && nodeInfoCallback)
{
_tree = tree;
_nodeInfoCallback = nodeInfoCallback;
_context = context;
Refresh();
}
else
{
_tree = nullptr;
_nodeInfoCallback = nullptr;
}
}
void Refresh()
{
Invalidate();
UpdateWindow();
}
protected:
LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
PAINTSTRUCT sp;
auto dc = BeginPaint(&sp);
SetTextAlign(dc, TA_CENTER | TA_BOTTOM);
SelectObject(dc, GetStockObject(DEFAULT_GUI_FONT));
RECT rect;
GetClientRect(&rect);
auto width = rect.right - rect.left;
auto height = rect.bottom - rect.top;
auto halfWidth = width / 2;
auto halfHeight = height / 2;
POINT nodePos = { halfWidth, _rowSpacing };
POINT nextNodePos = nodePos;
if (_tree && _nodeInfoCallback)
{
void* nodeStack[256] = { _tree }; //节点栈, 保存当前访问节点在二叉树中的路径
POINT nodePosStack[256] = { nodePos }; //节点栈中每个节点的位置,
int n = 1; //
void* y = nullptr; // 临时变量, 保存最后访问的节点,用来维护层级
int h = 1; // 当前节点在二叉根中的层级
while (n > 0)
{
void* x = nodeStack[n - 1]; // 取出当前节点
nodePos = nodePosStack[n - 1]; // 取出当前位置
NodeInfo nodeInfo = {};
nodeInfo.ColorFill = 0xffffff;
_nodeInfoCallback(_context, x, h, &nodeInfo);
if (nodeInfo.left || nodeInfo.right)
{
if (y && (nodeInfo.left == y || nodeInfo.right == y))
{
n--; // 退栈
h--;
}
else
{
if (nodeInfo.right)
{
nextNodePos.y = nodePos.y + _rowSpacing;
nextNodePos.x = nodePos.x + (halfWidth - 50) / ( 1 << h);
MoveToEx(dc, nodePos.x, nodePos.y, nullptr);
LineTo(dc, nextNodePos.x, nextNodePos.y);
nodeStack[n] = nodeInfo.right;
nextNodePos.y += 10;
nodePosStack[n] = nextNodePos;
n++;
}
if (nodeInfo.left)
{
nextNodePos.y = nodePos.y + _rowSpacing;
nextNodePos.x = nodePos.x - (halfWidth - 50) / (1 << h);
MoveToEx(dc, nodePos.x, nodePos.y, nullptr);
LineTo(dc, nextNodePos.x, nextNodePos.y);
nodeStack[n] = nodeInfo.left;
nextNodePos.y += 10;
nodePosStack[n] = nextNodePos;
n++;
}
DrawNode(dc, &nodePos, &nodeInfo);
h++;
}
}
else
{
DrawNode(dc, &nodePos, &nodeInfo);
n--;
}
y = x;
}
}
EndPaint(&sp);
return 0;
}
virtual DWORD _ThreadCallback()
{
MSG msg;
Create(nullptr);
UpdateWindow();
ShowWindow(SW_SHOW);
while (GetMessage(&msg, 0, 0, 0))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
DestroyWindow();
return 0;
}
virtual void DrawNode(HDC dc, LPPOINT pos, NodeInfo* nodeInfo)
{
COLORREF textColor = SetTextColor(dc, nodeInfo->ColorLabel);
HBRUSH brush = CreateSolidBrush(nodeInfo->ColorFill);
HPEN borderPen = CreatePen(PS_SOLID, 1, nodeInfo->ColorBorder);
ATLASSERT(brush);
HGDIOBJ oldBrush = SelectObject(dc, brush);
HGDIOBJ oldPen = SelectObject(dc, borderPen);
int oldBkMode = SetBkMode(dc, TRANSPARENT);
SIZE size;
int labelLen = _tcslen(nodeInfo->Label);
::GetTextExtentPoint32(dc, nodeInfo->Label, labelLen, &size);
size.cx += 20;
size.cy += 20;
RECT rect = { 0,0, size.cx , size.cy };
::OffsetRect(&rect, pos->x - size.cx / 2, pos->y - size.cy + 10 );
::Ellipse(dc, rect.left, rect.top, rect.right, rect.bottom);
if (nodeInfo->Label[0])
TextOut(dc, pos->x, pos->y, nodeInfo->Label, labelLen);
SetBkMode(dc, oldBkMode);
SelectObject(dc, oldPen);
SelectObject(dc, oldBrush);
SetTextColor(dc, textColor);
DeleteObject(brush);
DeleteObject(borderPen);
}
private:
static DWORD CALLBACK _threadCallback(TreeWindow* pthis)
{
return pthis->_ThreadCallback();
}
private:
CHandle _thread;
void* _tree = nullptr;
void* _context = nullptr;
void (*_nodeInfoCallback)(void* , void*, int, NodeInfo*) = nullptr;
int _rowSpacing = 48;
};