目录
前言
在直方图问题中,从⼀个具有n个关键值的集合开始,要求输出不同关键值的列表以及每个关键值在集合中出现的次数(频率)。下图给出了⼀个含有10个关键值的例子。图a给出了直方图的输⼊,直方图的表格形式如图b所⽰,直方图的图形形式 如图c 所示。直方图⼀般⽤来确定数据的分布,例如,考试的分数、图象中的灰色比例、在生产商注册的汽车和居住在洛杉矶的⼈所获得的最高学位都可以用直方图来表示。
分别使用三种结构(数组、链表散列、⼆叉搜索树),写出求解直方图问题的程序,通过图形显示运行时间的比较。
一、创建一个单文档程序
二、添加资源
1.在菜单中添加选项
如下(示例):
右击编辑id
输入菜单项对应ID_CIN
输出菜单项对应ID_COUT
2.在资源中添加对话框
输入对话框如下(示例):
为对话框绑定类,类名为CCin
绑定变量与编辑框中
先修改编辑框id
第一个编辑框为IDC_EDIT_COUT
第二个编辑框为IDC_EDIT_KEY
输出对话框如下(示例):
为对话框绑定类,类名为CCout
绑定变量与滑动条,编辑框中
先修改滑动条id
依次为IDC_PROGRESS_ARRAY,IDC_PROGRESS_HashList,IDC_PROGRESS_TREE
将所有编辑框设置为只读
再绑定变量
先修改编辑框id
依次为IDC_EDIT_ARRAY,IDC_EDIT_HashLIST,IDC_EDIT_SEARCHTREE
再绑定变量
三、数据处理
输入对话框会得到用户输入的关键字数量和关键字,我们将对其用三种不同的数据结构进行处理并记录处理所使用时间。将处理所得结果传参给主界面,使其绘图其直方图。将所使用时间传参给输出对话框,滑动条与编辑框结合显示。
1.散列链表和二叉搜索树
其中散列链表和二叉搜索树需要自行添加头文件
HashList.h
#pragma once
//结点
class Node {
public:
int key;//关键值
int value;//频率
Node* next;//同一个hash值的下一个值
Node(Node* next) {
this->next = next;
}
Node(int key, int value, Node* next) {
this->key = key;
this->value = value;
this->next = next;
}
};
class HashList {
private:
const int default_init_capacity = 8; //初始化大小
const float load_factor = 0.75f; //装载因子的阈值
int capacity = default_init_capacity; //容量
int size = 0; //实际数量
int used = 0; //已经使用的索引的数量,也就是table的下标已经使用了的数量
public:
Node** table;
HashList() {
table = new Node*[default_init_capacity];
for (int i = 0; i < default_init_capacity; i++) //如果上面new最后没加括号()一定要记得初始化
{
table[i] = nullptr;
}
}
~HashList()
{
for (int i = 0; i < capacity; i++) {
if (table[i] == nullptr ) {
delete table[i];
continue;
}
else if (table[i]->next == nullptr)
{
delete table[i]->next;
delete table[i];
continue;
}
Node* n = table[i]->next;
while (n != nullptr) {
Node* tmp = n;
n = n->next;
delete tmp;
}
}
delete[] table;
}
void put(int key, int value = 1)//放入元素
{
size_t index = hash(key);
if (table[index] == nullptr) //如果index这个位置未使用,创建哨兵
table[index] = new Node(nullptr);
if (table[index]->next == nullptr) //当前位置第一次插入
{
table[index]->next = new Node(key, value, nullptr);
++size;
++used;
if (used >= load_factor * capacity) //如果装载因子大于阈值就扩容
resize();
}
else
{ // 可能是key相同,也可能是不同key计算出相同的hash值(散列冲突)
Node* tmp = table[index]->next;
while (tmp != nullptr) {
if (tmp->key == key) { //如果是key相同,那么更新value
tmp->value++;
return;
}
tmp = tmp->next;
}
//上面遍历了一遍链表,key都没有相同的,这里就是散列冲突的情况了
tmp = table[index]->next;
table[index]->next = new Node(key, value, tmp);
++size;
}
}
bool empty()
{
if (size == 0)
return true;
else
return false;
}
int Size()
{
return size;
}
int Capacity()
{
return capacity;
}
private:
size_t hash(int key)
{
return key % capacity;
}
void resize() //扩容
{
Node** oldtable = table;
int oldcapacity = capacity;
used = 0; //used重置
capacity *= 2;
table = new Node*[capacity]();
for (int i = 0; i < oldcapacity; i++) {
if (oldtable[i] == nullptr || oldtable[i]->next == nullptr)
continue;
Node* e = oldtable[i];
while (e->next != nullptr) {
e = e->next; //oldtable[index]是哨兵,不存数据的,第一个next才开始存数据,所以这里要先e=e->next;
size_t index = hash(e->key); //重新计算hash,capacity变了hash可能会变
if (table[index] == nullptr) { //如果index这个位置未使用,创建一个哨兵
table[index] = new Node(nullptr);
++used;
}
table[index]->next = new Node(e->key, e->value, table[index]->next);
}
}
}
};
Tree.h
#pragma once
// 二叉树结点类模板
struct BinTreeNode
{
// 数据成员:
int key;// 数据成分
int value;// 数据成分
BinTreeNode *leftChild;// 左孩子指针成分
BinTreeNode *rightChild;// 右孩子指针成分
//构造函数模板:
BinTreeNode()// 无参数的构造函数模板
{
leftChild = rightChild = nullptr;// 叶结点左右孩子为空
}
BinTreeNode(int k, int v = 1,// 已知数据元素值,指向左右孩子的指针构造一个结点
BinTreeNode *lChild = nullptr,
BinTreeNode *rChild = nullptr)
{
key = k;// 数据元素值
value = v;// 数据元素值
leftChild = lChild;// 左孩子
rightChild = rChild;// 右孩子
}
};
#include"stdlib.h"
// 二叉排序树类模板
int* m_Tree_Key;//存贮关键字
int* m_Tree_Value;//存储关键字对应频率
int m_Tree_Num;//存贮关键字数量
class BinarySortTree
{
protected:
// 数据成员:
BinTreeNode *root;
public:
BinarySortTree()// 无参数的构造函数模板
{
root = nullptr;
}
BinarySortTree(int e)
// 操作结果:建立以e为根的二叉排序树
{
root = new BinTreeNode(e);
}
void DestroyHelp(BinTreeNode *&r)
// 操作结果:销毁以r的二叉排序树
{
if (r != NULL)
{// r非空,实施销毁
DestroyHelp(r->leftChild); // 销毁左子树
DestroyHelp(r->rightChild); // 销毁右子树
delete r; // 销毁根结点
r = NULL;
}
}
virtual ~BinarySortTree()// 析构函数模板
{
DestroyHelp(root);
}
void insert(int k)
{
//定义一个临时指针 用于移动
BinTreeNode* temp = root;//方便移动 以及 跳出循环
while (temp == nullptr)
{
BinTreeNode *p; // 插入的新结点
p = new BinTreeNode(k);
root = p;
return;
}
BinTreeNode* prev = nullptr;//定位到待插入位置的前一个结点
while (temp != nullptr)
{
prev = temp;
if (k < temp->key)
{
temp = temp->leftChild;
}
else if (k > temp->key)
{
temp = temp->rightChild;
}
else
{
temp->value++;
return;
}
}
if (k < prev->key)
{
prev->leftChild = (BinTreeNode*)malloc(sizeof(BinTreeNode));
prev->leftChild->key = k;
prev->leftChild->value = 1;
prev->leftChild->leftChild = nullptr;
prev->leftChild->rightChild = nullptr;
}
else if (k > prev->key)
{
prev->rightChild = (BinTreeNode*)malloc(sizeof(BinTreeNode));
prev->rightChild->key = k;
prev->rightChild->value = 1;
prev->rightChild->leftChild = nullptr;
prev->rightChild->rightChild = nullptr;
}
else if (k == prev->key)
{
prev->value++;
}
}
void PreOrderHelp(const BinTreeNode *r) const
// 操作结果:先序遍历以r为根的二叉排序树
{
if (r != NULL)
{
m_Tree_Key[m_Tree_Num] = r->key;// 访问根结点
m_Tree_Value[m_Tree_Num] = r->value;
m_Tree_Num++;
PreOrderHelp(r->leftChild); // 遍历左子树
PreOrderHelp(r->rightChild);// 遍历右子树
}
}
void PreOrder()
// 操作结果:先序遍历二叉排序树
{
m_Tree_Key = new int[100];//给一个较大的数组,防止越界
m_Tree_Value = new int[100];//给一个较大的数组,防止越界
m_Tree_Num = 0;
PreOrderHelp(root);
}
};
2.输入对话框数据处理
打开类向导在输入对话框类中添加如下自定义变量
添加确认键响应程序即双击确认键
void CCin::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData();//控件内容同步到变量中
int* m_key = new int[100];
int i = 0;
int count = 0;//统计输入关键字的数量
while (true)
{
int r = m_keystr.Find(',', i);
if (r == -1)//到达最后一个数字
{
m_key[count++] = _ttoi(m_keystr.Mid(i));
break;
}
m_key[count++] = _ttoi(m_keystr.Mid(i, r));//转换数字保存
i = r + 1;//下一次开始截取字符的位置
}
//如果统计到的关键字数量与输入的关键字数量不一致
if (count != m_count)
{
m_keystr = "";//清空
m_count = 0;
MessageBox(TEXT("输入错误,请重新输入"));//弹出提示
UpdateData(0);//更新控件内容
}
else
{
//数组
clock_t m_ArrayStart, m_ArrayEnd;
m_ArrayStart = clock();
m_ArrayKey = new int[100];
m_ArrayValue = new int[100];
m_ArrayKey[0] = m_key[0];
for (int i = 0; i < count; i++)
m_ArrayValue[i] = 0;
m_ArrayValue[0] = 1;
m_ArrayNum = 1;//没有重复的关键值个数
for (int i = 1; i < count; i++)
{
int j;
for (j = 0; j < m_ArrayNum; j++)
{
if (m_ArrayKey[j] == m_key[i])
{
m_ArrayValue[j] += 1;
break;
}
}
if (j == m_ArrayNum)
{
m_ArrayKey[m_ArrayNum] = m_key[i];
m_ArrayValue[m_ArrayNum] += 1;
m_ArrayNum++;
}
}
m_ArrayEnd = clock();
m_ArrayTime = m_ArrayEnd - m_ArrayStart;
//散列链表
clock_t m_ListStart, m_ListEnd;
m_ListStart = clock();
HashList hl;
for (int i = 0;i < m_count;i++)
{
hl.put(m_key[i]);//放入关键字
}
m_ListNum = hl.Size();//关键字个数
m_ListKey = new int[m_ListNum];//关键字数组
m_ListValue = new int[m_ListNum];//频率数组
int t = 0;
//遍历散列链表,将关键字与频率分别放入数组中
for (int i = 0; i < hl.Capacity(); i++) {
if (hl.table[i] == nullptr || hl.table[i]->next == nullptr) {
continue;
}
Node* n = hl.table[i]->next;
while (n != nullptr) {
m_ListKey[t] = n->key;
m_ListValue[t] = n->value;
n = n->next;
t++;
}
}
m_ListEnd = clock();
m_ListTime = m_ListEnd - m_ListStart;
//二叉搜索树
clock_t m_TreeStart, m_TreeEnd;//开始时间与结束时间
m_TreeStart = clock();//获取当前时间(ms)
BinarySortTree brt;
for (int i = 0;i < m_count;i++)
{
brt.insert(m_key[i]);//放入关键字
}
brt.PreOrder();
m_TreeNum = m_Tree_Num;
m_TreeKey = m_Tree_Key;
m_TreeValue = m_Tree_Value;
m_TreeEnd = clock();
m_TreeTime = m_TreeEnd - m_TreeStart;
CDialogEx::OnOK();
}
}
3.输出对话框数据处理
打开类向导在输出对话框类中添加如下自定义变量
在初始化函数OnInitDialog()中输入以下代码
BOOL CCout::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: 在此添加额外的初始化
m_ArrayTimeStr.Format("%d ms", m_Array_Time);
m_ArrayTime.SetRange(0, 50);
m_ArrayTime.SetPos(m_Array_Time);//设置滑动条位置
m_ListTimeStr.Format("%d ms", m_List_Time);
m_ListTime.SetRange(0, 50);
m_ListTime.SetPos(m_List_Time);//设置滑动条位置
m_TreeTimeStr.Format("%d ms", m_Tree_Time);
m_TreeTime.SetRange(0, 50);
m_TreeTime.SetPos(m_Tree_Time);//设置滑动条位置
UpdateData(0);//更新控件内容
return TRUE; // return TRUE unless you set the focus to a control
// 异常: OCX 属性页应返回 FALSE
}
4.主界面数据处理和绘图
在view类属性中添加输入对话框和输出对话框command响应程序。
在类向导中添加自定义绘图函数 void DrawArrayHistogram(GetDC())等
对话框响应函数代码如下:
void CEx_HistogramView::OnCin()
{
// TODO: 在此添加命令处理程序代码
CCin dlg;
if (IDOK == dlg.DoModal())
{
m_keystr = dlg.m_keystr;//获取关键字
m_count = dlg.m_count;//获取输入数量
//数组
clock_t m_ArrayStart, m_ArrayEnd;//设置起始时间
m_ArrayStart = clock();
m_ArrayKey = dlg.m_ArrayKey;//获取由数组统计的关键字
m_ArrayValue = dlg.m_ArrayValue;//对应的频率
m_ArrayNum = dlg.m_ArrayNum;//获取关键字的个数
m_ArrayTime = dlg.m_ArrayTime;//获取开始时间
DrawArrayHistogram(GetDC());//开始绘制数组直方图
m_ArrayEnd = clock();
clock_t m_Array_Time = m_ArrayEnd - m_ArrayStart;
m_ArrayTime += m_Array_Time;
//散列链表
clock_t m_ListStart, m_ListEnd;
m_ListStart = clock();
m_ListKey = dlg.m_ListKey;//获取由散列链表统计的关键字
m_ListValue = dlg.m_ListValue;//对应的频率
m_ListNum = dlg.m_ListNum;//获取关键字的个数
m_ListTime = dlg.m_ListTime;//获取开始时间
DrawListHistogram(GetDC());//开始绘制散列链表直方图
m_ListEnd = clock();
clock_t m_List_Time = m_ListEnd - m_ListStart;
m_ListTime += m_List_Time;
//二叉搜索树
clock_t m_TreeStart, m_TreeEnd;
m_TreeStart = clock();
m_TreeKey = dlg.m_TreeKey;//获取由二叉搜索树统计的关键字
m_TreeValue = dlg.m_TreeValue;//对应的频率
m_TreeNum = dlg.m_TreeNum;//获取关键字的个数
m_TreeTime = dlg.m_TreeTime;//获取开始时间
DrawTreeHistogram(GetDC());//开始绘制二叉搜索树直方图
m_TreeEnd = clock();
clock_t m_Tree_Time = m_TreeEnd - m_TreeStart;
m_TreeTime += m_Tree_Time;
}
}
void CEx_HistogramView::OnCout()
{
// TODO: 在此添加命令处理程序代码
CCout dlg;
dlg.m_Array_Time = m_ArrayTime;
dlg.m_List_Time = m_ListTime;
dlg.m_Tree_Time = m_TreeTime;
dlg.DoModal();
}
函数代码如下:
void CEx_HistogramView::DrawArrayHistogram(CDC* pDC)
{
COLORREF cr = RGB(255, 255, 255);
CPen Pen(PS_INSIDEFRAME, 2, cr);//颜色为白色的笔
CPen* oldPen = pDC->SelectObject(&Pen);//保存原来的笔
CRect rc;
GetClientRect(&rc);//获取当前窗口大小
CRect rc_array(0, 0, rc.Width() / 3, rc.Height());
pDC->Rectangle(rc_array);//覆盖当前窗口
pDC->SelectObject(oldPen);//还原原来的笔
//展示输入的个数和关键值
CString str;
str.Format("n=%d; 关键值=[%s]", m_count, m_keystr);
//编写字体,更加美观
LOGFONT lf;//定义逻辑字体的结构变量
memset(&lf, 0, sizeof(LOGFONT));//将lf所有成员置0
lf.lfHeight = 25;//设置字体高度
lf.lfWeight = 10;//设置字体宽度
lf.lfCharSet = GB2312_CHARSET;//用逻辑字体结构创建字体
CFont cf;//在设备环境中使用字体
cf.CreateFontIndirect(&lf);
CFont* oldfont = pDC->SelectObject(&cf);
pDC->DrawText(str, CRect(0, 0, rc.Width() / 3, 100), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
pDC->DrawText("Araay:", CRect(0, 100, rc.Width() / 3, 200), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
//画分割线
pDC->MoveTo(rc.Width() / 3, 0);
pDC->LineTo(rc.Width() / 3, rc.Height());
pDC->MoveTo(0, 100);
pDC->LineTo(rc.Width() / 3, 100);
pDC->MoveTo(0, 200);
pDC->LineTo(rc.Width() / 3, 200);
pDC->MoveTo(0, 200 + (rc.Height() - 200) / 2);
pDC->LineTo(rc.Width() / 3, 200 + (rc.Height() - 200) / 2);
//直方图的表格形式
CRect rc_b(0, 200, rc.Width() / 3, (rc.Height() - 200) / 2 + 200);//表格形式所在矩形
rc_b.DeflateRect(50, 50);//缩小矩形大小
pDC->Rectangle(rc_b);//绘制矩形
int rc_bWidth = rc_b.Width() / 2;//每段宽度
int rc_bHeight = rc_b.Height() / (m_ArrayNum + 1);//每段高度
//画分割线
pDC->MoveTo(50 + rc_b.Width() / 2, 200 + 50);
pDC->LineTo(50 + rc_b.Width() / 2, 200 + 50 + rc_b.Height());
pDC->DrawText("关键字", CRect(50, 200 + 50, 50 + rc_bWidth, 200 + 50 + rc_bHeight), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
pDC->DrawText("频率", CRect(50 + rc_bWidth, 200 + 50, 50 + rc_bWidth * 2, 200 + 50 + rc_bHeight), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_bStr(50, 200 + 50 + rc_bHeight, 50 + rc_bWidth, 200 + 50 + 2 * rc_bHeight);
for (int i = 0;i < m_ArrayNum;i++)
{
//绘制横线
pDC->MoveTo(50, 200 + 50 + (i + 1)*rc_bHeight);
pDC->LineTo(50 + rc_b.Width(), 200 + 50 + (i + 1)* rc_bHeight);
//展示关键字与频率
CString key;
key.Format("%d", m_ArrayKey[i]);
pDC->DrawText(key, rc_bStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_bStr.OffsetRect(rc_bWidth, 0);
CString value;
value.Format("%d", m_ArrayValue[i]);
pDC->DrawText(value, rc_bStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_bStr.OffsetRect(-rc_bWidth, rc_bHeight);
}
//直方图的图形形式
//求最大频率
int maxnum = m_ArrayValue[0];
for (int i = 0;i < m_ArrayNum;i++)
{
if (m_ArrayValue[i] > maxnum)
maxnum = m_ArrayValue[i];
}
CRect rc_c(0, (rc.Height() - 200) / 2 + 200, rc.Width() / 3, rc.Height());//图形形式所在矩形
rc_c.DeflateRect(50, 50);//缩小矩形
pDC->Rectangle(rc_c);//绘制矩形
int rc_cWidth = rc_c.Width() / m_ArrayNum;//每段宽度
int rc_cHeight = rc_c.Height() / maxnum;//每段高度
//绘制频率刻度
pDC->DrawText("频率", CRect(1, 200 + ((rc.Height() - 200) / 2), 1 + 50, 200 + ((rc.Height() - 200) / 2) + 50), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_cValueStr(0, rc.Height() - 50 - rc_cHeight, 50, rc.Height() - 50);//频率刻度所在矩形
for (int i = 0;i < maxnum;i++)
{
CString strvalue;
strvalue.Format("%d", i + 1);
pDC->DrawText(strvalue, rc_cValueStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_cValueStr.OffsetRect(0, -rc_cHeight);
}
//绘制关键字刻度
pDC->DrawText("关键值", CRect(rc.Width() / 3 - 1 - 70, rc.Height() - 50, rc.Width() / 3 - 1, rc.Height()), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_cKeyStr(50, rc.Height() - 50, 50 + rc_cWidth, rc.Height());//关键字刻度所在矩形
for (int i = 0;i < m_ArrayNum;i++)
{
CString strkey;
strkey.Format("%d", m_ArrayKey[i]);
pDC->DrawText(strkey, rc_cKeyStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_cKeyStr.OffsetRect(rc_cWidth, 0);//右移矩形
}
//定义画刷,笔
COLORREF crSeg = RGB(0, 0, 192);
CBrush brush1(HS_FDIAGONAL, crSeg);
CBrush brush2(HS_BDIAGONAL, crSeg);
CPen pen(PS_INSIDEFRAME, 2, crSeg);
CBrush* oldbrush = pDC->SelectObject(&brush1);
CPen* oldpen = pDC->SelectObject(&pen);
CRect rcSeg(rc_c);
rcSeg.right = rcSeg.left + rc_cWidth;//是每段矩形宽度等于rc_cWidth
for (int i = 0;i < m_ArrayNum;i++)
{
//确保相邻的矩形使用不同画刷,美观
if (i % 2 == 0)
pDC->SelectObject(brush1);
else
pDC->SelectObject(brush2);
rcSeg.top = rcSeg.bottom - m_ArrayValue[i] * rc_cHeight - 2;//计算每段矩形的高度
pDC->Rectangle(rcSeg);//绘制矩形
rcSeg.OffsetRect(rc_cWidth, 0);//右移矩形
}
pDC->SelectObject(oldfont);//还原字体
pDC->SelectObject(oldbrush);//还原画刷
pDC->SelectObject(oldpen);//还原笔
}
void CEx_HistogramView::DrawListHistogram(CDC* pDC)
{
COLORREF cr = RGB(255, 255, 255);
CPen Pen(PS_INSIDEFRAME, 2, cr);//颜色为白色的笔
CPen* oldPen = pDC->SelectObject(&Pen);//保存原来的笔
CRect rc;
GetClientRect(&rc);//获取当前窗口大小
CRect rc_list(rc.Width() / 3, 0, rc.Width() / 3 * 2, rc.Height());
pDC->Rectangle(rc_list);//覆盖当前窗口
pDC->SelectObject(oldPen);//还原原来的笔
//展示输入的个数和关键值
CString str;
str.Format("n=%d; 关键值=[%s]", m_count, m_keystr);
//编写字体,更加美观
LOGFONT lf;//定义逻辑字体的结构变量
memset(&lf, 0, sizeof(LOGFONT));//将lf所有成员置0
lf.lfHeight = 25;//设置字体高度
lf.lfWeight = 10;//设置字体宽度
lf.lfCharSet = GB2312_CHARSET;//用逻辑字体结构创建字体
CFont cf;//在设备环境中使用字体
cf.CreateFontIndirect(&lf);
CFont* oldfont = pDC->SelectObject(&cf);
pDC->DrawText(str, CRect(rc.Width() / 3, 0, rc.Width() / 3 * 2, 100), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
pDC->DrawText("HashList:", CRect(rc.Width() / 3, 100, rc.Width() / 3 * 2, 200), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
//画分割线
pDC->MoveTo(rc.Width() / 3, 0);
pDC->LineTo(rc.Width() / 3, rc.Height());
pDC->MoveTo(rc.Width() / 3 * 2, 0);
pDC->LineTo(rc.Width() / 3 * 2, rc.Height());
pDC->MoveTo(rc.Width() / 3, 100);
pDC->LineTo(rc.Width() / 3 * 2, 100);
pDC->MoveTo(rc.Width() / 3, 200);
pDC->LineTo(rc.Width() / 3 * 2, 200);
pDC->MoveTo(rc.Width() / 3, (rc.Height() - 200) / 2 + 200);
pDC->LineTo(rc.Width() / 3 * 2, (rc.Height() - 200) / 2 + 200);
//直方图的表格形式
CRect rc_b(rc.Width() / 3, 200, rc.Width() / 3 * 2, (rc.Height() - 200) / 2 + 200);//表格形式所在矩形
rc_b.DeflateRect(50, 50);//缩小矩形大小
pDC->Rectangle(rc_b);//绘制矩形
int rc_bWidth = rc_b.Width() / 2;//每段宽度
int rc_bHeight = rc_b.Height() / (m_ListNum + 1);//每段高度
//画分割线
pDC->MoveTo(rc.Width() / 3 + 50 + rc_b.Width() / 2, 200 + 50);
pDC->LineTo(rc.Width() / 3 + 50 + rc_b.Width() / 2, 200 + 50 + rc_b.Height());
pDC->DrawText("关键字", CRect(rc.Width() / 3 + 50, 200 + 50, rc.Width() / 3 + 50 + rc_bWidth, 200 + 50 + rc_bHeight), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
pDC->DrawText("频率", CRect(rc.Width() / 3 + 50 + rc_bWidth, 200 + 50, rc.Width() / 3 + 50 + rc_bWidth * 2, 200 + 50 + rc_bHeight), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_bStr(rc.Width() / 3 + 50, 200 + 50 + rc_bHeight, rc.Width() / 3 + 50 + rc_bWidth, 200 + 50 + 2 * rc_bHeight);
for (int i = 0;i < m_ListNum;i++)
{
//绘制横线
pDC->MoveTo(rc.Width() / 3 + 50, 200 + 50 + (i + 1)*rc_bHeight);
pDC->LineTo(rc.Width() / 3 + 50 + rc_b.Width(), 200 + 50 + (i + 1)* rc_bHeight);
//展示关键字与频率
CString key;
key.Format("%d", m_ListKey[i]);
pDC->DrawText(key, rc_bStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_bStr.OffsetRect(rc_bWidth, 0);
CString value;
value.Format("%d", m_ListValue[i]);
pDC->DrawText(value, rc_bStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_bStr.OffsetRect(-rc_bWidth, rc_bHeight);
}
//直方图的图形形式
//求最大频率
int maxnum = m_ListValue[0];
for (int i = 0;i < m_ListNum;i++)
{
if (m_ListValue[i] > maxnum)
maxnum = m_ListValue[i];
}
CRect rc_c(rc.Width() / 3, (rc.Height() - 200) / 2 + 200, rc.Width() / 3 * 2, rc.Height());//图形形式所在矩形
rc_c.DeflateRect(50, 50);//缩小矩形
pDC->Rectangle(rc_c);//绘制矩形
int rc_cWidth = rc_c.Width() / m_ListNum;//每段宽度
int rc_cHeight = rc_c.Height() / maxnum;//每段高度
//绘制频率刻度
pDC->DrawText("频率", CRect(rc.Width() / 3 + 1, 200 + ((rc.Height() - 200) / 2), rc.Width() / 3 + 1 + 50, 200 + ((rc.Height() - 200) / 2) + 50), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_cValueStr(rc.Width() / 3, rc.Height() - 50 - rc_cHeight, rc.Width() / 3 + 50, rc.Height() - 50);//频率刻度所在矩形
for (int i = 0;i < maxnum;i++)
{
CString strvalue;
strvalue.Format("%d", i + 1);
pDC->DrawText(strvalue, rc_cValueStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_cValueStr.OffsetRect(0, -rc_cHeight);
}
//绘制关键字刻度
pDC->DrawText("关键值", CRect(rc.Width() / 3 * 2 - 1 - 70, rc.Height() - 50, rc.Width() / 3 * 2 - 1, rc.Height()), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_cKeyStr(rc.Width() / 3 + 50, rc.Height() - 50, rc.Width() / 3 + 50 + rc_cWidth, rc.Height());//关键字刻度所在矩形
for (int i = 0;i < m_ListNum;i++)
{
CString strkey;
strkey.Format("%d", m_ListKey[i]);
pDC->DrawText(strkey, rc_cKeyStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);//居中|垂直|单行显示
rc_cKeyStr.OffsetRect(rc_cWidth, 0);//右移矩形
}
//定义画刷,笔
COLORREF crSeg = RGB(0, 0, 192);
CBrush brush1(HS_FDIAGONAL, crSeg);
CBrush brush2(HS_BDIAGONAL, crSeg);
CPen pen(PS_INSIDEFRAME, 2, crSeg);
CBrush* oldbrush = pDC->SelectObject(&brush1);
CPen* oldpen = pDC->SelectObject(&pen);
CRect rcSeg(rc_c);
rcSeg.right = rcSeg.left + rc_cWidth;//是每段矩形宽度等于rc_cWidth
for (int i = 0;i < m_ListNum;i++)
{
//确保相邻的矩形使用不同画刷,美观
if (i % 2 == 0)
pDC->SelectObject(brush1);
else
pDC->SelectObject(brush2);
rcSeg.top = rcSeg.bottom - m_ListValue[i] * rc_cHeight - 2;//计算每段矩形的高度
pDC->Rectangle(rcSeg);//绘制矩形
rcSeg.OffsetRect(rc_cWidth, 0);//右移矩形
}
pDC->SelectObject(oldfont);//还原字体
pDC->SelectObject(oldbrush);//还原画刷
pDC->SelectObject(oldpen);//还原笔
}
void CEx_HistogramView::DrawTreeHistogram(CDC* pDC)
{
COLORREF cr = RGB(255, 255, 255);
CPen Pen(PS_INSIDEFRAME, 2, cr);//颜色为白色的笔
CPen* oldPen = pDC->SelectObject(&Pen);//保存原来的笔
CRect rc;
GetClientRect(&rc);//获取当前窗口大小
CRect rc_Tree(rc.Width() / 3 * 2, 0, rc.Width(), rc.Height());
pDC->Rectangle(rc_Tree);//覆盖当前窗口
pDC->SelectObject(oldPen);//还原原来的笔
//展示输入的个数和关键值
CString str;
str.Format("n=%d; 关键值=[%s]", m_count, m_keystr);
//编写字体,更加美观
LOGFONT lf;//定义逻辑字体的结构变量
memset(&lf, 0, sizeof(LOGFONT));//将lf所有成员置0
lf.lfHeight = 25;//设置字体高度
lf.lfWeight = 10;//设置字体宽度
lf.lfCharSet = GB2312_CHARSET;//用逻辑字体结构创建字体
CFont cf;//在设备环境中使用字体
cf.CreateFontIndirect(&lf);
CFont* oldfont = pDC->SelectObject(&cf);
pDC->DrawText(str, CRect(rc.Width() / 3 * 2, 0, rc.Width(), 100), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
pDC->DrawText("Tree:", CRect(rc.Width() / 3 * 2, 100, rc.Width(), 200), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
//画分割线
pDC->MoveTo(rc.Width() / 3 * 2, 0);
pDC->LineTo(rc.Width() / 3 * 2, rc.Height());
pDC->MoveTo(rc.Width(), 0);
pDC->LineTo(rc.Width(), rc.Height());
pDC->MoveTo(rc.Width() / 3 * 2, 100);
pDC->LineTo(rc.Width(), 100);
pDC->MoveTo(rc.Width() / 3 * 2, 200);
pDC->LineTo(rc.Width(), 200);
pDC->MoveTo(rc.Width() / 3 * 2, (rc.Height() - 200) / 2 + 200);
pDC->LineTo(rc.Width(), (rc.Height() - 200) / 2 + 200);
//直方图的表格形式
CRect rc_b(rc.Width() / 3 * 2, 200, rc.Width(), (rc.Height() - 200) / 2 + 200);//表格形式所在矩形
rc_b.DeflateRect(50, 50);//缩小矩形大小
pDC->Rectangle(rc_b);//绘制矩形
int rc_bWidth = rc_b.Width() / 2;//每段宽度
int rc_bHeight = rc_b.Height() / (m_TreeNum + 1);//每段高度
//画分割线
pDC->MoveTo(rc.Width() / 3 * 2 + 50 + rc_b.Width() / 2, 200 + 50);
pDC->LineTo(rc.Width() / 3 * 2 + 50 + rc_b.Width() / 2, 200 + 50 + rc_b.Height());
pDC->DrawText("关键字", CRect(rc.Width() / 3 * 2 + 50, 200 + 50, rc.Width() / 3 * 2 + 50 + rc_bWidth, 200 + 50 + rc_bHeight), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
pDC->DrawText("频率", CRect(rc.Width() / 3 * 2 + 50 + rc_bWidth, 200 + 50, rc.Width() / 3 * 2 + 50 + rc_bWidth * 2, 200 + 50 + rc_bHeight), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_bStr(rc.Width() / 3 * 2 + 50, 200 + 50 + rc_bHeight, rc.Width() / 3 * 2 + 50 + rc_bWidth, 200 + 50 + 2 * rc_bHeight);
for (int i = 0;i < m_TreeNum;i++)
{
//绘制横线
pDC->MoveTo(rc.Width() / 3 * 2 + 50, 200 + 50 + (i + 1)*rc_bHeight);
pDC->LineTo(rc.Width() / 3 * 2 + 50 + rc_b.Width(), 200 + 50 + (i + 1)* rc_bHeight);
//展示关键字与频率
CString key;
key.Format("%d", m_TreeKey[i]);
pDC->DrawText(key, rc_bStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_bStr.OffsetRect(rc_bWidth, 0);
CString value;
value.Format("%d", m_TreeValue[i]);
pDC->DrawText(value, rc_bStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_bStr.OffsetRect(-rc_bWidth, rc_bHeight);
}
//直方图的图形形式
//求最大频率
int maxnum = m_TreeValue[0];
for (int i = 0;i < m_TreeNum;i++)
{
if (m_TreeValue[i] > maxnum)
maxnum = m_TreeValue[i];
}
CRect rc_c(rc.Width() / 3 * 2, (rc.Height() - 200) / 2 + 200, rc.Width(), rc.Height());//图形形式所在矩形
rc_c.DeflateRect(50, 50);//缩小矩形
pDC->Rectangle(rc_c);//绘制矩形
int rc_cWidth = rc_c.Width() / m_TreeNum;//每段宽度
int rc_cHeight = rc_c.Height() / maxnum;//每段高度
//绘制频率刻度
pDC->DrawText("频率", CRect(rc.Width() / 3 * 2 + 1, 200 + ((rc.Height() - 200) / 2), rc.Width() / 3 * 2 + 1 + 50, 200 + ((rc.Height() - 200) / 2) + 50), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_cValueStr(rc.Width() / 3 * 2, rc.Height() - 50 - rc_cHeight, rc.Width() / 3 * 2 + 50, rc.Height() - 50);//频率刻度所在矩形
for (int i = 0;i < maxnum;i++)
{
CString strvalue;
strvalue.Format("%d", i + 1);
pDC->DrawText(strvalue, rc_cValueStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_cValueStr.OffsetRect(0, -rc_cHeight);
}
//绘制关键字刻度
pDC->DrawText("关键值", CRect(rc.Width() - 1 - 70, rc.Height() - 50, rc.Width() - 1, rc.Height()), DT_CENTER | DT_VCENTER | DT_SINGLELINE);
CRect rc_cKeyStr(rc.Width() / 3 * 2 + 50, rc.Height() - 50, rc.Width() / 3 * 2 + 50 + rc_cWidth, rc.Height());//关键字刻度所在矩形
for (int i = 0;i < m_TreeNum;i++)
{
CString strkey;
strkey.Format("%d", m_TreeKey[i]);
pDC->DrawText(strkey, rc_cKeyStr, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
rc_cKeyStr.OffsetRect(rc_cWidth, 0);//右移矩形
}
//定义画刷,笔
COLORREF crSeg = RGB(0, 0, 192);
CBrush brush1(HS_FDIAGONAL, crSeg);
CBrush brush2(HS_BDIAGONAL, crSeg);
CPen pen(PS_INSIDEFRAME, 2, crSeg);
CBrush* oldbrush = pDC->SelectObject(&brush1);
CPen* oldpen = pDC->SelectObject(&pen);
CRect rcSeg(rc_c);
rcSeg.right = rcSeg.left + rc_cWidth;//是每段矩形宽度等于rc_cWidth
for (int i = 0;i < m_TreeNum;i++)
{
//确保相邻的矩形使用不同画刷,美观
if (i % 2 == 0)
pDC->SelectObject(brush1);
else
pDC->SelectObject(brush2);
rcSeg.top = rcSeg.bottom - m_TreeValue[i] * rc_cHeight - 2;//计算每段矩形的高度
pDC->Rectangle(rcSeg);//绘制矩形
rcSeg.OffsetRect(rc_cWidth, 0);//右移矩形
}
pDC->SelectObject(oldfont);//还原字体
pDC->SelectObject(oldbrush);//还原画刷
pDC->SelectObject(oldpen);//还原笔
}
四、最后效果图
总结
以上就是今天要讲的内容,本文仅仅简单介绍了mfc中利用不同的数据结构生成直方图的使用。