原創公衆號:bigsai 文章收藏在 GitHub
經過前面數據結構與算法前導我麼知道了數據結構的一些概念和重要性,那麼咱們今天總結下線性表相關的內容。固然,我用本身的理解解
分享給你們。java
其實說實話,可能不少人依然分不清線性表,順序表,和鏈表
之間的區別和聯繫!node
邏輯結構
, 就是對外暴露數據之間的關係,不關心底層如何實現。物理結構
,他是實現一個結構實際物理地址上的結構。好比順序表就是用數組
實現。而鏈表用指針
完成主要工做。不一樣的結構在不一樣的場景有不一樣的區別。對於java來講,你們都知道List
接口類型,這就是邏輯結構,由於他就是封裝了一個線性關係的一系列方法和數據。而具體的實現其實就是跟物理結構相關的內容。好比順序表的內容存儲使用數組的,而後一個get,set,add方法都要基於數組
來完成,而鏈表是基於指針
的。當咱們考慮對象中的數據關係就要考慮指針
的屬性。指針的指向和value。git
下面用一個圖來淺析線性表的關係。可能有些不太確切,可是其中能夠參考,而且後面也會根據這個圖舉例。github
對於一個線性表
來講。無論
它的具體實現
方法如何,咱們應該有的函數名稱
和實現效果
應該一致。你也能夠感受的到在一些結構的設計。好比List的Arraylist
和LinkedList
。Map的HashMap和currentHashMap他們的接口api都是相同的,可是底層設計實現確定是有區別的。算法
因此,基於面向對象的編程思惟,咱們能夠將線性表寫成一個接口,而具體實現的順序表和鏈表能夠繼承
這個接口的方法,提升程序的可讀性。編程
還有一點比較重要的,記得初學數據結構與算法時候實現的線性表都是固定類型
(int),隨着知識的進步,咱們應當採用泛型
來實現更合理。至於接口的具體設計以下:api
package LinerList; public interface ListInterface<T> { void Init(int initsize);//初始化表 int length(); boolean isEmpty();//是否爲空 int ElemIndex(T t);//找到編號 T getElem(int index) throws Exception;//根據index獲取數據 void add(int index,T t) throws Exception;//根據index插入數據 void delete(int index) throws Exception; void add(T t) throws Exception;//尾部插入 void set(int index,T t) throws Exception; String toString();//轉成String輸出 }
順序表是基於數組實現的,因此一些方法要基於數組的特性。對於順序表應該有的基礎屬性爲一個數組data
和一個length
.數組
還有須要注意的是初始化數組的大小,你能夠固定大小
,可是筆者爲了可用性若是內存不夠將擴大二倍
。固然這樣極可能由於空間使用問題形成很大的浪費
。微信
一些基本的額方法就不說了,下面着重講解一些初學者容易混淆的概念和方法實現。這裏把順序表比做一隊坐在板凳上的人。數據結構
add(int index,T t)
其中index爲插入的編號位置,t爲插入的數據
-根據圖片你就很好理解插入操做。當插入一個index時候,他的後面全部元素都要後移一位。你能夠看的出插入時候整個操做的臃腫性。因此這也是順序表性能表現最差
的地方,頻繁的插入,刪除。
同理,刪除也是很是佔用資源的。原理和插入相似,不過人走了,空一個小板凳
後面的人須要往前挪
。
其餘操做就很簡單了。好比若是按照編號獲取數據getElem(int index)
,你能夠直接根據數據座標返回。a[index],而其餘操做,能夠經過遍歷直接操做數組
便可。
我想,表應該是不少人感受很繞的東西,這個很大緣由可能由於指針
。不少人說java
沒指針,其實java他也有隱形指針。只不過不能直接用罷了。
指針創建的數據關係每每比數組這些要抽象的多。對於指針域,你把他當成一個對象就行了,不過這個對象指向的是另外一個同等級對象
。對於這個關係,你能夠比做每一個person類。每一個person類都有老公(老婆)
,而這個老公老婆也是一個實際對象,能夠理解這更像一種邏輯約定關係
,而不是硬生生的關係吧。
指針你能夠考慮成腦子記憶
。上面的順序表咱們說它有序由於每一個小板凳(數組)有編號
,咱們能夠根據這個來肯定位置。而對於鏈表來講,你能夠看做成一個站在操場上的一隊人。而他的操做也略有不一樣,下面針對一些比較特殊和重要的進行概括。
對於線性表,咱們只須要一個data數組和length就能表示基本信息。而對於鏈表,咱們須要一個node(head頭節點)
,和length
,固然,這個node也是一個結構體。
class node<T>{ T data;//節點的結果 node next;//下一個鏈接的節點 public node(){} public node(T data) { this.data=data; } public node(T data, node next) { this.data = data; this.next = next; } }
固然,這個節點有數據域
和指針域
。數據域就是存放真實的數據,而指針域就是存放下一個node的指針。因此相比順序表,若是用滿數組狀況下,鏈表佔用更多的資源,由於它要存放指針佔用資源。
add(int index,T t)
其中index爲插入的編號位置,t爲插入的數據
加入插入一個節點node
,根據index找到插入的前一個節點叫pre
。那麼操做流程爲
node.next=pre.next
以下1的操做,將插入節點後面聯繫起來。此時node.next和pre.next一致。pre.next=node
由於咱們要插入node,而node鏈能夠替代pre自身的next。那麼直接將pre指向node。那麼就至關於原始鏈表插入了一個node。
不少人搞不清什麼是帶頭節點
和不帶頭節點
。帶頭節點就是head節點不放數據,第0項從head後面那個開始數。而不帶頭節點的鏈表head放數據,head節點就是第0位
。
主要區別:
可看做一列火車的火車頭
)。而方便的就是帶頭節點在首位插入更簡單。由於插入第0位
也是在head的後面
。node纔是
插入後最長鏈的首位節點
,head在他的後面,而在鏈表中head一般表示首位節點
,因此head不表示第二個節點,直接"="node節點
。這樣head和node都表示操做完成的鏈表。可是對外暴露的只有head。因此head只能指向第一個節點!)next
爲null
。不能和插入普通位置相比!
按照index移除:delete(int index)
按照尾部移除(拓展):deleteEnd()
這個方法我沒有寫,可是我給你們講一下,按照尾部刪除的思想就是:
node.next!=null
時node=node.next
指向下一個node.next==null
時候。說明這個節點時最後一個。你能夠node=null
。這個這個node的前驅pre的next就是null。這個節點就被刪除了。頭部刪除(帶頭節點):
head.next(第1個元素)
=head.next.next(第二個元素)
頭部刪除(不帶頭節點)
第一個
就是存貨真價實的元素
的。不帶頭節點刪除也很簡單。直接將head移到第二位
就好了。即:head=head.next
從head開始
。而後另外一個節點team=head
或head.next
。而後用這個節點不停的等於它指向的next去查找咱們須要的內容即while(循環條件){team=team.next}相似。if else
.package LinerList; public class seqlist<T> implements ListInterface<T> { private Object[] date;//數組存放數據 private int lenth; public seqlist() {//初始大小默認爲10 Init(10); } public void Init(int initsize) {//初始化 this.date=new Object[initsize]; lenth=0; } public int length() { return this.lenth; } public boolean isEmpty() {//是否爲空 if(this.lenth==0) return true; return false; } /* * * @param t * 返回相等結果,爲-1爲false */ public int ElemIndex(T t) { // TODO Auto-generated method stub for(int i=0;i<date.length;i++) { if(date[i].equals(t)) { return i; } } return -1; } /* *得到第幾個元素 */ public T getElem(int index) throws Exception { // TODO Auto-generated method stub if(index<0||index>lenth-1) throw new Exception("數值越界"); return (T) date[index]; } public void add(T t) throws Exception {//尾部插入 add(lenth,t); } /* *根據編號插入 */ public void add(int index, T t) throws Exception { if(index<0||index>lenth) throw new Exception("數值越界"); if (lenth==date.length)//擴容 { Object newdate[]= new Object[lenth*2]; for(int i=0;i<lenth;i++) { newdate[i]=date[i]; } date=newdate; } for(int i=lenth-1;i>=index;i--)//後面元素後移動 { date[i+1]=date[i]; } date[index]=t;//插入元素 lenth++;//順序表長度+1 } public void delete(int index) throws Exception { if(index<0||index>lenth-1) throw new Exception("數值越界"); for(int i=index;i<lenth;i++)//index以後元素前移動 { date[i]=date[i+1]; } lenth--;//長度-1 } @Override public void set(int index, T t) throws Exception { if(index<0||index>lenth-1) throw new Exception("數值越界"); date[index]=t; } public String toString() { String vaString=""; for(int i=0;i<lenth;i++) { vaString+=date[i].toString()+" "; } return vaString; } }
package LinerList; class node<T>{ T data;//節點的結果 node next;//下一個鏈接的節點 public node(){} public node(T data) { this.data=data; } public node(T data, node next) { this.data = data; this.next = next; } } public class Linkedlist<T> implements ListInterface<T>{ node head; private int length; public Linkedlist() { head=new node(); length=0; } public void Init(int initsize) { head.next=null; } public int length() { return this.length; } public boolean isEmpty() { if(length==0)return true; else return false; } /* * 獲取元素編號 */ public int ElemIndex(T t) { node team=head.next; int index=0; while(team.next!=null) { if(team.data.equals(t)) { return index; } index++; team=team.next; } return -1;//若是找不到 } @Override public T getElem(int index) throws Exception { node team=head.next; if(index<0||index>length-1) { throw new Exception("數值越界"); } for(int i=0;i<index;i++) { team=team.next; } return (T) team.data; } public void add(T t) throws Exception { add(length,t); } //帶頭節點的插入,第一個和最後一個同樣操做 public void add(int index, T value) throws Exception { if(index<0||index>length) { throw new Exception("數值越界"); } node<T> team=head;//team 找到當前位置node for(int i=0;i<index;i++) { team=team.next; } node<T>node =new node(value);//新建一個node node.next=team.next;//指向index前位置的下一個指針 team.next=node;//本身變成index位置 length++; } @Override public void delete(int index) throws Exception { if(index<0||index>length-1) { throw new Exception("數值越界"); } node<T> team=head;//team 找到當前位置node for(int i=0;i<index;i++)//標記team 前一個節點 { team=team.next; } //team.next節點就是咱們要刪除的節點 team.next=team.next.next; length--; } @Override public void set(int index, T t) throws Exception { // TODO Auto-generated method stub if(index<0||index>length-1) { throw new Exception("數值越界"); } node<T> team=head;//team 找到當前位置node for(int i=0;i<index;i++) { team=team.next; } team.data=t;//將數值賦值,其餘不變 } public String toString() { String va=""; node team=head.next; while(team!=null) { va+=team.data+" "; team=team.next; } return va; } }
package LinerList; public class test { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub System.out.println("線性表測試:"); ListInterface<Integer>list=new seqlist<Integer>(); list.add(5); list.add(6); list.add(1,8); list.add(3,996); list.add(7); System.out.println(list.ElemIndex(8)); System.out.println(list.toString()); list.set(2, 222); System.out.println(list.toString()); list.delete(4); System.out.println(list.toString()); System.out.println(list.length()); System.out.println("鏈表測試:"); list=new Linkedlist<Integer>(); list.add(5); list.add(6); list.add(1,8); list.add(3,996); list.add(7); System.out.println(list.ElemIndex(8)); System.out.println(list.toString()); list.set(2, 222); System.out.println(list.toString()); list.delete(4); System.out.println(list.toString()); System.out.println(list.length()); } }
輸出:
線性表測試:
1
5 8 6 996 7
5 8 222 996 7
5 8 222 996
4
鏈表測試:
1
5 8 6 996 7
5 222 6 996 7
5 222 6 996
4
這裏的只是簡單實現,實現基本方法。鏈表也只是單鏈表。完善程度還能夠優化。若是有錯誤還請大佬指正。
單鏈表查詢速度較慢
,由於他須要從頭遍歷。若是頻繁操做尾部,能夠考慮鏈表中不只有head在加尾tail
節點。而順序表查詢速度雖然快可是插入很費時費力。實際應用根據需求選擇!
java中的Arraylist和LinkedList就是兩種方式的表明,不過LinkedList使用雙向鏈表優化,而且jdk的api作了大量優化。因此你們不用造輪子
,能夠直接用,可是仍是頗有學習價值
的。
若是有不理解或者不懂的能夠聯繫交流討論。
原創不易,bigsai請朋友們幫兩件事幫忙一下:
我們下次再見!