2.鏈表


實驗2-鏈表

1、實驗目的:
(1)掌握線性表的定義;
(3)掌握線性表的基本操作,如建立、查找、插入和刪除等。
(4)掌握文件方式輸入

2、實驗內容:
定義一個包含學生信息(學號,姓名,成績)的單鏈表,使其具有如下功能(參見教材中基本操作):
(1) 從文件中輸入學生信息(前插法和后插法構建表);
(2) 逐個顯示學生表中所有學生的相關信息(顯示表,即遍歷表);
(3) 根據姓名進行查找,返回此學生的學號和成績(查找);
(4) 根據指定的位置可返回相應的學生信息(學號,姓名,成績)(取值 );
(5) 給定一個學生信息,插入到表中指定的位置(插入);
(6) 刪除指定位置的學生記錄(刪除);
(7) 統計表中學生個數(相當於求表長)。

說明

實驗指導代碼存在的一些問題:

  • students.txt 使用的是相對路徑,所以運行時需要將該文件放至可執行文件所在的路徑,否則無法找到文件。對於 VisualStudio,調試運行時可執行文件的位置是 項目路徑\Debug ,請將 students.txt 復制到該文件夾下,使用 DevCpp 的同學同理。
  • 代碼沒有檢查 students.txt 中的空白行,所以可能輸入無效數據,請把 students.txt 最后一行空行刪除
  • 函數命名風格很難看,為了和實驗指導書一致,我沒有修改。建議自己改寫函數名,請參考 Google 開源項目風格指南

代碼參考

文件名: students.txt

學號	        姓名	成績
201713160101	ZSY		85
201713160102	WYX		70
201713160103	FHY		90
201713160104	WJY		65
201713160105	LZC		75
201713160106	ZL		55
201713160107	ZYZ		95
201713160108	LZY		85
201713160109	ZC		80
201713160110	TL		70
201713160111	PYF		60
201713160112	LCG		50
201713160113	CYF		40
201713160114	KDJ		75
201713160115	KYM		85
201713160116	CPY		65
201713160117	LL		45
201713160118	LYH		55
201713160119	DJX		80
201713160120	ZXH 	90

文件名: LinkList.cpp

#include <iostream>
#include <cstring>
#include <fstream>

using std::cin;
using std::cout;
using std::endl;
using std::fstream;
using std::string;

#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status; //Status 是函數返回值類型,其值是函數結果狀態代碼。

struct Student
{
    char no[80];   //學號
    char name[80]; //姓名
    int price;     //成績
};

struct LNode
{
    Student data;       //結點的數據域
    struct LNode *next; //結點的指針域
};
typedef LNode *LinkList;  //LinkList為指向結構體LNode的指針類型
typedef Student ElemType; //ElemType 為可定義的數據類型,此設為Student類型

// --------------------- 以下是鏈表相關函數聲明 ------------------------------

Status InitList_L(LinkList &L);                      //初使化鏈表
void CreateList_H(LinkList &L);                      //前插法構建鏈表
void CreateList_R(LinkList &L);                      //后插法構建鏈表
int ListLength_L(LinkList &L);                       //鏈表長度
Status ListDisplay(LinkList &L);                     //顯示
LNode *LocateElem_L(LinkList &L, char *name);        //查找
Status GetElem_L(LinkList &L, int i, Student &e);    //獲取元素
Status ListInsert_L(LinkList &L, int i, Student &e); //插入元素
Status ListDelete_L(LinkList &L, int i);             //刪除
Status DestroyList_L(LinkList &L);

// --------------------- 一些用到的全局變量 -----------------------------------
string head_1, head_2, head_3; //文件中的標題(第一行)
int length;                    // 全局變量保存鏈表長度

// -------------- 下面是 main 函數, 程序入口 --------------------------------
int main()
{
    int i, choose;
    Student e;
    char name[80];
    LinkList L = NULL;
    LNode *p = NULL;

    cout << "---------------------\n";
    cout << "1.初使化鏈表\n";
    cout << "2.前插法構建鏈表\n";
    cout << "3.后插法構建鏈表\n";
    cout << "4.求鏈表長度\n";
    cout << "5.顯示鏈表內容\n";
    cout << "6.查找學生信息\n";
    cout << "7.獲取學生信息\n";
    cout << "8.插入學生信息\n";
    cout << "9.刪除學生信息\n";
    cout << "10.銷毀鏈表\n";
    cout << "0.退出\n";
    cout << "---------------------\n\n";

    choose = -1;
    while (choose != 0)
    {
        cout << "\n請選擇:";
        cin >> choose;
        switch (choose)
        {
        case 1: //初始化一個單鏈表
            if (InitList_L(L))
                cout << "成功建立鏈表!\n";
            else
                cout << "建立鏈表失敗!\n";
            break;

        case 2: //使用前插法創建單鏈表
            CreateList_H(L);
            cout << "輸入 students.txt 信息完畢(前插法)\n";
            break;

        case 3: //使用后插法創建單鏈表
            CreateList_R(L);
            cout << "輸入 students.txt 信息完畢(后插法)\n";
            break;

        case 4: //求單鏈表長度
            cout << "當前鏈表長度為:" << ListLength_L(L) << endl;
            break;

        case 5: //顯示鏈表內容
            cout << "當前鏈表內容為:\n";
            ListDisplay(L);
            break;

        case 6: //單鏈表的按序號取值
            cout << "請輸入學生序號:";
            cin >> i;
            if (GetElem_L(L, i - 1, e))
            {
                cout << "查找成功\n";
                cout << "第" << i << "本學生證的信息是:\n";
                cout << "\t" << e.no << "\t" << e.name << "\t" << e.price << endl;
                cout << endl;
            }
            else
                cout << "查找失敗\n\n";
            break;

        case 7: //單鏈表的按值查找
            cout << "請輸入所要查找學生姓名:";
            cin >> name;
            p = LocateElem_L(L, name);
            if (p)
            {
                cout << "查找成功\n";
                cout << "對應的學生信息為:\n";
                cout << "\t" << p->data.no << "\t" << p->data.name << "\t" << p->data.price << endl;
            }
            else
                cout << "查找失敗! " << name << " 沒有找到\n\n";
            break;

        case 8: //單鏈表的插入
            cout << "請輸入插入的位置:";
            cin >> i;
            cout << "輸入學生信息(學號 姓名 成績):";
            cin >> e.no >> e.name >> e.price;
            if (ListInsert_L(L, i - 1, e))
                cout << "插入成功.\n\n";
            else
                cout << "插入失敗!\n\n";
            break;

        case 9: //單鏈表的刪除
            cout << "請輸入所要刪除的學生的編號:";
            cin >> i;
            if (ListDelete_L(L, i - 1))
                cout << "刪除成功!\n\n";
            else
                cout << "刪除失敗!\n\n";
            break;

        case 10: //銷毀鏈表
            if (DestroyList_L(L))
                cout << "成功銷毀鏈表!\n\n";
            break;
        }
    }
    return 0;
}

// ------------------------ 鏈表相關函數的實現 -------------------

Status InitList_L(LinkList &L)
{
    //構造一個空的單鏈表L
    L = new LNode;
    L->next = NULL;
    length = 0;
    return OK;
}

void CreateList_H(LinkList &L)
{
    fstream file; //打開文件進行讀寫操作
    file.open("students.txt");
    if (!file)
    {
        cout << "未找到相關文件,無法打開!" << endl;
        exit(ERROR);
    }
    file >> head_1 >> head_2 >> head_3; //文件數據中的標題:第一行
    while (!file.eof())
    {                                                        //將文件中的信息運用前插法插入到鏈表中
        LNode *p = new LNode;                                //生成新結點
        file >> p->data.no >> p->data.name >> p->data.price; //輸入元素值賦給新結點*p的數據域
        p->next = L->next;
        L->next = p;
        length++; //同時對鏈表長度進行統計
    }
    file.close();
}

void CreateList_R(LinkList &L)
{ //算法2.12 后插法創建單鏈表(文件輸入)
    //正位序輸入n個元素的值,建立帶表頭結點的單鏈表L
    LNode *r = L; // 指向最后一個結點
    fstream file; //打開文件進行讀寫操作
    file.open("students.txt");
    if (!file)
    {
        cout << "未找到相關文件,無法打開!" << endl;
        exit(ERROR);
    }
    file >> head_1 >> head_2 >> head_3; //文件數據中的標題:第一行,不需要直接丟棄
    while (!file.eof())
    {                                                        //將文件中的信息運用后插法插入到鏈表中
        LNode *p = new LNode;                                //生成新結點
        file >> p->data.no >> p->data.name >> p->data.price; //輸入元素值賦給新結點*p的數據域
        p->next = NULL;
        r->next = p;
        r = p;
        length++; //同時對鏈表長度進行統計
    }
    file.close();
}

Status ListDisplay(LinkList &L)
{
    LNode *p = L->next; //p指向第一個結點(跳過頭結點)
    int i = 1;
    while (p)
    {
        cout << "[" << i << "]\t" << p->data.no << "\t" << p->data.name << "\t" << p->data.price << "\n";
        p = p->next;
        i++;
    }
    cout << endl;
    return OK;
}

int ListLength_L(LinkList &L)
{
    //返回L中數據元素個數
    LNode *p = L->next; //p指向第一個數據結點
    int i = 0;
    while (p)
    {
        i++;
        p = p->next;
    }
    return i; // 其實可以直接return length;
}

LNode *LocateElem_L(LinkList &L, char *name)
{
    LNode *p = L->next;
    while (p && strcmp(p->data.name, name) != 0)
        p = p->next;
    return p;
}

Status GetElem_L(LinkList &L, int i, Student &e)
{
    if (i < 0 || i >= length)
        return ERROR; // 下標越界了
    LNode *p = L->next;
    for (int j = 0; j < i; j++)
        p = p->next;
    e = p->data;
    return OK;
}

Status ListInsert_L(LinkList &L, int i, Student &e)
{
    if (i < 0 || i > length)
        return ERROR;
    LNode *p = L->next;
    for (int j = 0; j < i - 1; j++) // p 指向 i - 1 號結點
        p = p->next;
    LNode *n = new LNode;
    n->data = e;
    n->next = p->next; // 把新的結點 n 插入 i 號位置
    p->next = n;
    return OK;
}

Status ListDelete_L(LinkList &L, int i)
{
    if (i < 0 || i >= length)
        return ERROR;
    LNode *p = L->next;
    for (int j = 0; j < i - 1; j++) // p 指向 i -1 號結點
        p = p->next;
    LNode *t = p->next; // 指向i號結點
    p->next = t->next;
    delete t; // 刪除i號結點釋放內存
    return OK;
}

Status DestroyList_L(LinkList &L)
{
    LNode *p = L->next;
    LNode *n = NULL;
    while (p)
    {
        n = p->next; // 記下p的后繼結點
        delete p;    // 刪除p指向的結點
        p = n;
    }
    L->next = NULL; // 頭結點后繼置空
    length = 0;
    return OK;
}

運行截圖

OOP 版

使用 C++11

#include <iostream>
#include <string>
#include <exception>
#include <fstream>

using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::string;

using Score = int; // 成績的類型

struct Student
{
    string name; // 姓名
    size_t id;   // 學號
    Score score; // 成績
};

// 單鏈表
template <typename ElemType>
class LinkList
{
    struct LNode
    {
        ElemType data;
        LNode *next;
    };

private:
    LNode *head_;   // 頭結點
    size_t length_; // 當前元素個數

public:
    LinkList();
    ~LinkList();
    LinkList(const LinkList &) = delete;
    LinkList &operator=(const LinkList &) = delete;

    LNode *GetPreNode(size_t pos);          // 返回 pos 的前驅結點
    bool IsEmpty() { return length_ == 0; } // 判空
    size_t Length() { return length_; }     // 獲取數據長度
    ElemType &operator[](size_t pos);       // 取值
    bool Insert(size_t pos, ElemType elem); // 在 pos 插入一個元素
    bool TailInsert(ElemType elem);         // 尾插法插入元素
    bool HeadInsert(ElemType elem);         // 頭插法插入元素
    ElemType Pop(size_t pos);               // 彈出 pos 位置的元素
    bool Delete(size_t pos);                // 刪除 pos 位置的元素
};

template <typename ElemType>
LinkList<ElemType>::LinkList() : length_(0)
{
    head_ = new LNode(); // 創建頭結點
    if (!head_)
        throw std::bad_alloc(); // 分配內存失敗
    head_->next = nullptr;
}

template <typename ElemType>
LinkList<ElemType>::~LinkList()
{
    LNode *p = head_->next;
    while (p)
    {
        head_->next = p->next;
        delete p;
        p = head_->next;
    }
    delete head_;
}

template <typename ElemType>
typename LinkList<ElemType>::LNode *LinkList<ElemType>::GetPreNode(size_t pos)
{
    if (pos < 0 || pos > length_)
        throw std::out_of_range("out of range at position " + pos);
    LNode *p = head_;
    int pos_ = pos - 1;
    for (int i = -1; i < pos_; i++)
        p = p->next;
    return p;
}

template <typename ElemType>
bool LinkList<ElemType>::Insert(size_t pos, ElemType elem)
{
    LNode *pre = GetPreNode(pos);
    if (!pre)
        return false;
    LNode *node = new LNode();
    node->data = elem;
    node->next = pre->next;
    pre->next = node;
    length_++;
}

template <typename ElemType>
bool LinkList<ElemType>::HeadInsert(ElemType elem)
{
    LNode *node = new LNode();
    if (!node)
        return false;
    node->data = elem;
    node->next = head_->next; // 原 pos 位的結點變成插入結點的后繼
    head_->next = node;       // pos 位的前驅結點指向插入結點
    length_++;
    return true;
}

template <typename ElemType>
bool LinkList<ElemType>::TailInsert(ElemType elem)
{
    LNode *last = GetPreNode(Length()); // 找到最后一個元素
    LNode *node = new LNode();
    if (!node)
        return false;
    node->data = elem;
    node->next = nullptr;
    last->next = node;
    length_++;
    return true;
}

template <typename ElemType>
ElemType &LinkList<ElemType>::operator[](size_t pos)
{
    LNode *pre = GetPreNode(pos);
    if (!pre || !pre->next)
        throw std::out_of_range("out of range at position " + pos);
    return pre->next->data;
}

template <typename ElemType>
ElemType LinkList<ElemType>::Pop(size_t pos)
{
    LNode *pre = GetPreNode(pos);
    if (!pre || !pre->next)
        throw std::out_of_range("out of range at position " + pos);
    ElemType elem = pre->next->data; // 保存原始值
    LNode *tmp = pre->next->next;    // pos + 1 號結點
    delete pre->next;                // 刪除 pos 號結點
    pre->next = tmp;
    length_--;
    return elem;
}

template <typename ElemType>
bool LinkList<ElemType>::Delete(size_t pos)
{
    try
    {
        Pop(pos);
        return true;
    }
    catch (const std::out_of_range)
    {
        return false;
    }
}

// ------------ End of LinkList ----------------------

class StudentManager
{
private:
    LinkList<Student> data_;
    string data_path_ = "./students.txt";

public:
    StudentManager() = default;
    ~StudentManager() = default;
    StudentManager(const StudentManager &) = delete;
    StudentManager &operator=(const StudentManager &) = delete;

    void Run();                    // 運行
    void ReadDataWithHeadInsert(); // 前插法構建數據鏈表
    void ReadDataWithTailInsert(); // 后插法構建數據鏈表
    void ClearScreen();            // 清屏
    void ShowMenu();               // 顯示主目錄
    void ShowAllStudentInfo();     // 顯示所有學生信息
    void InsertStudent();          // 插入一個學生
    void DeleteStudent();          // 刪除學生
    void SearchByName();           // 根據姓名查找學生
    void ShowListStatus();         // 顯示鏈表狀態
};

void StudentManager::ClearScreen()
{
#if defined(__linux__)
    system("clear");
#elif defined(_WIN32)
    system("cls");
#endif
}

void StudentManager::ReadDataWithHeadInsert()
{
    ifstream file(data_path_);
    if (!file)
    {
        cout << "打開文件失敗: " << data_path_ << endl;
        return;
    }
    string line;
    getline(file, line); // 首行丟棄
    while (!file.eof())
    {
        Student stu;
        file >> stu.id >> stu.name >> stu.score;
        data_.HeadInsert(stu);
    }
    cout << "數據讀取完成!" << endl;
}

void StudentManager::ReadDataWithTailInsert()
{
    ifstream file(data_path_);
    if (!file)
    {
        cout << "打開文件失敗: " << data_path_ << endl;
        return;
    }
    string line;
    getline(file, line);
    while (!file.eof())
    {
        Student stu;
        file >> stu.id >> stu.name >> stu.score;
        data_.TailInsert(stu);
    }
    cout << "數據讀取完成!" << endl;
}

void StudentManager::ShowAllStudentInfo()
{
    if (data_.IsEmpty())
    {
        cout << "沒有學生信息可以刪除!" << endl;
        return;
    }
    cout << "序號\t姓名\t學號\t\t成績" << endl;
    for (size_t i = 0; i < data_.Length(); ++i)
        cout << "[" << i + 1 << "]\t" << data_[i].name << '\t' << data_[i].id << '\t' << data_[i].score << endl;
}

void StudentManager::InsertStudent()
{
    size_t pos;
    cout << "插入位置(1-" << data_.Length() << "): ";
    cin >> pos;
    if (pos < 1 || pos > data_.Length())
    {
        cout << "輸入有誤!" << endl;
        return;
    }
    string name;
    size_t id;
    Score score;
    cout << "輸入學號: ";
    cin >> id;
    cout << "輸入姓名: ";
    cin >> name;
    cout << "輸入成績: ";
    cin >> score;
    Student stu = {name, id, score};
    if (data_.Insert(pos - 1, stu))
        cout << "插入數據成功!" << endl;
    else
        cout << "插入數據失敗!" << endl;
}

void StudentManager::DeleteStudent()
{
    if (data_.IsEmpty())
    {
        cout << "沒有學生信息可以刪除!" << endl;
        return;
    }

    ShowAllStudentInfo();
    size_t index;
    cout << "輸入學生編號: ";
    cin >> index;
    if (index < 1 || index > data_.Length())
    {
        cout << "編號有誤!" << endl;
        return;
    }
    if (data_.Delete(index - 1))
        cout << "刪除成功!" << endl;
    else
        cout << "刪除失敗!" << endl;
}

void StudentManager::SearchByName()
{
    string name;
    cout << "輸入學生姓名:";
    cin >> name;
    bool is_found = false; // 標記是否找到信息
    for (size_t i = 0; i < data_.Length(); ++i)
        if (data_[i].name == name)
        {
            cout << "姓名: " << name << "\t學號: " << data_[i].id << "\t成績: " << data_[i].score << endl;
            is_found = true;
        }
    if (!is_found)
        cout << "查無此人: " << name << endl;
}

void StudentManager::ShowListStatus()
{
    cout << "學生數量: " << data_.Length() << endl;
}

void StudentManager::ShowMenu()
{
    ClearScreen();
    cout << endl;
    cout << "=================" << endl;
    ShowListStatus();
    cout << "=================" << endl;
    cout << "1.讀取文件(前插法)" << endl;
    cout << "2.讀取文件(后插法)" << endl;
    cout << "3.全部學生信息" << endl;
    cout << "4.查找學生信息" << endl;
    cout << "5.刪除學生信息" << endl;
    cout << "6.插入學生信息" << endl;
    cout << "7.退出程序" << endl;
    cout << "=================" << endl;
    cout << endl;

    int choose;
    cout << "輸入序號: ";
    cin >> choose;
    if(cin.fail())
    {
        cin.clear();
        cin.ignore(100, '\n'); // 清除 cin 的fail的狀態, 否則無限循環
        return;
    }
    switch (choose)
    {
    case 1:
        ReadDataWithHeadInsert();
        break;
    case 2:
        ReadDataWithTailInsert();
        break;
    case 3:
        ClearScreen();
        ShowAllStudentInfo();
        break;
    case 4:
        SearchByName();
        break;
    case 5:
        ClearScreen();
        DeleteStudent();
        break;
    case 6:
        InsertStudent();
        break;
    case 7:
        exit(0);
    default:
        cout << "序號有誤!" << endl;
    }

    // 故意暫停, 回車繼續
    cout << "\n按 Enter 繼續...";
    cin.get(); // 讀取緩沖區的換行符
    cin.get(); // 等待用戶按 Enter
}

void StudentManager::Run()
{
    while (true)
    {
        ShowMenu();
    }
}

// -------------- End of StudentManager -------------

int main(int argc, char const *argv[])
{
    StudentManager manager;
    manager.Run();
}


免責聲明!

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



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