头指针—(头结点)(可以没有)—首元结点
//库函数头文件包含
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
//函数状态码定义
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef int ElemType; //假设线性表中的元素均为整型
typedef struct LNode
{
ElemType data;//data是ElemType类型的;
struct LNode *next;//next是*类型;
}LNode,*LinkList;//*在LinkList前面
因为链表中,以元素(数据元素的映象) + 指针(指示后继元素存储位置) = 结点(表示数据元素 或 数据元素的映象),以“结点的序列”表示线性表称作链表。
一、链表初始化
Status InitList(LinkList &L)
{
L=(LinkList)malloc(sizeof(LNode));//生成新结点作为头指针,用头指针L指向头结点;
if(L==NULL) return ERROR;
L->next=NULL;//头结点的指针域为空
return OK;
}
二、链表的创建
方式1:前插法创建单链表
此方法采用的是倒叙插入的方式,比如输入的是1 2 3,但是实际上插入的数据排序为3 2 1;
void CreateList_H(LinkList &L,int n)//逆位序输入n个元素的值
{
LinkList p;//p是LinkList类型的;
L=(LinkList)malloc(sizeof(LNode));//新建一个带头结点的单链表L;
if(L==NULL) return ERROR;
L->next=NULL;//将单链表设置为空
for(int i=0;i<n;i++)
{
p=(LinkList)malloc(sizeof(LNode));//生成新结点;
scanf("%d",&p->data);//不要忘记输入元素;//这里千万不要忘记加&符号
p->next=L->next;//p->next等于的是L->next,而不是NULL
L->next=p;//插入到表头
}
}
首先创建一个带有头结点的空的单链表,然后倒叙(代码i是从n开始不断减一)不断创建新结点,输入值(千万别忘了),建立链表关系。
方式2:后插法创建单链表
此方法采用的是正叙插入的方式,比如输入的是1 2 3,实际上插入的数据排序为1 2 3;
倒叙插入数据的时候就是一直在头结点之后进行插入数据,因此不需要考虑太多的问题,但是,正序插入数据的时候,是不断在新插入的数据之后再插入数据,因此必须要用另外一个变量不断指向新插入的结点,以便于插入后续的结点。
void CreateList_H(LinkList &L,int n)
{
LinkList p,r;
L=(LinkList)malloc(sizeof(LNode));//新建一个带头结点的单链表L;
if(L==NULL) return ERROR;
L->next=NULL;//将单链表设置为空
r=L;//注意是在这里给r赋值的,让r指向头结点//这里是为了让r不断指向新创立的结点;
for(int i=0;i<n;i++)
{
p=(LinkList)malloc(sizeof(LNode));
scanf("%d",&p->data);
p->next=NULL;//因为新的结点的位置一直都是在尾结点之前,所以新结点的下一个一定是NULL;
r->next=p;//将p插入到r之后;
r=p;//让r指向新的结点p;
}
}
注意:
(1)上面的操作都是对于单链表的操作
(2)注意r赋值的位置
算法的时间复杂度为:O(Listlength(L))
三、链表的插入
在链表中插入结点只需要修改指针。但同时,若要在第 i 个结点之前插入元素,修改的是第 i-1 个结点的指针。因此,在单链表中第 i 个结点之前进行插入的基本操作为: 找到线性表中第i-1个结点,然后修改其指向后继的指针。
Status ListInsert_L(LinkList&L,int i,ElemType e)//在线性表第i个位置插入元素e//插入和创建不一样,插入是在创建的基础上进行的改动;
{
LinkList p,s;
p=L;//因为现在需要找的位置是插入位置的前一个,插入的位置也有可能为头结点的后面,因此以p为起点;
int j=0;//因为以p为起点,所以j的初始值为0;
while(p&&j<i-1)//这里是在寻找要插入结点位置的前一个
{
p=p->next;
j++;
}
if(!p||j>i-1) return ERROR;//p为空的话就停止;
s=(LinkList)malloc(sizeof(LNode));//要为插入的地方创建新的结点;
s->data=e;//千万不要忘记在创建结点之后给新结点赋值;
s->next=p->next;
p->next=s;
return OK;
}
注意:
(1)千万不要忘记在创建新结点之后赋值
(2)插入的时候,先要找到前一个插入的位置,因为插入的位置也有可能为头结点的后面,因此以p为起点,所以j的初始值为0(这里还用了一个变量j是因为怕有错误的位置出现)。找到位置之后,创建新的结点,输入元素,然后再将新结点放到找到的p的位置的后面;
(3)在p后面插入s的操作
s = (LinkList) malloc ( sizeof (LNode)); // 生成新结点
s->data = e;
s->next = p->next; p->next = s; // 插入
算法的时间复杂度为:O(ListLength(L))
四、链表的删除
Status ListDelete_L(LinkList&L,int i,ElemType& e)//注意e前有&号
{
LinkList p,r;
p=L;//有可能删除的是第一个元素,所以p最开始的位置是头结点;
int j=0;
while(p->next&&j<i-1)//因为要删除的是下一个元素,所以要保证下一个元素有数,所以在while中是p->next//保证p不是最后一个元素
{
p=p->next;
j++;
}
if(!(p->next)||j>i-1) return ERROR;
r=p->next;//先记录要删除的位置,否则删除不了;//不需要重新分配,因为p就已经有结点空间了;
e=p->next->data;
p->next=p->next->next;
free(r);
return OK;
}
注意:
(1)p最开始指向的位置是头结点,因为万一删除的位置是首元结点
(2)while(p->next&&j<i-1)
与插入不同,这样是为了保证p指向的不是最后一个元素,因为删除的时候如果想要删除的是最后一个元素的话,要找到最后一个元素的前一个;插入的话,可以一直找到最后一个元素,因为可以在最后一个元素后插入元素。删除则不同,不可删除最后一个元素之后的元素。
(3)注意记录要删除元素的位置
(4)在单链表中删除第 i 个结点的基本操作为:找到线性表中第i-1个结点,修改其指向后继的指针。
q = p->next; p->next = q->next;
e = q->data; free(q);
算法的时间复杂度为:O(ListLength(L))
删除和插入一样,都需要找到要插入或者删除位置的前一个,但是还是有不同点,删除的时候要保证要删除的位置有数,不能是空的,所以在while中的判断不同
五、链表的清空
void ClearList(&L)// 将单链表重新置为一个空表
{
LinkList r;
//p=L;没有必要,因为要置为空表,所以不断删除头结点之后的元素即可,不需要借助另外一个来删除;
while(L->next)
{
r=L->next;
L->next=r->next;
free(r);
}
}
注意:
(1)在置为空表的时候,不需要借助别的元素,因为直接删除头结点之后的东西
算法时间复杂度:O(ListLength(L))
六、查询某位置的元素
求在第i个位置的元素值
Status GetElen_L(LinkList L,int i,ElemType &e)
{
LinkList p;
p=L->next;//查找元素从第一个元素开始查找就可以啦
int j=1;
while(p&&j<i)
{
p=p->next;
j++;
}
if(!p&&j>i) return ERROR;
e=p->data;
return OK;
}
注意:
(1)查找元素的时候,从第一个元素开始查找就可以了
算法时间复杂度:O(ListLength(L))