以书本实例完善为主,书上内容多则补充,少则自己写,为了统一格式,各链表都带头结点。
1.头文件和宏定义(各链表通用)
#include<stdio.h>
#include<stdlib.h> //malloc、realloc、free函数
#include<string.h>
#define ElemType int
#define MaxSize 6 //静态链表最大长度
2.头插法建立单链表
typedef struct LNode
{
ElemType data;
struct LNode *next; //此处next为只能指向LNode(即链表结点)的结构体指针变量,因为结点为结构体
}LNode,*LinkList;
LinkList HeadInsert_List(LinkList L) //头插法,返回一个结构体类型的结点变量,个人认为返回头结点毫无必要
{
int x,i=1;
L=(LinkList)malloc(sizeof(LNode)); //头结点分配空间,若不分配,L为头指针,则该链表为没有头结点的单链表
L->next=NULL;
LNode *s,*p=L;
printf("请输入头插法链表%d号数据(输入9999结束):",i);
scanf("%d",&x);
while(x!=9999) //因为x申明在先,所以循环外先要赋一次值
{
i++;
printf("请输入头插法链表%d号数据(输入9999结束):",i);
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
s->next=L->next;
L->next=s;
scanf("%d",&x);
}
while(p->next!=NULL)
{
printf("%d ",p->next->data);
p=p->next;
}
printf("\n");
printf("插入成功!");
return L;
}
运行结果:
可以看到,因为是头插法,数据在链表中其实是倒序存放的,为了统一,以下链表均采用尾插法。
3.尾插法建立单链表
LinkList TailInsert_List(LinkList L) //尾插法,主要步骤与头插法类似,不再过多注释
{
int x,i=1;
L=(LinkList)malloc(sizeof(LNode)); //头结点分配空间,即带头结点
LNode *s,*p=L,*r=L; //与头插法主要不同点在于建立
printf("请输入尾插法链表%d号数据(输入9999结束):",i);
scanf("%d",&x);
while(x!=9999)
{
i++;
s=(LNode*)malloc(sizeof(LNode));
s->data=x;
r->next=s;
r=s;
printf("请输入尾插法链表%d号数据(输入9999结束):",i);
scanf("%d",&x);
}
printf("插入成功!\n数据为:");
while(p->next!=NULL)
{
printf("%d",p->next->data);
p=p->next;
}
return L;
}
void main()
{
LinkList L;
L=TailInsert_List(L);
}
运行结果:
可以看到尾插法运行结果为正序
4.尾插法建立双链表
双:两个
链:链接(c语言中为指针)
表:线性表
双链表即表示结点有两个指针的线性表
typedef struct DNode
{
ElemType data;
struct DNode *prior,*next;
}DNode,*DLinkList;
DLinkList TailInsert_List(DLinkList L) //尾插法建立双链表,返回头结点
{
L=(DNode *)malloc(sizeof(DNode));
L->next=NULL;
L->prior=NULL; //头结点初始化前后指针都为空
int x,i=1;
DNode *s,*r=L,*p=L;
printf("请输入尾插法双链表%d号数据(输入9999结束):",i);
scanf("%d",&x);
while(x!=9999)
{
i++;
printf("请输入尾插法双链表%d号数据(输入9999结束):",i);
s=(DNode *)malloc(sizeof(DNode));
s->data=x;
s->next=NULL;
s->prior=r;
r->next=s; //与单链表稍有不同,不加赘述了
r=s;
scanf("%d",&x);
}
while(p->next!=NULL)
{
printf("%d ",p->next->data);
p=p->next;
}
printf("建立成功!");
return L;
}
void main()
{
DNode *L;
TailInsert_List(L);
}
运行结果
5.循环链表
//循环链表
typedef struct CNode //C即为circle(循环)的缩写
{
ElemType data;
struct CNode *next;
}CNode,*CLinkList;
CLinkList TailInsert_List(CLinkList L) //尾插法建立循环链表
{
int x,i=1;
L=(CLinkList)malloc(sizeof(CNode)); //头结点分配空间,即带头结点
L->next=L;
CNode *s,*p=L,*r=L;
printf("请输入尾插法链表%d号数据(输入9999结束):",i);
scanf("%d",&x);
while(x!=9999)
{
i++;
s=(CNode*)malloc(sizeof(CNode));
s->data=x;
r->next=s;
s->next=L; //与建立单链表最大不同就是表尾指针要指向头结点
r=s;
printf("请输入尾插法循环链表%d号数据(输入9999结束):",i);
scanf("%d",&x);
}
while(p->next!=L)
{
printf("%d ",p->next->data);
p=p->next;
}
printf("%d ",p->next->next->data); //输出当前p指向的下下个元素数据,即位置1的数据,证明其循环性成立
return L;
}
void main()
{
CNode *L;
TailInsert_List(L);
}
运行结果:
为了证明该链表的循环性,在函数运行结束后,输出尾结点指向的下下个数据元素(即元素1),能输出则得证
6.静态链表(个人认为是链表中较难实现,也最少考的内容)
思想:先建立辅助数组,数组中data为空,next为满,从1到MaxSize-1,尾结点next为0(书本中要求为-1),每次插入数据从1号(注意因为是数组,1号实际上是第二个结点)开始逐个插入,顺序可以自己决定,只需要修改对应next值即可(此处又有一个注意点:若要随机插入,需要判断目标插入位置是否已有数据,即数据域是否有内容或指针域是否已被修改),为了简化理解,代码以顺序插入为例。
typedef struct
{
ElemType data;
int next;
}SLinkList[MaxSize];
void reserve(SLinkList *L)
{
for(int i=0;i<MaxSize-1;i++)
{
L[i]->next=i+1;
L[i]->data=-1;
}
L[MaxSize-1]->next=0;
}
int malloc_L(SLinkList *L)
{
int i=L[0]->next;
if(L[0]->next)
{
L[0]->next=L[i]->next;
}
return i;
}
int init_L(SLinkList *L)
{
reserve(L);
int a=malloc_L(L);
int temp=a;
for(int i=1;i<4;i++)
{
int j=malloc_L(L);
L[temp]->next=j;
L[j]->data=i;
temp=j;
}
L[temp]->next=0;
return a;
}
void Display(SLinkList *L,int a)
{
int temp=a;
while (L[temp]->next)
{
printf("%d,%d ",L[temp]->data,L[temp]->next);
temp=L[temp]->next;
}
printf("%d,%d",L[temp]->data,L[temp]->next);
}
void main()
{
SLinkList L[MaxSize];
int a=init_L(L);
printf("静态链表为:\n");
Display(L,a);
}
若不理解可以参考:详细过程
总结:链表这一部分是数据结构中非常基础、非常常考、也非常多变,适合出题的一章,务必了解各种链表的建立(如上)和单链表的增删查改、整合、逆置,这些内容会在后面完成。链表基本操作