繪制二叉樹
在學習二叉樹的時候,二叉樹里的數據不容易直觀地體現出來,因為它的結構比較特殊,不能很好發揮 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;
};