陣列與連結串列
前言
陣列和連結串列是兩種資料結構,陣列非常簡單易用但是它有兩個非常大的缺點,一個是陣列一旦建立無法擴充套件,另一個則是陣列的查詢和刪除的速度很慢.
連結串列改善了一些陣列的缺點,但是同樣的連結串列自身也存在一些自己的缺點.
本篇部落格將為大家介紹一下這陣列和連結串列特點及各自的優缺點.
閱讀前的準備工作
大O表示法,一種粗略的評價計算機演算法效率的方法.後面的內容會用到表示效率的方法.
1. 陣列
我們按陣列中的陣列是否排序對陣列進行劃分,將陣列分為無序陣列和有序陣列.無序陣列中的陣列是無序的,而有序陣列中的資料則是升序或者降序排序的.
1.1 無序陣列
因為無序陣列中的資料是無序的,往陣列中新增資料時不用進行比較和移動資料,所以往無序數組裡面新增資料很快.無論是新增第一個資料還是第一萬個資料所需的時間是相同的,效率為O(1).
至於查詢和刪除速度就沒有那麼快了,以陣列中有一萬個資料項為例,最少需要比較1次,最多則需要比較一萬次,平均下來需要比較5000次,即N/2次比較,N代表資料量,大O表示法中常數可以忽略,所以效率為O(N).
結論:
- 插入很快,因為總是將資料插入到陣列的空餘位置.
- 查詢和刪除很慢,假設陣列的長度為N,那麼平均的查詢/刪除的比較次數為N/2,並且還需要移動資料.
1.2 有序陣列
無序陣列中存放的資料是無序的,有序數組裡面存放的資料則是有序的(有可能是升序有可能是降序).
因為有序陣列中的資料是按升序/降序排列的,所以插入的時候需要進行排序並且移動資料項,所有有序陣列的插入速度比無序陣列慢. 效率為O(N).
刪除速度和無序陣列一樣慢 效率為O(N).
有序陣列的查詢速度要比無序陣列快,這是因為使用了一個叫做二分查詢的演算法.
二分查詢: 二分查詢也稱折半查詢(Binary Search),它是一種效率較高的查詢方法。但是,折半查詢要求線性表必須採用順序儲存結構,而且表中元素按關鍵字有序排列.
有一個關於二分查詢的形象類比 -> 猜數遊戲
假設要在0-100之間猜一個數,那麼你第一個要猜的數字就是100的一半50的時候,你的朋友會告訴你這個數字比要猜的數字是大還是小,如果比數字大,你接下來要猜的數字就是50的一半25,你的朋友說比這個數字要大,那麼你下面要猜的數字就是25-50中間的那個數37,以此類推...
使用二分查詢可極大的提高查詢的效率,假設一個有序陣列有十億個資料,那麼查詢到所需的數字,最多隻需比較30次.
有序陣列使用二分查詢的效率為O(logN).有序陣列也可以通過二分查詢來新增和刪除資料以提高效率,但是依然需要在新增/刪除後移動資料項,所以效率依然會有影響.
總結:
- 有序陣列的查詢速度比無序陣列高,效率為O(logN)
- 有序陣列的刪除和新增速度很慢,效率為O(N)
1.3 陣列總結
陣列雖然簡單易用,但是陣列有兩個致命的缺點:
- 陣列儲存的數量有限,建立的過大浪費資源,建立的過小溢位
- 陣列的效率比其他資料結構低
- 無序陣列插入效率為O(1)時間,但是查詢花費O(N)時間
- 有序陣列查詢花費O(logN)時間,插入花費O(N)時間
- 刪除需要移動平均半數的資料項,所以刪除都是O(N)的時間
2. 連結串列
陣列一經建立大小就固定住了,無法修改,連結串列在這方面做出了改善,只要記憶體夠用就可以無限制的擴大.
連結串列是繼陣列之後應用最廣泛的資料結構.
2.1 連結串列的特點
連結串列為什麼叫連結串列呢? 因為它儲存資料的方式就像一條鎖鏈
連結串列儲存資料的方式很像上面的這一條鎖鏈,每一塊鎖鏈就是一個鏈節點,鏈節點儲存著自己的資料同時通過自己的next()方法指向下一個鏈節點. 連結串列通過鏈節點不斷地呼叫next()方法就可以遍歷連結串列中的所有資料.
在連結串列中,每個資料項都被包含在"鏈節點"(link)中,一個鏈結點是某個類的物件,這個類可以叫做Link.因為一個連結串列中有許多類似的鏈結點,所以有必要用一個不同於連結串列的類來表達鏈結點.
每個Link物件中都包含一個對下一個鏈結點引用的欄位(通常叫做next).
連結串列本身的物件中有一個欄位指向對第一個鏈結點的引用.
資料與連結串列查詢資料的區別: 在陣列中查詢資料就像在一個大倉庫裡面一樣,一號房間沒有,我們去二號房間,二號房間沒有我們去三號房間,以此類推.. 按照地址找完所有房間就可以了.
而在連結串列中查詢資料就像單線彙報的地下工作者,你是孤狼你想要彙報點情報給你的頂級上司毒蜂,但是你必須先報告給你的接頭人豬剛鬣,豬剛鬣在報告給它的單線接頭人土行孫,最後由土行孫報告給毒蜂.只能一個找一個,這樣最終完成任務.
2.2 Java程式碼
鏈節點類:
/**
* @author liuboren
* @Title: 鏈節點
* @Description:
* @date 2019/11/20 19:30
*/
public class Link {
// 儲存的資料
public int data;
// 指向的下一個鏈節點
public Link nextLink;
public Link(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Link getNextLink() {
return nextLink;
}
public void setNextLink(Link nextLink) {
this.nextLink = nextLink;
}
}
連結串列類
/**
* @author liuboren
* @Title: 連結串列類
* @Description:
* @date 2019/11/20 19:31
*/
public class LinkList {
private Link first;
public LinkList() {
first = null;
}
// 新增鏈節點方法
public void insertFirst(int data) {
Link link = new Link(data);
link.setNextLink(first);
first = link;
}
}
在新增節點的時候,新增的link的next方法指向原來的first節點,並將連結串列類的first指向新增的節點.
2.4 其他連結串列
剛剛介紹的連結串列是單向連結串列,只能從後往前遍歷,其他的連結串列還有雙端連結串列、雙向連結串列、有序連結串列.
再簡單介紹一下雙端連結串列吧.
雙端連結串列就是在單向連結串列的基礎上,新增一個成員變數指向連結串列的最後一個物件.
雙端連結串列程式碼:
/**
* @author liuboren
* @Title: 連結串列類
* @Description:
* @date 2019/11/20 19:31
*/
public class LinkList {
private Link first;
private Link last;
public LinkList() {
first = null;
}
public boolean isEmpty() {
return first == null;
}
// 新增鏈節點方法
public void insertFirst(int data) {
Link newLink = new Link(data);
newLink.setNextLink(first);
if (isEmpty()) {
last = newLink;
}
first = newLink;
}
}
雙向連結串列則是可以從first和last兩個方向進行遍歷,有序連結串列的資料都是按照關鍵字的順序排列的,本文不再展開了.
2.5 連結串列的效率
連結串列的效率:
- 在表頭插入和刪除速度都很快,花費O(1)的時間.
- 平均起來,查詢&刪除&插入在制定鏈節點後面都需要搜尋一半的鏈節點需要O(N)次比較,雖然陣列也需要O(N)次比較,但是連結串列讓然要快一些,因為不需要移動資料(只需要改變他們的引用)
3. 總結
連結串列解決了陣列大小不能擴充套件的問題,但是連結串列自身依然存在一些問題(在連結串列的鏈節點後面查詢&刪除&插入的效率不高),那麼有沒有一種資料結構即擁有二者的優點又改善了二者的缺點呢,答案是肯定的,下篇部落格將為您介紹這種優秀的資料結構,敬請期