[从今天开始修炼数据结构]基本概念 [从今天开始修炼数据结构]线性表及其实现以及实现有Itertor的ArrayList和LinkedList [从今天开始修炼数据结构]栈、斐波那契数列、逆波兰四则运算的实现 [从今天开始修炼数据结构]队列、循环队列、PriorityQueue的原理及实现 一、线性表   1,什么是线性表   线性表就是零个或多个数据元素的有限序列。线性表中的每个元素只能有零个或一个前驱元素,零个或一个后继元素。在较复杂的线性表中,一个数据元素可以由若干个数据项组成。比如牵手排队的小朋友,可以有学号、姓名、性别、出生日期等数据项。   2,线性表的抽象数据类型   线性表的抽象数据类型定义如下。 复制代码 ADT List Data 线性表的数据对象集合为{a1,a2,...,a3},每个元素的类型均为DataType Operation InitList (L) : 初始化操作,建立一个空的线性表L ListEmpty (L) : 若线性表为空,则返回true,否则返回false ClearList(L): 清空线性表 GetElem(L,i,e): 将线性表L中的第i个位置元素值返回给e LocateElem(L,e): 在L中查找与给定值e相等的元素,若查找成功,返回位 序;若查找失败,返回0 ListInsert(L,i,e): 在L中的第i个位置插入新元素e ListDelete(L,i,e): 删除线性表L中的第i个未知元素,并通过e返回其值 ListLength(L): 返回L中的元素个数 endADT 复制代码   上述操作是最基本的,对于实际中涉及的线性表有更复杂的操作,但一般可以用上述简单操作的组合来完成,例如我们来思考将线性表A和B组合,实现并集操作,只要循环B中的元素,判断该元素是否在A中,若不存在,插入A即可,实现如下。 复制代码 public class ListDemo { public static void main(String[] args){ List ListA = new ArrayList(); List ListB = new ArrayList(); ListA.add("zhang"); ListA.add("li"); ListA.add("wang"); ListB.add("zhang"); ListB.add("li"); ListB.add("liu"); for (String name: ListB ) { if (!ListA.contains(name)){ ListA.add(name); } } System.out.println(ListA.toString()); } } 复制代码 二、线性表的顺序存储结构   1,顺序存储结构的实现   可以用一维数组来实现顺序存储结构。描述顺序存储结构需要三个属性:     1,存储空间的起始位置:即数组的存储位置     2,线性表的最大存储容量:数组初始化的长度     3,线性表的当前长度      2,顺序存储结构的泛型实现    复制代码 package List; import java.util.Iterator; import java.util.List; public class ArrayListDemo { //初始化后可存放的数量,从1开始计数 private int capacity; //private T[] data; 不能构造泛型数组,我们用Object[] private Object[] data; //已存的数量,从0开始计数。 当size == capacity 时,满。 size = index + 1. private int size; private static int DEFAULT_CAPACITY = 10; /** * 自定义顺序存储线性表的构造函数 * @param capacity 数组的初始化长度 */ public ArrayListDemo(int capacity) { //下面写法会报错。因为Java中不能使用泛型数组,有隐含的ClassCastException //date = new T[length]; //那么JDK中的ArrayList是怎么实现的? //JDK中使用Object[]来初始化了表 if (capacity > 0){ this.capacity = capacity; this.data = new Object[capacity]; size = 0; }else { throw new IndexOutOfBoundsException("长度不合法"); } } public ArrayListDemo() { this(DEFAULT_CAPACITY); } public void add(T element){ if (size >= capacity){ grow(); } data[size++] = element; } private void grow() { capacity = (int)(capacity * 1.5); Object[] newDataArr = new Object[(int)(capacity)]; for (int i = 0; i < size; i++){ newDataArr[i] = data[size]; } data = newDataArr; } /** * 指定序号元素的获取,Java核心技术中称随机存取 * @param index 得到数组元素的角标 * @return 对应的数据元素 */ public T getElem(int index){ if (index >= size){ throw new IndexOutOfBoundsException(); } return (T) data[index]; //这里会报警告,如何解决? } public void insertElem(int index, T elem){ if (size == capacity){ grow(); } for (int i = size - 1; i > index; i--){ data[i + 1] = i; } size++; data[index] = elem; } private void checkIndex(int index)throws IndexOutOfBoundsException{ //TODO if (index < 0){ throw new IndexOutOfBoundsException(); } } /** * 从指定index的位置上移除元素,且返回该元素的值 * @param index 要移除的索引 * @return 被移除的元素的值 * @throws IndexOutOfBoundsException 超出范围 */ public T removeElem(int index)throws IndexOutOfBoundsException{ checkIndex(index); if (index >= size){ throw new IndexOutOfBoundsException(); }else { Object elem = data[index]; for (int i = index; i < size - 1; i++){ data[i] = data[i + 1]; } size--; return (T)elem; //这里会报警告,如何解决? } } public int size(){ return capacity; } public ArrayListItertor itertor(){ return new ArrayListItertor(); } private class ArrayListItertor implements List { private int currentIndex; public ArrayListItertor(){ currentIndex = 0; } @Override public boolean hasNext() { return !(currentIndex >= size); } @Override public Object next() { Object o = data[currentIndex]; currentIndex++; return o; } } } 复制代码   顺序存储结构的优点:无需为表中元素的逻辑关系而增加额外的存储空间;可以快速存取表中任意位置的元素   缺点:插入和删除操作需移动大量元素;当线性表长度变化较大时,难以确定存储空间容量;造成存储空间的“碎片”。   三、链式存储结构   1,链式存储结构的概念   为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了储存其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。把存储数据元素信息的域成为数据域,把存储后继位置的域成为指针域。   指针域中存储的信息称作指针或链,这两部分信息组成数据元素ai的存储映像,称为结点Node。   链表中第一个结点的存储位置叫做头指针。   链表中最后一个结点指针为“空”。   2,链式存储结构的实现    复制代码 package List; import java.nio.file.NotDirectoryException; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; /* 模仿JDK的双向链表实现一个简单的LinkedList 没有头结点 */ public class LinkedListDemo implements Iterable { private Node firstNode; private Node lastNode; private int size; public LinkedListDemo(){ size = 0; } /** * 在表尾插入一个新节点 * @param data 新节点的数据域 */ public void addLast(T data){ Node last = lastNode; Node node = new Node<>(data, last, null); lastNode = node; if (last == null){ firstNode = node; } else{ last.nextNode = node; } size++; } /** * 在表头插入一个新结点 * @param data 新结点的数据域 */ public void addFirst(T data){ Node first = firstNode; Node node = new Node(data,null, first); firstNode = node; if (first == null){ lastNode = node; }else { first.preNode = node; } size++; } public T get(int index){ checkIndex(index); return search(index).data; } public int size(){ return size; } private Node search(int index){ checkIndex(index); Node pointer = firstNode; for (int i = 0; i < index; i++){ pointer = pointer.nextNode; } return pointer; } /** * 在index前面插入一个元素 * @param index 索引 * @param data 数据域 */ public void insert(int index, T data){ checkIndex(index); if (index == 0){ Node f = firstNode; Node newNode = new Node<>(data, null, f); firstNode = newNode; f.preNode = newNode; }else { Node n = search(index); Node pre = n.preNode; Node newNode1 = new Node<>(data, pre, n); pre.nextNode = newNode1; n.preNode = newNode1; } size++; } /** * 检查index是否越界.合法的最大index = size - 1. * @param index 要检查的索引 */ private void checkIndex(int index) { if (index >= size || index < 0){ throw new IndexOutOfBoundsException(); } } /** * 根据结点的次序来删除结点。 后发现与JDK中的删除操作不同。 * JDK中LinkedList没有按照次序插入或删除的操作,都使用比较数据域是否相同的方法来删除。 * @param index 结点的次序 * @return 被删除结点的数据域 */ public T remove(int index){ checkIndex(index); Node pointer = search(index); Node pre = pointer.preNode; Node next = pointer.nextNode; if (firstNode == null) { pre.nextNode = next; }else { pre.nextNode = next; pointer.nextNode = null; } if (next == null){ lastNode = pre; }else { next.preNode = pre; pointer.preNode = null; } size--; return pointer.data; } /** * 清空链表,帮助GC回收内存。 * 在JDK中,LinkedList实现了Iterator,如果迭代到链表的中间,那么只释放表头的话就不会引起GC回收 * 所以要在循环中逐一清空每一个结点。 */ public void clear(){ for (Node pointer = firstNode;pointer != null; ){ Node next = pointer.nextNode; pointer.data = null; pointer.preNode = null; pointer.nextNode = null; pointer = next; } size = 0; firstNode = null; lastNode = null; } private static class Node{ T data; Node nextNode; Node preNode; public Node(){ } private Node(T data, Node pre, Node next){ this.data = data; this.preNode = pre; this.nextNode = next; } } public LinkedListItertor itertor(){ return new LinkedListItertor(); } class LinkedListItertor implements Iterator{ private Node currentNode; private int nextIndex; public LinkedListItertor(){ currentNode = firstNode; nextIndex = 0; } @Override public boolean hasNext() { return nextIndex != size; } @Override public T next() { Node node = currentNode; currentNode = currentNode.nextNode; nextIndex++; return node.data; } } } 复制代码 单链表与顺序存储结构对比: 单链表 顺序存储结构 查找 O(n) O(1) 插入和删除 O(1) O(n) 空间性能 需要预分配;分大了容易浪费;分小了需要扩容 不需要预分配 三、静态链表 1,什么是静态链表? 用数组描述的链表叫做静态链表。该数组的每一个元素有两个数据域,data存储数据,cur存储后继结点的角标。该数组会被建立的大一些,未使用的作为备用链表。 该数组的第一个元素的cur存储备用链表的第一个节点的下标;数组最后一个元素的cur存储第一个有数值的元素的下标。如下图。 若为新建的空链表,则如下图。 2,静态链表的实现 复制代码 package List; import java.util.List; import java.util.prefs.NodeChangeEvent; public class StaticLinkList { private ListNode[] list; private static int DEFAULT_CAPACITY = 1000; private int capacity; private int size; // private ListNode firstNode; // private ListNode endNode; // private int capacity; // private int size; private StaticLinkList(int capacity){ this.capacity = capacity; list = new ListNode[capacity]; list[0] = new ListNode(null, 1); list[capacity - 1] = new ListNode(null, 0); size = 0; /* size = 0; this.capacity = capacity; list = new Object[capacity]; firstNode = new ListNode(null, 1); list[0] = firstNode; endNode = new ListNode(null, 0); list[capacity - 1] = endNode; */ } public int size(){ return capacity; } public StaticLinkList(){ this(DEFAULT_CAPACITY); } public void addLast(T data){ ListNode tail = FindTail(); ListNode newNode = new ListNode(data, 0); list[list[0].cur] = newNode; tail.cur = list[0].cur; synchronize(); size++; } /** * 在index前面插入一个元素 由于是单向链表,所以要先取到index上一个元素 * @param index 角标 * @param data 数据 */ public void insert(int index, T data){ ListNode beforeIndexNode = searchPreNode(index); int indexCur = beforeIndexNode.cur; ListNode newNode = new ListNode(data, indexCur); list[list[0].cur] = newNode; beforeIndexNode.cur = list[0].cur; synchronize(); size++; } private void synchronize(){ int i = 1; while(list[i] != null){ i++; } list[0].cur = i; } public T delete(int index){ checkIndex(index); ListNode preNode = searchPreNode(index); ListNode indexNode = list[preNode.cur]; //这行报错NullPointerException int cur = indexNode.cur; preNode.setCur(indexNode.getCur()); indexNode.cur = 0; T data = indexNode.data; indexNode.data = null; synchronize(); size--; return data; } public void checkIndex(int index){ if (index >= size){ throw new IndexOutOfBoundsException(); } } private ListNode FindTail(){ ListNode tailNode = list[capacity - 1]; while (tailNode.cur != 0){ tailNode = list[tailNode.cur]; } return tailNode; } /** * 拿到index - 1这个结点,才能将新结点插入到index位置上 * @param index 要插入的索引(从0开始) * @return 返回查找到的结点 */ private ListNode searchPreNode(int index){ ListNode node = list[capacity - 1]; for(int i = 0; i < index; i++){ node = list[node.cur]; } return node; } private class ListNode{ private T data; private int cur; private ListNode(T data, int cur){ this.data = data; this.cur = cur; } public void setData(T data) { this.data = data; } public void setCur(int cur) { this.cur = cur; } private T getData(){ return data; } private int getCur(){ return cur; } } } 复制代码 3,静态链表的优点:在插入和删除操作时,只需要修改游标,不需要移动元素。       缺点:没有解决连续存储分配带来的表长难以确定的问题