朋友今天問我一個面試題,我看了看,給大家分享一下


  本人才疏學淺,望大家多給意見,有更好的做法大加分享分享

下面是題目:
  已知表table_department中有兩個字段,分別為d_id,d_name。d_id記錄的是部門編碼, d_name記錄的是部門名稱,各部門的組織方式如下:
A為頂級部門,A部門的下級部門使用AA、BA、CA……表示
AA的下級部門使用AAA、BAA、CAA……表示
BA的夏季部門使用ABA、BBA、CBA……表示
以此類推。
新建一個應用程序,寫一個頁面或窗體,講table_department表中的數據,按樹狀排列顯示,如下所示:
A總經辦
-AA生產部
--AAA保修部
--BAA非保部
-BA物流部
--ABA物流一部
--BBA物流二部
--CBA物流三部
-CA市場部
--ACA市場拓展
--BCA營銷部
---ABCA電器營銷部
---BBCA電子營銷部
…………
………… 加分項:
表table_department用XML實現

===================================================

這個問題我覺得會有比較多的解法,我這里暫且考慮面試的問題,所以給出的解法不會考慮太多嚴謹的東西
首先建庫建表添加數據

SQL腳本為
=================================================

 1 -- 創建數據庫
 2 if db_id('testdb') is not null
 3     drop database testdb;
 4 go
 5 create database testdb;
 6 
 7 -- 使用數據庫
 8 use testdb;
 9 --創建數據表
10 if object_id('table_department', 'U') is null
11 create table table_department
12 (
13     d_id varchar(10),
14     d_name nvarchar(50)
15 );
16 go
17 -- 添加數據
18 insert into table_department(d_id, d_name) values('A', '總經辦');
19 insert into table_department(d_id, d_name) values('AA', '生產部');
20 insert into table_department(d_id, d_name) values('BA', '物流部');
21 insert into table_department(d_id, d_name) values('CA', '市場部');
22 insert into table_department(d_id, d_name) values('AAA', '保修部');
23 insert into table_department(d_id, d_name) values('BAA', '非保部');
24 insert into table_department(d_id, d_name) values('ABA', '物流一部');
25 insert into table_department(d_id, d_name) values('BBA', '物流二部');
26 insert into table_department(d_id, d_name) values('CBA', '物流三部');
27 insert into table_department(d_id, d_name) values('ACA', '市場拓展');
28 insert into table_department(d_id, d_name) values('BCA', '經營部');
29 insert into table_department(d_id, d_name) values('ABCA', '電器經營部');
30 insert into table_department(d_id, d_name) values('BBCA', '電子經營部');


========================================

第一種解法

  也是最簡單傻瓜式的解法,使用ADO.Net讀取數據. 並將讀到的數據,根據d_id字段的數據創建TreeView節點,並加載數據

  觀察樹節點的規律,每個節點只有d_id的現實,只有最后一個節點現實完整的d_id和d_name,並且每個d_id的字符表示一個層次結構

  因此可以寫一個方法,根據包含d_id和d_name的字符串創建節點和添加數據

簡單步驟:

1、 首先該方法要往TreeView添加數據,因此該方法一定要有一個TreeNode參數(鑒於根節點只有一個,可以將A添加為根節點,或直接就將"公司"作為根節點)
2、 觀察d_id的字符串,根節點在最右邊,子節點在左邊(估計是為了故意設計的面試題,這樣不好排序)
實際這個很簡單,將d_id字符串轉換成字符數組,從后往左遍歷,並在TreeNode中創建節點,如果節點存在就不用創建
3、 因此方法原型可以定義為:

1 private void ShowFromString(string d_id, string id, string d_name, TreeNode tn)
2 {
3     // 實現代碼
4     // d_id創建結構使用
5     // id記錄部門id號
6     // d_name記錄部門名稱
7     // tn表示當前節點
8 }

4、 接下來看方法如何實現
  由於TreeNode是有層次顯示的,所以這里使用遞歸最為容易(循環感覺也可以實現)
  4.1 首先將d_id編程字符數組,並得到最后一個字符,這個頂級節點

1 char[] chs = d_id.ToCharArray();
2 string nodeStr = chs[chs.Length - 1].ToString();

 


  4.2 在TreeNode中檢索是否存在這個節點. 檢索存在就是看TreeNode的子節點中是否有Tag與nodeStr匹配的(這里可使用Linq,不過既然簡單用最原始的)
    寫一個方法,由於部門的名字是不會重復的,所以這么寫

 1 private bool IsExist(string nodeText, TreeNode tn)
 2 {
 3     bool isTrue = false;
 4     for(int i = 0; i < tn.Nodes.Count; i++)
 5     {
 6         if(tn.Nodes[i].Tag as string == nodeText)
 7         {
 8             isTrue = true;
 9             break;
10         }
11     }
12     return isTrue;
13 }

    該方法只要找到TreeNode直接子節點中存在與給定字符串相同的節點就返回true,否則返回false
  4.3 判斷是否存在節點,如果不存在就創建,如果存在就得到這個節點
    這里需要注意的是,所有節點的邏輯結構均有Tag屬性來確認,而Text屬性最終使用d_id與d_name替換,因此這里是一個臨時的值

TreeNode tnObj = null;
if(!IsExist(nodeStr, tn))
{
    tnObj = tn.Nodes.Add(nodeStr);
    tnObj.Tag = nodeStr;
}
else
{
    // 得到這個節點
}

  4.4 考慮如果存在就得到該節點,但是不要寫循環一次了,太麻煩,因此修改IsExist方法

 1 private bool IsExist(string nodeText, TreeNode tn, out TreeNode tnObj)
 2 {
 3     tnObj = null;
 4     bool isTrue = false;
 5     for(int i = 0; i < tn.Nodes.Count; i++)
 6     {
 7         if(tn.Nodes[i].Tag as string == nodeText)
 8         {
 9             isTrue = true;
10             tnObj = tn.Nodes[i];    // 將找到的節點直接返回
11             break;
12         }
13     }
14     return isTrue;
15 }

    因此4.3步的代碼可以改為

1 if(!IsExist(nodeStr, tn, out tnObj))
2 {
3     tnObj = tn.Nodes.Add(nodeStr);
4     tnObj.Tag = nodeStr;
5 }

    這個方法的思路來自int.TryParse方法,如果找到了,那么返回true,那么tnObj中就有了該節點
    如果沒有找到那么創建一個,反正tnObj中就有當前子節點

  4.5 這里應該判斷是不是最后一個節點,如果是最后一個節點那么就應該將id和d_name加到Text屬性上
    由於使用遞歸完成,因此再次調用這個方法的時候,會將存儲d_id的char數組最后一個字符去掉
    因此使用chs.Length == 1即可判斷是否為最后一個節點

 1 if(chs.Length == 1)
 2             {
 3                 // 將當前節點即為結束節點
 4                 tnObj.Text = string.Format("{0} {1}", id, d_name);
 5             }
 6             else
 7             {
 8                 // 如果不是最終節點,則遞歸
 9                 ShowFromString(new string(chs, 0, chs.Length - 1), id, d_name, tnObj);
10             }

 5、 整合一下方法

 1 private void ShowFromString(string d_id, string id, string d_name, TreeNode tn)
 2 {
 3     char[] chs = d_id.ToCharArray();
 4     string nodeStr = chs[chs.Length - 1].ToString();
 5     TreeNode tnObj = null;
 6     if(!IsExist(nodeStr, tn, out tnObj))
 7     {
 8         tnObj = tn.Nodes.Add(nodeStr);
 9         tnObj.Tag = nodeStr;
10     }
11     if(chs.Length == 1)
12     {
13         tnObj.Text = string.Format("{0} {1}", id, d_name);
14     }
15     else
16     {
17         ShowFromString(new string(chs, 0, chs.Length - 1), id, d_name, tnObj);
18     }
19 }
20 private bool IsExist(string nodeText, TreeNode tn, out TreeNode tnObj)
21 {
22     tnObj = null;
23     bool isTrue = false;
24     for(int i = 0; i < tn.Nodes.Count; i++)
25     {
26         if(tn.Nodes[i].Tag as string == nodeText)
27         {
28             isTrue = true;
29             tnObj = tn.Nodes[i];    // 將找到的節點直接返回
30             break;
31         }
32     }
33     return isTrue;
34 }

6、 添加窗體的Load事件,並添加代碼

 1 private void Form1_Load(object sender, EventAges e)
 2 {
 3     // 添加根節點公司, 就是在公司下面添加節點
 4     TreeNode tn = tvCompany.Nodes.Add("公司");
 5     // 處理數據庫,讀數據
 6     using(SqlConnection conn = new SqlConnection(@"server=.\sqlexpress;database=testdb;integrated security=true"))
 7     {
 8         using(SqlCommand cmd = new SqlCommand("select d_id, d_name from table_department", conn))
 9         {
10             conn.Open();
11             using(SqlDataReader reader = cmd.ExecuteReader())
12             {
13                 if(reader.HasRow)
14                 {
15                     while(reader.Read())
16                     {
17                         string d_id = reader.GetString(0);
18                         string d_name = reader.GetString(1);
19                         ShowFromString(d_id, d_id, d_name, tn);
20                     }
21                 }
22             }
23         }
24     }
25 }

7、 最后要用XML存儲,遞歸遍歷節點,創建XML數據,就像遍歷文件夾一樣
  7.1 添加遞歸方法

 1 private void GetXML(TreeNode tn, XElement ele)
 2 { 
 3     // 得到tn下的數據,並加到ele中
 4     for (int i = 0; i < tn.Nodes.Count; i++)
 5     {
 6         // 創建對應節點
 7         XElement ele1 = new XElement(tn.Nodes[i].Text.Replace(" ", "_")); // 由於XML中節點名中不允許有空格,所以去掉
 8         ele.Add(ele1);
 9 
10         // 遞歸
11         GetXML(tn.Nodes[i], ele1);
12     }
13 }

  7.2 添加按鈕事件

1 private void createXML_Click(object sender, EventArgs e)
2 {
3     XDocument xDoc = new XDocument();
4     xDoc.Add(new XElement("Company"));
5 
6     GetXML(tvCompany.Nodes[0], xDoc.Root);
7 
8     xDoc.Save("company.xml");
9 }

 

========================================

第二種方法

  第一種方法比較簡單,關鍵在於如何處理d_id結構而已,而且順序讀取和創建
  實際上TreeNode與XML結構一致,是可以同樣處理的,也就是說先從數據庫中取出數據,生成XML數據,在遞歸遍歷XML數據創建TreeNode

1、 從數據庫中讀取數據,並創建XML文件
  樹形結構有一個特點,就是每一個節點只允許有一個父節點和一個子節點,所以可以從數據庫中取出所有數據,得到所有數據的節點片段數據
  在根據一定算法將節點連起來
  1.1 添加一個方法,該方法完成從數據庫中讀取數據,並得到XML集合(數組也行,個人比較喜歡集合)

1 private List<XElement> GetElementByDatabase()
2 {
3     // 代碼
4 }

  1.2 讀取數據庫,創建XML集合

 1 private List<XElement> GetElementByDatabase()
 2 {
 3     List<XElement> list = new List<XElement>();
 4     using (SqlConnection conn = new SqlConnection(@"server=.\sqlexpress;database=testdb;integrated security=true"))
 5     {
 6         using (SqlCommand cmd = new SqlCommand("select d_id, d_name from table_department", conn))
 7         {
 8             conn.Open();
 9             using (SqlDataReader reader = cmd.ExecuteReader())
10             {
11                 if (reader.HasRows)
12                 {
13                     while (reader.Read())
14                     {
15                         string d_id = reader[0].ToString();
16                         string d_name = reader[1].ToString();
17                         // 開始生成XML數據
18                         list.Add(new XElement("department",
19                                                 new XAttribute("d_id", d_id),
20                                                 new XAttribute("d_name", d_name)
21                                 ));
22                     }
23                 }
24             }
25         }
26     }
27     return list;
28 }

2、 處理XML片段集合的結構,這個結構沒有構成樹狀結構,因此寫一個方法將這個XML片段集合變成一個XML樹片段
  這里算法有很多,也可以使用Linq查詢,但是我不打算詳細描述算法,因為有些比較抽象
  這里用一個不一定最快,但是很直觀的算法
  2.1 添加一個方法

1 public XElement GetXMLTree(List<XElement> listXML)
2 {
3     // 代碼
4 }

  2.2 了解到XML每一個節點至多只有一個父節點和子節點,因此只要將處理好節點的去掉即可
    同時每一個節點都是通過d_id分層次,而這個層次很有規律,父節點剛好比子節點多一個字符,也就是說
    父節點的d_id與子節點的d_id.Substring(1)相同
    所以就可以從最長的節點開始找,依次為每一個節點找父節點即可

 1 public XElement GetXMLTree(List<XElement> listXML)
 2 {
 3     // 先為listXML降序排序,因為d_id越長,節點越深
 4     listXML.Sort((XElement x1, XElement x2) => { return x2.Attribute("d_id").Value.Length - x1.Attribute("d_id").Value.Length; });
 5     // 從左開始為每一個節點找父節點,很顯然,最長的節點最深
 6     // 一旦找到父節點,添加進去,就可以將該節點從集合中移除
 7     for (int i = 0; i < listXML.Count; i++)
 8     {
 9         XElement curr = listXML[i];
10         for (int j = i + 1; j < listXML.Count; j++)
11         {
12             // 判斷是否為父子關系
13             if (curr.Attribute("d_id").Value.Substring(1) == listXML[j].Attribute("d_id").Value)
14             {
15                 listXML[j].Add(curr);
16             }
17         }
18     }
19     return listXML[listXML.Count - 1];
20 }

3、 XML結構有了,那么就可以保存該數據了

  另外遍歷XML結構,加載到TreeView控件中

 1 private void ShowTreeNode(TreeNode tn, XElement ele)
 2 {
 3     foreach (XElement item in ele.Elements())
 4     {
 5         TreeNode tn1 = tn.Nodes.Add(string.Format("{0} {1}", item.Attribute("d_id").Value, item.Attribute("d_name").Value));
 6         if (item.HasElements)
 7         {
 8             ShowTreeNode(tn1, item);
 9         }
10     }
11 }

4、 添加Load方法

1 XElement element = null; // 記錄要保存的XML數據
2 private void Form1_Load(object sender, EventArgs e)
3 {
4     List<XElement> xelements = GetElementByDatabase();
5 
6     element = GetXMLTree(xelements);
7 
8     ShowTreeNode(tvCompany.Nodes.Add("公司"), element);
9 }

5、 添加保存XML的代碼(添加XElement字段)

1 private void btnSave_Click(object sender, EventArgs e)
2 {
3     XDocument xDoc = new XDocument(element);
4     xDoc.Save("xml.xml");
5     MessageBox.Show("OK");
6 }

第三種方法
  寫了第二種方法就不太想寫第三種方法了,介紹一下基本思想吧
  為數據表創建一個對象模型,但是多出一個字段,就是記錄反序的d_id
  那么就可以利用排序等手段創建對象集合
  同時解析每一個字符創建TreeView節點了
=================================================


好了,就給出成型的兩個算法吧!如果有時間在慢慢看. 本人見識有限,還請大家多提意見,如果有更好的思路,借鑒一下啊!!!


2012年10月10日晚

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM