1.前面用
數組
實現了表結構
,也分析了數組表的侷限性(頭部修改困難)
2.今天來說另外一種數據結構:單鏈表
,它是一種最簡單的動態數據結構
3.鏈表有點像火車,一節拴着一節,想要在某節後加一節,打開鏈接處,再拴上就好了
4.本例操做演示源碼:但願你能夠和我在Github一同見證:DS4Android的誕生與成長,歡迎starnode
單鏈表是對節點(Node)的操做,而節點(Node)又承載數據(T)
總的來看,單鏈表經過操做節點(Node)從而操做數據(T),就像車箱運送獲取,車箱只是載體,貨物是咱們最關注
Node只不過是一個輔助工具,並不會暴露在外。它與數據相對應,又使數據按鏈式排布,
操縱節點也就等於操縱數據,就像提線木偶,並非直接操做木偶的各個肢體自己(數據T)。
爲了統一節點的操做,一般在鏈表最前面添加一個虛擬頭結點(避免對頭部單獨判斷)
複製代碼
注意:單鏈表的實際使用場景並很少,由於有比他更厲害的雙鏈表
在某些特定場景,好比只是頻繁對頭結點進行操做,單鏈表最佳,
單鏈表的講解爲雙鏈表作鋪墊,直接講雙鏈表肯跨度有點大。做爲數據結構之一,仍是要不要了解一下。git
這裏給出實現接口後的SingleLinkedChart以及簡單方法的實現github
/**
* 做者:張風捷特烈<br/>
* 時間:2018/11/22 0022:15:36<br/>
* 郵箱:1981462002@qq.com<br/>
* 說明:單鏈表實現表結構
*/
public class SingleLinkedChart<T> implements IChart<T> {
private Node headNode;//虛擬頭結點
private int size;//元素個數
public SingleLinkedChart() {
headNode = new Node(null, null);
size = 0;
}
@Override
public void add(int index, T el) {
}
@Override//默認添加到頭部
public void add(T el) {
add(0, el);
}
@Override//默認刪除頭部
public T remove(int index) {
return null;
}
@Override
public T remove() {
return remove(0);
}
@Override
public int removeEl(T el) {
return 0;
}
@Override
public boolean removeEls(T el) {
return false;
}
@Override
public void clear() {
headNode = new Node(null, null);
size = 0;
}
@Override
public T set(int index, T el) {
return null;
}
@Override
public T get(int index) {
return null;
}
@Override
public int[] getIndex(T el) {
return null;
}
@Override
public boolean contains(T el) {
return getIndex(el).length != 0;
}
@Override
public IChart<T> contact(IChart<T> iChart) {
return null;
}
@Override
public IChart<T> contact(int index, IChart<T> iChart) {
return null;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public int size() {
return size;
}
@Override
public int capacity() {
return size;
}
複製代碼
SingleLinkedChart至關於一列火車,暫且按下,先把車箱的關係弄好,最後再拼接列車會很是方便
這裏將Node做爲SingleLinkedChart的一個私有內部類,是爲了隱藏Node,並能使用SingleLinkedChart的資源
就像心臟在身體內部,外面人看不見,但它卻相當重要,而且還能獲取體內的信息與資源
一節車箱,最少要知道里面的貨物(node.T)
是什麼,它的下一節車箱(node.next)
是哪一個編程
/**
* 內部私有節點類
*/
private class Node {
/**
* 節點數據元素
*/
private T el;
/**
* 下一節點的引用
*/
private Node next;
private Node(Node next, T el) {
this.el = el;
this.next = next;
}
}
複製代碼
好比你的可視區域就是一節車箱的長度,一開始只能看見火車頭
從火車頭開始,你須要一節一節往下找,也就至關於,列車每次開一節,到你想要的位置,就停下來
這是你就能查看車箱的貨物(get到節點數據),如何用代碼來模擬呢?canvas
/**
* 根據索引尋找節點
*
* @param index 索引
* @return 節點
*/
private Node getNode(int index) {
//聲明目標節點
Node targetNode = headNode;
for (int i = 0; i < index; i++) {//火車運動驅動源
//一節一節走,直到index
targetNode = targetNode.next;
}
return targetNode;
}
複製代碼
能夠檢測一下:
index=0
時,targetNode = targetNode.next
執行1次,也就得到了T0車箱
index=1
時,targetNode = targetNode.next
執行2次,也就得到了T1車箱...數組
仍是想下火車頭:想在2號車箱(target)和1號車箱之間插入一節T4車箱怎麼辦?
第一步:找到2號車箱的前一節車箱--1號廂(target-1)
第二步:將1號廂(target-1)的鏈子(next)栓到T4車箱上,再把T4的鏈子栓到2號車箱(target)bash
/**
* 在指定鏈表前添加節點
*
* @param index 索引
* @param el 數據
*/
private void addNode(int index, T el) {
Node preTarget = getNode(index - 1);//獲取前一節車箱
//新建節點,同時下一節點指向target的下一節點--
//這裏有點繞,分析一下:例子:2號車箱和1號車箱之間插入一節T4車箱
//preTarget:1號車箱 preTarget.next:2號車箱
//T4車箱:new Node(preTarget.next, el)---建立時就把鏈子拴在了:preTarget.next:2號車箱
Node tNode = new Node(preTarget.next, el);
//preTarget的next指向tNode--- 1號車箱栓到T4車箱
preTarget.next = tNode;
size++;
}
複製代碼
你要把車箱的貨物換一下,這還不簡單,找到車箱,換唄微信
/**
* 修改節點
* @param index 節點位置
* @param el 數據
* @return 修改後的節點
*/
private Node<T> setNode(int index, T el) {
Node<T> node = getNode(index);
node.el = el;
return node;
}
複製代碼
要把T1車箱移除:T0和T2手拉手,好朋友,T1被孤立了,把本身的鏈子拿掉,傷心地走開...數據結構
/**
* 移除指定索引的節點
*
* @param index 索引
* @return 刪除的元素
*/
private T removeNode(int index) {
Node preTarget = getNode(index - 1);//獲取前一節車箱
Node target = preTarget.next;//目標車箱
//前一節車箱的next指向目標車箱下一節點
preTarget.next = target.next;//T0和T2手拉手
target.next = null;//T1把本身的鏈子拿掉,傷心地走開...
size--;
return target.el;
}
複製代碼
感受真正的鏈表就是一個包裝殼,暴漏了操做接口給外界,內部勞苦功高的仍是Node
這種封裝在編程裏很是常見,有些聞名遐邇的類中有不少都是默默無聞的大佬ide
可見在選中點的前面添加一個節點
處於單鏈表的特色:頭部添加容易,尾部添加要查詢一遍,因此默認是添加在頭部
@Override
public void add(int index, T el) {
// index能夠取到size,在鏈表末尾空位置添加元素。
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. Illegal index");
}
addNode(index + 1, el);
//爲了接口規範,計數從0開始,而鏈表沒有索引概念,只是第幾個,T0被認爲是第一節車箱。
//好比選中點2---說明是目標是第3節車箱,因此index + 1 =2+1
}
複製代碼
處於單鏈表的特色:頭部刪除容易,尾部刪除要查詢一遍,因此默認是刪除在頭部
@Override
public T remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Remove failed. Illegal index");
}
return removeNode(index + 1);//同理
}
複製代碼
@Override
public T set(int index, T el) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Set failed. Illegal index");
}
return setNode(index + 1, el).el;
}
@Override
public T get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Get failed. Illegal index");
}
return getNode(index + 1).el;
}
複製代碼
@Override
public boolean contains(T el) {
Node target = headNode;
while (target.next != null) {
if (el.equals(target.next)) {
return true;
}
}
return false;
}
複製代碼
@Override
public int[] getIndex(T el) {
int[] tempArray = new int[size];//臨時數組
int index = 0;//重複個數
int count = 0;
Node node = headNode.next;
while (node != null) {
if (el.equals(node.el)) {
tempArray[index] = -1;
count++;
}
index++;
node = node.next;
}
int[] indexArray = new int[count];//將臨時數組壓縮
int indexCount = 0;
for (int i = 0; i < tempArray.length; i++) {
if (tempArray[i] == -1) {
indexArray[indexCount] = i;
indexCount++;
}
}
return indexArray;
}
複製代碼
@Override
public int removeEl(T el) {
int[] indexes = getIndex(el);
int index = -1;
if (indexes.length > 0) {
index = indexes[0];
remove(indexes[0]);
}
return index;
}
@Override
public boolean removeEls(T el) {
int[] indexArray = getIndex(el);
if (indexArray.length != 0) {
for (int i = 0; i < indexArray.length; i++) {
remove(indexArray[i] - i); // 注意-i
}
return true;
}
return false;
}
複製代碼
///////////////只是實現一下,getHeadNode和getLastNode破壞了Node的封裝性,不太好/////////////
@Override
public IChart<T> contact(IChart<T> iChart) {
return contact(0, iChart);
}
@Override
public IChart<T> contact(int index, IChart<T> iChart) {
if (!(iChart instanceof SingleLinkedChart)) {
return null;
}
if (index < 0 || index > size) {
throw new IllegalArgumentException("Contact failed. Illegal index");
}
SingleLinkedChart<T> linked = (SingleLinkedChart<T>) iChart;
Node firstNode = linked.getHeadNode().next;//接入鏈表 頭結點
Node lastNode = linked.getLastNode();//接入鏈表 尾結點
Node target = getNode(index + 1);//獲取目標節點
Node targetNext = target.next;//獲取目標節點的下一節點
target.next = firstNode;//獲取目標節點的next連到 接入鏈表 頭結點
lastNode.next = targetNext; //待接入鏈表 尾結點連到 目標節點的下一節點
return this;
}
public Node getHeadNode() {
return headNode;
}
public Node getLastNode() {
return getNode(size);
}
///////////////////////////////////////////////////////////////
複製代碼
方法\數量 | 1000 | 10000 | 10W | 100W | 1000W |
---|---|---|---|---|---|
add首 | 0.0002秒 | 0.0009秒 | 0.0036秒 | 0.5039秒 | 3.1596秒 |
add尾 | 0.0029秒 | 0.1096秒 | 9.1836秒 | ---- | ---- |
remove首 | 0.0001秒 | 0.0016秒 | 0.0026秒 | 0.0299秒 | 0.1993秒 |
remove尾 | 0.0012秒 | 0.1009秒 | 8.9750秒 | ---- | ---- |
優勢: 動態建立,節省空間
頭部添加容易
缺點: 空間上不連續,形成空間碎片化
查找困難,只能從頭開始一個一個找
使用場景:徹底可用雙鏈表替代,只對前面元素頻繁增刪,單鏈表優點最高。
複製代碼
接口都是相同的,底層實現更換了,並不會影響視圖層,只是把視圖層的單體繪製更改一下就好了。
詳細的繪製方案見這裏
/**
* 繪製表結構
*
* @param canvas
*/
private void dataView(Canvas canvas) {
mPaint.setColor(Color.BLUE);
mPaint.setStyle(Paint.Style.FILL);
mPath.reset();
for (int i = 0; i < mArrayBoxes.size(); i++) {
SingleNode box = mArrayBoxes.get(i);
mPaint.setColor(box.color);
canvas.drawRoundRect(
box.x, box.y, box.x + Cons.BOX_WIDTH, box.y + Cons.BOX_HEIGHT,
BOX_RADIUS, BOX_RADIUS, mPaint);
mPath.moveTo(box.x, box.y);
mPath.rCubicTo(Cons.BOX_WIDTH / 2, Cons.BOX_HEIGHT / 2,
Cons.BOX_WIDTH / 2, Cons.BOX_HEIGHT / 2, Cons.BOX_WIDTH, 0);
if (i < mArrayBoxes.size() - 1) {
SingleNode box_next = mArrayBoxes.get(i + 1);
if (i % 6 == 6 - 1) {//邊界狀況
mPath.rLineTo(0, Cons.BOX_HEIGHT);
mPath.rLineTo(-Cons.BOX_WIDTH / 2, 0);
mPath.lineTo(box_next.x + Cons.BOX_WIDTH / 2f, box_next.y);
mPath.rLineTo(Cons.ARROW_DX, -Cons.ARROW_DX);
} else {
mPath.rLineTo(0, Cons.BOX_HEIGHT / 2f);
mPath.lineTo(box_next.x, box_next.y + Cons.BOX_HEIGHT / 2f);
mPath.rLineTo(-Cons.ARROW_DX, -Cons.ARROW_DX);
}
}
canvas.drawPath(mPath, mPathPaint);
canvas.drawText(box.index + "",
box.x + Cons.BOX_WIDTH / 2,
box.y + 3 * OFFSET_OF_TXT_Y, mTxtPaint);
canvas.drawText(box.data + "",
box.x + Cons.BOX_WIDTH / 2,
box.y + Cons.BOX_HEIGHT / 2 + 3 * OFFSET_OF_TXT_Y, mTxtPaint);
}
}
複製代碼
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1--github | 2018-11-21 | 看得見的數據結構Android版之表的數組實現(數據結構篇) |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人掘金 | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持