首語
- 之前立了個flag,LeetCode每天刷一道算法題,但隨着算法題的深入,發現自己對數據結構的知識有些模糊。所以開始對數據結構的知識進行學習,記錄的均是java版的數據結構和算法。2020年的第一篇博客從數據結構開始,第一節線性表(List)。首先是數據結構的相關概念。
數據結構
- 數據之間相互存在的一種或多種特定的關係的元素的集合!
邏輯結構
- 數據對象中數據元素之間的相互關係!
- 1.集合結構
- 2.線性結構
- 3.樹形結構
- 4.圖形結構
物理結構
- 1.順序存儲結構
- 2.鏈式存儲結構
數據類型
- 一組性質相同的值的集合及定義在此集合上的一些操作的總稱!
抽象數據類型
- 一個數字模型及定義在該模型上的一組操作!
- 接下來進入線性表的學習
線性表(List)
- a1是a2的前驅,ai+1是ai的後繼,a1沒有前驅,an沒有後繼。
- n爲線性表的長度,若n=0時,線性表爲空表。
順序存儲方式線性表
- 存儲位置連續,可以很方便計算各個元素的地址。如每個元素佔C個存儲單元,那麼有:
Loc(An)=Loc(An-1)+C,於是有:
Loc(An)=Loc(A1)+(i1)*C;
鏈式存儲方式線性表
- 線性表的鏈式存儲結構的特點是用一組任意的存儲單元存儲線性表的數據元素,這組存儲單元可以是連續的,也可以是不連續的。
- 優點:插入和刪除效率高。
//插入
s->next=p->next
p->next=s;
//刪除
p->next=p->next->next;
- 缺點:查詢效率低。
ArrayList源碼
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
循環鏈表
- 將單鏈表中終端結點的指針端由空指針改爲指向頭結點,就使整個單鏈表形成一個環,這種頭尾相連的單鏈表稱爲單循環鏈表,簡稱循環鏈表。
雙向循環鏈表
- 對於空的指針域
- 單向循環列表的每個結點中,再設置一個指向其前驅結點的指針域。
//雙向循環鏈表的插入
s->prior=p;//把p賦值給s的前驅,如圖1
s>next=p->next;//把p->next賦值給s的後繼,如圖2
p->next->prior=s;//把s賦值給p->next的前驅,如圖3
p->nexts;//把s賦值給p的後繼,如圖4
//雙向循環鏈表的刪除
p->prior->next=p->next;//把p->next賦值給p->prior的後繼,如圖1
p->next->prior=p->prior;//把p->prior賦值給p->next的前驅,如圖2
LinkedList源碼
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
/**
* Removes the element at the specified position in this list. Shifts any
* subsequent elements to the left (subtracts one from their indices).
* Returns the element that was removed from the list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
/**
* Unlinks non-null node x.
*/
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
ArrayList與LinkedList
區別
1.ArrayList是實現了基於動態數組的數據結構,LinkedList是基於鏈表結構。
2.對於隨機訪問的get和set方法,ArrayList要優於LinkedList,因爲LinkedList要移動指針。
3.對於新增和刪除操作add和remove,LinkedList比較佔優勢,因爲ArrayList要移動數據。
缺點
1.對ArrayList和LinkedList而言,在列表末尾增加一個元素所花的開銷都是固定的。
對ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是 統一的,分配一個內部Entry對象。
2.在ArrayList集合中添加或者刪除一個元素時,當前的列表所所有的元素都會被移動。而LinkedList集合中添加或者刪除一個元素的開銷是固定的。
3.LinkedList集合不支持 高效的隨機隨機訪問(RandomAccess),因爲可能產生二次項的行爲。
4.ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間
應用場景
- ArrayList使用在查詢比較多,但是插入和刪除比較少的情況,而LinkedList用在查詢比較少而插入刪除比較多的情況。
LeetCode題例
/**
* author : yhj
* date : 2019/12/16
* desc :將兩個有序鏈表合併爲一個新的有序鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。
* 示例:
* 輸入:1->2->4, 1->3->4
* 輸出:1->1->2->3->4->4
*/
public static class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
/**
* author : yhj
* date : 2020/1/6
* desc :給定一個排序鏈表,刪除所有重複的元素,使得每個元素只出現一次。
* 示例 1:
* 輸入: 1->1->2
* 輸出: 1->2
* 示例 2:
* 輸入: 1->1->2->3->3
* 輸出: 1->2->3
*/
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public ListNode deleteDuplicates(ListNode head) {
ListNode current = head;
while (current != null && current.next != null) {
if (current.next.val == current.val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return head;
}