数据结构-链式表之单链表

目录

一、定义:

1、链表特点:

2、链表内数据元素结构:

3、头节点、头指针

 二、程序实现

1、结构体定义

2、函数定义

3、创建结点

4、尾插函数

5、头插函数

6、尾删函数

7、头删函数

8、查找某一个值位置并插入值。

三、总结


一、定义:

逻辑结构上一个挨一个的数据,在实际存储时,并没有像顺序表那样也相互紧挨着。恰恰相反,数据随机分布在内存中的各个位置,这种存储结构称为线性表的链式存储。

1、链表特点:

        由于分散存储,为了能够体现出数据元素之间的逻辑关系,每个数据元素在存储的同时,要配备一个指针,用于指向它的直接后继元素,即每一个数据元素都指向下一个数据元素(最后一个指向NULL(空))。

2、链表内数据元素结构:

  1. 本身的信息,称为“数据域”;
  2. 指向直接后继的指针,称为“指针域”。

 

这两部分信息组成数据元素的存储结构,称之为“结点”。n个结点通过指针域相互链接,组成一个链表。

 图 3 中,由于每个结点中只包含一个指针域,生成的链表又被称为 线性链表 或 单链表。

3、头节点、头指针

头结点:有时,在链表的第一个结点之前会额外增设一个结点,结点的数据域一般不存放数据(有些情况下也可以存放链表的长度等信息),此结点被称为头结点。

若头结点的指针域为空(NULL),表明链表是空表。头结点对于链表来说,不是必须的,在处理某些问题时,给链表添加头结点会使问题变得简单。

 首元结点:链表中第一个元素所在的结点,它是头结点后边的第一个结点。

头指针:永远指向链表中第一个结点的位置(如果链表有头结点,头指针指向头结点;否则,头指针指向首元结点)。

头结点和头指针的区别:头指针是一个指针,头指针指向链表的头结点或者首元结点;头结点是一个实际存在的结点,它包含有数据域和指针域。两者在程序中的直接体现就是:头指针只声明而没有分配存储空间,头结点进行了声明并分配了一个结点的实际物理内存。

单链表可以没有头节点,但是不能没有头指针。

 二、程序实现

1、结构体定义

typedef int SLTDataType;
typedef struct SeqList{
	SLTDataType data;    //数据元素
	struct SeqList* next;//指向下一个结构体的指针
}SeqList;

2、函数定义

SeqList* SeqListCreatNode(SLTDataType data);			//创建节点
void SeqListPushBack(SeqList** pphead,SLTDataType data);	//尾插函数
void SeqListPushFront(SeqList**phead,SLTDataType data);	//头插函数
void SeqListPopBack(SeqList**phead);		//尾删函数
void SeqListPopFront(SeqList**phead);		//头删函数
SeqList* SeqListFindPosition(SeqList*phead,SLTDataType data);	//删除某一个值前的数值
void SeqListPushPosition(SeqList**phead,SeqList*pos,SLTDataType data);//在某个数值前插入一个数
void SeqListShow(SeqList*phead);

3、创建结点

SeqList* SeqListCreatNode(SLTDataType data)   
{
	SeqList* temp=(SeqList*)malloc(sizeof(SeqList));
	temp->data=data;
	temp->next=NULL;
	return temp;	
}

4、尾插函数

插入结点前,若链表为空链表,则头指针(phead)的值为NULL。插入节点后,头指针(phead)为新结点的地址。并且,头指针的值在尾插函数外同步需要更新。若函数直接传递phead的值,此时只是传递的函数外的phead的拷贝值。因此,函数形参需要传递phead的地址值,这样才能做到更新函数外phead的值。

void SeqListPushBack(SeqList** phead,SLTDataType data)//尾插函数
{
	SeqList* newcode=SeqListCreatNode(data); //此时涉及到刚开始插入时无节点情况,即头指针为NULL
											//所以新建节点后必须把头指针地址改为第一个节点地址
	if(!*phead){
		*phead=newcode;                    //判断链表是否为空,与不空的区别是会改变头指针的值
	}
	else{
		//找指向NULL的尾节点
		SeqList* temp=*phead;
		while(temp->next!=NULL)
		{
			temp=temp->next;
		}
		temp->next=newcode;
	}
}

5、头插函数

void SeqListPushFront(SeqList**phead,SLTDataType data)	//头插函数
{
	SeqList* newcode=SeqListCreatNode(data);
	newcode->next=*phead;
	*phead=newcode;
}

6、尾删函数

void SeqListPopBack(SeqList**phead)//尾删函数
{
	
	if(*phead==NULL){                    //判断内部是否有数据进行删除,如果为空,
会造成对空指针取值,程序崩溃
		printf("无数据可删除");
		return;
	}else if((*phead)->next==NULL){    //如果只有一个数据,删除后必定会造成头指针
数值改变。即由原结点的地址变为NULL,因此需单独情况判断                                                                                                                                                           
		free(*phead);
		*phead=NULL;
	}else{                            //两个及以上数据,尾删后头指针不会改变,因此
又是另外的情况
		SeqList* cur,*pre;
		cur=pre=*phead;
	
		while(cur->next!=NULL){
		pre=cur;
		cur=cur->next;
	}
		pre->next=NULL;
		free(cur);
		cur=NULL;}
}	

7、头删函数

(1)无数据时不能删除。(2)只存在一个数据时,(*phead)->next等于NULL;多个数据时,(*phead)->next取到下一数据地址。都不会发生错误,所以可以合并为一种情况。

void SeqListPopFront(SeqList**phead)//头删函数
{
	if(*phead==NULL){                    //无数据时不能删除
			printf("无数据可删除");
			return;
		}
	else{//只存在一个数据时,(*phead)->next等于NULL;多个数据时,(*phead)->next取到下一数据地址。都不会发生错误,所以可以合并为一种情况。
		SeqList* cur=*phead;
		*phead=(*phead)->next;
		free(cur);                    //释放删除的结点
		cur=NULL;                     //删除结点的指针设为空
	}
		}

8、查找某一个值位置并插入值。

(1)查找不涉及更改指针,因此可传递phead的拷贝.函数返回查找到的指针。查找不到返回NULL。

SeqList* SeqListFindPosition(SeqList*phead,SLTDataType data)//查找某一个值的位置
{
	//首先,需要查找到该值的位置
	SeqList* cur=phead;
	while(cur){
		if(cur->data==data){
			return cur;
		}
		cur=cur->next;
	}
	printf("1未找到该数值");
	return NULL;
}

(2)根据查找到的指针进行插入

pos为NULL的情况通过第二段代码进行过滤,保证调用函数SeqListPopPosition时,pos不为空。

此时,类似于头插操作。可能会造成头指针值更改,因此需进行分类。

void SeqListPushPosition(SeqList**phead,SeqList*pos,SLTDataType data)
{
	if(pos==*phead){                    //pos值为头指针值时,即为头插,头指针值改变。
		SeqListPushFront(phead,data);
	}
	else{                                //返回其他位置,头指针值不变
		SeqList* pre=*phead;//两个指针一前一后,后指针找到插入位置后的元素地址,前指针则是插入前的位置地址。
		SeqList* cur=*phead;            //设置cur值等于头指针
		while(cur!=pos){                //cur值不为pos值时,继续查找要插入的位置
			pre=cur;
			cur=cur->next;
		}
		SeqList* newone=SeqListCreatNode(data);
		pre->next=newone;
		newone->next=cur;
		return;
	}
}
	if(!pos){
		printf("2未找到该值");
	}else{
		SeqListPushPosition(&phead,pos,30);
	}

三、总结

线性表的链式存储相比于顺序存储,有两大优势:

  1. 链式存储的数据元素在物理结构没有限制,当内存空间中没有足够大的连续的内存空间供顺序表使用时,可能使用链表能解决问题。(链表每次申请的都是单个数据元素的存储空间,可以利用上一些内存碎片)
  2. 链表中结点之间采用指针进行链接,当对链表中的数据元素实行插入或者删除操作时,只需要改变指针的指向,无需像顺序表那样移动插入或删除位置的后续元素,简单快捷。


链表和顺序表相比,不足之处在于,当做遍历操作时,由于链表中结点的物理位置不相邻,使得计算机查找起来相比较顺序表,速度要慢。因此有查找更方便的链表结构,采用双指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值