总结一下链表的用法,加深自己的印象也给自己和其他萌新做参考。
链表其实跟数组一样,都是线性表的一类,不同的在于数组是顺式存储结构,而链表则是链式存储结构,为什么有时候要用链表呢?因为链表在插入和删除元素的时候比数组更加有优势,并且它不用担心溢出的问题。当然能用数组解决的问题,就尽量别用链表了,因为链表功能的实现方面还是相对麻烦的。(个人意见)
一 单链表
首先最简单的链表,单链表,由结点组成,结点中包含数据域(放数据)和指针域(指向下一个结点的地址,从而达到连起来的效果),首先要建立一个链表,要先有一个头指针(而头结点不是必要的)。
/*线性表的单链表存储结构*/
typedef int ElemType;/*typedef 起到起别名的作用,以int为例,实际情况请灵活变通一下。
typedef struct Node
{
ElemType data;/*数据域*/
struct Node *next;/*指针域*/
}Node;
typedef struct Node *LinkList;/*定义LickList,后面声明指针用的*/
这个是建立结点用的结构体。(链表必要元素)
然后接着就是链表的建立了
/*假设一个首项为一,公差为一的递增数列输入到链表里,假设有8项*/
void CreatListHead(LinkList *L , int n)/*头插法,后入的编号在前面
{
LinkList p;
int i;
*L = (LinkList)malloc(sizeof(Node));
(*L)->next=NULL; /*先建立一个带头结点的单链表*/
for(i=0;i<8;i++){
p=(LinkList)malloc(sizeof(Node));/*建立一个新结点,malloc的头问题是stdlib.h*/
p->data=i+1;
p->next=(*L)->next;
(*L)->next=p; /*相当于L的手不要NULL了,牵了p,而p牵了NULL*/
}
}
链表数据的输入也可以在主函数里面,看个人,输入跟插入其实差不多。
后插法比较符合我们的正常思维,多了个指向尾部的指针,代码如下:
void CreatListTail(LinkList *L , int n )
{
LinkList p,r;/*r为指尾部的指针*/
int i;
*L=(LinkList)malloc(sizeof(Node));
r=*L;
for(i=0;i<8;i++){
p=(LinkList)malloc(sizeof(Node));
p->data=i+1;
r->next=p;
r=p;
}
r->next=NULL; /*表示链表结束 */
}
接下来介绍一下单链表的插入和删除。
插入就相当于把一个位置让给一个结点,这个位置的前面的结点把指针指向它,而这个插入的结点把指针指向后面的结点。
代码如下:
void InsertList(LinkList *L , int i , ElemType e )/* i为插入位置,e为插入的值*/
{
LinkList p,s;
int j=1;
p=*L;
while(p&&j<i){
p=p->next;
++j;
}
if(!p||j>i)
return ERROR;
s=(LinkList)malloc(sizeof(Node));
s->data=e;
s->next=p->next;/*将p的后继结点赋给s的后继*/
p->next=s;/*p的后继指针指向s*/
return OK;
}
删除则是反过来,可以看作忽略掉要删除位置的结点,然后其前面的结点的指针去指向其后面即可。需要去回收要删除的结点(free)。
代码如下:
void DeleteList (LinkList *L,int i,ElemType *e)/*需要返回删除的值e*/
{
LinkList p,q;
int j;
p=*L;
while(p&&j<i){
p=p->next;
++j;
}
if(!p|j>i)
return ERROR;
q=p->next;/*p为删除位置的前一位,q为删除位置*/
p->next=q->next;/*p的后继指针指向删除位置q的后继指针,即跳过q*/
*e=q->data;
free(q);/*其功能与malloc相反*/
return OK;
}