【顺序表和链表】

81 篇文章 2 订阅
65 篇文章 4 订阅

大家好,这里是针对顺序表和链表做的一些总结归类,主要是介绍了顺序表和链表的相关操作,特意整理出来一篇博客供我们一起复习和学习,如果文章中有理解不当的地方,还希望朋友们在评论区指出,我们相互学习,共同进步!

一:线性表

线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表,链表,栈,队列,字符串等。

线性表在逻辑上是线性结构,也就是说是一条连续的实线,但其实在物理结构上不一定连续,其中线性表在储存的时候,通常以数组和链式结构的形式存储。
在这里插入图片描述
在这里插入图片描述

二:顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

顺序表一般可以分为:
1):静态顺序表:使用定长数组存储元素。
在这里插入图片描述

2):动态顺序表:使用定长数组存储元素。
在这里插入图片描述

2.1 接口实现

静态顺序表只适用于我们明确知道需要存多少个数据的情况。静态顺序表的定长数组导致如果N定大了,空间开辟多了造成浪费,开辟少了又不够用。所以基于此通常现实中都是使用动态顺序表,根据需要动态分配空间大小。
实现动态顺序表:
SeqList.h头文件:相关函数,结构体的声明。

typedef int SLDataType;

// 动态的顺序表
typedef struct SeqList
{
	SLDataType* a;//指向动态开辟的数组
	int size;     // 存储数据个数
	int capacity; // 存储空间大小
}SL, SeqList;

void SeqListPrint(SeqList* psl);//顺序表打印

void SeqListInit(SeqList* psl);//顺序表的初始化

void SeqListDestroy(SeqList* psl);//顺序表销毁

void SeqListPushBack(SeqList* psl, SLDataType x);//顺序表尾插

void SeqListPopBack(SeqList* psl);//顺序表尾删

void SeqListPushFront(SeqList* psl, SLDataType x);//顺序表头插

void SeqListPopFront(SeqList* psl);//顺序表头删

int SeqListFind(SeqList* psl, SLDataType x);//顺序表查找

void SeqListInsert(SeqList* psl, size_t pos, SLDataType x);//顺序表在pos位置插入x

void SeqListErase(SeqList* psl, size_t pos);//顺序表删除pos位置的值

SeqList.c这部分我们只简单介绍头插和尾插元素的代码;
尾插元素代码

void SeqListPushBack(SeqList* psl, SLDataType x){
	assert(psl);

	if (psl->capacity == psl->size)
	{
		int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		//扩容
		SLDataType* tmp = realloc(psl->a, sizeof(SLDataType)*newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			psl->a = tmp;
			psl->capacity = newCapacity;
		}
	}
	psl->a[psl->size] = x;
	psl->size++;
}

头插元素代码

void SeqListPushFront(SeqList* psl, SLDataType x){
	
	assert(psl);

	if (psl->capacity == psl->size)
	{
		int newCapacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		SLDataType* tmp = realloc(psl->a, sizeof(SLDataType)*newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		else
		{
			psl->a = tmp;
			psl->capacity = newCapacity;
		}
	}
	for (int i = psl->size; i > 0; i--)
	{
		psl->a[i] = psl->a[i - 1];
	}
	psl->a[0] = x;
	psl->size++;
}

三:链表

链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑结构是通过链表中的指针链接依次实现的。
在这里插入图片描述
在这里插入图片描述
注意:

  • 从上图可以看出来,链式结构在逻辑上是连续的,但是在物理上不一定连续;
  • 数据域用来存储数据,指针域用于建立与下一个结点的联系,当此节点为尾节点时,指针域的值为NULL
  • 建立链表时无需预先知道数据总量的,可以随机的分配空间,可以高效的在链表中的任意位置实时插入或删除数据;

3.1 链表与数组的区别

数组:一次性分配一块连续的存储区域。
优点:随机访问元素效率高
缺点

  1. 需要分配一块连续的存储区域(很大区域,有可能分配失败);
  2. 删除和插入某个元素效率低;

链表:无需一次性分配一块连续的存储区域,只需分配n块节点存储区域,通过指针建立关系。
优点

  1. 不需要一块连续的存储区域;
  2. 删除和插入某个元素效率高;

缺点:随机访问元素效率低

3.2 结构体的自引用

  1. Question:请问结构体可以嵌套本类型的结构体变量吗?
  2. Question:请问结构体可以嵌套本类型的结构体指针变量吗?
typedef struct _STUDENT{
	char name[64];
	int age;
}Student;

typedef struct _TEACHER{
	char name[64];
	Student stu; //结构体可以嵌套其他类型的结构体
	//Teacher stu;
	//struct _TEACHER teacher; //此时Teacher类型的成员还没有确定,编译器无法分配内存
	struct _TEACHER* teacher; //不论什么类型的指针,都只占4个字节,编译器可确定内存分配
}Teacher;

  • 结构体可以嵌套另外一个结构体的任何类型变量;
  • 结构体嵌套本结构体普通变量(不可以)。本结构体的类型大小无法确定,类型本质:固定大小内存块别名;
  • 结构体嵌套本结构体指针变量(可以), 指针变量的空间能确定,32位, 4字节, 64位, 8字节;

3.3 链表的分类

3.3.1 带头和不带头链表

  • 带头链表
    在这里插入图片描述

  • 不带头链表

  • 在这里插入图片描述

3.3.1 单向链表,双向链表,循环链表

  • 单向链表
    在这里插入图片描述

  • 双向链表
    在这里插入图片描述

  • 循环链表
    宏观来讲首尾互通!

3.4 链表的实现

无头+单向+非循环链表增删查改实现
头文件

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#include<assert.h>


typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data; // val
	struct SListNode* next; // 存储下一个节点的地址
}SListNode, SLN;
//单链表打印
void SListPrint(SListNode* phead);
//单链表尾插
void SListPushBack(SListNode** pphead, SLTDataType x);
//单链表头插
void SListPushFront(SListNode** pphead, SLTDataType x);
//单链表尾删
void SListPopBack(SListNode** pphead);
//单链表头删
void SListPopFront(SListNode** pphead);
//单链表查找
SListNode* SListFind(SListNode* phead, SLTDataType x);
// 在pos位置之前插入
void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x);
// 删除pos 位置
void SListErase(SListNode** pphead, SListNode* pos);
// 在pos之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x);
// 删除pos位置后面的值
void SListEraseAfter(SListNode* pos);
//单链表的销毁
void SListDestroy(SListNode** pphead);

1:单链表打印:

void SListPrint(SListNode* phead){
	SListNode* cur = phead;
	if (phead == NULL)
	{
		printf("空链表\n");
	}
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2:申请新节点

SListNode* BuySListNode(SLTDataType x){
	SListNode* Newnode = (SListNode*)malloc(sizeof(SListNode*));
	if (Newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	else{
		Newnode->data = x;
		Newnode->next = NULL;
	}
	return Newnode;
}

3:尾插

void SListPushBack(SListNode** pphead, SLTDataType x){
	SListNode* Newnode = BuySListNode(x);

	if (*pphead == NULL)
	{
		*pphead = Newnode;
	}
	else{
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = Newnode;
	}
}

4:头插

void SListPushFront(SListNode** pphead, SLTDataType x)
{
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

5:尾删

void SListPopBack(SListNode** pphead){
	if (*pphead == NULL)
	{
		printf("Erase fail\n");
		exit(-1);
	}
	else
	{
		SListNode* PopBack = *pphead;
		while (PopBack->next->next != NULL)
		{
			PopBack = PopBack->next;
		}
		/*free(PopBack->next);
		PopBack->next = NULL;*/
	}
}

6:头删

void SListPopFront(SListNode** pphead){
	if (*pphead == NULL){//空链表情况
		printf("Erase fail\n");
		exit(-1);
	}
	else if ((*pphead)->next == NULL)//链表一个元素情况
	{
		free(*pphead);
		*pphead = NULL;
	}
	else{//多元素情况
		//SListNode* PopFront = *pphead;
		*pphead = (*pphead)->next;
		//free(PopFront);
		//PopFront = NULL;
	}
}

7:查找

SListNode* SListFind(SListNode* phead, SLTDataType x){
	assert(phead);
	SListNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x){
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

8:固定位置插入

void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x){
	assert(pphead);
	assert(pos);
	if (pos == *pphead)//pos是第一个节点
	{
		SListPushFront(pphead, x);
	}
	else//pos不是第一个节点
	{
		SListNode* Newnode = BuySListNode(x);
		SListNode* pre = *pphead;
		while (pre->next != pos)
		{
			pre = pre->next;
		}
		pre->next = Newnode;
		Newnode->next = pos;
	}
	
}

9:固定位置删除

void SListErase(SListNode** pphead, SListNode* pos){
	assert(pphead && pos);
	//pos是第一个节点
	if (pos == *pphead)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* pre = *pphead;
		while (pre->next != pos)
		{
			pre = pre->next;
		}
		pre->next = pos->next;
		/*free(pos);
		pos == NULL;*/
	}
}

10:销毁链表

void SListDestroy(SListNode** pphead){
	assert(pphead);
	SListNode* cur = *pphead;
	while (cur)
	{
		SListNode* next = cur->next;
		//free(cur);
		cur = next;
	}

	*pphead = NULL;
	//SListNode* prev = *pphead;
	//SListNode* cur = *pphead;
	//while (prev->next != NULL)
	//{
	//	cur = prev;
	//	prev = prev->next;
	//	//free(cur);
	//	cur->next = NULL;
	//}
	//*pphead = NULL;
	//free(prev);
}

3.5 双向链表的实现

带头+双向+循环链表增删查改实现
头文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#include<assert.h>

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;
//双向链表打印
void ListPrint(LTNode* phead);
//双向链表初始化
void ListInit(LTNode** pphead);
//LTNode* ListInit();
//创建新节点
LTNode* BuyLTNode(LTDataType x);
//双向链表尾插
void ListPushBack(LTNode* phead, LTDataType x);
//双向链表尾删
void ListPopBack(LTNode* phead);
//双向链表头插
void ListPushFront(LTNode* phead, LTDataType x);
//双向链表头删
void ListPopFront(LTNode* phead);
//双向链表查找
LTNode* ListFind(LTNode* phead, LTDataType x);

// 在pos之前插入
void ListInsert(LTNode* pos, LTDataType x);
//void ListInsert(int i, LTDataType x);

// 删除pos位置的节点
void ListErase(LTNode* pos);

1:打印

void ListPrint(LTNode* phead){
	assert(phead);
	LTNode * cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

2:创建新节点

LTNode * BuyLTNode(LTDataType x){
	LTNode * Newnode = (LTNode *)malloc(sizeof(LTNode));
	if (!Newnode){
		printf("malloc fail\n");
	}
	Newnode->data = x;
	Newnode->next = NULL;
	Newnode->prev = NULL;

	return Newnode;
}

3:初始化

void ListInit(LTNode** pphead){
	assert(pphead);
	*pphead = BuyLTNode(0);
	(*pphead)->next = *pphead;
	(*pphead)->prev = *pphead;

}

4:尾插

void ListPushBack(LTNode* phead, LTDataType x){
	assert(phead);
	//ListInsert(phead, x);
	LTNode * newnode = BuyLTNode(x);
	LTNode * tail = phead->prev;//找尾

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

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

5:尾删

void ListPopBack(LTNode* phead){
	assert(phead);
	//ListErase(phead->prev);
	LTNode * tail = phead->prev;
	tail->prev->next = phead;
	phead->prev = tail->prev;
	free(tail);
	tail = NULL;
}

6:头插

void ListPushFront(LTNode* phead, LTDataType x){
	assert(phead);
	//ListInsert(phead->next, x);
	LTNode * newnode = BuyLTNode(x);

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

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

7:头删

void ListPopFront(LTNode* phead){
	assert(phead);
	//ListErase(phead->next);
	LTNode * popfront = phead->next;

	phead->next = popfront->next;
	popfront->next->prev = popfront->prev;

	free(popfront);
	popfront = NULL;
}

8:查找

LTNode* ListFind(LTNode* phead, LTDataType x){
	assert(phead);
	LTNode * cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

9:固定位置前一个插入

void ListInsert(LTNode* pos, LTDataType x){
	assert(pos);
	LTNode * newnode = BuyLTNode(x);
	LTNode * posfront = pos->prev;

	posfront->next = newnode;
	newnode->prev = posfront;

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

10:删除固定位置

void ListErase(LTNode* pos){
	assert(pos);
	LTNode * posfront = pos->prev;
	LTNode * postail = pos->next;

	posfront->next = postail;
	postail->prev = posfront;

	free(pos);
	pos = NULL;
}

注:

  • 最终头删尾删都可以用ListErase函数完成
  • 最终头插尾插都可以用ListInsert函数完成

有无哨兵位决定了是否需要二级指针。
带哨兵位的双向循环链表,删除插入操作如果需要改变,改变的也是哨兵位的那个结构体,所以传这个结构体的地址。
不带哨兵位的单链表,改变的是结构体的指针,所以需要改变时传结构体指针的地址,需要用二级指针来接收!

四:顺序表和链表的区别

引自比特科技课件此处引用于比特科技课件

深深的感谢您能够看到这里,祝您生活愉快,事业学习进步!!!

  • 27
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 25
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值