【数据结构(C语言版)】线性表

本文详细介绍了顺序表的静态和动态存储,以及动态顺序表的初始化、容量检查、插入、删除等操作。接着,讨论了单链表和带头双向循环链表的节点结构及相应的插入、删除、查找方法。最后,对比了顺序表和链表在存储、访问和操作效率上的差异。
摘要由CSDN通过智能技术生成

目录

1. 顺序表

1.1 顺序表的存储

1.1.1 顺序表的静态存储

1.1.2 顺序表的动态存储

1.2 动态顺序表的接口实现

1.2.1 初始化

1.2.2 检查容量

1.2.3 尾插

1.2.4 尾删

1.2.5 头插

1.2.6 头删

1.2.7 查找

1.2.8 插入

1.2.9 删除

1.2.10 打印

1.2.11 销毁

2. 单链表

2.1 单链表的结点

2.2 单链表的接口实现

2.2.1 申请结点

2.2.2 创建单链表

2.2.3 尾插

2.2.4 尾删

2.2.5 头插

2.2.6 头删

2.2.7 查找

2.2.8 插入

2.2.9 删除

2.2.10 打印

2.2.11 销毁

3. 带头双向循环链表

3.1 带头双向循环链表的结点

3.2 带头双向循环链表的接口实现

3.2.1 申请结点

3.2.2 初始化

3.2.3 尾插

3.2.4 尾删

3.2.5 头插

3.2.6 头删

3.2.7 查找

3.2.8 插入

3.2.9 删除

3.2.10 判空

3.2.11 表长

3.2.12 打印

3.2.13 销毁

4. 顺序表和链表的比较


1. 顺序表

1.1 顺序表的存储

1.1.1 顺序表的静态存储

#define N 10000      // 顺序表的最大长度
typedef int SLDataType;
typedef struct SeqList
{
	SLDataType a[N]; // 顺序表的元素
	int size;        // 顺序表的当前长度
}SL;

1.1.2 顺序表的动态存储

typedef int SLDataType;
typedef struct SeqList
{
	SLDataType* a; // 指示动态分配数组的指针
	int size;      // 顺序表的当前长度
	int capacity;  // 顺序表的容量
}SL;

1.2 动态顺序表的接口实现

1.2.1 初始化

void SLInit(SL* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = 0;
	ps->capacity = 0;
}

1.2.2 检查容量

void SLCheckCapacity(SL* ps)
{
	assert(ps);
	if (ps->size == ps->capacity) // 如果当前长度==容量,需要扩容
	{
		// 如果容量为0,则新容量设为4;如果容量不为0,则扩容2倍
        int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        // 扩容,开辟新空间
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));
        // 如果开辟空间失败,打印错误信息并退出程序
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
        // 如果开辟空间成功,将临时指针变量的值赋给原指针变量
		ps->a = tmp;
        // 改变容量的值为新容量
		ps->capacity = newCapacity;
	}
}

1.2.3 尾插

void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps); // 检查容量,满了则扩容
	ps->a[ps->size] = x; // 表尾插入元素
	ps->size++;          // 表的当前长度+1
}

1.2.4 尾删

void SLPopBack(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);
	ps->size--; // 表的当前长度-1
}

1.2.5 头插

void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps); // 检查容量,满了则扩容
	for (int i = ps->size; i >= 1; i--) // 所有元素后移
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x; // 表头插入元素
	ps->size++;   // 表的当前长度+1
}

1.2.6 头删

void SLPopFront(SL* ps)
{
	assert(ps);
	assert(ps->size > 0);
	for (int i = 1; i < ps->size; i++) // 除第一个元素外,所有元素前移
	{
		ps->a[i - 1] = ps->a[i];
	}
	ps->size--; // 表的当前长度-1
}

1.2.7 查找

int SLFind(SL* ps, SLDataType x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++)
	{
		if (ps->a[i] == x) // 查找第一个x
		{
			return i; // 查找成功,返回下标
		}
	}
	return -1; // 查找失败,返回-1
}

1.2.8 插入

下标(不是位序)为pos的位置插入元素

void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps);
	assert(pos >= 0 && pos <= ps->size);
	SLCheckCapacity(ps); // 检查容量,满了则扩容
	for (int i = ps->size; i > pos; i--) // 下标从pos到size-1的元素后移
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x; // pos下标的位置插入元素
	ps->size++;     // 表的当前长度+1
}

1.2.9 删除

删除下标(不是位序)为pos的位置的元素

void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos + 1; i < ps->size; i++) // 下标从pos+1到size-1的元素前移
	{
		ps->a[i - 1] = ps->a[i];
	}
	ps->size--; // 表的当前长度-1
}

1.2.10 打印

void SLPrint(SL* ps)
{
	assert(ps);
	for (int i = 0; i < ps->size; ++i)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

1.2.11 销毁

void SLDestroy(SL* ps)
{
	assert(ps);
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
		ps->size = ps->capacity = 0;
	}
}

2. 单链表

以下以无头单向非循环链表为例(头指哨兵位头)

2.1 单链表的结点

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;       // 数据域
	struct SListNode* next; // 指针域
}SLTNode;

2.2 单链表的接口实现

2.2.1 申请结点

SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); // 动态申请结点
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

2.2.2 创建单链表

单链表有n个结点:

SLTNode* CreateSList(int n)
{
	SLTNode* phead = NULL;
	SLTNode* ptail = NULL;
	int x = 0;
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &x); // 输入结点数据域的值
		SLTNode* newnode = BuySLTNode(x);
		if (phead == NULL)
		{
			ptail = phead = newnode; // 只有一个结点时,尾结点等于头结点
		}
		else
		{
			ptail->next = newnode; // 头结点不变,将新结点链接到原尾结点的尾部
			ptail = newnode;       // 新结点变成尾结点
		}
	}
	return phead;
}

2.2.3 尾插

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

如果形参是一级指针phead,那么当phead为空时,会令phead=newnode,但是对形参的修改不影响实参,所以要修改一级指针phead,形参就应该是二级指针pphead。要修改谁,就传谁的地址。

2.2.4 尾删

void SLTPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

2.2.5 头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

2.2.6 头删

void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

2.2.7 查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

2.2.8 插入

在pos位置之前插入x:

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pos);
	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

在pos位置之后插入x:

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

2.2.9 删除

删除pos位置的元素:

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

删除pos位置之后的元素:

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLTNode* nextNode = pos->next;
		pos->next = nextNode->next;
		free(nextNode);
	}
}

2.2.10 打印

void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

2.2.11 销毁

void SLTDestroy(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

3. 带头双向循环链表

3.1 带头双向循环链表的结点

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;

3.2 带头双向循环链表的接口实现

3.2.1 申请结点

LTNode* BuyListNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	node->data = x;
	node->next = NULL;
	node->prev = NULL;
	return node;
}

3.2.2 初始化

LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1); // 申请一个哨兵位头结点
	phead->next = phead; // next指向自己
	phead->prev = phead; // prev指向自己
	return phead;
}

3.2.3 尾插

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

3.2.4 尾删

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead); // 保证链表除了有哨兵位头结点,还有其他结点
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
}

3.2.5 头插

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* first = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

3.2.6 头删

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* first = phead->next;
	LTNode* second = first->next;
	free(first);
	phead->next = second;
	second->prev = phead;
}

3.2.7 查找

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

3.2.8 插入

在pos位置之前插入x:

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}

3.2.9 删除

删除pos位置的元素:

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}

3.2.10 判空

bool LTEmpty(LTNode* phead)
{
	assert(phead);

	/*if (phead->next == phead)
	{
		return true;
	}
	else
	{
		return false;
	}*/

	return phead->next == phead;
}

3.2.11 表长

size_t LTSize(LTNode* phead)
{
	assert(phead);
	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

3.2.12 打印

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

3.2.13 销毁

void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}

4. 顺序表和链表的比较

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持 O(1)不支持 O(n)
任意位置插入或删除元素可能需要搬移元素,效率低 O(n)只需修改指针指向
插入动态顺序表空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率
线性表是一种经常使用的基本数据结构,它可以用来存储一组具有相同数据类型的数据元素。在C语言中,线性表通常使用数组来实现。在实现线性表时,我们可以定义一个数组来存储元素,同时使用一个变量来记录线性表中元素的个数,以便于后续的插入、删除和查找等操作。 具体来说,线性表的实现可以包含以下几个主要的功能: 1. 初始化线性表:定义一个数组和一个变量,分别存储元素和元素个数,并将元素个数初始化为0,以便于后续操作。 2. 插入操作:向线性表中插入一个新的元素,具体实现可以包括以下步骤: - 判断线性表是否已满,若已满则不能再插入新元素,否则继续执行。 - 在线性表的末尾插入新元素,同时将元素个数加1。 3. 删除操作:从线性表中删除一个元素,具体实现可以包括以下步骤: - 判断线性表是否为空,若已空则不能再删除元素,否则继续执行。 - 根据用户指定的位置删除元素,并将后续元素向前移动一个位置,同时将元素个数减1。 4. 查找操作:在线性表中查找指定元素的位置,具体实现可以包括以下步骤: - 从线性表的第一个元素开始遍历,直到找到与指定元素相同的元素。 - 如果找到了相同的元素,则返回它的位置,否则返回查找失败的结果。 以上就是线性表的基本实现功能,通过这些操作可以方便地对线性表进行插入、删除和查找等操作,满足常见的应用需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值