線性表分析及Java實現
資料結構中的線性表,對應著Collection中的List介面。
在本節中,我們將做以下三件事
第一。我們先來看看線性表的特徵
第二,自己用JAVA實現List
第三,對比的線性表、鏈式表效能,以及自己的List效能與JDKList效能對比
線性表特徵:
第一,一個特定的線性表,應該是用來存放特定的某一個型別的元素的(元素的“同一性”)
第二, 除第一個元素外,其他每一個元素有且僅有一個直接前驅;除最後一個元素外,其他每一個元素有且僅有一個 直接後繼(元素的“序偶性”)
第二, 元素線上性表中的“下標”唯一地確定該元素在表中的相對位置(元素的“索引性”)
又,一.線性表只是資料的一種邏輯結構,其具體儲存結構可以為順序儲存結構和鏈式儲存結構來完成,對應可以得到順序表和連結串列,
二.對線性表的入表和出表順序做一定的限定,可以得到特殊的線性表,棧(FILO)和佇列(FIFO)
自己實現線性表之順序表
思路:
1. 順序表因為採用順序儲存形式,所以內部使用陣列來儲存資料
2.因為儲存的具體物件型別不一定,所以採用泛型操作
3.陣列操作優點:1.通過指標快速定位到下表,查詢快速
缺點:1.陣列宣告時即需要確定陣列大小。當操作中超過容量時,則需要重新宣告陣列,並且複製當前所有資料
2.當需要在中間進行插入或者刪除時,則需要移動大量元素(size-index個)
具體實現程式碼如下
- /**
- * 自己用陣列實現的線性表
- */
- public class ArrayList<E> {
- Object[] data = null;// 用來儲存此佇列中內容的陣列
- int current;// 儲存當前為第幾個元素的指標
- int capacity;// 表示陣列大小的指標
- /**
- * 如果初始化時,未宣告大小,則預設為10
- */
- public ArrayList() {
- this(10);
- }
- /**
- * 初始化線性表,並且宣告儲存內容的陣列大小
- * @param initalSize
- */
- public ArrayList(int initalSize) {
- if (initalSize < 0) {
- throw new RuntimeException("陣列大小錯誤:" + initalSize);
- } else {
- this.data = new Object[initalSize];
- this.current = 0;
- capacity = initalSize;
- }
- }
- /**
- * 新增元素的方法 新增前,先確認是否已經滿了
- * @param e
- * @return
- */
- public boolean add(E e) {
- ensureCapacity(current);// 確認容量
- this.data[current] = e;
- current++;
- return true;
- }
- /**
- * 確認系統當前容量是否滿足需要,如果滿足,則不執行操作 如果不滿足,增加容量
- * @param cur 當前個數
- */
- private void ensureCapacity(int cur) {
- if (cur == capacity) {
- // 如果達到容量極限,增加10的容量,複製當前陣列
- this.capacity = this.capacity + 10;
- Object[] newdata = new Object[capacity];
- for (int i = 0; i < cur; i++) {
- newdata[i] = this.data[i];
- }
- this.data = newdata;
- }
- }
- /**
- * 得到指定下標的資料
- * @param index
- * @return
- */
- public E get(int index) {
- validateIndex(index);
- return (E) this.data[index];
- }
- /**
- * 返回當前佇列大小
- * @return
- */
- public int size() {
- return this.current;
- }
- /**
- * 更改指定下標元素的資料為e
- * @param index
- * @param e
- * @return
- */
- public boolean set(int index, E e) {
- validateIndex(index);
- this.data[index] = e;
- return true;
- }
- /**
- * 驗證當前下標是否合法,如果不合法,丟擲執行時異常
- * @param index 下標
- */
- private void validateIndex(int index) {
- if (index < 0 || index > current) {
- throw new RuntimeException("陣列index錯誤:" + index);
- }
- }
- /**
- * 在指定下標位置處插入資料e
- * @param index 下標
- * @param e 需要插入的資料
- * @return
- */
- public boolean insert(int index, E e) {
- validateIndex(index);
- Object[] tem = new Object[capacity];// 用一個臨時陣列作為備份
- //開始備份陣列
- for (int i = 0; i < current; i++) {
- if (i < index) {
- tem[i] = this.data[i];
- }else if(i==index){
- tem[i]=e;
- }else if(i>index){
- tem[i]=this.data[i-1];
- }
- }
- this.data=tem;
- return true;
- }
- <br><br>/**<br> * 刪除指定下標出的資料<br> * @param index<br> * @return<br> */<br> public boolean delete(int index){<br> validateIndex(index);<br> Object[] tem = new Object[capacity];// 用一個臨時陣列作為備份<br> //開始備份陣列<br> for (int i = 0; i < current; i++) {<br> if (i < index) {<br> tem[i] = this.data[i];<br> }else if(i==index){<br> tem[i]=this.data[i+1];<br> }else if(i>index){<br> tem[i]=this.data[i+1];<br> }<br> }<br> this.data=tem;<br> return true;<br> }<br><br>}
自己實現線性表之連結串列
思路:1.連結串列採用鏈式儲存結構,在內部只需要將一個一個結點連結起來。(每個結點中有關於此結點下一個結點的引用)
連結串列操作優點:1.,因為每個結點記錄下個結點的引用,則在進行插入和刪除操作時,只需要改變對應下標下結點的引用即可
缺點:1.要得到某個下標的資料,不能通過下標直接得到,需要遍歷整個連結串列。
實現程式碼如下
- /**
- * 自己用鏈式儲存實現的線性表
- */
- public class LinkedList<E> {
- private Node<E> header = null;// 頭結點
- int size = 0;// 表示陣列大小的指標
- public LinkedList() {
- this.header = new Node<E>();
- }
- public boolean add(E e) {
- if (size == 0) {
- header.e = e;
- } else {
- // 根據需要新增的內容,封裝為結點
- Node<E> newNode = new Node<E>(e);
- // 得到當前最後一個結點
- Node<E> last = getNode(size-1);
- // 在最後一個結點後加上新結點
- last.addNext(newNode);
- }
- size++;// 當前大小自增加1
- return true;
- }
- public boolean insert(int index, E e) {
- Node<E> newNode = new Node<E>(e);
- // 得到第N個結點
- Node<E> cNode = getNode(index);
- newNode.next = cNode.next;
- cNode.next = newNode;
- size++;
- return true;
- }
- /**
- * 遍歷當前連結串列,取得當前索引對應的元素
- *
- * @return
- */
- private Node<E> getNode(int index) {
- // 先判斷索引正確性
- if (index > size || index < 0) {
- throw new RuntimeException("索引值有錯:" + index);
- }
- Node<E> tem = new Node<E>();
- tem = header;
- int count = 0;
- while (count != index) {
- tem = tem.next;
- count++;
- }
- return tem;
- }
- /**
- * 根據索引,取得該索引下的資料
- *
- * @param index
- * @return
- */
- public E get(int index) {
- // 先判斷索引正確性
- if (index >= size || index < 0) {
- throw new RuntimeException("索引值有錯:" + index);
- }
- Node<E> tem = new Node<E>();
- tem = header;
- int count = 0;
- while (count != index) {
- tem = tem.next;
- count++;
- }
- E e = tem.e;
- return e;
- }
- public int size() {
- return size;
- }
- /**
- * 設定第N個結點的值
- *
- * @param x
- * @param e
- * @return
- */
- public boolean set(int index, E e) {
- // 先判斷索引正確性
- if (index > size || index < 0) {
- throw new RuntimeException("索引值有錯:" + index);
- }
- Node<E> newNode = new Node<E>(e);
- // 得到第x個結點
- Node<E> cNode = getNode(index);
- cNode.e = e;
- return true;
- }
- /**
- * 用來存放資料的結點型內部類
- */
- class Node<e> {
- private E e;// 結點中存放的資料
- Node() {
- }
- Node(E e) {
- this.e = e;
- }
- Node<E> next;// 用來指向該結點的下一個結點
- // 在此結點後加一個結點
- void addNext(Node<E> node) {
- next = node;
- }
- }
- }
自己實現線性表之棧
棧是限定僅允許在表的同一端(通常為“表尾”)進行插入或刪除操作的線性表。
允許插入和刪除的一端稱為棧頂(top),另一端稱為棧底(base)
特點:後進先出 (LIFO)或,先進後出(FILO)
因為棧是限定線的線性表,所以,我們可以呼叫前面兩種線性表,只需要對出棧和入棧操作進行設定即可
具體實現程式碼
- /**
- * 自己用陣列實現的棧
- */
- public class ArrayStack<E> {
- private ArrayList<E> list=new ArrayList<E>();//用來儲存資料線性表<br> private int size;//表示當前棧元素個數
- /**
- * 入棧操作
- * @param e
- */
- public void push(E e){
- list.add(e);
- size++;
- }
- /**
- * 出棧操作
- * @return
- */
- public E pop(){
- E e= list.get(size-1);
- size--;
- return e;
- }
- }
至於用連結串列實現棧,則只需要把儲存資料的順序表改成連結串列即可,此處就不給出程式碼了
自己實現線性表之佇列
與棧類似
佇列是隻允許在表的一端進行插入,而在另一端刪除元素的線性表。
在佇列中,允許插入的一端叫隊尾(rear),允許刪除的一端稱為隊頭(front)。
特點:先進先出 (FIFO)、後進後出 (LILO)
同理,我們也可以呼叫前面兩種線性表,只需要對佇列的入隊和出隊方式進行處理即可
- package cn.javamzd.collection.List;
- /**
- * 用陣列實現的佇列
- */
- public class ArrayQueue<E> {
- private ArrayList<E> list = new ArrayList<E>();// 用來儲存資料的佇列
- private int size;// 表示當前棧元素個數
- /**
- * 入隊
- * @param e
- */
- public void EnQueue(E e) {
- list.add(e);
- size++;
- }
- /**
- * 出隊
- * @return
- */
- public E DeQueue() {
- if (size > 0) {
- E e = list.get(0);
- list.delete(0);
- return e;
- }else{
- throw new RuntimeException("已經到達佇列頂部");
- }
- }
- }
對比線性表和鏈式表
前面已經說過順序表和鏈式表各自的特點,這裡在重申一遍
陣列操作優點:1.通過指標快速定位到下標,查詢快速
缺點:1.陣列宣告時即需要確定陣列大小。當操作中超過容量時,則需要重新宣告陣列,並且複製當前所有資料
2.當需要在中間進行插入或者刪除時,則需要移動大量元素(size-index個)
連結串列操作優點:1.,因為每個結點記錄下個結點的引用,則在進行插入和刪除操作時,只需要改變對應下標下結點的引用即可
缺點:1.要得到某個下標的資料,不能通過下標直接得到,需要遍歷整個連結串列。
現在,我們通過進行增刪改查操作來感受一次其效率的差異
思路:通過兩個表,各進行大資料量操作(2W)條資料的操作,記錄操作前系統時間,操作後系統時間,得出操作時間
實現程式碼如下
- package cn.javamzd.collection.List;
- public class Test {
- /**
- * @param args
- */
- public static void main(String[] args) {
- //測試自己實現的ArrayList類和Linkedlist類新增20000個資料所需要的時間
- ArrayList<String> al = new ArrayList<String>();
- LinkedList<String> ll = new LinkedList<String>();
- Long aBeginTime=System.currentTimeMillis();//記錄BeginTime
- for(int i=0;i<30000;i++){
- al.add("now"+i);
- }
- Long aEndTime=System.currentTimeMillis();//記錄EndTime
- System.out.println("arrylist add time--->"+(aEndTime-aBeginTime));
- Long lBeginTime=System.currentTimeMillis();//記錄BeginTime
- for(int i=0;i<30000;i++){
- ll.add("now"+i);
- }
- Long lEndTime=System.currentTimeMillis();//記錄EndTime
- System.out.println("linkedList add time---->"+(lEndTime-lBeginTime));
- //測試JDK提供的ArrayList類和LinkedList類新增20000個資料所需要的世界
- java.util.ArrayList<String> sal=new java.util.ArrayList<String>();
- java.util.LinkedList<String> sll=new java.util.LinkedList<String>();
- Long saBeginTime=System.currentTimeMillis();//記錄BeginTime
- for(int i=0;i<30000;i++){
- sal.add("now"+i);
- }
- Long saEndTime=System.currentTimeMillis();//記錄EndTime
- System.out.println("JDK arrylist add time--->"+(saEndTime-saBeginTime));
- Long slBeginTime=System.currentTimeMillis();//記錄BeginTime
- for(int i=0;i<30000;i++){
- sll.add("now"+i);
- }
- Long slEndTime=System.currentTimeMillis();//記錄EndTime
- System.out.println("JDK linkedList add time---->"+(slEndTime-slBeginTime));
- }
- }
得到測試結果如下:
arrylist add time--->446 linkedList add time---->9767 JDK arrylist add time--->13 JDK linkedList add time---->12 |
由以上資料,我們可知:
1.JDK中的ArrayList何LinkedList在新增資料時的效能,其實幾乎是沒有差異的
2.我們自己寫的List的效能和JDK提供的List的效能還是存在巨大差異的
3.我們使用連結串列新增操作,花費的時間是巨大的,比ArrayList都大幾十倍
第三條顯然是跟我們最初的設計不相符的,按照我們最初的設想,連結串列的新增應該比順序表更省時
檢視我們寫的原始碼,可以發現:
我們每次新增一個資料時,都需要遍歷整個表,得到表尾,再在表尾新增,這是很不科學的
現改進如下:設立一個Node<E>類的成員變數end來指示表尾,這樣每次新增時,就不需要再重新遍歷得到表尾
改進後add()方法如下
- public boolean add(E e) {
- if (size == 0) {
- header.e = e;
- } else {
- // 根據需要新增的內容,封裝為結點
- Node<E> newNode = new Node<E>(e);
- //在表尾新增元素
- last.addNext(newNode);
- //將表尾指向當前最後一個元素
- last = newNode;
- }
- size++;// 當前大小自增加1
- return true;
- }
ArrayList新增的效率和JDK中對比起來也太低
分析原因為:
每次擴大容量時,擴大量太小,需要進行的複製操作太多
現在改進如下:
每次擴大,則擴大容量為當前的三倍,此改進僅需要更改ensureCapacity()方法中的一行程式碼,此處就不列出了。
改進後,再次執行新增元素測試程式碼,結果如下:
arrylist add time--->16 linkedList add time---->8 JDK arrylist add time--->7 JDK linkedList add time---->7 |
雖然還有改進的空間,但是顯然,我們的效果已經大幅度改進了,而且也比較接近JDK了
接下來測試插入操作的效率
我們只需要將測試程式碼中的新增方法(add())改成插入方法(insert(int index,E e)),為了使插入次數儘可能多,我們把index都設定為0
測試結果如下:
arrylist inset time--->17 linkedList inset time---->13 JDK arrylist inset time--->503 JDK linkedList inset time---->11 |
多次測試,發現我們寫的ArrayList在插入方法的效率都已經超過JDK了,而且也接近LinkedLst了。撒花!!!
接下來測試刪除、得到下標等等操作就不一一列出來了(只需要改變每次呼叫的方法即可)
恩,本來想今晚把所有的集合框架實現都寫一下的
但是不知不覺這都又2點了
明早還得去藍傑上課
果斷先睡吧
敬請大家期待我明日大作------------靜態/動態查詢表的實現,動態查詢表查詢/加入演算法的JAVA實現,Hash表的實現
good night
相關文章
- 線性表及其演算法(java實現)演算法Java
- 線性表及其實現
- Java中各種線性表的效能分析Java
- 線性表1.0(陣列實現)陣列
- 線性表-順序表C語言實現C語言
- 線性表的使用——順序實現
- go語言使用切片實現線性表Go
- 【轉】跳躍表-原理及Java實現Java
- Java: 實現自迴歸分析/線性迴歸分析/基金各項指標計算等Java指標
- Java內功祕籍-線性表Java
- ECharts實現資料圖表分析及程式碼Echarts
- Java實現資料結構之線性結構Java資料結構
- 資料結構:線性表(Python實現基本操作)資料結構Python
- 資料結構:線性表的順序實現2.2資料結構
- 資料結構中的線性表程式碼實現資料結構
- Java JDK 動態代理使用及實現原理分析JavaJDK
- 線性結構-線性表
- 線性表
- 數字孿生的實現方案及可行性分析
- Java實現管線拓撲關係連通性分析Java
- 資料結構和演算法(一)線性表實現資料結構演算法
- 線性表的順序儲存C++程式碼實現C++
- Java JDK 動態代理(AOP)使用及實現原理分析JavaJDK
- 【Redis】跳躍表原理分析與基本程式碼實現(java)RedisJava
- Java-JDK動態代理(AOP)使用及實現原理分析JavaJDK
- Java實現順序表Java
- JAVA 實現 - 雜湊表Java
- 運用sklearn進行線性判別分析(LDA)程式碼實現LDA
- ORACLE表連線方式分析及常見用法(zt)Oracle
- TensorFlow實現線性迴歸
- 【機器學習】線性迴歸sklearn實現機器學習
- 線性表演算法實現演算法
- pytorch實現線性迴歸PyTorch
- 實驗2.2 線性表的應用:遊戲遊戲
- 機器學習——多元線性迴歸分析(multiple regression)及應用機器學習
- 預處理(3):python實現用scikit-learn實現的線性判別分析(LDA)PythonLDA
- Chapter 1 線性表APT
- 詳細分析連結串列中的遞迴性質(Java 實現)遞迴Java