数据结构—线性表

线性表:

n(n >= 0)个数据元素组成的一个有限序列,可以在其任意位置上进行插入和删除操作的线性数据结构 

从数据在物理内存存储形式上线性表可分为:顺序表和链表

从上图可知:线性表中数据与数据之间存在一对一的关系,即除第一个元素和最后一个元素外,每个元素都有唯一的直接前驱和唯一的直接后继,第一个元素没有前驱,最后一个元素没有后继

顺序表:

用一段地址连续的存储单元依次存储数据元素的线性结构

地址连续的空间,一般情况下采用数组,但数组有静态数组和动态数组, 所以顺序表分为:静态顺序表和动态顺序表

静态顺序表:

结构的定义:

#define MAX_SIZE 10

typedef int DataType;

struct SeqList

{    

          DataType _array[MAX_SIZE]; 

          int _size;   // 顺序表中有效元素的个数

}

动态数据表:

结构的定义:

typedef int DataType;
typedef struct SeqList
{
         DataType* _a;  //指向顺序表的指针
         size_t _size;  //当前的有效数据个数
         size_t _capicity;  //顺序表容量
}SeqList;

动态顺序表的一些基本操作如下: 

typedef int DataType;
typedef struct SeqList
{
	DataType* _a;  //指向顺序表的指针
	size_t _size;  //当前的有效数据个数
	size_t _capicity;  //顺序表容量
}SeqList;

void SeqInit(SeqList* pSeq)  //线性表初始化
{
	assert(pSeq);

	pSeq->_a = (DataType*)malloc(sizeof(DataType)*5);
	pSeq->_size = 0;
	pSeq->_capicity = 10;
}

void SeqDestory(SeqList* pSeq)  //线性表销毁
{
	assert(pSeq);

	free(pSeq->_a);
	pSeq->_a = NULL;

	pSeq->_size = 0;
	pSeq->_capicity = 0;
}

//void SeqPrint(SeqList* pSeq)  //测试函数
//{
//	assert(pSeq);
//
//	for (int i = 0; i < pSeq->_size; ++i)
//		printf("%d  ",pSeq->_a[i]);
//
//	printf("\n");
//}

void SeqPushBack(SeqList* pSeq, DataType x)  //顺序表尾插
{
	assert(pSeq);

	if (pSeq->_size == pSeq->_capicity)  //增容判断
	{
		pSeq->_a = (DataType*)realloc(pSeq->_a, sizeof(DataType)*pSeq->_capicity * 2);
		pSeq->_capicity *= 2;
	}

	pSeq->_a[pSeq->_size++] = x;
}

void SeqPopBack(SeqList* pSeq)  //顺序表尾删
{
	assert(pSeq);

	if (pSeq->_size == 0)  //无元素可删
		return;

	--pSeq->_size;
}

void SeqPushFront(SeqList* pSeq, DataType x)  //顺序表头插
{
	assert(pSeq);

	if (pSeq->_size == pSeq->_capicity)  //增容判断
	{
		pSeq->_a = (DataType*)realloc(pSeq->_a, sizeof(DataType)*pSeq->_capicity * 2);
		pSeq->_capicity *= 2;
	}

	int end = pSeq->_size - 1;  //移动所有元素,腾出数组中下标为 0 的空间
	while (end >= 0) {
		pSeq->_a[end + 1] = pSeq->_a[end];
		--end;
	}

	pSeq->_a[0] = x;  //将要插入的元素放到第一个位置处
	++pSeq->_size;
}

void SeqPopFront(SeqList* pSeq)  //顺序表头删
{
	assert(pSeq);

	if (pSeq->_size == 0)  //无元素可删
		return;

	--pSeq->_size;
}

void SeqInsert(SeqList* pSeq, size_t pos, DataType x)  //指定位置处插入,pos的最小值是1
{
	assert(pSeq && pos > 0 && pos <= pSeq->_size+1);

	if (pSeq->_size == pSeq->_capicity)  //增容判断
	{
		pSeq->_a = (DataType*)realloc(pSeq->_a, sizeof(DataType)*pSeq->_capicity * 2);
		pSeq->_capicity *= 2;
	}

	int end = pSeq->_size -1;  //移动pos位置及后面的所有位置
	while (end >= (int)(pos-1)) {  //无符号数与有符号数比较时要仔细考虑,有些情况下需强转,否则会导致死循环
		pSeq->_a[end + 1] = pSeq->_a[end];
		--end;
	}

	pSeq->_a[pos - 1] = x;  //在pos位置插入要插入的元素
	++pSeq->_size;
}
void SeqErase(SeqList* pSeq, size_t pos)  //指定位置的删除,pos的最小值是1
{
	assert(pSeq && pos>0 && pos <= pSeq->_size);

	if (pSeq->_size == 0)  //无元素可删
		return;

	int cur = pos - 1;  //将pos位置后面的所有元素整体向前移动一个位置单元
	while (cur <= pSeq->_size-1){
		pSeq->_a[cur] = pSeq->_a[cur+1];
		++cur;
	}

	--pSeq->_size;
}

链表: 

一种链式存储的线性表,用一组地址任意的存储单元存放线性表的 数据元素,称存储单元为一个节点

链表的分类:

  • 单链表
  • 双链表
  • 双向顺环链表

以上三种类型的链表又分为有头节点和无头节点两类,所以链表的种类有六种

虽然链表有六种结构类型,我们常用的链表类型是无头单链表和带头的双向循环链表,下面我们只实现这两种类型的链表的一些基本操作

无头节点单链表:

结构的定义:

typedef int DataType;

typedef struct SListNode
{
         struct SListNode* _next;  //指向下一个节点的指针
         DataType _data;  //节点中的元素值
}SListNode;

无头节点单链表的一些基本操作:

typedef int DataType;
typedef struct SListNode
{
	struct SListNode* _next;
	DataType _data;
}SListNode;

SListNode* BuySListNode(DataType x)  //构造并初始化一个链表节点
{
	SListNode* node = (SListNode*)malloc(sizeof(SListNode));
	assert(node);

	node->_data = x;
	node->_next = NULL;

	return node;
}

//void SListPrint(SListNode* pHead)  //测试函数
//{
//	assert(pHead);
//
//	SListNode* cur = pHead;
//	while (cur) {
//		printf("%d ",cur->_data);
//		cur = cur->_next;
//	}
//
//	printf("\n");
//}

void SListDestory(SListNode** ppHead)  //链表的销毁
{
	assert(*ppHead);

	SListNode* cur = *ppHead;
	while (cur) {
		SListNode* next = cur->_next;  //记录即将要销毁的节点的下一个节点

		free(cur);
		cur = next;
	}

	*ppHead = NULL;
}

void SListPushBack(SListNode** ppHead, DataType x)  //链表的尾插
{
	SListNode* newnode = BuySListNode(x);

	if (*ppHead == NULL)  //处理一个节点都没有的情况
	{
		*ppHead = newnode;
		return;
	}

	SListNode* prev = NULL;
	SListNode* cur = *ppHead;
	while (cur) {
		prev = cur;  //这个指针的目的是最终指向最有一个节点

		cur = cur->_next;
	}

	prev->_next = newnode;
}

void SListPopBack(SListNode** ppHead)  //链表的尾删
{
	assert(*ppHead);

	if ((*ppHead)->_next == NULL)  //处理只有一个节点的情况(即头节点)
	{
		free(*ppHead);
		*ppHead = NULL;
		return;
	}

	SListNode* prev = NULL;
	SListNode* cur = *ppHead;
	while (cur->_next != NULL) {
		prev = cur;  //它的目的是记录最后一个节点的前一个节点

		cur = cur->_next;
	}

	free(cur);
	cur = NULL;

	prev->_next = NULL;
}

void SListPushFront(SListNode** ppHead, DataType x)  //链表的头插
{
	SListNode* newnode = BuySListNode(x);

	if (*ppHead == NULL)  //处理一个节点都没有的情况
	{
		*ppHead = newnode;
		return;
	}

	SListNode* next = *ppHead;  //每一次的头插操作都使链表的头指针发生变化
	*ppHead = newnode;
	newnode->_next = next;

}

void SListPopFront(SListNode** ppHead)  //链表的头删
{
	assert(*ppHead);

	if ((*ppHead)->_next == NULL)  //处理只有一个节点的情况(即头节点)
	{
		free(*ppHead);
		*ppHead = NULL;
		return;
	}

	SListNode* next = (*ppHead)->_next;  //记录链表的第二个节点
	free(*ppHead); 
	*ppHead = next;
}

SListNode* SListFind(SListNode* pHead, DataType x)  //查找
{
	assert(pHead);

	SListNode* cur = pHead;
	while (cur) {
		if (cur->_data == x)
			return cur;

		cur = cur->_next;
	}

	return NULL;
}

void SListInsest(SListNode** ppHead, SListNode* pos, DataType x)  //在pos之前插入
{
	assert(pos);

	if (pos == *ppHead)  //此时就是头插的情况了
	{
		SListPushFront(ppHead, x);
		return;
	}

	SListNode* newnode = BuySListNode(x);

	SListNode* cur = *ppHead;
	while (cur) {
		if (cur->_next == pos)  //最终使cur指向pos的前一个节点
			break;

		cur = cur->_next;
	}

	cur->_next = newnode;
	newnode->_next = pos;
}

void SListErase(SListNode** ppHead, SListNode* pos)  //删除pos指向的节点
{
	assert(ppHead && pos);

	if (pos == *ppHead)  //这种情况就是头删了
	{
		SListPopFront(ppHead);
		return;
	}

	SListNode* prev = NULL;
	SListNode* cur = *ppHead;
	while (cur) {
		prev = cur;  //使prev指向给pos的前一个节点
		if (cur->_next == pos)
			break;

		cur = cur->_next;
	}

	prev->_next = pos->_next;
	free(pos);
	pos = NULL;
}

有头节点双向循环链表:

结构的定义:

typedef int DataType;
typedef struct DListNode
{
        struct DListNode* _next;  //后序节点
        struct DListNode* _prev;  //前序节点
        DataType _data;  //节点的数据
}DListNode;

有头节点双向循环链表的一些基本操作:

typedef int DataType;
typedef struct DListNode
{
	struct DListNode* _next;  //后序节点
	struct DListNode* _prev;  //前序节点
	DataType _data;  //节点的数据
}DListNode;


DListNode* BuyDListNode(DataType x)  //申请节点并初始化
{
	DListNode* node = new DListNode;
	assert(node);

	node->_data = x;
	node->_prev = NULL;
	node->_next = NULL;

	return node;
}

DListNode* DListInit()  //链表的初始化
{
	DListNode* pHead = BuyDListNode(0);  //这个值随变给

	pHead->_prev = pHead;
	pHead->_next = pHead;

	return pHead;
}

void DListDestory(DListNode* pHead)  //链表的销毁
{
	assert(pHead);

	DListNode* cur = pHead->_next;
	DListNode* next = NULL;
	while (cur != pHead) {
		next = cur->_next;

		delete cur;
		cur = NULL;

		cur = next;
	}

	free(pHead);
	pHead = NULL;
}

//void DListPrint(DListNode* pHead)  //测试函数
//{
//	assert(pHead);
//
//	DListNode* cur = pHead->_next;
//	while (cur != pHead) {
//		printf("%d ",cur->_data);
//
//		cur = cur->_next;
//	}
//
//	printf("\n");
//}

void DListPushBack(DListNode* pHead, DataType x)  //链表的尾插
{
	assert(pHead);

	DListNode* newnode = BuyDListNode(x);

	DListNode* end = pHead->_prev;

	end->_next = newnode;
	newnode->_prev = end;

	newnode->_next = pHead;
	pHead->_prev = newnode;
}

void DListPopBack(DListNode* pHead)  //链表尾删
{
	assert(pHead);

	if (pHead->_next == pHead)
		return;

	DListNode* end = pHead->_prev;
	DListNode* prev = end->_prev;

	prev->_next = pHead;
	pHead->_prev = prev;

	delete end;
	end = NULL;
}

void DListPushFront(DListNode* pHead, DataType x)  //链表头插
{
	assert(pHead);

	DListNode* newnode = BuyDListNode(x);

	DListNode* cur = pHead->_next;
	
	pHead->_next = newnode;
	newnode->_prev = pHead;

	newnode->_next = cur;
	cur->_prev = newnode;
}

void DListPopFront(DListNode* pHead)  //链表头删
{
	assert(pHead);

	if (pHead->_next == pHead)
		return;

	DListNode* cur = pHead->_next;
	DListNode* next = cur->_next;

	pHead->_next = next;
	next->_prev = pHead;

	delete cur;
	cur = NULL;
}

DListNode* DListFind(DListNode* pHead, DataType x)  //查找
{
	assert(pHead);

	DListNode* cur = pHead->_next;
	while (cur != pHead) {
		if (cur->_data == x)
			return cur;

		cur = cur->_next;
	}

	return NULL;
}

void DListInsert(DListNode* pHead, DListNode* pos, DataType x)  //在pos节点之前插入
{
	assert(pHead && pos);

	DListNode* newnode = BuyDListNode(x);

	DListNode* prev = pos->_prev;

	prev->_next = newnode;
	newnode->_prev = prev;

	newnode->_next = pos;
	pos->_prev = pos;
}

void DListErase(DListNode* pHead, DListNode* pos)  //删除pos指向的节点
{
	assert(pHead && pos);

	if (pHead->_next == pHead)
		return;

	DListNode* prev = pos->_prev;
	DListNode* next = pos->_next;

	prev->_next = next;
	next->_prev = prev;

	delete pos;
	pos = NULL;
}

顺序表与链表的比较:

思考:比较顺序表和链表的优缺点,他们分别在什么场景下使用它?

  • 顺序表支持随机访问,单链表不支持随机访问
  • 顺序表插入/删除数据效率很低,时间复杂度为O(N)(除尾插尾删),单链表插入/删除效率更高,时间复杂度为O(1)
  • 顺序表的CPU高速缓存效率更高,单链表CPU高速缓存效率低 

总结一下:

这里认识了所有类型的线性表,其中实现了动态顺序表、无头节点单链表、带头节点的双向循环链表的一些基本操作,我觉得很好理解这里面的逻辑,所以没详细说明。下一篇博客,写写关于链表的经典面试题,到这里我会详细分析每个题目的逻辑,晚安啦~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值