[從今天開始修煉數據結構]線性表及其實現以及實現有Itertor的ArrayList和LinkedList

1、線性表java

  1,什麼是線性表node

  線性表就是零個或多個數據元素的有限序列。線性表中的每一個元素只能有零個或一個前驅元素,零個或一個後繼元素。在較複雜的線性表中,一個數據元素能夠由若干個數據項組成。好比牽手排隊的小朋友,能夠有學號、姓名、性別、出生日期等數據項。數組

  2,線性表的抽象數據類型ide

  線性表的抽象數據類型定義以下。函數

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<String> ListA = new ArrayList<String>();
        List<String> ListB = new ArrayList<String>();

        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());
    }
}

2、線性表的順序存儲結構學習

  1,順序存儲結構的實現this

  能夠用一維數組來實現順序存儲結構。描述順序存儲結構須要三個屬性:spa

    1,存儲空間的起始位置:即數組的存儲位置指針

    2,線性表的最大存儲容量:數組初始化的長度

    3,線性表的當前長度

  

 

 

   2,順序存儲結構的泛型實現

 

  

package List;

import java.util.Iterator;
import java.util.List;

public class ArrayListDemo<T> {
//初始化後可存放的數量,從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;
}
}
}

  順序存儲結構的優勢:無需爲表中元素的邏輯關係而增長額外的存儲空間;能夠快速存取表中任意位置的元素

  缺點:插入和刪除操做需移動大量元素;當線性表長度變化較大時,難以肯定存儲空間容量;形成存儲空間的「碎片」。  

3、鏈式存儲結構

  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<T> implements Iterable {
    private Node<T> firstNode;
    private Node<T> lastNode;
    private int size;

    public LinkedListDemo(){
        size = 0;
    }

    /**
     * 在表尾插入一個新節點
     * @param data 新節點的數據域
     */
    public void addLast(T data){
        Node<T> last = lastNode;
        Node<T> 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<T> first = firstNode;
        Node<T> node = new Node<T>(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<T> search(int index){
        checkIndex(index);
        Node<T> 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<T> f = firstNode;
            Node<T> newNode = new Node<>(data, null, f);
            firstNode = newNode;
            f.preNode = newNode;
        }else {
            Node<T> n = search(index);
            Node<T> pre = n.preNode;
            Node<T> 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<T> pointer = search(index);
        Node<T> pre = pointer.preNode;
        Node<T> 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<T> pointer = firstNode;pointer != null; ){
            Node<T> 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>{
        T data;
        Node<T> nextNode;
        Node<T> preNode;

        public  Node(){
        }
        private Node(T data, Node<T> pre, Node<T> next){
            this.data = data;
            this.preNode = pre;
            this.nextNode = next;
        }
    }

    public LinkedListItertor itertor(){
        return new LinkedListItertor();
    }

    class LinkedListItertor implements Iterator{
        private Node<T> currentNode;
        private int nextIndex;

        public LinkedListItertor(){
            currentNode = firstNode;
            nextIndex = 0;
        }
        @Override
        public boolean hasNext() {
            return nextIndex != size;
        }

        @Override
        public T next() {
            Node<T> node = currentNode;
            currentNode = currentNode.nextNode;
            nextIndex++;
            return node.data;
        }
    }
}

單鏈表與順序存儲結構對比:

  單鏈表 順序存儲結構
查找 O(n) O(1)
插入和刪除 O(1) O(n)
空間性能

須要預分配;分大了容易浪費;分小了須要擴容

不須要預分配

3、靜態鏈表

1,什麼是靜態鏈表?

用數組描述的鏈表叫作靜態鏈表。該數組的每個元素有兩個數據域,data存儲數據,cur存儲後繼結點的角標。該數組會被創建的大一些,未使用的做爲備用鏈表。

該數組的第一個元素的cur存儲備用鏈表的第一個節點的下標;數組最後一個元素的cur存儲第一個有數值的元素的下標。以下圖。

 

 若爲新建的空鏈表,則以下圖。

 

 2,靜態鏈表的實現

 

package List;

import java.util.List;
import java.util.prefs.NodeChangeEvent;

public class StaticLinkList <T>{
    private ListNode<T>[] 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<T>(null, 1);
        list[capacity - 1] = new ListNode<T>(null, 0);
        size = 0;
        /*
        size = 0;
        this.capacity = capacity;
        list = new Object[capacity];
        firstNode = new ListNode<Integer>(null, 1);
        list[0] = firstNode;
        endNode = new ListNode<Integer>(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<T> newNode = new ListNode<T>(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<T> newNode = new ListNode<T>(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<T> preNode = searchPreNode(index);
        ListNode<T> 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<T> searchPreNode(int index){
        ListNode<T> node = list[capacity - 1];
        for(int i = 0; i < index; i++){
            node = list[node.cur];
        }
        return node;
    }

    private class ListNode<T>{
        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,靜態鏈表的優勢:在插入和刪除操做時,只須要修改遊標,不須要移動元素。

      缺點:沒有解決連續存儲分配帶來的表長難以肯定的問題; 失去了順序存儲結構隨機存取的特性。

  在高級語言中不常使用,但能夠學習這種解決問題的方法。

4、循環鏈表

 1,什麼是循環鏈表

  將單鏈表中終端節點的指針端由空指針改成指向頭結點,就使整個單鏈表造成一個環,這種頭尾相接的單鏈表成爲單循環鏈表,簡稱循環鏈表

  循環鏈表解決了一個很麻煩的問題,如何從當中一個結點出發,訪問到鏈表的所有結點。循環鏈表的結構以下圖所示

 

 單鏈表表尾的判斷是p->next是否爲空,而循環鏈表是判斷p->next是否爲頭結點。

  2,改造循環鏈表

  咱們在上面的循環鏈表中,訪問表頭的複雜度爲O(1) , 訪問表尾的複雜度爲O(n)。那麼能不能讓訪問表尾的複雜度也爲O(1)呢?

  咱們把頭指針改成尾指針,以下圖

 

 

 

這樣不論是訪問表頭仍是表尾都方便了不少。若是咱們要把兩個循環鏈表合併,只須要作以下操做。

 

 3,循環鏈表的實現

 

package List;

public class CircularLinkedList<T> {
private Node<T> last;
private Node<T> headNode;

public CircularLinkedList(){
Node<T> node = new Node<>();
headNode = new Node<T>(null, node);
last = new Node<T>(null, headNode);
headNode.nextNode = last;
}

/**
* 添加數據時先調用此函數設置last中的data,而後再使用add添加其餘元素
* @param data last中的data
*/
public void setLast(T data){
last.data = data;
}

public void add(T data){
Node<T> newNode = new Node<>(data, headNode);
last.nextNode = newNode;
last = newNode;
}

public T deleteFromLast(){
Node<T> lastNode = last;
T data = lastNode.data;
//lastNode.data = null;
Node<T> node = lastNode;
while (node.nextNode != last){
node = node.nextNode;
}
node.nextNode = headNode;
last = node;
lastNode.nextNode = null;
lastNode.data = null;
return data;
}

public boolean combine(CircularLinkedList<T> circularLinkedList){
Node<T> listHeadNode = circularLinkedList.last.nextNode;
last.nextNode = circularLinkedList.last.nextNode.nextNode;
circularLinkedList.last.nextNode = headNode;
listHeadNode.nextNode = null;
last = circularLinkedList.last;
return true;
}

private static class Node<T>{
T data;
Node<T> nextNode;

public Node(){
}
private Node(T data, Node<T> next){
this.data = data;
this.nextNode = next;
}
}
}

雙向鏈表的實如今上面ArrayList的實現中給出。

總結:

  咱們就線性表的兩大結構作了講述,先將的順序存儲結構,一般用數組實現;而後是咱們的重點,由順序存儲結構的插入和刪除操做不方便,消耗時間大,受固定的存儲空間限制,咱們引入了鏈式存儲結構。分爲單鏈表、靜態鏈表、循環鏈表、雙向鏈表作了講解。

 

  

 

 

相關文章
相關標籤/搜索