单链表LinkList定义与操作

/*
1、单链表的优点:方便插删,但插入,删除速度为O(n) (因为前提是需要进行查找后再插删,查找的复杂度为O(n))
2、单链表的缺点:查找,存取(查找基础上)的速度为O(n)(绝大部分操作都基于查找之上)
3、单链表有”动态链表“(指针)和“静态链表”(数组)两种实现方式,单链表更注重一个结点的位置(指针或者索引值),而不注重列表的位序
4、“循环链表”与“单链表”的区别为最后一个结点指向头结点(头结点不存储数据),循环时要判断是否指向头指针来结束循环。
   也有循环链表不包含头结点,只有一个尾指针(更方便两个循环链表的合并)
5、链表一般都具有一个头结点,头结点不存储数据,空表正好可以用一个头结点表示,同时在头结点的基础上还可以存在一个表头,
   用于存储该链表的信息(如链表长度,链表头结点地址以及尾结点地址等等)




*/



#include<stdio.h>
#define OK 1
#define ERROR 0
typedef int Status;//表示返回的状态值 
typedef int ElemType;







//”动态“单链表的结构体定义
//结构体定义完后声明变量需带上struct关键字
//typedef的作用是将数据类型用一个标志符代替,此处的结构体也是一种数据类型
//该typedef定义了两种类型,一种是该结构体类型Node(用于表示节点),一种是该结构体指针类型LinkList(用于表示链表的头结点)
//Node* 就等于 LinkList
typedef struct Node //此处使用非匿名定义结构体是因为 结构体内部 需要用到该结构体
{
	//数据域,该结点存储的数据
	ElemType data;
	//指针域,指向下一个结点的指针
	struct Node *node;
}Node,*LinkList;      //两种定义





//从表尾到表头逆向建立一个长度为n的空的单链表(头结点相当于一个中转站)
//这种方式创建链表比从表头向表尾创建更方便
//
void CreateLinkList(LinkList &L,int n)//因为要修改指针的指向,因此此处需要&来返回数据到实参中
{
	//创建一个结点指针
	Node *p;
	//给结点头开辟内存
	L=(Linklist)malloc(sizeof(Node));
	L->node=NULL;//先指空
	int i=1;
	for(;i<=n;i++)
	{
		p=(Node*)malloc(sizeof(Node));//开辟一个结点的内存
		//插入结点,顺序是越先进越靠后
		p->node=L->node;
		L->node=p;
	}
	
	
	
}





//从链表中得到第i位的元素,即数据的存取
//算法循环次数由i决定,平均时间复杂度为O(n)
Status GetElem(LinkList L,int i,ElemType &e)
{
	
	//获得第一个结点
	Node *p=L->node;
	int j=1;
	while(j<i&&p)//若p为空则退出,p为空即到达指针尾部
	{
	    p=p->node;	
		j++;
	}
	if(!p)//如果为空
	{
		return ERROR;//表明超出检索范围,返回错误状态
	}
	else
	{
		e=p->data;//将值返回给e
		return OK;//返回正确状态
	}
}



//在链表插入元素到第i个位置,最多插入到length+1的地方(末尾)
//由于插入之前需要进行查找,因此时间复杂度为O(n)
Status LinkListInsert(LinkList L,ElemType e,int i)
{
	Node* p=L; //获得头结点
	int j=0;//标记i-1位的结点
	Node* newNode=(Node*)malloc(sizeof(Node));//创建一个结点
	while(j<i-1&&p)
	{
		p=p->node;//得到下一个结点的地址
	    j++;//得到结点对应的序号
	}
	//循环结束后的p为指向i-1位结点的指针
	if(!p||i<1)//如果p为空表明i大于length+1,i必须大于0才可以插入
	{
		return ERROR;//报错插入失败
	}
	//给结点赋值
	newNode->data=e;
	//插入结点
	newNode->node=p->node;
	p->node=newNode;
	//返回插入成功
	return OK;

}


//删除单链表中第i位元素的结点,并返回其值
//删除结点同样要进行查找,因此时间复杂度为O(n)
Status LinkListDelete(LinkList L,int i,ElemType &e)
{
	int j=0;
	Node* p=L;
	//循环找第i-1位的结点的地址p
	while(j<i-1&&p)
	{
		p=p->node;
		j++;
	}
	if(!p||i<1)
	{
		return ERROR;
	}
	//获取删除的元素值
	e=p->node->data;
	//删除结点
	p->node=p->node->node;
	//释放被删除结点内存
	free(p->node);
	//返回删除成功
	return OK;
}



//合并两个非递减顺序排序的单链表
//实质上是对表链的指针导向进行更改形成一条新的路线
//相比列表的合并的优点为:时间复杂度差不多,但是空间复杂度却更小,几乎不用多余空间
//时间复杂度取决于两个表的表长,所以平均时间复杂度为O(n)
void MergeLinkList(LinkList &L1,LinkList &L2,LinkList &L3)//因为要释放L1和L2,因此需要&传递回去将L1L2指针指空
{
	Node *p1,*p2,*p3;
	p1=Li->node;
	p2=L2->node;
	p3=L3=(LinkList)malloc(sizeof(Node));
	//循环比较大小
	while(p1&&p2)//两个同时不为空时继续
	{
		if(p1->data>p2->data)
		{
			//若p2的元素比较小,则p3指向p2的结点指针,同时p3前进一步变成p2的结点指针,
			//p2则更进一步变成下一个结点的指针,因此上一个指针就被P3拿走了,
			//下一次循环p3再决定这个结点接下来指向哪一个结点
			p3=p3->node=p2;
			p2=p2->node;
		}
		else
		{
			p3=p1->node=p1;
			p1=p1->node;
		}
	}
	//插入剩余段
	p3->node=p1?p1:p2;//如果p1没有空则指向p1
	//释放之前的头结点的内存
	free(L1);
	free(L2);
}





//-------------------------------------------------------------------------------------





//定义最大长度
#define MAXSIZE 1000

//”静态“单链表的结构体定义
//静态链表使用 数组结构 来实现,虽然空间上是静态的,且空间大小会提前开辟,但是每一个元素结点仍具有动态特性
//SLinkList相当于struct Node [MAXSIZE]结构体数组数据类型,其中数组的[0]位为头结点,data不存储,只在node中存储第一个结点的索引
typedef struct 	Node
{
	//数据域
	ElemType data;
	//指针域
	int node;
}SLinkList[MAXSIZE],Node;


//从静态单链表中取得第i位元素
//平均时间复杂度为O(n)
Status GetElem(SLinkList L,int i,ElemType &e)
{
	int k=L[0].node,j=1;//得到第一个结点的地址,和它对应的索引(排在第几位)
	while(j<i&&L[j].node!=0)
	{
		j++;
		k=L[k].node;//得到下一个结点的地址
	}
	if(L[j].node==0||i<1)
	{
		return ERROR;
	}
	e=L[k].data
	return OK;
	
}


//得到元素在链表中对应的位置
//时间复杂度为O(n)
int LocateElem(SLinkList L,ElemType e)
{
	int i=1,k=L[0].node;
	while(L[k].data!=e&&L[k].node!=0)
	{
		k=L[k].node;
		i++;
	}
	if(L[k].node==0)
	{
		return 0;
	}
	return i;
		
}


//得到元素在相对于SLinkList这个数组的索引值,没有则返回0
//时间复杂度为O(n)
int LocateElem(SLinkList L,ElemType e)
{
	int k=L[0].node;
	while(L[k].data!=e&&k)//当k为0时即遍历完毕,退出循环,返回0表示无此元素
	{
		k=L[k].node;
	}
	return k;
}






/*下面开始 ,L[0]将用作备用链表的头指针,链表地址则用自定义变量来指向
(第一次获得链表的头地址可以用mallocSLinkList函数来分配结点)

*/
//插入删除在最后的AUB函数中体现(通过备用链表和已使用的链表两条链表来实现,备用链表每次尾部或头部提供新的插入结点,
//备用链表也将删除的不用的结点插入备用链表的头尾)





//初始化静态链表(开始链表为空,备用链表为满,因此将所有的结点先串成一个备用链表)
//用L[0]来指向备用链表头地址,而链表的头地址用变量来指
//O(n)
void InitialSLinkList(SLinkList &L)
{
	int i=0;
	while(i<MAXSIZE)
	{
		L[i].node=i+1;
		i++;
	}
	L[i].node=0;
}


//给静态链表分配内存(相当于自己实现的malloc,实际上是从备用链表中取出一个结点用到链表中去)
//分配成功则返回分配的结点的地址
//O(1)
int MallocSLinkList(SLinkList &L)
{
	int i=L[0].node;//得到备用空间的头指针
	if(L[0].node)//若为0则表明备用空间为空
	{
	    L[0].node=L[i].node;
	}
	L[i].node=0;//分配的结点指向0
	return i;
}



//将地址为k的结点回收到备用链表中去
//O(1)
void FreeSLinkList(SLinkList &L,int k)
{
	L[k].node=L[0].node;//将新的结点插入到备用链表的头指针中去
	L[0].node=k;
}




//实现将A与B集合的交集剔除,再合并两个集合,即(A-B)U(B-A),而A和B通过scanf函数输入
//限定条件为:AB中没有重复元素
//k返回合并后的链表头地址
//平均时间复杂度为O(la*lb)
void AUB(SLinkList &L,int &k)
{
	int p,q,q1,la,lb,i,j;//r用于标记链表最后一个结点的地址 
	ElemType b;
	InitialSLinkList(L);//初始化链表将备用链表连成串
	//MallocSLinkList函数分配的新结点指向0
	k=MallocSLinkList(L);   //k存储链表头结点的地址,头结点不存储数据.
	p=k;       //p标记末尾结点地址用于插入结点,
	scanf(la,lb);   //输入两个集合的长度
	for(i=0;i<la;i++)   //将A集合插入到链表中去
	{
		p=L[p].node=MallocSLinkList(L);//链表指向一个新分配的结点,然后p向前进一步指向新的结点
		scanf(L[p].data);//输入一个元素到新的结点中
	}
	for(i=0;i<lb;i++)
	{
		scanf(b);//获得集合B的元素
		q=L[k].node;//指向第一个结点,q用于遍历链表
		q1=k;//q1指向q的前一个结点,主要在删除结点时,单链表不需要从头开始遍历到q-1这个结点
		while(L[q].node&&L[q].data!=b)//若未到达尾部(下个结点地址不为0)且元素不相等则继续循环
		{
			q=L[q].node;//指向下一个结点 
			q1=L[q1].node;
		}
		if(L[q].node==0)//即没有相等元素,此时将元素插入链表       (静态链表的插入)
		{
			//开辟结点,并将其插入到链表的结尾(由于MallocSLinkList分配的地址指向0,因此不需要改变新开辟的结点的指向)
			p=L[p].node=MallocSLinkList(L);
			L[p].data=b;
		}
		else//存在相等元素则将原链表中结点删除                (静态链表的删除)
		{
			L[q1].node=L[q].node;
			FreeSLinkList(L,q)//回收废弃的q这个结点
			if(q==p)
			{
				p=q1;//如果删除的是尾结点,则刷新用于下一次插入的p的地址
			}
		}
		
	}
	
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值