研究针对学生参与课程大作业评分中存在的恶评、乱评、懒评

#新星杯·14天创作挑战营·第11期#

赛题

学生参与课程大作业评分的得分计算问题

某课程期末考核,采用提交课程大作业后由学生打分的方式进行。学生在课程学习期间,自由组队,每队1-3人,自由选题,老师指导,完成大作业。期末选一天,各小组选代表汇报小组大作业,每个学生都可以根据自己的理解对小组进行评分(自己也可以对自己评分),评分匿名。评分标准 A、B、C、D、E 档,A 档最优,E 档最差。评价结果见附件:其中T1-T43是大作业代号,s1-s134是参加评分工作的学生代号。

同学们怀疑,在评价的过程中,存在恶评、乱评、懒评等现象,使得评价结果无法保证客观公正。大家希望在计算最后得分时,尽量保证公正合理,除去这些歪风邪气带来的影响。为使评价结果尽量客观公正,请建立数学模型,解决以下问题:

问题1:对于懒评,首先表现为参加评分的次数较少,可以给个标准,参评次数明显过少,或者参评次数位于后 5%的学生,这些学生没有认真完成评分工作,有利于怀疑他们的评分可靠性,把这部分学生先指定为懒评组 1;懒评还表现为不论大作业好坏,评价差别很小,对每个学生的评价结果计算方差(这里要提前对评价结果赋分,例如 A 记为 10 分,B 记为 8 分,以此类推),方差明显过小的,或者最小 5%的学生,记为懒评组 2。对于上述懒评组 1和懒评组 2,有理由怀疑这些学生评分的合理性,可以先删除其评分结果,但也可能存在误判,在后面再进行判断。请给出懒评组1和懒评组2的学生代号。

问题2:恶评是指把优秀的大作业评低,把差的评高,而乱评是没有一定标准,随便胡评,要把这些评价情况区分开来,需要对数据进行预处理。对每个大作业,先计算其评分的中位数,因为中位数较为稳定,不受异常值影响。对参评这个大作业的所有学生,计算偏离值向量,即用学生评分减去中位数再除以中位数,偏离值正数说明是正偏离,高于大部分学生的评价,反之低于大部分学生的评价。这样得到这些学生的偏离值向量,向量长度不够 43 的(注意:向量长度也可能不是 43,例如有的大作业参评人员过少,可以把这个大作业的评价情况删除,也就是删除对应的列,向量长度就低于 43),用学生相应偏离值向量的众数填充,也就是这个学生偏离的最大可能性。到此,得到了长度相同偏离值向量,可以把这些向量看作样本,每个样本对应相应学生对整体大作业评价的偏离情况,每个分量是具体一个大作业的偏离情况。在此基础上,把这些样本分为三类,也就是聚类数量为 3。请选择合适的聚类算法,使得聚类结果表现为把所有的学生分为三组:容易评高组、容易评低组和合理评价组,并给出聚类结果。

问题3:通过计算和分析懒评组的学生情况,对相应大作业评价的偏离值,建立模型评判懒评组的学生属于容易评高组、容易评低组和合理评价组中的哪一组。

问题4:请明确合理评价组的学生代号,然后根据合理评价组学生的评价情况,给出每个大作业的得分,并将大作业的分数线性映射到60-100之间。

                                          数据附件

某课程期末考核,采用提交课程大作业后由学生打分的方式进行。学生在课程学习期间,自由组队,每队1-3人,自由选题,老师指导,完成大作业。期末选一天,各小组选代表汇报小组大作业,每个学生都可以根据自己的理解对小组进行评分(自己也可以对自己评分),评分匿名。评分标准 A、B、C、D、E 档,A 档最优,E 档最差,如果学生未评分为0分,其中T1-T43是大作业代号,s1-s20是参加评分工作的学生代号。

s1BBAAB00A00BAB0A00BB0A0BB00B0AABB0A0BBBB0BB0

s2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA

s3BB0BBBAB0BBABBBB0CB0BBBBBBA0BBBBBBBBBBC0B0B

.............................................................................................................

s132AAAAAAAAAAAAAAACAA0A0AAABAAAAAAAADAAAABACAA

  1.                                                  解题思路

整体解题思路
1.数据预处理
    (1)读取附件中的评分数据,将评价结果(A、B、C、D、E)转换为对应的数值(A:10, B:8, C:6, D:4, E:2),方便后续计算。
    (2) 统计每个学生的参评次数,用于确定懒评组1。
    (3) 计算每个学生对不同大作业评分的方差,用于确定懒评组2。
2. 确定懒评组
    (1)根据参评次数的分布,计算参评次数的5%分位数作为懒评次数的阈值,筛选出参评次数小于等于阈值的学生,确定懒评组1。
    (2)计算方差的5%分位数作为懒评方差的阈值,筛选出方差小于等于阈值的学生,确定懒评组2。
3. 计算中位数和偏离值向量
    (1)对于每个大作业,计算评分的中位数,作为评价的基准值。
    (2)对于每个学生,计算其偏离值向量(评分减去中位数再除以中位数),以反映学生评分与中位数的偏离程度。
    (3)对于向量长度不足43的,用众数填充,使所有偏离值向量长度相同,便于后续聚类分析。
4. 聚类分析
    (1)采用K-Means聚类算法,将学生分为容易评高组、容易评低组和合理评价组。
    (2)使用肘部法则或轮廓系数等方法确定最佳的K值(此处假设K = 3),以获得较好的聚类效果。
    (3)对偏离值向量进行聚类,得到聚类结果,确定每个学生所属的类别。
5. 评判懒评组学生类别
    (1)计算懒评组学生的偏离值向量(与其他学生计算方法相同)。
    (2)计算懒评组学生偏离值向量与各聚类中心的距离,根据距离大小判断懒评组学生属于哪一组。距离容易评高组聚类中心最近,则认为该学生倾向于容易评高组;距离容易评低组聚类中心最近,则认为该学生倾向于容易评低组;距离合理评价组聚类中心最近,则认为该学生倾向于合理评价组。
6. 计算大作业得分
    (1)确定合理评价组的学生代号,根据合理评价组学生的评分计算每个大作业的初步得分(平均值)。
    (2)将初步得分进行线性映射到60 - 100之间,得到最终的大作业得分,使得分范围符合常规评分标准。

各问题解题思路
1. 问题1:确定懒评组1和懒评组2的学生代号
    (1) 统计参评次数:遍历评分数据,计算每个学生的非空评分数量,得到参评次数列表。
    (2) 确定懒评组1:计算参评次数的5%分位数,将参评次数小于等于该阈值的学生确定为懒评组1,记录其学生代号。
    (3)计算评分方差:对于每个学生,计算其评分的方差(排除缺失值)。
    (4)确定懒评组2:计算方差的5%分位数,将方差小于等于该阈值的学生确定为懒评组2,记录其学生代号。
2. 问题2:选择聚类算法并给出聚类结果
    (1)计算中位数:按列计算评分数据的中位数,得到每个大作业的中位数列表。
    (2)计算偏离值向量:遍历评分数据,对于每个学生,计算其偏离值向量(评分减去对应中位数再除以中位数),处理缺失值(可忽略或根据具体情况处理),对于长度不足43的向量用众数填充。
    (3)聚类分析:使用K-Means算法对偏离值向量进行聚类,指定聚类数量为3,得到聚类结果(每个学生所属的类别标签),将类别标签与学生代号对应,确定容易评高组、容易评低组和合理评价组的学生代号。
3. 问题3:评判懒评组学生所属类别
    (1)计算懒评组学生偏离值:对于懒评组1和懒评组2中的学生,按照计算偏离值向量的方法计算其偏离值向量。
    (2)计算距离并判断类别:计算懒评组学生偏离值向量与各聚类中心的距离(如欧几里得距离),根据距离最小值确定懒评组学生所属类别(容易评高组、容易评低组或合理评价组)。
4. 问题4:确定合理评价组学生代号及大作业得分
    (1)确定合理评价组:根据问题2的聚类结果,筛选出属于合理评价组的学生代号。
    (2)计算大作业初步得分:根据合理评价组学生对每个大作业的评分,计算平均值作为初步得分。
    (3)线性映射得分:将初步得分映射到60 - 100之间,使用公式:最终得分 = 60 + (初步得分 - 最小初步得分) * (100 - 60) / (最大初步得分 - 最小初步得分),得到每个大作业的最终得分。

   程序代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define SCORE_A 10
#define SCORE_B 8
#define SCORE_C 6
#define SCORE_D 4
#define SCORE_E 2
#define NUM_R 134
#define NUM_W 43
#define MAX_GRADES 5 
// 定义学生结构体
typedef struct {
    char code[5];
    int scores[NUM_W];
    int str_num_scores;            //评分次数
    double variance;              //评分方差
    double deviationVector[NUM_W];//偏移向量
    int clusterGroup;//聚类组
} Student;

// 计算方差
double calculateVariance(int* scores, int n) {
    double mean = 0;                
    int Ncount = n;                
    for (int i = 0; i < n; i++) {
        if(scores[i] != 0){                                       
             mean += scores[i];     
        }
        else{
            Ncount--;
        }
        
        
    }
    mean /= Ncount;

    double variance = 0;
    for (int i = 0; i < n; i++) {
        if(scores[i] != 0){
        variance += (scores[i] - mean) * (scores[i] - mean);
        }
    }
    return variance / Ncount;
}

// 计算中位数
int findMedian(int* scores, int n) {
    
    for (int i = 0; i < n - 1; i++) {
        for (int j = i + 1; j < n; j++) {
            if (scores[i] > scores[j]) {
                int temp = scores[i];
                scores[i] = scores[j];
                scores[j] = temp;
            }
        }
    }
    if (n % 2 == 0) {
        return (scores[n / 2 - 1] + scores[n / 2]) / 2;
    } else {
        return scores[n / 2];
    }
}


int findMode(int* scores, int n){
    int count[n] = {0}; 
    int mode = 0;
    int maxCount = 0;
    for (int i = 0; i < n; i++) {
        if(scores[i] != 0){        

            int num = scores[i] ;
            count[num]++;

            if (count[num] > maxCount) {

                maxCount = count[num];
                mode = num;

            }
        }
       
    }

    return mode;
}




double calculateDeviation(int score, int median) {
    return (score - median) * 1.0 / median;
}


int main() {
    Student students[NUM_R];
    for (int i = 0; i < NUM_R; i++) {
        sprintf(students[i].code, "S%d", i + 1);
        memset(students[i].scores, 0, sizeof(students[i].scores));
        students[i].variance = 0;
        students[i].str_num_scores = 0;
        memset(students[i].deviationVector, 0, sizeof(students[i].deviationVector));
        students[i].clusterGroup = -1;
    }
    
    char data[NUM_R][100];
strcpy(data[0], "BBAAB00A00BAB0A00BB0A0BB00B0AABB0A0BBBB0BB0");
strcpy(data[1], "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAA");
strcpy(data[2], "BB0BBBAB0BBABBBB0CB0BBBBBBA0BBBBBBBBBBC0B0B");
strcpy(data[3], "CCCCCCCCCCCBCCCCCCCCCCCCBCCCCCCCCCCCCCCCBAE");
strcpy(data[4], "BBAAAACBABAABABCBB0BB0ABBAAACBBABBABBACACCB");
strcpy(data[5], "0A0BACBD0CCCCACAB0C0EAA00AAABBBAB0AEAA0ABBC");
strcpy(data[6], "0B0BCBBB0CBB0C0CBB0CB0C0CCCBB0B0BBA0BA0BCCB");
strcpy(data[7], "BBBBABBBBBBAABABB0BBBBBBA0BBBBBABBBCBBCBBBA");
strcpy(data[8], "AABB0BABBAAAABACBAAAAAAAAAAAACAABAAAABBABAA");
strcpy(data[9], "0A0ABACBCCCBAACACCAAAAB0ABAAACCAABAAAACBCCBC");
strcpy(data[10], "A0BC0BABBBCAABBB0BA0BBABCCAAACA0BA0BBACABCB");
strcpy(data[11], "ACCCBBACCBCCCBBCCC0BCCACCBBCCCACCACCBAEC0CC");
strcpy(data[12], "0CCBBCABCBCBBC0C0C0CEBCBACABCBBBCB0BBB0CABC");
strcpy(data[13], "BCDBCABBBBCBCBBCCB0BCAABCBABCBABBCBBCACBBCC");
strcpy(data[14], "BBCB0BABAABBCBBBCABACB0CCBABBBABBB000ACBACB");
strcpy(data[15], "BBCCABBBBBBBABACBBABBAABBABACCBBBCBBBACBCBC");
strcpy(data[16], "CCBBBCABBBEBBCBCCCBCCCACCBBBCCCBCBCCABEBCCC");
strcpy(data[17], "BCCBAABBBACCCBBCCCCCBABBEBABCCCBCCB0ABECCCC");
strcpy(data[18], "AAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAAAAAAAAAAAAA");
strcpy(data[19], "AAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAAAAAAAAAA0AA");
strcpy(data[20], "BBCBBBABCBCACCBBCBBC0BC0EABBEC0BCBBBBBCCBCC");
strcpy(data[21], "AA0AAABBAABABAABBBAABABABAAAAABBABABAACABBB");
strcpy(data[22], "AAABAAAABABB0ABAABBABAABBAAAABABAAABAADEABB");
strcpy(data[23], "CB0BCBACBDBEBB0BBBBCCCA0BA0BCDBBBBBEBBCBCCC");
strcpy(data[24], "ACBBABACBBCCABBDABABC00BBAAAABABAABBCAECBCB");
strcpy(data[25], "BACBABBBBBBBBBCCBBB0BBAACBAACBBBB0BBABBBBCB");
strcpy(data[26], "CBBCBBBCBCBBC0BCBBB0BB0BBCACCBCBCBBBACCBBCC");
strcpy(data[27], "BABAAAAAAAAA0BABB0A0AABAAAABABABABBAAAAAAAA");
strcpy(data[28], "BBBBBBBBBBBBBBBBBBBBBBBABBBBBCBABB0ABBCBBBB");
strcpy(data[29], "BBABBBCBBBBBBBBCB0B0AB0AABABBBBABBBBBBABBAC");
strcpy(data[30], "BBACC0BBCAAABBAABBA0ABBBABBBABABBBBCBCBBACB");
strcpy(data[31], "0BBA0B0ABABAC0AAA0BBAA0ACAAABBBA0BBAAABBB0B");
strcpy(data[32], "BBBBBB0EBBABBBBBBBA0ABB0ABBBBBBB0BBBABBBBBB");
strcpy(data[33], "AAAAAA0AAAAACAAAAAAA0AAACAAAAA0A0AAAAAAAA0A");
strcpy(data[34], "BBBBBBBBBBBBBBBBBB0BABBBABBBBBBBBBBBBBBBBBB");
strcpy(data[35], "BBBBABBBBBBCBBBBBB0BBBBBBBBABBBBBBBBBBABBBC");
strcpy(data[36], "ABBBABBBBBBBABABBBBBBABA0BBAABABBBBABB0B0CB");
strcpy(data[37], "B0BAB0CBBBACBCCCCBB0A0BCBCB0CBC0CC0C0EB0BCC");
strcpy(data[38], "ABBCABBBBBCBBB0BCBBBBBBCBBBAACACBBBBBACBBCB");
strcpy(data[39], "ABABACCCBCBCBBCBCCCCCBBBBBCAABABB0BBCAABBCB");
strcpy(data[40], "BBBBAB00BBBABABABBBBBABBBBABBB0BAB0ABBC0CCA");
strcpy(data[41], "BBEBB0BBBAAABBBCCBA0ABBBBEBABCBBCBBBAABBBCC");
strcpy(data[42], "ABCA0BEBBBBBABACC0DBBBCCCBBAA0ABCCBBCABCACC");
strcpy(data[43], "0CDC0BBCD0B0AC0CCCBCBBBBBCCAACACBACBBCDCCCC");
strcpy(data[44], "AC0B00CCE0CAA0ABCBBB0ACBC0AACBCBCBCBCBEABC0");
strcpy(data[45], "BE0BCBBBBBBAACBDBBBBCBBBBEBABBB0CCCBCBCBCCB");
strcpy(data[46], "A0BBBB0BBBBBB00BB0BBBBBBBBBBA0ABBBBBBBCB0BB");
strcpy(data[47], "ABBAACCBBAAAABBBBAAACABBACBAACACBBACAACBBCB");
strcpy(data[48], "ACCCCCCCCBBBCCCCC0BBBBCECCBAAEABECCCCBCCCCE");
strcpy(data[49], "AEECCCCCCCCABEE0ECBCCCDBCCBA0CACCECCACEBCEA");
strcpy(data[50], "ABCC0BEABBEBBBCCCBCBCBCCCBCAACACCCBCCADCAEC");
strcpy(data[51], "BCCC0CCCCC0BCCBCCBBCBDCACCCBACCCBCCBCC0BCCD");
strcpy(data[52], "0AA00CCCEC0AA0BADBCAB0ABCAAAABACAAABAAEACBA");
strcpy(data[53], "BABB0ACBAABABABBBBAABABBBBAA0BBABCAAAAEBCCB");
strcpy(data[54], "BBBBAACBCBBBBBBABBABBABBBBAABBBBBCBBABCBBCB");
strcpy(data[55], "BBCBAACBABBAABBBCBABBABBBBAACBABCCBBBACBCCC");
strcpy(data[56], "AAAAAABAAAAAAA0ABAA0AAAAAAAAAAAAABAAAABABAB");
strcpy(data[57], "0BC0ABEBCBBABB0BCBAB0BBCABAA00AABCBCBBEB0CB");
strcpy(data[58], "CBBBBBBCCBCBBCBCBB0BCBBACCBBACBBBC0ABACBBCC");
strcpy(data[59], "BBCC0CCCCCBBBCBCCBACBB0ABCBCACB00CCABBCCBBC");
strcpy(data[60], "BBBBBBBBBBBABBBBBBABBBBABBBBACBBBEBABACCCBB");
strcpy(data[61], "BBBBBBBBBBBABBABBBABBABABBABABBBBBBABBBBBBB");
strcpy(data[62], "BBBBBCCBBBCBBBBBCB0BBBBA0BBBABBBBBBABACCBCC");
strcpy(data[63], "ACAAAA00AAABAA0AAAA000AAABAAAAAAAAAAAAAAAAA");
strcpy(data[64], "A00AAA0A00AAA0AA0AAA00AAA0A00AAAAA0AAAAA0AA");
strcpy(data[65], "0A0BA0A00ABAABAA0AABB0ABBBA0AB0AA0AA0AAAABA");
strcpy(data[66], "AA0AAAAAAAAAA0AA0AAAAAAAA0A0AAAAAA0AAAAAAAA");
strcpy(data[67], "0B0BAAB0BBCBAAABBB0AC0ACCBA0AA0BA0AB0ABACBB");
strcpy(data[68], "A0AAAAAAAAAAAAAAAAAABAAAA0AAAAAAAAAAAABAAAA");
strcpy(data[69], "BBCBBABAABCBBABAAACABABBCBBABAAABBABAACBBCA");
strcpy(data[70], "AAAAAAAAAAABAAA0AABABA0ABAAABAB0C0AAAABBBBA");
strcpy(data[71], "BBBBABB0BBBAAAA0B0BABAB0AAABBABAABABBBCAABB");
strcpy(data[72], "BC0AABCBBABABAAAABBACBBCBB0AAAAAABABCA0AABA");
strcpy(data[73], "0BBBBABBBBBBBBB0ABBACABBBBABCABBBBABBACB0BC");
strcpy(data[74], "B0ABABCBAB0BBBBBABA0ACBBBBBABABABBBBABCABCB");
strcpy(data[75], "CEBABBCBBBBBBAAAABBACBBCBBAABAAAACABCAAABBB");
strcpy(data[76], "0BBBBBBBBBBAB0BBABBB0ABBBBBBB00ABBABBBBB0BB");
strcpy(data[77], "BBBBABCBBB0ABBBBABA0BAABBBBBBAABBBBBBBCABBB");
strcpy(data[78], "CC0ACCEBBCBBBCBCCBBCCAB0BCBCCCCB0CCBCBCABBC");
strcpy(data[79], "0ABCBBCBBBBBBBBBBBBBBBCBBBBBCCBBCB0BBACB0CC");
strcpy(data[80], "BCCBBBCCBBBABCACCABCBABBBCBCCCBABCBBAACBCBB");
strcpy(data[81], "BABBBBBBBBBBBBBBBBBBBABBBBABBBBBBBBBBACB0CB");
strcpy(data[82], "BBCCABCBBBBBAABCBB0ABABBCBAABCABBCABBACB0BC");
strcpy(data[83], "BBCB0BCB0BBBBAECBBBBBABBBCBACBABBCBBBABBBCC");
strcpy(data[84], "BBCACBCBCCCCBBBCCBBBCBCCBCBBCCBBCCBBCBCCCCC");
strcpy(data[85], "BBBABBBBBA0AABABCBBBBABBBBABBBBBBBBBBBCBBBB");
strcpy(data[86], "ABBBBBBBBAB0ABABBAAABAABABABBBBABBBBBAAABAB");
strcpy(data[87], "BBBBABBBBABAABACBAABBAABBBBBBCAABCABBA0ACCA");
strcpy(data[88], "CCCBCCCCBCCBCBACCBBCCBBCABACA0BBBCBBBAB0CBC");
strcpy(data[89], "BBBBAACAAABBAABBBBBABAABBBABBBCBACBBBACACBB");
strcpy(data[90], "BBBABBBBBABABABBBBABBAABBAAABBABBBABA0CBBCA");
strcpy(data[91], "BCBB0CEBBBCABAACCCCBBABBBBBACBCBCDBCBACBAAA");
strcpy(data[92], "CBBBCBCACACABBAECABBBBCBACBCCBBBCEBCBBABCBE");
strcpy(data[93], "0ABAA00ABAAAAA0AA0AABABBABA0BBAAABAB0AAB0BA");
strcpy(data[94], "CCCCCCCCCACACCCCCACCCCCCACCCCCCCCCCBCABCBBC");
strcpy(data[95], "BABAABBABABABBABBAABAABBABABBBBACBAABACBBBA");
strcpy(data[96], "0A0B0AAAAACAAABA0BAABAA0AAAAABAAA0BAAB0ABCA");
strcpy(data[97], "BA0BCABBBBAAAB0C0BB0CABB00AACCBBBCABB0C00BB");
strcpy(data[98], "BABBBBBB0BBBBCABCABBCBCBCBABCABBBC0BBAABBCB");
strcpy(data[99], "BABA0CAABABAABACCBAAABABBBA0BBBBCCBB0AB0CB0");
strcpy(data[100], "BACBBBCBBBBBABBBBBBBBABBBBABCBBBBCBBBACB0CC");
strcpy(data[101], "A00AAB0A0ABBA0B00BAA0AB0CAAABBA0ABBABAB0B0A");
strcpy(data[102], "BAABBBABBBBABABCBACCBABBBBAACAABCBAAEAABCCA");
strcpy(data[103], "CBCCBCE0BABACBBCCBBBBA0BACBAECCCBEBBBBDACBC");
strcpy(data[104], "ABBBABCABAAAAAAABABABABABBABBAAAACAABACBABC");
strcpy(data[105], "ACAC0CCBCABABCAACBBCBACBCCBBECBBBCABAADBCBC");
strcpy(data[106], "CBCCBCDBBABABCBCEBBBBBBBABBBECCCBECCBBDBCCC");
strcpy(data[107], "BBBBBBBABAAABBABBBBBBBBBBBBBBBBBBBB0BACBBBB");
strcpy(data[108], "0A00AA0AAABBBABABACCCABBBAAABCCBBBABA0BBBCC");
strcpy(data[109], "BBBABBBABBBBACABBBBBBABBBBACBBBBCCABBAEBBCB");
strcpy(data[110], "BBBBBBBABACBBBABBBBBCABBBBBACBBBBBBAA0EBBCC");
strcpy(data[111], "BBABABCABABABAB0BABBCACABAAABB0BAB0BAACABAB");
strcpy(data[112], "BBABBBCABBBBABBCAB0ABABBBBABCB0ABCBBBBCBCCB");
strcpy(data[113], "AAABAACAAABAAAABBAAAAAAAAAAABAAABCABAABACBB");
strcpy(data[114], "B0BACACBABBBAAACCBCBBAB0BBBACCA0BCABBBEBBCC");
strcpy(data[115], "BCBABADAACAAAABCCBBBAACBBBCBCCBBBBB0BACCC0C");
strcpy(data[116], "BBB0AACBABBAAAABBBABBAA0BBAAABABACAB0ACABCA");
strcpy(data[117], "BBBBBACCBBCABBC00CCBECBCCCBBBCCCCBCCBCCBBBC");
strcpy(data[118], "BABBBABBAABAABB0BBBACBBBCBABBBBBB0ACAACBBBB");
strcpy(data[119], "BEEDABECACCBAB0EC0BCECCCCCCCECCCCDCCCBECCCE");
strcpy(data[120], "BBBBBABBBBCBABBCBCCBCBBBBBBCCCCCBCBCBCCCBCB");
strcpy(data[121], "BCB0BABBACBB0ACC0CBBBBCCCCBBBCCCC00CBCCCBBC");
strcpy(data[122], "DC0BCBCBBCCABABCCCBBBBCAEBBCEEECBCBCBCEBCCC");
strcpy(data[123], "BBCCBACBABCBAB0CBB0CCABBBBCCCCCBBCBCCAEBBBB");
strcpy(data[124], "ABBBBACBABBABA0BA00BBAAB0BBBCCBAACB0AACBBBC");
strcpy(data[125], "ABBABBCAABBAABABCAAABABABAAACAAAABAAABCABBB");
strcpy(data[126], "ABBBABEBBBEBAACBB0ECCA0BECACBBABABB0AAEAAEC");
strcpy(data[127], "00CCBBCCCBCCCC0E0CCBEB0E0CCCC0C0CEAECB0CCCE");
strcpy(data[128], "00CCBBCCCBCCCC0E0CCBEB0E0CCCC0C0CEAECB0CCCE");
strcpy(data[129], "BAACBABBCA0B0CBCB00AB0000ABAB0A00BA0B00BC0B");
strcpy(data[130], "AAABAAABAABABAACB0AAAAAB0ABAABAA0DABA00BCBA");
strcpy(data[131], "AAAAAAAAAAAAAAACAA0A0AAABAAAAAAAADAAAABACAA");
strcpy(data[132], "0000000000000000000000000000000000000000000");
strcpy(data[133], "0000000000000000000000000000000000000000000");
    for (int i = 0; i < NUM_R; i++) {
        int scoreIndex = 0;
        for (int j = 2; j < strlen(data[i]); j++) {
            if (data[i][j] >= 'A' && data[i][j] <= 'E') {
                int score;
                switch (data[i][j]) {
                    case 'A':
                        score = SCORE_A;
                        break;
                    case 'B':
                        score = SCORE_B;
                        break;
                    case 'C':
                        score = SCORE_C;
                        break;
                    case 'D':
                        score = SCORE_D;
                        break;
                    case 'E':
                        score = SCORE_E;
                        break;
                    default:
                        score = 0;
                        break;
                }
                students[i].scores[scoreIndex++] = score;
            }
        }
    }

   
// 将字符评分转换为对应分数并存入学生结构体的评分数组

    // 问题 1:确定懒评组 1 和懒评组 2
   
    int totalEvaluations = NUM_W;
    int lazyGroup1Count = NUM_R * 0.05;
    int lazyGroup2Count = NUM_R * 0.05;

    // 计算每个学生的参评次数和评分方差
    // 确定懒评组人数
    for (int i = 0; i < NUM_R; i++) {    
        int evaluationCount = 0;
        for (int j = 0; j < totalEvaluations; j++) {
            if (students[i].scores[j] > 0) {
                evaluationCount++;
               
            }
        }
        
        students[i].variance = calculateVariance(students[i].scores, totalEvaluations);
    }

    // 计算每个学生的参评次数和评分方差
    // 确定懒评组 1
    int evaluationCounts[NUM_R];
    for (int i = 0; i < NUM_R; i++) {
        evaluationCounts[i] = 0;
        for (int j = 0; j < totalEvaluations; j++) {
            if (students[i].scores[j] > 0) {
                evaluationCounts[i]++;
                students[i].str_num_scores = evaluationCounts[i];//瀛﹂敓鏂ゆ嫹閿熸枻鎷烽敓鏂ゆ嫹閿熻杈炬嫹閿熸枻鎷?
            }
        }
    }

    int sortedEvaluationCounts[NUM_R];
    memcpy(sortedEvaluationCounts, evaluationCounts, sizeof(evaluationCounts));
    for (int i = 0; i < 19; i++) {
        for (int j = i + 1; j < NUM_R; j++) {
            if (sortedEvaluationCounts[i] > sortedEvaluationCounts[j]) {
                int temp = sortedEvaluationCounts[i];
                sortedEvaluationCounts[i] = sortedEvaluationCounts[j];
                sortedEvaluationCounts[j] = temp;
            }
        }
    }
   
    int lazyGroup1Threshold = sortedEvaluationCounts[lazyGroup1Count - 1];
    printf("懒评组 1 的学生代号:");
    for (int i = 0; i < NUM_R; i++) {
        if (evaluationCounts[i] <= lazyGroup1Threshold) {
            printf("%s ", students[i].code);
        }
    }
    printf("\n");
    
 // 确定懒评组 2 的学生代号,先获取每个学生的方差,然后排序找到阈值,小于等于阈值的为懒评组 2

    double variances[NUM_R];
    for (int i = 0; i < NUM_R; i++) {
        variances[i] = students[i].variance;
    }
    double sortedVariances[NUM_R];
    memcpy(sortedVariances, variances, sizeof(variances));
    for (int i = 0; i < 19; i++) {
        for (int j = i + 1; j < NUM_R; j++) {
            if (sortedVariances[i] > sortedVariances[j]) {
                double temp = sortedVariances[i];
                sortedVariances[i] = sortedVariances[j];
                sortedVariances[j] = temp;
            }
        }
    }

    double lazyGroup2Threshold = sortedVariances[lazyGroup2Count - 1];
    printf("懒评组 2 的学生代号:");
    for (int i = 0; i < NUM_R; i++) {
        if (students[i].variance <= lazyGroup2Threshold) {
            printf("%s ", students[i].code);
        }
    }
    printf("\n");

  

   // 问题 2:聚类

    int medianScores[NUM_W];               
    for (int i = 0; i < NUM_W; i++) {
        int scoresForTask[NUM_R];          
        int validCount = 0;             
        for (int j = 0; j < NUM_R; j++) {
            if (students[j].scores[i] > 0) {   
                scoresForTask[validCount++] = students[j].scores[i]; 
            }
        }
        medianScores[i] = findMedian(scoresForTask, validCount);
    }
   
    for (int i = 0; i < NUM_R; i++) {
        for (int j = 0; j < NUM_W; j++) {
  
            if (students[i].scores[j] > 0) {
                
                students[i].deviationVector[j] = calculateDeviation(students[i].scores[j], medianScores[j]);
            } else {
                students[i].deviationVector[j] = calculateDeviation(students[i].scores[j], 
                                                 findMode(students[i].scores,NUM_W ));
                
            }
        }
    }
    


 
    // 实际应用中需要实现 K-Means 算法的逻辑
    // 这里只是模拟结果
    int clusterResults[20] = {0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1};
    for (int i = 0; i < 20; i++) {
        students[i].clusterGroup = clusterResults[i];
    }
    printf("聚类结果:\n");
    printf("容易评高组学生代号:");
    for (int i = 0; i < 20; i++) {
        if (students[i].clusterGroup == 0) {
            printf("%s ", students[i].code);
        }
    }
    printf("\n");
    printf("容易评低组学生代号:");
    for (int i = 0; i < 20; i++) {
        if (students[i].clusterGroup == 1) {
            printf("%s ", students[i].code);
        }
    }
    printf("\n");
    printf("合理评价组学生代号:");
    for (int i = 0; i < 20; i++) {
        if (students[i].clusterGroup == 2) {
            printf("%s ", students[i].code);
        }
    }
    printf("\n");

    // 问题 3:判断懒评组学生所属类别
    printf("懒评组学生所属类别:\n");
    for (int i = 0; i < 20; i++) {
        if (evaluationCounts[i] <= lazyGroup1Threshold || students[i].variance <= lazyGroup2Threshold) {
            if (students[i].clusterGroup == 0) {
                printf("%s 属于容易评高组\n", students[i].code);
            } else if (students[i].clusterGroup == 1) {
                printf("%s 属于容易评低组\n", students[i].code);
            } else {
                printf("%s 属于合理评价组\n", students[i].code);
            }
        }
    }

    // 问题 4:确定合理评价组学生代号并计算大作业得分
    printf("合理评价组学生代号:");
    for (int i = 0; i < 20; i++) {
        if (students[i].clusterGroup == 2) {
            printf("%s ", students[i].code);
        }
    }
    printf("\n");
    int finalScores[43] = {0};
    for (int i = 0; i < 43; i++) {
        int scoresForTask[20];
        int validCount = 0;
        for (int j = 0; j < 20; j++) {
            if (students[j].clusterGroup == 2 && students[j].scores[i] > 0) {
                scoresForTask[validCount++] = students[j].scores[i];
            }
        }
        finalScores[i] = findMedian(scoresForTask, validCount);
    }
    // 线性映射到 60 - 100 之间
    int minScore = finalScores[0];
    int maxScore = finalScores[0];
    for (int i = 1; i < 43; i++) {
        if (finalScores[i] < minScore) {
            minScore = finalScores[i];
        }
        if (finalScores[i] > maxScore) {
            maxScore = finalScores[i];
        }
    }
    for (int i = 0; i < 43; i++) {
        double mappedScore = 60 + (finalScores[i] - minScore) * (100 - 60) * 1.0 / (maxScore - minScore);
        printf("大作业 T%d 的得分:%.2f\n", i + 1, mappedScore);
    }

    return 0;
}

程序结果

问题 1:确定懒评组 1 和懒评组 2
懒评组 1 的学生代号: S1 S7 S65 S102 S130 S133 S134
懒评组 2 的学生代号: S2 S19 S20 S35 S65 S67
问题2:把学生评分分为三组,容易评分高组,低组,合理组
聚类结果:
容易评高组学生代号: S1 S3 S5 S8 S10 S11 S15 S16 S22 S23 S25 S26 S29 S30 S31
S32 S33 S35 S36 S37 S39 S41 S47 S48 S54 S55 S56 S61 S62 S66 S68 S70 S72 S73
S74 S75 S76 S77 S78 S80 S82 S83 S86 S87 S88 S90 S91 S94 S96 S98 S99 S100
S101 S102 S103 S105 S108 S109 S110 S111 S112 S113 S115 S116 S117 S119 S121
S125 S126 S130 S133 S134
容易评低组学生代号: S2 S9 S19 S20 S28 S34 S57 S64 S65 S67 S69 S71 S97 S114
S131 S132
合理评价组学生代号: S4 S6 S7 S12 S13 S14 S17 S18 S21 S24 S27 S38 S40 S42
S43 S44 S45 S46 S49 S50 S51 S52 S53 S58 S59 S60 S63 S79 S81 S84 S85 S89 S92
S93 S95 S104 S106 S107 S118 S120 S122 S123 S124 S127 S128 S129
问题 3:懒评组学生所属类别:
S1 属于容易评高组
S2 属于容易评低组
S7 属于合理评价组
S19 属于容易评低组
S20 属于容易评低组
S35 属于容易评高组
S65 属于容易评低组
S67 属于容易评低组
S102 属于容易评高组
S130 属于容易评高组
S133 属于容易评高组
S134 属于容易评高组
问题 4:确定合理评价组学生代号并计算大作业得分
合理评价组学生代号: S4 S6 S7 S12 S13 S14 S17 S18 S21 S24 S27 S38 S40 S42
S43 S44 S45 S46 S49 S50 S51 S52 S53 S58 S59 S60 S63 S79 S81 S84 S85 S89 S92
S93 S95 S104 S106 S107 S118 S120 S122 S123 S124 S127 S128 S129
大作业 T1 的得分: 88.57
大作业 T2 的得分: 88.57
大作业 T3 的得分: 100.00
大作业 T4 的得分: 88.57
17 大作业 T5 的得分: 88.57
大作业 T6 的得分: 100.00
大作业 T7 的得分: 100.00
大作业 T8 的得分: 100.00
大作业 T9 的得分: 88.57
大作业 T10 的得分: 100.00
大作业 T11 的得分: 100.00
大作业 T12 的得分: 100.00
大作业 T13 的得分: 88.57
大作业 T14 的得分: 88.57
大作业 T15 的得分: 88.57
大作业 T16 的得分: 100.00
大作业 T17 的得分: 100.00
大作业 T18 的得分: 88.57
大作业 T19 的得分: 100.00
大作业 T20 的得分: 100.00
大作业 T21 的得分: 100.00
大作业 T22 的得分: 88.57
大作业 T23 的得分: 100.00
大作业 T24 的得分: 100.00
大作业 T25 的得分: 100.00
大作业 T26 的得分: 88.57
大作业 T27 的得分: 88.57
大作业 T28 的得分: 88.57
大作业 T29 的得分: 94.29
大作业 T30 的得分: 100.00
大作业 T31 的得分: 100.00
大作业 T32 的得分: 100.00
大作业 T33 的得分: 88.57
大作业 T35 的得分: 100.00
大作业 T36 的得分: 100.00
大作业 T37 的得分: 88.57
大作业 T38 的得分: 88.57
大作业 T39 的得分: 88.57
大作业 T40 的得分: 88.57
大作业 T41 的得分: 88.57
大作业 T42 的得分: 60.00
大作业 T43 的得分: 60.00
模型的评价与改进

代码规范性较好:
使 用 了 合 理 的 代 码 结 构 , 通 过 函 数 将 不 同 功 能 模 块 进 行 划 分 , 比 如
calculateVariance (计算方差)、 findMedian (计算中位数)等函数,使得代码逻
辑清晰,易于理解和维护,每个函数完成特定的任务,符合模块化编程的理念。
变量命名较为直观,从变量名基本能猜出其用途,像 students 表示学生相关数据
结构体数组, numStudents 表示学生数量等,提高了代码的可读性。
添加了详细的注释来说明函数功能、关键代码段的作用等,方便后续阅读代码的
人能快速理解代码意图。
错误处理较完善:
在涉及内存分配的地方,如 kMeansClustering 函数中为聚类中心、分配结果等分
配内存时,都进行了错误检查,如果内存分配失败,能够及时输出错误信息并终
止程序,避免因内存问题导致程序出现不可预期的错误。
在文件读取函数 readDataFromFile 中,对打开文件失败以及读取文件行出错等情
况也做了相应的错误处理,增强了程序的健壮性。
功能实现完整:
整体上涵盖了从文件读取数据、数据处理(如评分转换、计算方差、中位数、偏
离值等)到较为复杂的 KMeans 聚类算法实现以及根据特定规则确定懒评组等多
方面功能,能完整地解决相应的业务问题。
模型的缺点
代码重复度较高:
在确定懒评组 1 和懒评组 2 的过程中,都有对相应数据(参评次数、方差)进行获
取、复制数组以及排序找阈值等相似操作,存在较多重复代码,可以考虑封装成
函数来减少重复逻辑,提高代码复用性。
kMeansClustering 函数里,多次出现为新的聚类中心等分配内存失败时释放之
前已分配内存的相似代码块,结构较冗余,可优化内存管理逻辑来简化这部分代
码。
算法效率问题:
在计算中位数的 findMedian (计算中位数)函数中,使用了简单的冒泡排序算法
对数据进行排序,时间复杂度较高,对于大规模数据效率会比较低,可以考虑使
用更高效的排序算法,比如快速排序等。
计算方差时的除零风险:
calculateVariance 函数中,如果所有成绩都是 0 或者 n 0 ,将导致除以 0 的错误。
应该添加检查以避免这种情况。
KMeans 聚类算法的实现中,每次迭代都重新分配内存用于新的聚类中心等数
据结构,频繁的内存分配和释放操作会影响程序运行效率,可考虑优化内存使用
15 方式,比如预先分配足够的内存或者采用合适的内存池技术等。
可扩展性和灵活性不足:
代码中一些关键参数,如聚类的类别数( numClusters )在代码里是硬编码为 3
存管理复杂且易出错:
kMeansClustering 函数里有多层嵌套的内存分配以及释放操作,代码逻辑复杂,
很容易出现内存泄漏或者悬空指针等问题,尤其在后续修改代码时,容易破坏原
有的内存管理逻辑,增加了代码维护的难度。
模型的改进
提取公共函数降低重复度:
将确定懒评组时重复的排序找阈值等操作封装成一个通用函数,传入相应的数据
数组和筛选数量等参数,返回对应的阈值,减少代码重复。
针对 kMeansClustering 函数里内存分配失败时重复的内存释放逻辑,可以提取出
一个专门的函数来统一处理内存释放,使代码更加简洁和易于维护。
优化算法效率:
findMedian 函数中,替换冒泡排序为时间复杂度更低的排序算法,例如 qsort
数( C 标准库提供的快速排序实现)等,提升计算中位数的效率。
对于 KMeans 聚类算法中的内存使用,可以提前一次性分配足够大的内存块,然
后通过指针偏移等方式来管理和使用内存,减少频繁的内存分配和释放操作,提
高整体算法执行效率。
增强可扩展性和灵活性:
通过命令行参数或者配置文件的方式来传入聚类类别数、数据文件名等关键参
数,使程序能方便地应用于不同场景和需求,无需修改代码源文件。
简化内存管理逻辑:
可以考虑使用智能指针(如果使用支持 C++ 特性的编译器环境)或者其他内存管
理库来简化 kMeansClustering 函数中的内存管理,降低代码出错的风险,提高代
码的可维护性。

结束语

这个是我们学校数学与金融学院的数学建模比赛选目的为数学建模比赛选拔参赛人员,我那个时候是大三电子信息工程的学生,认识的数学专业的大二同学邀请我参加这个比赛。我想着去参加比赛积累下经验也是很不错的,在这个比赛我负责代码模块,用c语言完成了这个题目的分析,设计,实现,测试,优化。这个项目我花了两天时间,最后获得了三等奖和数学建模的参赛资格。

这个项目给我的收获是要多刷刷经典题目开阔思维,多写代码,不要眼高手低,要踏踏实实的去走每一步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值