数据结构2(初):顺序表和链表

目录

1、线性表

2、顺序表

2.1、概念及结构

2.2、顺序表的实现

2.3、顺序表的问题及思考

3、链表

3.1、链表的概念及结构

3.2、链表的分类

3.3、无头单向非循环链表的实现

3.4、带头双向循环链表的实现

4、顺序表和链表的区别和联系


1、线性表

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

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。

2、顺序表

2.1、概念及结构

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

顺序表一般可以分为:

1. 静态顺序表:使用定长数组存储元素。

2. 动态顺序表:使用动态开辟的数组存储。

2.2、顺序表的实现

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空 间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间 大小,所以下面我们实现动态顺序表。

头文件:

//头文件h.h
//动态的顺序表
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int type;

typedef struct list
{
	type* a;//指向动态内存开辟的空间
	size_t capacity;//容量空间的大小
	size_t size;//存储的内容大小
}LT;

//顺序表的的初始化
void LTinit(LT* p);

//检查空间,如果满了就进行增容
void LTcheckcapacity(LT* p);

//尾插
void LTtailpush(LT* p,type x);

//头插
void LTfrontpush(LT* p, type x);

//尾删
void LTtailpop(LT* p);

//头删
void LTfrontpop(LT* p);

//查找
int LTfind(LT ph,type x);

//pos位置前插入
void LTinsert(LT* p,size_t pos, type x);

//删除pos位置的值
void LTdelete(LT* p, size_t pos);

//顺序表的销毁
void LTdestory(LT* p);

//顺序表的打印
void LTprint(LT ph);

实现:

//实现f.c
#include"h.h"
//顺序表的的初始化
void LTinit(LT* p)//传一级指针就可以改变一个结构体内部的成员。
{
	assert(p);

	p->a = NULL;
	p->capacity = 0;
	p->size = 0;
}

//检查空间
bool LTcheck(LT* p)
{
	assert(p);

	if (p->capacity == p->size)
	{
		return true;
	}
	else
		return false;
}

//检查空间,如果满了就进行增容
void LTcheckcapacity(LT* p)
{
	assert(p);
	
	if (LTcheck(p) == true)
	{
		int newcapacity = p->capacity == 0 ? 4 : p->capacity * 2;           
		LT* newlist = (LT*)realloc(p->a, sizeof(type) * newcapacity);            

		if (newlist == NULL)//开完容量不要忘记还要判断容量是否开辟成功
		{
			perror("fail realloc");
			return;
		}

		p->a = newlist;
		p->capacity = newcapacity;
	}
}

//尾插
void LTtailpush(LT* p,type x)
{
	assert(p);

	LTcheckcapacity(p);

	p->a[p->size] = x;

	p->size++;

}

//头插
void LTfrontpush(LT* p, type x)
{
	assert(p);

	LTcheckcapacity(p);

	int i = 0;
	for (i = p->size; i > 0; i--)
	{
		p->a[i] = p->a[i - 1];
	}

	p->a[0] = x;
	p->size++;
}

//顺序表的打印   
void LTprint(LT ph)
{
	int i = 0;
	for (i = 0; i < ph.size; i++)
	{
		printf("%d->", ph.a[i]);
	}
	printf("NULL\n");
}

//尾删
void LTtailpop(LT* p)  
{
	assert(p);

	p->size = p->size - 1;
}

//头删
void LTfrontpop(LT* p)
{
	assert(p);

	for (int i = 0; i < p->size-1; i++)
	{
		p->a[i] = p->a[i + 1];
	}
	p->size = p->size - 1;
}

//查找
int LTfind(LT ph,type x)
{
	int i = 0;
	while (i<ph.size)
	{
		if (ph.a[i] == x)
		{
			return i;
		}
		i++;
	}
	return -1;
}


//pos位置前插入(这里的pos位置表示下标)
void LTinsert(LT* p, size_t pos, type x)
{
	assert(p);
	assert(pos >= 0 && pos < p->size);                

	int i = 0;
	for (i = p->size; i > pos; i--)
	{
		p->a[i] = p->a[i - 1];
	}
	p->a[pos] = x;
	p->size++;
}

//删除pos位置的值(注意这里的pos表示下标)
void LTdelete(LT* p, size_t pos)
{
	assert(p);
	assert(pos >= 0 && pos < p->size);                                               

	int i = pos;
	while (i < p->size-1)
	{
		p->a[i] = p->a[i + 1];
		i++;
	}
	p->size--;
}

//顺序表的销毁
void LTdestory(LT* p)
{
	assert(p);
	if (p->a != NULL)              //要注意进行判断一下。
	{
		free(p->a);
		p->a = NULL;
		p->capacity = 0;
		p->size = 0;
	}
}

2.3、顺序表的问题及思考

问题:

1. 中间/头部的插入删除,时间复杂度为O(N)

2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

思考:如何解决以上问题呢?下面给出了链表的结构来看看。

3、链表

3.1、链表的概念及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。

这种结构就像小火车一样

注意:

1、从图上也可以看出,链式结构在逻辑上是连续的,但是在物理上不一定是连续的。

2、链表上的节点一般是从堆上申请的。

3、从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续也可能不连续。

3.2、链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

1. 单向或者双向

2. 带头或者不带头

3. 循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

1. 无头单向非循环链表:

结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2. 带头双向循环链表:

结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

3.3、无头单向非循环链表的实现

头文件:

//头文件head.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int type;

typedef struct ChainList
{
	type data;
	struct ChainList* next;
}CList;

//动态申请一个节点;
CList* CListApply(type x);

//遍历链表
void CListPrint(CList* p);

//单链表头插
void CListpushfront(CList** pf, type x);

//单链表尾插
void CListpushback(CList** pf, type x);

//单链表的头删
void CListpopfront(CList** pf);

//单链表的尾删
void CListpopback(CList** pf);

//单链表pos之前插入
void CListpush(CList** pf, CList* pos, type x);

//单链表的pos删除
void CListpop(CList** pf, CList* pos);

//单链表的销毁
void CListdestory(CList** pf);

//单链表的查找
CList* CListfind(CList* p, type x);

源文件:

//实现funtion.c
#include"head.h"
//动态申请一个节点;
CList* CListApply(type x)
{
	CList* newapply = (CList*)malloc(sizeof(CList));
	if (newapply == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newapply->data = x;
	newapply->next = NULL;

	return newapply;
}

//单链表打印
void CListPrint(CList* p)
{
	CList* cur = p;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//单链表头插
void CListpushfront(CList** pf,type x)
{
	assert(pf);
	CList* newfront = CListApply(x);
	newfront->next = *pf;
	*pf = newfront;
}


//单链表尾插
void CListpushback(CList** pf, type x)
{
	assert(pf);
	CList* newback= CListApply(x);
	//空链表
	if (*pf == NULL)
	{
		*pf = newback;
	}
	//非空链表
	else
	{
		CList* tail = *pf;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newback;
	}
}

//单链表的尾删
void CListpopback(CList** pf)
{
	assert(pf);
	assert(*pf);
	//一个节点
	
	if ((*pf)->next == NULL)      //要注意要用上括号包起来*pf
	{
		free(*pf);
		*pf = NULL;
	}
	//多个节点
	else
	{
		CList* p = *pf;
		while (p->next->next != NULL)
		{
			p = p->next;
		}
		free(p->next);
		p->next = NULL;
	}
}

//单链表的头删
void CListpopfront(CList** pf)
{
	assert(*pf);
	assert(pf);
	CList* p = *pf;
	*pf=(*pf)->next;
	free(p);
}


//单链表的查找
CList* CListfind(CList* p,type x)
{
	//assert(p);
	CList* test = p;
	while (test)
	{
		if (test->data == x)
		{
			//printf("找到了");
			return test;
		}
		else
		{
			test = test->next;
		}          
	}
	return NULL;
}

//单链表pos之前插入
void CListpush(CList** pf,CList* pos, type x)
{
	assert(*pf);
	assert(pos);
	assert(pf);
	//第一个节点
	if (*pf==pos)
	{
		CListpushfront(pf,x);
	}
	//多个节点
	else
	{
		CList* p = *pf;
		while (p->next != pos)
		{
			p = p->next;
		}
		CList* exchange = CListApply(x);
		p->next = exchange;
		exchange->next = pos;
	}
}

//单链表的pos删除
void CListpop(CList** pf, CList* pos)
{
	assert(*pf);
	assert(pos);
	assert(pf);
	//一个节点
	if (*pf == pos)
	{
		free(pos);        
		pos = NULL;
	}
	//多个节点时
	else
	{
		CList* p = *pf;
		while (p->next != pos)
		{
			p = p->next;
		}
		p->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//单链表的销毁
void CListdestory(CList** pf)
{
	assert(pf);
	CList* p = *pf;
	while (p)
	{
		CList* next = p->next;
		free(p);
		p = next;
	}
	*pf = NULL;
}

3.4、带头双向循环链表的实现

头文件:

//头文件head.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LTDataNode;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataNode data;
}LTNode;

LTNode* LTInit();

void LTPrint(LTNode* phead);

bool LTEmpty(LTNode* phead);

void LTPushBack(LTNode* phead, LTDataNode x);

void LTPushFront(LTNode* phead, LTDataNode x);

void LTPopBack(LTNode* phead);

void LTPopFront(LTNode* phead);

LTNode* LTFind(LTNode* phead, LTDataNode x);

//在pos之前插入
void LTInsert(LTNode* pos, LTDataNode x);

//删除pos处的值
void LTErase(LTNode* pos);

void LTDestory(LTNode* phead);

实现:

//实现funtion.c
#include"head.h"
LTNode* BuyLTNode(LTDataNode x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;
}

LTNode* LTInit()
{
	LTNode* phead= BuyLTNode(-1);
	phead->prev = phead;
	phead->next=phead;

	return phead;
}

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

bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead == phead->next;
}


void LTPushBack(LTNode* phead, LTDataNode x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);

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

void LTPushFront(LTNode* phead, LTDataNode x)
{
	assert(phead);
	LTNode* newnode= BuyLTNode(x);



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

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



void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	LTNode* tail = phead->next;
	LTNode* tailprev = tail->prev;

	free(tail);
	tailprev->next = phead;
	phead->prev = tailprev;
}


void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	LTNode* next = phead->next;
	phead->next = next->next;
	phead->next->prev = phead;

	free(next);
}


LTNode* LTFind(LTNode* phead, LTDataNode x)
{
	assert(phead);

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


//
void LTInsert(LTNode* pos, LTDataNode x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

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

void LTErase(LTNode* pos)
{
	assert(pos);

	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;

	posprev->next = posnext;
	posnext->prev = posprev;

	free(pos);
}


void LTDestory(LTNode* phead)
{
	assert(phead);

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

4、顺序表和链表的区别和联系

不同点                                       顺序表                                           链表

存储空间上 :                       物理上一定连续                             逻辑上连续,但物理上不一定连续

随机访问  :                                    支持O(1)                                         不支持:O(N)

任意位置插入或者删除元素:   可能需要搬移元素,效率低 O(N)        只需修改指针指向

插入 :                                    动态顺序表,空间不够时需要扩容                没有容量的概念

应用场景 :                              元素高效存储+频繁访问                      任意位置插入和删除频繁

缓存利用率:                        高 (物理地址连续,CPU高速缓存命中率高)                   低

备注:缓存利用率参考存储体系结构以及局部原理性。

上面的主存也就是内存,是带电存储信息的。本地二级存储,也就是硬盘,是不带电存储的。

CPU是不会直接访问内存的,因为CPU很快,内存比较慢。

在CPU和内存之间还有三级缓存(L1,L2,L3)以及寄存器。

数据较少时就会将数据直接放入寄存器中,数据较大就会放入到三级缓存中,一级一级放。

1、先看数据是否在缓存中,在就叫缓存命中,则直接进行访问。

2、不在就不命中,先加载数据到缓存,然后再访问。

对于CPU来讲,它是不会一个一个字节去加载数据的,一般来讲都是要一块一块的加载数据的。

对于更多关于CPU缓存的知识,请自行搜索。

另外本篇文章还没有结束,因为太长了,换到了下一篇来讲。

另外,欢迎各位读者到评论区进行交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/637147.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

200+有趣的HTML前端游戏项目合集(5月17日更新,持续更新中)

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

工作干到抑郁了,要不要辞职?

在知乎上看到以为网友提问&#xff1a;工作干到抑郁&#xff0c;该不该辞职&#xff1f; 今天和大家聊聊这个话题&#xff0c;如果你也有类似的情况&#xff0c;希望这篇文章能帮到你。 熟悉瑶琴的朋友&#xff0c;都知道瑶琴在去年有一次裸辞的经历。离职前&#xff0c;严重的…

多台Centos快速区分,让Centos开机自动显示它的IP地址!

背景说明&#xff1a;当公司拥有多台Centos服务器&#xff0c;管理员很容易弄混淆导致不好区分&#xff0c;在这样的情况下我们可以写个简单脚本来实现开机自动显示它的IP地址&#xff0c;从而达到区分开来的结果&#xff01; 首先我们来开下效果&#xff0c;登录之前的 下面是…

【加密与解密(第四版)】第十八章笔记

第十八章 反跟踪技术 18.1 由BeginDebugged引发的蝴蝶效应 IsDebuggerPresent()函数读取当前进程PEB中的BeginDebugged标志 CheckRemoteDebuggerPresent() 反调试总结&#xff1a;https://bbs.kanxue.com/thread-225740.htm https://www.freebuf.com/articles/others-articl…

细胞冻存——让你的细胞“长生不老”

《星际穿越》电影中提到漫长的太空旅程中&#xff0c;宇航员可以进入休眠水床休眠&#xff0c;并自行设定唤醒时间。在《异形》《深空失忆》《三体》等科幻作品中&#xff0c;都出现此类技术。《三体》中&#xff0c;休眠后来成为人类最普遍的一项技术。技术上的人类低温休眠&a…

JavaEE-网络初识

文章目录 一、网络背景1.1 起源1.2 国内网络的发展 二、关键概念2.1 网络2.2 设备2.3 ip地址与端口号 三、协议3.1 协议分层3.2 OSI七层模型3.3 TCP/IP五层模型3.4 数据传输过程的简单叙述 一、网络背景 1.1 起源 在国外大概时上世纪70年代左右&#xff0c;网络就出现了&…

项目集成SkyWalking,基于k8s搭建

一、搭建SkyWalking 官方文档&#xff08;英文&#xff09;&#xff1a;skywalking/docs at master apache/skywalking 中文可以使用&#xff1a;GitHub - SkyAPM/document-cn-translation-of-skywalking: [已过期,请使用官网AI文档] The CN translation version of Apache…

【LeetCode:496. 下一个更大元素 I + 单调栈】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

深度学习——图像分类(CNN)—训练模型

训练模型 1.导入必要的库2.定义超参数3.读取训练和测试标签CSV文件4.确保标签是字符串类型5.显示两个数据框的前几行以了解它们的结构6.定义图像处理参数7.创建图像数据生成器8.设置目录路径9.创建训练和验证数据生成器10.构建模型11.编译模型12.训练模型并收集历史13.绘制损失…

【AD21】PCB板尺寸与层名称标注

PCB绘制完成后&#xff0c;需要给上级或生产制造商发送输出文件&#xff0c;输出文件中包含板尺寸标识和层标识可以方便工作的交接。 1. 板尺寸标识 首先板尺寸标识所在的层要在与板框不同的机械层&#xff0c;这里我选择机械5层。 点击放置->尺寸->线性尺寸 这里板尺…

微信小程序uniapp+django洗脚按摩足浴城消费系统springboot

原生wxml开发对Node、预编译器、webpack支持不好&#xff0c;影响开发效率和工程构建。所以都会用uniapp框架开发 前后端分离&#xff0c;后端给接口和API文档&#xff0c;注重前端,接近原生系统 使用Navicat或者其它工具&#xff0c;在mysql中创建对应名称的数据库&#xff0…

利用大模型构造数据集,并微调大模型

一、前言 目前大模型的微调方法有很多&#xff0c;而且大多可以在消费级显卡上进行&#xff0c;每个人都可以在自己的电脑上微调自己的大模型。 但是在微调时我们时常面对一个问题&#xff0c;就是数据集问题。网络上有许多开源数据集&#xff0c;但是很多时候我们并不想用这…

Gerchberg-Saxton (GS) 和混合输入输出(Hybrid Input-Output, HIO)算法

文章目录 1. 简介2. 算法描述3. 混合输入输出&#xff08;Hybrid Input-Output, HIO&#xff09;算法3.1 HIO算法步骤3.2 HIO算法的优势3.3 算法描述 4. 算法实现与对比5. 总结参考文献 1. 简介 Gerchberg-Saxton (GS) 算法是一种常用于相位恢复和光学成像的迭代算法。该算法最…

【抽代复习笔记】18-置换练习题(2)及两个重要定理

最近一直忙于学校的事情&#xff0c;好久没更新了&#xff0c;实在抱歉。接下来几期大概也会更得慢一些&#xff0c;望见谅。 练习4&#xff1a;写出4次对称群S4中所有置换。 解&#xff1a;由上一篇笔记结尾的定理我们知道&#xff0c;4次对称群的阶&#xff08;也就是所含元…

JSON的序列化与反序列化以及VSCode执行Run Code 报错

JSON JSON: JavaScript Object Notation JS对象简谱 , 是一种轻量级的数据交换格式。 JSON格式 { "name":"金苹果", "info":"种苹果" } 一个对象&#xff1a;由一个大括号表示.括号中通过键值对来描述对象的属性 (可以理解为, 大…

2024年 电工杯 (A题)大学生数学建模挑战赛 | 园区微电网风光储协调优化配置 | 数学建模完整代码解析

DeepVisionary 每日深度学习前沿科技推送&顶会论文&数学建模与科技信息前沿资讯分享&#xff0c;与你一起了解前沿科技知识&#xff01; 本次DeepVisionary带来的是电工杯的详细解读&#xff1a; 完整内容可以在文章末尾全文免费领取&阅读&#xff01; 问题重述…

MVS net笔记和理解

文章目录 传统的方法有什么缺陷吗&#xff1f;MVSnet深度的预估 传统的方法有什么缺陷吗&#xff1f; 传统的mvs算法它对图像的光照要求相对较高&#xff0c;但是在实际中要保证照片的光照效果很好是很难的。所以传统算法对镜面反射&#xff0c;白墙这种的重建效果就比较差。 …

【Python自动化测试】:Unittest单元测试与HTMLTestRunner自动生成测试用例的好帮手

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 &#x1f525;前言&#x1f680;unittest编写测试用例&#x1f680;unittest测…

【408精华知识】Cache类题目解题套路大揭秘

有关Cache的题目&#xff0c;需要理解Cache的工作原理&#xff0c;也即给出一个地址&#xff0c;要知道如何在Cache中寻找或者如何将其从主存中复制入Cache&#xff0c;同时理解Cache中具体是如何存储的&#xff0c;包含三种存储方式&#xff0c;分别是直接映射、全相联映射、组…

clion/pycharm 安装中文

楼主版本 2024.1 mac 操作系统&#xff0c;理论上不同版本和不同操作系统操作应该大同小异 首先找到插件的位置 方式一 1、进入工程&#xff0c;右上角找到设置 2、找到插件&#xff08;欢迎界面也能找到这个&#xff09; 方式二 在欢迎界面找到插件 最后 插件商店搜索 l…