提起链表,就不得不提“顺序表”。
作为顺序存储结构的“龙头老大”——链表的地位无可比拟:比如有一道算法题叫“大数相加”,如果不用链表的话,将会是非常繁琐和低效的。
见下:​大数相加

单链表

单链表是一种最简单的链式存储结构,可以看做是以“节点的序列”来表示线性表。
其中,每个结点包括两个域:存放数据元素信息的域,称为数据域;指针域用来存储直接后即的域。而指针域中存储的信息称为指针。由于这种链表只含有一个指针域,所以又称为:单链表
线性表的链式表示也可叫单链表。我们假设L为第一个节点(也称“头结点”)(头结点一定不存放数据)。
由于单链表可由头指针唯一确定,所以在C语言可用“结构指针”来描述。(就此,引入:结构体)

typedef struct LNode{
ElemType date; //数据域
struct LNode *next; //指针域
}LNode,*LinkKList; //定义指针类型
LinKList L; //L为单链表的头指针

单链表的初始化
单链表的初始化就是建立一个只有头结点的空链表。实际上就是创建一个节点,而不需要设置它的数据域,只需将它的指针域设置为空。算法描述如下:

void InitLinKList(LinKList &L){
L=new LNode;
if(!L) exit(overflow);
L->next=NULL;
}

求表长
求长度,我们需要设一个移动工作指针p(一定要是指针,代码中有体现)和一个计数器j,初始时p=L->next,j=0,若p非空,则计数器加1,并将指针下移一个位置,直到达到链表尾。

int LinKListLen(LinKList L){
LNode *p;
int j=0;
p=L->next; //这一句接下来会很常见:p指向第一个节点
while(!p) {
j++;
p=p->next;
} //p指向第j个节点
return j;
}

p=L->next ?为毛没有数据影响?可以直接操作下一个节点?
其实,正如我们所知道的那样,作为头结点的L是没有数据存储功能的,它只有一个指针,等着有“人”随时来用,而在链表中,第一个“指针”p充当了这个角色,链接起头结点和其它节点,使其成为完整的链表。

按序号查找
类似于数组中按下表找元素。链表的查找,其实就是递归的过程。

LNode *Get_Elem_L(LinKList L,int i){

LNode *p;
int j=1;
p=L->next;
while(p&&j<i) {
p=p->next;
++j;
}
if(j==i) return p;
else return null;
}

插入操作
修改指针域!!!

int LinKListInsert_L(LinKList &L,int i,ElemType x){   //在单链表L中第i个位置之前插入元素x 
LNode *p,*s;
p=Get_Elem_L(L,i-1);
if(!p) return error;
s=new LNode; //生成新节点
if(!p) return error;
s->date=x;
s->next=p->next; //插入L中
p->next=s;
return OK;
}

对于这个插入,我有必要画张图来“澄清”一下:

数据结构与算法:小叙链表_链表

删除操作
就是“跳跃隔离”,让指针直接指向要删除的节点的下一节点。

int LinKLisDelete_L(LinKList &L,int i){
LNode *p,*q;
p=GetElem_L(L,i-1);
if(!p) return ERROR;
q=p->next; p->next=q->next; //删除并释放节点
delete q;
return OK;
}

将单链表L置空
即将单链表L的所有节点所占空间释放掉。

void ClearLinKList(LinKList &L){
LNode *p;
while(L->next){
p=L->next;
L->next=p->next;
delete p;
}
}

单链表的建立

  1. 头插法
    单链表是一种动态的结构,它不需要分配空间。因此生成链表的过程是一个“主键插入”的过程。
    步骤: 建立空表 -> 建立新增节点 -> 新增节点插入(头结点和第一个节点之间) -> 重复前面两个步骤
void CreateLinKList(LinKList &L,int n){   //逆序输入n个数据元素,建立带头结点的单链表 
LNode *p;
InitLinKList(L); //建立空表
for(int i=n;i>0;i--){
p=new LNode; //新增节点
scanf("%d",&p->date); //输入元素值
p->next=L->next;
L->next=p; //插入到表头
}
}
  1. 尾插法
    其与头插法不同的是:新增节点插入在单链表的尾部,因此要设置一个工作指针r,让r始终指向链表的最后一个节点。
void DispLinKList(LinKList &L,int n){
LNode *p,*r;
InitLinKList(L);
r=L; //r始终指向表尾
for(int i=1;i<n;i++){
p=new LNode;
scanf("%d",&p->date);
r->next=p;
r=p;
}
r->nextNULL;
}

双向链表

双向链表:比单链表多了一个“方向”,其极大的便利了单链表中“单向”带来的一系列问题(如:只能从表头出发按指针顺序往后找),但是其复杂性却大大增加了。

双向链表的初始化

typedef struct DuLNode{
ElemType data; //数据域
struct DuLNode *prior; //指向前驱的指针域
struct DuLNode *next; //指向后继的指针域
}DuLNode,*DuLinKList;

这里一定要注意了:双向链表中强调了关于“域”的概念。这就说明:两个箭头,不一般。。。

void InitDuLinKList(DuLinKList &L){
L=new DuLNode;
if(!L) exit(overflow);
L->next=L->prior=NULL;
}

(这一部分有不懂的步骤完全可以参照前面“单链表”的注释)

按序号查找
没啥说的,顺序判断,找“i”节点。知道表遍历结束为止。

DuLNode *GetElem_L(DuLinKList L,int i){
DuLNode *p;
p=L->next;
int j=1; //p指向“头结点”,j为计数器
while(p!=NULL&&j<i){
p=p->next;
++j;
}
if(j==i) return p;
else return NULL;
}

插入操作
这个注意了啊,他是双向链表,每一个节点前后都有两条“指向”!!!
(看清下面步骤操作,最好在纸上画出来)

int DuLinKListInsert(DuLinKList &L,int i,ElemType x){
DuLNode *p,*s;
p=GetElem_L(L,i-1);
if(!p) return error;
s=new DuLNode; //生成新节点
s->data=x;
if(p->next){ //插在尾部以外的任何位置
p->next->prior=s;
s->next=p->next;
s->prior=p;
p->next=s;
}
else{ //插在L的尾部
s->next=p->next;
p->next=s;
s->prior=p;
}
return OK;
}

删除操作
删除时也要考虑“两条指向”的问题,不过它相对“插入”来说,已经好了很多了。

int DuLinKListDelete(DuLinKList &L,int i){
DuLNode *p,*q;
p=GetElem_L(L,i-1);
if(!p) return error;
if(p->next->next){
q=p->next;
q->next->prior=p;
p->next=q->next; //删除并释放节点
}
else{ //删除尾部节点
q=p->next;
p=p->next=NULL;
}
delete q;
return OK;
}

双向链表的建立
这一步简直与单链表的“头插法”一模一样,在此不再多说。
注意一点:p->prior=L; //双向!!!

链表的应用

例1:将两个有序表La和Lb归并成一个有序表,要求不另设新空间。
分析:因为不另设新空间,故新表要占用原表空间,故要比较大小,第一个小的数直接当做新表的“第一结点”。

void mergelist(LinKList &La,LinKList &Lb,LinKList &Lc){
LinKList pa,pb,pc;
pa=La->next;
pb=Lb->next;
Lc=pc=La;
while(pa&&pb){
if(pa->data<=pb->data) {
pc->next=pa;
pc=pa;
pa=pa->next;
}
else{
pc->next=pb;
pc=pb;
pb=pb->next;
}
}
if(!pa) pc->next=pb;
if(!pb) pc->next=pa;
delete Lb;
}

想一想:开头的La、Lb是引用表,还是引用头结点?

例2:在一个递增有序的链表中,有相同的元素存在,编写算法删去相同元素节点,使输出链表不再有重复元素。
提示:双指针法,同时进行,不断后移,寻找间距,消除间距。

void deletesame(LinKList &L){
LNode *pre,*p,*q;
pre=L->next; //pre永远作为p所指节点的前驱节点
p=pre->next; //工作指针p
while(p)
if(p->date==pre->date){
q=p;
p=p->next;
delete q;
}
else{ //递推向后移动——相互不断赋值
pre->next=p;
pre=p;
p=p->next;
} //一定注意上面三句的顺序!
pre->next=p;
}

这几天比较忙,循环链表会更新的。。。