数据结构01:线性表和顺序表

96 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

现在前面:什么是数据结构?

编程中我们需要跟各种各样的数据打交道,计算机的程序也都是在接收数据操作数据返回数据。而不同的数据在计算机中的表达也都不一样(例:字符[1]和数值【1】),而这些数据的组织、存储形式则被称之为数据结构

线性表

线性表是最基本、最简单的一种数据结构,是n个具有相同特性的数据元素的有限序列

这句定义有着些许抽象、拗口,但只要我们分析一下就能提取出一下几个特点:

  • 可以包含有多个元素;
  • 元素具有相同特性(类型);
  • 线性表是有限的。

换而言之只要是满足 了以上三点的数据结构,都属于线性表。而常见的线性表有:(字符)串、数组、链表、栈、队列等。

而在线性表中,除了头节点没有前驱、尾节点没有后继其余所有节点都有且只有一个前驱和一个后续。如图例所示:

image.png

顺序表

顺序表是线性表的一种,其的全名是:顺序存储结构,又称线性表的顺序存储结构。这里的顺序并不是指表中的元素按值的大小顺序排列,而是指表中的元素在内存存储中是按顺序相邻排列的

我们可以将内存空间假象为一个表格,程序执行时所需要的用到的变量、数据都存储在这个表格中。

image.png

当我们新建变量时,计算机就会在内存中开辟一个空间出来存储该变量。而当我们新建一个顺序表时,计算机就会在内存中开辟一片连续的空间(方格)来存放顺序表的元素,并记录下顺序表的起始位置,当我们使用元素时,计算机就会从顺序表的起点与我们给出的偏移量计算来确定元素对应的位置。

image.png

我们编程中常用的数组就是一种顺序表。

顺序表的实际操作

在java中,创建数组时,必须指定数组的大小(元素的个数)

// 创建一个长度为 arrLength 的 int 数组
public int[] creatArr(int arrLength)    
{
    int[] arr = new int[arrLength];
    return arr;
}

当数组 arr[] 创建好后,变量 arr 实际存储的是 arr[0] 在内存中的地址,当我们需要操作数组中的元素时,需要给出指定元素的下标,让计算机得以通过下标来确定要操作的元素是哪一个。

// 向顺序表 arr 中插入元素
public void insertNode(int[] arr, int node) // 在顺序表的末位插入元素
{   
    for(int i = 0; i < arr.length; i++)
    {
        // java 在初始化数组时,会将内部的元素全部初始化为 0
        if(arr[i] == 0)
        {
            arr[i] = node;
            return;
        }
    }
    System.out.println("这个顺序表已经满了哦~");
}
​
// 在顺序表 arr 的指定位置插入元素
public void insertNode(int[] arr, int index, int node)
{
    if(index >= arr.length)
    {
        System.out.println("这个表好像没有这么大的空间呢~");
        return;
    }
    for(int i = arr.length-1; i > index; i--)
    {
        if(i==arr.length-1 && arr[i] != 0)
        {
            System.out.println("这个表好像已经满了!?");
            System.out.println("管他的,反正把数插进去进行了!");
        }
        arr[i] = arr[i-1];  // 将 index 之后的元素都往后移动一个位置 为 node 腾出空间
    }
    arr[index] = node;
}

PS:上面的两个插入方法中,参数传入的都是数组在内存中的地址,所以方法里面的修改也都是对内存中数组的修改,故不需要返回值。

有增有减,有时我们需要给顺序表添加元素,有时候我们则需要删除顺序表的元素。

// 删除顺序表 arr[] 指定位置的元素
public void delNode(int[] arr, int index)
{
    if(index >= arr.length)
    {
        System.out.println("这个表好像没有这么大的空间呢~");
        return;
    }
    // 让 index 之后的元素都向前移动一格,将 index 原有的位置盖住 实现删除操作
    for(int i = index; i < arr.length-2; i++)
    {
        arr[index] = arr[index+1];
    }
    arr[arr.length-1] = 0;  
}

在上面的插入方法中,我们会发现顺序表在创建的时候必须指定大小,可我们常常在创建的时候是不知道我们最后要使用多少个元素的,所以我们需要给线性表进行扩容。

// 这里对顺序表进行扩容实际上是重新创建一个新的顺序表
// 再将原表的元素存到新表当中
public int[] expansion(int[] arr, int length)
{
    int[] arrNew = new int[length];
    for(int i = 0; i < arr.length-1; i++)
        arrNew[i] = arr[i];
    return arrNew;
}

PS:这里和之前的方法不同,之前是对一个顺序表进行操作所以不需要返回值。而这里是创建一个新顺序表,所以在创建完成后需要把新的表返回给调用者。

有的时候,或是为了备份或是为了测试,我们需要有多个相同的顺序表,可是int[] arrB = arr;(浅拷贝)是不能满足我们的要求的。

在前面创建线性表时,我们有提到,变量 arr 并不是直接存储整个表的值,而是存储着表中首个元素在内存中的地址(可以参考 C 中的指针)。

所以如果我们直接执行 int[] arrB = arr;的话,仅仅只是将 arrB 指向 arr 指向的地址,也就是说如果我们我们修改 arrB[0] 然后再取出 arr[0] 就会发现,arr[0] 的值也发生了变化,这不是我们所希望看到的。

image.png

我们想要的是程序新创建一个新的数组,让我们操作 arrBarr 不会受到影响。

// 这也就是顺序表的深拷贝
public int[] copy(int[] arr)
{
    int[] arrNew = new int[arr.length];
    for(int i = 0; i < arr.length; i++)
        arrNew[i] = arr[i];
    return arrNew;
}   // 话说这和扩容的代码是不是一样的?

image.png 这样子我们得到的 arrB 就是与 arr 毫不相干的一个新的顺序表了。

小结

本章主要讲解了线性表的特点,以及线性表的顺序存储结构的构成及其在java主要的性质。

天天学习,加速成长!

希望与各位一起变得越来越强!