線性表

線性表

定義

  • 0個或多個數據元素的有限序列。線性表是一個序列,元素之間有順序,第一個元素沒有前驅,左後一個元素沒有後繼,中間每一個元素有且只有一個前驅和後繼。

抽象數據結構

ADT 線性表(List)

Data
	線性表的數據對象集合爲{a_1,a_2,...,a_n},每一個元素的類型均爲DataType。其中,除第一個元素a_1外,每一個元素有且只有一個前驅元素,除了最後一個元素a_n外,每一個元素有且只有一個後繼元素,元素之前的關係是一對一。
	
	
operation
	InitList(*L):	初始化操作,建立一個空的線性表L
	ListEmpty(L):   若線性表爲空,返回true,否則返回false
	ClearList(*L):  將線性表清空
	GetElem(L,i,*e): 將線性表L中的第i個位置的元素返回給e
	LocateElem(L,e): 在線性表L中查找與給定值e相等的元素,如果查找成功,返回該元素在表中的序號,表示成功,否則返回-1,表示失敗。
	ListInsert(*L,i,e): 在線性表L中的第i個位置元素插入新元素e
	ListDelete(*L,i,*e): 刪除線性表L中第i個位置元素,用e接收返回值
	ListLength(L):    返回線性表L的元素個數

endADT

線性表的順序存儲結構

定義

  • 用一段地址連續的存儲單元依次存儲線性表的數據元素

  • 數據長度和線性表長度

    • 數據長度是指存放線性表的存儲空間的長度,這個值在線性表初始化後不再更改
    • 線性表長度,是指表中數據元素的個數,隨着插入、刪除操作該值會修改

操作

本文使用JAVA中AarrayList作爲案例

  • 創建
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
// 無參構造,製造一個Object數組
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 有參構造 ,製造一個指定長度的new Object[initialCapacity]的數組
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
  • 獲取

    線性表通過下表索引來獲取,輸入的索引需要小於等數組長度size。下面舉例說明java中ArrayList的get方法

public E get(int index) {
    rangeCheck(index);

    return elementData(index);
}

// 進行校驗是否小於size
private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    E elementData(int index) {
        return (E) elementData[index];
    }
  • 插入

    指定索引處追加,將輸入數據插入到指定位置,在指定位置後的每一個元素索引+1後移一位

    // 指定索引位置追加    
    public void add(int index, E element) {
            rangeCheckForAdd(index);
    
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }
    
    // 尾部追加
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // size+1
        elementData[size++] = e;
        return true;
    }
    
    	// 計算擴容問題
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
    	// 
        private void ensureExplicitCapacity(int minCapacity) {
            // 當前列表的操作次數
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            // 新的長度 原長度上擴容1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
  • 刪除

    • 輸入索引刪除當前索引的值,該索引後的值每一個索引-1向前移動
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // size -1
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

優缺點

  • 優點

    • 元素間的邏輯關係不需要用額外的儲存空間來保存
    • 快速獲取線性表中任一位置的元素
  • 缺點

    • 插入、刪除操作需要移動大量元素
    • 線性表長度變化較大時,存儲空間的容量難以確認

線性錶鏈式存儲結構

定義

  • 爲了表示每一個數據元素ai與其直接後繼元素ai+1之間的邏輯關係,對元素ai來說,除了存儲本身信息外還需要存儲一個知識其直接後繼元素的信息。我們把存儲數據元素信息的域稱謂數據域,把存儲直接後繼位置的域稱爲指針域。指針域中存儲的信息乘坐指針,由這兩部分信息組成的數據元素稱之結點
  • n個結點鏈接成一個連表,即爲線性表的鏈式存儲結構,鏈表的每一個結點只包含一個指針域,稱之爲單鏈表
  • 鏈表中的第一個結點的存儲位置叫做頭指針,頭指針的數據域不存放數據,只存放只想第一個數據元素的地址

1555401690438

  • 頭指針
    • 圖中0001就是頭指針
    • 頭指針是指鏈表只想第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針
    • 頭指針具有標識作用
    • 無論鏈表是否爲空,頭指針均不能爲空,頭指針是鏈表的必要元素
  • 頭結點
    • 頭結點是爲了操作的統一和方便而設立,放在第一個元素結點之前,其數據域一般沒有實際意義
    • 有了頭結點,對在第一個元素結點前插入和刪除第一個元素結點,做了統一
    • 頭結點不一定是鏈表的必須要素

單鏈表操作

  1. 插入

    1. 頭部插入&尾部插入

      1555462867894

    2. 中間插入

      1555462456055

  2. 刪除

    1. 頭結點刪除&尾結點刪除

      1555463326470

    2. 中間刪除

      1555463592598

/**
 * <p>Title : Node </p>
 * <p>Description : 單向鏈表結點</p>
 *
 * @author huifer
 * @date 2019-04-16
 */
public class Node {

    /**
     * 數據
     */
    public int data;
    /**
     * 下一個結點
     */
    public Node next=null;
    public Node(int data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Node() {
    }

    public Node(int data) {
        this.data = data;
    }
}
/**
 * <p>Title : SingleLinkedList </p>
 * <p>Description : 單向鏈表</p>
 *
 * @author huifer
 * @date 2019-04-16
 */
public class SingleLinkedList {

    /**
     * 頭結點
     */
    private Node head;

    /**
     * 尾結點
     */
    private Node tail;


    private int length;

    public static void main(String[] args) {
        SingleLinkedList s = new SingleLinkedList();
        s.insertLast(1);
        s.insertLast(2);
        s.insertLast(3);
        s.inster(100, 1);
//        Node node = s.deleteFirst();
//        Node node = s.deleteLast();

//        Node node = s.delete(1);
        Node node = s.get(1);
        System.out.println(node);
        s.print();
    }


    /**
     * 輸出所有數據
     */
    public void print() {
        if (isEmpty()) {
            return;
        }
        Node cur = head;
        while (cur != null) {
            System.out.print(cur.data + "\t");
            cur = cur.next;
        }
        System.out.println();
    }


    /**
     * 根據索引獲取數據
     * @param position 索引
     * @return
     */
    public Node get(int position) {
        int i = 0;
        Node cur = head;
        Node rs = null;
        while (cur != null) {
            if (i == position) {
                cur = cur.next;
                rs = cur;
                break;
            }
            i++;
        }
        return rs;
    }


    /**
     * 結點是否空
     */
    public boolean isEmpty() {
        return length == 0;
    }

    public int getLength() {
        return length;
    }

    /**
     * 向鏈表的最後追加一個結點
     */
    public Node insertLast(int data) {
        Node node = new Node(data);
        if (isEmpty()) {
            // 當鏈表是空的時候頭尾都是本身
            head = node;
            tail = node;
        } else {

            // 尾部追加
            tail.next = node;
            tail = node;
        }
        length++;
        return node;
    }

    /**
     * 頭部插入
     */
    public Node inertFirst(int data) {
        Node node = new Node(data);
        Node lastNode;
        lastNode = head;
        head = node;
        head.next = lastNode;
        length++;
        return node;
    }

    /**
     * 指定位置插入數據
     *
     * @param data 數據
     * @param position 指定索引
     */
    public Node inster(int data, int position) {
        if (position < 0) {
            throw new IndexOutOfBoundsException();
        }
        Node node = new Node(data);
        if (position == 0) {
            // 第一個位置插入
            inertFirst(data);
        } else if (isEmpty() || position >= getLength()) {
            // 最後一個位置插入
            insertLast(data);
        } else {
            // 中間部分插入
            // node的上一個結點
            Node cur = head;
            // node 的下一個結點
            Node nextNode = cur.next;
            for (int i = 1; i < getLength(); i++) {
                if (i == position) {
                    // 遍歷結點 當i等於輸入的目標位置後
                    // 上一個結點指向node
                    cur.next = node;
                    // node 的下一個結點只想 cur的下一個
                    node.next = nextNode;
                } else {
                    // 節點更新
                    cur = cur.next;
                    nextNode = cur.next;
                }
            }
            // 頭尾追加都有length++
            length++;
        }

        return node;
    }

    public Node getHead() {
        return head;
    }

    public Node getTail() {
        return tail;
    }

    /**
     * 刪除頭結點
     */
    public Node deleteFirst() {
        if (isEmpty()) {
            throw new RuntimeException("沒有節點 不能進行刪除操作");
        }
        // 刪除節點等於頭
        Node deleteNode = head;
        // 頭結點等於後續
        head = deleteNode.next;
        length--;
        return deleteNode;
    }

    /**
     * 從最後一個結點開始刪除
     */
    public Node deleteLast() {
        Node deleteNode = tail;
        if (isEmpty()) {
            throw new RuntimeException("沒有節點 不能進行刪除操作");
        }
        if (length == 1) {
            head = null;
            tail = null;
        } else {
            Node lastNode = head;
            // 根據單向鏈表的描述,最後一個節點的指針域爲null 作爲結束
            while (lastNode.next != tail) {
                lastNode = lastNode.next;
            }
            tail = lastNode;
            tail.next = null;
        }
        length--;
        return deleteNode;
    }

    /**
     * 指定位置刪除
     *
     * @param position 索引
     */
    public Node delete(int position) {
        if (isEmpty()) {
            throw new RuntimeException("沒有節點 不能進行刪除操作");
        }
        if (position < 0 || position > getLength() - 1) {
            throw new IndexOutOfBoundsException("下標越界");
        }
        if (position == 0) {
            return deleteFirst();
        } else if (position == getLength() - 1) {
            return deleteLast();
        } else {
            // 上一個元素
            Node lastNode = head;
            // 當前元素
            Node cur = lastNode.next;
            // 下一個元素
            Node nextNode = cur.next;

            for (int i = 1; i < getLength(); i++) {
                if (i == position) {
                    lastNode.next = nextNode;
                    break;
                } else {
                    lastNode = cur;
                    cur = nextNode;
                    nextNode = nextNode.next;
                }
            }
            length--;
            return cur;

        }

    }


    /**
     * 刪除指定數據
     */
    public Integer deleteByData(int data) {
        if (isEmpty()) {
            throw new RuntimeException("沒有節點 不能進行刪除操作");

        }

        if (head.data == data) {
            deleteFirst();
            return data;
        } else if (tail.data == data) {
            deleteLast();
            return data;
        } else {
            // 上一個元素
            Node lastNode = null;
            // 當前元素
            Node cur = head;
            // 下一個元素
            Node nextNode = cur.next;

            while (cur != null) {
                if (cur.data == data) {
                    lastNode.next = nextNode;
                    length--;
                    return data;
                }
                if (nextNode == null) {
                    return null;
                } else {
                    lastNode = cur;
                    cur = nextNode;
                    nextNode = nextNode.next;
                }
            }

        }
        return null;


    }


}

靜態鏈表

定義
  • 靜態鏈表使用數組形式保存 [數據,遊標]如下表
索引 數據 下一個的索引
0 null 1
1 數據1 2
2 數據2 3
4 null 0

1555486556754

/**
 * <p>Title : StaticLinkedList </p>
 * <p>Description : 靜態鏈表</p>
 *
 * @author huifer
 * @date 2019-04-17
 */
public class StaticLinkedList {

    private static final int maxSize = 10;
    private StaticNode[] staticNodes = null;

    private int length;

    public StaticLinkedList() {
        this(maxSize);
    }

    public StaticLinkedList(int maxSize) {
        staticNodes = new StaticNode[maxSize];
        for (int i = 0; i < maxSize - 1; i++) {
            staticNodes[i] = new StaticNode(null, i + 1);
        }

        staticNodes[maxSize - 1] = new StaticNode(null, 0);

        this.length = 0;
    }


    public static void main(String[] args) {

        StaticLinkedList staticLinkedList = new StaticLinkedList();
        staticLinkedList.add(999, 1);
        staticLinkedList.add(777, 2);


        staticLinkedList.printAll();

        System.out.println();
    }

    public boolean isEmpty() {
        return length == 0;
    }

    public boolean isFull() {
        return length == maxSize;
    }

    /**
     * 插入數據
     */
    public void insert(Integer data) {
        int t = staticNodes[maxSize - 1].cur;
        int first = staticNodes[0].cur;

        staticNodes[maxSize - 1].cur = first;
        staticNodes[0].cur = staticNodes[first].cur;

        staticNodes[first].cur = t;
        staticNodes[first].data = data;
        length++;
    }

    public boolean add(Integer data, int index) {
        if (isFull() || index > maxSize || index < -1 || data == null) {
            return false;
        }
        if (index == 0) {
            insert(data);
            return true;
        }
        if (index > length) {
            index = length;
        }

        int firstUser = staticNodes[maxSize - 1].cur;
        int firstNull = staticNodes[0].cur;

        for (int i = 1; i < index; i++) {
            firstUser = staticNodes[firstUser].cur;
        }

        int nextUser = staticNodes[firstUser].cur;
        int nextNull = staticNodes[firstNull].cur;

        staticNodes[0].cur = nextNull;
        staticNodes[firstUser].cur = firstNull;
        staticNodes[firstNull].cur = nextUser;
        staticNodes[firstNull].data = data;
        return true;
    }

    public boolean deleteByData(Integer data) {
        if (isEmpty()) {
            return false;
        }
        int temp = maxSize - 1;
        while (temp != 0) {
            if (staticNodes[staticNodes[temp].cur].data.equals(data)) {
                int p = staticNodes[temp].cur;

                staticNodes[temp].cur = staticNodes[p].cur;
                staticNodes[p].cur = staticNodes[0].cur;
                staticNodes[0].cur = p;
                staticNodes[p].data = null;
                length--;
                return true;
            }
            temp = staticNodes[temp].cur;
        }
        return false;
    }

    public boolean deleteAll() {
        if (isEmpty()) {
            return true;
        }
        for (int i = 0; i < maxSize - 1; i++) {
            staticNodes[i].cur = i + 1;
            staticNodes[i].data = null;
        }
        staticNodes[maxSize - 1].cur = 0;
        staticNodes[maxSize - 1].data = null;

        length = 0;
        return true;
    }

    public void print() {
        int first = staticNodes[maxSize - 1].cur;

        for (int i = first; i != 0; ) {
            System.out.print(staticNodes[i].data + "\t");
            i = staticNodes[i].cur;
        }

    }


    public void printAll() {

        System.out.println("鏈表:");
        for (int i = 0; i < maxSize; i++) {
            System.out.print("[索引:" + i + " 數據:" + staticNodes[i].data + " 下一個cur:" + staticNodes[i].cur + "]");

        }

    }

}
優缺點
  • 優點
    • 刪除修改操作只需要修改遊標
  • 缺點
    • 列表長度不確定
    • 失去了順序存儲結構隨機存取的特性

循環鏈表

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

1555545028097

java實現
public class CircularNode {

    public Integer data;
    public CircularNode next;

    public CircularNode(Integer data) {
        this.data = data;
    }

    public CircularNode() {
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("{");
        sb.append("\"data\":")
                .append(data);
        sb.append(",\"next\":")
                .append(next);
        sb.append('}');
        return sb.toString();
    }
}
public class CircularLinkedList {


    private CircularNode head;


    public CircularLinkedList() {
        head = new CircularNode();
        head.data = null;
        head.next = head;
    }

    public static void main(String[] args) {
        CircularLinkedList c = new CircularLinkedList();
        c.add(1);
        c.add(2);
        c.add(3);
        c.printall();

        c.delete(2);
        c.printall();

        CircularNode node = c.get(1);
        System.out.println(node);

    }

    public void add(Integer data) {
        CircularNode node = new CircularNode(data);
        if (head.next == head) {
            head.next = node;
            // 最後一個結點的指針域指向頭
            node.next = head;
        } else {
            // // 找到最後一個元素進行追加
            CircularNode tem = head;
            while (tem.next != head) {
                tem = tem.next;
            }
            tem.next = node;
            node.next = head;
        }
    }

    public void delete(Integer data) {
        CircularNode tem = head;
        while (tem.next != head) {
            // 判斷tem當前指向的結點數據是否和輸入數據一樣
            if (tem.next.data.equals(data)) {
                // 刪除結點
                tem.next = tem.next.next;
            } else {
                // next指針後移
                tem = tem.next;
            }
        }
    }

    public CircularNode get(int i) {
        if (i < 0 || i > size()) {
            throw new IndexOutOfBoundsException();
        } else {
            int count = 0;
            CircularNode node = new CircularNode();
            CircularNode tem = head;
            while (tem.next != head) {
                count++;
                if (count == i) {
                    node.data = tem.next.data;
                }
                tem = tem.next;
            }
            return node;
        }
    }

    public int size() {
        CircularNode tem = head;
        int size = 0;
        // 當tem的指針域指向了head說明到了最後一個結點
        while (tem.next != head) {
            size++;
            tem = tem.next;
        }
        return size;
    }

    public void printall() {
        System.out.println("循環鏈表");
        CircularNode node = head;
        while (node.next != head) {

            node = node.next;
            System.out.print(node.data + "\t");

        }
        System.out.println();
    }
}

雙向鏈表

定義
  • 雙向鏈表是單鏈表的每個結點中,再設置一個指向前驅結點的指針域,雙向鏈表的指針域分別指向一個前驅,一個後繼

1555545312866

  • 刪除

    1555550590107

  • 插入

    1555550816286

java實現
package com.huifer.data.list.doubleLinkedList;

/**
 * <p>Title : DoubleNode </p>
 * <p>Description : 雙線循環鏈表的結點</p>
 *
 * @author huifer
 * @date 2019-04-18
 */
public class DoubleNode {

    /**
     * 前一個結點
     */
    public DoubleNode prev = null;
    /**
     * 後一個結點
     */
    public DoubleNode next = null;

    /**
     * 數據
     */
    public Integer data;

    public DoubleNode() {
    }

    public DoubleNode(Integer data, DoubleNode prev, DoubleNode next) {

        this.prev = prev;
        this.next = next;
        this.data = data;
    }
}

package com.huifer.data.list.doubleLinkedList;

/**
 * <p>Title : DoubleLinkedList </p>
 * <p>Description : 雙向循環鏈表</p>
 *
 * @author huifer
 * @date 2019-04-18
 */
public class DoubleLinkedList {

    private DoubleNode first;
    private DoubleNode last;
    private int size = 0;

    public static void main(String[] args) {
        DoubleLinkedList d = new DoubleLinkedList();
        d.add(0);
        d.add(1);
        d.add(2);

        d.add(1, 999);
        d.delete(999);
        System.out.println();
    }

    public void add(Integer data) {
        addLast(data);
    }


    public boolean delete(Integer data) {
        if (data == null) {
            throw new RuntimeException("參數不能空");
        } else {
            for (DoubleNode d = first; d != null; d = d.next) {
                if (d.data.equals(data)) {
                    deleteNode(d);
                    return true;
                }
            }
        }
        return false;
    }

    private Integer deleteNode(DoubleNode d) {
        DoubleNode next = d.next;
        DoubleNode prev = d.prev;

        if (prev == null) {
            first = next;
        } else {
            // 當前結點上一個結點等與當前結點的下一個
            prev.next = next;
            d.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            // 當前結點的下一個結點等與當前結點的上一個結點
            next.prev = prev;
            d.next = null;
        }

        d.data = null;
        size--;
        return d.data;
    }


    public DoubleNode get(int index) {
        return node(index);
    }


    private void addLast(Integer data) {
        // 原來的最後一個結點
        DoubleNode l = this.last;
        // 新增節點 ,新增節點的上一個結點是最後一個 沒有下一個結點
        DoubleNode newNode = new DoubleNode(data, l, null);
        last = newNode;
        if (l == null) {
            // 如果最後一個結點等於空 那麼只有一個結點 第一個結點等於新節點
            first = newNode;
        } else {
            // 否則l的下一個結點等於新節點
            l.next = newNode;
        }
        size++;
    }


    public void add(int index, Integer data) {
        if (!(index >= 0 && index <= size)) {
            throw new IndexOutOfBoundsException();
        }
        if (size == index) {
            addLast(data);
        } else {
            addbefor(data, node(index));
        }

    }


    private void addbefor(Integer data, DoubleNode node) {
        // 輸入結點的上一個結點
        DoubleNode pred = node.prev;
        // 新節點構造   (數據, 上一個結點是輸入結點的下一個,下一個結點時輸入結點)
        DoubleNode newNode = new DoubleNode(data, pred, node);
        // 輸入結點的下一個結點時新結點
        node.prev = newNode;
        if (pred == null) {
            first = newNode;
        } else {
            pred.next = newNode;
        }
        size++;
    }


    private DoubleNode node(int index) {
        // 一半一半的去查詢找到這個結點
        if (index < (size >> 1)) {
            // 當index這個值小於總量的一半從頭查詢 反之從尾部開始
            DoubleNode d = first;
            for (int i = 0; i < index; i++) {
                d = d.next;
            }
            return d;
        } else {
            DoubleNode d = this.last;
            for (int i = size - 1; i < index; i--) {
                d = d.prev;
            }
            return d;
        }
    }


}

順序存儲結構和單鏈表的對比

對比內容 順序存儲結構 單鏈表
存儲 用連續的儲存單元來存放 任意粗存單元
查詢 O(1) O(n)
插入刪除 O(n) O(n)
  • 線性表需要頻繁查找用順序儲存
  • 線性表需要頻繁插入刪除用單鏈表
  • 線性表中元素數量不確定的時候用單鏈表

總結

線性表
順序存儲結構
鏈式存儲結構
單鏈表
靜態鏈表
循環鏈表
雙向鏈表

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章