我们在编程时,往往都会用到数组,数组是顺序存储结构,其在内存中,都是以相邻地址来做存储的,
例如,定义一个整型的数组,int a[10] ;在内存中,这样表现:
每个成员占用4个字节,然而,在我们使用数组时,很多情况是这样的:
从图中可以看出,数组在使用时会产生离散空间,这会造成内存的浪费,并且,数组是需要先分配,在使用的,很多时候我们是不知道我们接收到的数据的,即使使用动态分配,其原理还是离不开先分配后使用,内存需求是不确定的,所以我们引入了链表;
我们将链表中的每一个成员设置为一个结构体,该结构体包含了数据段(Data)
与指针段(nextNode),数据段用来存储我们想存储的数据,数据是多样的,数据的信息更是多样,例如我们要保存有个学生的学号,姓名和年龄,所以这个数据段,我们任然可以用一个结构体来表示;而指针段则存储了指向下一个结点位置的指针,因为指向链表中的结点,所以指针的类型与该结点的结构体一样;定义结点:
typedef struct
{
char key[10]; //该节点的关键字
char name[20]; //数据内容,假设为学生姓名
int age; //学生年龄
}Data;
typedef struct Node
{
Data nodeData; //数据段;
struct Node *nextNode; //指向下一个结点的指针
}CLType;
定义一个链表很简单,只需定义一个CLType 类型的头指针即可,该指针指向链表的第一个结点(头结点),若为空表,则头指针为空;一个完整的链表如图:
头指针指向头结点,头结点的nextNode指正指向其下一个结点,以此往下,就把各个结点“ 串 ” 了起来,并且,这些结点的位置在物理内存中可以不相邻,只要指针以此指向他们的下一个结点即可;我们又称结点的的上一个结点是该节点的前去,该节点指向的结点为其后继,最后一个结点的指针指向空,这是尾结点;
既然知道了链表的结构,那么怎么将我们想要的数据插入到链表中呢?
首先来讲尾插法:顾名思义,尾插法就是从链表的尾部进行插入,每来一个新的 结点,就将其安排在链表的尾部;
既然要插入到尾部,则必须让链表当前的尾结点的nextNode指针指向新来的结点,并将新结点的nextNode指针指向空;
1.定义两个 CLType 类型的指针,一个是新节点,一个用来遍历链表(我们并不知道尾结点的指针,只知道头指针,所以我们需要从头结点出发,根据链表的指针顺藤摸瓜一直找到指向NULL的指针为止,第二个指针就用来保存我们遍历的链表结点的指针);
2.为新节点指针申请存储空间;
3.将数据存入新节点;
4.遍历链表,找到指向NULL的指针,即尾结点;
5.插入新节点
CLType *CLAddEnd(CLType *head,Data nodeData) //返回值是CLtype类型的,参数会用到头指针,传入的一个结构体
{
CLType *node,*htemp;
if((node = (CLType *)malloc(sizeof(CLType))) == NULL) //分配存储空间
{
perror("malloc failure"); //分配失败
return NULL;
}
else
{
node->nodeData = nodeData; //将参数的值赋给新结点;
node->nextNode = NULL;
if(head == NULL) //空表,则成为头结点
{
head = node;
return head;
}
htemp = head; //将头指针的值赋给临时变量htemp;
while(htemp->nextNode != NULL)
{
htemp = htemp->nextNode; //移动临时变量
}
htemp->nextNode = node;
return head;
}
}
1.定义一个新结点;
2.malloc() 分配内存;
3.赋值;
4.将头指针所指的结点赋予新结点的 nextNode,将头指针指向新结点;
CLType *CLAddFirst(CLType *head,Data nodeData) //头插法
{
CLType *node;
if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
{
printf("malooc failure");
return NULL;
}
else
{
node->nodeData = nodeData; //保存数据
node->nextNode = head; //指向头结点所指的指针
head = node; //头结点指向新增结点
return head;
}
}
注意:头插法操作比较简单,但是,在将新结点插入到头指针之后的操作顺序是不能颠倒的,必须先将头指针所保存的地址赋给新结点的nextNode指针,再将头指针指向新结点node,后面结点的插入也是此,只是将头指针换成了某个结点的nextNode指针;
通常我们需要在已创建的链表中查找某个节点,在这,我们可以用到结点数据段中的某得成员进行查找,例如使用关键字进行查找,我们可以输入想要查找的关键字,然后从头结点依次往下查找,查找到后返回该节点的指针;
定义一个临时指针。用来遍历这个链表。若查找不到就下移,超找到了就返回这个指针当前所指,若到表尾任然未找到则返回空;
CLType *CLFindNode(CLType *head,char *key) //按关键字查找结点
{
CLType *htemp;
htemp = head; //保存头结点的指针
while(htemp)
{
if(strcmp(htemp->nodeData.key,key) == 0) //查找关键字
{
return htemp;
}
htemp = htemp->nextNode; //处理下一个结点
}
return NULL;
}
回想头插法,在插入一个结点时,先将要插入位置前的那一个结点的指针所指向的地址赋给新结点的nextNode指针,再将要插入位置前的哪一个结点的指针指向新结点,具体如何找到在哪开插入,我们可以使用关键字进行循环查找;这就会用到我们的前一个功能查找,定义一个临时的变量 nodetemp,这个变量用来保存插入位置的前一个结点,即在该节点后插入;
顺序:
nodetemp->nextNode = node->nextNode;
nodetemp->nextNode = node;
删除结点很简单,只需将要删除结点的上一个结点指向要删除结点的下一个结点,再将删除结点的内存释放即可,所以我们要用到两个指针,分别代表要删除的结点和该结点的上一个结点;
node->nextNode = htemp->nextNode;
free(nodetemp);
int CLDeleteNode(CLType *head,char *key)
{
CLType *node,*htemp; //node用来保存要删除结点的前一个结点
htemp = head;
while(htemp)
{
if(strcmp(htemp->nodeData.key,key) == 0)
{
node->nextNode = htemp->nextNode;
free(htemp);
return 1;
}
else
{
node = htemp;
htemp = htemp->nextNode; //node指向htemp所指,htemp后移;
}
}
return 0; //未删除
}
只需遍历链表,找到nextNode为空的结点,每后移一个结点,length+1;
int CLLength(CLType *head)
{
CLType *htemp;
int Len = 0;
htemp = head;
while(htemp)
{
Len++;
htemp = htemp->nextNode;
}
return Len;
}
定义一个结点类型(CLType)的结构体Data,和一个指针node,指针用来遍历链表,而Data用来保存该指针当前所指的结点信息,打印完该信息后,指针后移,再用后移后的指针给结构体赋值,循环一直到表尾;
void CLAllNode(CLType *head)
{
CLType *htemp;
Data nodeData;
htemp = head;
printf("链表当前共有%d个结点。链表如下:\n",CLLength(head));
while(htemp)
{
nodeData = htemp->nodeData;
printf("结点(%s,%s,%d)\n",nodeData.key,nodeData.name,nodeData.age);
htemp = htemp->nextNode;
}
}
我将所有的操作函数全部写到一个c文件里,写一个主函数,通过通过标准输入获取到信息,再使用尾插法将信息存入链表,然后依次演示代码功能;
#include
#include
typedef struct
{
char key[10];
int age;
char name[20];
}Data;
typedef struct Node
{
Data nodeData;
struct Node *nextNode;
}CLType;
CLType *CLAddEnd(CLType *head,Data nodeData) //尾插法
{
CLType *node,*htemp;
if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
{
perror("malloc failure");
return NULL;
}
else
{
node->nodeData = nodeData;
node->nextNode = NULL;
if(head == NULL)
{
head = node;
return head;
}
htemp = head;
while(htemp->nextNode != NULL)
{
htemp = htemp->nextNode;
}
htemp->nextNode = node;
return head;
}
}
CLType *CLAddFirst(CLType *head,Data nodeData) //头插法
{
CLType *node;
if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
{
printf("malooc failure");
return NULL;
}
else
{
node->nodeData = nodeData; //保存数据
node->nextNode = head; //指向头结点所指的指针
head = node; //头结点指向新增结点
return head;
}
}
CLType *CLFindNode(CLType *head,char *key) //按关键字查找结点
{
CLType *htemp;
htemp = head; //保存头结点的指针
while(htemp)
{
if(strcmp(htemp->nodeData.key,key) == 0) //查找关键字
{
return htemp;
}
htemp = htemp->nextNode; //处理下一个结点
}
return NULL;
}
CLType *CLInsertNode(CLType *head,char *findkey,Data nodeData)
{
CLType *node,*nodetemp;
if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
{
printf("malooc failure");
return 0;
}
node->nodeData = nodeData;
nodetemp = CLFindNode(head,findkey);
if(nodetemp)
{
node->nextNode = nodetemp->nextNode;
nodetemp->nextNode = node;
}
else
{
printf("未找到关键字\n");
free(node);
}
return head;
}
int CLDeleteNode(CLType *head,char *key)
{
CLType *node,*htemp; //node用来保存要删除结点的前一个结点
htemp = head;
while(htemp)
{
if(strcmp(htemp->nodeData.key,key) == 0)
{
node->nextNode = htemp->nextNode;
free(htemp);
return 1;
}
else
{
node = htemp;
htemp = htemp->nextNode;
}
}
return 0; //未删除
}
int CLLength(CLType *head)
{
CLType *htemp;
int Len = 0;
htemp = head;
while(htemp)
{
Len++;
htemp = htemp->nextNode;
}
return Len;
}
void CLAllNode(CLType *head)
{
CLType *htemp;
Data nodeData;
htemp = head;
printf("链表当前共有%d个结点。链表如下:\n",CLLength(head));
while(htemp)
{
nodeData = htemp->nodeData;
printf("结点(%s,%s,%d)\n",nodeData.key,nodeData.name,nodeData.age);
htemp = htemp->nextNode;
}
}
int main(int argc,char **argv)
{
CLType *node,*head = NULL;
Data nodeData;
char key[10],findkey[10];
printf("链表测试,先输入链表中的数据,格式为:关键字 姓名 年龄\n");
do
{
fflush(stdin); //清空输入缓冲区;
scanf("%s",nodeData.key);
if(strcmp(nodeData.key,"0") == 0) //若关键字为0则表示输入完成
{
break;
}
else
{
scanf("%s%d",nodeData.name,&nodeData.age);
head = CLAddEnd(head,nodeData);
}
}while(1);
CLAllNode(head);
printf("\n演示插入结点,输入插入位置的关键字:");
scanf("%s",findkey);
printf("输入插入结点的数据(关键字,姓名,年龄):");
scanf("%s%s%d",nodeData.key,nodeData.name,&nodeData.age);
head = CLInsertNode(head,findkey,nodeData);
CLAllNode(head);
printf("\n演示删除结点,输入删除结点的关键字:");
fflush(stdin); //清空输入缓冲区
scanf("%s",key);
CLDeleteNode(head,key);
CLAllNode(head);
printf("演示按关键字查找,输入查找的关键字:");
fflush(stdin); //清空输入缓冲区
scanf("%s",key);
node = CLFindNode(head,key);
if(node)
{
nodeData = node -> nodeData;
printf("关键字为%s的结点为(%s,%s,%d)\n",key,nodeData.key,nodeData.name,nodeData.age);
}
else
{
printf("在链表中未找到关键字为%s的结点!\n",key);
}
}
编译运行程序
按照要求,输入信息;
输入0按下enter表示输入完成;
输入完成后,程序会打印链表,并提示开始演示插入操作,要求我们输入插入的关键字,我们想在余小c后面插入数据,那就输入他的关键字2003,之后程序提示我们要插入的数据信息,按照格式书写即可,按下回车:
程序打印了插入蛇哥后的链表,现在演示删除结点,输入要删除的关键字,若果我们想删除霸哥,则输入霸哥的关键字2004,按下enter:
霸哥被成功删除,现在提示输入查找,输入2006,按下回车:
这就是链表的一些基本操作了。