本文我们将学习单链表的创建以及相关操作。使用c语言实现,结尾有全部代码。
(仅供正在学习的朋友参考,如有错误的地方,欢迎评论指出。)
很多朋友在初次学习时常常会搞不清楚头指针、头结点、结点以及链表关系或存在,本文章中我会一一为各位指出,至少是便于理解的。
单链表想象图(带头结点):
![](https://i-blog.csdnimg.cn/blog_migrate/36732f819dcd58f67574c490c60b17be.png)
注:不带头结点单链表想象图即把该图中“head”去掉即可,正常创建单链表都是带头结点的,原因是大部分情况下,带头结点的单链表可以轻松的完成很多操作。
在本文章的示例中我们将用到两个头文件,分别是:
#include<stdlib.h>
#include<stdio.h>
下面我们做一些准备工作:
const int TRUE = 1;
const int FALSE = 0;
const int OK = 1;
const int ERROR = 0;
typedef int ELEMTYPE
void Test_Point(int k){ //排错测试
printf("\n测试点%d\n",k);
}
在这段代码中我们定义了TRUE、OK、FALSE、ERROR和ELEMTYPE,这些都是为了方便接下来介绍操作代码而定义的,其最关键的用处就是增加我们阅读代码时对自定义函数的理解。第6~8行为测试代码,用于排查一些错误。
定义结点:
typedef struct NODE{
ELEMTYPE data;//数据域
struct NODE *next;//指针域,指向下一结点
}NODE,*linked;
一个结点由一个数据域和一个指针域组成,定义结点的时候要注意有效数据域占单个结点比例大小,尽量大密度。此段代码解析:
typedef + 关键字 + 自定义名称,给关键字定义别称。这里typedef struct NODE{}NODE,*linked;
这里NODE是这个结构体的别称,使用方法:NODE p;此时定义了一个名为p的NODE结构体。
这里*linked是这个结构体的指针别称,使用方法:linked p;此时定义了一个指向NODE结构体的指针。等价于NODE *p;
ELEMTYPE data:数据域部分,可以定义多个数据,这里ELEMTYPE是int别称。
struct NODE *next:指针域部分,可以定义多个指针(双向链表、树等),这里next指向下一个结点,一个一个下一个结点连接起来就是一根链,每个结点带有数据组起来就是表,二者结合起来就是链表(仅供理解使用)。
初始化操作(带头结点单链表定义):
int InitList_Head(linked &L){
L = (linked)malloc(sizeof(NODE));//申请头结点空间
L->next = NULL;/*让头结点next指向NULL。NULL是非常实用的,定义函数时要非常注意NULL的使用*/
if(L){//判断是否申请头结点空间成功
printf("初始化成功!\n");
return OK;
}
else{
printf("初始化失败!\n");
return ERROR;
}
}
本段代码运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/7a21faa21d0ff5029ee2aeea223a24c5.png)
判断链表是否为空操作:
int EmptyList(linked L){
if(L->next){//判断条件,如果头结点下一结点即首元结点为空,则链表为空
printf("链表为非空!\n");
return FALSE;
}
else{
printf("链表为空!\n");
return TRUE;
}
}
运行代码:
![](https://i-blog.csdnimg.cn/blog_migrate/2a2509d9a2511e1255acfeabb3faae70.png)
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/3c715fd509fa7da2a38e2774f08364e7.png)
返回链表长度操作:
int Length_Linklist(linked L){
linked p;//定义一个能够指向结构体NODE的指针
p = L->next;//让p指向链表L的首元结点。注:此处L可以理解为头结点
int counter = 0;//计数器
while(p){//遍历链表计数
counter++;
p = p->next;//每一次循环计数结束后让本结点指针指向下一结点
}
return counter;//返回计数器数字
}
运行代码:
![](https://i-blog.csdnimg.cn/blog_migrate/65347fe829902cf57dfff9b3197d2616.png)
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/ffabadc07c366d4a79f70e1fa3545ce1.png)
头插法创建单链表操作:
int Createlist_Head(linked &L){
linked p;//定义一个能够指向NODE结构体的指针p
int k;//要输入的链表长度k(可以根据具体情况不需要,本文章为了便于刚接触的朋友理解故加一个长度)
printf("请输入要创建的链表长度:\n");
scanf("%d",&k);
printf("请输入要存储的数据:\n");
for(int i = 0;i < k;i++){//依次录入数据并且将其接入链表
p = (linked)malloc(sizeof(NODE));//向内存申请结点空间
scanf("%d",&p->data);//录入数据进入指针p指向的结点。注:此处的结点可以理解为漂浮在某个空间中不接触任何物体的物体,此时,它拥有了数据
p->next = L->next;//让拥有了数据的p指针指向的结点的指针域指向头结点的指针域指向的方向,即指向NULL。
L->next = p;//让头结点的next指向p指向的结点。
}
return OK;
}
尾插法创建单链表操作:
int Createlist_Tail(linked &L){
linked p,q;
int k;
p = L;//让p指向L即让p指向头结点
printf("请输入要创建的链表长度:\n");
scanf("%d",&k);
printf("请输入要存储的数据:\n");
for(int i = 0;i < k;i++){
q = (linked)malloc(sizeof(NODE));
scanf("%d",&q->data);
q->next = p->next;
p->next = q;
p = q;
}
return OK;
}
逻辑想象及各自对应代码(尾插法,因为理解尾插法就能够理解头插法):
![](https://i-blog.csdnimg.cn/blog_migrate/126f0cd789052605051e92fed5dc5381.png)
需要注意的是很多初学的朋友不能够理解p,q,L之间的交互,其实它们也都只是指针而已,要自己细细品读才能深刻理解。
运行代码:
![](https://i-blog.csdnimg.cn/blog_migrate/c055bec9cc0787ff42ac48623b6eb3b0.png)
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/e4aa7914c9f39db2516a1934e05ee073.png)
注意:头插法输出为正序输入,反向输出;尾插法输出为正序输入,正序输出。
输出链表操作:
void Output(linked L){
linked p;
p = L->next;
while(p){
printf("%3d",p->data);
p = p->next;
}
printf("\n");
}
运行代码以及结果在上一个例子中。
获取链表中某位置的数据:
int GetData(linked L,int i){//i为要查找的位置
linked p;
p = L;
if(i <= 0 || i > Length_Linklist(L)){//查找位置是否在链表长度范围内
printf("非法的获取位置!!!\n");
return FALSE;
}
if(p){//判断链表是否为空
for(int h = 0;h < i;h++){//遍历寻找到第i个位置
p = p->next;
}
printf("链表中第%d个元素为%d\n",i,p->data);
}
else{
printf("无该位置数据!\n");
return ERROR;
}
}
调用GetData:GerData(L2,1);——查找L2中第一个元素
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/6c76b53a252ee3c935cb84fe9293d228.png)
单链表链表插入操作:
int InsList(linked &L,int i,ELEMTYPE e){//i为要插入的位置,e为要插入的数据
int k;
linked p,q;
p = L;
k = 1;
while(p && k < i){//找到要插入的位置
p = p->next;
++k;
}
if(!p || k > i)//判断插入位置是否合法
return ERROR;
/*以下代码同头插法操作思路相同*/
q = (linked)malloc(sizeof(NODE));
q->data = e;
q->next = p->next;
p->next = q;
return OK;
}
调用函数:InsList(L2,1,11);——在L2表中第一个位置插入数据11
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/9f84d4d7a354b64c9933f7ba4d714f85.png)
单链表删除结点操作:
int DelList(linked &L,int i,ELEMTYPE &e){//e用来返回要删除的数据
int k;
linked p,q;
p = L;
k = 1;
while(p->next && k < i){
p = p->next;
++k;
}
if(!(p->next) || k > i)
return ERROR;
q = p->next;
p->next = q->next;
e = q->data;
free(q);
return OK;
}
调用函数:DelList(L2,1,e);——删除L2中第一个元素,并将删除的元素赋给e。
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/54f1e7bcd1ed84ef6149277b6fc67be7.png)
判断链表中是否有某元素操作:
int LocateElem(linked L,ELEMTYPE e){
int i = 0;
linked p;
p = L ->next;
while(p){
i++;
if(p->data == e)
return i;
p = p->next;
}
return NULL;
}
调用函数:LocateElem(L2,2);在L2表中查询是否有数据2
运行代码:
![](https://i-blog.csdnimg.cn/blog_migrate/44d1d0a14789f502ccafe4337da1fc29.png)
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/a008a341cdab63deb4107069292033aa.png)
全部代码:
#include<stdlib.h>
#include<stdio.h>
const int TRUE = 1;
const int FALSE = 0;
const int OK = 1;
const int ERROR = 0;
typedef int ELEMTYPE
/*测试点*/
void Test_Point(int k){ //排错测试
printf("\n测试点%d\n",k);
}
/*定义结点*/
typedef struct NODE{
ELEMTYPE data;//数据域
struct NODE *next;//指针域,指向下一结点
}NODE,*linked;
/*初始化链表(带头结点)*/
int InitList_Head(linked &L){
L = (linked)malloc(sizeof(NODE));//申请头结点空间
L->next = NULL;/*让头结点next指向NULL。NULL是非常实用的,定义函数时要非常注意NULL的使用*/
if(L){//判断是否申请头结点空间成功
printf("初始化成功!\n");
return OK;
}
else{
printf("初始化失败!\n");
return ERROR;
}
}
/*判断链表是否为空*/
int EmptyList(linked L){
if(L->next){//判断条件,如果头结点下一结点即首元结点为空,则链表为空
printf("链表为非空!\n");
return FALSE;
}
else{
printf("链表为空!\n");
return TRUE;
}
}
/*返回链表长度*/
int Length_Linklist(linked L){
linked p;//定义一个能够指向结构体NODE的指针
p = L->next;//让p指向链表L的首元结点。注:此处L可以理解为头结点
int counter = 0;//计数器
while(p){//遍历链表计数
counter++;
p = p->next;//每一次循环计数结束后让本结点指针指向下一结点
}
return counter;//返回计数器数字
}
/*头插法创建链表*/
int Createlist_Head(linked &L){
linked p;//定义一个能够指向NODE结构体的指针p
int k;//要输入的链表长度k(可以根据具体情况不需要,本文章为了便于刚接触的朋友理解故加一个长度)
printf("请输入要创建的链表长度:\n");
scanf("%d",&k);
printf("请输入要存储的数据:\n");
for(int i = 0;i < k;i++){//依次录入数据并且将其接入链表
p = (linked)malloc(sizeof(NODE));//向内存申请结点空间
scanf("%d",&p->data);//录入数据进入指针p指向的结点。注:此处的结点可以理解为漂浮在某个空间中不接触任何物体的物体,此时,它拥有了数据
p->next = L->next;//让拥有了数据的p指针指向的结点的指针域指向头结点的指针域指向的方向,即指向NULL。
L->next = p;//让头结点的next指向p指向的结点。
}
return OK;
}
/*尾插法创建链表*/
int Createlist_Tail(linked &L){
linked p,q;
int k;
p = L;//让p指向L即让p指向头结点
printf("请输入要创建的链表长度:\n");
scanf("%d",&k);
printf("请输入要存储的数据:\n");
for(int i = 0;i < k;i++){
q = (linked)malloc(sizeof(NODE));
scanf("%d",&q->data);
q->next = p->next;
p->next = q;
p = q;
}
return OK;
}
/*链表输出*/
void Output(linked L){
linked p;
p = L->next;
while(p){
printf("%3d",p->data);
p = p->next;
}
printf("\n");
}
/*查找链表中i位置的数据*/
int GetData(linked L,int i){//i为要查找的位置
linked p;
p = L;
if(i <= 0 || i > Length_Linklist(L)){//查找位置是否在链表长度范围内
printf("非法的获取位置!!!\n");
return FALSE;
}
if(p){//判断链表是否为空
for(int h = 0;h < i;h++){//遍历寻找到第i个位置
p = p->next;
}
printf("链表中第%d个元素为%d\n",i,p->data);
}
else{
printf("无该位置数据!\n");
return ERROR;
}
}
/*插入结点*/
int InsList(linked &L,int i,ELEMTYPE e){//i为要插入的位置,e为要插入的数据
int k;
linked p,q;
p = L;
k = 1;
while(p && k < i){//找到要插入的位置
p = p->next;
++k;
}
if(!p || k > i)//判断插入位置是否合法
return ERROR;
/*以下代码同头插法操作思路相同*/
q = (linked)malloc(sizeof(NODE));
q->data = e;
q->next = p->next;
p->next = q;
return OK;
}
/*删除结点*/
int DelList(linked &L,int i,ELEMTYPE &e){//e用来返回要删除的数据
int k;
linked p,q;
p = L;
k = 1;
while(p->next && k < i){
p = p->next;
++k;
}
if(!(p->next) || k > i)
return ERROR;
q = p->next;
p->next = q->next;
e = q->data;
free(q);
return OK;
}
/*查询链表中是否有某数据*/
int LocateElem(linked L,ELEMTYPE e){
int i = 0;
linked p;
p = L ->next;
while(p){
i++;
if(p->data == e)
return i;
p = p->next;
}
return NULL;
}
void main(){
linked L1,L2;//定义链表
int e;
Initlist_head(L1);//初始化带头结点链表L1
Initlist_head(L2);//初始化带头结点链表L2
EmptyList(L2);
printf("\nL2表长为:%d\n",Length_Linklist(L2));
Test_Point(2);//测试点2
printf("头插法L1相关操作:\n");
Createlist_Head(L1);//创建头插链表L1
printf("输出L1:");
Output(L1);//输出头插法链表L1
printf("尾插法L2相关操作:\n");
Createlist_Tail(L2);//创建尾插链表L2
printf("输出L2:");
Output(L2);//输出尾插法链表L2
printf("L2表长为:%d\n",Length_Linklist(L2));//输出L2表长
Test_Point(3);//测试点3
printf("L2");
GetData(L2,1);
Test_Point(5);
InsList(L2,1,11);
Output(L2);
DelList(L2,1,e);
printf("删除的结点数据为:%d\n",e);
printf("删除第一位元素后的L2链表为:");
Output(L2);
if(LocateElem(L2,1)){
printf("该数字为第%d位\n",LocateElem(L2,2));
}
else printf("无该数字\n");
}
运行结果:
![](https://i-blog.csdnimg.cn/blog_migrate/8a5c6ac97786162b0ad8a6b252f85599.png)
博主也正在学习,偶尔会分享学习进程,文章肯定有不妥的地方,欢迎各位朋友评论斧正。
关于数据结构,一定一定要亲手操作一遍,其中抽象思想一定一定要慢慢细品才能有所感悟。