从这里开始将要进行Java数据结构的相关讲解,Are you ready?Let‘s go~~
java中的数据结构模型可以分为一下几部分:
1.线性结构
2.树形结构
3.图形或者网状结构
接下来的几张,我们将会分别讲解这几种数据结构,主要也是通过Java代码的方式来讲解相应的数据结构。
今天要讲解的是:Java线性结构
Java数据结构之线性结构
说到线性结构的话,我们可以根据其实现方式分为三类:
1)顺序结构的线性表
2)链式结构的线性表
3)栈和队列的线性表
1、顺序结构的线性表
所谓顺序存储,指的是两个元素在物理上的存储地址和逻辑上的存储地址是一致的,逻辑上相邻的两个元素,它们在物理中存储的地址
也是相邻的。对于jdk中典型的应用就是List接口的实现类ArrayList和Vector(它们两个的区别在于是否是线程同步的)。
如果感兴趣可以查看它们的源代码,下面我们以数组为例,来模仿一下它们...
package com.yonyou.test; import java.util.Arrays; /** * 测试类 * @author 小浩 * @创建日期 2015-3-20 */ public class Test { public static void main(String[] args) { SequenceList<String> list=new SequenceList<String>(); System.out.println("线性表的初始化长度为:"+list.length()); list.add("Hello"); list.add("World"); list.add("天下太平"); System.out.println("当前list中的元素为:"+list); list.remove(); System.out.println("当前list中的元素为:"+list); System.out.println("当前元素线性表是否为空:"+list.empty()); } } /** * 创建一个线性链表 * 注意这个类是线程不安全的,在多线程下不要使用 * @author 小浩 * @创建日期 2015-3-20 * @param <T> */ class SequenceList<T> { //数组的默认初始化长度 private int DEFAULT_SIZE = 10; // 保存数组的长度。 private int capacity; // 定义一个数组用于保存顺序线性表的元素 private Object[] elementData; // 保存顺序表中元素的当前个数 private int size = 0; // 以默认数组长度创建空顺序线性表 public SequenceList() { capacity = DEFAULT_SIZE; elementData = new Object[capacity]; } // 以一个初始化元素来创建顺序线性表 public SequenceList(T element) { this(); elementData[0] = element; size++; } /** * 以指定长度的数组来创建顺序线性表 * @param element 指定顺序线性表中第一个元素 * @param initSize 指定顺序线性表底层数组的长度 */ public SequenceList(T element , int initSize) { capacity = 1; // 把capacity设为大于initSize的最小的2的n次方 while (capacity < initSize) { capacity <<= 1; } elementData = new Object[capacity]; elementData[0] = element; size++; } /** * 获取顺序线性表的大小 * @return */ public int length() { return size; } /** * 获取顺序线性表中索引为i处的元素 * @param i * @return */ @SuppressWarnings("unchecked") public T get(int i) { if (i < 0 || i > size - 1) { throw new IndexOutOfBoundsException("线性表索引越界"); } return (T)elementData[i]; } /** * 查找顺序线性表中指定元素的索引 * @param element * @return */ public int locate(T element) { for (int i = 0 ; i < size ; i++) { if (elementData[i].equals(element)) { return i; } } return -1; } /** * 向顺序线性表的指定位置插入一个元素。 * @param element * @param index */ private void insert(T element , int index) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("线性表索引越界"); } ensureCapacity(size + 1); // 将index处以后所有元素向后移动一格。 System.arraycopy(elementData , index , elementData , index + 1 , size - index); elementData[index] = element; size++; } /** * 在线性顺序表的开始处添加一个元素。 * @param element */ public void add(T element) { insert(element , size); } /** * 扩充底层数组长度,很麻烦,而且性能很差 * @param minCapacity */ private void ensureCapacity(int minCapacity) { // 如果数组的原有长度小于目前所需的长度 if (minCapacity > capacity) { // 不断地将capacity * 2,直到capacity大于minCapacity为止 while (capacity < minCapacity) { capacity <<= 1; } elementData = Arrays.copyOf(elementData , capacity); } } /** * 删除顺序线性表中指定索引处的元素 * @param index * @return */ @SuppressWarnings("unchecked") private T delete(int index) { if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException("线性表索引越界"); } T oldValue = (T)elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) { System.arraycopy(elementData , index+1 , elementData, index , numMoved); } // 清空最后一个元素 elementData[--size] = null; return oldValue; } /** * 删除顺序线性表中最后一个元素 * @return */ public T remove() { return delete(size - 1); } /** * 判断顺序线性表是否为空表 * @return */ public boolean empty() { return size == 0; } /** * 清空线性表 */ public void clear() { // 将底层数组所有元素赋为null Arrays.fill(elementData , null); size = 0; } /** * 重写toString */ public String toString() { if (size == 0) { return "[]"; } else { StringBuilder sb = new StringBuilder("["); for (int i = 0 ; i < size ; i++ ) { sb.append(elementData[i].toString() + ", "); } int len = sb.length(); return sb.delete(len - 2 , len).append("]").toString(); } } }
具体的讲解这里就不说了,还是那句话,如果感兴趣可以查看相关源代码。
2、链式结构的线性表
链式存储结构的线性表是相对于顺序结构的线性表而言的。对于链式存储的线性表,其中各个元素的物理地址和逻辑地址是不确定的。
也就是说逻辑上相邻的两个元素,他们在物理上不一定是相邻的。
在Java的jdk中典型的应用就是List接口下面的LinkedList(线程不安全,多线程下不要使用),如果感兴趣的可以查看相关源代码。
这里仅仅简单的模仿一下。
首先模仿的是单链表:
package com.yonyou.test; /** * 测试类 * @author 小浩 * @创建日期 2015-3-20 */ public class Test { public static void main(String[] args) { LinkList<String> list=new LinkList<String>(); System.out.println("线性表的初始化长度为:"+list.length()); list.add("Hello"); list.add("World"); list.add("天下太平"); System.out.println("当前list中的元素为:"+list); list.remove(); System.out.println("当前list中的元素为:"+list); System.out.println("当前元素线性表是否为空:"+list.empty()); } } /** * 创建一个链式线性表 * 注意这个类是线程不安全的,在多线程下不要使用 * @author 小浩 * @创建日期 2015-3-20 * @param <T> */ class LinkList<T> { /**定义一个内部类Node,Node实例代表链表的节点。 * * @author 小浩 * @创建日期 2015-3-20 */ private class Node { // 保存节点的数据 private T data; // 指向下个节点的引用 private Node next; // 无参数的构造器 @SuppressWarnings("unused") public Node() { } // 初始化全部属性的构造器 public Node(T data , Node next) { this.data = data; this.next = next; } } // 保存该链表的头节点 private Node header; // 保存该链表的尾节点 private Node tail; //保存该链表中已包含的节点数 private int size; /** * 创建空链表 */ public LinkList() { // 空链表,header和tail都是null header = null; tail = null; } /** * 以指定数据元素来创建链表,该链表只有一个元素 * @param element */ public LinkList(T element) { header = new Node(element , null); // 只有一个节点,header、tail都指向该节点 tail = header; size++; } /** * 返回链表的长度 * @return */ public int length() { return size; } /** * 获取链式线性表中索引为index处的元素 * @param index * @return */ public T get(int index) { return getNodeByIndex(index).data; } // 根据索引index获取指定位置的节点 private Node getNodeByIndex(int index) { if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException("线性表索引越界"); } // 从header节点开始 Node current = header; for (int i = 0 ; i < size && current != null ; i++ , current = current.next) { if (i == index) { return current; } } return null; } /** * 查找链式线性表中指定元素的索引 * @param element * @return */ public int locate(T element) { // 从头节点开始搜索 Node current = header; for (int i = 0 ; i < size && current != null ; i++ , current = current.next) { if (current.data.equals(element)) { return i; } } return -1; } /** 向线性链式表的指定位置插入一个元素。 * * @param element * @param index */ public void insert(T element , int index) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("线性表索引越界"); } // 如果还是空链表 if (header == null) { add(element); } else { // 当index为0时,也就是在链表头处插入 if (index == 0) { addAtHeader(element); } else { // 获取插入点的前一个节点 Node prev = getNodeByIndex(index - 1); // 让prev的next指向新节点, // 让新节点的next引用指向原来prev的下一个节点。 prev.next = new Node(element , prev.next); size++; } } } /** * 采用尾插法为链表添加新节点。 * @param element */ public void add(T element) { // 如果该链表还是空链表 if (header == null) { header = new Node(element , null); // 只有一个节点,header、tail都指向该节点 tail = header; } else { // 创建新节点 Node newNode = new Node(element , null); // 让尾节点的next指向新增的节点 tail.next = newNode; // 以新节点作为新的尾节点 tail = newNode; } size++; } /**采用头插法为链表添加新节点。 * * @param element */ private void addAtHeader(T element) { // 创建新节点,让新节点的next指向原来的header // 并以新节点作为新的header header = new Node(element , header); // 如果插入之前是空链表 if (tail == null) { tail = header; } size++; } /** * 删除链式线性表中指定索引处的元素 * @param index * @return */ public T delete(int index) { if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException("线性表索引越界"); } Node del = null; // 如果被删除的是header节点 if (index == 0) { del = header; header = header.next; } else { // 获取删除点的前一个节点 Node prev = getNodeByIndex(index - 1); // 获取将要被删除的节点 del = prev.next; // 让被删除节点的next指向被删除节点的下一个节点。 prev.next = del.next; // 将被删除节点的next引用赋为null. del.next = null; } size--; return del.data; } /** * 删除链式线性表中最后一个元素 * @return */ public T remove() { return delete(size - 1); } /** * 判断链式线性表是否为空表 * @return */ public boolean empty() { return size == 0; } /** * 清空线性表 */ public void clear() { // header、tail赋为null header = null; tail = null; size = 0; } /** * 重写toString方法 */ public String toString() { // 链表为空链表时 if (empty()) { return "[]"; } else { StringBuilder sb = new StringBuilder("["); for (Node current = header ; current != null ; current = current.next ) { sb.append(current.data.toString() + ", "); } int len = sb.length(); return sb.delete(len - 2 , len).append("]").toString(); } } }
其次模仿的是循环链表(即将链表的尾节点的next指向头节点)
具体代码就不写了,感兴趣的可以自己写写看,也不难...主要就是注意设置和维护下面这条语句
tail.next=header;
最后要讲解的双向链表的内容(相对于单链表而言,每个节点不仅有一个next,而且有一个指向前一个元素的pre)
具体的内容请参考下面的代码:
package com.yonyou.test; /** * 测试类 * @author 小浩 * @创建日期 2015-3-20 */ public class Test { public static void main(String[] args) { DoubleLinkList<String> list=new DoubleLinkList<String>(); System.out.println("线性表的初始化长度为:"+list.length()); list.add("Hello"); list.add("World"); list.add("天下太平"); System.out.println("当前list中的元素为:"+list); list.remove(); System.out.println("当前list中的元素为:"+list); System.out.println("当前元素线性表是否为空:"+list.empty()); } } /** * 创建一个双向链式线性表 * 注意这个类是线程不安全的,在多线程下不要使用 * @author 小浩 * @创建日期 2015-3-20 * @param <T> */ class DoubleLinkList<T> { // 定义一个内部类Node,Node实例代表链表的节点。 private class Node { // 保存节点的数据 private T data; // 指向上个节点的引用 private Node prev; // 指向下个节点的引用 private Node next; // 无参数的构造器 public Node() { } // 初始化全部属性的构造器 public Node(T data , Node prev , Node next) { this.data = data; this.prev = prev; this.next = next; } } // 保存该链表的头节点 private Node header; // 保存该链表的尾节点 private Node tail; // 保存该链表中已包含的节点数 private int size; /** * 创建空链表 */ public DoubleLinkList() { // 空链表,header和tail都是null header = null; tail = null; } /** * 以指定数据元素来创建链表,该链表只有一个元素 * @param element */ public DoubleLinkList(T element) { header = new Node(element , null , null); // 只有一个节点,header、tail都指向该节点 tail = header; size++; } /** * 返回链表的长度 * @return */ public int length() { return size; } /** * 获取链式线性表中索引为index处的元素 * @param index * @return */ public T get(int index) { return getNodeByIndex(index).data; } /**根据索引index获取指定位置的节点 * * @param index * @return */ private Node getNodeByIndex(int index) { if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException("线性表索引越界"); } if (index <= size / 2) { // 从header节点开始 Node current = header; for (int i = 0 ; i <= size / 2 && current != null ; i++ , current = current.next) { if (i == index) { return current; } } } else { // 从tail节点开始搜索 Node current = tail; for (int i = size - 1 ; i > size / 2 && current != null ; i++ , current = current.prev) { if (i == index) { return current; } } } return null; } /** * 查找链式线性表中指定元素的索引 * @param element * @return */ public int locate(T element) { // 从头节点开始搜索 Node current = header; for (int i = 0 ; i < size && current != null ; i++ , current = current.next) { if (current.data.equals(element)) { return i; } } return -1; } /** * 向线性链式表的指定位置插入一个元素。 * @param element * @param index */ public void insert(T element , int index) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("线性表索引越界"); } // 如果还是空链表 if (header == null) { add(element); } else { // 当index为0时,也就是在链表头处插入 if (index == 0) { addAtHeader(element); } else { // 获取插入点的前一个节点 Node prev = getNodeByIndex(index - 1); // 获取插入点的节点 Node next = prev.next; // 让新节点的next引用指向next节点,prev引用指向prev节点 Node newNode = new Node(element , prev , next); // 让prev的next指向新节点。 prev.next = newNode; // 让prev的下一个节点的prev指向新节点 next.prev = newNode; size++; } } } /** * 采用尾插法为链表添加新节点。 * @param element */ public void add(T element) { // 如果该链表还是空链表 if (header == null) { header = new Node(element , null , null); // 只有一个节点,header、tail都指向该节点 tail = header; } else { // 创建新节点,新节点的pre引用指向原tail节点 Node newNode = new Node(element , tail , null); // 让尾节点的next指向新增的节点 tail.next = newNode; // 以新节点作为新的尾节点 tail = newNode; } size++; } /** * 采用头插法为链表添加新节点。 * @param element */ public void addAtHeader(T element) { // 创建新节点,让新节点的next指向原来的header // 并以新节点作为新的header header = new Node(element , null , header); // 如果插入之前是空链表 if (tail == null) { tail = header; } size++; } /** * 删除链式线性表中指定索引处的元素 * @param index * @return */ public T delete(int index) { if (index < 0 || index > size - 1) { throw new IndexOutOfBoundsException("线性表索引越界"); } Node del = null; // 如果被删除的是header节点 if (index == 0) { del = header; header = header.next; // 释放新的header节点的prev引用 header.prev = null; } else { // 获取删除点的前一个节点 Node prev = getNodeByIndex(index - 1); // 获取将要被删除的节点 del = prev.next; // 让被删除节点的next指向被删除节点的下一个节点。 prev.next = del.next; // 让被删除节点的下一个节点的prev指向prev节点。 if (del.next != null) { del.next.prev = prev; } // 将被删除节点的prev、next引用赋为null. del.prev = null; del.next = null; } size--; return del.data; } /** * 删除链式线性表中最后一个元素 * @return */ public T remove() { return delete(size - 1); } /** * 判断链式线性表是否为空链表 * @return */ public boolean empty() { return size == 0; } /** * 清空线性表 */ public void clear() { // 将底层数组所有元素赋为null header = null; tail = null; size = 0; } /** * 重写toString方法 */ public String toString() { // 链表为空链表时 if (empty()) { return "[]"; } else { StringBuilder sb = new StringBuilder("["); for (Node current = header ; current != null ; current = current.next ) { sb.append(current.data.toString() + ", "); } int len = sb.length(); return sb.delete(len - 2 , len).append("]").toString(); } } public String reverseToString() { // 链表为空链表时 if (empty()) { return "[]"; } else { StringBuilder sb = new StringBuilder("["); for (Node current = tail ; current != null ; current = current.prev ) { sb.append(current.data.toString() + ", "); } int len = sb.length(); return sb.delete(len - 2 , len).append("]").toString(); } } }
由于篇幅的限制,对于线性结构中的栈和队列的讲解,请看下一篇博客~~~