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

时间:2022-05-07
本文章向大家介绍【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 }

  线性表的两种实现比较

  • 空间性能:

顺序表:顺序表的存储空间是静态分布的,需要一个长度固定的数组,因此总有部分数组元素被浪费。

链表:链表的存储空间是动态分布的,因此不会空间浪费。但是由于链表需要而外的空间来为每个节点保存指针,因此要牺牲一部分空间。

  • 时间性能:

顺序表:顺序表中元素的逻辑顺序与物理存储顺序是保持一致的,而且支持随机存取。因此顺序表在查找、读取时性能很好。

链表:链表采用链式结构来保存表内元素,因此在插入、删除元素时性能要好