宿舎管理システムの設計と実装(C言語によるデータ構造の応用)

概要

本稿では、データ構造とアルゴリズムに関する課題として開発した「宿舎管理照会ソフトウェア」について紹介する。学生の宿舎情報を効率的に管理・検索・操作することを目的とし、C言語で実装された単方向連結リストを基盤としたシステムである。主な機能には、学生情報の追加・削除・検索・ソート・表示が含まれる。ユーザーインターフェースはテキストベースのメニュー形式で、操作性と視認性に配慮している。

データ構造の設計

学生データは以下の構造体で表現される。学籍番号、氏名、宿舎番号、年齢を文字列型で保持し、動的メモリ管理に対応できるようにポインタを用いる。
typedef struct StudentNode {
    char studentId[16];
    char name[21];
    char dormNumber[16];
    char age[4];
    struct StudentNode* next;
} StudentNode;
グローバル変数として、リストの先頭を指すheadと、登録人数を示すstudentCountを定義する。
StudentNode* head = NULL;
int studentCount = 0;

主要機能の実装

宿舎の収容状況確認

指定された宿舎番号に現在何名の学生が入居しているかをカウントする補助関数。新規登録時に最大6名制限をチェックするために使用される。
int getOccupancy(const char* targetDorm) {
    int count = 0;
    StudentNode* ptr = head;
    while (ptr != NULL) {
        if (strcmp(ptr->dormNumber, targetDorm) == 0) {
            count++;
        }
        ptr = ptr->next;
    }
    return count;
}

学生情報の登録

標準入力から各項目を受け取り、新しいノードを生成してリスト末尾に追加する。宿舎の空き状況をリアルタイムで確認しながら登録を行う。
void registerStudent() {
    StudentNode* newEntry = (StudentNode*)malloc(sizeof(StudentNode));
    if (!newEntry) {
        printf("メモリ確保失敗\n");
        exit(1);
    }

    printf("学籍番号: "); scanf("%s", newEntry->studentId);
    printf("氏名: ");     scanf("%s", newEntry->name);

    char tempDorm[16];
    do {
        printf("宿舎番号: "); scanf("%s", tempDorm);
        if (getOccupancy(tempDorm) < 6) {
            strcpy(newEntry->dormNumber, tempDorm);
            break;
        } else {
            printf("満室です。別の部屋を入力してください。\n");
        }
    } while (1);

    printf("年齢: "); scanf("%s", newEntry->age);

    // リスト末尾への挿入
    if (head == NULL) {
        head = newEntry;
    } else {
        StudentNode* current = head;
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = newEntry;
    }
    newEntry->next = NULL;
    studentCount++;
    printf("登録完了\n");
}

情報の削除

学籍番号をキーに一致するノードを探索し、リストから削除する。前節点の存在を考慮してポインタを再接続し、不要になったメモリを解放する。
void removeStudent() {
    if (head == NULL) {
        printf("データがありません\n");
        return;
    }

    char targetId[16];
    printf("削除対象の学籍番号: ");
    scanf("%s", targetId);

    StudentNode *current = head, *previous = NULL;
    while (current != NULL && strcmp(current->studentId, targetId) != 0) {
        previous = current;
        current = current->next;
    }

    if (current == NULL) {
        printf("該当者なし\n");
        return;
    }

    if (previous == NULL) {
        head = current->next;
    } else {
        previous->next = current->next;
    }

    free(current);
    studentCount--;
    printf("削除成功\n");
}

二分探索による検索

学籍番号での検索には二分探索を採用するため、事前に配列にコピーしてソート済み状態にする必要がある。以下はその探索処理の例。
int binarySearchById(StudentNode** array, int size, const char* key) {
    int left = 0, right = size - 1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        int cmp = strcmp(array[mid]->studentId, key);
        if (cmp == 0) return mid;
        if (cmp < 0) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

テーブル形式の出力整形

視覚的に整った表形式でデータを表示するため、フィールドごとに適切な幅を計算して中央揃えで出力する。
#define COL_ID    15
#define COL_NAME  20
#define COL_DORM  15
#define COL_AGE   10

void printCentered(const char* text, int width) {
    int len = strlen(text);
    int pad = (width - len) / 2;
    for (int i = 0; i < pad; i++) putchar(' ');
    printf("%s", text);
    for (int i = 0; i < width - len - pad; i++) putchar(' ');
    printf("│");
}

void displayRecord(StudentNode* node) {
    printf("│");
    printCentered(node->studentId,  COL_ID);
    printCentered(node->name,      COL_NAME);
    printCentered(node->dormNumber,COL_DORM);
    printCentered(node->age,       COL_AGE);
    printf("\n");
}

ソート機能

連結リストのままでは効率的なソートが困難なため、一時配列にポインタをコピーしてソートを行い、その後リスト構造を再構築する手法を採用。学籍番号(バブルソート)、年齢(挿入ソート)、宿舎番号(選択ソート)の3種類を実装。
void sortByStudentId() {
    if (head == NULL || studentCount < 2) return;

    StudentNode** tempArray = malloc(studentCount * sizeof(StudentNode*));
    StudentNode* current = head;
    for (int i = 0; i < studentCount; i++) {
        tempArray[i] = current;
        current = current->next;
    }

    // バブルソート
    for (int i = 0; i < studentCount - 1; i++) {
        for (int j = 0; j < studentCount - i - 1; j++) {
            if (strcmp(tempArray[j]->studentId, tempArray[j+1]->studentId) > 0) {
                StudentNode* tmp = tempArray[j];
                tempArray[j] = tempArray[j+1];
                tempArray[j+1] = tmp;
            }
        }
    }

    // リストの再構成
    head = tempArray[0];
    current = head;
    for (int i = 1; i < studentCount; i++) {
        current->next = tempArray[i];
        current = current->next;
    }
    current->next = NULL;

    free(tempArray);
    printf("ソート完了\n");
}

メインメニューとフロー制御

ユーザー操作を統合的に管理するメニュー関数。無限ループ内で入力を受け付け、対応する機能を呼び出す。
void showMenu() {
    system("cls");
    printf("┌──────────────────────────┐\n");
    printf("│      宿舎管理システム      │\n");
    printf("├──────────────────────────┤\n");
    printf("│ 1. 学生登録                │\n");
    printf("│ 2. 全件表示                │\n");
    printf("│ 3. 検索                    │\n");
    printf("│ 4. ソート                  │\n");
    printf("│ 5. 削除                    │\n");
    printf("│ 0. 終了                    │\n");
    printf("└──────────────────────────┘\n");
}

int main() {
    int choice;
    showMenu();
    while (1) {
        printf("選択: ");
        scanf("%d", &choice);
        switch (choice) {
            case 1: showMenu(); registerStudent(); break;
            case 2: showMenu(); displayAll(); break;
            case 3: showMenu(); performSearch(); break;
            case 4: showMenu(); performSort(); break;
            case 5: showMenu(); removeStudent(); break;
            case 0: exit(0);
            default: showMenu(); printf("無効な入力\n");
        }
    }
    return 0;
}

タグ: C言語 データ構造 連結リスト 二分探索 ソートアルゴリズム

5月30日 11:45 投稿