【数据结构2】链表


由于顺序表的 插入删除操作需要移动大量元素,影响了效率。 链式存储不要求逻辑上相邻的两个元素在物理位置上也相邻,而是通过“链”建立起数据元素的逻辑关系。因此,在链表的插入和删除不需要移动元素,只需修改指针。
链式存储的线性表称为 链表。其中每个结点(Node)只包含一个数据域和一个指针域的链表称为 单链表,首尾相连的单链表称为 循环单链表。每个结点只包含一个数据域和两个指针域的链表称为 双链表,首尾相连的双链表称为 循环双链表。还有一种链表称为 静态链表,该链表也有数据域和指针域,这里的指针是结点相对地址(数组下标),又称为 游标(cur),静态链表和顺序表一样要预先分配一块连续的内存空间。

1单链表

1.1结点定义

通常用头指针来标识一个单链表,如单链表LLinkList L;),L=NULL时表示一个空表。因此,为了操作方便就在单链表的首元结点前附加一个结点,即头结点LinkList L=(LinkList)malloc(sizeof(LNode));)。头结点数据域可以不带今后任何信息,但可记录链表长度,头结点指针域则指向首元结点。此时判断带头结点为空的条件为:L->next==NULL
这里写图片描述
单链表结点定义为:

typedef struct LNode{	//定义单链表结点类型 
	ElemType data;		//数据域 
	struct LNode *next;	//指针域 
}LNode,*LinkList;		//LinkList相当于LNode*,即:struct LNode*

##1.2头插法建立单链表

头插法建立单链表

#include<stdio.h>		//NULL
#include<malloc.h>		//malloc
#define ElemType int
typedef struct LNode{	//定义单链表结点类型 
	ElemType data;		//数据域 
	struct LNode *next;	//指针域 
}LNode,*LinkList;		//LinkList相当于LNode*,即:struct LNode*
//头插法建立单链表 
LinkList ListCreat_L_FromHead(){
	LinkList L=(LinkList)malloc(sizeof(LNode));
	L->next=NULL;
	int e,length=0;
	printf("请输入插法建立单链表元素(以-1结束)\n");
	scanf("%d",&e);
	while(e!=-1){
		LNode *s=(LNode*)malloc(sizeof(LNode));
		s->data=e;
		s->next=L->next;
		L->next=s;
		length++;
		scanf("%d",&e);
	}
	L->data=length;		//令头结点记录链表长度 
	return L;
}
int main(){
	LinkList L= ListCreat _L_FromHead();
	printf("单链表元素个数:%d 分别是:",L->data);
	for(LNode* N=L->next;N;N=N->next){
		printf("%d ",N->data);
	}
	return 0;
}

##1.3尾插法建立单链表

尾插法建立单链表

#include<stdio.h>		//NULL
#include<malloc.h>		//malloc
#define ElemType int
typedef struct LNode{	//定义单链表结点类型 
	ElemType data;		//数据域 
	struct LNode *next;	//指针域 
}LNode,*LinkList;		//LinkList相当于LNode*,即:struct LNode*
//尾插法建立单链表 
void ListCreat _L_FromTail(LinkList &L){
	L=(LinkList)malloc(sizeof(LNode));
	L->data=0;L->next=NULL;	//L->data为链表长度 
	LNode *r=L;				//r为表尾指针 
	printf("请输入插法建立单链表元素(以-1结束)\n");
	int e;scanf("%d",&e);
	while(e!=-1){
		LNode *s=(LNode*)malloc(sizeof(LNode));
		s->data=e;s->next=NULL;
		r->next=s;
		r=r->next;
		L->data++;			//链表长度+1
		scanf("%d",&e);
	}
}
int main(){
	LinkList L;
	ListCreat _L_FromTail(L);
	printf("单链表元素个数:%d 分别是:",L->data);
	for(LNode* N=L->next;N;N=N->next){
		printf("%d ",N->data);
	}
	return 0;
}

##1.4按序号查找表结点

//按序号查找第i个结点 
LNode* ListGetElem_L (LinkList L,int i){
	LNode* p=L->next;		//p为首元结点
	if(i==0)return L;		//返回头结点
	if(i<0)return NULL;		//i无效返回NULL
	for(int j=1;j<i&&p;j++){
		p=p->next;
	} 
	return p;				//返回第i个结点指针;若i>表长则返回的是NULL 
}

##1.5按值查找表结点

//按值e查找表结点 
LNode* ListLocate_L (LinkList L,int e){
	LNode* p=L->next;		//p为首元结点
	while(p->data!=e&&p){
		p=p->next;
	} 
	return p;				//返回第i个结点指针;若i>表长则返回的是NULL 
}

##1.6插入结点操作
在第i个位置插入结点要先查找插入位置的前驱结点,单链表插入要执行两步必要操作:

  • 把新结点s挂接后继结点并赋值;
  • 把新结点挂在前驱结点p。

这里写图片描述

//在第i个位置插入结点操作 
bool ListInsert_L(LinkList &L,int i,int e){
	LNode* p= ListGetElem_L(L,i-1);	//查找插入位置的前驱结点 
	if(p==NULL)return false;	//i定位无效,插入失败返回false 
	LNode* s=(LNode*)malloc(sizeof(LNode));	//为新值分配结点空间 
	s->next=p->next;			//1.把新结点s挂接后继结点p->next
	s->data=e;					// 新结点赋值
	p->next=s;					//2.把新结点s挂在前驱结点p
	return true;				//插入成功返回true 
}

##1.7删除结点操作
删除第i个结点要先查找删除位置的前驱结点,单链表删除要执行也两步必要操作:

  • 将前驱结点p指向预删除结点的后继结点;
  • 释放预删除结点空间。

这里写图片描述

//删除第i个结点操作 
bool ListDelete_L(LinkList &L,int i,int &e){
	LNode* p=ListGetElem_L(L,i-1);	//查找删除位置的前驱结点 
	if(p||p->next)return false;	//i定位无效,删除失败返回false 
	LNode* s=p->next;			//将s指向预删除结点p->next
	e=s->data;					//将预删除结点s的值赋给e引用传回
	p->next=p->next->next;		//将前驱结点p指向预删除结点的后继结点
	free(s);					//释放预删除结点空间
	return true;				//删除成功返回true 
}

##1.8合并有序链表
将两个有序单链表La和Lb合并为一个有序单链表Lc。

//合并有序链表 
void ListMerge_L(LinkList &La,LinkList &Lb,LinkList &Lc){
	LNode* pa=La->next,*pb=Lb->next;
	LNode* pc=Lc=La;				//用La头结点作为Lc的头结点 
	Lc->data=La->data+Lb->data;		//Lc的长度为La长度与Lb长度之和
	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;
		}
	} 
	pc->next=pa?pa:pb;				//插入剩余段 
	free(Lb);						//释放Lb的头结点 
}

#2循环双链表
循环双链表定义头结点要维护循环双链表规则,即:L->next=L->prior=L;。若DLNode *p=L->next;…;p=p->next;,则判断循环双链表为空的条件是p==L;
##2.1结点定义
这里写图片描述

#include<stdio.h>
#include<stdlib.h>
#define ElemType int
typedef struct DLNode{
	ElemType data;
	struct DLNode *prior,*next;
}DLNode,*DLinkList;

int main(int argc,char ** argv ){
	DLinkList L=(DLinkList)malloc(sizeof(DLNode));
	L->next=L->prior=L;		//维护循环双链表规则 
	return 0;
}

##2.2插入和删除操作
这里写图片描述

//双循环双链表第i个位置插入e 
bool ListInsert_DL(DLinkList &L,int i,ElemType e){
	if(i<=0)return false;
	DLNode *p=L;
//	for(int j=1;j<i;j++,p=p->next);			//方式一:p是s的前驱 
	for(int j=0;j<i;j++,p=p->next);			//方式二:p是s的后继 
	DLNode* s=(DLNode*)malloc(sizeof(DLNode));
	if(!s)return false;
	s->data=e;
//	s->next=p->next;s->prior=p->next->prior;//方式一:p是s的前驱 
//	p->next->prior=s;p->next=s;
	s->prior=p->prior;p->prior->next=s;		//方式二:p是s的后继 
	s->next=p;p->prior=s;
	return true;
}

#3循环单链表
#4带尾指针的循环单链表
#5静态链表
静态链表的插入、删除操作和动态链表的相同,只需修改指针(游标),而不需移动元素。但静态链表使用没有比单链表的方便,但对于一些不支持指针的高级语言(Basic语言)又是一种巧妙的设计方法。
我们用一个例子来说明静态链表的用法。现求集合(A-B)||(B-A)的元素,即遍历B中元素,如果A中没有该元素则插入A,否则删除从A中该元素。
这里写图片描述

#include<stdio.h>
#define MAXSIZE 11 				//减去空闲链表和占用链表两个头指针,有MAXSIZE-2个空闲可以分配 
#define ElemType char
typedef struct{
	ElemType data;				//值域 
	int cur;					//游标 
}component,SLinklist[MAXSIZE];

	int s;
	SLinklist SL;
void Print();	//声明 Print函数 

/*初始化一维数组space为空闲链表,space[0].cur为头指针*/
void InitSpace_SL(SLinklist &space){
	for(int i=0;i<MAXSIZE;i++)
		space[i].cur=i+1;
	space[MAXSIZE-1].cur=0;	
}
/*从空闲链表分配一个空间以下标i返回,空闲链表空间不够则返回0*/
int Malloc_SL(SLinklist &space){
	int i=space[0].cur;
	if(space[0].cur)space[0].cur=space[i].cur;
	return i;
}
//回收下标为k的空闲结点到空闲链表 
void Free_SL(SLinklist &space,int k){
	space[k].cur=space[0].cur;
	space[0].cur=k;
}

/*求集合(A-B)||(B-A)*/
void difference(SLinklist &space,int &S){
	InitSpace_SL(space);			//初始化空闲链表 
	S=Malloc_SL(space);				//生成占用链表S的头结点 
	int m,n,r=S;					//r表示占用链表S当前最后结点 
	printf("input the number of A and B: ");
	scanf("%d %d",&m,&n);getchar();	//输入A和B的元素个数 
	
	/*将A元素加入占用链表 */
	for(int j=1;j<=m;j++){			
		int i=Malloc_SL(space);
		printf("input the data of A one by one with a Enter: ");
		scanf("%c",&space[i].data);getchar();//
		space[r].cur=i;		r=i;
		//Print();//
	}
	space[r].cur=0;					//尾结点指针域为0 
	Print();//
	
	/*依次输入B的元素,若不在当前占用链表S中,则插入;否则删除 */
	for(int j=1;j<=n;j++){			
		printf("input the data of B one by one with a Enter: ");
		ElemType b;scanf("%c",&b);getchar();//
		int p=S;
		int k=space[S].cur;
		while(k!=space[r].cur&&space[k].data!=b){
			p=k;k=space[k].cur;	//p表示k的前一个元素 
		}
		if(k==space[r].cur){	//该元素不在占用链表S中,插入之 
			int i=Malloc_SL(space);
			space[i].data=b;
			space[i].cur=space[r].cur;
			space[r].cur=i;
			r=i;				//表尾r指向最新元素 
		}else{					//该元素已在占用链表S中,删除之 
			space[k].data=' ';	//把删除的值清空 
			space[p].cur=space[k].cur;
			Free_SL(space,k);
			if(r==k)r=p;		//若删除的是r所结点,则需修改尾指针 
		}
		
		Print();//
	}//for
}

int main(int argc,char ** argv){
	difference(SL,s);
	printf("data head index s: %d\n",s);
	return 0;
}

/*定义 Print函数 */
void Print(){
	printf("index:\t");
	for(int i=0;i<MAXSIZE;i++){
		printf("%d\t",i);
	}printf("\n");
	printf("data:\t");
	for(int i=0;i<MAXSIZE;i++){
		printf("%c\t",SL[i].data);
	}printf("\n");
	printf("cur:\t");
	for(int i=0;i<MAXSIZE;i++){
		printf("%d\t",SL[i].cur);
	}printf("\n\n");
	printf(":----------------------------------\n");

}

Wu_Being 博客声明:本人博客欢迎转载,请标明博客原文和原链接!谢谢!
【数据结构系列】《【数据结构2】链表》http://blog.csdn.net/u014134180/article/details/54091157
我的GitHub代码文件:https://github.com/1040003585/Data_Structure

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值