一、基本概念

  1. 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。
  2. 线性结构拥有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的,链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息。
  3. 线性结构中存在两种操作受限的使用场景,即队列和栈。栈的操作只能在线性表的一端进行,就是我们常说的先进后出(FILO),队列的插入操作在线性表的一端进行而其他操作在线性表的另一端进行,先进先出(FIFO),由于线性结构存在两种存储结构,因 此队列和栈各存在两个实现方式。

二、实现

2.1、数组

2.1.1 Java中的数组定义

int[] array = new int[] {0,1,2,3,4,5,6,7,8,9}; //数组的静态定义方式

int[] array = new int[10]; //数组的动态定义方式

  • 在Java中定义数组的时候,数组的长度和内容只能够指定其中一个,不能即指定长度又指定内容,也不能不指定长度,也不指定内容;
  • 声明数组类型的时候,我们推荐将数组元素类型和[]放在一起,将类似于int[]整体看做一个独立的数据类型;
  • 在使用动态方式创建数组的时候,虚拟机在为数组开辟空间之后,这个数组中并不是“真空”的,而是使用元素默认值进行占位:

byte[]、short[]、int[]、long[]:默认值为0
        float[]、double[]:默认值为0.0
        boolean[]:默认值为false
        char[]:默认值为Unicode值的0
        引用数据类型数组,如String[]:默认值为null;

  • 不管数组中存储的元素类型是基本数据类型的元素还是引用数据类型的元素,数组类型本身是一种引用数据类型

2.1.2 数组的内存特性

数组在内存中特性,我们可以使用一句话来进行概括:定长且连续。

  • 定长指的是在Java中,一个数组对象在内存中一旦被创建,其长度将不能被修改;如果要修改一个数组的长度,那么只能重新new一个数组;
  • 连续指的是在Java中,存在于同一个数组中的所有元素,其内存地址之间是连续有规律的

2.1.3 数组的读写

1、定长导致增删效率慢

  • 数组一旦被创建,其长度是不可以被改变的,如果需要修改就要重新新建一个数组,在Java中创建对象是十分消耗时间和内存的一种操作,所以涉及到数组元素的插入和删除,必然会对数组对象的重新创建和数组元素的拷贝

2、连续导致遍历效率快

  • 在Java中,处于同一个数组中的元素之间的内存地址是有规律的,也就是说可以认为这些内存地址是连续存在的只要是连续存在的内存地址,那么我们就可以直接通过某种方式计算得到某一位元素的内存地址,进而访问这个数组元素。

也就是说,在通过下标访问数组中元素的时候,我们并不需要从数组的第一个元素开始,一个一个的向后查询这些元素,我们只要根据这些规律计算得到目标元素的内存地址就可以。

数组中下标为0的元素的内存地址就是这个数组在内存中的起始地址,也称之为数组的首元素地址,由此可见,通过公式计算元素的内存地址,比逐个遍历数组元素进行查找要快的多

3、可以快速随机访问

在按照下标访问数组中元素的时候,我们并不需要逐个遍历数组中的元素进行查找我们只需要按照数组元素首地址、单个数组元素大小和目标元素下标这三个参数直接套用公式,就能够计算得到目标元素的内存地址而其中,数组的首地址和数组中单个元素的内存大小都是在创建数组的时候就已经确定的,所以我们只要告诉虚拟机,我们要访问的数组元素下标就可以了。
公式:

  • 数组目标元素内存地址 = 数组的首元素地址 + (数组元素占用内存大小 * 目标元素下标)

通过这种公式计算的方式得到数组元素内存地址的方式,称之为快速随机访问(QuickRandom Access)。

2.1.4 案例

1、数组逆序

//思路解析:
//使用两个变量i和j,分别指向数组的起点和终点,i变量向后走,j向前走
//在遍历数组的过程中将array[i]和array[j]中的元素值使用一个临时空间进行互换,循环条件为i<j
public class ArrayReverse {public void reverse(int[] array) {//循环中使用两个变量i和j,分别指向数组的起点和数组的终点,并且通过循环,让i向后走,j向前走,同时对调array[i]和array[j]中的元素for(int i = 0, j = array.length-1; i < j; i++, j--) {int tmp = array[i];array[i] = array[j];array[j] = tmp;}}public static void main(String[] args) {int[] array = new int[] {1,2,3,4,5,6,7,8,9};ArrayReverse ar = new ArrayReverse();ar.reverse(array);System.out.println(Arrays.toString(array));}}

2、数组合并

public class SortedArrayMerge {public int[] sortedArrayMerge(int[] arr1, int[] arr2) {//[1]创建一个新数组,作为结果数组,结果数组的长度是两个参数数组长度之和int[] resultArray = new int[arr1.length + arr2.length];//[2]分别创建两个变量,用来遍历两个数组int i = 0;  //变量i遍历arr1数组int j = 0;  //变量j遍历arr2数组//[3]同时还得创建第三个变量,用来控制结果数组下标的变化int k = 0;  //变量k控制结果数组下标的变化//[4]使用循环比较arr1[i]和arr2[j]之间的大小关系,将较小的一个元素落入结果数组中while(i < arr1.length && j < arr2.length) {  //循环结束条件是其中的某一个数组已经被遍历完成//谁小谁就落在结果数组中,哪一个参数数组的元素落在结果数组中,哪一个参数数组的下标就向前进1if(arr1[i] < arr2[j]) {resultArray[k] = arr1[i];i++;}else {resultArray[k] = arr2[j];j++;}//但是不管是哪一个参数数组中的元素落入结果数组,结果数组的下标都要+1k++;}//[5]为了防止其中一个数组已经遍历结束,另一个数组中还有剩余元素,将另一个数组中的剩余元素全部直接拷贝到结果数组中if(i < arr1.length) {  //arr1没有遍历完成while(i < arr1.length) {resultArray[k] = arr1[i];i++;k++;}}if(j < arr2.length) {  //arr2没有遍历完成while(j < arr2.length) {resultArray[k] = arr2[j];j++;k++;}}//[6]返回结果数组return resultArray;}public static void main(String[] args) {int[] arr1 = new int[] {1,3,4,5,7};int[] arr2 = new int[] {0,1,5,7,7,8,9};SortedArrayMerge sam = new SortedArrayMerge();int[] resultArray = sam.sortedArrayMerge(arr1, arr2);System.out.println(Arrays.toString(resultArray));}}

2.2 链表

链表和数组不同,它的结构在内存中不是连续的。

  • 链表的节点一般分为两个部分:data数据域,用来存储要保存的数据,例如一个字符串、一个User对象等等;next后继指针域,用来保存下一个节点的内存地址,串起整个链表结构;
  • 在链表中,链表的第一个节点通常不存储任何数据,他仅仅用来引起整个链表,我们将这个特殊的节点称之为数组的头结点
  • 在整条链表中,我们只要知道了链表头结点的内存地址,就可以顺着之后每一个节点的next后继指针域向下,逐个找到后续的所有节点;
  • 链表的最后一个节点的后继指针域取值为null,这一特性在遍历整个链表的时候,常用来判断是否还有后继节点。
链表节点的Java代码可以按照如下的格式进行声明:class Node{Object data;//数据域的定义,为了能够保存任意数据类型的数据,采用Object数据类Node next;//后继指针域,因为一个节点的下一位还是一个节点,所以使用Node数据类
}

2.2.1.1 单链表

单链表的每一个节点只有一个next后继指针域,所以在遍历这个链表的时候,只能够单向的从前向后进行遍历不能够从后向前进行遍历,所以这种链表结构称之为单链表。

2.2.1.2 双链表

如果我们在链表的节点中定义两个指针域,一个指向当前节点的下一个节点,一个指向当前节点的前一个节点那么我们就可以从前后两个方向来遍历这个链表,由此也就产生了双链表结构。

2.2.1.3 循环链表

如果一个链表的最后一个节点的后继指针域并不是指向null,而是回过头来直接指向第一个存储数据的节点那么这种结构就形成了环链表结构,也称之为循环链表循环链表结构在诸如磁盘模拟算法、解决约瑟夫环问题等场景下,有着大量的应用。

2.2.2 内存特性

链表的内存特性,正好和数组是相反的,一句话概括就是:不定长且不连续。

  • 不定长:在内存中,链表的节点数量是动态分配的,一个链表结构中存在多少个节点,取决于我们向这个链表中添加了多少元素。如果我们想要向这个链表中追加元素或者插入元素,那么我们只要新建一个节点保存这个元素,并且改变几个引用值,就可以完成操作并不需要重建整个链表,更不需要拷贝原始链表中的任何元素
  • 不连续:在每次添加新元素到链表中的时候,链表的节点都是重新new出来的。正如大家知道的,每次new出来的对象,即使数据类型是一样的,但是他们之间的内存地址也是互相没有关系的

即使是存储在同一个链表中的不同节点,他们之间的内存地址也是没有规律,不连续的这样一来,如果我们想要遍历链表中所有的节点,或者按照下标找到链表中的特定节点,那么不得不每一次都重新从链表的头结点出发一个一个的遍历节点,查找想要的元素。

2.2.3 链表的读写效率

1.不定长导致增删快

在同一个链表中插入元素的时候,是不需要重新创建一整条链表的,我们只要创建一个新节点并且改变原始链表中的一些后即指针的取值,就能够实现节点的添加操作;同理,从链表中删除节点也是一样的操作,都不涉及到整个链表的重新创建。

  • 在向链表中添加节点和删除节点的时候,我们更多的是在操作节点的后继指针的取值,而并没有创建或者删除整个链表结构这样一来,和数组相比,我们就能够节省下来大量用于创建对象和拷贝原始数据的时间。所以,链表的不定长特性导致链表增删元素比较快。

2.不连续导致遍历慢

当一个链表需要存储一个新的元素的时候,都要重新new一个节点对象出来,而重新new出来的节点对象的内存地址和其他节点的内存地址之间是没有任何关系的。所以从这一点看来,如果我们想要按照下标对链表中的元素进行访问,类似于数组中的快速随机访问的公式就是不可用的了。所以我们在按照下标访问链表元素的时候,就不得不每一次都从链表的头结点开始,每向后遍历一个节点就记一次数,直到到达目标下标的节点为止。

2.2.4 案例

1、向链表中追加新元素,插入新元素,遍历链表

public class MyLinked {private static class Node {public Object data;  //数据域public Node next;  //后继指针域}//链表的头结点private Node head = new Node();//创建一个节点类型变量,用来指向链表的最后一个节点private Node lastNode = head;  //初始状态下,这个变量直接指向链表的头结点//创建一个变量,用来记录当前链表中已经存在的节点数量private int size = 0;/*** 向链表的结尾添加元素的方法* @param data 加入链表的元素*/public void add(Object data) {//[1]创建一个新节点,用来保存加入链表的数据Node node = new Node();node.data = data;//[2]将新节点挂在最后一个节点的末尾lastNode.next = node;//[3]lastNode变量指向这个节点lastNode = node;//[4]节点数量+1size++;}/*** 向链表指定下标插入元素的方法* @param index 指定的元素下标* @param data 新的数据*/public void add(int index, Object data) {//[1]首先判断下,用户指定插入的节点下标是不是超过了最大节点数量,如果是,那么直接使用追加的方式添加节点if(index >= size) {add(data);return;  //方法结束}//-----走到这一步,说明用户指定添加元素的下标,并没有超过最大元素数量,使用插入的方式将节点添加到链表中-----//[2]使用一个新节点保存新元素Node node = new Node();node.data = data;//[3]创建一个计步器变量,用来记录从链表头结点开始已经访问了多少个节点int step = 0;//[4]从链表的头结点开始,一个节点一个节点的向后查找并计步,直到找到插入为止之前的一个节点为止Node current = head;  //使用一个节点型变量,用来指示当前遍历到的节点while(step < index) {current = current.next;  //遍历下一个节点step++;  //计步器自增}//[5]将新节点插入到指定的下标位上node.next = current.next;current.next = node;size++;}/*** 遍历链表的方法*/public void iterate() {//[0]整个遍历的前提条件是链表中存在元素if(size > 0) {//[1]创建一个临时变量,用来标记当前正在遍历的节点Node current = head.next;  //最开始这个变量指向链表头的下一个节点,也就是真正保存数据的第一个节点//[2]使用循环遍历整个数组,边遍历边打印System.out.print("[");while(current != null) {System.out.print(current.data + " ");current = current.next;}System.out.print("]");}}public static void main(String[] args) {MyLinked ml = new MyLinked();ml.add("a");ml.add("b");ml.add("c");ml.add("d");ml.add("e");ml.add(0, "f");ml.add(2, "g");ml.add(100, "h");ml.iterate();}}

2.3 数组和链表在Java中的典型应用

在Java中,如果我们要在不同的应用场景下,使用数组或者链表的特性对数据进行存储和遍历,并不需要每一次都手动封装这些数据结构,因为在Java中已经将这些数据结构封装好了。

1、ArrayList ——数组的典型封装

2、LinkedList——链表的典型封装

详细的使用可以参考:https://blog.csdn.net/cyl101816/article/details/76851191

数据结构(一)线性存储结构相关推荐

  1. 数据结构之线性存储结构

    转自:http://segmentfault.com/a/1190000002471232 昨天和同事们聚餐,说到链表,由于对数据结构的不了解(在学校的时候看过一点,现在早忘了),直接丢人现眼了.意识 ...

  2. python树结构_数据结构的树存储结构

    之前介绍的所有的数据结构都是线性存储结构.本章所介绍的树结构是一种非线性存储结构,存储的是具有"一对多"关系的数据元素的集合. (A)                       ...

  3. 20.0、C语言数据结构——图的存储结构

    20.0.C语言数据结构--图的存储结构 图的存储结构相比较线性表与树来说就复杂很多了: 1. 我们回顾下,对于线性表来说,是一对一的关系,所以用数组或者链表均可简单存放:树结构是一对多的关系,所以我 ...

  4. 数据结构和数据存储结构

    数据结构和数据存储结构 数据结构和数据存储结构是不同的:一个是逻辑概念上的一个是真实存储在计算机上的 数据的存储结构:顺序.链式.索引.散列 数据的存储结构是针对计算机来说的,指的是数据的逻辑结构在计 ...

  5. 数据结构 二叉树的存储结构_线程二叉树| 数据结构

    数据结构 二叉树的存储结构 线程二叉树 (Threaded Binary Tree ) A binary tree can be represented by using array represen ...

  6. 数据结构——图(存储结构)

    数据结构--图 图的定义和基本术语 图的类型定义 图的存储结构 数组(邻接矩阵表示法) 网(即有权图)的邻接矩阵表示法 邻接表 邻接表表示法(链式) 图的邻接表存储表示 采用邻接表表示法创建无向网 邻 ...

  7. 数据结构--逻辑结构和存储结构区别和内容

    转载博客地址:http://c.biancheng.net/view/7524.html 1.逻辑结构 逻辑结构是指数据元素间抽象化的相互关系,与数据的存储无关,独立于计算机,它是从具体问题中抽象出来 ...

  8. 数据结构(逻辑结构和存储结构)

    一.逻辑结构: 逻辑结构分为四种类型:集合结构,线性结构,树形结构,图形结构. 集合结构:表面意思,没有什么深刻意义,就是数据元素同属一个集合,单个数据元素之间没有任何关系.如下图所示. 线性结构:类 ...

  9. 数据结构 - 逻辑结构和存储结构

    程序=算法+数据结构 N.沃思(Niklaus Wirth)教授提出: 程序=算法+数据结构 以上公式说明了如下两个问题: (1)算法决定如何构造和组织数据(算法→数据结构). (2)算法的选择依赖于 ...

最新文章

  1. 厦大计算机研究生和福大,考研:只知道厦门大学?福建还有这些211值得你了解...
  2. 分享一道JS前端闭包面试题
  3. C++操作SQLite数据库
  4. android开发关于和使用本机内存,内置存储卡和外置存储卡大揭秘
  5. Chroot vsftpd with non-system users (ftp)
  6. 如何成为云原生时代的卓越架构师?
  7. [机械]“重工业面临两大危机”——向文波(三一重工股份有限公司执行总裁)
  8. STL源码剖析 空间配置器 查漏补缺
  9. 结对项目——最大子数组
  10. 吴恩达机器学习(十)支持向量机(SVM)
  11. 低延时互动直播双十一优惠活动
  12. Java蓝桥杯杨辉三角形_Java蓝桥杯练习——杨辉三角形
  13. android中TextView属性之autoText解析
  14. spss25 等待服务器响应出现问题,spss打开配置服务器失败
  15. android手机内存单位 吉字节,Android的尺寸单位
  16. C++ 打表法计算农历错误数据处理
  17. 编程数学读书笔记 -- 第二章逻辑
  18. word常用技巧教程
  19. 菜孔孔学python--列表
  20. Java多线程模式(一)

热门文章

  1. jvm命令和可视化工具 调优
  2. 乐视 Astra 运行调试
  3. 架构师十项能力,对标阿里P8晋升要求,学习内容梳理
  4. dolphinscheduler v2.0.1 master和worker执行流程分析(四)
  5. 4.5 并发技术:同步调度
  6. ecplise中maven仓库地址配置
  7. 前端新手常用的几款HTML5开发工具
  8. 快出数量级的性能是怎样炼成的
  9. mysql判断是否存在索引并删除_mysql判断索引存在时删除索引的方法_MySQL
  10. 大数据分析助力金融业 可精准预测上市公司业绩