链表基础:
对于需要经常插入数据的线性表,一般采用连式存储的链表,链表是采用动态存储分配的一种结构,可以根据需要申请内存单元。
链表的每一个结点都应该包括两个部分,一部分是实际数据,一部分是下一个结点的地址,操作链表时,需要定义一个“头指针“变量(head),该指针指向第一个结点,第一个结点又指向第二个结点。。。一直到最后一个结点,最后一个结点不再指向其他结点,称为”表尾“,在表尾地址放入”NULL“,表示空地址,链表至此结束。
如果链表每个结点只包含一个指针,称为单链表,如果包含两个指针,一个指向下一个结点,一个指向上一个结点,称为双向链表。
在链表中,逻辑上相邻的结点在内存中不一定相邻,逻辑相邻关系通过指针实现,因此对于链表的访问只能从表头逐个查找,一直找到需要的结点为止,而不是像顺序表那样随机访问。
可以用malloc函数动态分配结点的存储空间,删除某个结点时,再用free函数释放结点所占用的内存空间。
链表的基本操作:
链表的基本操作有:添加结点(加到表头,加到表尾),查找,插入,删除,获取链表结点数量等。
首先定义操作链表的头文件 ”ChainList.h“
#include<stdio.h>
#include<stdlib.h>
typedef struct Node //定义结构体作为链表的数据类型
{
DATA data; //保存呢结点的具体数据
struct Node *next; //指针next用来指向下一个结点
} ChainListType;
ChainListType *ChainListAddEnd(ChainListType *head,DATA data);
ChainListType *ChainListAddFirst(ChainListType *head,DATA data);
ChainListType *ChainListFind(ChainListType *head,char *key);
ChainListType *ChainListInsert(ChainListType *head,char *findkey,DATA data);
int ChainListDelete(ChainListType *head, char *key);
int ChainListLength(ChainListType *head);
因为需要动态分配内存的函数malloc,需要头文件stdlib.h。。如果需要做双向链表,需要在结构体中再增加一个指针,指向上一个结点。
链表操作函数保存在文件"ChainList.c"中:
1.添加结点到尾部:
若要在尾部添加结点,需要从头(head)开始逐个往后,直到找到最后一个结点,即表尾,然后将表尾结点的地址部分由NULL更改为新增的结点地址(原来表尾的结点指向新增的结点),然后将新增结点地址部分设置为NULL。
#include<string.h>
//添加结点到尾部
ChainListType *ChainListAddEnd(ChainListType *head,DATA data) //添加节点到链表尾部
{
ChainListType *node,*h; //node为新增结点
if(!(node=(ChainListType *)malloc(sizeof(ChainListType)))) //分配内存失败
{
printf("为保存节点数据申请内存失败\n");
return NULL;
}
node->data=data; //保存数据
node->next=NULL; //设置结点指针为空 ,即表尾
if(head==NULL) //判断头指针是否为空,若为空则指向当前节点
{
head=node; //链表的头指针指向当前结点
return head; //返回头指针
}
//若头指针不为 空
h=head; //保存头指针
while(h->next!=NULL) //循环,使指针一直指到最后,即NULL
h=h->next; //往后一直查找
h->next=node; //原来表尾结点地址指向新增结点地址
return head;
}
对于添加结点到尾部时,如果只知道头指针head,则要从头部一直查找到链表的尾部,如果经常需要这样做,可以定义一个尾指针,指向链表尾部,这样在多次给尾部添加结点时,可省去查找过程。
2.添加结点到首部
在首部添加结点,使新增结点指向头指针所指的结点,head指向新增结点即可。
//添加结点到首部
ChainListType *ChainListAddFirst(ChainListType *head,DATA data) //head为链表头指针,data为结点保存数据
{
ChainListType *node,*h;
if(!(node=(ChainListType *)malloc(sizeof(ChainListType)))) //分配内存失败
{
printf("为保存节点数据申请内存失败\n");
return NULL;
}
node->data=data;
node->next=head; //指向头指针 所指向的结点
head=node; //头指针指向新增结点
return head;
}
3.查找结点
对于链表中保存的数据,可通过关键字查找
//查找结点
ChainListType *ChainListFind(ChainListType *head,char *key)
{
ChainListType *h;
h=head; //保存链表头指针
while(h) //结点有效,进行查找 h的值不为NULL,即未到表尾
{
if(strcmp(h->data.key,key)==0) //结点关键字与传入关键字相同
return h; //找到目标节点,并返回该结点的指针
h=h->next; //没有找到,处理下一个结点
}
return NULL;
}
从头指针开始,如果指针h的值不为NULL,就对结点关键字和查找关键字进行比较,是否相等,一直到表尾,若还没周到返回空指针。
4.插入结点
插入结点即在链表中间部分新增结点,首先找到插入的位置A(逻辑位置),使插入位置A原来指向位置B的结点指向新增结点,而新增结点指向位置B。
//插入结点
ChainListType *ChainListInsert(ChainListType *head,char *findkey,DATA data)
{
ChainListType *node,*node1;
if(!(node=(ChainListType *)malloc(sizeof(ChainListType)))) //分配内存失败
{
printf("为保存节点数据申请内存失败\n");
return 0;
}
node->data=data;
node1=ChainListFind(head,findkey); //findkey为在链表中查找的结点(A)关键字,找到A后在该结点后面添加新增结点
if(node1) //找到要插入的节点
{
node->next=node1->next; //新插入结点指向关键节点A的下一个结点B
node1->next=node; //关键节点A指向新插入的结点
} else{
free(node); //释放内存
printf("没有找到插入的位置");
}
return head;
}
6.删除结点
首先查找到需要删除的结点(delete),然后还需要定义一个指向需要删除结点的前一个结点的指针(A),修改A,使A原来指向需要删除结点(delete)修改为指向删除结点后的那个结点(B),这样完成了逻辑上的删除,然后再释放掉需要删除结点(delete)的内存,这样才删除成功。
A | delete | B |
A | B |
注:若是查找到结点后,直接释放内存,而没有完成逻辑上的删除,则A结点以后无法完成后面链接,B结点也无法被前面的指针链接到,这样的删除是不行的。
//删除节点
int ChainListDelete(ChainListType *head,char *key) //key为需要删除的关键字
{
ChainListType *node,*h; //node保存删除节点的前一个结点
node=h=head; //从头部开始
while(h)
{
if(strcmp(h->data.key,key)==0)
{
node->next=h->next; //使前一个结点指向当前结点的下一个结点,逻辑删除
free(h); //释放内存
return 1;
}else{
node=h; //指向当前节点
h=h->next; //指向下一个结点
}
}
return 0; //未删除
}
7.链表长度
//链表的长度
int ChainListLength(ChainListType *head)
{
ChainListType *h;
int i=0;
h=head;
while(h) //遍历链表
{
i++; //累加结点数量
h=h->next; //处理下一个节点
}
return i;
}
以上是链表的基本操作,下面用链表制作一个简单的通讯录
最简单的通信录包括:添加联系人,查找联系人,删除联系人,显示联系人信息。下面用上面介绍的链表来实现这些功能。
通信录的头文件:
typedef struct
{
char key[15]; //设置名字关键字
char addr[20];
char tele[15];
}DATA; //数据结点类型
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include "ChainList.h"
#include "chainlist.cpp"
结构体中定义 姓名(并设置姓名为关键字),联系人地址和电话号。引用上面操作过的函数文件
1.显示联系人模块:
//显示联系人
void ChainListAll(ChainListType *head) //遍历链表
{
ChainListType *h;
DATA data;
h=head;
printf("数据如下:\n");
printf("\n");
while(h) //循环处理链表每个结点
{
data=h->data; //获取结点数据
printf("姓名:%s\n",data.key);
printf("地址:%s\n",data.addr);
printf("电话号:%s\n",data.tele);
h=h->next; //处理下一个结点
}
printf("\n");
return;
}
2.添加联系人模块
//添加联系人
ChainListType *input(ChainListType *head) //向通讯录输入信息
{
DATA data;
printf("输入联系人信息\n");
printf("姓名:");
scanf("%s",data.key);
printf("地址:");
scanf("%s",data.addr);
printf("电话:");
scanf("%s",data.tele);
printf("\n");
return ChainListAddEnd(head,data); //调用添加函数
}
3.查找模块
void find(ChainListType *head)
{
ChainListType *h;
DATA data;
char name[15];
printf("输入查找的姓名:");
scanf("%s",name);
h=ChainListFind(head,name);
if(h)
{
data=h->data; //获取结点数据
printf("姓名:%s\n",data.key);
printf("地址:%s\n",data.addr);
printf("手机:%s\n",data.tele);
}else
printf("没有找到相应的联系人,请重新查找\n");
printf("\n");
}
4.删除模块
//删除联系人
void Delete(ChainListType *head)
{
ChainListType *h=head;
DATA data;
char name[15];
char ch;
printf("输入要删除的姓名:");
scanf("%s",name);
printf("删除人信息:\n");
data=h->data; //获取结点信息
printf("姓名:%s\n",data.key); //显示删除信息
printf("地址:%s\n",data.addr);
printf("手机:%s\n",data.tele);
printf("已经删除!"); //提示删除成功
}
5.主函数
//主模块
int main()
{
ChainListType *node,*head=NULL;
int select; //选择菜单
do{
printf("1.添加联系人\n");
printf("2.查找联系人\n");
printf("3.删除联系人\n");
printf("4.显示联系人\n");
printf("0.退出\n");
select=getch();
switch(select)
{
case '1':
printf("\n添加联系人\n");
head=input(head);
break;
case '2':
printf("\n查找联系人\n");
find(head);
break;
case '3':
printf("\n删除联系人\n");
Delete(head);
break;
case '4':
printf("\n显示联系人\n");
ChainListAll(head);
break;
case '0':
return 0;
}
} while(select!=0);
}