基礎資料結構與演算法
阿新 • • 發佈:2021-07-06
我們程式裡的資料儲存方式有幾種?
2種 陣列(順序儲存) 連結串列(鏈式儲存)
資料結構 | 儲存形式 |
佇列、棧 | 陣列、連結串列 |
樹 | 連結串列 |
圖 | 陣列(領接矩陣)、連結串列(領接表) |
雜湊表 | 陣列 |
陣列:連續儲存,隨機訪問、需要一次性分配好、擴容的時間複雜度O(N)
連結串列:不存在擴容問題、刪除的時間複雜度O(1)、較大的儲存空間
資料結構,在工作中都是無形中使用到,但是很少會去手寫一種資料結構。
下面將分別使用陣列和連結串列實現佇列
1 /** 2 * 使用陣列完成環形佇列 3 * 4 * @author jianqang111 5 * @version1.0 6 * @date 2021/7/5 14:20 7 */ 8 public class ArrayQueue { 9 //rear指標,入棧下標的指標 10 int rear = 0; 11 //font指標,出棧小標的指標 12 int font = 0; 13 //判斷是否為滿的標誌 14 boolean flag = false; 15 //底層陣列 16 private int[] array; 17 //陣列長度 18 private int len; 19 20 public ArrayQueue(int len) {21 //初始化陣列 22 array = new int[len]; 23 this.len = len; 24 } 25 26 //出佇列 27 public int pop() { 28 //首先判斷是否為空 29 if (isEmpty()) { 30 throw new RuntimeException("佇列為空"); 31 } 32 int value = array[font]; 33 font = (font + 1) % len;34 //出佇列後,修改flag 35 flag = false; 36 return value; 37 38 } 39 40 //入佇列 41 public int push(int value) { 42 //首先判斷是否為滿 43 if (isFull()) { 44 throw new RuntimeException("佇列為滿"); 45 } 46 array[rear] = value; 47 rear = (rear + 1) % len; 48 //入佇列後,判斷是為滿的,只有入隊操作才有可能導致佇列為滿 49 flag = rear == font; 50 return value; 51 } 52 53 //是否為空 54 public boolean isEmpty() { 55 //環形佇列,font指標等於rear指標即為空,並且flag標誌位false 56 return font == rear && !flag; 57 } 58 59 //是否為滿 60 public boolean isFull() { 61 //環形佇列,rear指標等於font指標即為空,並且flag標誌為true 62 return rear == font && flag; 63 } 64 65 //遍歷佇列 66 public void print() { 67 for (int i : array) { 68 System.out.print(i + ","); 69 } 70 System.out.println(); 71 } 72 73 public static void main(String[] args) { 74 ArrayQueue arrayQueue = new ArrayQueue(3); 75 arrayQueue.push(1); 76 arrayQueue.push(2); 77 arrayQueue.push(3); 78 arrayQueue.pop(); 79 arrayQueue.pop(); 80 arrayQueue.push(4); 81 arrayQueue.push(5); 82 arrayQueue.push(6); 83 arrayQueue.print(); 84 System.out.println(arrayQueue.pop()); 85 } 86 }
1 package com.demo.資料結構; 2 3 /** 4 * 使用連結串列實現佇列 5 * 6 * @author jianqang111 7 * @version 1.0 8 * @date 2021/7/5 15:09 9 */ 10 public class LinkedQueue { 11 //內部類,節點類 12 private class Node { 13 public int value; 14 public Node left; 15 public Node right; 16 17 public Node(int value) { 18 this.value = value; 19 } 20 } 21 22 //佇列最大長度 23 int maxSize; 24 //隊列當前長度 25 int size; 26 //尾節點 27 Node lastNode; 28 //頭節點 29 Node headNode; 30 31 public LinkedQueue(int len) { 32 this.maxSize = len; 33 } 34 35 //入隊方法 36 public void push(int value) { 37 //先判斷佇列是否為滿 38 if (isFull()) { 39 throw new RuntimeException("佇列為滿"); 40 } 41 Node newNode = new Node(value); 42 //如果頭節點或尾節點為空,則當前值為尾節點,頭節點 43 if (lastNode == null || headNode == null) { 44 lastNode = newNode; 45 headNode = newNode; 46 } else { 47 //設定lastNode副本的右節點 48 lastNode.right = newNode; 49 //設定newNode的左節點 50 newNode.left = lastNode; 51 //lastNode指向newNode 52 lastNode = newNode; 53 } 54 size++; 55 } 56 57 //出隊方法 58 public int pop() { 59 //先判斷佇列是否為空 60 if (isEmpty()) { 61 throw new RuntimeException("佇列為空"); 62 } 63 //頭節點的值 64 int value = headNode.value; 65 //頭節點等於頭結點的右節點 66 headNode = headNode.right; 67 //將頭節點的左節點設定為空,方便gc 68 if (headNode != null) { 69 headNode.left = null; 70 } 71 size--; 72 return value; 73 } 74 75 //是否為空 76 public boolean isEmpty() { 77 return size == 0; 78 } 79 80 //是否為滿 81 public boolean isFull() { 82 return size == maxSize; 83 } 84 85 //遍歷列印 86 public void print() { 87 Node head = headNode; 88 while (head != null) { 89 System.out.print(head.value + ","); 90 head = head.right; 91 } 92 System.out.println(); 93 } 94 95 public static void main(String[] args) { 96 LinkedQueue linkedQueue = new LinkedQueue(3); 97 linkedQueue.push(1); 98 linkedQueue.push(2); 99 linkedQueue.push(3); 100 linkedQueue.pop(); 101 linkedQueue.pop(); 102 linkedQueue.push(4); 103 linkedQueue.push(5); 104 linkedQueue.pop(); 105 linkedQueue.push(6); 106 linkedQueue.print(); 107 System.out.println(linkedQueue.headNode); 108 } 109 110 }
mysql的索引設計:
目前mysql的常用版本的預設資料引擎是InnerDB,而InnerDB的索引資料結構是B+樹,為什麼索引的資料結構不選擇陣列,連結串列,二叉排序樹,二叉平衡樹,B樹呢?
我們先實現一下二叉排序樹和二叉平衡樹
1 package com.demo.資料結構; 2 3 /** 4 * 二叉排序樹 5 * 一棵空樹,或者是具有下列性質的二叉樹: 6 * (1)若左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 7 * (2)若右子樹不空,則右子樹上所有結點的值均大於或等於它的根結點的值; 8 * (3)左、右子樹也分別為二叉排序樹; 9 * 10 * @author jianqang111 11 * @version 1.0 12 * @date 2021/7/6 8:59 13 */ 14 public class BtnTree { 15 //內部類,節點類 16 protected class Node { 17 public int value; 18 public Node left; 19 public Node right; 20 21 public Node(int value) { 22 this.value = value; 23 } 24 } 25 26 //根節點 27 public Node root; 28 29 //把值加入到二叉排序樹中 30 public void add(int value) { 31 Node node = new Node(value); 32 if (root == null) { 33 root = node; 34 return; 35 } 36 Node model = root; 37 while (model != null) { 38 //判斷是否比當前節點小,是否走左邊 39 if (value < model.value) { 40 if (model.left == null) { 41 model.left = node; 42 return; 43 } else { 44 model = model.left; 45 } 46 }//判斷是否比當前節點大,是否走右邊 47 else if (value >= model.value) { 48 if (model.right == null) { 49 model.right = node; 50 return; 51 } else { 52 model = model.right; 53 } 54 } 55 } 56 } 57 58 public static void main(String[] args) { 59 BtnTree btnTree = new BtnTree(); 60 btnTree.add(5); 61 btnTree.add(3); 62 btnTree.add(8); 63 btnTree.add(2); 64 btnTree.add(4); 65 btnTree.add(7); 66 btnTree.add(9); 67 btnTree.add(9); 68 System.out.println(); 69 } 70 }
package com.demo.資料結構; /** * 平衡二叉樹 * 任意節點的子樹的高度差都小於等於1 * * @author jianqang111 * @version 1.0 * @date 2021/7/6 9:54 */ public class AvlBtnTree extends BtnTree { //計算樹的高度 public int treeHight(Node node) { if (node == null) { return 0; } return Math.max(treeHight(node.left) + 1, treeHight(node.right) + 1); } //獲取左子樹的高度 public int getLeftHigh() { return treeHight(root.left); } //獲取右子樹的高度 public int getRightHigh() { return treeHight(root.right); } /** * 左旋操作 */ public void leftRotate(Node node) { //1.建立一個新節點,值等於當前節點的值 Node newNode = new Node(node.value); //2.把新節點的左子樹設定成當前節點的左子樹 newNode.left = node.left; //3.把新節點的右子樹設定位當前節點的右子樹的左子樹 newNode.right = node.right.left; //4.把當前節點的值換位右子節點的值 node.value = node.right.value; //5.把當前節點的右子樹,設定為右子樹的右子樹 node.right = node.right.right; //6.把當前節點的左子樹設定為新節點 node.left = newNode; } /** * 右旋操作 * 當左子樹的高度比右子樹大於1的時候就需要右旋 */ public void rightRotate(Node node) { //1.建立一個根節點,值等於當前根節點的值 Node newNode = new Node(node.value); //2.把新節點的右子樹設定成當前節點的右子樹 newNode.right = node.right; //3.把新節點的左子樹設定成當前節點的左子樹的右子樹 newNode.left = node.left.right; //4.把當前節點的值換位左子節點的值 node.value = node.left.value; //5.把當前節點的左子樹設定成左子樹的左子樹 node.left = node.left.left; //6.把當前節點的右子樹設定為新節點 node.right = newNode; } /** * 雙旋轉操作 * 符合右旋轉的條件 * 當前節點的左子樹的右子樹高度,大於當前節點的左子樹的左子樹 */ public void doubleRotate(Node node) { //1.對當前節點的左孩子進行左旋 leftRotate(node.left); //2.再對當前節點進行右旋 rightRotate(node); } //插入值 @Override public void add(int value) { super.add(value); //當左子樹的高度比右子樹大於1的時候就需要右旋,判斷是否需要右旋 if (getLeftHigh() - getRightHigh() > 1) { //判斷是否需要雙旋轉,當前節點的左子樹的右子樹高度大於當前節點的左子樹的左子樹 if (treeHight(root.left.right) > treeHight(root.left.left)) { doubleRotate(root); } else { rightRotate(root); } } //當右子樹的高度比左子樹大於1的時候就需要左旋,判斷是否需要左旋 else if (getRightHigh() - getLeftHigh() > 1) { leftRotate(root); } } public static void main(String[] args) { System.out.println("右旋演示"); AvlBtnTree rightRotateExa = new AvlBtnTree(); rightRotateExa.add(10); rightRotateExa.add(8); rightRotateExa.add(12); rightRotateExa.add(7); rightRotateExa.add(9); rightRotateExa.add(6); System.out.println("左旋演示"); AvlBtnTree leftRotateExa = new AvlBtnTree(); leftRotateExa.add(4); leftRotateExa.add(3); leftRotateExa.add(6); leftRotateExa.add(5); leftRotateExa.add(7); leftRotateExa.add(8); System.out.println("雙旋演示"); AvlBtnTree doubleRotateExa = new AvlBtnTree(); doubleRotateExa.add(10); doubleRotateExa.add(7); doubleRotateExa.add(11); doubleRotateExa.add(6); doubleRotateExa.add(8); doubleRotateExa.add(9); System.out.println("演示結束"); } }
總結一下他們各自的不足
陣列,只適合靜態資料,插入,刪除操作很麻煩
連結串列:二叉排序樹,直接可以用二分查詢思想,但是如果插入的資料是遞增或者是遞減的話,就很容易退化變成單鏈表的情況,效率是O(n),
二叉排序樹:io次數過多,而且容易形成單鏈表.
二叉平衡樹:io次數過多,因為區域性性原理,一次資料量比較大.
B樹:io次數不穩定,io次數取決於樹的高度.
再來看看B+樹的優點
1.單一節點儲存更多的元素,使得查詢的IO次數更少。 2.所有查詢都要查詢到葉子節點,查詢效能穩定。 3.所有葉子節點形成有序連結串列,便於範圍查詢。 4.掃表能力更強,不需要一直去遍歷這棵樹了。最後對InnerDB的索引總結一下:
- mysql的查詢的速度,很大程度上是取決於索引的,官方對索引的解釋是讓mysql高速獲取資料的資料結構,而索引的底層是B+樹,B+樹的好處是,B+樹是矮胖的多叉樹只有葉子節點會儲存資料,非葉子節點儲存鍵值,當mysql從磁碟中讀取資料時,它會讀取一頁資料,預設是16kb,一個葉子節點儲存16kb的資料,節點中的值會經過一個排序的,所有的葉子節點都處在同一層的,葉子節點之間使用雙向指標連線,最底層的葉子節點形成了一個雙向有序連結串列。這樣就可以支援範圍查詢
- 索引欄位的選擇應該唯一性佔比高的欄位,像性別,身份證型別等區分度不高的欄位是不建議生成索引的。
- 索引的型別應該優先考慮組合索引,一個組合索引相當於多個索引的集合,使用組合索引查詢資料的時候應該符合最左字首匹配原則,否則會導致查詢沒有使用到索引導致查詢時間過長。
- 建立組合索引時,應該考慮組合索引的欄位能滿足大部分的查詢語句,這樣就能減少回表次數,因為非聚簇索引的值儲存的是主鍵的值,當鍵值欄位不滿足查詢語句的欄位就會用拿到主鍵的值去聚簇索引拿到其餘的值。
- 在對索引欄位進行篩選的時候,應該避免導致索引失效。像對索引欄位進行函式式操作或者運演算法操作,where 語句中使用null或is not null,like語句中將百分號放在首位,使用not in,not exist等等操作
如果想要了解得更加詳細,可以訪問一下地址
https://blog.csdn.net/qq_35190492/article/details/109257302