目录
前言:
一:单个节点的设计和主逻辑
结点设计
主逻辑
二:接口实现
(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 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 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 data;cur = cur->next;}// 快速排序QuickSort(arr, 0, len - 1);// 将数组转换为链表cur = phead;for (int i = 0; i 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 #include #include #include //方便后面更改这些信息的最大容量#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 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 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 data;//cur = cur->next;//}////// 快速排序//QuickSort(arr, 0, len - 1);////// 将数组转换为链表//cur = phead;//for (int i = 0; i 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;}