Algorithms_基础数据结构(03)_线性表之链表_双向链表
文章目錄
- 大綱圖
- 雙向鏈表
- 雙向鏈表的基本結構
- 雙向鏈表的基本操作
- 頭插
- 尾插
- 中間部位插入
- 刪除頭部
- 刪除尾部
- 刪除中間位置的數據
- 查找
- 更新
- Code
- 總結
大綱圖
雙向鏈表
Algorithms_基礎數據結構(02)_鏈表&鏈表的應用案例之單向鏈表中梳理了 單向鏈表的基本操作,接下來我們繼續來看下雙向鏈表吧。
雙向鏈表的基本結構
單向鏈表只有一個方向,結點只有一個后繼指針next指向后面的結點。
雙向鏈表,顧名思義,它支持兩個方向,每個結點不止有一個后繼指針next指向后面的結點,還有一個前驅指針prev指向前面的結點。
雙向鏈表需要額外的兩個空間來存儲后繼結點和前驅結點的地址。所以,如果存儲同樣多的數據,雙向鏈表要比單鏈表占用更多的內存空間。
雖然兩個指針比較浪費存儲空間,但可以支持雙向遍歷,這樣也帶來了雙向鏈表操作的靈活性。那相比單鏈表,雙向鏈表適合解決哪種問題呢?
-----> B+Tree:Mysql索引 葉子節點 雙向鏈表
雙向鏈表的基本操作
頭插
尾插
中間部位插入
刪除頭部
刪除尾部
刪除中間位置的數據
查找
通常,雙向鏈表同單鏈表一樣,都僅有一個頭指針。所以雙鏈表查找指定元素的實現同單鏈表類似,都是從表頭依次遍歷表中元素,直到找到對應的元素為止。
更新
更改雙鏈表中指定結點數據域的操作那必須要先查找到該節點,因此是在查詢的基礎上完成的。------>即:通過遍歷找到存儲有該數據元素的結點,直接更改其數據域即可。
Code
/*** @author 小工匠* @version v1.0* @create 2020-01-03 06:08* @motto show me the code ,change the word* @blog https://artisan.blog.csdn.net/* @description**/public class ArtisanDoubleLinkedList {private ArtisanNode head; // head節點private ArtisanNode tail; // tail節點 為了方便直接獲取tail節點,省去每一次都要遍歷的操作private int size; // 鏈表元素數量/*** 雙向鏈表初始化*/public ArtisanDoubleLinkedList() {this.head = null;this.tail = null;}/*** 頭插* @param data*/public void add2Head(Object data) {ArtisanNode node = new ArtisanNode(data); // 新的Nodeif (this.head == null) { // 如果head節點為null, head和tail節點均為這個新的node節點this.tail = node;} else {// 將原來的頭節點的前驅節點指向node, 將新節點的后驅節點指向headthis.head.pre = node;node.next = head;}this.head = node; // 將新的節點置為head節點size++;}/*** 尾插 (低效)** @param data*/public void add2Tail(Object data) {// 從頭部遍歷,找到最后的節點,然后加到尾部ArtisanNode node = new ArtisanNode(data); // 要加入的節點ArtisanNode currentNode = head;if (currentNode == null){add2Head(data);}while(currentNode !=null){if (currentNode.next == null){ // 說明找到了當前的tail節點currentNode.next = node ;// 將當前tail節點的next指針指向新的tail節點node.pre = currentNode; //新的tail節點的pre指向當前tail節點節點this.tail = node;break;}else{currentNode = currentNode.next;}}size++;}/*** 尾插 (利用tail 無需遍歷 效率更高)** @param data*/public void add2Tail2(Object data) {// 已經設置tail了,直接用即可,效率更高ArtisanNode node = new ArtisanNode(data); // 要加入的節點if (this.head == null){add2Head(data);}else {tail.next = node;node.pre = tail;tail = node;}}/**** @param postition* @param data*/public void add2Nth(int postition ,Object data) {ArtisanNode newNode = new ArtisanNode(data); // 新的NodeArtisanNode currentNode = head;if (postition == 0 ){ // 如果是0 ,添加到頭節點add2Head(data);}else {for (int i = 1; i < postition; i++) { // 找到要插入的位置的前面的節點currentNode = currentNode.next;}// 與后繼節點建立雙層邏輯關系newNode.next = currentNode.next;currentNode.next.pre = newNode;// 與前置節點建立雙層邏輯關系currentNode.next = newNode;newNode.pre = currentNode;}size++;}/*** 根據value 查找元素* @param data* @return*/public ArtisanNode find(Object data){ // 從頭遍歷ArtisanNode currentNode = head;while(currentNode != null){if (data.equals(currentNode.data)){printPreAndNextInfo(currentNode);break;}else{currentNode = currentNode.next;}}return currentNode;}/*** 刪除頭部節點*/public void deleteHead(){this.head = this.head.next; // 將當前頭節點的下一個節點置為頭節點this.head.pre = null; // 將前置節點置為nullsize--;}/*** 刪除尾部節點*/public void deleteTail(){ArtisanNode currentNode = this.head;ArtisanNode previousNode = null;while (currentNode != null){if (currentNode.next == null){currentNode.pre = null;// 最后一個節點的pre置為置為nullpreviousNode.next = null;// 前置節點的next指針置為nullthis.tail = previousNode; // 將當前節點的前一個節點置為tail節點}else { // 如果當前節點的next指針指向不為空,則把下個節點置為當前節點,繼續遍歷previousNode = currentNode;// 保存上一個節點的信息currentNode = currentNode.next;}}}/*** 刪除指定位置的節點* @param position*/public ArtisanNode deleteNth(int position){ArtisanNode currentNode = this.head;if (position == 0 ){deleteHead();}else {for (int i = 1 ; i < position ; i++){// 找到要刪除節點的前一個節點currentNode = currentNode.next;}currentNode.next.next.pre = currentNode; // 將 要刪除節點的后一個節點的前驅節點 指向 當前節點(要刪除的節點的前一個節點)currentNode.next = currentNode.next.next; // 將 要刪除節點的前一個節點的next指針指向 要刪除節點的后一個節點}size--;return currentNode.next ; // 返回刪除的節點}/*** 獲取tail節點* @return tail節點*/public ArtisanNode getTail(){System.out.println("tail節點的值為:" + this.tail.data );return this.tail;}/*** 獲取head節點* @return head節點*/public ArtisanNode getHead(){System.out.println("head節點的值為:" + this.head.data );return this.head;}/*** 打印鏈表中的數據*/public void print() {ArtisanNode currentNode = this.head;// 從head節點開始遍歷while (currentNode != null) { // 循環,節點不為空 輸出當前節點的數據System.out.print(currentNode.data + " -> ");currentNode = currentNode.next; // 將當前節點移動到下一個節點,循環直到為null}System.out.print("null");System.out.println();}/*** 打印前后節點信息* @param currentNode*/private void printPreAndNextInfo(ArtisanNode currentNode) {System.out.println("當前節點:" + currentNode.data);if (currentNode.pre != null){System.out.println("當前節點【" + currentNode.data + "】的前驅節點:" + currentNode.pre.data);}else{System.out.println("當前節點【"+ currentNode.data + "】為head節點");}if (currentNode.next != null){System.out.println("當前節點【"+ currentNode.data + "】的后繼節點:" + currentNode.next.data);}else{System.out.println("當前節點【"+ currentNode.data + "】為tail節點");}}public static void main(String[] args) {ArtisanDoubleLinkedList doubleLinkedList = new ArtisanDoubleLinkedList();doubleLinkedList.add2Head("artisanData96");doubleLinkedList.add2Head("artisanData97");doubleLinkedList.add2Head("artisanData99");doubleLinkedList.add2Head("artisanData98");doubleLinkedList.getTail();doubleLinkedList.add2Tail("artisanData100");doubleLinkedList.getTail();doubleLinkedList.print();doubleLinkedList.getHead();// doubleLinkedList.add2Nth(2,"addedDataByPos");// doubleLinkedList.add2Tail2(1); // doubleLinkedList.add2Tail2(2); // doubleLinkedList.add2Tail2(3); // doubleLinkedList.add2Tail2(4);// doubleLinkedList.print(); // // System.out.println("tail:" + doubleLinkedList.tail.data); // // doubleLinkedList.find("artisanData98"); // doubleLinkedList.deleteHead(); // doubleLinkedList.print(); // doubleLinkedList.find("artisanData99");// System.out.println("被刪除節點:" + doubleLinkedList.deleteNth(1).data); // doubleLinkedList.print(); // doubleLinkedList.find("artisanData96");}/*** 雙向鏈表中的節點*/class ArtisanNode {ArtisanNode pre; // 前驅結點Object data; // 數據ArtisanNode next;// 后繼節點public ArtisanNode(Object data) {this.data = data;}} }總結
重要區別:
-
1.數組簡單易用,在實現上使用的是連續的內存空間,可以借助CPU的緩存機制,預讀數組中的數據,所以訪問效率更高。
-
2.鏈表在內存中并不是連續存儲,所以對CPU緩存不友好,沒辦法有效預讀。
-
3.數組的缺點是大小固定,一經聲明就要占用整塊連續內存空間。如果聲明的數組過大,系統可能沒有足夠的連續內存空間分配給它, 導致“內存不足(out ofmemory)”。如果聲明的數組過小,則可能出現不夠用的情況。注意下標越界的問題。
-
4.動態擴容:數組需再申請一個更大的內存空間,把原數組拷貝進去,非常費時。鏈表本身沒有大小的限制,天然地支持動態擴容,使用的時候也需要考慮占用內存的問題。
總結
以上是生活随笔為你收集整理的Algorithms_基础数据结构(03)_线性表之链表_双向链表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CPU_X86架构和ARM架构入门篇
- 下一篇: Algorithms_基础数据结构(04