一、链表的定义
n个节点离散分配,节点之间通过指针相连。除了首节点和尾节点之外,每个节点都只有一个前驱结点和一个后继节点。
如下图:
大家有没有发现,链表的结构很像一种交通工具,什么呢?
火车。(节点==车厢,指针==车厢间相连的绳索)
二、链表的实现
注意,这里实现的是链表中最简单的单链表。
1、创建链表
1.1 定义链表最基本的组成元素–节点
我们由上面链表的定义可以知道,链表的每个节点应该含有两部分:
1、每个节点本身存储的数据,我们假定data 为 int 类型;
2、当前节点指向下一个节点的指针。
好,我们根据以上两部分进行节点的定义:
/**
NODE:节点类型
PNODE:指向节点的指针
*/
typedef struct Node
{
int data;
struct Node * pNext;
}NODE,*PNODE;
1.2创建链表
上面我们定义好了链表的节点,接下来我们就进入激动人心的一刻吧,创建属于你的链表!
上面讲了,链表的结构就像火车的结构,我们不妨以火车为例进行讲解吧!
1、创建火车头:
//PNODE是指向NODE节点的指针类型
PNODE pHead=(PNODE)malloc(sizeof(NODE));
完美!我们创造了一个火车头,并且使用pHead指向了这个火车头。
如下图:
2、创建火车尾部
//PNODE是指向NODE节点的指针类型
PNODE pHead=(PNODE)malloc(sizeof(NODE));
//pTail是指向火车尾部的指针
PNODE pTail=pHead;
pHead->pNext=NULL;
由于现在只有一节车厢,所以pHead和pTail都指向这节车厢。
如下图:
3、添加车厢
//假设我们添加3节车厢
for(i=0;i<3;i++)
{
printf("请输入第%d节车厢的数值\n",i+1);
scanf("%d",&val);
PNODE pNew=(PNODE)malloc(sizeof(NODE));
pNew->data=val;
pTail->pNext=pNew;
pTail=pNew;
pNew->pNext=NULL;
}
当i=0的时候,我们添加1节车厢,假设第1节车厢存储的值为1。
结构从
变为
上述pTail=pNew,使得pTail指向pNew指向的节点。
当i=1的时候,我们添加第2节车厢,假设第2节车厢存储的值为2。
当i=3的时候,同理添加第3节车厢。
如此,3节车厢就添加完了。
注意,pTail的作用是始终指向最后一节车厢,方便添加新车厢。
添加车厢的时候直接让pTail指向新的车厢,再让pTail指向新添加的车厢(最后一节车厢)。
总体代码:
//
typedef struct Node
{
int data;
struct Node * pNext;
}NODE,*PNODE;
//创建链表
PNODE createList()
{
int i,val,n;
PNODE pHead=(PNODE)malloc(sizeof(NODE));
PNODE pTail=pHead;
pHead->pNext=NULL;
printf("请输入链表长度:");
scanf("%d",&n);
for(i=0;i<n;i++)
{
printf("请输入第%d个数值\n",i+1);
scanf("%d",&val);
PNODE pNew=(PNODE)malloc(sizeof(NODE));
pNew->data=val;
pTail->pNext=pNew;
pTail=pNew;
pNew->pNext=NULL;
}
//将指向火车头的指针返回
return pHead;
}
2、遍历链表
2.1判断链表是否为空
我们要遍历一个链表,首先要判断链表是否为空。
链表空的话,我们就直接返回,不遍历。
/**
返回1:链表为空
返回0:链表不空
*/
//判断链表是否为空
int isEmpty(PNODE pHead)
{
if(pHead->pNext==NULL)
{
return 1;
}
else
{
return 0;
}
}
2.2遍历链表
如果链表不空的话,我们就进行如下遍历
PNODE p=pHead->pNext;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->pNext;
}
我们定义一个指针p
PNODE p=pHead->pNext;使得p指向了第1节车厢
下面,只要p不为空(p!=NULL)
即p有所指向,那么就打印p所指向的车厢的里面的data值。
然后,p再指向下一节车厢(p=p->pNext)
循环上面步骤,直到p指向最后一节车厢
再循环,p指向第4节车厢。
唉?第4节车厢没了!
那么p==NULL了,退出while循环。至此,循环遍历完毕。
完整代码如下:
//判断链表是否为空
int isEmpty(PNODE pHead)
{
if(pHead->pNext==NULL)
{
return 1;
}
else
{
return 0;
}
}
//遍历链表
void traverseList(PNODE pHead)
{
if(isEmpty(pHead))
{
printf("链表为空\n");
}
else
{
PNODE p=pHead->pNext;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->pNext;
}
free(p);
}
}
3、删除链表
假设p指向第1节车厢,q指向第2节车厢。
比如我们要删除第2节车厢。
如图:
即:
p->pNext=q->pNext;(p指向第1节车厢,q指向第2节车厢)
使第1节车厢的pNext指向第3节车厢,就删除了第2节车厢。
整体代码为:
//删除任意位置的元素
void deleteList(PNODE pHead,int pos)
{
if(isEmpty(pHead))
{
printf("链表为空,不可删除\n");
}
else
{
PNODE p=pHead->pNext;
//注意这里的i=2(因为p指向的是要删除车厢的前一节车厢)
int i=2;
while(p!=NULL && i!=pos)
{
p=p->pNext;
i++;
}
PNODE q=p->pNext;
p->pNext=q->pNext;
free(q);
}
}
三、整体代码
#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{
int data;
struct Node * pNext;
}NODE,*PNODE;
//创建链表
PNODE createList()
{
int i,val,n;
PNODE pHead=(PNODE)malloc(sizeof(NODE));
PNODE pTail=pHead;
pHead->pNext=NULL;
printf("请输入链表长度:");
scanf("%d",&n);
for(i=0;i<n;i++)
{
printf("请输入第%d个数值\n",i+1);
scanf("%d",&val);
PNODE pNew=(PNODE)malloc(sizeof(NODE));
pNew->data=val;
pTail->pNext=pNew;
pTail=pNew;
pNew->pNext=NULL;
}
return pHead;
}
//判断链表是否为空
int isEmpty(PNODE pHead)
{
if(pHead->pNext==NULL)
{
return 1;
}
else
{
return 0;
}
}
//遍历链表
void traverseList(PNODE pHead)
{
if(isEmpty(pHead))
{
printf("链表为空\n");
}
else
{
PNODE p=pHead->pNext;
while(p!=NULL)
{
printf("%d ",p->data);
p=p->pNext;
}
free(p);
}
}
//删除任意位置的元素
void deleteList(PNODE pHead,int pos)
{
if(isEmpty(pHead))
{
printf("链表为空,不可删除\n");
}
else
{
PNODE p=pHead->pNext;
int i=2;
while(p!=NULL && i!=pos)
{
p=p->pNext;
i++;
}
PNODE q=p->pNext;
p->pNext=q->pNext;
free(q);
}
}
int main()
{
PNODE pHead=createList();
bubbleSort(pHead);
traverseList(pHead);
return 0;
}
四、总结
从以上实现可以看出,链表的删除非常简便,但是查找很慢,很繁琐,需要从pHead一个个查起。
优点:插入删除简单,花费时间短。空间没有限制。
缺点:查询慢,比数组多一个pNext域,所占总空间大。
线性结构有数组和链表。数组和链表各有优缺点。没有哪一个最好。也就是没有最好,只有更好,看哪个更适合你的需求,就选择哪个结构。
本人初学,很多问题不甚明了。
如有错误或不当之处,敬请您不吝指正,
如果有问题需要探讨,烦请留言回复。