目录
前言:
一:单个节点的设计和主逻辑
结点设计
主逻辑
二:接口实现
(1)生成一个新的结点
(2)增加信息
(3)打印信息
(4)查找
(5)删除信息
(6)修改信息
(7)排序
插入排序
快速排序
(8)已有数据读取
(9)更新数据录入
三:全部代码
contact.h(声明)
contact.c(接口)
test.c(主逻辑)
前言:
本文使用的存储结构是不带哨兵位链表,还包含了文件操作(读取和录入),有类似课程设计的同学可能需要(完整代码在最后)。
链表(知道基础概念,清楚形参实参关系的可以不看):
https://blog.csdn.net/2301_76269963/article/details/129586021?spm=1001.2014.3001.5502
一:单个节点的设计和主逻辑
结点设计
需求:一个人要包含姓名、年龄、性别、电话号码、地址这些信息,把这些信息封装成一个结构体。
要让每个人的数据链接成一个整体,节点除了个人信息之外还要保存下个结点的地址。
主逻辑
(1)每次开始都从文件中读取数据
(2)通讯录的功能包括:①增加数据
②依据名字删除数据
③依据名字查找数据
④依据名字修改数据
⑤依据年龄排序数据
⑥打印数据
(3)退出后重新录入数据
二:接口实现
(1)生成一个新的结点
//生成一个新节点 Node* BuyNewNode() { Node* NewNode = (Node*)malloc(sizeof(Node)); if (NewNode == NULL) { printf("malloc error\n"); exit(-1); } NewNode->next = NULL; printf("请输入名字:"); scanf("%s", NewNode->data.name); printf("请输入年龄:"); scanf("%d", &NewNode->data.age); printf("请输入性别:"); scanf("%s", NewNode->data.sex); printf("请输入电话:"); scanf("%s", NewNode->data.tele); printf("请输入地址:"); scanf("%s", NewNode->data.addr); return NewNode; }
(2)增加信息
//增加信息,可能要更改头指针,传二级指针修改一级指针 void AddContact(Node** pphead) { //生成新节点 Node* NewNode = BuyNewNode(); //单独处理空的情况 if (*pphead == NULL) { *pphead = NewNode; return; } else { NewNode->next = *pphead; *pphead = NewNode; } }
图解:
(3)打印信息
//打印信息 void PrintContact(Node* phead) { //注意打印格式,加-表示字符左对齐,数字表示最小字符数(不足补空格) printf("%-20s\t%-5s\t%-5s\t%-20s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址"); while (phead != NULL) { printf("%-20s\t%-5d\t%-5s\t%-20s\t%-20s\n", phead->data.name,phead->data.age, phead->data.sex,phead->data.tele,phead->data.addr); //迭代 phead = phead->next; } }
(4)查找
注意:后续的删除和修改都需要用到查找,为增强代码复用性,查找函数返回结点的地址,如果未查找到就返回空。
//查找,第二个参数为名字字符串首地址 Node* FindContact(Node* phead,char* name) { //遍历链表 while (phead) { //strcmp()函数用于字符串比较,相同返回0,否则返回非0值 if (strcmp(phead->data.name, name) == 0) { return phead; } phead = phead->next; } //没找到返回空 return NULL; }
(5)删除信息
先查找,找到待删除结点地址,如果待删除结点为头节点,需要更新头指针;
否则就需要找到待删除结点的前一个结点,前一个结点指针域指向待删除结点的下一个结点,然后释放待删除结点。
//删除信息,可能要更改头指针,传二级指针修改一级指针 void DelContact(Node** pphead) { //信息为空不能删除 assert(*pphead != NULL); //查找 char name[max_name]; printf("请输入要删除联系人的名字:"); scanf("%s", name); Node* pos = FindContact(*pphead,name); if (pos == NULL) { printf("查找失败\n"); return; } //如果pos是第一个节点 if (pos == *pphead) { //找到下一个 Node* NEXT = pos->next; free(pos); *pphead = NEXT; } else { //找到前一个 Node* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); } printf("删除成功\n"); }
图解:
(6)修改信息
先查找到目标结点,然后进行修改。
//修改信息 void ModifyContact(Node* phead) { //查找 char name[max_name]; printf("请输入要修改联系人的名字:"); scanf("%s", name); Node* pos = FindContact(phead, name); if (pos == NULL) { printf("查找失败\n"); return; } else { printf("找到了,进行修改\n"); printf("请输入名字:"); scanf("%s", pos->data.name); printf("请输入年龄:"); scanf("%d", &pos->data.age); printf("请输入性别:"); scanf("%s", pos->data.sex); printf("请输入电话:"); scanf("%s", pos->data.tele); printf("请输入地址:"); scanf("%s", pos->data.addr); } }
(7)排序
这里给两种实现方式,第一种使用插入排序(方便理解),第二种使用快速排序(效率更快,有空间消耗,代码量更多)。
插入排序
插入排序的核心思想是:将一个未排序的元素插入到已排序的子序列中,使得插入后依然保持有序。具体实现时,我们可以从第二个元素开始,将其与前面已排序序列的元素比较,找到其正确的插入位置,然后插入即可。在插入过程中,需要将已排序序列中比该元素大的元素后移,为该元素腾出插入位置(看一遍就可以看代码和图解)。
//交换数据 void swap(Node* p1, Node* p2) { struct PepInfo tmp = p1->data; p1->data = p2->data; p2->data = tmp; } //进行排序 void sort(Node* phead) { //空不排序 assert(phead != NULL); Node* begin = phead; Node* end = phead->next; while (end) { while (begin != end) { if (begin->data.age > end->data.age) { swap(begin,end); } begin = begin->next; } //更新 end = end->next; begin = phead; } }
图解:
快速排序
这里就不展开讲快速排序的思想了,也不建议用快速排序,一般通讯录这样的数据量用插入排序就足够了,感兴趣的同学可以看链接。
快速排序链接:https://blog.csdn.net/2301_76269963/article/details/130473353?spm=1001.2014.3001.5502
先将链表中的数据拷贝到数组中,对数组进行排序,再把数组数据拷贝回链表。
//交换数据 void swap(PepInfo* p1, PepInfo* p2) { PepInfo tmp = *p1; *p1 = *p2; *p2 = tmp; } // 三数取中 int GetMidIndex(PepInfo* a, int left, int right) { int midi = left + (right - left) / 2; if (a[midi].age > a[right].age) { if (a[midi].age < a[left].age) return midi; else if (a[right].age > a[left].age) return right; else return left; } else //a[right] > a[mid] { if (a[midi].age > a[left].age) return midi; else if (a[left].age < a[right].age) return left; else return right; } } //前后指针法 int partion3(PepInfo* a, int left, int right) { int midi = GetMidIndex(a, left, right); swap(&a[midi], &a[left]); int keyi = left; int prev = left; int cur = left + 1; while (cur <= right) { //++prev和cur相等,无效交换,不换 if (a[cur].age < a[keyi].age && ++prev != cur) { swap(&a[prev], &a[cur]); } cur++; } swap(&a[keyi], &a[prev]); return prev; } //快速排序 void QuickSort(PepInfo* a, int left, int right) { //如果left>right,区间不存在 //left==right,只有一个元素,可以看成是有序的 if (left >= right) { return; } else { //单次排序 int keyi = partion3(a, left, right); //分成左右区间,排序左右区间 QuickSort(a, left, keyi - 1); QuickSort(a, keyi + 1, right); } } // 链表快速排序函数 void sortList(Node* phead) { // 处理空或单节点链表的情况 if (!phead || !phead->next) { return; } //求链表长度 int len = 0; Node* cur = phead; while (cur) { len++; cur = cur->next; } // 将链表转换为数组 PepInfo* arr = malloc(len * sizeof(PepInfo)); if (arr == NULL) { printf("malloc error\n"); exit(-1); } cur = phead; for (int i = 0; i < len; i++) { arr[i] = cur->data; cur = cur->next; } // 快速排序 QuickSort(arr, 0, len - 1); // 将数组转换为链表 cur = phead; for (int i = 0; i < len; i++) { cur->data = arr[i]; cur = cur->next; } free(arr); }
(8)已有数据读取
①生成一个新结点,将读取的数据存入新结点中。
②第一次读取时链表为空,修改头指针。
③后面的新结点链接到链表的尾部。
(注意:读模式打开文件需要文件夹中有这个文件,第一次需要手动创建一个data.txt文件)
//已有数据读取,可能要更改头指针,传二级指针修改一级指针 void StartFileEntry(Node** pphead) { FILE* pf = fopen("data.txt", "rb"); if (pf == NULL) { printf("打开文件失败\n"); exit(-1); } //保存读取到的数据 PepInfo* tmp = (PepInfo*)malloc(sizeof(PepInfo)); if (tmp == NULL) { printf("malloc error\n"); exit(-1); } //先读取一次 fread(tmp, sizeof(PepInfo), 1, pf); //保存尾部节点地址 Node* tail = NULL; //读取到结尾时feof()返回0,未读取到结尾返回非0值 while (!feof(pf)) { //生成新节点 Node* NewNode = (Node*)malloc(sizeof(Node)); if (NewNode == NULL) { printf("malloc error\n"); exit(-1); } NewNode->data = *tmp; NewNode->next = NULL; //第一次头为空,更新 if (*pphead == NULL) { *pphead = NewNode; tail = *pphead; } //头不为空,新节点链接到尾后面,尾指针更新 else { tail->next = NewNode; tail = tail->next; } //继续读取 fread(tmp, sizeof(PepInfo), 1, pf); } fclose(pf); }
(9)更新数据录入
链表为空,直接返回;否则遍历链表,依次录入数据。
//新数据录入 void UpdateFileEntry(Node* phead) { FILE* pf = fopen("data.txt", "wb"); if (phead == NULL) { return; } else { while (phead) { fwrite(&(phead->data), sizeof(PepInfo), 1, pf); phead = phead->next; } } fclose(pf); }
三:全部代码
contact.h(声明)
#pragma once #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> //方便后面更改这些信息的最大容量 #define max_name 20 #define max_tele 20 #define max_addr 20 #define max_sex 10 //个人信息 typedef struct PepInfo { char name[max_name]; int age; char sex[max_sex]; char tele[max_tele]; char addr[max_addr]; }PepInfo; typedef struct Node { //个人信息 struct PepInfo data; //指针域 struct Node* next; }Node; //生成一个新节点 Node* BuyNewNode(); //增加信息,可能要更改头指针,传二级指针修改一级指针 void AddContact(Node** pphead); //打印信息 void PrintContact(Node* phead); //查找 Node* FindContact(Node* phead, char* name); //删除信息,可能要更改头指针,传二级指针修改一级指针 void DelContact(Node** pphead); //修改信息 void ModifyContact(Node* phead); //进行排序 // 链表快速排序函数 void sortList(Node* phead); //插入排序 void sort(Node* phead); //已有数据读取,可能要更改头指针,传二级指针修改一级指针 void StartFileEntry(Node** pphead); //更新数据录入 void UpdateFileEntry(Node* phead);
contact.c(接口)
#define _CRT_SECURE_NO_WARNINGS 1 #include "contact.h" //生成一个新节点 Node* BuyNewNode() { Node* NewNode = (Node*)malloc(sizeof(Node)); if (NewNode == NULL) { printf("malloc error\n"); exit(-1); } NewNode->next = NULL; printf("请输入名字:"); scanf("%s", NewNode->data.name); printf("请输入年龄:"); scanf("%d", &NewNode->data.age); printf("请输入性别:"); scanf("%s", NewNode->data.sex); printf("请输入电话:"); scanf("%s", NewNode->data.tele); printf("请输入地址:"); scanf("%s", NewNode->data.addr); return NewNode; } //增加信息,可能要更改头指针,传二级指针修改一级指针 void AddContact(Node** pphead) { //生成新节点 Node* NewNode = BuyNewNode(); //单独处理空的情况 if (*pphead == NULL) { *pphead = NewNode; return; } else { NewNode->next = *pphead; *pphead = NewNode; } } //打印信息 void PrintContact(Node* phead) { //注意打印格式,加-表示字符左对齐,数字表示最小字符数(不足补空格) printf("%-20s\t%-5s\t%-5s\t%-20s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址"); while (phead != NULL) { printf("%-20s\t%-5d\t%-5s\t%-20s\t%-20s\n", phead->data.name,phead->data.age, phead->data.sex,phead->data.tele,phead->data.addr); //迭代 phead = phead->next; } } //查找,第二个参数为名字字符串首地址 Node* FindContact(Node* phead,char* name) { //遍历链表 while (phead) { //strcmp()函数用于字符串比较,相同返回0,否则返回非0值 if (strcmp(phead->data.name, name) == 0) { return phead; } phead = phead->next; } //没找到返回空 return NULL; } //删除信息,可能要更改头指针,传二级指针修改一级指针 void DelContact(Node** pphead) { //断言,信息为空不能删除 assert(*pphead != NULL); //查找 char name[max_name]; printf("请输入要删除联系人的名字:"); scanf("%s", name); Node* pos = FindContact(*pphead,name); if (pos == NULL) { printf("查找失败\n"); return; } //如果pos是第一个节点 if (pos == *pphead) { //找到下一个 Node* NEXT = pos->next; free(pos); *pphead = NEXT; } else { //找到前一个 Node* prev = *pphead; while (prev->next != pos) { prev = prev->next; } prev->next = pos->next; free(pos); } printf("删除成功\n"); } //修改信息 void ModifyContact(Node* phead) { //查找 char name[max_name]; printf("请输入要修改联系人的名字:"); scanf("%s", name); Node* pos = FindContact(phead, name); if (pos == NULL) { printf("查找失败\n"); return; } else { printf("找到了,进行修改\n"); printf("请输入名字:"); scanf("%s", pos->data.name); printf("请输入年龄:"); scanf("%d", &pos->data.age); printf("请输入性别:"); scanf("%s", pos->data.sex); printf("请输入电话:"); scanf("%s", pos->data.tele); printf("请输入地址:"); scanf("%s", pos->data.addr); } } 交换数据 //void swap(PepInfo* p1, PepInfo* p2) //{ // PepInfo tmp = *p1; // *p1 = *p2; // *p2 = tmp; //} // 三数取中 //int GetMidIndex(PepInfo* a, int left, int right) //{ // int midi = left + (right - left) / 2; // // if (a[midi].age > a[right].age) // { // if (a[midi].age < a[left].age) // return midi; // else if (a[right].age > a[left].age) // return right; // else // return left; // } // else // a[right] > a[mid] // { // if (a[midi].age > a[left].age) // return midi; // else if (a[left].age < a[right].age) // return left; // else // return right; // } //} // 前后指针法 //int partion3(PepInfo* a, int left, int right) //{ // int midi = GetMidIndex(a, left, right); // swap(&a[midi], &a[left]); // // int keyi = left; // int prev = left; // int cur = left + 1; // while (cur <= right) // { // //++prev和cur相等,无效交换,不换 // if (a[cur].age < a[keyi].age && ++prev != cur) // { // swap(&a[prev], &a[cur]); // } // cur++; // } // swap(&a[keyi], &a[prev]); // return prev; //} // 快速排序 //void QuickSort(PepInfo* a, int left, int right) //{ // //如果left>right,区间不存在 // //left==right,只有一个元素,可以看成是有序的 // if (left >= right) // { // return; // } // else // { // //单次排序 // int keyi = partion3(a, left, right); // //分成左右区间,排序左右区间 // QuickSort(a, left, keyi - 1); // QuickSort(a, keyi + 1, right); // } //} // 链表快速排序函数 //void sortList(Node* phead) //{ // // 处理空或单节点链表的情况 // if (!phead || !phead->next) // { // return; // } // // //求链表长度 // int len = 0; // Node* cur = phead; // while (cur) // { // len++; // cur = cur->next; // } // // // 将链表转换为数组 // PepInfo* arr = malloc(len * sizeof(PepInfo)); // if (arr == NULL) // { // printf("malloc error\n"); // exit(-1); // } // cur = phead; // for (int i = 0; i < len; i++) // { // arr[i] = cur->data; // cur = cur->next; // } // // // 快速排序 // QuickSort(arr, 0, len - 1); // // // 将数组转换为链表 // cur = phead; // for (int i = 0; i < len; i++) // { // cur->data = arr[i]; // cur = cur->next; // } // // free(arr); //} //交换数据 void swap(Node* p1, Node* p2) { struct PepInfo tmp = p1->data; p1->data = p2->data; p2->data = tmp; } //进行排序 void sort(Node* phead) { //空不排序 assert(phead != NULL); Node* begin = phead; Node* end = phead->next; while (end) { while (begin != end) { if (begin->data.age > end->data.age) { swap(begin,end); } begin = begin->next; } end = end->next; begin = phead; } } //已有数据读取,可能要更改头指针,传二级指针修改一级指针 void StartFileEntry(Node** pphead) { FILE* pf = fopen("data.txt", "rb"); if (pf == NULL) { printf("打开文件失败\n"); exit(-1); } //保存读取到的数据 PepInfo* tmp = (PepInfo*)malloc(sizeof(PepInfo)); if (tmp == NULL) { printf("malloc error\n"); exit(-1); } //先读取一次 fread(tmp, sizeof(PepInfo), 1, pf); //保存尾部节点地址 Node* tail = NULL; //读取到结尾时feof()返回0,未读取到结尾返回非0值 while (!feof(pf)) { //生成新节点 Node* NewNode = (Node*)malloc(sizeof(Node)); if (NewNode == NULL) { printf("malloc error\n"); exit(-1); } NewNode->data = *tmp; NewNode->next = NULL; //第一次头为空,更新 if (*pphead == NULL) { *pphead = NewNode; tail = *pphead; } //头不为空,新节点链接到尾后面,尾指针更新 else { tail->next = NewNode; tail = tail->next; } //继续读取 fread(tmp, sizeof(PepInfo), 1, pf); } fclose(pf); } //新数据录入 void UpdateFileEntry(Node* phead) { FILE* pf = fopen("data.txt", "wb"); if (phead == NULL) { return; } else { while (phead) { fwrite(&(phead->data), sizeof(PepInfo), 1, pf); phead = phead->next; } } fclose(pf); }
test.c(主逻辑)
#define _CRT_SECURE_NO_WARNINGS 1 #include "contact.h" void menu() { printf("********************************\n"); printf("***** 1.增加 2.删除 ******\n"); printf("***** 3.查找 4.修改 ******\n"); printf("***** 5.排序 6.打印 ******\n"); printf("***** 0.退出 ******\n"); printf("********************************\n"); } int main() { Node* head = NULL; int input = 0; //原信息录入,这里用读模式打开,第一次需要手动创建文件 StartFileEntry(&head); do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: AddContact(&head); printf("增加成功\n"); break; case 2: DelContact(&head); break; case 3: { char name[max_name]; printf("请输入要查找联系人的名字:"); scanf("%s", name); Node* pos = FindContact(head, name); if (pos == NULL) { printf("查找失败\n"); } else { printf("找到了\n"); printf("%-20s\t%-5d\t%-5s\t%-20s\t%-20s\n", pos->data.name, pos->data.age, pos->data.sex, pos->data.tele, pos->data.addr); } break; } case 4: ModifyContact(head); break; case 5: sort(head); printf("排序成功\n"); break; case 6: PrintContact(head); break; case 0: printf("退出\n"); break; default: printf("非法输入,重新输入\n"); break; } } while (input); //新信息录入 UpdateFileEntry(head); return 0; }