111 經濟部聯招(台電、中油、台水、台糖)新進職員 統計資訊 國營事業聯招 (資料庫及資料探勘、程式設計)B 考古題詳解
第 一 題16 分
某學生之成績資料如下表,請回答下列問題。(2 題,每題 8 分,共 16 分)
| 學期 | 110_01 | 學號 | 410552223 | 姓名 | 陳 OO |
|---|---|---|---|---|---|
| 性別 | F | 生日 | 2001/10/5 | 連絡住址 | OO 市 OO 區 OO 路 ** 號 |
| 編號 | P110233 | 緊急聯絡人 | 陳 OO | 關係 | 父親 |
| 電話 | 29123334<br>0911222333 | 緊急聯絡人住址 | OO 市 OO 區 OO 路 ** 號 |
| 科目代號 | 科目名稱 | 學分 | 選別 | 成績 | 教師編號 | 教師姓名 |
|---|---|---|---|---|---|---|
| C10032 | 國文 | 3 | 必 | 85 | T01245 | 王 OO |
| C20015 | 英文 | 3 | 必 | 86 | T04578 | 李 OO |
| C60001 | 網頁設計 | 2 | 選 | 78 | T06543 | 趙 OO |
| C60012 | 程式語言 | 4 | 必 | 65 | T06543 | 趙 OO |
| C61001 | 計算機概論 | 3 | 必 | 88 | T06578 | 陳 OO |
(一) 請將該學生成績資料進行正規化後,列出分析的資料表,並以下列方式書寫(主鍵需加註底線標示)。 註:主鍵不一定是單一值,也可以是複合值。
(二) 承小題(一),請繪出對應之 E-R 圖。
登入後即可作答並保存紀錄。
核心觀念
本題考查資料庫正規化、功能相依性、主鍵與外鍵,以及由關聯綱目繪製 E-R 圖。
原始資料混合了:
- 學生基本資料
- 多值電話
- 緊急聯絡人資料
- 學期資料
- 科目資料
- 教師資料
- 學生成績資料
若全部放在同一張表,會產生資料重複、插入異常、刪除異常及更新異常,因此需拆解成多張具明確功能的資料表。
正規化重點如下:
- 第一正規化(1NF):每個欄位只能存放不可再分割的單一值,因此學生的兩支電話不可放在同一欄。
- 第二正規化(2NF):非主鍵欄位必須完全相依於整個主鍵,不可只相依於複合主鍵的一部分。
- 第三正規化(3NF):非主鍵欄位不可透過其他非主鍵欄位間接相依。
本題以「同一學期中,每一科目由一位教師授課」為資料規則,並將「編號 P110233」視為該學生該學期的成績單編號。
解題方法
由原始資料可判斷下列功能相依性:
- 學號 姓名、性別、生日、聯絡住址
- 教師編號 教師姓名
- 科目代號 科目名稱、學分、選別、教師編號
- 編號 學號、學期
- (編號、科目代號) 成績
電話欄位有兩個值:
因此電話必須另拆成多筆資料,形成獨立的學生電話表。
成績則是「某一份成績單中的某一科成績」,因此必須以:
作為複合主鍵。
(一)正規化後的資料表
1. 學生資料表
資料如下:
| 學號 | 姓名 | 性別 | 生日 | 聯絡住址 |
|---|---|---|---|---|
| 410552223 | 陳 OO | F | 2001/10/5 | OO 市 OO 區 OO 路 ** 號 |
主鍵為學號,因為每位學生具有唯一學號。
2. 學生電話表
資料如下:
| 學號 | 電話 |
|---|---|
| 410552223 | 29123334 |
| 410552223 | 0911222333 |
此表採用複合主鍵 ,避免同一位學生的不同電話互相衝突。
其中學號為外鍵,參照「學生」表的學號。
3. 緊急聯絡人表
資料如下:
| 學號 | 緊急聯絡人姓名 | 關係 | 緊急聯絡人住址 |
|---|---|---|---|
| 410552223 | 陳 OO | 父親 | OO 市 OO 區 OO 路 ** 號 |
本題資料中每位學生只有一位緊急聯絡人,因此以學號作為主鍵,形成學生與緊急聯絡人的一對一關係。
若實際系統允許同一學生登錄多位緊急聯絡人,則此表可改用「緊急聯絡人編號」作為主鍵;本題依題目資料採用學號主鍵。
4. 學期成績單表
資料如下:
| 編號 | 學號 | 學期 |
|---|---|---|
| P110233 | 410552223 | 110_01 |
其中:
- 編號為主鍵。
- 學號為外鍵,參照「學生」表。
- 學號與學期也可設定為候選鍵,表示同一學生在同一學期只有一份成績單。
5. 教師表
資料如下:
| 教師編號 | 教師姓名 |
|---|---|
| T01245 | 王 OO |
| T04578 | 李 OO |
| T06543 | 趙 OO |
| T06578 | 陳 OO |
教師姓名只依賴教師編號,因此獨立建立教師表,可避免教師姓名在多筆科目資料中重複。
6. 科目表
資料如下:
| 科目代號 | 科目名稱 | 學分 | 選別 | 教師編號 |
|---|---|---|---|---|
| C10032 | 國文 | 3 | 必 | T01245 |
| C20015 | 英文 | 3 | 必 | T04578 |
| C60001 | 網頁設計 | 2 | 選 | T06543 |
| C60012 | 程式語言 | 4 | 必 | T06543 |
| C61001 | 計算機概論 | 3 | 必 | T06578 |
其中教師編號為外鍵,參照「教師」表。
在本題資料規則下,一個科目對應一位授課教師,因此:
7. 修課成績表
資料如下:
| 編號 | 科目代號 | 成績 |
|---|---|---|
| P110233 | C10032 | 85 |
| P110233 | C20015 | 86 |
| P110233 | C60001 | 78 |
| P110233 | C60012 | 65 |
| P110233 | C61001 | 88 |
主鍵為複合主鍵:
理由是:
第 二 題18 分
某關聯資料庫(Relational Database)包含下列兩個資料表(Tables),有底線者為主鍵:
教師(Teacher)資料表
| 教師代碼<br>() | 教師姓名<br>(TName) | 系所代號<br>(DeptNo) | 性別<br>(Sex) | 到校年<br>(ArrivalYear) | 身分別<br>(Separate) | 薪資<br>(Salary) |
|---|
註:身分別(Separate)欄位的值為字元 Y 時表示專任,N 時表示兼任。
系所(Department)資料表
| 系所代號<br>() | 系所中文名稱<br>(DeptName) | 所在建築<br>(Building) |
|---|
針對下列問題,請分別寫出 SQL 指令以進行查詢:(3 題,每題 6 分,共 18 分)
(一) 對所有專任教師,依性別分別列出平均薪資,平均薪資欄位另取名為 AvgSalary。 (二) 列出「企管系」教師最高薪資與最低薪資的差距,差距欄位另取名為 DiffSalary。 (三) 列出到校 10 年(含)以上的教師。
登入後即可作答並保存紀錄。
核心觀念
本題考查 SQL 的下列觀念:
WHERE:先篩選符合條件的資料列。GROUP BY:依指定欄位分組。AVG:計算平均值。MAX、MIN:分別取得最大值與最小值。- 欄位別名:使用
AS重新命名輸出欄位。 JOIN:連結教師表與系所表,以系所中文名稱查詢教師資料。
題目指定以民國 111 年為基準,因此「到校 10 年(含)以上」表示:
整理得:
以下解答均以命題當年(民國 111 年)法規為準;本題不涉及法條。
解題方法
(一)對所有專任教師,依性別列出平均薪資
專任教師的條件是 Separate = 'Y'。篩選後,再依 Sex 分組,並使用 AVG(Salary) 計算各性別的平均薪資。
SELECT Sex, AVG(Salary) AS AvgSalary
FROM Teacher
WHERE Separate = 'Y'
GROUP BY Sex;
說明:
WHERE Separate = 'Y'篩選專任教師。GROUP BY Sex將資料依性別分組。AVG(Salary)計算每一組的平均薪資。AS AvgSalary將平均薪資欄位命名為AvgSalary。
(二)列出「企管系」教師最高薪資與最低薪資的差距
「企管系」是系所中文名稱,存放於 Department.DeptName,因此必須以 DeptNo 作為連接條件連結兩個資料表。
薪資差距的計算公式為:
SQL 指令如下:
SELECT MAX(T.Salary) - MIN(T.Salary) AS DiffSalary
FROM Teacher AS T
JOIN Department AS D
ON T.DeptNo = D.DeptNo
WHERE D.DeptName = '企管系';
說明:
JOIN Department AS D將教師表與系所表連結。ON T.DeptNo = D.DeptNo是兩表的連接條件。WHERE D.DeptName = '企管系'篩選企管系教師。
第 三 題16 分
假設某家電腦公司銷售筆記型電腦,針對各種客戶分類,建立了銷售調查統計表,經過幾個月銷售統計,收集到下表的調查資料,請回答下列問題:(3 題,共 16 分)
| 人數 | 年齡 | 收入 | 學生 | 信用 | 購買筆電 |
|---|---|---|---|---|---|
| 60 | 青年 | 高 | 否 | 良 | 沒買 |
| 64 | 青年 | 高 | 否 | 優 | 沒買 |
| 100 | 中年 | 高 | 否 | 良 | 買 |
| 64 | 老年 | 中 | 否 | 良 | 買 |
| 58 | 老年 | 低 | 是 | 良 | 買 |
| 64 | 老年 | 低 | 是 | 優 | 沒買 |
| 50 | 中年 | 低 | 是 | 優 | 買 |
| 130 | 青年 | 中 | 否 | 良 | 沒買 |
| 64 | 青年 | 低 | 是 | 良 | 買 |
| 130 | 老年 | 中 | 是 | 良 | 買 |
| 32 | 青年 | 中 | 是 | 優 | 買 |
| 64 | 中年 | 中 | 否 | 優 | 買 |
| 30 | 中年 | 高 | 是 | 良 | 買 |
| 100 | 老年 | 中 | 否 | 優 | 沒買 |
(一) 若你是銷售人員,請問哪一項客戶分類資料,能直接說明最有效的銷售?(3 分) (二) 請問年齡層為中年的客群,購買筆記型電腦的機率為何(2 分)?並說明原因(5 分)。 (三) 請以年齡特徵作為根節點,針對客戶分類,建立一個最有效的銷售之決策樹。(6 分)
登入後即可作答並保存紀錄。
核心觀念
本題考查分類資料的加權統計、條件機率,以及決策樹分類。
表中的「人數」是各類客戶的權數,因此計算購買率時,必須加總人數,不能只計算資料列數。
購買機率公式為:
決策樹則是依據各特徵對「買/沒買」的區分能力進行分割。若某分支分割後全部屬於同一類別,即達到純分類,便可直接形成葉節點。
(一)最有效的客戶分類
解題方法
比較各項分類中「購買筆電」的比例。
| 分類條件 | 購買人數 | 總人數 | 購買率 |
|---|---|---|---|
| 年齡:青年 | |||
| 年齡:中年 | |||
| 年齡:老年 | |||
| 收入:高 | |||
| 收入:中 | |||
| 收入:低 | |||
| 學生:否 | |||
| 學生:是 | |||
| 信用:良 | |||
| 信用:優 |
其中,若以「單一分類特徵」作為銷售對象判斷,「學生=是」的購買率為 ,高於其他分類條件,因此最能直接說明銷售成效。
解題技巧
判斷銷售對象時,重點是比較條件購買率,而不是比較購買總人數。購買總人數最多的分類,不一定代表購買意願最高。
(二)中年客群購買機率
計算
中年客群共有:
其中購買筆電的人數為:
因此:
原因說明
表中所有年齡為「中年」的客戶資料,其「購買筆電」欄均為「買」,沒有任何中年客戶標記為「沒買」。
第 四 題15 分
有一費氏(Fibonacci)數學函式如下:(3 題,每題 5 分,共 15 分)
(一) 請以遞迴(Recursive)方式寫出上列函式程式碼。 (二) 請以非遞迴(Non-Recursive)方式寫出上列函式程式碼。 (三) 為避免因為遞迴呼叫浪費函式重複計算的時間,試修改(一)中的程式碼,仍須使用遞迴的方式,使其計算時不須重複計算 和 函式。
登入後即可作答並保存紀錄。
核心觀念
本題考查費氏數列的遞迴定義、迴圈迭代,以及遞迴搭配記憶化(Memoization)的最佳化方法。
費氏數列定義如下:
其中:
- 、是遞迴終止條件。
- 是遞迴關係式。
- 未設定終止條件時,函式會無限遞迴。
解題方法
(一)以遞迴方式撰寫
依照題目定義,當 或 時直接回傳結果;當 時,依遞迴關係計算。
int fibonacci_recursive(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return fibonacci_recursive(n - 1)
+ fibonacci_recursive(n - 2);
}
也可以合併兩個基本條件:
int fibonacci_recursive(int n)
{
if (n <= 1)
return n;
return fibonacci_recursive(n - 1)
+ fibonacci_recursive(n - 2);
}
例如:
因此:
此方法直接呈現數學定義,但會重複計算相同的子問題,時間複雜度約為 ,空間複雜度為遞迴深度 。
(二)以非遞迴方式撰寫
非遞迴方式使用兩個變數保存前兩項:
prev2:代表前前項prev1:代表前一項current:代表目前計算出的項目
int fibonacci_iterative(int n)
{
if (n <= 1)
return n;
int prev2 = 0; // F(0)
int prev1 = 1; // F(1)
int current;
for (int i = 2; i <= n; i++)
{
current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return prev1;
}
計算 時,變數變化如下:
| 目前項目 | prev2 | prev1 | |
|---|---|---|---|
| 初始 | — | ||
| 2 | 1 | 1 | |
| 3 | 1 | 2 | |
| 4 | 2 | 3 | |
| 5 | 3 | 5 | |
| 6 | 5 | 8 |
因此:
此方法每一項只計算一次,時間複雜度為 ,額外空間複雜度為 。
第 五 題15 分
A 公司 1 月人事資料檔案如下表,請撰寫下列程式片段。(2 題,共 15 分)
| emp-id(員工代號) | dep-id(部門代號) | age(年齡) | s_salary(特別費) |
|---|---|---|---|
| 123455 | ABC | 25 | 0 |
| 987655 | ABC | 30 | 5000 |
| A12345 | AAB | 30 | 10000 |
| 123455 | AAB | 30 | 500 |
| 12345 | AXX | 25 | 5000 |
| 234561 | AXX | 35 | 15000 |
(一) A 公司員工代號共 6 碼數字,第 6 碼是前 5 碼總和的個位數,請撰寫一函式 check_dit(),以檢查員工代號之正確性。(7 分) (二) 請撰寫一函式 main(),逐筆讀取人事資料檔案,並呼叫(一)函式,篩選正確員工代號資料,最後列出特別費合計超過 100,000 元之部門代號及合計金額。(8 分)
登入後即可作答並保存紀錄。
核心觀念
本題考查兩個重點:
- 字串格式檢查
- 員工代號必須為長度 6 的數字字串。
- 前 5 碼轉換成數字後相加。
- 第 6 碼必須等於前 5 碼總和的個位數。
- 檔案逐筆讀取與部門彙總
- 逐筆讀取人事資料。
- 只有員工代號正確的資料才納入統計。
- 依部門代號累計
s_salary。 - 最後列出合計超過 元的部門。
驗證公式為:
(一)check_dit 函式
解題方法
以字串方式處理員工代號,原因如下:
- 可直接判斷長度是否為 6。
- 可逐一檢查每一個字元是否為數字。
- 可避免員工代號開頭為
0時,轉成整數後遺失前導零。
函式回傳值定義如下:
- 回傳
1:員工代號正確。 - 回傳
0:員工代號錯誤。
C 語言程式碼
#include <stdio.h>
#include <string.h>
int check_dit(const char emp_id[])
{
int i;
int sum = 0;
int last_digit;
/* 員工代號必須剛好為 6 碼 */
if (strlen(emp_id) != 6)
return 0;
/* 前 5 碼必須都是數字 */
for (i = 0; i < 5; i++) {
if (emp_id[i] < '0' || emp_id[i] > '9')
return 0;
sum += emp_id[i] - '0';
}
/* 第 6 碼也必須為數字 */
if (emp_id[5] < '0' || emp_id[5] > '9')
return 0;
last_digit = emp_id[5] - '0';
/* 檢查第 6 碼是否為前 5 碼總和的個位數 */
if (last_digit != sum % 10)
return 0;
return 1;
}
資料驗證
| 員工代號 | 前 5 碼總和 | 正確個位數 | 實際第 6 碼 | 判定 |
|---|---|---|---|---|
123455 | 5 | 5 | 正確 | |
987655 | 5 | 5 | 正確 | |
A12345 | 含有英文字母 | — | — | 錯誤 |
123455 | 5 | 5 | 正確 | |
12345 | 長度為 5 | — | — | 錯誤 |
234561 | 0 | 1 | 錯誤 |
(二)main 函式
解題方法
使用陣列儲存各部門的累計特別費:
dep_sum[i]
代表:
dep_id_list[i]
部門的特別費合計。
每讀取一筆資料時:
- 呼叫
check_dit。 - 員工代號錯誤時跳過該筆資料。
- 員工代號正確時,尋找部門代號。
- 找到既有部門便累加金額。
- 找不到部門便新增一筆部門資料。
- 檔案讀取完畢後,列出合計超過 元的部門。
C 語言完整程式碼
假設檔案每行格式如下:
emp-id dep-id age s_salary
程式碼如下:
第 六 題20 分
快速排序法(Quick Sort)是排序演算法中的一種,處理過程是先選擇一個資料為基準點,所有比基準點小的元素放在左邊,比基準點大的元素放在右邊,之後再反覆對基準點左右兩邊的數列執行相同的處理,直到數列只剩下一個數值或沒有數值時即完成排序。請撰寫一函式 QuickSort(),將傳入的一維陣列利用快速排序法,由小至大排序陣列元素。(20 分)
登入後即可作答並保存紀錄。
核心觀念
快速排序法(Quick Sort)採用「分割與征服」(Divide and Conquer)策略,核心步驟如下:
- 選擇一個元素作為基準值(pivot)。
- 將陣列重新排列,使得:
- 小於等於 pivot 的元素位於左側;
- 大於 pivot 的元素位於右側。
- 對 pivot 左右兩側的子陣列遞迴執行相同程序。
- 子陣列長度為 或 時,該子陣列必定已排序完成。
本題要求由小至大排序,因此分割時需將較小元素放在左方、較大元素放在右方。
解題方法
採用 Lomuto 分割法,選擇目前處理區間的最後一個元素作為 pivot。
假設目前處理範圍為:
令:
並以變數 表示「小於等於 pivot 的元素」區域最後位置。由 到 逐一掃描:
- 若 ,表示該元素應放在左側,先將 加一,再交換 與 。
- 掃描完成後,將 pivot 與 交換。
- 此時 pivot 已位於最終正確位置 。
分割完成後,再分別處理:
以及:
因為 pivot 位於 ,不必再參與遞迴。
Python 參考實作
def QuickSort(arr):
def partition(left, right):
pivot = arr[right]
i = left - 1
for j in range(left, right):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[right] = arr[right], arr[i + 1]
return i + 1
def quick_sort(left, right):
if left >= right:
return
pivot_index = partition(left, right)
quick_sort(left, pivot_index - 1)
quick_sort(pivot_index + 1, right)
quick_sort(0, len(arr) - 1)
return arr
範例:
data = [35, 12, 8, 26, 49, 17]
QuickSort(data)
print(data)
輸出:
[8, 12, 17, 26, 35, 49]
此寫法直接在原陣列上交換元素,屬於原地排序,最後回傳排序後的陣列。
分割程序說明
以陣列:
為例,第一次選擇最後元素 為 pivot。
掃描後,所有小於等於 的元素會被移至左側:
此時 已位於正確位置,接著分別對: