【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 }

  线性表的两种实现比较

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

 

    • 链表:链表的存储空间是动态分布的,因此不会空间浪费。但是由于链表需要而外的空间来为每个节点保存指针,因此要牺牲一部分空间。
    • 时间性能: 顺序表:顺序表中元素的逻辑顺序与物理存储顺序是保持一致的,而且支持随机存取。因此顺序表在查找、读取时性能很好。

 

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

 

目录
相关文章
|
2月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
44 1
|
2月前
|
存储 Java
告别混乱!用Java Map优雅管理你的数据结构
【10月更文挑战第17天】在软件开发中,随着项目复杂度增加,数据结构的组织和管理至关重要。Java中的Map接口提供了一种优雅的解决方案,帮助我们高效、清晰地管理数据。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,有效提升了代码质量和维护性。
87 2
|
2月前
|
存储 Java 开发者
Java Map实战:用HashMap和TreeMap轻松解决复杂数据结构问题!
【10月更文挑战第17天】本文深入探讨了Java中HashMap和TreeMap两种Map类型的特性和应用场景。HashMap基于哈希表实现,支持高效的数据操作且允许键值为null;TreeMap基于红黑树实现,支持自然排序或自定义排序,确保元素有序。文章通过具体示例展示了两者的实战应用,帮助开发者根据实际需求选择合适的数据结构,提高开发效率。
63 2
|
24天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
61 16
|
19天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
37 6
|
24天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
2月前
|
存储 算法 Java
Java 中常用的数据结构
【10月更文挑战第20天】这些数据结构在 Java 编程中都有着广泛的应用,掌握它们的特点和用法对于提高编程能力和解决实际问题非常重要。
30 6
|
2月前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
30 1
|
25天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
115 9
|
15天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
22 1