什麼是數據結構
簡單來說就是數據元素和定義在數據元素上的一組操作的一個集合。
其中,基於數據的操作,需要保證操作後的數據仍然滿足原有關係。
研究重點:存儲數據間的關係(順序映像、非順序映像)
順序映像:以數據元素之間的物理位置緊鄰來表示關係
非順序映像:藉助指示元素存儲地址的“指針”來表示關係
物理關係:邏輯關係在計算機中的表示
線性表
順序表
操作主要有:增、刪、查、改
優勢:隨機訪問方便(在相同的時間裏訪問線性表中任意一個元素,每個元素位序唯一,類型數組下標)
難點:在操作元素後,維護原本的一一對應的元素關係。
對於 n個 元素線性表,有n+1個合法插入位置 / 有n個合法刪除位置。
實現方式:
存儲元素:數組 elements[] / size 表示數組中元素個數 / 用接口來接收 增,刪,查,改的方法
(接口在某種程度上可以看成是一種 抽象協議 )
代碼思路:
1.設置接口,定義增刪查改方法和重寫toString()方法
2.定義 類LineList 實現接口,類中成員變量:數組elements[] ,元素個數int size ,存儲上限MAX_VALUE
3.在類中重寫接口的方法:
插入:1)判斷插入位置是否合法
2)判斷數組容量,不夠就需要擴容(重點)
3)移動數組,空出插入位置
4)插入元素,元素個數 + 1
5)返回 插入成功
擴容的步驟: 1.先擴容爲原數組的1.5倍
2.判斷擴容後的數組是否大於用戶指定的值
3.再判斷擴容後的數組大小是否越界,如果超過了數據類型的最大值,就會出現符號反轉 ,如int 型 ,2147483647 + 1 = - 2147483648,如果越界,就將類型的最大值賦給數組空間長度。
刪除:1)判斷刪除位置是否合法
2)判空
3)用一個參數接收要刪除的元素,作爲返回值
4)將要刪除的元素的後面所有元素整體往前移動一位,覆蓋掉原來的待刪除元素(System.arraycopyOf(),後面會提)
5)將數組最後一個元素置爲null,因爲最後一個元素已經前移了一位,最後一個應該不存在了
6)元素個數 - 1,返回待刪除元素
修改:1)判斷查找位置是否合法,判斷是否數組爲空
2)找到位序,直接修改即可
訪問:1)判斷訪問位置是否合法,判斷數組是否爲空
2)找到位序,直接返回即可
線性表 順序映像 代碼如下
接口
public interface LinearList { //添加元素 /** * * @param e 帶插入到線性表中的元素 * @param index 待插入元素插入到線性表中的位序 * @return 表示本次插入操作是否成功 */ boolean insert(String e, int index); /** * 將帶插入元素插入到當前線性表中,表尾 * * @param e 待添加元素 * @return 添加操作是否成功 */ boolean insert(String e); //刪除元素 /** * * @param index 要刪除當前線性表中,哪個位序的元素 * @return 待刪除元素的元素值 */ String delete(int index); /** * * @param e 在線性表中,待刪除的元素的元素值和該參數的元素值相同 * @return 當前的刪除操作是否成功 */ boolean delete(String e); //修改 /** * * @param index 指明線性表中待修改的元素 * @param newValue 指明線性表中待修改的元素,修改之後的值 */ void set(int index, String newValue); //查找 /** * * @param e 待查找的元素 * @param fromIndex 從哪個地方開始查找 * @return */ int indexOf(String e, int fromIndex); /** * * @return 當前線性表,所有元素的字符串表示 [1,2,3] [] [1] */ String toString(); }
線性表主體代碼
public class Mylinelist implements LinearList { //設置參數 //數組 private String[] element; //當前數組元素個數 private int size; //線性表存儲上限 private final static int MAX_ARRAY_SIZE = Integer.MAX_VALUE; //設置一個初始值 private final static int DEFAULT_INTI_SIZE = 10; public Mylinelist() { //初始化線性表中,實際存放元素的數組 element = new String[DEFAULT_INTI_SIZE]; } //*********用戶傳入的數組容量********* public Mylinelist(int capacity) { if(capacity < 0 || capacity > MAX_ARRAY_SIZE){ throw new IllegalArgumentException("超出存放範圍"); } //初始化用戶傳入的數組容量大小的數組 element = new String[capacity]; } @Override public boolean insert(String e, int index) { //判斷index 合法性 if(index < 0 || index > size){ throw new IllegalArgumentException("不合法的插入位置"); } //判斷數組容量問題 ensurInsert(size + 1); //插入 element[index] = e; //插入後 size++; return true; } private void ensurInsert(int capacity) { //擴容 if(capacity > element.length){ int oldcapacity = element.length; int newcapacity = oldcapacity + (oldcapacity >> 1); //如果數組超過最大值導致變成負數 if(newcapacity - capacity < 0){ newcapacity = capacity; } //數組移動 element = Arrays.copyOf(element,newcapacity); } } @Override public boolean insert(String e) { return false; } @Override public String delete(int index) { //判斷刪除合法性 if(index < 0 || index > size - 1){ throw new IllegalArgumentException("刪除位置不合法"); } //保留要返回的刪除元素 String oldNum = element[index]; //元素前移 if(index != size - 1){ System.arraycopy(element,index + 1, element, index,size - index - 1); } //最後一個元素設置爲null element[size - 1] = null ; //元素個數少一 size--; //返回刪除元素值 return oldNum; } @Override public boolean delete(String e) { //判空比較 if (element == null) { throw new NullPointerException("無法刪除"); } //遍歷比較符合的第一個元素 for (int i = 0; i < size; i++) { if (Objects.equals(e, element[i])) { int copyNum = size - i - 1; if (copyNum != 0) { System.arraycopy(element, i + 1, element, i - 1 , size - i ); //將最後一個置爲空 element[size] = null; //size-- size--; //刪除成功 return true; } } } //比較不成功 return false; } @Override public void set(int index, String newValue) { //檢查位序 if(index < 0 || index > size){ throw new IllegalArgumentException("修改位序不合法"); } //修改元素值 element[index] = newValue; } @Override public int indexOf(String e, int fromIndex) { for (int i = fromIndex; i < size ; i++) { if(Objects.equals( e, element[i])){ return i; } } return -1; } @Override public String toString() { if(size == 0){ return "[]"; } StringBuilder builder = new StringBuilder(); builder.append("["); for (int i = 0; i < size; i++) { builder.append(element[i]); if(i == size -1){ builder.append("]"); break; } builder.append(","); } return builder.toString(); } }
測試
public class TestList { public static void main(String[] args) { Mylinelist mylinelist = new Mylinelist(); for (int i = 0; i < 10 ; i++) { mylinelist.insert(i + "", i);//" "和 "\t"均算成字符 } System.out.println(mylinelist.toString()); //正常運行 刪除 mylinelist.delete(6);//index是數組下標(0開始) System.out.println(mylinelist.toString()); //正常運行 查找 System.out.println( mylinelist.indexOf("4",2)); //正常運行 修改 mylinelist.set(5,"15"); System.out.println(mylinelist.toString()); } }
其中有幾個方法需要注意:
- 數組元素整體複製移動:System.arraycopy( 原數組 , 要移動的起始下標index,新數組 , 要放入的起始位置 , 複製元素的個數 ) eg.System.arraycopy(elements, index , elements ,index+1,size - index)-----完成同一個數組中的元素整體後移一位
- 數組擴容:Arrays.copyOf( 新數組,擴容數組長度) eg.elements = Arrays.copyOf(elements , size + 1) ---完成原數組的擴容操作
- 可變字符串序列:StringBuilder 類(假如對象 builder ),有一個方法:append----用於不斷添加字符 eg.builder.append("a");builder.append("b"); 最後輸出就是 ab
還有一個方法: deleteCharAt(int index)----用於刪除可變字符串 某一個位置的字符
4.設置數組的最大容量的方法:private final static int MAX_ARRY_SIZE = Integer.MAX_VALUE;
Integer.MAX_VALUE 就是 int 類型的最大值,同理可以設置 Integer.MIN_VALUE , Short.MAX_VALUE...........
鏈表
屬於非順序映像,必須順序訪問,可以實現隨機存取。
非順序映像一定不以元素的物理位置來表示元素之間的關係。
實現方式:節點類Node 中存儲一個節點元素,String item,
一個指向下一個節點的引用 Node next
一個構造方法,初始化 結點元素和指向下一個結點的引用
每個結點類的對象 就可以看成鏈表中的 一個結點
代碼思路:
1.定義接口,插入add(String e , int index)刪除、查找、修改
2.定義成員變量:定義一個指向第一個節點的引用 Node first ; 一個指向最後一個節點的引用 Node last ;鏈表中元素個數 size
3.實現接口:
插入:1)判斷插入位置的合法性 rangeCheck(index)
2)找到指定位序的前一個節點( preNode方法):先設定一個Node node來接收first引用,然後遍歷,不斷將node = node.next ,直到插入位序的前一個節點,此時 就將這個node給到Node pre,即Node pre = preNode( index ),此時,pre引用就表示了插入位序的前一個節點引用。
3)插入元素(Link(Node pre,String e) 方法):判斷pre ==null,則將插入的節點給到 first ,如果size ==0,則將last =first,如果鏈表不空的話,就
Node node = pre.next( node 表示插入位序的下一個位序的結點的引用,pre.next代表插入位序的前一個結點的指向,指向了一個實例node,即完成了解開原本鏈表的插入位序結點和前一個結點的鏈接----pre.next)
然後 pre.next = new Node(e , node)( 其中new Node(e,node)表示新建一個Node類的實例,因爲Node中的node表示下一個結點的引用,所以指向了node,也就是之前pre.next所指向的結點,即原鏈表的插入位序所在的結點)
照這個思路,單鏈表的插入操作就非常好理解了。
刪除:1)判斷刪除鏈表是否合法 [ 0--size -1]
2)找到刪除結點的前驅 Node pre =preNode( index )
3)判斷刪除的是否是表頭結點,是表頭,則first = first.next,判斷是否只有一個節點,如果刪除了first
4 )非表頭時的刪除操作 先找個值來接收待刪除節點int oldValue = cur.item
Node cur = pre.next; pre.next = cur.next ; if(pre.next == null){last = pre}
查找:遍歷鏈表就可以了
修改:遍歷找到當前節點直接修改item。
代碼實現如下
public class MyLinkedList implements LinearList { //1. 第一個(節點)的引用 private Node first; //2. 定義指向當前鏈表中最後一個節點的引用 private Node last; //3. 當前鏈表中包含的元素個數 private int size; //定義無參構造方法 public MyLinkedList() { } @Override public boolean add(String e, int index) { //1. index 插入的位序是否合法 [0,size] rangeCheckForAdd(index); //2.找到指定位序的前一個節點 Node pre = preNode(index); //3.向鏈表中插入帶插入元素 link(pre, e); return true; } private void link(Node pre, String e) { if(pre == null) { // 要在當前鏈表中的第一個位置插入元素 first = new Node(e, first); if(size == 0) { //在插入當前元素之前,鏈表爲空 //此時如果插入這個節點,那麼這個節點既是當前鏈表中的第一個節點又是當前鏈表中的最後一個節點 last = first; } } else { //當指定的插入位序,不是第一個位序的時候 有前驅 //插入位序,的下一個位序的結點的引用 Node node = pre.next; //構造待插入元素的結點,並且,該節點插入到鏈表 pre.next = new Node(e, node); if(node == null) { last = pre.next; } } size++; } /* 找到指定位序index,指明的前一個位序的結點的引用 */ private Node preNode(int index) { if(index == 0) { // 當前要插入的元素,是整個線性表中第一個元素的位置 return null; } //對於指定位序的元素,查找其前驅 //找前驅 Node node = first; for(int i = 1; i < index; i++) { node = node.next; } return node; } private void rangeCheckForAdd(int index) { if(index < 0 || index > size) { throw new ArrayIndexOutOfBoundsException("Index: " + index + ", " + "Size: " + size); } } //其實質實現的是尾插法 @Override public boolean add(String e) { //找帶插入元素的前驅 last link(last, e); return true; } //實現頭插法 public boolean addHead(String e) { // link(null, e); return true; } @Override public String remove(int index) { // 判斷刪除的合法位序 [0, size - 1] rangeCheck(index); //找到待刪除節點的前驅 Node pre = preNode(index); String oldValue = unlink(pre); return oldValue; } private String unlink(Node pre) { //當pre爲null的時候,只有一種情況,就是在當前線性表不爲空的情況下,刪除的是表頭結點 String oldValue = null; if(pre == null) { //刪除表頭元素 oldValue = first.item; first = first.next; if(first == null) { //在刪除表頭結點之前,線性表中只包含一個表頭結點 last = null; } } else { //當要刪除的結點不是表頭結點的時候 //表示待刪除節點的引用 Node cur = pre.next; //待刪除節點的元素值 oldValue = cur.item; //從鏈表中刪除,待刪除結點 pre.next = cur.next; if(pre.next == null) { //刪除的是表尾節點,因此修改last的值 last = pre; } } size--; return oldValue; } private void rangeCheck(int index) { if(index < 0 || index >= size) { throw new ArrayIndexOutOfBoundsException("Index: " + index + ", Size: " + size); } } @Override public boolean remove(String e) { if (size == 0) { throw new ArrayIndexOutOfBoundsException(""); } Node pre = null; for(Node node = first; node != null; node = node.next) { if(Objects.equals(e, node.item)) { unlink(pre); } pre = node; } return true; } @Override public void set(int index, String newValue) { //1. 檢查合法位序 rangeCheck(index); //2. 找到index指明的位序的,節點 Node node = preNode(index + 1); //3.修改 node.item = newValue; } @Override public int indexOf(String e) { // 當鏈表爲空的時候,根據需求,也可以拋出一個自定義的編譯時異常 // if(size == 0) { // throw new ArrayIndexOutOfBoundsException(""); // } int index = 0; for(Node node = first; node != null; node = node.next) { if(Objects.equals(e, node.item)) { return index; } index++; } return -1; } private class Node { //表示存儲的數據元素的值 String item; //指向下一個節點的引用 Node next; public Node(String item, Node next) { this.item = item; this.next = next; } } /* 讓使用者獲取,當前線性表中的元素個數 */ public int size() { return size; } /* 判斷當前線性表是否爲空表 */ public boolean isEmpty() { return size == 0; } @Override public String toString() { if(size == 0) { return "[]"; } StringBuilder builder = new StringBuilder("["); //[ 1,2, good taste 消除了對邊界條件的特殊判斷 for(Node node = first; node.next != null; node = node.next) { builder.append(node.item); builder.append(','); } builder.append(last.item); builder.append(']'); return builder.toString(); } public String getHeadValue() { return first.item; } }
測試類:
public class TestList { public static void main(String[] args) { // MySquentialList mySquentialList = new MySquentialList(); // // //測試我的添加功能 // testAdd(mySquentialList); // // //測試刪除功能 //// mySquentialList.remove("1"); //// System.out.println(mySquentialList.toString()); // // testRemove(mySquentialList); MyLinkedList myLinkedList = new MyLinkedList(); testLinkedAdd(myLinkedList); System.out.println(myLinkedList); insertLinkedAt(myLinkedList,"999", 1); System.out.println(myLinkedList); insertLinkedHead(myLinkedList, "2333"); System.out.println(myLinkedList); myLinkedList.remove(4); System.out.println(myLinkedList); } private static void testLinkedAdd(MyLinkedList myLinkedList) { for(int i = 100; i < 110; i++) {//循環遍歷插入 myLinkedList.add(i+""); } } public static void testRemove(MySquentialList mySquentialList) { mySquentialList.add(null); System.out.println(mySquentialList.toString()); // mySquentialList.remove(null); // System.out.println(mySquentialList.toString()); mySquentialList.remove("1"); System.out.println(mySquentialList.toString()); } public static void testAdd(MySquentialList list) { for (int i = 0; i < 20; i++) { list.add(i + "", i); } System.out.println(list.toString()); } public static void insertLinkedAt(MyLinkedList list, String e, int index) { list.add(e, index); } public static void insertLinkedHead(MyLinkedList list, String e) { list.addHead(e); } }
操作受限的線性表(棧和隊列)
棧:
特點:先進後出,合法插入位置和刪除位置只有一個-----棧頂
可以類比JVM中的內存模型中的方法棧:棧幀(在調用方法時,產生棧幀,調用結束,銷燬棧幀,後調用的方法先結束)
實現棧的代碼思路
1.定義接口:入棧 boolean push(String s) ; 出棧String pop(); 訪問棧頂元素 String peek();
2.定義成員變量:elements[] ,存放棧內元素 , int size 表示棧內元素個數 ;int top 表示棧頂指針,初始值爲0; 存儲上限Integer.MAX_VALUE ; 構造方法:可以讓用戶指定棧空間大小
3.實現接口
入棧:1)保證存儲容量足夠讓元素入棧 ensureCapacity( mincapacity )如果不夠就需要擴容
2)入棧:elements[ top ] = e ; top++;
其中重點是擴容。
出棧:1 ) top -- ; elements[ top ] = null ;
訪問棧頂元素:1) 判斷棧空
2) return elements[ top - 1]
4.也可以通過繼承順序映像/多態來完成入棧和出棧的操作(可以自己去實現)
代碼如下
public class HomeWork_Stack implements Stack_1 { //成員變量 private String[] elements;//存放棧中元素值 private int top;//棧頂指針 private final static int MAX_VALUE =Integer.MAX_VALUE; //構造方法 HomeWork_Stack() { } HomeWork_Stack(int capacity) {//讓用戶指定 創建的初始數組大小 if(capacity < 0){ throw new ArrayIndexOutOfBoundsException("輸入數值非法"); } elements = new String[capacity]; } @Override public boolean push(String s) { //入棧必定合法 //確保有足夠的容量讓元素入棧 ensureCapacity(top + 1); //元素入棧 add( s ); return true;//入棧成功 } private void add(String s) {//在棧頂添加元素 elements[top] = s; top++; } private void ensureCapacity(int minCapacity) { if(minCapacity - elements.length > 0 ){//如果輸入的容量 大於 原數組的容量,擴容 grow(minCapacity); } } private void grow(int minCapacity) { int oldcapacity = elements.length;//先接收原數組容量 int newcCapacity = oldcapacity +(oldcapacity >> 1); if(newcCapacity < minCapacity){ newcCapacity = minCapacity;//如果擴容後的數組容量不到輸入時的容量,則按輸入的容量來擴容 } if(newcCapacity - MAX_VALUE > 0){// >0 表示已經超出最大範圍 newcCapacity = hugeCapacity(minCapacity); } } private int hugeCapacity(int minCapacity) { if(minCapacity < 0){ throw new ArrayIndexOutOfBoundsException("容量超出範圍"); } return MAX_VALUE;//如果擴容後超出範圍,只能給到範圍最大值 } @Override public String pop() {//出棧 //先判斷棧是否爲空 if( top == 0 ){//數組元素可以是null,所以不能判斷爲 elements[top] == null throw new ArrayIndexOutOfBoundsException("數組爲空,無法出棧"); } //出棧,先保留要出棧元素 String oldValue = elements[top - 1]; elements[top - 1] = null; //指針下移 top--; return oldValue; } @Override public String peek() { //判斷棧內是否有元素可訪問 if(top == 0){ throw new NullPointerException("棧內無元素訪問"); } //返回 訪問棧頂元素 return elements[top - 1]; } public String toString(){ if(top == 0 ){ return "[]"; } StringBuilder builder = new StringBuilder(); builder.append("["); for (int i = 0; i < top - 1; i++) { builder.append(elements[i]); builder.append(","); } builder.append(elements[top - 1]); builder.append("]"); return builder.toString(); } }
測試:
public class TestStack { public static void main(String[] args) { HomeWork_Stack homeWork_stack = new HomeWork_Stack(10);//初始化數組空間 //添加元素 功能正常 homeWork_stack.push("1"); homeWork_stack.push("3"); homeWork_stack.push("5"); homeWork_stack.push("7"); System.out.println("入棧後棧空間" + homeWork_stack.toString()); //刪除元素 功能正常 homeWork_stack.pop(); System.out.println("出棧後棧空間" + homeWork_stack.toString()); //查找元素 功能正常 ,沒有刪除元素 System.out.println(homeWork_stack.peek()); System.out.println(homeWork_stack.toString()); } }
隊列
特點:添加元素在隊尾,刪除元素在隊頭。
用處:如樹的廣度優先遍歷,需要藉助隊列模型;棧在非遞歸的深度優先遍歷中,也需要藉助隊列模型
非順序印象的實現思路:
1)定義接口:添加元素, boolean Queue(String e) 尾插法;String deQueue() 刪除隊頭;String peek() 訪問隊頭元素
String peek(){ if isEmpty(return){return null} return first.item; }
2)代碼複用,繼承接口和鏈表,調用其尾插法
順序印象的代碼實現
循環隊列
實現步驟:1)定義接口 : 入隊,出隊,查找隊頭元素
2)定義成員變量:1.實際存放隊列元素的數組 elements[] ; 兩個指針 front / rear ,尾指針的位置移動: rear = (rear+1) % 數組長度 ; 定義隊列中元素個數 size ( size =(rear - front+elements.length)% elements.length ) ; 常量:最大範圍,初始數組容量。
3)定義構造方法,接收用戶傳來的參數,可以自定義隊列大小。
4)入隊:ensureCapacity 確保容量,不夠就要擴容,循環隊列的擴容標誌爲