说到链表,对于许多刚开始学习C语言的同学来说,的确还是有点不好理解(我就是这样的),查了好多有关链表的博客和文章,也有了自己的理解,才感觉吃透了。现在就把我学习链表这块儿的经历以及想法分享给大家吧。
对于初学链表,我们就要先与数组进行区分:
一·从逻辑属性区分:
1. 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标直接存取。
2. 链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐)链表必须根据next指针找到下一个元素
二·从内存分配上来看:
1. (静态)数组从栈中分配空间, 每个成员的地址都是连续的。(也就是说倘若数组的第一个元素在地址A,则数组第二个元素就在地址A+1。)
2. 链表从堆中分配空间, 每个节点没有相对固定的位置关系。(比如某个节点在地址A其后的节点不一定是A+1,而在内存的其他空闲区域,呈现一种随机的状态。)
接着, 就先从单链表的创建开始吧。单链表常用的创建可从有无头结点分类,而带头结点的创建方式又分为尾插法和头插法。
先讲一下不带头结点的吧,首先我们先得定义一个结构体类型,里面可以有数据域,但必须得有一个指针类型*next,它表示指向下一个结构体类型。我们现在就通过程序来讲解单链表的创建过程,以及注意的点。
#include<stdio.h>
#include<stdlib.h>
struct Node
{
int date;//这里date 代表数据域,我们暂且在这当它为一个整型数据
struct Node *next;//next表示指向本结构体类型的指针类型
};
int iCount;//定义一个全局变量,它表示结点个数
struct Node *Create()//定义Creat函数创建链表
{
struct Node *pHead=NULL;//初始化链表,使头指针为空
struct Node *pEnd,*pNew;//定义指针类型,使pEnd指向原来的结点,pNew指向新创建的结点
iCount=0;//初始化链表长度,即结点个数为0
pEnd=pNew=(struct Node *)malloc(sizeof(struct Node));//为pEnd和pNew分配内存空间
scanf("%d",&date);
while(pNew->date!=0)//用来终止循环结束的条件
{
iCount++;//每循环一次,结点自增
if(iCount==1)//判断是否是第一个结点
{
pNew->next=pHead;//使新结点的指针指向为空
pEnd=pNew;//pEnd即为pNew
pHead=pNew;//头指针指向首节点,pNew最初指向首结点,也即是最后一个结点
}
else
{
pNew->next=NULL;//新结点的指针指向为NULL
pEnd->next=pNew;//将原来结点的指针指向为新结点
pEnd=pNew;//将pEnd代替原来的pNew
}
pNew=(struct Node*)malloc(sizeof(struct Node));
scanf("%d",&pNew->date);
}
free(pNew);//释放结点
return pHead;
}
void print(struct student *Node)
{
struct NOde *pTemp;//pTemp为循环所用而临时创建的
pTemp=pHead;//临时指针指向首结点的地址
while(pTemp!=NULL)
{
printf("%d\n",pTemp->date);
pTemp=pTemp->next;
}
}
void main()
{
struct Node *pHead;
pHead=Create();
print(pHead);
}
这就是不带头结点的创建链表的方式,是不是感觉特别麻烦。所以,以后对于链表创建这块,我们还是使用带头结点的两种方式之一吧,上面的理解理解就行,实在理解不了,就放弃吧。
首先,还是直接看代码吧。
直接从创建开始吧,前面的结构体定义都和上面的一样。
这是头插法,我们需要注意的就是每次插入时,必须先连接后面的结点的指针指向,然后才连接前面的结点,还有就是遍历的时候它是逆置的,遍历时,它的头结点没有内容,需要从phead->next开始。
struct Node *creat()
{
struct Node *h,*r,*p;
h=(struct Node *)malloc(sizeof(struct Node));//创建头结点
h->next=NULL;
r=h;
do{
p=(struct Node *)malloc(sizeof(struct Node));
scanf("%d",&p->date);
p->next=r->next;//每次让让新结点的指针指向原来结点指针指向的结点
r->next=p;//将原来结点指针指向为新结点
}while(p->number!=0);//用来终止循环结束的条件
return (h);
}
至于尾插法,程序代码和这个相似度太高,必须得注意区分。
struct Node *creat()
{
struct Node *h,*r,*p;
h=(struct Node *)malloc(sizeof(struct Node));//创建头结点
h->next=NULL;
r=h;
do{
p=(struct Node *)malloc(sizeof(struct Node));
scanf("%d",&p->date);
//p->next=r->next;
r->next=p;//循环内部只是让最后一个结点指针指向连新创建的结点
//r->next=p;
r=p;//每次让指针r始终跟踪当前链表中的最后一个结点
}while(p->number!=0);//用来终止循环结束的条件
r->next =NULL;//最后才连接后面的结点
return (h);
}
最后在拓展一点,用递归创建循环链表,附上代码,自己琢磨琢磨吧。
这只是为你们提供一点思想,就写得简单的,还是希望给你带来帮助。
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
int data;
struct node *next;
}Linklist;
Linklist *creat(int arr[],int index,int index_cnt)
{
Linklist *head=NULL;
if(index_cnt <= index){
head=(Linklist *)malloc(sizeof(Linklist));
head->data=arr[index_cnt];
head->next=creat(arr,index,index_cnt+1);
}
return head;
}
void print(Linklist *head)
{
Linklist *p=head;
for(p=head;p!=NULL;p=p->next){
printf("%d\n",p->data);
}
}
int main()
{
Linklist *head;
int arr[5]={1,2,3,4,5};
head=creat(arr,sizeof(arr)/sizeof(arr[0])-1,0);
print(head);
return 0;
}
这就是链表常见的创建方法,都是自己的理解,有不足和讲错的地方还请各位指点,最后还是希望这篇文章能给初学链表的各位带来一点收获,仅此而已。
戳我获取链表的删查和排序。