Java數(shù)據(jù)結(jié)構(gòu)之順序表和鏈表精解
前言
兩個(gè)數(shù)據(jù)結(jié)構(gòu):順序表和鏈表
數(shù)據(jù)結(jié)構(gòu)是一門學(xué)科,和語言無關(guān)。
數(shù)據(jù) + 結(jié)構(gòu):一種描述和組織數(shù)據(jù)的方式。
1. 順序表
順序表是用一段物理地址連續(xù)的存儲(chǔ)單元依次存儲(chǔ)數(shù)據(jù)元素的線性結(jié)構(gòu),一般情況下采用數(shù)組存儲(chǔ)。在數(shù)組上完成數(shù)據(jù)的增刪查改。其邏輯上和物理上都是連續(xù)的。
問題引入:一個(gè)數(shù)組放在這,我們?nèi)绾尾拍茏约翰蝗?shù),讓程序自己進(jìn)行計(jì)數(shù)?
答:在引入變量,每次放一個(gè)元素就更新一次。(如下圖,為問題的示意)
也就是說順序表的底層其實(shí)是一個(gè)數(shù)組,在 java 當(dāng)中順序表都是動(dòng)態(tài)的因?yàn)?java 當(dāng)中的new 其實(shí)就相當(dāng)于 c 語言中的 malloc 。
代碼實(shí)現(xiàn)
我們來實(shí)現(xiàn)一個(gè)動(dòng)態(tài)順序表,以下是需要支持的接口。 各個(gè)函數(shù)的具體實(shí)現(xiàn)部分要我們自己來寫。
public class MyArrayList {//此為創(chuàng)建一個(gè)接口 public int[] elem; public int usedSize; public static int capacity = 10; public MyArrayList() { this.elem = new int[capacity];//新建一個(gè)數(shù)組 } public boolean isFull() { return this.usedSize == capacity;//判斷數(shù)組是否已滿 } // 在 pos 位置新增元素 public void add(int pos, int data) { //pos為要插入元素的下標(biāo),data為要插入的數(shù)據(jù) if (pos < 0 || pos > this.usedSize) { System.out.println("pos位置不合法!"); return; } //1、滿的情況 2、pos的合法情況 if (isFull()) { //擴(kuò)容 this.elem = Arrays.copyOf(this.elem, 2 * capacity); capacity *= 2;//新的容量 }//此處調(diào)用前面 isfull() 函數(shù)來判斷是否已滿,返回真,執(zhí)行if里面的語句,擴(kuò)容兩倍;若不滿,返回假,跳過if內(nèi)的語句。 for (int i = this.usedSize - 1; i >= pos; i--) { this.elem[i + 1] = this.elem[i]; }//在前面的代碼確定數(shù)組里面有盈余位置的情況下,將前一個(gè)數(shù)賦給后一個(gè)位置,從而騰出 pos 位置 this.elem[pos] = data; //給pos 位置的函數(shù)賦我們想要的值(即data) this.usedSize++;//usedsize 是奇數(shù)器,pos位置增加一個(gè)數(shù)組,自然要算上 } // 打印順序表 public void display() { for (int i = 0; i < this.usedSize; i++) { System.out.print(this.elem[i] + " "); } System.out.println(); }// 此處使用for循環(huán)遍歷打印 public boolean isEmpty() { return this.usedSize == 0; }//判斷數(shù)組是否為空 // 判定是否包含某個(gè)元素 public boolean contains(int toFind) { if (isEmpty()) return false; for (int i = 0; i < this.usedSize; i++) { if (this.elem[i] == toFind) { return true; } } return false; }//toFind 為我們要查找的元素,先判斷是否為空,再用循環(huán)遍歷判斷是否為該數(shù) // 查找某個(gè)元素對(duì)應(yīng)的位置 public int search(int toFind) { if (isEmpty()) return -1; for (int i = 0; i < this.usedSize; i++) { if (this.elem[i] == toFind) { return i; } } return -1; }//這個(gè)查找和上面的那個(gè)判斷唯一的區(qū)別就是上面返回的是真與假,這個(gè)返回的是查到的那個(gè)數(shù),本質(zhì)的方法都是一樣的 // 獲取 pos 位置的元素 public int getPos(int pos) { if (isEmpty()) { //return -1; 業(yè)務(wù)的處理 throw new RuntimeException("順序表是空的");//手動(dòng)拋出錯(cuò)誤(異常) } if (pos < 0 || pos >= this.usedSize) { throw new RuntimeException("pos不合法");//手動(dòng)拋出錯(cuò)誤(異常) } return this.elem[pos]; }//這個(gè)其實(shí)很簡單,只需排除一下空表和pos 不合法的情況,之后返回 elem[pos]的值就行 // 獲取順序表長度 public int size() { return this.usedSize; } // 給 pos 位置的元素設(shè)為 value public void setPos(int pos, int value) { if (pos < 0 || pos >= this.usedSize) { System.out.println("pos不合法!"); return; } if (isEmpty()) { System.out.println("順序表為空!"); return; } this.elem[pos] = value; }//這個(gè)也簡單,只要判斷一下 數(shù)組是否不合法,是否為空,直接 this.elem[pos] = value就行了 //刪除第一次出現(xiàn)的關(guān)鍵字key public void remove(int toRemove) { if (isEmpty()) return; int index = search(toRemove); if (index == -1) { System.out.println("沒有你要?jiǎng)h除的數(shù)字"); return; } for (int i = index; i < this.usedSize - 1; i++) { this.elem[i] = this.elem[i + 1]; } this.usedSize--; } //這里就是判斷數(shù)組是否為空,index 是否存在(此處調(diào)上面 search 函數(shù)),最后用for 循環(huán)遍歷,將后一個(gè)元素覆蓋在前一個(gè)元素上面 // 清空順序表 public void clear() { for (int i = 0; i < this.usedSize; i++) { this.elem[i] = 0; } this.usedSize = 0; }//用for循環(huán)遍歷每一個(gè)元素,將其賦值為 0 ,之后再將計(jì)數(shù)器 usedsized 也賦值為 0 }
順序表的寫起來是非常簡單的,但是他也是有一定的缺陷和不足的。它主要是負(fù)責(zé)實(shí)現(xiàn)增刪查改的功能,查改功能是很簡便的,如果直接就給定下標(biāo)的話,時(shí)間復(fù)雜度就是O(1),但是增刪的話,時(shí)間復(fù)雜度就必定為 O(N),這就非常麻煩(也就是說以后當(dāng)我們工作中要用查改就選用順序表就是最優(yōu)選的)。所以我們接下來就有必要引入鏈表這一種數(shù)據(jù)結(jié)構(gòu)。
2. 鏈表
鏈表是一種物理存儲(chǔ)結(jié)構(gòu)上非連續(xù)存儲(chǔ)結(jié)構(gòu),其存儲(chǔ)結(jié)構(gòu)便是上面放值,下面放下一個(gè)節(jié)點(diǎn)的地址,也就是說,雖然他是不連續(xù)的,當(dāng)上一個(gè)節(jié)點(diǎn)仍然能找到下一個(gè)節(jié)點(diǎn),類似于鏈子一樣,一個(gè)串一個(gè),但區(qū)別是,鏈表彼此之間不相接觸。
鏈表圖解
代碼實(shí)現(xiàn)
class Node {//創(chuàng)建一個(gè)節(jié)點(diǎn)類 //一個(gè)節(jié)點(diǎn)是有兩個(gè)域或者多個(gè)域的,以下便是創(chuàng)建的兩個(gè)域 public int val;//鏈表里面的數(shù)值 public Node next;//這是一個(gè)引用變量,用于標(biāo)識(shí)每個(gè)鏈表單元的下一個(gè)數(shù)的地址,因?yàn)樗娴氖枪?jié)點(diǎn),而節(jié)點(diǎn)的類型就是Node,所以我們也用Node 來定義這個(gè)變量。 public Node(int val) { //這是一個(gè)類里面的一個(gè)方法用來給鏈表當(dāng)中的val賦值 this.val = val;//因?yàn)閚ext存的hi下一個(gè)節(jié)點(diǎn)的地址,所以我們是不知道也不能賦值的 } } //鏈表 public class MyLinkedList {//此處為創(chuàng)建鏈表的接口 public Node head;//標(biāo)識(shí)單鏈表的頭節(jié)點(diǎn)(這也是一個(gè)引用變量) /** * 窮舉的方式 創(chuàng)建鏈表 當(dāng)然很low。此處只是為了好理解 */ public void createList() { Node node1 = new Node(12);//新建一個(gè)節(jié)點(diǎn)的對(duì)象node1,此為節(jié)點(diǎn)1,賦值為12 Node node2 = new Node(3);//此為節(jié)點(diǎn)2,賦值3 Node node3 = new Node(5);//此為節(jié)點(diǎn)3,賦值5 Node node4 = new Node(2);//此為節(jié)點(diǎn)4,賦值6 node1.next = node2;//node1中的next存node2(node2是一個(gè)對(duì)象的引用,存的是對(duì)象在堆中的地址) // 也就是說node1.next指向node2指向的地址 node2.next = node3;//以下三個(gè)原理同上 node3.next = node4; this.head = node1;//此處為定義一個(gè)head(后面head可能會(huì)有改動(dòng)) } /** * 打印單鏈表 */ public void show() { Node cur = this.head;//將head的值暫時(shí)存到cur中,這樣就是單純地是cur在變,head值不改變 while(cur != null) { System.out.print(cur.val+" "); cur = cur.next;//通過這道程序?qū)ur依次向后移動(dòng)最后到空的時(shí)候打印出來 } System.out.println(); } //得到單鏈表的長度 public int size() { Node cur = this.head;//同樣地,此處還以cur為介質(zhì)向后移動(dòng) int count = 0; while (cur != null) { count++;//4 cur 依次經(jīng)過各個(gè)節(jié)點(diǎn)的時(shí)候count便隨之計(jì)數(shù) cur = cur.next; } return count; } //查找是否包含關(guān)鍵字key是否在單鏈表當(dāng)中 15 public boolean contains(int val) {//這里函數(shù)參數(shù)中的val就是我們要查找的 key Node cur = this.head; while (cur != null) {//同樣的方法遍歷節(jié)點(diǎn) if(cur.val == val) { return true; } cur = cur.next; } return false;//如果遍歷完了都沒有找到那就肯定沒有了 } //頭插法 public void addFirst(int data) {//date為要插入的數(shù)據(jù) Node node = new Node(data);//此處為創(chuàng)建要插入的節(jié)點(diǎn)(對(duì)象)內(nèi)部存儲(chǔ)的值為 date if(this.head == null) {//判斷頭結(jié)點(diǎn)是否為空 this.head = node;// 若為空,則根本沒有鏈表,直接將要插入的節(jié)點(diǎn)賦給head }else { //此為有鏈表存在的狀況 node.next = this.head;//此處操作讓head原本指向的對(duì)象(節(jié)點(diǎn))成為鏈表中的第二個(gè)節(jié)點(diǎn) this.head = node; //上面的操作讓head指向了node指向的節(jié)點(diǎn)(head是頭結(jié)點(diǎn)的標(biāo)志,自然要讓他移到第一位) } } //尾插法 public void addLast(int data) {//data為要插入的數(shù)據(jù) Node node = new Node(data);//新建一個(gè)節(jié)點(diǎn)改值為data if(this.head == null) {//判斷鏈表是否為空,若為空,則為一個(gè)節(jié)點(diǎn)便也是最后一個(gè) this.head = node;//直接讓head指向node指向的節(jié)點(diǎn)即可 }else {//若節(jié)點(diǎn)不為空時(shí) Node cur = this.head;//將head中的地址賦給中間變量cur while (cur.next != null) { cur = cur.next; //用cur遍歷節(jié)點(diǎn) } cur.next = node;//這是的cur已經(jīng)待在了最后一個(gè)節(jié)點(diǎn)上它的next上沒有東西了 //所以這個(gè)時(shí)候?qū)ode指向的地址交給next,既完成了節(jié)點(diǎn)的插入 } } public Node searchPrev(int index) { Node cur = this.head;//同樣地,以 cur為介質(zhì) int count = 0; while(count != index-1) { cur = cur.next;//用cur 遍歷鏈表 count++; } return cur;//此時(shí)返回的 cur剛好指向 index-1這個(gè)節(jié)點(diǎn) } //任意位置插入,第一個(gè)數(shù)據(jù)節(jié)點(diǎn)為0號(hào)下標(biāo) public void addIndex(int index,int data) { if(index < 0 || index > size()) {//判斷index是否合法 System.out.println("下標(biāo)不合法"); return; } //頭插法 if(index == 0) {//index為0時(shí),就是頭插法 addFirst(data); return; } //尾插法 if(index == size()) {//index為數(shù)組長度是就是尾插法 addLast(data); return; } Node cur = searchPrev(index);//讓cur 拿到第index-1位置節(jié)點(diǎn)地址 Node node = new Node(data);//創(chuàng)建一個(gè)存了數(shù)據(jù)為data 的節(jié)點(diǎn) node.next = cur.next;//將剛剛創(chuàng)建的節(jié)點(diǎn)中的next存進(jìn) cur 中的next(即把index的地址放到node的next里) cur.next = node;//再將node中存著的地址放到cur的next中 //插中間歸根到底就是next的交換,以下為傳遞順序: //index-1中index的地址 ——> node.next //node的地址 ——> cur.next } //下面這段代碼用來找val的前驅(qū)節(jié)點(diǎn) public Node searchPrevNode(int val) { Node cur = this.head;//還是以 cur 為中間量 while (cur.next != null) {//cur 遍歷至數(shù)組的最后一位 if(cur.next.val == val) {//這里是判斷cur的下一個(gè)節(jié)點(diǎn)中的值是否為val return cur;//如果是的就返回cur } cur = cur.next; } return null; } //刪除第一次出現(xiàn)關(guān)鍵字為key的節(jié)點(diǎn) public void remove(int val) { if(this.head == null) return; //單獨(dú)判斷頭節(jié)點(diǎn)的問題 if(this.head.val == val) { this.head = this.head.next; return;//這段代碼的意思就是如果就是如果第一個(gè)節(jié)點(diǎn)中的值就是要?jiǎng)h除的值 //那么直接就讓第一個(gè)節(jié)點(diǎn)的引用指向下一個(gè)節(jié)點(diǎn),其實(shí)就是忽略第一個(gè)起到了一個(gè)刪除效果 //而原來第一個(gè)節(jié)點(diǎn)變成了沒有人引用的對(duì)象會(huì)被jvm回收 } Node cur = searchPrevNode(val);//拿到下一個(gè)節(jié)點(diǎn)值為val的cur if(cur == null) {// System.out.println("沒有你要?jiǎng)h除的節(jié)點(diǎn)!"); return; } //下面就是cur 存在的情況 Node del = cur.next;//創(chuàng)建一個(gè)節(jié)點(diǎn)del讓其使用cur中下一個(gè)節(jié)點(diǎn)的地址(也就是要?jiǎng)h的val地址) cur.next = del.next;//再把del中的存的下一個(gè)地址賦給 cur,那么就間接地抹去了val } //刪除所有值為key的節(jié)點(diǎn) public void removeAllKey(int val) { if(this.head == null) {//節(jié)點(diǎn)是否為空? return; } Node prev = this.head;//讓 prev指向 head指向的對(duì)象(prev本質(zhì)上就是前驅(qū)節(jié)點(diǎn)) Node cur = this.head.next;//讓 cur指向 head.next位置 while (cur != null) { //用cur 來遍歷數(shù)組 if(cur.val == val) { // prev.next = cur.next;//抹去要?jiǎng)h的節(jié)點(diǎn) cur = cur.next;//移動(dòng)至下一個(gè)節(jié)點(diǎn)處 }else {//以下是 cur處的值 不等于 val 的情況 prev = cur;//將prev移到cur的位置 cur = cur.next;//再將cur 向后移動(dòng) } } //只有頭結(jié)點(diǎn)且頭結(jié)點(diǎn)便是要?jiǎng)h除的val的情況 if(this.head.val == val) { this.head = this.head.next; } } public void clear(){ } }
到此這篇關(guān)于Java數(shù)據(jù)結(jié)構(gòu)之順序表和鏈表精解的文章就介紹到這了,更多相關(guān)Java 順序表和鏈表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java數(shù)據(jù)結(jié)構(gòu)基礎(chǔ):單鏈表與雙向鏈表
- Java 數(shù)據(jù)結(jié)構(gòu)之刪除鏈表中重復(fù)的結(jié)點(diǎn)
- Java實(shí)現(xiàn)鏈表數(shù)據(jù)結(jié)構(gòu)的方法
- 帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表
- Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之環(huán)形鏈表
- Java?數(shù)據(jù)結(jié)構(gòu)與算法系列精講之單向鏈表
- Java?精煉解讀數(shù)據(jù)結(jié)構(gòu)的鏈表的概念與實(shí)現(xiàn)
- Java數(shù)據(jù)結(jié)構(gòu)之雙向鏈表圖解
- Java鏈表數(shù)據(jù)結(jié)構(gòu)及其簡單使用方法解析
相關(guān)文章
Java實(shí)現(xiàn)簡單圖形界面計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡單圖形界面計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04使用lombok@Data存在extends時(shí)需要注意的問題
在Java編程中,正確實(shí)現(xiàn)equals方法是保證對(duì)象比較一致性的關(guān)鍵,使用instanceof檢查類型可能導(dǎo)致違反對(duì)稱性原則,即當(dāng)子類和父類都重寫equals時(shí)可能出現(xiàn)a.equals(b)不等于b.equals(a)的情況,Lombok的@EqualsAndHashCode注解可以通過callSuper=true參數(shù)2024-10-10解決mybatis case when 報(bào)錯(cuò)的問題
這篇文章主要介紹了解決mybatis case when 報(bào)錯(cuò)的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02Java基于分治算法實(shí)現(xiàn)的線性時(shí)間選擇操作示例
這篇文章主要介紹了Java基于分治算法實(shí)現(xiàn)的線性時(shí)間選擇操作,涉及java排序、比較、計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下2017-11-11Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(41)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07Java優(yōu)先隊(duì)列?priority?queue
本文主要介紹了Java優(yōu)先隊(duì)列?priority?queue,優(yōu)先隊(duì)列是一種特殊的數(shù)據(jù)結(jié)構(gòu)隊(duì)列中每一個(gè)元素都被分配到一個(gè)優(yōu)先權(quán)值,出隊(duì)順序按照優(yōu)先權(quán)值來劃分。一般有兩種出隊(duì)順序高優(yōu)先權(quán)出隊(duì)或低優(yōu)先權(quán)出隊(duì),想了解具體內(nèi)容的小伙伴可以參考下文內(nèi)容,希望對(duì)你有所幫助2021-12-12PowerJob的CleanService清理服務(wù)流程
這篇文章主要為大家介紹了PowerJob的CleanService清理服務(wù)流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>2024-02-02