单链表的应用(电话本)

单链表

单链表的定义就不说了, 很简单, 请自行百度; 那么从今天的主题<单链表的应用>入手; 利用单链表实现电话本的模拟程序:定义单链表的数据类型,将头插法和尾插法、插入、逆置、删除、查找、修改、计数、输出等操作都定义成子函数的形式,最后在主函数中调用它( 兄弟们调用就不弄了, 自行测试, 增加动手能力 )
整个程序最主要的步骤还是插入, 查找, 删除操作, 其他操作都可以围绕这三个操作来进行, 其实单链表无论是什么操作都可以总结为指针的操作, 无非是指针的指向, 通过指针的指向就能完成很多步骤, 但是在单链表中由于指针是单向的, 不像双链表那样可以双向操作, 所以操作起来有点不太方便(其实还是很简单的, 只是说了吓唬人的)
此处做注释: 链表的head节点数据域不存数据, head所在的位置是-1, 链表中元素是从0号开始
那么我就先从insert开始:

insert操作
void insert(int index,LNode *L1,LNode *L2);
insert就是在特定的位置(index)把L2节点连接到L1上面, 所以就有如下实现方式:

void insert(int index,LNode *L1,LNode *L2){
	for(int i=0;(i<index)&&(L1!=NULL)&&(L2!=NULL);i++,L1=L1->next);
	L2->next=L1->next;L1->next=L2;
} 

L1与L2需要判断是否为空(避免引用非法指针), 上面的for操作主要是将指针L1移动到需要插入的位置的前一个位置, 首先将L2的next指针存入原本L1的后一个位置的地址, 然后将L1的next指针存入L2的地址, 这两步操作不能颠倒, 颠倒, 原本链表中L1后面部分的链表结构将会丢失; (请细细理解)
在创建链表的过程中我们可以选择头插法或者尾插法进行创建链表;
所谓头插法简单的来说就是每次插入操作总是把插入节点的位置定位0号, 头插法的意义就在于我们可以很容易完成逆置的操作, 头插法代码如下:

void insertHead(LNode *L1,LNode *L2){
		insert(0,L1,L2);
}

头插法就是在0号位置把L2插入到L1的后面, 如果将头插法应用到创建链表, 那么此处的L1便是head

尾插法就是每次在尾部插入节点, 插入的位置总是末尾; 代码如下:

void insertTail(LNode *L1,LNode *L2,int t){
		insert(t,L1,L2);
}

此处L1, L2的含义与头插法中的含义一致, t表示末尾的位置, 末尾需要传入参数来确定, 待会在main函数中测试再进行说明, 两个插入的区别就在于位置的变化, 由两种插入方面所形成的链表顺序是相反的

逆置操作
上面提到逆置操作需要用到头插法, 因为每次节点传入的insertHead中, 我总是把节点插入到0号位置, 也就是head后面, 一个正序的链表, 我把它的节点按顺序不断拆下来, 同时将每个拆下来的节点连接到head的0号位上面, 而不是又按照原有的顺序进行顺序连接(表面上看好像连和没连一样, 这里你需要画图好好思考), 举个例子: head 0 1 2 3的顺序表, 0拆下连接到head上面, 1拆下连接到head上面, 此时0就连接到1号上面, 顺序为head 1 0, 同理, 2拆下连接到head上面, 1又连接到2上面, 顺序为head 2 1 0 ,最后整个过程重复, 得到的顺序就是head 3 2 1 0, 然后就可以做到把一个正序的链表变为逆序的
话不多说, 上代码:

void convert(LNode *L){//此处逆置是对整个链表逆置, L传入的是head
	LNode* p=L->next;
	L->next=NULL;
	LNode* q;
	int t_size=size;
	while(t_size>0){
		q=p->next;
		p->next=NULL;
		insert(0,L,p);
		p=q;t_size--;
	}
}

LNode* p=L->next;L->next=NULL;LNode* q;是将head分离, while内部的操作主要是通过不断迭代的方式, 做到将第一个节点与第二个节点进行分离, 以达到将在0号位插入的效果

删除, 查找, 修改
这三步操作是围绕查找来, 由于单链表结构的特点, 所以只能做到顺序查找(可采用平衡二叉搜索树, 实现链表的O(logn)的复杂度)
上代码:

LNode *find(LNode *L,char *e){
	while(L!=NULL){//避免引用非法指针
		if(strcmp(L->data.name,e)==0)
			return L;
		L=L->next;
	}
	return head; 
}

传入head节点, 以及所要查找的元素e(使用指针, 其效果与char e[]一致), 不断比较, 成功则返回所指向的节点, 失败则返回head

删除: 删除我做了修改, 让它具有两个功能–删除特定元素, 删除整个链表(其实还是为了偷懒, 少写代码)

void remove(LNode *L,LNode *LTarget_value){
	while(L->next!=NULL){
		if((L->next==LTarget_value)&&(LTarget_value!=NULL)){//用于删除特定元素
			LNode* p=L->next;L->next=p->next;//将所要删除的节点进行分离操作
			size--;//删除一个人就需要总人数减一次
			delete p;return;//若删除p立马退出 
		}else if(LTarget_value==NULL){//用于删除整个链表
			LNode* p=L->next;L->next=p->next;
			delete p;//分离一个节点进行一次删除,直到删除到只剩下head节点才停止
		}
		if(LTarget_value!=NULL) L=L->next;//只有在删除特定元素的时候才进行L的移动 
	}
	if(LTarget_value==NULL) delete L;//只有在删除整个链表才删除head
}

修改操作: 修改可针对特定元素, 此处只做简单说明
上代码:

void revise(LNode *L,char *e,char *Ae,int i){
	LNode *p=find(L,e);
	if(p->data.name==NULL && p->data.num=NULL )
		return;//表示没有找到要修改的元素,p现在是head,head里面没有存数据, 直接跳出程序
	if(i==1)	strcpy(p->data.name,Ae);
	else if(i==2)	strcpy(p->data.num,Ae);
}

L表示head, e表示要查找的目标元素, Ae表示替换e, i表示选择替换name, 还是替换num

输出
输出就很简单了, 不做叙述

void display(LNode *L){
	while(L!=NULL){
		printf("%s %s\n",L->data.name,L->data.num);
		L=L->next;
	}
}

此次测试所用的结构体

struct DataType{
	char name[10];
	char num[12]; 
}; 
struct LNode{
	LNode *next;
	DataType data;
};

两个全局变量

LNode *head=new LNode();//head节点
int size=0;//用作计数, 计算存入多少联系人

main函数 的测试代码

int main(){
	int t_size;
	int sum=0,i;
	char na[10],nu[12];  
	printf("输入需要创建的电话本大小以及1.头插OR2.尾插\n");
	scanf("%d%d",&t_size,&i);
	while(sum<t_size){
		printf("输入名字,号码\n");fflush(stdin);
		scanf("%s%s",na,nu);
		LNode *s=new LNode();s->next=NULL;
		strcpy(s->data.name,na);
		strcpy(s->data.num,nu);
		size++; //每输入一次,size进行一次增加, 只有在输入才有人数的增加
		if(i==1){//输入时选择头插法,还是尾插法
			insertHead(head,s);
		}else if(i==2){
			insertTail(head,s,sum);
		}++sum;
	}
	display(head);
//	printf("输入要查找的元素\n");fflush(stdin); 
//	scanf("%s",na);
//	printf("%s\n",find(head,na)->data.name);
//	printf("删除链表中元素\n");fflush(stdin); 
//	scanf("%s",na);
//	if(i==1){
//		puts("输入要删除的元素");scanf("%s",na);
//		remove(head,find(head,na));
//	} 
//	display(head);
//	puts("若号码以及名字都要修改那么你可选择直接删除");
//	puts("需要修改的元素,修改后的值,1.修改名字,2.修改号码");
//	scanf("%s%s%d",na,nu,&i);
//	revise(head,na,nu,i);
	puts("输入在几号位置进行插入操作,插入元素为,0号是首元素");
	scanf("%d",&i);
	printf("输入名字,号码\n");fflush(stdin);
	scanf("%s%s",na,nu);
	LNode *s=new LNode();s->next=NULL;
	strcpy(s->data.name,na);
	strcpy(s->data.num,nu);
	insert(i,head,s);
//	convert(head);
	display(head);
	remove(head,NULL); 
	return 0;
}

代码进行拼凑就可使用, 可能存在异常, 但是主要还是阐述思想, 上面注释部分为测试每个功能的代码, 最后还需做修改, 写的不好, 还有很多地方需要改进, 就不做修改了, 界面做的很简陋, 整个程序最核心的还是insert, find, remove, 理解头插法与尾插法的区别, 有问题还请在下方留言

  • 14
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一个模块——主函数main()的功能是:根据选的选项调用各函数,并完成相应的功能。 
   第二个模块——Menu()的功能是:显示提示选。 
   第三个模块——Quit()的功能是:退出选。 
   第四个模块——Create()的功能是:创建新的数据记录。 
   第五个模块——Add()的功能是:增加新的数据记录,并返回选。 
   第六个模块——Find()的功能是:按要求查询相关的信息,如果找到了,则显示该信息,如果未找到,则提示文件中没有该信息,并返回选。 
   第七个模块——Alter()[的功能是:修改某条记录的信息,如果未找到要修改的记录,则提示系统中无此记录,并返回选。 
   第八个模块——Delete()的功能是:删除某条记录,如果未找到要删除的记录,则提示通讯录中没有,并返回选。 
   第九个模块——List()的功能是:显示所有记录。 一、用链表或者顺序表实现以下系统,完成线性表的建立(至少包括10个结点),以及线性表中信息(结点)的插入、查找、删除、修改、输出等操作,具体的模块要求见上方的“总的模块要求”。建议用“文件”存储数据。 1.通讯录管理系统的设计与实现 (1)通讯者信息包括:编号(char num[10])、姓名(char name[10])、性别(char sex[10])、电话(char phone[20]) (2)除了总的模块要求外,还需统计通讯录中男性人数及女性人数,并求出通讯录中的第一个模块——主函数main()的功能是:根据选的选项调用各函数,并完成相应的功能。 
   第二个模块——Menu()的功能是:显示提示选。 
   第三个模块——Quit()的功能是:退出选。 
   第四个模块——Create()的功能是:创建新的数据记录。 
   第五个模块——Add()的功能是:增加新的数据记录,并返回选。 
   第六个模块——Find()的功能是:按要求查询相关的信息,如果找到了,则显示该信息,如果未找到,则提示文件中没有该信息,并返回选。 
   第七个模块——Alter()[的功能是:修改某条记录的信息,如果未找到要修改的记录,则提示系统中无此记录,并返回选。 
   第八个模块——Delete()的功能是:删除某条记录,如果未找到要删除的记录,则提示通讯录中没有,并返回选。 
   第九个模块——List()的功能是:显示所有记录。 一、用链表或者顺序表实现以下系统,完成线性表的建立(至少包括10个结点),以及线性表中信息(结点)的插入、查找、删除、修改、输出等操作,具体的模块要求见上方的“总的模块要求”。建议用“文件”存储数据。 1.通讯录管理系统的设计与实现 (1)通讯者信息包括:编号(char num[10])、姓名(char name[10])、性别(char sex[10])、电话(char phone[20]) (2)除了总的模块要求外,还需统计通讯录中男性人数及女性人数,并求出通讯录中的男女比例。 男女比例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值