博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Java数据结构学习笔记之一】线性表的存储结构及其代码实现
阅读量:7026 次
发布时间:2019-06-28

本文共 12162 字,大约阅读时间需要 40 分钟。

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

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

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

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

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

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

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

 

1 import java.util.Arrays;  2 public class SequenceList
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+1112 , 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 //将底层数组所有元素赋为null132 Arrays.fill(elementData , null);133 size = 0;134 }135 public String toString()136 {137 if (size == 0)138 {139 return "[]";140 }141 else142 {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
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 else119 {120 //当index为0时,也就是在链表头处插入121 if (index == 0)122 {123 addAtHeader(element);124 }125 else126 {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 else152 {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指向原来的header166 //并以新节点作为新的header167 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 else192 {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 //将底层数组所有元素赋为null225 header = null;226 tail = null;227 size = 0;228 }229 public String toString()230 {231 //链表为空链表时232 if (empty())233 {234 return "[]";235 }236 else237 {238 StringBuilder sb = new StringBuilder("[");239 for (Node current = header ; current != null240 ; 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 else256 {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 }

  线性表的两种实现比较

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

 

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

 

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

 

转载地址:http://pqoxl.baihongyu.com/

你可能感兴趣的文章