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

单链表

单链表是一种最简单的链式存储结构,可以看做是以“节点的序列”来表示线性表。
其中,每个结点包括两个域:存放数据元素信息的域,称为数据域;指针域用来存储直接后即的域。而指针域中存储的信息称为指针。由于这种链表只含有一个指针域,所以又称为:单链表
线性表的链式表示也可叫单链表。我们假设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;
}

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

相关文章:

  • 2021-07-29
  • 2021-06-23
  • 2021-11-26
  • 2021-05-29
  • 2021-09-17
  • 2021-07-28
  • 2021-08-27
  • 2021-04-25
猜你喜欢
  • 2021-04-24
  • 2021-06-11
  • 2021-04-14
  • 2021-06-02
  • 2022-03-03
  • 2022-12-23
相关资源
相似解决方案