No.1 不要小瞧數組
所謂數組,是有序的元素序列。若將有限個類型相同的變量的集合命名,那麼這個名稱爲數組名。組成數組的各個變量稱爲數組的分量,也稱爲數組的元素,有時也稱爲下標變量。用於區分數組的各個元素的數字編號稱爲下標。數組是在程序設計中,爲了處理方便, 把具有相同類型的若干元素按無序的形式組織起來的一種形式。這些無序排列的同類數據元素的集合稱爲數組。
1.1 代碼實現
public class Array<E> {
// 泛型數組
private E[] data;
// 存儲元素數量
private int size;
// 構造函數
public Array() {
this(10);
}
//構造函數
public Array(int capacity) {
data = (E[]) new Object[capacity];
size = 0;
}
// 獲取數組存放元素的個數
public int getSize() {
return size;
}
// 獲取數組容量
public int getCapacity() {
return data.length;
}
// 判斷數組是否爲空
public boolean isEmpty() {
return size == 0;
}
// 向數組頭部插入元素
public void addFirst(E elem) {
for (int i = size; i >= 0; i--) {
data[i + 1] = data[i];
}
data[0] = elem;
size++;
}
// 向數組末尾添加元素
public void addLast(E elem) {
if (size == data.length) {
resize(data.length * 2);
}
data[size++] = elem;
}
// 向指定位置插入元素
public void add(int index, E elem) {
if (size == data.length) {
resize(data.length * 2);
}
if (index < 0 || index > size) {
throw new IllegalArgumentException("Illegal insertion position");
}
for (int i = size - 1; i > index - 1; i--) {
data[i + 1] = data[i];
}
data[index] = elem;
size++;
}
// 獲取索引對應的元素
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Array Index Overflow");
}
return data[index];
}
// 獲取第一個元素
public E getFirst() {
return get(0);
}
// 獲取最後一個元素
public E getLast() {
return get(size - 1);
}
// 修改索引對應的元素
public void set(int index, E elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Illegal insertion position");
}
data[index] = elem;
}
// 判斷是否存在該元素
public boolean contains(E elem) {
for (int i = size; i > 0; i--) {
if (data[i].equals(elem)) {
return true;
}
}
return false;
}
// 查找元素對應的索引
public int find(E elem) {
for (int i = 0; i < size; i++) {
if (data[i].equals(elem)) {
return i;
}
}
return -1;
}
// 刪除指定索引的元素
public E remove(int index) {
E ret = data[index];
for (int i = index; i < size; i++) {
data[i] = data[i + 1];
}
size--;
data[size] = null;
if (size == data.length / 2 && data.length / 2 != 0) {
resize(data.length / 2);
}
return ret;
}
// 從頭部刪除元素
public E removeFirst() {
return remove(0);
}
// 從尾部刪除元素
public E removeLast() {
return remove(size - 1);
}
// 刪除指定元素
public void removeElem(E elem) {
int index = find(elem);
if (index != -1) {
remove(index);
}
}
}
1.2 動態申請數組空間
private void resize(int newCapacity){
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < data.length; i++) {
newData[i] = data[i];
}
data = newData;
}
No.2 鏈表
鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。鏈表由一系列結點(鏈表中每一個元素稱爲結點)組成,結點可以在運行時動態生成。每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域。 相比於線性表順序結構,操作複雜。由於不必須按順序存儲,鏈表在插入的時候可以達到O(1)的複雜度,比另一種線性表順序錶快得多,但是查找一個節點或者訪問特定編號的節點則需要O(n)的時間,而線性表和順序表相應的時間複雜度分別是O(logn)和O(1)。
使用鏈表結構可以克服數組鏈表需要預先知道數據大小的缺點,鏈表結構可以充分利用計算機內存空間,實現靈活的內存動態管理。但是鏈表失去了數組隨機讀取的優點,同時鏈表由於增加了結點的指針域,空間開銷比較大。鏈表最明顯的好處就是,常規數組排列關聯項目的方式可能不同於這些數據項目在記憶體或磁盤上順序,數據的存取往往要在不同的排列順序中轉換。鏈表允許插入和移除表上任意位置上的節點,但是不允許隨機存取。鏈表有很多種不同的類型:單向鏈表,雙向鏈表以及循環鏈表。鏈表可以在多種編程語言中實現。像Lisp和Scheme這樣的語言的內建數據類型中就包含了鏈表的存取和操作。程序語言或面嚮對象語言,如C,C++和Java依靠易變工具來生成鏈表。
2.1 代碼實現
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node() {
this(null, null);
}
public Node(E e) {
this(e, null);
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
@Override
public String toString() {
return e.toString();
}
}
// 虛擬頭結點
private Node dummyHead;
// 節點數量
private int size;
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
// 獲取節點數量
public int getSize() {
return size;
}
// 是否爲空
public boolean isEmpty() {
return size == 0;
}
// 頭插法添加節點
public void addFirst(E e) {
add(0, e);
}
// 插入節點
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
prev.next = new Node(e, prev.next);
size++;
}
// 尾插法添加節點
public void addLast(E e) {
add(size, e);
}
// 查看頭結點的元素
public E getFirst() {
return get(0);
}
// 查看節點元素
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
// 查看尾節點的元素
public E getLast() {
return get(size - 1);
}
// 修改節點的元素
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
// 刪除頭結點
public E removeFirst() {
return remove(0);
}
// 刪除節點
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Remove failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node delNode = prev.next;
prev.next = delNode.next;
E ret = delNode.e;
delNode = null;
size--;
return ret;
}
// 刪除尾節點
public E removeLast() {
return remove(size - 1);
}
// 是否存在元素
public boolean contains(E e) {
for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
}
2.2 移除鏈表元素
思路:首先通過while循環找到那個節點,先對其判空,然後將該節點的前一節點直接指向該節點的下一節點。
public class Solution {
public ListNode removeElements(ListNode head, int val) {
while (head != null && head.val == val) {
head = head.next;
}
if (head == null) {
return null;
}
ListNode listNode = head;
while (listNode.next != null) {
if (listNode.next.val == val) {
listNode.next = listNode.next.next;
} else {
listNode = listNode.next;
}
}
return head;
}
}
2.3 反轉鏈表
思路:循環將後面的節點的下一節點指向前一節點。
public class Solution {
public ListNode reverseList(ListNode head) {
// 定義前一節點
ListNode prev = null;
// 定義當前節點
ListNode cur = head;
while (cur != null) {
// 創建當前節點的下一節點
ListNode next = cur.next;
// 當前節點的下一節點賦值爲前一節點
cur.next = prev;
// 前一節點賦值爲當前節點
prev = cur;
// 當前節點賦值爲下一節點
cur = next;
}
return prev;
}
}
2.4 刪除鏈表中的節點
思路:將下一個節點中的值賦值給當前節點,刪除下一節點即可。
class Solution {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
}
No.3 棧
棧(stack)又名堆棧,它是一種運算受限的線性表。其限制是僅允許在表的一端進行插入和刪除運算。這一端被稱爲棧頂,相對地,把另一端稱爲棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成爲新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成爲新的棧頂元素。
3.1 代碼實現
3.1.1 接口
public interface Stack<E> {
// 獲取棧內元素數量
int getSize();
// 是否爲空棧
boolean isEmpty();
// 壓棧
void push(E e);
// 彈棧
E pop();
// 獲取棧頂元素
E peek();
}
3.1.2 數組實現
public class ArrayStack<E> implements Stack<E> {
Array<E> array;
public ArrayStack(){
array = new Array<>();
}
public ArrayStack(int capacity){
array = new Array<>(capacity);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@Override
public void push(E e) {
array.addLast(e);
}
@Override
public E pop() {
return array.removeLast();
}
@Override
public E peek() {
return array.getLast();
}
}
3.1.3 鏈表實現
public class LinkedListStack<E> implements Stack<E> {
LinkedList<E> linkedList;
public LinkedListStack() {
linkedList = new LinkedList<>();
}
@Override
public int getSize() {
return linkedList.getSize();
}
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public void push(E e) {
linkedList.addLast(e);
}
@Override
public E pop() {
return linkedList.removeLast();
}
@Override
public E peek() {
return linkedList.getLast();
}
}
3.2 括號匹配
思路:判斷是否(、[、{ 中的任意一個,如果是,則壓棧,判斷是否是 )、]、} 中的任意一個,如果是,則彈棧並和對應的相匹配,如果匹配失敗,返回 false,如果匹配成功,繼續執行,直到棧爲空
import java.util.Stack;
public class Solution {
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) {
return false;
}
char topChar = stack.pop();
if (c == ')' && topChar != '(') {
return false;
}
if (c == ']' && topChar != '[') {
return false;
}
if (c == '}' && topChar != '{') {
return false;
}
}
}
return stack.isEmpty();
}
}
No.4 隊列
隊列是一種特殊的線性表,特殊之處在於它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱爲隊尾,進行刪除操作的端稱爲隊頭。
4.1 代碼實現
4.1.1 接口
public interface Queue<E> {
// 獲取隊列元素數量
int getSize();
// 是否爲空隊列
boolean isEmpty();
// 入隊
void enqueue(E e);
// 出隊
E dequeue();
// 獲取隊首元素
E getFront();
}
4.1.2 數組實現
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue(){
array = new Array<>();
}
public ArrayQueue(int capacity) {
array = new Array<>(capacity);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public void enqueue(E e) {
array.addLast(e);
}
@Override
public E dequeue() {
return array.removeFirst();
}
@Override
public E getFront() {
return array.getFirst();
}
}
4.1.2 鏈表實現
public class LinkedListQueue<E> implements Queue<E> {
private LinkedList<E> linkedList;
public LinkedListQueue(){
linkedList = new LinkedList<>();
}
@Override
public int getSize() {
return linkedList.getSize();
}
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public void enqueue(E e) {
linkedList.addLast(e);
}
@Override
public E dequeue() {
return linkedList.removeFirst();
}
@Override
public E getFront() {
return linkedList.getFirst();
}
}
4.2 循環隊列
思路:捨棄一個空間,用來判斷是否爲空,即尾指針加1模隊列長度和頭指針相等;
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front, tail;
public LoopQueue() {
this(10);
}
public LoopQueue(int capacity) {
data = (E[]) new Object[capacity + 1];
front = 0;
tail = 0;
}
public int getCapacity() {
return data.length - 1;
}
@Override
public boolean isEmpty() {
return front == tail;
}
@Override
public int getSize() {
return (tail - front + data.length) % data.length;
}
@Override
public void enqueue(E e) {
// 如果尾節點加1模隊列長度和頭結點相等,則擴容
if ((tail + 1) % data.length == front) {
resize(getCapacity() * 2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
}
public E dequeue(){
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}else {
E e = data[front];
front = (front + 1) % data.length;
if (getSize() == getCapacity()/ 4 && getCapacity() / 2 != 0){
resize(getCapacity() / 2);
}
return e;
}
}
@Override
public E getFront() {
if (isEmpty()){
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
}
return data[front];
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity + 1];
for (int i = 0; i < getCapacity(); i++) {
newData[i] = data[(i + front) % data.length];
}
data = newData;
front = 0;
tail = getSize();
}
}
No.5 二叉搜索樹
二叉查找樹(Binary Search Tree),(又:二叉搜索樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別爲二叉排序樹。
5.1 代碼實現
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class BST<E extends Comparable<E>> {
private class Node {
public E e;
public Node left, right;
public Node(E e) {
this.e = e;
this.left = null;
this.right = null;
}
private Node root;
private int size;
}
private Node root;
private int size;
public BST() {
root = null;
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
// 向二分搜索樹添加新的元素e
public void add(E e) {
root = addPlus(root, e);
}
// 向以node爲根的二分搜索樹中插入元素e(遞歸)
private void add(Node node, E e) {
// 如果e==node.e, 則返回
if (e.equals(node.e)) {
return;
}
// 如果e<node.e 並且node沒有左孩子,創建節點作爲根節點的左孩子
else if (e.compareTo(node.e) < 0 && node.left == null) {
node.left = new Node(e);
size++;
return;
}
// 如果e>node.e,並且node沒有右孩子,創建節點作爲根節點的右孩子
else if (e.compareTo(node.e) > 0 && node.right == null) {
node.right = new Node(e);
size++;
return;
}
// 如果e<node.e, 將node.left作爲根節點
if (e.compareTo(node.e) < 0) {
add(node.left, e);
} else {
// 如果e>=node.e,將node.right作爲根節點
add(node.right, e);
}
}
// 返回插入新節點後二分搜索樹的根
private Node addPlus(Node node, E e) {
// 如果根節點爲空,創建並返回該節點
if (node == null) {
size++;
return new Node(e);
}
// 如果e<node.e,調用add經node.left作爲根節點
if (e.compareTo(node.e) < 0) {
node.left = addPlus(node.left, e);
}
// 如果e>=node.e,調用add經node.left作爲根節點
else {
node.right = addPlus(node.right, e);
}
return node;
}
// 從二分搜索樹中刪除並返回最小值
public E removeMin() {
E ret = min();
removeMin(root);
return ret;
}
// 刪除以node爲根節點的二分搜索樹中的最小節點
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else {
node.left = removeMin(node.left);
return node;
}
}
// 從二分搜索樹中刪除並返回最大值
public E removeMax() {
E ret = max();
removeMax(root);
return ret;
}
// 刪除以node爲根節點的二分搜索樹中的最大節點
private Node removeMax(Node node) {
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
} else {
node.right = removeMax(node.right);
return node;
}
}
public void remove(E e) {
root = remove(root, e);
}
private Node remove(Node node, E e) {
if (node == null) {
return null;
}
if (e.compareTo(node.e) < 0) {
node.left = remove(node.left, e);
return node;
} else if (e.compareTo(node.e) > 0) {
node.right = remove(node.right, e);
return node;
} else {
if (node.left == null){
// 待刪除節點左孩子爲空,將右孩子替換成該節點返回
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else if (node.right == null) {
// 待刪除節點右孩子爲空,將左孩子替換成該節點返回
Node leftNode = node.left;
node.right = null;
size--;
return leftNode;
} else {
// 返回該節點右孩子的最小節點
Node middleNode = min(node.right);
// 將刪除了node.right的二叉搜索樹賦值給middleNode.right
middleNode.right = removeMin(node.right);
// 將node.left賦值給middleNode.left
middleNode.left = node.left;
node.left = node.right = null;
return middleNode;
}
}
}
// 從二分搜索樹中查找並返回最小值
public E min() {
if (isEmpty()) {
throw new IllegalArgumentException("BST is empty!");
}
return min(root).e;
}
// 以node爲根節點從二分搜索樹中查找並返回最小節點
private Node min(Node node) {
if (node.left == null) {
return node;
} else {
return min(node.left);
}
}
// 從二分搜索樹中查找並返回最大值
public E max() {
if (isEmpty()) {
throw new IllegalArgumentException("BST is empty!");
}
return max(root).e;
}
// 以node爲根節點從二分搜索樹中查找並返回最大節點
private Node max(Node node) {
if (node.right == null) {
return node;
} else {
return max(node.right);
}
}
// 查看二分搜搜樹是否包含元素e
public boolean contains(E e) {
return contains(root, e);
}
// 以node爲根的二分搜索樹中是否包含元素e(遞歸)
private boolean contains(Node node, E e) {
if (node == null) {
return false;
}
if (e.compareTo(node.e) == 0) {
return true;
} else if (e.compareTo(node.e) < 0) {
return contains(node.left, e);
} else {
return contains(node.right, e);
}
}
// 二分搜索樹的前序遍歷
public void preOrder() {
preOrder(root);
}
// 以node爲根節點的二分搜索樹前序遍歷(遞歸)
private void preOrder(Node node) {
if (node != null) {
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
}
// 二分搜索樹的中序遍歷
public void inOrder() {
inOrder(root);
}
// 以node爲根節點的二分搜索樹中序遍歷(遞歸)
private void inOrder(Node node) {
if (node != null) {
inOrder(node.left);
System.out.println(node.e);
inOrder(node.right);
}
}
// 二分搜索樹的後序遍歷
public void postOrder() {
postOrder(root);
}
// 以node爲根節點的二分搜索樹後序遍歷(遞歸)
private void postOrder(Node node) {
if (node != null) {
postOrder(node.left);
postOrder(node.right);
System.out.println(node.e);
}
}
}
5.2 深度優先遍歷
思路:由於隊列是先進先出的,所以首先將根節點加入隊列,循環將當前節點的左右節點加入隊列
public void levelOrder() {
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
Node cur = queue.remove();
System.out.println(cur.e);
if (cur.left != null) {
queue.add(cur.left);
}
if (cur.right != null) {
queue.add(cur.right);
}
}
}
}
No.6 集合
計算機科學中,集合是一組可變數量的數據項(也可能是0個)的組合,這些數據項可能共享某些特徵,需要以某種操作方式一起進行操作。一般來講,這些數據項的類型是相同的,或基類相同(若使用的語言支持繼承)。列表(或數組)通常不被認爲是集合,因爲其大小固定,但事實上它常常在實現中作爲某些形式的集合使用。集合的種類包括列表,集,多重集,樹和圖。枚舉類型可以是列表或集。
6.1 代碼實現
6.1.1 接口
public interface Set<E> {
void add(E e);
void remove(E e);
boolean contains(E e);
int getSize();
boolean isEmpty();
}
6.1.2 數組實現
public class ArraySet<E extends Comparable<E>> implements Set<E> {
private Array<E> array;
public ArraySet() {
array = new Array<>();
}
@Override
public void add(E e) {
if (!array.contains(e)) {
array.addLast(e);
}
}
@Override
public void remove(E e) {
array.removeElem(e);
}
@Override
public boolean contains(E e) {
return array.contains(e);
}
@Override
public int getSize() {
return array.getSize();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
}
6.1.2 鏈表實現
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node() {
this(null, null);
}
public Node(E e) {
this(e, null);
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
@Override
public String toString() {
return e.toString();
}
}
// 虛擬頭結點
private Node dummyHead;
// 節點數量
private int size;
// 構造
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
// 獲取節點數量
public int getSize() {
return size;
}
// 是否爲空
public boolean isEmpty() {
return size == 0;
}
// 頭插法添加節點
public void addFirst(E e) {
add(0, e);
}
// 插入節點
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
prev.next = new Node(e, prev.next);
size++;
}
// 尾插法添加節點
public void addLast(E e) {
add(size, e);
}
// 查看頭結點的元素
public E getFirst() {
return get(0);
}
// 查看節點元素
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
// 查看尾節點的元素
public E getLast() {
return get(size - 1);
}
// 修改節點的元素
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed.Ill index.");
}
Node cur = dummyHead;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
// 刪除頭結點
public E removeFirst() {
return remove(0);
}
// 刪除節點
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Remove failed.Ill index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node delNode = prev.next;
prev.next = delNode.next;
E ret = delNode.e;
delNode = null;
size--;
return ret;
}
// 刪除尾節點
public E removeLast() {
return remove(size - 1);
}
// 是否存在元素
public boolean contains(E e) {
for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
Node cur = dummyHead.next;
while (cur != null) {
stringBuilder.append(cur + "->");
cur = cur.next;
}
stringBuilder.append("NULL");
return stringBuilder.toString();
}
}
6.1.3 二叉樹實現
public class BSTSet<E extends Comparable<E>> implements Set<E> {
private BST<E> bst;
public BSTSet() {
bst = new BST<>();
}
@Override
public void add(E e) {
if (!contains(e)) {
bst.add(e);
}
}
@Override
public void remove(E e) {
bst.remove(e);
}
@Override
public boolean contains(E e) {
return bst.contains(e);
}
@Override
public int getSize() {
return bst.getSize();
}
@Override
public boolean isEmpty() {
return false;
}
}
6.2 兩個數組的交集
思路:將第一個數組中的不重複放到集合中,遍歷第二個數組,如果已經存在於集合中,就將它添加到數組中。
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set = new LinkedHashSet<>();
for (int num : nums1) {
set.add(num);
}
ArrayList<Integer> arrayList = new ArrayList<>();
for (int num : nums2) {
if (set.contains(num)) {
arrayList.add(num);
set.remove(num);
}
}
int[] res = new int[arrayList.size()];
for (int i = 0; i < arrayList.size(); i++) {
res[i] = arrayList.get(i);
}
return res;
}
}
6.3 唯一摩爾斯密碼詞
思路:將每個字符對應的密碼詞轉換成字符串,添加到集合中,判斷集合的大小。
class Solution {
public int uniqueMorseRepresentations(String[] words) {
String[] codes = {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."};
Set<String> strings = new LinkedHashSet<>();
for (String word : words) {
StringBuilder res = new StringBuilder();
for (int i = 0; i < word.length(); i++) {
res.append(codes[word.charAt(i) - 'a']);
}
strings.add(res.toString());
}
return strings.size();
}
}
No.7 映射
將鍵映射到值的對象。一個映射不能包含重複的鍵;每個鍵最多隻能映射到一個值。此接口取代 Dictionary類,後者完全是一個抽象類,而不是一個接口。
7.1 代碼實現
7.1.1 接口
public interface Map<K, V> {
void add(K key, V value);
V remove(K key);
boolean contains(K key);
V get(K key);
void set(K key, V newValue);
int getSize();
boolean isEmpty();
}
7.1.2 鏈式實現
import java.util.ArrayList;
public class LinkedListMap<K, V> implements Map<K, V> {
private class Node {
public K key;
public V value;
public Node next;
public Node() {
this(null, null, null);
}
public Node(K key) {
this(key, null, null);
}
public Node(K key, V value) {
this(key, value, null);
}
public Node(K key, V value, Node next) {
this.key = key;
this.value = value;
this.next = next;
}
@Override
public String toString() {
return key.toString() + " : " + value.toString();
}
}
private Node dummyHead;
private int size;
public LinkedListMap() {
dummyHead = new Node();
size = 0;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
private Node getNode(K key) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.key.equals(key))
return cur;
cur = cur.next;
}
return null;
}
@Override
public boolean contains(K key) {
return getNode(key) != null;
}
@Override
public V get(K key) {
Node node = getNode(key);
return node == null ? null : node.value;
}
@Override
public void add(K key, V value) {
Node node = getNode(key);
if (node == null) {
dummyHead.next = new Node(key, value, dummyHead.next);
size++;
} else
node.value = value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(key);
if (node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
@Override
public V remove(K key) {
Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.key.equals(key))
if (prev.next != null) {
Node delNode = prev.next;
prev.next = delNode.next;
size--;
return delNode.value;
}
prev = prev.next;
}
return null;
}
}
7.1.3 二叉樹實現
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import java.util.ArrayList;
public class BSTMap<K extends Comparable<K>, V> implements Map<K, V> {
private class Node {
public K key;
public V value;
public Node left, right;
public Node() {
this(null, null, null, null);
}
public Node(K key) {
this(key, null, null, null);
}
public Node(K key, V value) {
this(key, value, null, null);
}
public Node(K key, V value, Node left) {
this(key, value, left, null);
}
public Node(K key, V value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "Node{" +
"key=" + key +
", value=" + value +
", left=" + left +
", right=" + right +
'}';
}
}
private Node root;
private int size;
public BSTMap() {
root = null;
size = 0;
}
@Override
public void add(K key, V value) {
root = add(root, key, value);
}
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0) {
node.left = add(node.left, key, value);
} else if (key.compareTo(node.key) > 0) {
node.right = add(node.right, key, value);
} else {
node.value = value;
}
return node;
}
private Node min(Node node) {
if (node.left == null) {
return node;
} else {
return min(node.left);
}
}
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else {
node.left = removeMin(node.left);
return node;
}
}
@Override
public V remove(K key) {
Node node = getNode(root, key);
if (node != null){
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key) {
if (node == null) {
return null;
}
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
return node;
} else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
return node;
} else {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
} else if (node.right == null) {
Node leftNode = node.left;
node.left = null;
return leftNode;
} else {
Node middleNode = getNode(node.right, key);
middleNode.right = removeMin(node.right);
middleNode.left = node.left;
node.left = node.right = null;
return middleNode;
}
}
}
@Override
public boolean contains(K key) {
return getNode(root, key) != null;
}
@Override
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
@Override
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null) {
throw new IllegalArgumentException(key + "doesn't exist!");
} else {
node.value = newValue;
}
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
private Node getNode(Node node, K key){
if(node == null)
return null;
if(key.equals(node.key))
return node;
else if(key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
}
7.2 兩個數組的交集 II
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums1){
if (!map.containsKey(num)){
map.put(num, 1);
} else {
map.put(num, map.get(num) + 1);
}
}
ArrayList<Integer> list = new ArrayList<>();
for (int num : nums2) {
if (map.containsKey(num)) {
list.add(num);
map.put(num, map.get(num) - 1);
if (map.get(num) == 0){
map.remove(num);
}
}
}
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
}
No.8 堆
堆是計算機科學中一類特殊的數據結構的統稱。堆通常是一個可以被看做一棵樹的數組對象。堆總是滿足下列性質:
- 堆中某個節點的值總是不大於或不小於其父節點的值;
- 堆總是一棵完全二叉樹。
將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。常見的堆有二叉堆、斐波那契堆等。
堆是線性數據結構,相當於一維數組,有唯一後繼。
堆的定義如下:n個元素的序列{k1,k2,ki,…,kn}當且僅當滿足下關係時,稱之爲堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)
若將和此次序列對應的一維數組(即以一維數組作此序列的存儲結構)看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或完全二叉樹的根)必爲序列中n個元素的最小值(或最大值)。
8.1 代碼實現
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap() {
data = new Array<>();
}
public MaxHeap(int capacity) {
data = new Array<>(capacity);
}
public MaxHeap(E[] arr) {
data = new Array<>(arr);
for (int i = parent(arr.length - 1); i >= 0; i--) {
siftDown(i);
}
}
public int size() {
return data.getSize();
}
public boolean isEmpty() {
return data.isEmpty();
}
// 返回索引的父節點的索引
private int parent(int index) {
if (index == 0) {
throw new IllegalArgumentException("index-0 doesn't have parent.");
} else {
return (index - 1) / 2;
}
}
// 返回索引的左孩子的索引
private int leftChild(int index) {
return index * 2 + 1;
}
// 返回索引的右孩子的索引
private int rightChild(int index) {
return index * 2 + 2;
}
public void add(E e) {
data.addLast(e);
siftUp(data.getSize() - 1);
}
// 上浮
private void siftUp(int k) {
while (k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0) {
data.swap(k, parent(k));
k = parent(k);
}
}
// 返回最大值
public E findMax() {
if (size() == 0) {
throw new IllegalArgumentException("Can not findMax when heap is empty.");
}
return data.get(0);
}
// 取出堆中的最大元素
public E extractMax() {
E ret = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
// 下沉
private void siftDown(int k) {
while (leftChild(k) < data.getSize()) {
int j = leftChild(k); // 在此輪循環中,data[k]和data[j]交換位置
if (j + 1 < data.getSize() &&
data.get(j + 1).compareTo(data.get(j)) > 0)
j++;
// data[j] 是 leftChild 和 rightChild 中的最大值
if (data.get(k).compareTo(data.get(j)) >= 0)
break;
data.swap(k, j);
k = j;
}
}
// 返回堆中的最大元素,並將其替換成元素e
public E replace(E e) {
E ret = findMax();
data.set(0, e);
siftDown(0);
return ret;
}
}
8.2 基於最大堆實現優先隊列
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
private MaxHeap<E> maxHeap;
public PriorityQueue() {
maxHeap = new MaxHeap<>();
}
@Override
public int getSize() {
return maxHeap.size();
}
@Override
public boolean isEmpty() {
return maxHeap.size() == 0;
}
@Override
public void enqueue(E e) {
maxHeap.add(e);
}
@Override
public E dequeue() {
return maxHeap.extractMax();
}
@Override
public E getFront() {
return maxHeap.findMax();
}
}
8.3 前K個高頻元素
思路:首先將元素值作爲鍵,元素的出現的次數作爲值存儲到映射中,然後循環映射,將前k個映射中的元素添加到堆中,如果後面的元素的頻率比已經添加到堆中的元素頻率高,刪除該元素,將這個元素添加到堆中。
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
public class Sokution {
private class Freq implements Comparable<Freq> {
int e, freq;
public Freq(int e, int freq) {
this.e = e;
this.freq = freq;
}
@Override
public int compareTo(Freq another) {
if (this.freq < another.freq) {
return -1;
} else if (this.freq > another.freq) {
return 1;
} else {
return 0;
}
}
}
public List<Integer> topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
PriorityQueue<Freq> priorityQueue = new PriorityQueue<>();
for (int key : map.keySet()) {
if (priorityQueue.size() < k) {
priorityQueue.add(new Freq(key, map.get(key)));
} else if (map.get(key) > priorityQueue.peek().freq){
priorityQueue.remove();
priorityQueue.add(new Freq(key, map.get(key)));
}
}
LinkedList<Integer> list = new LinkedList();
while (!priorityQueue.isEmpty()) {
list.add(priorityQueue.remove().e);
}
return list;
}
}
No.9 線段樹
線段樹是一種二叉搜索樹,與區間樹相似,它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。
使用線段樹可以快速的查找某一個節點在若干條線段中出現的次數,時間複雜度爲O(logN)。而未優化的空間複雜度爲2N,實際應用時一般還要開4N的數組以免越界,因此有時需要離散化讓空間壓縮。
9.1 代碼實現
public class SegmentTree<E> {
private E[] tree;
private E[] data;
private Merger<E> merger;
public SegmentTree(E[] arr, Merger<E> merger) {
this.merger = merger;
data = (E[]) new Object[arr.length];
for (int i = 0; i < arr.length; i++) {
data[i] = arr[i];
}
tree = (E[]) new Object[4 * arr.length];
bulidSegmentTree(0, 0, data.length - 1);
}
// 在treeIndex的位置創建表示區間[l...r]的線段樹
private void bulidSegmentTree(int treeIndex, int l, int r) {
if (l == r) {
tree[treeIndex] = data[l];
return;
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex);
int mid = l + (r - l) / 2;
bulidSegmentTree(leftTreeIndex, l, mid);
bulidSegmentTree(rightTreeIndex, mid + 1, r);
tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
}
public int getSize() {
return data.length;
}
public E get(int index) {
if (index < 0 || index > data.length) {
throw new IllegalArgumentException("Index is illegal.");
}
return data[index];
}
// 計算左孩子的索引
public int leftChild(int index) {
return 2 * index + 1;
}
// 計算右孩子的索引
public int rightChild(int index) {
return 2 * index + 2;
}
// 在tree中查找區間[l...r]
public E query(int queryL, int queryR) {
if (queryL < 0 || queryL >= data.length || queryR < 0 || queryR >= data.length || queryL > queryR) {
throw new IllegalArgumentException("Index is illegal.");
}
return query(0, 0, data.length - 1, queryL, queryR);
}
private E query(int treeIndex, int l, int r, int queryL, int queryR) {
if (l == queryL && r == queryR) {
return tree[treeIndex];
}
int mid = l + (r - l) / 2;
int leftChildIndex = leftChild(treeIndex);
int rightChildIndex = rightChild(treeIndex);
if (queryL >= mid + 1) {
return query(rightChildIndex, mid + 1, r, queryL, queryR);
} else if (queryR <= mid + 1) {
return query(leftChildIndex, l, mid, queryL, queryR);
} else {
E leftResult = query(leftChildIndex, l, mid, queryL, mid);
E rightResult = query(rightChildIndex, mid + 1, r, mid + 1, queryR);
return merger.merge(leftResult, rightResult);
}
}
public void set(int index, E e) {
if (index < 0 || index >= data.length) {
throw new IllegalArgumentException("Index is illegal.");
}
data[index] = e;
set(0, 0, data.length - 1, index, e);
}
private void set(int treeIndex, int l, int r, int index, E e) {
if (l == r) {
data[treeIndex] = e;
return;
}
int mid = l + (r - l) / 2;
int leftChildIndex = leftChild(treeIndex);
int rightChildIndex = rightChild(treeIndex);
if (index >= mid + 1) {
set(rightChildIndex, mid+1, r, index, e);
} else {
set(leftChildIndex, l, mid, index, e);
}
tree[treeIndex] = merger.merge(tree[leftChildIndex], tree[rightChildIndex]);
}
}
9.2 區域和檢索 - 數組不可變
思路:創建一個比傳來的數組多一個空間的數組,循環新數組的值等於新數組上一個索引的值加傳參數組上一個索引值。
public class NumArray {
private int[] sum;
public NumArray(int[] nums) {
sum = new int[nums.length + 1];
sum[0] = 0;
for (int i = 1; i < sum.length; i++) {
sum[i] = sum[i - 1] + nums[i - 1];
}
}
public int sumRange(int i, int j) {
return sum[j + 1] - sum[i];
}
}
9.3 區域和檢索 - 數組可修改
思路:和9.2一樣,只不過增加了更新,更新之後需要從索引開始遍歷修改後面的元素值。
class NumArray {
private int[] sum;
private int[] data;
public NumArray(int[] nums) {
data = new int[nums.length];
for (int i = 0; i < data.length; i++) {
data[i] = nums[i];
}
sum = new int[nums.length + 1];
sum[0] = 0;
for (int i = 1; i < sum.length; i++){
sum[i] = sum[i - 1] + nums[i - 1];
}
}
public void update(int i, int val) {
data[i] = val;
for (int j = i + 1; j < sum.length ; j++) {
sum[j] = sum[j - 1] + data[j - 1];
}
}
public int sumRange(int i ,int j) {
return sum[j + 1] - sum[i];
}
}
No.10 Trie
在計算機科學中,Trie,又稱字典樹、單詞查找樹或鍵樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:利用字符串的公共前綴來減少查詢時間,最大限度地減少無謂的字符串比較,查詢效率比哈希樹高。
10.1 代碼實現
import java.util.HashMap;
import java.util.Map;
public class Trie {
private class Node {
public boolean isWord;
public Map<Character, Node> next;
public Node() {
this(false);
}
public Node(boolean isWord) {
this.isWord = isWord;
next = new HashMap<>();
}
}
private Node root;
private int size;
public Trie() {
root = new Node();
size = 0;
}
public int getSize() {
return size;
}
public void add(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (!cur.isWord) {
cur.isWord = true;
size ++;
}
}
public boolean contains(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return cur.isWord;
}
public boolean isPrefix(String prefix) {
Node cur = root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return true;
}
}
10.2 添加與搜索單詞 - 數據結構設計
思路:將傳來的字符串循環遍歷,添加到trie中,遍歷結束,將最後一個字符設置單詞結束標誌。
import java.util.HashMap;
import java.util.Map;
public class WordDictionary{
private class Node {
public boolean isWord;
public Map<Character, Node> next;
public Node() {
this(false);
}
public Node(boolean isWord) {
this.isWord = isWord;
next = new HashMap<>();
}
}
private Node root;
public WordDictionary() {
root = new Node();
}
public void addWord(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (!cur.isWord) {
cur.isWord = true;
}
}
public boolean search(String word) {
return match(root, word, 0);
}
private boolean match(Node node, String word, int index) {
if (index == word.length()) {
return node.isWord;
}
char c = word.charAt(index);
if (c != '.') {
if (node.next.get(c) == null) {
return false;
} else {
return match(node.next.get(c), word, index + 1);
}
} else {
for (char nextChar : node.next.keySet()) {
if (match(node.next.get(nextChar), word, index + 1)) {
return true;
}
}
return false;
}
}
}
10.3 實現 Trie (前綴樹)
public class Trie {
private class Node {
public boolean isWord;
public Map<Character, Node> next;
public Node() {
this(false);
}
public Node(boolean isWord) {
this.isWord = isWord;
next = new HashMap<>();
}
}
private Node root;
public Trie() {
root = new Node();
}
/** Inserts a word into the trie. */
public void insert(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
cur.next.put(c, new Node());
}
cur = cur.next.get(c);
}
if (!cur.isWord) {
cur.isWord = true;
}
}
public boolean search(String word) {
Node cur = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return cur.isWord;
}
public boolean startsWith(String prefix) {
Node cur = root;
for (int i = 0; i < prefix.length(); i++) {
char c = prefix.charAt(i);
if (cur.next.get(c) == null) {
return false;
}
cur = cur.next.get(c);
}
return true;
}
}
No.11 並查集
並查集,在一些有N個元素的集合應用問題中,我們通常是在開始時讓每個元素構成一個單元素的集合,然後按一定順序將屬於同一組的元素所在的集合合併,其間要反覆查找一個元素在哪個集合中。這一類問題近幾年來反覆出現在信息學的國際國內賽題中,其特點是看似並不複雜,但數據量極大,若用正常的數據結構來描述的話,往往在空間上過大,計算機無法承受;即使在空間上勉強通過,運行的時間複雜度也極高,根本就不可能在比賽規定的運行時間(1~3秒)內計算出試題需要的結果,只能用並查集來描述。
並查集是一種樹型的數據結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。常常在使用中以森林來表示。
11.1 代碼實現
11.1.1 接口
public interface UF {
int getSize();
boolean isConnected(int p, int q);
void unionElements(int p, int q);
}
11.1.2 數組實現
public class UnionFind implements UF {
private int[] id;
public UnionFind(int size) {
id = new int[size];
for (int i = 0; i < size; i++) {
id[i] = i;
}
}
@Override
public int getSize() {
return id.length;
}
// 查看元素p所屬的集合編號
private int find(int p) {
if (p < 0 || p >= id.length) {
throw new IllegalArgumentException("p is out of bound.");
}
return id[p];
}
// 查看元素p和元素q是否所屬相同集合
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
//合併元素p和元素q所屬的集合
public void unionElements(int p, int q) {
int pID = find(p);
int qID = find(q);
if (pID == qID) {
return;
} else {
for (int i = 0; i < id.length; i++) {
if (id[i] == pID) {
id[i] = qID;
}
}
}
}
}
11.1.3 基於孩子指向父親的數據結構實現
public class UnionFind implements UF {
private int[] parent;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
parent[pRoot] = qRoot;
}
}
11.2 size 優化
集合內元素少那個集合掛載在集合元素多的那個集合的根節點上。
public class UnionFind implements UF {
private int[] parent;
private int[] sz;
public UnionFind(int size) {
parent = new int[size];
sz = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
sz[i] = 1;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
if (sz[pRoot] < sz[qRoot]) {
parent[pRoot] = qRoot;
sz[qRoot] += sz[pRoot];
} else {
parent[qRoot] = pRoot;
sz[pRoot] += sz[qRoot];
}
}
}
11.3 rank 優化
集合深度小的掛載在集合深度大的根節點上
public class UnionFind implements UF {
private int[] parent;
private int[] rank;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
if (rank[pRoot] < rank[qRoot]) {
parent[pRoot] = qRoot;
} else if (rank[qRoot] < rank[pRoot]){
parent[qRoot] = pRoot;
} else {
parent[qRoot] = pRoot;
rank[qRoot] += 1;
}
}
}
11.4 路徑壓縮
在查找過程中讓節點的父節點變成爺爺節點,減少深度
public class UnionFind implements UF {
private int[] parent;
private int[] rank;
public UnionFind(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize() {
return parent.length;
}
public int find(int p) {
if (p < 0 || p >= parent.length) {
throw new IllegalArgumentException("p is out of bound.");
}
while (p != parent[p]) {
// 路徑壓縮,將p的父節點指向p的爺爺節點,減少深度
parent[p] = find(parent[p]);
}
return parent[p];
}
@Override
public boolean isConnected(int p, int q) {
return find(p) == find(q);
}
@Override
public void unionElements(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) {
return;
}
if (rank[pRoot] < rank[qRoot]) {
// 如果pRoot的深度小qRoot,就讓qRoot指向pRoot
parent[pRoot] = qRoot;
} else if (rank[qRoot] < rank[pRoot]){
// 如果pRoot的深度大於qRoot,就讓pRoot指向qRoot
parent[qRoot] = pRoot;
} else {
// 如果pRoot和qRoot的深度相同,就讓pRoot指向qRoot,rank加1
parent[qRoot] = pRoot;
rank[qRoot] += 1;
}
}
}
No.12 平衡二叉樹
平衡二叉搜索樹(Self-balancing binary search tree)又被稱爲AVL樹(有別於AVL算法),且具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。平衡二叉樹的常用實現方法有紅黑樹、AVL、替罪羊樹、Treap、伸展樹等。 最小二叉平衡樹的節點總數的公式如下 F(n)=F(n-1)+F(n-2)+1 這個類似於一個遞歸的數列,可以參考Fibonacci(斐波那契)數列,1是根節點,F(n-1)是左子樹的節點數量,F(n-2)是右子樹的節點數量。
12.1 代碼實現
import java.util.ArrayList;
public class AVLTree<K extends Comparable<K>, V> {
private class Node {
public K key;
public V value;
public Node left, right;
public int height;
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
height = 1;
}
}
private Node root;
private int size;
public AVLTree() {
root = null;
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
// 判斷是否是二叉搜索樹
public boolean isBST() {
ArrayList<K> keys = new ArrayList<>();
inOrder(root, keys);
for (int i = 1; i < keys.size(); i++)
if (keys.get(i - 1).compareTo(keys.get(i)) > 0)
return false;
return true;
}
// 判斷是否是平衡二叉樹
public boolean isBalanced() {
return isBalanced(root);
}
// 判斷node爲根的二分搜索樹是否是平衡二叉樹,遞歸算法
private boolean isBalanced(Node node) {
if (node == null)
return true;
int balanceFactor = getBalanceFactor(node);
if (Math.abs(balanceFactor) > 1)
return false;
return isBalanced(node.left) && isBalanced(node.right);
}
private void inOrder(Node node, ArrayList<K> keys) {
if (node == null)
return;
inOrder(node.left, keys);
keys.add(node.key);
inOrder(node.right, keys);
}
private int getHeight(Node node) {
if (node == null)
return 0;
return node.height;
}
// 獲得node的平衡因子
private int getBalanceFactor(Node node) {
if (node == null)
return 0;
return getHeight(node.left) - getHeight(node.right);
}
// 對節點y進行左旋轉操作,返回旋轉後的根節點x
// y x
// / \ / \
// T1 x 向左旋轉(y) y z
// / \ ---------------> / \ / \
// T2 z T1 T2 T3 T4
// / \
// T3 T4
private Node leftRotate(Node y) {
Node x = y.right;
Node T2 = x.left;
// 向左旋轉過程
x.left = y;
y.right = T2;
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
// 對節點y進行向右旋轉操作,返回旋轉後的根節點x
// y x
// / \ / \
// x T4 向右旋轉(y) z y
// / ---------------> / \ / \
// z T1 T2 T3 T4
// /
// T1
private Node rightRotate(Node y) {
Node x = y.left;
Node T3 = x.right;
// 向右旋轉過程
x.right = y;
y.left = T3;
// 更新height
y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1;
x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1;
return x;
}
// 向二分搜索樹中添加新的元素(key, value)
public void add(K key, V value) {
root = add(root, key, value);
}
// 向以node爲根的二分搜索樹中插入元素(key, value),遞歸算法,返回插入新節點後二分搜索樹的根
private Node add(Node node, K key, V value) {
if (node == null) {
size++;
return new Node(key, value);
}
if (key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if (key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else // key.compareTo(node.key) == 0
node.value = value;
// 更新height
node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right));
// 計算平衡因子
int balanceFactor = getBalanceFactor(node);
// 平衡維護
// LL
if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)
return rightRotate(node);
// RR
if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)
return leftRotate(node);
// LR
if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
// 返回以node爲根節點的二分搜索樹中,key所在的節點
private Node getNode(Node node, K key) {
if (node == null)
return null;
if (key.equals(node.key))
return node;
else if (key.compareTo(node.key) < 0)
return getNode(node.left, key);
else // if(key.compareTo(node.key) > 0)
return getNode(node.right, key);
}
public boolean contains(K key) {
return getNode(root, key) != null;
}
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null)
throw new IllegalArgumentException(key + " doesn't exist!");
node.value = newValue;
}
// 返回以node爲根的二分搜索樹的最小值所在的節點
private Node minimum(Node node){
if(node.left == null)
return node;
return minimum(node.left);
}
// 從二分搜索樹中刪除鍵爲key的節點
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
// 向以node爲根的二分搜索樹中刪除元素,遞歸算法
private Node remove(Node node, K key){
if( node == null )
return null;
Node retNode;
if( key.compareTo(node.key) < 0 ){
node.left = remove(node.left , key);
// return node;
retNode = node;
}
else if(key.compareTo(node.key) > 0 ){
node.right = remove(node.right, key);
// return node;
retNode = node;
}
else{ // key.compareTo(node.key) == 0
// 待刪除節點左子樹爲空的情況
if(node.left == null){
Node rightNode = node.right;
node.right = null;
size --;
// return rightNode;
retNode = rightNode;
}
// 待刪除節點右子樹爲空的情況
else if(node.right == null){
Node leftNode = node.left;
node.left = null;
size --;
// return leftNode;
retNode = leftNode;
}
// 待刪除節點左右子樹均不爲空的情況
else{
// 找到比待刪除節點大的最小節點, 即待刪除節點右子樹的最小節點
// 用這個節點頂替待刪除節點的位置
Node successor = minimum(node.right);
//successor.right = removeMin(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
// return successor;
retNode = successor;
}
}
if(retNode == null)
return null;
// 更新height
retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right));
// 計算平衡因子
int balanceFactor = getBalanceFactor(retNode);
// 平衡維護
// LL
if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)
return rightRotate(retNode);
// RR
if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)
return leftRotate(retNode);
// LR
if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {
retNode.left = leftRotate(retNode.left);
return rightRotate(retNode);
}
// RL
if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {
retNode.right = rightRotate(retNode.right);
return leftRotate(retNode);
}
return retNode;
}
}
12.2 左旋轉和右旋轉
左旋轉
- 將根節點的右孩子作爲新根節點。
- 將新根節點的左孩子作爲原根節點的右孩子。
- 將原根節點作爲新根節點的左孩子。
右旋轉
- 將根節點的左孩子作爲新根節點。
- 將新根節點的右孩子作爲原根節點的左孩子。
- 將原根節點作爲新根節點的右孩子。
12.2 保持平衡的原理
添加方法同二分搜索樹相同,添加之後會進行平衡維護,分爲四種情況:
- LL:對該節點進行右旋轉
- RR:對該節點進行左旋轉
- LR:對左孩子進行左旋轉,對該節點進行右旋轉
- RL:對右孩子進行右旋轉,對該節點進行左旋轉
No.13 紅黑樹
紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構,典型的用途是實現關聯數組。
它是在1972年由Rudolf Bayer發明的,當時被稱爲平衡二叉B樹(symmetric binary B-trees)。後來,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改爲如今的“紅黑樹”。
紅黑樹和AVL樹類似,都是在進行插入和刪除操作時通過特定操作保持二叉查找樹的平衡,從而獲得較高的查找性能。
它雖然是複雜的,但它的最壞情況運行時間也是非常良好的,並且在實踐中是高效的: 它可以在O(log n)時間內做查找,插入和刪除,這裏的n 是樹中元素的數目。
13.1 代碼實現
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node{
public K key;
public V value;
public Node left, right;
public boolean color;
public Node(K key, V value) {
this.key = key;
this.value = value;
left = null;
right = null;
color = RED;
}
}
private Node root;
private int size;
public RBTree() {
root = null;
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
// 判斷節點顏色
private boolean isRed(Node node) {
if (node == null) {
return BLACK;
}
return node.color;
}
// node x
// / \ 左旋轉 / \
// T1 x ---------> node T3
// / \ / \
// T2 T3 T1 T2
private Node leftRotate(Node node){
Node x = node.right;
// 左旋轉
node.right = x.left;
x.left = node;
x.color = node.color;
node.color = RED;
return x;
}
// node x
// / \ / \
// x T2 右旋轉 z node
// / \ ---------> / \
// y T1 T1 T2
private Node rightRotate(Node node) {
Node x = node.left;
// 右旋轉
node.left = x.right;
x.right = node;
x.color = node.color;
node.color = RED;
return x;
}
// 顏色翻轉
private void filpColors(Node node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
// 向二分搜索樹添加新的元素
public void add(K key, V value) {
root = add(root, key, value);
root.color = BLACK;
}
private Node add(Node node, K key, V value) {
if (node == null) {
size ++;
return new Node(key, value); // 默認節點顏色爲紅色
}
if (key.compareTo(node.key) < 0)
node.left = add(node.left, key, value);
else if (key.compareTo(node.key) > 0)
node.right = add(node.right, key, value);
else
node.value = value;
// 如果該節點的右孩子是紅色並且左孩子不是紅色,進行左旋轉
if (isRed(node.right) && !isRed(node.left))
node = leftRotate(node);
// 如果該節點的左孩子是紅色並且左孩子的左孩子是紅色,進行右旋轉
if (isRed(node.left) && isRed(node.left.left))
node = rightRotate(node);
// 如果該節點的左孩子和右孩子都是紅色,顏色翻轉
if (isRed(node.left) && isRed(node.right))
filpColors(node);
return node;
}
private Node getNode(Node node, K key) {
if (node==null)
return null;
if (key.equals(node.key))
return node;
else if (key.compareTo(node.key) < 0)
return getNode(node.left, key);
else
return getNode(node.right, key);
}
public boolean contains(K key) {
return getNode(root, key) == null;
}
public V get(K key) {
Node node = getNode(root, key);
return node == null ? null : node.value;
}
public void set(K key, V newValue) {
Node node = getNode(root, key);
if (node == null)
throw new IllegalArgumentException(key + "doesn't exist.");
node.value = newValue;
}
private Node minimum(Node node) {
if (node.left == null)
return null;
return minimum(node.left);
}
public V remove(K key) {
Node node = getNode(root, key);
if (node != null) {
root = remove(root, key);
return node.value;
}
return null;
}
private Node remove(Node node, K key) {
if (node == null)
return null;
if (key.compareTo(node.key) < 0) {
node.left = remove(node.left, key);
return node;
}
else if (key.compareTo(node.key) > 0) {
node.right = remove(node.right, key);
return node;
}
else {
// 待刪除節點左子樹爲空,
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size --;
return rightNode;
}
// 待刪除節點右子樹爲空
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size --;
return leftNode;
}
// 待刪除節點左右子樹均不爲空,找到比待刪除節點大的最小節點,將這個節點替換待刪除節點的位置
Node successor = minimum(node.right);
successor.right = remove(node.right, successor.key);
successor.left = node.left;
node.left = node.right = null;
return successor;
}
}
}
13.2 紅黑樹添加元素
- 如果添加的元素比根節點還要大,我們直接對其進行顏色翻轉。
- 如果添加的元素比根節點的左孩子小的時候,我們對其進行右旋轉,變成第一種情況。
- 如果添加的元素介於根節點和左孩子之間的時候,我們對其進行左旋轉,可以變成第二種情況。
No.14 哈希表
散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
給定表M,存在函數f(key),對任意給定的關鍵字值key,代入函數後若能得到包含該關鍵字的記錄在表中的地址,則稱表M爲哈希(Hash)表,函數f(key)爲哈希(Hash) 函數。
哈希衝突的處理方式
鏈地址法:
當哈希表的索引產生衝突時,會將新的元素放在該索引元素的後面,形成一個查找表,換句話說,哈希表就是一個Tree 數組,在 Java 8之前,每一個位置對應一個鏈表,從 JAVA 8開始,當衝突達到一定規模時,會將鏈表轉換成紅黑樹
import java.util.TreeMap;
public class HashTable<K, V> {
private final int[] capacity
= {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741};
private static final int upperTol = 10;
private static final int lowerTol = 2;
private int capacityIndex = 0;
private TreeMap<K, V>[] hashtable;
private int M;
private int size;
public HashTable() {
this.M = capacity[capacityIndex];
size = 0;
hashtable = new TreeMap[M];
for (int i = 0; i < M; i++) {
hashtable[i] = new TreeMap<>();
}
}
private int hash(K key) {
return (key.hashCode() & 0x7fffffff) % M;
}
public int getSize() {
return size;
}
public void add(K key, V value) {
TreeMap<K, V> map = hashtable[hash(key)];
if (map.containsKey(key))
map.put(key, value);
else {
map.put(key, value);
size++;
if (size >= upperTol * M && capacityIndex + 1 < capacity.length ) {
capacityIndex ++;
resize(capacity[capacityIndex]);
}
}
}
public V remove(K key) {
TreeMap<K, V> map = hashtable[hash(key)];
V ret = null;
if (map.containsKey(key)) {
ret = map.get(key);
ret = map.remove(key);
size --;
if (size < lowerTol * M && M / 2 >= capacity[capacityIndex] && capacityIndex -1 >= 0) {
capacityIndex --;
resize(M / 2);
}
}
return ret;
}
public void set(K key, V value) {
TreeMap<K, V> map = hashtable[hash(key)];
if (map.containsKey(key))
map.put(key, value);
else
throw new IllegalArgumentException(key + "doesn't exist.");
}
public V get(K key) {
TreeMap<K, V> map = hashtable[hash(key)];
if (map.containsKey(key))
return map.get(key);
else
throw new IllegalArgumentException(key + "doesn't exist.");
}
private void resize(int newM) {
TreeMap<K,V>[] newHashtable = new TreeMap[newM];
for (int i = 0; i < newM; i++)
newHashtable[i] = new TreeMap<>();
int oldM = this.M;
this.M = newM;
for (int i = 0; i < oldM; i++) {
TreeMap<K, V> map = hashtable[i];
for (K key : map.keySet())
newHashtable[hash(key)].put(key, map.get(key));
}
this.hashtable = newHashtable;
}
}