Java版數據結構與算法——線性表

*************************************優雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程

請關注微信公眾號:HB荷包
在這里插入圖片描述
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
SimpleExecutor

五、線性表

線性表是最基本、最簡單、也是最常用的一種數據結構。一個線性表是n個具有相同特性的數據元素的有限序列

前驅元素: 若A元素在B元素的前面,則稱A為B的前驅元素

后繼元素: 若B元素在A元素的后面,則稱B為A的后繼元素

線性表的特征:

  1. 第一個數據元素沒有前驅,這個數據元素被稱為頭結點;
  2. 最后一個數據元素沒有后繼,這個數據元素被稱為尾結點;
  3. 除了第一個和最后一個數據元素外,其他數據元素有且僅有一個前驅和一個后繼。

如果把線性表用數學語言來定義,則可以表示為(a1,…ai-1,ai,ai+1,…an),ai-1領先于ai,ai領先于ai+1,稱ai-1是ai的 前驅元素,ai+1是ai的后繼元素

1591523145870

線性表的分類:

線性表中數據存儲的方式可以是順序存儲,也可以是鏈式存儲,按照數據的存儲方式不同,可以把線性表分為順序 表和鏈表。

5.1 順序表☆

順序表是在計算機內存中以數組的形式保存的線性表,線性表的順序存儲是指用一組地址連續的存儲單元,依次存儲線性表中的各個元素、使得線性表中再邏輯結構上相鄰的數據元素存儲在相鄰的物理存儲單元中,即通過數據元素物理存儲的相鄰關系來反映數據元素之間邏輯上的相鄰關系。

1591523151353

5.1.1 順序表的實現

API設計

類名SequenceList
構造方法SequenceList(int capacity):創建容量為capacity的SequenceList對象
成員方法1. public void clear():空置線性表
2. publicboolean isEmpty():判斷線性表是否為空,是返回true,否返回false
3. public int length():獲取線性表中元素的個數
4. public T get(int i):讀取并返回線性表中的第i個元素的值
5. public void insert(int i,T t):在線性表的第i個元素之前插入一個值為t的數據元素。
6. public void insert(T t):向線性表中添加一個元素t
7. public T remove(int i):刪除并返回線性表中第i個數據元素。
8. public int indexOf(T t):返回線性表中首次出現的指定的數據元素的位序號,若不存在,則返回-1
成員變量1. private T[] eles:存儲元素的數組
2. private int N:當前線性表的長度

代碼實現

public class SequenceList {/*** 存儲元素的數組*/private String[] eles;/*** 當前線性表元素的個數*/private int n;public SequenceList(int capacity) {// 構造一個長度為capacity的順序表eles = new String[capacity];n = 0;}/*** 置空線性表*/public void clear() {n = 0;// 將數據整個置空eles = new String[eles.length];}/*** 判斷線性表是否為空** @return*/public boolean isEmpty() {return n == 0;}/*** 獲取線性表中元素個數** @return*/public int length() {return n;}/*** 讀取并返回線性表中的第index個元素的值** @param index* @return*/public String get(int index) {// 判斷一下下標是否合法if (index < 0 || index >= n) {System.err.println("當前元素不存在");return null;}return eles[index];}/*** 在線性表的第i個元素之前插入一個值為t的數據元素** @param index* @param v*/public void insert(int index, String v) {if (n == eles.length) {System.err.println("當前表已滿");return;}if (index >= eles.length) {System.err.println("下標位置非法");return;}// 判斷一下i是否合法if (index < 0 || index > n) {System.err.println("下標位置非法");return;}// 把index位置空出來,index位置以及后面的元素依次向后移動一位for (int i = n; i > index; i--) {eles[i] = eles[i - 1];}// 把v放到index處eles[index] = v;n++;}/*** 向線性表中添加一個元素t** @param v*/public void insert(String v) {// 判斷一下元素個數是否已經超過了數組的最大容量if (n == eles.length) {System.err.println("當前表已滿");return;}eles[n++] = v;}/*** 刪除并返回線性表中第index個元素** @param index* @return*/public String remove(int index) {if (index < 0 || index >= n) {System.out.println("當前要刪除的元素不存在");return null;}// 獲取要刪除的元素String result = eles[index];// 把index后面的元素都向前移動一格for (int i = index; i < n - 1; i++) {eles[i] = eles[i + 1];}// 元素個數-1n--;return result;}/*** 返回線性表中首次出現的指定的元素數據的索引** @param v* @return*/public int indexOf(String v) {return 0;}}class Test1 {public static void main(String[] args) {SequenceList list = new SequenceList(10);list.insert("劉備");list.insert("張飛");list.insert("關羽");list.insert("曹操");System.out.println(list.get(2));System.out.println(list.remove(0));System.out.println(list.get(0));list.clear();System.out.println(list.length());}
}

5.1.2 可變容量的順序表

在之前的實現中,當我們使用SequenceList時,先new SequenceList(5)創建一個對象,創建對象時就需要指定容 器的大小,初始化指定大小的數組來存儲元素,當我們插入元素時,如果已經插入了5個元素,還要繼續插入數 據,則會報錯,就不能插入了。這種設計不符合容器的設計理念,因此我們在設計順序表時,應該考慮它的容量的 伸縮性。

考慮容器的容量伸縮性,其實就是改變存儲數據元素的數組的大小,那我們需要考慮什么時候需要改變數組的大 小?

1.添加元素時:

添加元素時,應該檢查當前數組的大小是否能容納新的元素,如果不能容納,則需要創建新的容量更大的數組,我 們這里創建一個是原數組兩倍容量的新數組存儲元素

1592144615179

2.移除元素時:

移除元素時,應該檢查當前數組的大小是否太大,比如正在用100個容量的數組存儲10個元素,這樣就會造成內存 空間的浪費,應該創建一個容量更小的數組存儲元素。如果我們發現數據元素的數量不足數組容量的1/4,則創建一個是原數組容量的1/2的新數組存儲元素。

1592144750266

順序表的容量可變代碼:

public class SequenceList2 {/*** 存儲元素的數組*/private String[] eles;/*** 當前線性表元素的個數*/private int n;public SequenceList2(int capacity) {// 如果傳入的個數小于1,則默認為1if(capacity < 1) {capacity = 1;}// 構造一個長度為capacity的順序表eles = new String[capacity];n = 0;}/*** 置空線性表*/public void clear() {n = 0;// 將數據整個置空eles = new String[eles.length];}/*** 判斷線性表是否為空** @return*/public boolean isEmpty() {return n == 0;}/*** 獲取線性表中元素個數** @return*/public int length() {return n;}/*** 讀取并返回線性表中的第index個元素的值** @param index* @return*/public String get(int index) {// 判斷一下下標是否合法if (index < 0 || index >= n) {System.err.println("當前元素不存在");return null;}return eles[index];}/*** 在線性表的第i個元素之前插入一個值為t的數據元素** @param index* @param v*/public void insert(int index, String v) {// 判斷一下i是否合法if (index < 0 || index > n) {System.err.println("下標位置非法");return;}if (n == eles.length) {resize(eles.length * 2);}// 把index位置空出來,index位置以及后面的元素依次向后移動一位for (int i = n; i > index; i--) {eles[i] = eles[i - 1];}// 把v放到index處eles[index] = v;n++;}/*** 向線性表中添加一個元素t** @param v*/public void insert(String v) {// 判斷一下元素個數是否已經超過了數組的最大容量if (n == eles.length) {resize(eles.length * 2);}eles[n++] = v;}/*** 刪除并返回線性表中第index個元素** @param index* @return*/public String remove(int index) {if (index < 0 || index >= n) {System.out.println("當前要刪除的元素不存在");return null;}// 獲取要刪除的元素String result = eles[index];// 把index后面的元素都向前移動一格for (int i = index; i < n - 1; i++) {eles[i] = eles[i + 1];}// 元素個數-1n--;// 判斷一下當前元素數量已經不足數組大小的1/4,則重置數組大小if (n > 0 && n < eles.length / 4) {resize(eles.length / 2);}return result;}/*** 將現有的數組改變為容量為newSize的新數組** @param newSize*/private void resize(int newSize) {// 記錄舊數組String[] temp = eles;// 創建新數組eles = new String[newSize];// 把就舊組中的元素拷貝到新數組for (int i = 0; i < n; i++) {eles[i] = temp[i];}}/*** 返回線性表中首次出現的指定的元素數據的索引** @param v* @return*/public int indexOf(String v) {return 0;}}class Test2 {public static void main(String[] args) {SequenceList2 list = new SequenceList2(0);list.insert("劉備");System.out.println(list.length());list.insert("張飛");System.out.println(list.length());list.insert("關羽");System.out.println(list.length());list.insert("曹操");System.out.println(list.length());}
}

5.1.3 時間復雜度

get(i):不難看出,不論數據元素量N有多大,只需要一次eles[i]就可以獲取到對應的元素,所以時間復雜度為O(1); insert(int i,T t):每一次插入,都需要把i位置后面的元素移動一次,隨著元素數量N的增大,移動的元素也越多,時間復雜為O(n);

remove(int i):每一次刪除,都需要把i位置后面的元素移動一次,隨著數據量N的增大,移動的元素也越多,時間復雜度為O(n);

由于順序表的底層由數組實現,數組的長度是固定的,所以在操作的過程中涉及到了容器擴容操作。這樣會導致順序表在使用過程中的時間復雜度不是線性的,在某些需要擴容的結點處,耗時會突增,尤其是元素越多,這個問題越明顯

5.2 鏈表☆

之前我們已經使用順序存儲結構實現了線性表,我們會發現雖然順序表的查詢很快,時間復雜度為O(1),但是增刪的效率是比較低的,因為每一次增刪操作都伴隨著大量的數據元素移動。這個問題有沒有解決方案呢?有,我們可以使用另外一種存儲結構實現線性表,鏈式存儲結構。

鏈表是一種物理存儲單元上非連續、非順序的存儲結構,其物理結構不能直觀的表示數據元素的邏輯順序,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。鏈表由一系列的結點(鏈表中的每一個元素稱為結點)組成,結點可以在運行時動態生成。

1592146102381

那我們如何使用鏈表呢?按照面向對象的思想,我們可以設計一個類,來描述結點這個事物,用一個屬性描述這個 結點存儲的元素,用另外一個屬性描述這個結點的下一個結點。

節點API設計

類名Node
構造方法Node(T t,Node next):創建Node對象
成員變量T item:存儲數據
Node next:指向下一個結點

代碼實現

public static class Node {public String item;public Node next;public Node(String item, Node next) {this.item = item;this.next = next;}
}

5.2.1 單向鏈表

單向鏈表是鏈表的一種,它由多個結點組成,每個結點都由一個數據域和一個指針域組成,數據域用來存儲數據, 指針域用來指向其后繼結點。鏈表的頭結點的數據域不存儲數據,指針域指向第一個真正存儲數據的結點。

1592146542211

API設計

類名LinkList
構造方法LinkList():創建LinkList對象
成員方法1. public void clear():空置線性表
2. publicboolean isEmpty():判斷線性表是否為空,是返回true,否返回false
3. public int length():獲取線性表中元素的個數
4. public T get(int i):讀取并返回線性表中的第i個元素的值
5. public void insert(T t):往線性表中添加一個元素;
6. public void insert(int i,T t):在線性表的第i個元素之前插入一個值為t的數據元素。
7. public T remove(int i):刪除并返回線性表中第i個數據元素。
8. public int indexOf(T t):返回線性表中首次出現的指定的數據元素的位序號,若不存在,則返回-1。
成員內部類private class Node:結點類
成員變量1. private Node head:記錄首結點
2. private int N:記錄鏈表的長度

代碼實現

public class LinkList {/*** 記錄首結點*/private Node head;/*** 鏈表長度*/private int n;public LinkList() {// 初始化鏈表n = 0;head = new Node(null, null);}/*** 置空線性表*/public void clear() {head.item = null;head.next = null;n = 0;}/*** 判斷線性表是否為空** @return*/public boolean isEmpty() {return n == 0;}/*** 獲取線性表中元素個數** @return*/public int length() {return n;}/*** 讀取并返回線性表中的第index個元素的值** @param index* @return*/public String get(int index) {if (index < 0 || index >= n) {System.out.println("下標不合法");return null;}// 獲取第一個元素節點Node item = head.next;for (int i = 0; i < index; i++) {item = item.next;}return item.item;}/*** 在線性表的第i個元素之前插入一個值為t的數據元素** @param index* @param v*/public void insert(int index, String v) {if(index<0||index>=n) {System.out.println("位置不合法");return;}// 尋找index之前的節點// pre是我們要插入元素位置的上一個節點Node pre = head;for (int i = 0; i < index; i++) {// 取出下一個元素賦值給prepre = pre.next;}// 到這里,插入下標的上一個節點就找到了// 取出index下一個位置的元素Node next = pre.next;// 構建新的NodeNode newNode = new Node(v, next);pre.next = newNode;// 長度+1n++;}/*** 向線性表中添加一個元素t** @param v*/public void insert(String v) {// 找到最后一個節點Node node = head;// 只要node的下一個節點不為null,說明還有下一個元素while (node.next != null) {node = node.next;}// 到這里,node就是最后一個節點// 創建一個新的節點Node newNode = new Node(v, null);// 直接將最后一個節點的下一個結點指向新結點node.next = newNode;// 鏈表長度+1n++;}/*** 刪除并返回線性表中第index個元素** @param index* @return*/public String remove(int index) {if(index < 0 || index >= n) {System.out.println("下標位置不合法");return null;}// 尋找index之前的元素Node pre = head;for (int i = 0; i < index; i++) {// 取出下一個元素賦值給prepre = pre.next;}// 到這里就找到了之前的元素// 獲取當前index位置的結點Node current = pre.next;// 獲取當前index位置的下一個節點Node next = current.next;// 前一個節點指向下一個節點pre.next = next;// 長度-1n--;return current.item;}/*** 返回線性表中首次出現的指定的元素數據的索引** @param v* @return*/public int indexOf(String v) {return 0;}private class Node {public String item;public Node next;public Node(String item, Node next) {this.item = item;this.next = next;}}
}class Test3 {public static void main(String[] args) {LinkList list = new LinkList();list.insert("劉備");System.out.println(list.get(0));list.insert("關羽");System.out.println(list.get(1));list.insert(0, "張飛");System.out.println(list.get(0)+list.get(1));System.out.println(list.remove(1));System.out.println(list.get(1));System.out.println(list.length());list.clear();System.out.println(list.length());System.out.println(list.isEmpty());}
}

5.2.2 雙向鏈表

雙向鏈表也叫雙向表,是鏈表的一種,它由多個結點組成,每個結點都由一個數據域和兩個指針域組成,數據域用 來存儲數據,其中一個指針域用來指向其后繼結點,另一個指針域用來指向前驅結點。鏈表的頭結點的數據域不存 儲數據,指向前驅結點的指針域值為null,指向后繼結點的指針域指向第一個真正存儲數據的結點

1592150585161

按照面向對象的思想,我們需要設計一個類,來描述結點這個事物。由于結點是屬于鏈表的,所以我們把結點類作 為鏈表類的一個內部類來實現

節點API 設計

類名Node
構造方法Node(T t,Node pre,Node next):創建Node對象
成員變量T item:存儲數據
Node next:指向下一個結點
Node pre:指向上一個結點

雙向鏈表API設計

類名TowWayLinkList
構造方法TowWayLinkList():創建TowWayLinkList對象
成員方法1. public void clear():空置線性表
2. public boolean isEmpty():判斷線性表是否為空,是返回true,否返回false
3. public int length():獲取線性表中元素的個數
4. public T get(int i):讀取并返回線性表中的第i個元素的值
5. public void insert(T t):往線性表中添加一個元素;
6. public void insert(int i,T t):在線性表的第i個元素之前插入一個值為t的數據元素。
7. public T remove(int i):刪除并返回線性表中第i個數據元素。
8. public int indexOf(T t):返回線性表中首次出現的指定的數據元素的位序號,若不存在,則返回-1。
9. public T getFirst():獲取第一個元素
10. public T getLast():獲取最后一個元素
成員內部類private class Node:結點類
成員變量1.private Node first:記錄首結點
2.private Node last:記錄尾結點
3.private int N:記錄鏈表的長度

代碼實現

public class TowWayLinkList {/*** 記錄首結點*/private Node first;/*** 記錄尾結點*/private Node last;/*** 鏈表長度*/private int n;public TowWayLinkList() {last = null;first = new Node(null, null, null);n = 0;}/*** 置空線性表*/public void clear() {last = null;first.next = null;first.item = null;first.pre = null;n = 0;}/*** 判斷線性表是否為空** @return*/public boolean isEmpty() {return n == 0;}/*** 獲取線性表中元素個數** @return*/public int length() {return n;}/*** 讀取并返回線性表中的第index個元素的值** @param index* @return*/public String get(int index) {if (index < 0 || index >= n) {System.out.println("下標不合法");return null;}// 獲取第一個元素節點Node item = first.next;for (int i = 0; i < index; i++) {item = item.next;}return item.item;}/*** 在線性表的第i個元素之前插入一個值為t的數據元素** @param index* @param v*/public void insert(int index, String v) {if (index < 0 || index >= n) {System.out.println("下標不合法");return;}// 獲取頭結點Node pre = first;for (int i = 0; i < index; i++) {pre = pre.next;}// 到這里之后,我們就找到了待插入下標位置的前一個元素Node next = pre.next;// 構建一個新的節點Node newNode = new Node(v, pre, next);pre.next = newNode;next.pre = newNode;// 長度+1n++;}/*** 向線性表中添加一個元素t** @param v*/public void insert(String v) {if (last == null) {// 如果last為空說明鏈表剛被初始化// 那么第一個元素,就是lastlast = new Node(v, first, null);first.next = last;} else {// 說明鏈表已經有了元素Node oldLast = last;Node node = new Node(v, oldLast, null);oldLast.next = node;last = node;}// 長度+1n++;}/*** 刪除并返回線性表中第index個元素** @param index* @return*/public String remove(int index) {if (index < 0 || index >= n) {System.out.println("下標不合法");return null;}// 獲取頭結點Node pre = first;for (int i = 0; i < index; i++) {pre = pre.next;}// 到這里,pre就是我們要刪除下標位置的前一個節點// 獲取待刪除的當前節點Node current = pre.next;// 獲取待刪除節點的下一個節點Node next = current.next;pre.next = next;next.pre = pre;// 長度-1n--;return current.item;}/*** 獲取第一個元素** @return*/public String getFirst() {if (isEmpty()) {return null;}return first.next.item;}/*** 獲取最后一個元素** @return*/public String getLast() {if (isEmpty()) {return null;}return last.item;}/*** 返回線性表中首次出現的指定的元素數據的索引** @param v* @return*/public int indexOf(String v) {return 0;}private class Node {public String item;/*** 指向下一個節點*/public Node next;/*** 指向上一個節點*/public Node pre;public Node(String item, Node pre, Node next) {this.item = item;this.pre = pre;this.next = next;}}
}class Test4 {public static void main(String[] args) {TowWayLinkList list = new TowWayLinkList();list.insert("劉備");System.out.println(list.get(0));list.insert("關羽");System.out.println(list.get(1));list.insert(0, "張飛");System.out.println(list.get(0)+list.get(1));System.out.println(list.remove(1));System.out.println(list.get(1));System.out.println(list.length());list.clear();System.out.println(list.length());System.out.println(list.isEmpty());}
}

5.2.3 時間復雜度

get(int i):每一次查詢,都需要從鏈表的頭部開始,依次向后查找,隨著數據元素N的增多,比較的元素越多,時間 復雜度為O(n)

insert(int i,T t):每一次插入,需要先找到i位置的前一個元素,然后完成插入操作,隨著數據元素N的增多,查找的 元素越多,時間復雜度為O(n);

remove(int i):每一次移除,需要先找到i位置的前一個元素,然后完成插入操作,隨著數據元素N的增多,查找的元素越多,時間復雜度為O(n)

相比較順序表,鏈表插入和刪除的時間復雜度雖然一樣,但仍然有很大的優勢,因為鏈表的物理地址是不連續的, 它不需要預先指定存儲空間大小,或者在存儲過程中涉及到擴容等操作,同時它并沒有涉及的元素的交換。

相比較順序表,鏈表的查詢操作性能會比較低。因此,如果我們的程序中查詢操作比較多,建議使用順序表,增刪 操作比較多,建議使用鏈表。

結論:鏈表的查詢性能比數組(順序表)低,但是增刪比數組高。其實,在我們使用的過程中,鏈表的使用頻率是相當低的,因為只要我們盡可能的避免了順序表的擴容,并且保證每次插入都是在順序表最后一位插入,那么順序表的增刪操作性能也比鏈表要高

5.2.4 單鏈表反轉☆

1592230710574

需求:

原鏈表中數據為:1->2->3>4

反轉后鏈表中數據為:4->3->2->1

API設計

方法作用
public void reverse()對整個鏈表反轉
public Node reverse(Node curr):反轉鏈表中的某個結點curr,并把反轉后的curr結點返回

使用遞歸可以完成反轉,遞歸反轉其實就是從原鏈表的第一個存數據的結點開始,依次遞歸調用反轉每一個結點, 直到把最后一個結點反轉完畢,整個鏈表就反轉完畢。

1592231330947

代碼

public class LinkList2 {/*** 記錄首結點*/private Node head;/*** 鏈表長度*/private int n;public LinkList2() {// 初始化鏈表n = 0;head = new Node(null, null);}/*** 置空線性表*/public void clear() {head.item = null;head.next = null;n = 0;}/*** 判斷線性表是否為空** @return*/public boolean isEmpty() {return n == 0;}/*** 獲取線性表中元素個數** @return*/public int length() {return n;}/*** 讀取并返回線性表中的第index個元素的值** @param index* @return*/public String get(int index) {if (index < 0 || index >= n) {System.out.println("下標不合法");return null;}// 獲取第一個元素節點Node item = head.next;for (int i = 0; i < index; i++) {item = item.next;}return item.item;}/*** 在線性表的第i個元素之前插入一個值為t的數據元素** @param index* @param v*/public void insert(int index, String v) {if (index < 0 || index >= n) {System.out.println("位置不合法");return;}// 尋找index之前的節點// pre是我們要插入元素位置的上一個節點Node pre = head;for (int i = 0; i < index; i++) {// 取出下一個元素賦值給prepre = pre.next;}// 到這里,插入下標的上一個節點就找到了// 取出index下一個位置的元素Node next = pre.next;// 構建新的NodeNode newNode = new Node(v, next);pre.next = newNode;// 長度+1n++;}/*** 向線性表中添加一個元素t** @param v*/public void insert(String v) {// 找到最后一個節點Node node = head;// 只要node的下一個節點不為null,說明還有下一個元素while (node.next != null) {node = node.next;}// 到這里,node就是最后一個節點// 創建一個新的節點Node newNode = new Node(v, null);// 直接將最后一個節點的下一個結點指向新結點node.next = newNode;// 鏈表長度+1n++;}/*** 刪除并返回線性表中第index個元素** @param index* @return*/public String remove(int index) {if (index < 0 || index >= n) {System.out.println("下標位置不合法");return null;}// 尋找index之前的元素Node pre = head;for (int i = 0; i < index; i++) {// 取出下一個元素賦值給prepre = pre.next;}// 到這里就找到了之前的元素// 獲取當前index位置的結點Node current = pre.next;// 獲取當前index位置的下一個節點Node next = current.next;// 前一個節點指向下一個節點pre.next = next;// 長度-1n--;return current.item;}/*** 返回線性表中首次出現的指定的元素數據的索引** @param v* @return*/public int indexOf(String v) {return 0;}/*** 對整個鏈表進行翻轉*/public void reverse() {if (n == 0) {// 空鏈表,不翻轉return;}reverse(head.next);}/*** 翻轉鏈表中某個節點,并返回當前節點** @param current* @return*/private Node reverse(Node current) {// 判斷一下當前節點是否是最后一個節點if (current.next == null) {// 說明當前節點是最后一個節點// 最后一個節點需要讓頭節點指向它head.next = current;return current;}// 如果不是最后一個節點,翻轉下一個節點Node next = reverse(current.next);next.next = current;// 當前節點的下一個節點設為nullcurrent.next = null;return current;}private class Node {public String item;public Node next;public Node(String item, Node next) {this.item = item;this.next = next;}}
}class Test5 {public static void main(String[] args) {LinkList2 list2 = new LinkList2();list2.insert("劉備");list2.insert("關羽");list2.insert("張飛");System.out.println(list2.get(0) + list2.get(1) + list2.get(2));list2.reverse();System.out.println(list2.get(0) + list2.get(1) + list2.get(2));}
}

5.2.5 循環鏈表

循環鏈表,顧名思義,鏈表整體要形成一個圓環狀。在單向鏈表中,最后一個節點的指針為null,不指向任何結 點,因為沒有下一個元素了。要實現循環鏈表,我們只需要讓單向鏈表的最后一個節點的指針指向頭結點即可。

1592231973849

循環鏈表的構建

public class ForList {public static void main(String[] args) {Node n1 = new Node("1", null);Node n2 = new Node("2", null);Node n3 = new Node("3", null);Node n4 = new Node("4", null);Node n5 = new Node("5", null);// 構造循環鏈表n1.next = n2;n2.next = n3;n3.next = n4;n4.next = n5;n5.next = n1;}private static class Node {public String item;public Node next;public Node(String item, Node next) {this.item = item;this.next = next;}}
}

5.2.6 快慢指針

快慢指針指的是定義兩個指針,這兩個指針的移動速度一塊一慢,以此來制造出自己想要的差值,這個差值可以然 我們找到鏈表上相應的結點。一般情況下,快指針的移動步長為慢指針的兩倍

5.2.6.1 中間值問題

找出鏈表的中間元素值并返回。

利用快慢指針,我們把一個鏈表看成一個跑道,假設a的速度是b的兩倍,那么當a跑完全程后,b剛好跑一半,以 此來達到找到中間節點的目的。

如下圖,最開始,slow與fast指針都指向鏈表第一個節點,然后slow每次移動一個指針,fast每次移動兩個指針。

1592233560821

代碼

    private static int getMid(Node node) {// 給一個快指針Node fast = node;// 給一個慢指針Node slow = node;// 遍歷鏈表,移動指針while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}return slow.item;}

5.2.6.2 單鏈表有環問題

單鏈表有環問題和循環鏈表是兩碼事。

循環鏈表是我們為了解決某個問題而給出的解決方案。

而單鏈表有環問題則是我們在使用鏈表的過程中失誤操作鎖帶來的bug。

1592234160973

使用快慢指針的思想,還是把鏈表比作一條跑道,鏈表中有環,那么這條跑道就是一條圓環跑道,在一條圓環跑道 中,兩個人有速度差,那么遲早兩個人會相遇,只要相遇那么就說明有環。

    /*** 判斷當前鏈表是否有環** @param node* @return*/public static boolean isCircle(Node node) {Node slow = node;Node fast = node;// 遍歷while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if(fast == slow) { return true;}}return false;}

5.2.6.3 有環鏈表入口問題

查找有環鏈表中環的入口結點。

當快慢指針相遇時,我們可以判斷到鏈表中有環,這時重新設定一個新指針指向鏈表的起點,且步長與慢指針一樣 為1,則慢指針與“新”指針相遇的地方就是環的入口。

1592234728809

    /*** 獲取有環鏈表的環入口,返回入口值** @param node* @return*/public static int getEnter(Node node) {Node slow = node;Node fast = node;// 定義一個新指針Node temp = null;// 遍歷鏈表while (fast != null && fast.next != null) {fast = fast.next.next;slow = slow.next;if(fast == slow) {// 說明兩個指針相遇了,有環,新指針開始移動temp = node;continue;}if(temp!=null) {// temp不為空,說明新指針已經開始移動了temp = temp.next;// 判斷一下新指針與慢指針是否相等,如相遇,則是環起點if(temp == slow) {return temp.item;}}}return 0;}

5.2.7 約瑟夫環問題

問題描述:

傳說有這樣一個故事,在羅馬人占領喬塔帕特后,39 個猶太人與約瑟夫及他的朋友躲到一個洞中,39個猶太人決 定寧愿死也不要被敵人抓到,于是決定了一個自殺方式,41個人排成一個圓圈,第一個人從1開始報數,依次往 后,如果有人報數到3,那么這個人就必須自殺,然后再由他的下一個人重新從1開始報數,直到所有人都自殺身亡 為止。然而約瑟夫和他的朋友并不想遵從。于是,約瑟夫要他的朋友先假裝遵從,他將朋友與自己安排在第16個與第31個位置,從而逃過了這場死亡游戲 。

問題轉換:

41個人坐一圈,第一個人編號為1,第二個人編號為2,第n個人編號為n。

1.編號為1的人開始從1報數,依次向后,報數為3的那個人退出圈;

2.自退出那個人開始的下一個人再次從1開始報數,以此類推;

3.求出最后退出的那個人的編號。

1591523307920

解題思路:

1.構建含有41個結點的單向循環鏈表,分別存儲1~41的值,分別代表這41個人;

2.使用計數器count,記錄當前報數的值;

3.遍歷鏈表,每循環一次,count++;

4.判斷count的值,如果是3,則從鏈表中刪除這個結點并打印結點的值,把count重置為0;

public class Joseph {public static void main(String[] args) {// 構建循環鏈表Node first = new Node(1, null);// 記錄前一個節點Node pre = first;// 構造一個41節點的循環鏈表for (int i = 2; i <= 41; i++) {// 每一次循環,構建一個NodeNode node = new Node(i, null);pre.next = node;// 記錄當前節點為上一個節點pre = node;if (i == 41) {// 說明到最后了,讓最后一個節點指向第一個節點pre.next = first;}}// 計數器countint count = 0;// 遍歷鏈表,每次循環count++Node n = first;// 記錄上一個節點Node back = null;// 循環鏈表while (n != n.next) {// 報數count++;// 判斷count是否為3,如果是,刪除當前節點if (count == 3) {back.next = n.next;System.out.println("當前死亡的人:" + n.item);// 計數器清零count = 0;// 下一個人繼續n = n.next;} else {// 記錄上一個節點back = n;// 下一個人繼續n = n.next;}}}private static class Node {public int item;public Node next;public Node(int item, Node next) {this.item = item;this.next = next;}}}

5.3 棧

5.3.1 棧概述

棧是一種基于先進后出(FILO)的數據結構,是一種只能在一端進行插入和刪除操作的特殊線性表。它按照先進后出 的原則存儲數據,先進入的數據被壓入棧底(壓棧/入棧),最后的數據在棧頂,需要讀數據的時候從棧頂開始彈出數據(彈棧/出棧)(最后一 個數據被第一個讀出來)。

我們稱數據進入到棧的動作為壓棧,數據從棧中出去的動作為彈棧

1592747976236

5.3.2 棧的實現

api設計

類名Stack
構造方法Stack():創建Stack對象
成員方法1. public boolean isEmpty():判斷棧是否為空,是返回true,否返回false
2. public int size():獲取棧中元素的個數
3. public T pop():彈出棧頂元素
4. public void push(T t):向棧中壓入元素t
成員變量1. private Node head:記錄首結點
2. private int N:當前棧的元素個數
public class Stack {/*** 首結點*/private Node head;/*** 棧中元素個數*/private int n;public Stack() {// 初始化棧head = new Node(null, null);n = 0;}/*** 判斷棧是否為空** @return*/public boolean isEmpty() {return n == 0;}/*** 獲取棧中元素的個數** @return*/public int size() {return n;}/*** 彈棧** @return*/public String pop() {// 記錄第一個元素Node oldFirst = head.next;if (oldFirst == null) {return null;}// 刪除首個元素head.next = head.next.next;// 個數-1n--;return oldFirst.item;}/*** 壓棧** @param t*/public void push(String t) {// 記錄舊的下一個節點Node oldNext = head.next;// 創建新的節點Node node = new Node(t, oldNext);// 個數+1n++;// 新的節點連接到head的下一個節點head.next = node;}private class Node {public String item;public Node next;public Node(String item, Node next) {this.item = item;this.next = next;}}
}class Test6 {public static void main(String[] args) {Stack stack = new Stack();stack.push("a");stack.push("b");stack.push("c");stack.push("d");System.out.println(stack.size());System.out.println(stack.pop());System.out.println(stack.size());System.out.println(stack.isEmpty());stack.pop();stack.pop();stack.pop();System.out.println(stack.isEmpty());System.out.println(stack.size());}
}

5.3.3 括號匹配案例

給定一個字符串,里邊可能包含"()"小括號和其他字符,請編寫程序檢查該字符串的中的小括號是否成對出現。 (左括號數量==右括號數量?)

例如:

“(上海)(長安)”:正確匹配

“上海((長安))”:正確匹配

“上海(長安(北京)(深圳)南京)”:正確匹配

“上海(長安))”:錯誤匹配

“((上海)長安”:錯誤匹配**

分析

  1. 創建一個棧用來存儲左括號
  2. 從左往右遍歷字符串,拿到每一個字符
  3. 判斷該字符是不是左括號,如果是,放入棧中存儲
  4. 判斷該字符是不是右括號,如果不是,繼續下一次循環
  5. 如果該字符是右括號,則從棧中彈出一個元素t;
  6. 判斷元素t是否為null,如果不是null,則證明有對應的左括號,如果是null,則證明沒有對應的左括號
  7. 循環結束后,判斷棧中還有沒有剩余的左括號,如果有,則不匹配,如果沒有,則匹配

代碼

class Test7 {public static void main(String[] args) {// 待匹配括號的字符串String str = "(哈哈(發的(官方)發給我個( 發(發(更好)發(功德符(發順豐的( 跟我說)(阿發) 吧)ad發)不是是)) GV) 發";boolean isMatch = matchBrackets(str);System.out.println("匹配字符串括號結果:" + isMatch);}/*** 判斷str中左右括號是否匹配** @param str 待匹配的字符串* @return*/private static boolean matchBrackets(String str) {// 1. 創建一個棧來存儲左括號Stack stack = new Stack();// 2. 從右往右遍歷字符串,拿到每一個字符for (int i = 0; i < str.length(); i++) {// 拿到每一個字符串String currentChar = str.charAt(i) + "";// 3.判斷該字符串是不是左括號,如果是,則放入棧中if (currentChar.equals("(")) {stack.push(currentChar);// 4. 判斷該字符串是否是右括號,如果是,從棧中彈出一個元素t} else if (currentChar.equals(")")) {String t = stack.pop();// 判斷t是否為null。如果為null說明沒有左括號匹配,否則有左括號匹配if (t == null) {return false;}// 如果不是,繼續下一個循環}}// 循環完畢之后,判斷棧中是否還有剩余的左括號,如果有,說明不匹配,如果沒有,則匹配if (stack.size() == 0) {return true;} else {return false;}}
}

5.4 隊列

隊列是一種基于先進先出(FIFO)的數據結構,是一種只能在一端進行插入,在另一端進行刪除操作的特殊線性表,它 按照先進先出的原則存儲數據,先進入的數據,在讀取數據時先讀被讀出來。

1592750161523

api設計

類名Queue
構造方法Queue():創建Queue對象
成員方法1. public boolean isEmpty():判斷隊列是否為空,是返回true,否返回false
2. public int size():獲取隊列中元素的個數
3. public T dequeue():從隊列中拿出一個元素
4. public void enqueue(T t):往隊列中插入一個元素
成員變量1. private Node head:記錄首結點
2. private int N:當前隊列的元素個數
3. private Node last:記錄最后一個結點

代碼實現

public class Queue {/*** 首結點*/private Node head;/*** 當前隊列的元素個數*/private int n;/*** 記錄最后一個結點*/private Node last;public Queue() {head = new Node(null, null);last = null;n = 0;}/*** 判斷隊列是否為空** @return*/public boolean isEmpty() {return n == 0;}/*** 獲取隊列中元素的個數** @return*/public int size() {return n;}/*** 從隊列中拿出一個元素** @return*/public String dequeue() {if (isEmpty()) {return null;}// 不是空,出列// 獲取當前的第一個元素(對應圖中的1元素)Node oldFirst = head.next;// 讓head結點指向下一個結點(對應圖中的2元素)head.next = head.next.next;// 個數-1n--;if (isEmpty()) {last = null;}return oldFirst.item;}/*** 往隊列中插入一個元素** @param t*/public void enqueue(String t) {// 判斷last是否為nullif (last == null) {// last為空,要插入的元素就是lastlast = new Node(t, null);// 讓首結點指向lasthead.next = last;} else {// 不是第一個元素// 取出舊結點(last)Node oldLast = last;// 創建新的結點給lastlast = new Node(t, null);// 讓舊的last元素指向新的結點oldLast.next = last;}// 個數+1n++;}private class Node {public String item;public Node next;public Node(String item, Node next) {this.item = item;this.next = next;}}
}class Test8 {public static void main(String[] args) {Queue queue = new Queue();queue.enqueue("a");queue.enqueue("b");queue.enqueue("c");queue.enqueue("d");System.out.println(queue.size());System.out.println(queue.dequeue());System.out.println(queue.size());System.out.println(queue.isEmpty());queue.dequeue();queue.dequeue();queue.dequeue();System.out.println(queue.isEmpty());System.out.println(queue.size());}
}

*************************************優雅的分割線 **********************************

分享一波:程序員賺外快-必看的巔峰干貨

如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程

請關注微信公眾號:HB荷包
在這里插入圖片描述
一個能讓你學習技術和賺錢方法的公眾號,持續更新
*************************************優雅的分割線 **********************************
SimpleExecutor

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:
http://www.pswp.cn/news/536937.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/536937.shtml
英文地址,請注明出處:http://en.pswp.cn/news/536937.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于 CODING 的 Spring Boot 持續集成項目

本文作者&#xff1a;CODING 用戶 - 廖石榮 持續集成的概念 持續集成(Continuous integration,簡稱 CI&#xff09;是一種軟件開發實踐&#xff0c;即團隊開發成員經常集成他們的工作&#xff0c;通常每個成員每天至少集成一次&#xff0c;也就意味著每天可能會發生多次集成。每…

lvs mysql 端口_LVS配置及多端口服務配置

一、5、各主機IP地址&#xff1a;主機IP網關Client192.168.86.116RouterF0/0:192.168.x.xFo/1:192.168.xx.xxF0/1DirectorEth0:192.168.86.111/24(DIP)Eth0:1:192.168.86.254/32(VIP)F0/1Real 1Eth0:192.168.86.112/24(DIP)lo:1:192.168.86.254/32(VIP)F0/1Real 2Eth0:192.168.…

Mybatis組成部分

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

Stream流與Lambda表達式(一) 雜談

一、流 轉換為數組、集合 package com.java.design.java8.Stream;import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner;import java.util.A…

一年java工作經驗-面試總結

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

linux mysql python包_03_mysql-python模塊, linux環境下python2,python3的

---恢復內容開始---1、Python2 正常[rootIP ~]#pip install mysql-pythonDEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 wont be maintained after that date. A future version of pip will drop …

我的這套VuePress主題你熟悉吧

最近熬了很多個夜晚, 踩坑無數, 終于寫出了用VuePress驅動的主題. 只需體驗三分鐘&#xff0c;你就會跟我一樣&#xff0c;愛上這款主題. vuepress-theme-indigo-material, 已經發布到npm, 請客官享用~~ 介紹 vuepress-theme-indigo-material 的原主題是hexo-theme-indigo, git…

兩年Java工作經驗應該會些什么技術

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

centos 6 mysql 5.7.13 編譯安裝_Centos 6.5 下面 源碼編譯 安裝 Mysql 5.7.13

安裝軟件依賴包yum -y install gcc gcc-c ncurses ncurses-devel cmake下載軟件包cd /usr/local/srcwget https://downloads.mysql.com/archives/get/file/mysql-5.7.13.tar.gz --no-check-certificate下載 boost 庫&#xff0c;MySQL 5.7.5 開始Boost庫是必需的cd /usr/loca…

LeetCode 237. 刪除鏈表中的節點(Python3)

題目&#xff1a; 請編寫一個函數&#xff0c;使其可以刪除某個鏈表中給定的&#xff08;非末尾&#xff09;節點&#xff0c;你將只被給定要求被刪除的節點。 現有一個鏈表 -- head [4,5,1,9]&#xff0c;它可以表示為: 示例 1: 輸入: head [4,5,1,9], node 5 輸出: [4,1,9…

使用Uniapp隨手記錄知識點

使用uniapp隨手記錄知識點 1 組件內置組件擴展組件 2 vuex狀態管理使用流程mapState 輔助函數gettersMutation 1 組件 內置組件 內置組件內主要包含一些基礎的view button video scroll-view等內置基礎組件&#xff0c;滿足基礎場景 擴展組件 擴展組件是uniapp封裝了一些成…

一年Java經驗應該會些什么

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

mysql查詢各類課程的總學分_基于jsp+mysql的JSP學生選課信息管理系統

運行環境: 最好是java jdk 1.8&#xff0c;我們在這個平臺上運行的。其他版本理論上也可以。IDE環境&#xff1a; Eclipse,Myeclipse,IDEA都可以硬件環境&#xff1a; windows 7/8/10 2G內存以上(推薦4G&#xff0c;4G以上更好)可以實現&#xff1a; 學生&#xff0c;教師角色的…

80端口占用分析

SQL Server 2008 里面的組件——SQL Server Reporting Services (MSSQLSERVER)。是 SQL Server 的日志系統&#xff0c;就是他好端端的突然占用了80端口&#xff0c;而且對于普通人來講&#xff0c;這個組件的作用沒啥用&#xff0c;關掉也是節約資源。 關閉服務 ReportServer …

三年java經驗應該會什么?

*************************************優雅的分割線 ********************************** 分享一波:程序員賺外快-必看的巔峰干貨 如果以上內容對你覺得有用,并想獲取更多的賺錢方式和免費的技術教程 請關注微信公眾號:HB荷包 一個能讓你學習技術和賺錢方法的公眾號,持續更…

python call agilent com_PyVISA通過RS232(USB)與安捷倫34970A通信時出現超時錯誤

這是我第一次嘗試使用Pyvisa&#xff0c;以便使用RS232連接(使用USB端口)與Agilent 34970A進行通信。在這就是發生在我身上的事情&#xff0c;插入基本的第一行&#xff1a;IN: import visaIN: rmvisa.ResourceManager()IN: print rm.list_resources()(uASRL4::INSTR,)IN: inst…

python加法運算符可以用來連接字符串并生成新字符串_中國大學MOOCPython語言入門網課答案...

中國大學MOOCPython語言入門網課答案表達式int(40.5)的值為____________。表達式160.5的值為____________________。python程序只能使用源代碼進行運行&#xff0c;不能打包成可執行文件。python語句list(range(1,10,3))執行結果為___________________。pip命令也支持擴展名為.…

全是滿滿的技術文檔

*************************************話不多說-先上教程 ********************************** 完整躺賺教程(不需任何技術,照做就能賺錢):點擊此處獲取 提取碼&#xff1a;6666 被動收入教程(需要一定的技術,會搭建服務器,會發布項目<教程里面會教你>):點擊此處獲取 提…

JavaScript面試的完美指南(開發者視角)

2019獨角獸企業重金招聘Python工程師標準>>> 摘要&#xff1a; 面試季手冊。 原文&#xff1a;javascript 面試的完美指南(開發者視角)作者&#xff1a;前端小智Fundebug經授權轉載&#xff0c;版權歸原作者所有。 為了說明 JS 面試的復雜性&#xff0c;首先&#x…

電腦上mysql數據庫無法登錄_無法遠程登入MySQL數據庫的幾種解決辦法MySQL綜合 -電腦資料...

方法一&#xff1a;嘗試用MySQL Adminstrator GUI Tool登入MySQL Server&#xff0c;Server卻回復錯誤訊息&#xff1a;Host 60-248-32-13.HINET-IP.hinet.net is not allowed to connect to thisMySQL server這個是因為權限的問題&#xff0c;處理方式如下&#xff1a;shell&g…