线性表:顺序表和链表的定义,接口实现,功能讲解,二者区别,分别适用于什么场景。

线性表

顺序表的定义

顺序表,我把它简单的理解为一个结构体里,存了两个成员,一个数组data(为了保存数据),一个整型sz(记录数组中数据的个数)。因为顺序表有连续排列的顺序,所以用数组保存数据更方便。定义代码如下:

typedef int DATA_TYPE;
typedef struct PSeqList
{
	DATA_TYPE data[MAX];
	int sz;
}SeqList,*PSeqList

用typedef可以使顺序表保存各种类型的数据。

顺序表的接口

1.顺序表的初始化

顺序表初始化的时候,需要把记录数据的整型 sz 初始化为0,也可以把所有数据都初始化为0。

void InitSeqList(PSeqList pSeq)
{
	assert(pSeq != NULL);
	pSeq->sz = 0;
	memset(pSeq->data,0,sizeof(pSeq->data));
}
2.顺序表的尾插和尾删

顺序表的尾插需要把数据放入当前的数组的尾部保存起来,而顺序表的sz成员,sz-1就是当前数组尾部的下下表可以直接保存,保存完给sz++,要注意的是数组不能越界。

void PushBack(PSeqList pSeq, DATA_TYPE d)
{
	assert(pSeq != NULL);
	if(pSeq->sz >= MAX)
	{
		printf("顺序表已满,无法添加!\n");
		return;
	}
	pSeq->data[pSeq->sz]=d;
	pSeq->sz++;
}

顺序表的尾删也是只需要删除数组的尾部,不过我们只需要减少sz的值,sz–,不需要改变数组中的数,因为sz就代表数组中的数,sz–已经证明数组中的最后一位丢失了。

void PopBack(PSeqList pSeq)
{
	assert(pSeq != NULL);
	if(pSeq->sz == 0)
	{
		printf("顺序表为空,无法删除!\n");
		return;
	}
	pSeq->sz--;
}
3.顺序表的头插和头删

道理和尾删一样,不过需要放在数组的头部,也就是新插入的数据下标是0,并且以前的数据不能丢失,而且顺序不能改变,这样就比较麻烦了,因为我们需要讲下标为0开始的数据每一个往后挪动一位,才能正确插入新数据。记住sz++,还有顺序表满的情况。

void PushFront(PSeqList pSeq, DATA_TYPE d)
{
	int i;
	assert(pSeq != NULL);
	if(pSeq->sz >= MAX)
	{
		printf("顺序表已满,无法添加!\n");
		return;
	}
	for(i=pSeq->sz ;i>0 ;i--)
	{
		pSeq->data[i] = pSeq->data[i-1];
	}
	pSeq->data[i] = d;
	pSeq->sz++;
}

头删就是将数组中下标为0的数据移除掉,麻烦的是,必须将下标0之后的所有数据都向前移一位,记得sz–,和顺序表为空的情况。

void PopFront(PSeqList pSeq)
{
	int i;
	assert(pSeq != NULL);
	if(pSeq->sz == 0)
	{
		printf("顺序表为空,无法删除!\n");
		return;
	}
	for(i=0 ;i<pSeq->sz-1 ;i++)
	{
		pSeq->data[i] = pSeq->data[i+1];
	}
	pSeq->sz--;
}
4.顺序表的随机插入和删除

和上面的类似,直接看的代码

void Insert(PSeqList pSeq, int pos, DATA_TYPE d)
{
	int i;
	assert(pSeq != NULL);
	if(pSeq->sz >= MAX)
	{
		printf("顺序表已满,无法添加!\n");
		return;
	}
	for(i=pSeq->sz ;i>pos ;i--)
	{
		pSeq->data[i] = pSeq->data[i-1];
	}
	pSeq->data[pos] = d;
	pSeq->sz++;
}

void Erase(PSeqList pSeq, int pos)
{
	int i;
	assert(pSeq != NULL);
	if(pSeq->sz == 0)
	{
		printf("顺序表为空,无法删除!\n");
		return;
	}
	for(i=pos ;i<pSeq->sz-1 ;i--)
	{
		pSeq->data[i] = pSeq->data[i+1];
	}
	pSeq->sz--;
}

除了这些最基本的接口还需要一些接口,比如说查找,删除元素,判断顺序表是否为空。

链表的定义

链表也可以用来保存数据,并且对数据进行操作,和顺序表不同的是,顺序表所使用的空间是连续的数组,而链表是随机分配的空间,用指针互相连接起来。链表的各个结点也是一个结构体,它里面有两个成员变量,一个用来保存数据。另一个是一个指针,指向下一个结点。结构体代码如下:

typedef int DATA_TYPE;
typedef struct Node
{
	DATA_TYPE data;          //用来保存数据
	struct Node * next;     //指向下一个结点地址的指针
}Node,*pNode,List,*pList;

链表在计算机中保存方式可以图解为下面这样:
这里写图片描述
上图是带有头结点的链表的表示方式,链表还有不带头结点的,只需要把第一个那个没有保存数据的结点换成一个pNode结点类型的指针指向第一个data数据就好了。

链表的接口

1.链表的初始化

链表的初始化很简单,分为两种
1.如果不是带有头结点的就直接给结点类型的指针赋值为NULL;

void InitLinkList(pList* pplist)
{
	*pplist = NULL; 
	//这里用二级指针是因为不带头结点的链表,可能需要改变指向第一个结点的指针的指向
}

2.如果是带有头结点的,就需要申请一块结点结构体大小的空间,然后让它的next指针指向NULL。

void InitLinkList(pList pplist)
{
	pplist = (pList)malloc(sizeof(List));   
//里改变第一个结点的指向,只会改变头结点的next指向,头结点不需要改变,所以不用二级指针
}

(一下均以无头结点的链表为例)

2.链表的尾删和尾增

链表的尾部操作很简单只。
尾增需要让指向头结点的指针,找到尾部结点,然后将原本指向NULL的next指针指向一个新开辟的数据为需要增加的数的结点空间。

void PushBack(pList* pplist,DATA_TYPE d)
{
	pNode newNode= (pNode)malloc(sizeof(Node));
	pList cur = *pplist;
	newNode->data = d;
	newNode->next = NULL;
	if(*pplist == NULL)
	{
		*pplist = newNode;  //注意当链表为空时,直接把新结点赋值给链表的头指针
		return;
	}
	while(cur->next != NULL)
	{
		cur = cur->next;
	}
	cur->next = newNode;
}

尾删没有新数据,所以只需要找到倒数第二个结点和最后一个结点,然后让倒数第二个结点的next指针指向NULL,然后free掉最后一个结点。

void PopBack(pList *pplist)
{
	pNode cur = *pplist;
	pNode ret = *pplist;
	while(cur->next != NULL)   
	{
		ret = cur;
		cur = cur->next;
	}
	free(ret->next);
	ret->next = NULL;
}
3.链表的头删和头增

链表的头部操作需要改变链表头指针的指向,比如头增,需要将创建的新结点的next指向第一个结点,然后将链表的头指针指向新结点,达到连接的效果。

void PushFront(pList* pplist,DATA_TYPE d)
{
	pNode newNode = (pNode)malloc(sizeof(Node));
	newNode->data = d;
	newNode->next = *pplist;
	*pplist = newNode;
}

头删,需要让链表的头指针指向原链表第一个结点的next,也就是第二个结点,达到删除第一个结点的效果。

void PopFront(pList *pplist)
{
	pNode cur = *pplist;
	*pplist = cur->next;
	free(cur);
	cur = NULL;
}
4.链表的指定删除和增加

链表的指定增加我们用图来看更加直观:
这里写图片描述

void Insert(pList *pplist,DATA_TYPE c,DATA_TYPE d)
{
	pList cur = *pplist;
	pNode ret = *pplist;
	pNode newNode = (pNode)malloc(sizeof(Node));
	newNode->data = d;
	newNode->next = NULL;
	if((*pplist)->data==c)
	{
		newNode->next = *pplist;    //当链表为空的时候,将新结点直接赋值给链表的头指针	
		*pplist = newNode;
		return;
	}
	while(cur->data != c)
	{
		ret = cur;
		cur = cur->next;
	}
	ret->next = newNode;
	newNode->next = cur;
}

链表的指定删除,我们也用图解:
这里写图片描述

void Erase(pList* pplist, pNode pos)
{
	pNode cur = *pplist;
	pNode ret = NULL;
	assert(pplist);
	assert(pos);
	while(cur != pos)
	{
		ret = cur;
		cur = cur->next;
	}
	ret->next = cur->next;
	free(cur);
	cur = NULL;
}

还有一些接口,比如查找,删除指定元素,上面这个叫单链表,甚至还有双向链表和环链表,后面有时间再写吧。

链表和顺序表的区别和应用场景

1.顺序表支持随机访问,单链表不支持随机访问。
2.顺序表的插入/删除数据非常麻烦,时间复杂度为O(n),
单链表的插入/删除效率高,时间复杂度为O(1);
3.顺序表的CPU高速缓存效率更高,单链表CPU高速缓存效率低(频繁的申请空间,容易造成内存碎片)。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值