数据结构(二)–数组和链表
数据结构主要可以分为两大模块:
- 线性结构
- 非线性结构
本文主要开始讲线性结构。
什么是线性结构
线性结构,顾名思义,就是这些数据所有节点都能被一根线(指针)联系起来的一种结构。
线性结构的存储方式:
- 连续存储:【数组】
- 离散存储:【链表】
线性结构的常见应用方式:
- 栈
- 队列
专题 :【递归】
数组和链表
本小节学习数组和链表,从底层去了解和实现数组与链表,并分析两者对应的优缺点
数组
数组是最常见的链式存储结构,它是一段连续的内存空间,在内存中我们可以简单表示为下图样式
通过上图我们可以把代码中int arr[6] = {1,2,3,4,5,6};
执行的操作从内存中脑补出来,同时我们可以简单分析一下,数组应该有的一些基本使用。如
- 初始化、
- 添加新元素、
- 插入新元素、
- 删除某个元素、
- 判断是否为空数组、
- 是否是满数组、
- 排序
- 倒序
- 查询是否包含某个元素
- ······
本小节就带着你手把手实现一个简单的数组的封装,借此来了解数组的数据结构以及内部的一些基本算法知识。这里就简单的以一个 int 类型的数组来示例,后面学到泛型的时候便可更加好的理解数组的实现。
首先简单分析一下数组中基本的属性,我们有上面的数组内存中的逻辑图可以确定数组有对应的内存空间,有一个内存起始地址,因为系统分配内存的时候长度是一定的,所以数组中有一个表示最大长度的属性,数组中的元素也可能会根据实际个数不定,所以肯定有元素个数的标记,这个标记不仅可以拿来查看数组元素个数,也能确定了元素的下标。通过简单的分析我们可以总结数组中基本的属性如下:
- 【pBase】 数组首字节地址
- 【length】 数组长度(决定分配的内存大小)
- 【count】 数组中有效元素个数
下面就根据上面的分析来实现一个简单的数组,这里只说一下思路,完整代码请在这里下载数组实现代码
|
数组的内存结构是线性的,通过这个例子的实现也更加容易理解。数组是一种常用的也是固定内存排序线性结构。然而在很多情况下是数据在内存中并不会这样线性的去排布。
虽说C语言是面向过程的语言,事实上这只是一种表象,学了数据结构其实我们可以发现我们完全可以用 C语言 实现面向对象语言的特性。事实上几乎所有的操作系统内核、机器语言底层、编译器层面都是使用 C语言 来实现的。
链表
链表是一种元素内存空间离散排列的线性数据结构。它的内存存储示意图大致如下:
链表是整个数据结构的一个基础,后面的线性结构几乎都需用到链表的知识,下面就从这几个方面来介绍链表。
- 定义
- 分类
- 算法
- 优缺点
【链表定义/特点】
n个节点离散分配
彼此通过指针相连
每个节点只有一个前驱节点,每个节点只有一个后续节点
首节点没有前驱节点,尾节点没有后续节点
首节点:
第一个有效节点
尾节点:
最后一个有效节点
头结点:
第一个有效节点前面的那个节点,头结点并不存放有效数据,加头结点的目的主要是为了方便对链表的操作,头结点的数据类型和首节点一样
头指针:
指向头结点的指针变量
尾指针:
指向尾节点的指针变量
确定一个链表需要的最少的参数有哪些?
根据链表的示意图可以简单推出来所有该链表的信息。
实际上只需要一个关键信息:头指针
因为我们通过头指针就可以推算出链表的其他所有信息。
【节点的表示】
|
【链表的分类】
链表可以分为如下类别:
- 单链表 – 上面的实例
- 双链表 – 每个节点有两个指针域,分别指向前后两个节点
- 循环链表 – 能通过任何一个节点找到其他所有节点的链表,环形链表
- 非循环链表 – 非环形普通列表
【关于链表的常见算法】
常见算法有:
- 创建链表
- 遍历
- 查找
- 清空
- 销毁
- 求长度
- 排序
- 删除节点
- 插入节点
- ·····
下面就逐步讲一下各个算法的思路,用伪算法的方式来帮助理解
1 创建链表
创建链表,首先需要确定链表的长度 int length 然后根据长度创建对应个数的节点,并把新创建的节点挂到链表的最后。简单来说就是:
1 创建头结点
2 循环创建新节点挂到链表尾部
创建头节点,首先要创建一个没有存储值的头节点,我们本身定义了 NODE 结构体,可以根据NODE类型动态分配一块内存空间
|
创建新节点,需要根据定义的长度循环,这里的难点就是如何把新节点挂到链表尾部【这里还是要明确指针和指针变量的概念,这样会更好理解一些】
为了能让新创建的节点挂到尾部,我们直接使用pHead->pNext = pNew;
是不行的,这样只能最后添加一个,并且其他的都会被泄漏。这里我们使用一个站位尾节点来表示尾节点
|
为了便于理解,请先弄清楚,指针和指针变量区别,下图逐步讲解
2 遍历链表
遍历链表思路:
遍历链表只能通过头结点的 pNext 指针找到下一个节点,并循环到 pNext == NULL 为止,分别打印 值域的值即可。
|
3 判断是否为空链表
判断是够为空链表,直接拿头结点的 pNext 指针取值判断是否为 NULL 即可
|
4 求链表长度
求链表长度类似于遍历链表,这里主要是计算有效节点的个数,这个个数就是长度
|
5 插入节点
|
本方法代码实现
|
6 删除节点
|
代码实现
|
7 链表排序
链表排序同数组不同,但很相似,都是把所有元素遍历然后找到相邻或者相反的值互换位置。
参考数组排序我们可以看一下代码实现
|
小结和感悟
数组和链表内部是不一样的,但是他们的排序的思路确实一样的,比如冒泡。
从排序的思路上都是前一个跟后一个元素比较,把小的放到前面,以此类推
这里简单引入yi算法:
狭义的算法:与数据的存储方式密切相关
广义的算法:与数据的存储方式无关
泛型:利用某种技术达到同一种效果:不同的存储方式,执行的方式是一样的
如何学习算法?
- 看真题流程
- 分析每个语句的功能
- 试数
自己搞不定,就去看答案,把答案弄懂,实在不行就先记住答案。
本文从数据结构的线性结构开始,分析了两种线性结构【数组】和【链表】,
进而逐步讲解和自己手动实现了数组和链表。
通过这两个结构,在讲解过程中也代入了一写算法的基础知识,希望能对大家有所帮助!
本文中所有代码连接地址如下:https://github.com/xiaoyouPrince/DataStructure;