• 【Java数据结构学习笔记之一】线性表的存储结构及其代码实现


    应用程序后在那个的数据大致有四种基本的逻辑结构:

    • 集合:数据元素之间只有"同属于一个集合"的关系
    • 线性结构:数据元素之间存在一个对一个的关系
    • 树形结构:数据元素之间存在一个对多个关系
    • 图形结构或网状结构:数据元素之间存在多个对多个的关系



    对于数据不同的逻辑结构,计算机在物理磁盘上通常有两种屋里存储结构

    • 顺序存储结构
    • 链式存储结构


    本篇博文主要讲的是线性结构,而线性结构主要是线性表,非线性结构主要是树和图。
    线性表的基本特征:

    • 总存在唯一的第一个数据元素
    • 总存在唯一的最后一个数据元素
    • 除第一个数据元素外,集合中的每一个数据元素都只有一个前驱的数据元素
    • 除最后一个数据元素外,集合中的每一个数据元素都只有一个后继的数据元素



    1.线性表的顺序存储结构:是指用一组地址连续的存储单元一次存放线性表的元素。为了使用顺序结构实现线性表,程序通常会采用数组来保存线性中的元素,是一种随机存储的数据结构,适合随机访问。java中ArrayList类是线性表的数组实现。

      1 import java.util.Arrays;
      2 public class SequenceList<T>
      3 {
      4     private int DEFAULT_SIZE = 16;
      5     //保存数组的长度。
      6     private int capacity;
      7     //定义一个数组用于保存顺序线性表的元素
      8     private Object[] elementData;
      9     //保存顺序表中元素的当前个数
     10     private int size = 0;
     11     //以默认数组长度创建空顺序线性表
     12     public SequenceList()
     13     {
     14         capacity = DEFAULT_SIZE;
     15         elementData = new Object[capacity];
     16     }
     17     //以一个初始化元素来创建顺序线性表
     18     public SequenceList(T element)
     19     {
     20         this();
     21         elementData[0] = element;
     22         size++;
     23     }
     24     /**
     25      * 以指定长度的数组来创建顺序线性表
     26      * @param element 指定顺序线性表中第一个元素
     27      * @param initSize 指定顺序线性表底层数组的长度
     28      */
     29     public SequenceList(T element , int initSize)
     30     {
     31         capacity = 1;
     32         //把capacity设为大于initSize的最小的2的n次方
     33         while (capacity < initSize)
     34         {
     35             capacity <<= 1;
     36         }
     37         elementData = new Object[capacity];
     38         elementData[0] = element;
     39         size++;
     40     }
     41     //获取顺序线性表的大小
     42     public int length()
     43     {
     44         return size;
     45     }
     46     //获取顺序线性表中索引为i处的元素
     47     public T get(int i)
     48     {
     49         if (i < 0 || i > size - 1)
     50         {
     51             throw new IndexOutOfBoundsException("线性表索引越界");
     52         }
     53         return (T)elementData[i];
     54     }
     55     //查找顺序线性表中指定元素的索引
     56     public int locate(T element)
     57     {
     58         for (int i = 0 ; i < size ; i++)
     59         {
     60             if (elementData[i].equals(element))
     61             {
     62                 return i;
     63             }
     64         }
     65         return -1;
     66     }
     67     //向顺序线性表的指定位置插入一个元素。
     68     public void insert(T element , int index)
     69     {
     70         if (index < 0 || index > size)
     71         {
     72             throw new IndexOutOfBoundsException("线性表索引越界");
     73         }
     74         ensureCapacity(size + 1);
     75         //将index处以后所有元素向后移动一格。
     76         System.arraycopy(elementData , index , elementData
     77              , index + 1 , size - index);
     78         elementData[index] = element;
     79         size++;
     80     }
     81     //在线性顺序表的开始处添加一个元素。
     82     public void add(T element)
     83     {
     84         insert(element , size);
     85     }
     86     //很麻烦,而且性能很差
     87     private void ensureCapacity(int minCapacity)
     88     {
     89         //如果数组的原有长度小于目前所需的长度
     90         if (minCapacity > capacity)
     91         {
     92             //不断地将capacity * 2,直到capacity大于minCapacity为止
     93             while (capacity < minCapacity)
     94             {
     95                 capacity <<= 1;
     96             }
     97             elementData = Arrays.copyOf(elementData , capacity);
     98         }
     99     }
    100     //删除顺序线性表中指定索引处的元素
    101     public T delete(int index)
    102     {
    103         if (index < 0 || index > size - 1)
    104         {
    105             throw new IndexOutOfBoundsException("线性表索引越界");
    106         }
    107         T oldValue = (T)elementData[index];
    108         int numMoved = size - index - 1;
    109         if (numMoved > 0)
    110         {
    111             System.arraycopy(elementData , index+1
    112                 , elementData, index ,     numMoved);
    113         }
    114         //清空最后一个元素
    115         elementData[--size] = null; 
    116         return oldValue;
    117     }
    118     //删除顺序线性表中最后一个元素
    119     public T remove()
    120     {
    121         return delete(size - 1);
    122     }
    123     //判断顺序线性表是否为空表
    124     public boolean empty()
    125     {
    126         return size == 0;
    127     }
    128     //清空线性表
    129     public void clear()
    130     {
    131         //将底层数组所有元素赋为null
    132         Arrays.fill(elementData , null);
    133         size = 0;
    134     }
    135     public String toString()
    136     {
    137         if (size == 0)
    138         {
    139             return "[]";
    140         }
    141         else
    142         {
    143             StringBuilder sb = new StringBuilder("[");
    144             for (int i = 0 ; i < size ; i++ )
    145             {
    146                 sb.append(elementData[i].toString() + ", ");
    147             }
    148             int len = sb.length();
    149             return sb.delete(len - 2 , len).append("]").toString();
    150         }
    151     }
    152 }

    2.线性表链式存储结构:将采用一组地址的任意的存储单元存放线性表中的数据元素。
    链表又可分为:

    • 单链表:每个节点只保留一个引用,该引用指向当前节点的下一个节点,没有引用指向头结点,尾节点的next引用为null。
    • 循环链表:一种首尾相连的链表。
    • 双向链表:每个节点有两个引用,一个指向当前节点的上一个节点,另外一个指向当前节点的下一个节点。


    下面给出线性表双向链表的实现:java中LinkedList是线性表的链式实现,是一个双向链表。

      1 public class DuLinkList<T>
      2 {
      3     //定义一个内部类Node,Node实例代表链表的节点。
      4     private class Node
      5     {
      6         //保存节点的数据
      7         private T data;
      8         //指向上个节点的引用
      9         private Node prev;
     10         //指向下个节点的引用
     11         private Node next;
     12         //无参数的构造器
     13         public Node()
     14         {
     15         }
     16         //初始化全部属性的构造器
     17         public Node(T data , Node prev , Node next)
     18         {
     19             this.data = data;
     20             this.prev = prev;
     21             this.next = next;
     22         }
     23     }
     24     //保存该链表的头节点
     25     private Node header;
     26     //保存该链表的尾节点
     27     private Node tail;
     28     //保存该链表中已包含的节点数
     29     private int size;
     30     //创建空链表
     31     public DuLinkList()
     32     {
     33         //空链表,header和tail都是null
     34         header = null;
     35         tail = null;
     36     }
     37     //以指定数据元素来创建链表,该链表只有一个元素
     38     public DuLinkList(T element)
     39     {
     40         header = new Node(element , null , null);
     41         //只有一个节点,header、tail都指向该节点
     42         tail = header;
     43         size++;
     44     }
     45     //返回链表的长度    
     46     public int length()
     47     {
     48         return size;
     49     }
     50 
     51     //获取链式线性表中索引为index处的元素
     52     public T get(int index)
     53     {
     54         return getNodeByIndex(index).data;
     55     }
     56     //根据索引index获取指定位置的节点
     57     private Node getNodeByIndex(int index)
     58     {
     59         if (index < 0 || index > size - 1)
     60         {
     61             throw new IndexOutOfBoundsException("线性表索引越界");
     62         }
     63         if (index <= size / 2)
     64         {
     65             //从header节点开始
     66             Node current = header;
     67             for (int i = 0 ; i <= size / 2 && current != null
     68                 ; i++ , current = current.next)
     69             {
     70                 if (i == index)
     71                 {
     72                     return current;
     73                 }
     74             }
     75         }
     76         else
     77         {
     78             //从tail节点开始搜索
     79             Node current = tail;
     80             for (int i = size - 1 ; i > size / 2 && current != null
     81                 ; i++ , current = current.prev)
     82             {
     83                 if (i == index)
     84                 {
     85                     return current;
     86                 }
     87             }
     88         }
     89         return null;
     90     }
     91     //查找链式线性表中指定元素的索引
     92     public int locate(T element)
     93     {
     94         //从头节点开始搜索
     95         Node current = header;
     96         for (int i = 0 ; i < size && current != null
     97             ; i++ , current = current.next)
     98         {
     99             if (current.data.equals(element))
    100             {
    101                 return i;
    102             }
    103         }
    104         return -1;
    105     }
    106     //向线性链式表的指定位置插入一个元素。
    107     public void insert(T element , int index)
    108     {
    109         if (index < 0 || index > size)
    110         {
    111             throw new IndexOutOfBoundsException("线性表索引越界");
    112         }
    113         //如果还是空链表
    114         if (header == null)
    115         {
    116             add(element);
    117         }
    118         else
    119         {
    120             //当index为0时,也就是在链表头处插入
    121             if (index == 0)
    122             {
    123                 addAtHeader(element);
    124             }
    125             else
    126             {
    127                 //获取插入点的前一个节点
    128                 Node prev = getNodeByIndex(index - 1);
    129                 //获取插入点的节点
    130                 Node next = prev.next;
    131                 //让新节点的next引用指向next节点,prev引用指向prev节点
    132                 Node newNode = new Node(element , prev , next);
    133                 //让prev的next指向新节点。
    134                 prev.next = newNode;
    135                 //让prev的下一个节点的prev指向新节点
    136                 next.prev = newNode;
    137                 size++;
    138             }
    139         }
    140     }
    141     //采用尾插法为链表添加新节点。
    142     public void add(T element)
    143     {
    144         //如果该链表还是空链表
    145         if (header == null)
    146         {
    147             header = new Node(element , null , null);
    148             //只有一个节点,header、tail都指向该节点
    149             tail = header;
    150         }
    151         else
    152         {
    153             //创建新节点,新节点的pre指向原tail节点
    154             Node newNode = new Node(element , tail , null);
    155             //让尾节点的next指向新增的节点
    156             tail.next = newNode;
    157             //以新节点作为新的尾节点
    158             tail = newNode;
    159         }
    160         size++;
    161     }
    162     //采用头插法为链表添加新节点。
    163     public void addAtHeader(T element)
    164     {
    165         //创建新节点,让新节点的next指向原来的header
    166         //并以新节点作为新的header
    167         header = new Node(element , null , header);
    168         //如果插入之前是空链表
    169         if (tail == null)
    170         {
    171             tail = header;
    172         }
    173         size++;
    174     }
    175     //删除链式线性表中指定索引处的元素
    176     public T delete(int index)
    177     {
    178         if (index < 0 || index > size - 1)
    179         {
    180             throw new IndexOutOfBoundsException("线性表索引越界");
    181         }
    182         Node del = null;
    183         //如果被删除的是header节点
    184         if (index == 0)
    185         {
    186             del = header;
    187             header = header.next;
    188             //释放新的header节点的prev引用
    189             header.prev = null;
    190         }
    191         else
    192         {
    193             //获取删除点的前一个节点
    194             Node prev = getNodeByIndex(index - 1);
    195             //获取将要被删除的节点
    196             del = prev.next;
    197             //让被删除节点的next指向被删除节点的下一个节点。
    198             prev.next = del.next;
    199             //让被删除节点的下一个节点的prev指向prev节点。
    200             if (del.next != null)
    201             {
    202                 del.next.prev = prev;
    203             }        
    204             //将被删除节点的prev、next引用赋为null.
    205             del.prev = null;
    206             del.next = null;
    207         }
    208         size--;
    209         return del.data;
    210     }
    211     //删除链式线性表中最后一个元素
    212     public T remove()
    213     {
    214         return delete(size - 1);
    215     }
    216     //判断链式线性表是否为空链表
    217     public boolean empty()
    218     {
    219         return size == 0;
    220     }
    221     //清空线性表
    222     public void clear()
    223     {
    224         //将底层数组所有元素赋为null
    225         header = null;
    226         tail = null;
    227         size = 0;
    228     }
    229     public String toString()
    230     {
    231         //链表为空链表时
    232         if (empty())
    233         {
    234             return "[]";
    235         }
    236         else
    237         {
    238             StringBuilder sb = new StringBuilder("[");
    239             for (Node current = header ; current != null
    240                 ; current = current.next )
    241             {
    242                 sb.append(current.data.toString() + ", ");
    243             }
    244             int len = sb.length();
    245             return sb.delete(len - 2 , len).append("]").toString();
    246         }
    247     }
    248     public String reverseToString()
    249     {
    250         //链表为空链表时
    251         if (empty())
    252         {
    253             return "[]";
    254         }
    255         else
    256         {
    257             StringBuilder sb = new StringBuilder("[");
    258             for (Node current = tail ; current != null 
    259                 ; current = current.prev )
    260             {
    261                 sb.append(current.data.toString() + ", ");
    262             }
    263             int len = sb.length();
    264             return sb.delete(len - 2 , len).append("]").toString();
    265         }
    266     }
    267 }

      线性表的两种实现比较

      • 空间性能:
      • 顺序表:顺序表的存储空间是静态分布的,需要一个长度固定的数组,因此总有部分数组元素被浪费。
        链表:链表的存储空间是动态分布的,因此不会空间浪费。但是由于链表需要而外的空间来为每个节点保存指针,因此要牺牲一部分空间。
      • 时间性能:
      • 顺序表:顺序表中元素的逻辑顺序与物理存储顺序是保持一致的,而且支持随机存取。因此顺序表在查找、读取时性能很好。
        链表:链表采用链式结构来保存表内元素,因此在插入、删除元素时性能要好
  • 相关阅读:
    shell:bash基本特性
    python_day02
    python_day01
    centos环境下安装python3以及pip3
    http1到http3的巨大变化
    HTTP协议
    bootstrap
    JQuery
    BOM与DOM
    23种设计模式的几种常见的设计模式
  • 原文地址:https://www.cnblogs.com/ECJTUACM-873284962/p/7485590.html
Copyright © 2020-2023  润新知