目录
前言:
一:单个节点的设计和主逻辑
结点设计
主逻辑
二:接口实现
(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;elsereturn 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;elsereturn 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; }