(数据结构初阶)顺序表&链表

目录

一 顺序表

①顺序表的结构

②顺序表的初始化和销毁

③顺序表增容

④顺序表数据插入

⑤顺序表的数据删除

⑥顺序表中数据的查找

⑦指定位置插入数据(可复用,优化头插与尾插)

⑧删除指定位置的数据(可复用,优化头删与尾删)

⑨顺序表修改pos位置的值

二 无头、单向、非循环链表链表

①结构

②动态申请一个节点

③单链表打印

④单链表头插尾插

⑤单链表头删尾删

⑥单链表数据查找(兼具修改的作用)

⑦单链表在指定位置之后插入数据

⑧单链表删除在指定位置之后的数据

三 带头双向循环链表的实现

1.双向链表结构与链表初始化

2.打印双向链表

3.链表的数据插入

4.链表数据的删除

5.链表数据查找与判断链表是否为空

6.求链表长度及销毁链表

四 顺序表与链表的优缺点


一 顺序表

1.顺序表本质就是数组,动态增长(顺序表的动态存储),并且要求里面的数据是从左向右连续存储的。顺序表逻辑结构与物理结构一致。

2.顺序表缺陷:(1)动态增容有性能消耗。(2)头部插入需要挪动数据。

3.顺序表优点:支持随机访问。

4.顺序表动态存储的实现。

①顺序表的结构

//顺序表(sequence list)
typedef struct SeqList
{
	SeqDateType* a;    //指向动态开辟的数组
	int size;          //有效数据的个数
	int capacity;      //容量
}SeqList,SEQ;    //将srtuct SeqList 替换为 SeqList或者SEQ

②顺序表的初始化和销毁

//顺序表初始化
void SeqListInit(SeqList* pq)
{
	assert(pq);

	pq->a =NULL;
	pq->size = pq->capacity = 0;
}
//顺序表的销毁
void SeqListDestory(SeqList* pq)
{
	assert(pq);

	free(pq->a);
	pq->a = NULL;
	pq->capacity = pq->size = 0;
}

③顺序表增容

//顺序表增容
void CheckCapacity(SeqList* pq)
{
	//满了需要增容
	if (pq->size == pq->capacity)
	{
		int newcapacity = pq->capacity == 0 ? 4 : pq->capacity * 2;
		SeqDateType * newA = (SeqDateType *)realloc(pq->a,sizeof(SeqDateType)* newcapacity);
		if (newA == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		pq->a = newA;
		pq->capacity = newcapacity;
	}
}

④顺序表数据插入

//头插
void SeqListPushFront(SeqList* pq, SeqDateType x)
{
	assert(pq);
	CheckCapacity(pq);

	int end = pq->size-1;
	while (end>=0)
	{
		pq->a[end + 1] = pq->a[end];
		end--;
	}
	pq->a[0] = x;
	pq->size++;
}
//尾插
void SeqListPushBack(SeqList* pq, SeqDateType x)
{
	assert(pq);
	CheckCapacity(pq);

	pq->a[pq->size] = x;
	pq->size++;
}

⑤顺序表的数据删除

//头删
void SeqListPopFront(SeqList* pq)
{
	assert(pq);
	assert(pq->size > 0);

	for(int i=0;i<pq->size;i++)
	{
		pq->a[i] = pq->a[i+1];
	}
	pq->size--;
}
//尾删
void SeqListPopBack(SeqList* pq)
{
	assert(pq);
	assert(pq->size > 0);

	--pq->size;
}

⑥顺序表中数据的查找

//查找数据
int SeqListFind(SeqList* pq, SeqDateType x)
{
	assert(pq);
	assert(pq->size > 0);
	for (int i = 0; i < pq->size; i++)
	{
		if (x == pq->a[i])
		{
			return i;
		}
	}
	return -1;
}

⑦指定位置插入数据(可复用,优化头插与尾插)

//指定位置插入数据
void SeqListInsert(SeqList* pq, int pos, SeqDateType x)
{
	assert(pq);
	CheckCapacity(pq);
	assert(pq->size>=pos && pos>=0);

	int end = pq->size - 1;
	while (end >= pos)
	{
		pq->a[end + 1] = pq->a[end];
		end--;
	}
	pq->a[pos] = x;
	pq->size++;
}

⑧删除指定位置的数据(可复用,优化头删与尾删)

//指定位置删除
void SeqListErase(SeqList* pq, int pos)
{
	assert(pq);
	assert(pq->size > 0 && pos >= 0);
	for (int i = pos; i < pq->size-1; i++)
	{
		pq->a[i] = pq->a[i+1];
	}
	pq->size--;
}

⑨顺序表修改pos位置的值

// 顺序表修改pos位置的值
void SeqListModify(SeqList* pq, int pos, SeqDateType x)
{
	assert(pq);
	assert(pos >= 0 && pos < pq->size);

	pq->a[pos] = x;
}

二 无头、单向、非循环链表链表

1.概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。通常作为哈希桶、图的邻接表的子结构使用

2.链表的优点:(1)空间按需索取。(2)头插不需要挪动数据。

3.缺点:不支持随机访问。

4.无头、单向、非循环链表的实现。

①结构

typedef int SLTDateType;

//单链表的结构
typedef struct SListNode
{
	SLTDateType date;   //存放单链表的数据
	struct SListNode* next;//存放下一个节点的地址
}SListNode;

②动态申请一个节点

//创建新的节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* node = (SListNode*)malloc(sizeof(SListNode));
	if (node!=NULL)
	{
		node->date = x;
		node->next = NULL;
		return node;
	}
}

③单链表打印

//打印单链表
void SListPrint(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur != NULL)
	{
		printf("%d->", cur->date);
		cur = cur->next;
	}
	printf("NULL\n");
}

④单链表头插尾插

//尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	//如果链表为空
	if (*pplist == NULL)
	{
		*pplist = newnode;
	}
	else   //不为空
	{
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}


//头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode= BuySListNode(x);
	newnode->next = *pplist;
	*pplist = newnode;
}

⑤单链表头删尾删

//尾删
void SListPopBack(SListNode** pplist)
{
	//链表为空
	if (*pplist == NULL) 
	{
		return;
	}
	else if((*pplist)->next==NULL)   //链表有一个节点
	{
		free(*pplist);
		*pplist = NULL;
	}
	else  //有多个节点
	{
		SListNode* pre = NULL;
		SListNode* tail = *pplist;
		while (tail->next != NULL)
		{
			pre = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		pre->next = NULL;
	}
}


//头删
void SListPopFront(SListNode** pplist)
{
	//链表为空
	if (*pplist == NULL)
	{
		return;
	}
	else   //不为空
	{
		SListNode* next =(*pplist)->next;
		free(*pplist);
		*pplist = next;
	}
}


⑥单链表数据查找(兼具修改的作用)

//单链表的查找
SListNode* SListPopFind(SListNode* plist, SLTDateType x)
{
	SListNode* cur = plist;
	while (NULL != cur)
	{
		if (x == cur->date)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


    //SListNode* pos=SListPopFind(plist, 6);
    //if (NULL != pos)
	//{
	//	printf("找到了\n");
	//	pos->date = 100;
	//}
	//else
	//{
	//	printf("没找到\n");
	//}
	//SListPrint(plist);

⑦单链表在指定位置之后插入数据

//指定位置之后插入数据
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
    assert(pos);
    SListNode* newnode = BuySListNode(x);
    newnode->next = pos->next;
    pos->next = newnode;
}

⑧单链表删除在指定位置之后的数据

//删除指定位置之后的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (NULL == pos->next)
	{
		return;
	}
	else
	{
		SListNode* next = pos->next;
		pos->next = next->next;
		free(next);
	}
}

三 带头双向循环链表的实现

1.双向链表结构与链表初始化

//结构
typedef int LTDateType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDateType date;
}ListNode;
//创建新的节点
ListNode* BuyListNode(LTDateType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	assert(node);
	node->next = NULL;
	node->prev = NULL;
	node->date = x;
	return node;
}
//链表初始化(创建头节点)
ListNode* ListInit()
{
	ListNode* phead = BuyListNode(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

2.打印双向链表

//打印双向链表
void ListNodePrint(ListNode* phead)
{
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d->", cur->date);
		cur = cur->next;
	}
	printf("\n");
}

3.链表的数据插入

//在指定位置之前插入数据
void ListInisert(ListNode* pos, LTDateType x)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* newnode = BuyListNode(x);

	pos->prev = newnode;
	newnode->prev = prev;
	prev->next = newnode;
	newnode->next = pos;
}

//尾插
void ListPushBack(ListNode* phead, LTDateType x)
{
	//assert(phead);
	//ListNode* tail = phead->prev;
	//ListNode* newnode = BuyListNode(x);

	//tail->next = newnode;
	//newnode->prev = tail;
	//newnode->next = phead;
	//phead->prev = newnode;

	ListInisert(phead, x);//复用替换
}

//头插
void ListPushFront(ListNode* phead, LTDateType x)
{
	//assert(phead);
	//ListNode* first = phead->next;
	//ListNode* newnode = BuyListNode(x);

	//newnode->next = first;
	//first->prev = newnode;
	//newnode->prev = phead;
	//phead->next = newnode;

	ListInisert(phead->next, x);//复用替换
}

4.链表数据的删除

//删除指定位置数据
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prev = pos->prev;
	ListNode* next = pos->next;
	free(pos);

	next->prev = prev;
	prev->next = next;
}

//尾删
void ListPopBack(ListNode* phead)
{
	//assert(phead);
	//assert(phead->prev!= phead);
	//ListNode* tail = phead->prev;
	//ListNode* tailprev = tail->prev;
	//free(tail);
	//tailprev->next = phead;
	//phead->prev = tailprev;

	ListErase(phead->prev);//复用
}

//头删
void ListPopFront(ListNode* phead)
{
	//assert(phead);
	//assert(phead->prev != phead);
	//ListNode* first = phead->next;
	//ListNode* second = first->next;
	//free(first);
	//phead->next = second;
	//second->prev = phead;

	ListErase(phead->next);//复用
}

5.链表数据查找与判断链表是否为空

//查找
ListNode* ListFind(ListNode* phead, LTDateType x)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur!=phead)
	{
		if (cur->date == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//判断链表是否为空,为空返回0,不为空返回1
int ListEmpty(ListNode* phead)
{
	assert(phead);
	return phead->next == phead ? 0 : 1;
}

6.求链表长度及销毁链表

//求链表的长度
int ListSize(ListNode* phead)
{
	assert(phead);
	int size = 0;
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

//销毁链表
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

四 顺序表与链表的优缺点

1.顺序表优点:①支持按下标随机访问。②CPU高速缓存命中率比较高。(物理空间是连续的)

2.顺序表缺点:①空间不够需要增容。(一定的性能消耗),可能存在一定的空间浪费。②头部或者中间插入删除数据,需要挪动数据,效率较低。(O(N))。

3.双向带头循环链表优点:①按需要申请内存,需要存一个数据,就申请一块内存,也就不存在空间浪费。②任意位置O(1)时间内插入删除数据。

4.双向带头循环链表缺点:不支持带下标的随机访问。

5.总结:这两数据结构是相辅相成的,互相弥补对方的缺点,结合使用场景来进行选择使用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zhang丶&|!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值