線性表
定義
- 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個結點鏈接成一個連表,即爲線性表的鏈式存儲結構,鏈表的每一個結點只包含一個指針域,稱之爲單鏈表
- 鏈表中的第一個結點的存儲位置叫做頭指針,頭指針的數據域不存放數據,只存放只想第一個數據元素的地址
- 頭指針
- 圖中0001就是頭指針
- 頭指針是指鏈表只想第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針
- 頭指針具有標識作用
- 無論鏈表是否爲空,頭指針均不能爲空,頭指針是鏈表的必要元素
- 頭結點
- 頭結點是爲了操作的統一和方便而設立,放在第一個元素結點之前,其數據域一般沒有實際意義
- 有了頭結點,對在第一個元素結點前插入和刪除第一個元素結點,做了統一
- 頭結點不一定是鏈表的必須要素
單鏈表操作
-
插入
-
頭部插入&尾部插入
-
中間插入
-
-
刪除
-
頭結點刪除&尾結點刪除
-
中間刪除
-
/**
* <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 |
/**
* <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 + "]");
}
}
}
優缺點
- 優點
- 刪除修改操作只需要修改遊標
- 缺點
- 列表長度不確定
- 失去了順序存儲結構隨機存取的特性
循環鏈表
定義
- 將單鏈表中終端結點的指針端由空指針改爲指向頭結點,使整個單鏈表形成一個環,這種頭尾相接的單鏈表稱爲單循環鏈表,簡稱循環鏈表。
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();
}
}
雙向鏈表
定義
- 雙向鏈表是單鏈表的每個結點中,再設置一個指向前驅結點的指針域,雙向鏈表的指針域分別指向一個前驅,一個後繼
-
刪除
-
插入
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) |
- 線性表需要頻繁查找用順序儲存
- 線性表需要頻繁插入刪除用單鏈表
- 線性表中元素數量不確定的時候用單鏈表