1. 程式人生 > >紅黑樹真的沒你想的那麼難

紅黑樹真的沒你想的那麼難

概述

TreeMap是紅黑樹的java實現,紅黑樹能保證增、刪、查等基本操作的時間複雜度為O(lgN)。
首先我們來看一張TreeMap的繼承體系圖:

image
還是比較直觀的,這裡來簡單說一下繼承體系中不常見的介面NavigableMap和SortedMap,這兩個介面見名知意。先說NavigableMap介面,NavigableMap介面聲明瞭一些列具有導航功能的方法,比如:

/**
 * 返回紅黑樹中最小鍵所對應的 Entry
 */
Map.Entry<K,V> firstEntry();

/**
 * 返回最大的鍵 maxKey,且 maxKey 僅小於引數 key
 */
K lowerKey(K key); /** * 返回最小的鍵 minKey,且 minKey 僅大於引數 key */ K higherKey(K key); // 其他略

通過這些導航方法,我們可以快速定位到目標的 key 或 Entry。至於 SortedMap 介面,這個介面提供了一些基於有序鍵的操作,比如:

/**
 * 返回包含鍵值在 [minKey, toKey) 範圍內的 Map
 */
SortedMap<K,V> headMap(K toKey);();

/**
 * 返回包含鍵值在 [fromKey, toKey) 範圍內的 Map
 */
SortedMap<K,V> subMap(K fromKey, K toKey);

// 其他略

以上就是兩個介面的介紹,很簡單。至於AbstractMap和Map這裡就不說了,大家有興趣自己去看看Javadoc吧。關於TreeMap的繼承體系就這裡就說到這,接下來我們進入細節部分分析。

原始碼分析

新增

紅黑樹最複雜的無非就是增刪了,這邊我們先介紹增加一個元素,瞭解紅黑樹的都知道,當往 TreeMap 中放入新的鍵值對後,可能會破壞紅黑樹的性質。首先我們先鞏固一下紅黑樹的特性。

  • 節點是紅色或黑色。

  • 根節點是黑色。

  • 每個葉子節點都是黑色的空節點(NIL節點)。

  • 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)。

  • 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。

接下來我們看看新增到底做了什麼處理:

    public V put(K key, V value) {
        TreeMapEntry<K,V> t = root;
        if (t == null) {

            if (comparator != null) {
                if (key == null) {
                    comparator.compare(key, key);
                }
            } else {
                if (key == null) {
                    throw new NullPointerException("key == null");
                } else if (!(key instanceof Comparable)) {
                    throw new ClassCastException(
                            "Cannot cast" + key.getClass().getName() + " to Comparable.");
                }
            }
            root = new TreeMapEntry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        TreeMapEntry<K,V> parent;
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        TreeMapEntry<K,V> e = new TreeMapEntry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

這邊會先把根節點暫存依賴,如果根節點為null,則講新增的這個節點設為根節點。 如果初始化的時候指定了comparator比較器,則講其插入到指定位置,否則使用key進行比較並且插入。不斷的進行比較,找到沒有子節點的節點,將其插入到相應節點。(注:如果查找出有相同的值只會更新當前值,cmp小於0是沒有左節點,反之沒有右節點。)

新插入的樹破環的紅黑樹規則,我們會通過fixAfterInsertion去進行相應的調整,這也是TreeMap插入實現的重點,具體我們看看他是怎麼通過java實現的。

    private void fixAfterInsertion(TreeMapEntry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                TreeMapEntry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                TreeMapEntry<K,V> y = leftOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

首先將新插入的節點設定為紅色,這邊做了一個判斷,新節點不為null,新節點不為根節點並且新節點的父節點為紅色。才會進入內部的判斷,因為其本身就是一個紅黑樹。如果新節點的父節點為黑色,則他依舊滿足紅黑樹的特性。所以當其父節點為紅色進入內部的判斷。

如果新節點是其祖父節點的左子孫,則拿到其祖父節點的右兒子,也就是新節點的叔叔。如果叔叔節點是紅色。則將其父節點設為黑色,講叔父節點設為黑色,然後講新節點直接其祖父節點。

否則如果新節點是其父節點的右節點,以其父節點進行左轉,將父節點設為黑色,祖父節點設為紅色,在通過祖父節點進行右轉。

else內容和上述基本一致。自己分析~~

最後我們還需要將跟節點設為黑色。

我們稍微看一下,他是怎麼進行左轉和右轉的。

// 右旋與左旋思路一致,只分析其一
// 結果相當於把p和p的兒子調換了
private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        // 取出p的右兒子
        Entry<K,V> r = p.right;
        // 然後將p的右兒子的左兒子,也就是p的左孫子變成p的右兒子
        p.right = r.left;
        if (r.left != null)
            // p的左孫子的父親現在是p
            r.left.parent = p;

        // 然後把p的父親,設定為p右兒子的父親
        r.parent = p.parent;
        // 這說明p原來是root節點
        if (p.parent == null)
            root = r;
        else if (p.parent.left == p)
            p.parent.left = r;
        else
            p.parent.right = r;
        r.left = p;
        p.parent = r;
    }
}

//和左旋類似
private void rotateRight(Entry<K,V> p) {
    // ...
}

下面我們通過圖解來看看如何插入一顆紅黑樹。

現有陣列int[] a = {1, 10, 9, 2, 3, 8, 7, 4, 5, 6};我們要將其變為一棵紅黑樹。

首先插入1,此時樹是空的,1就是根結點,根結點是黑色的:

然後插入元素10,此時依然符合規則,結果如下:

當插入元素9時,這時是需要調整的第一種情況,結果如下:

紅黑樹規則4中強調不能有兩個相鄰的紅色結點,所以此時我們需要對其進行調整。調整的原則有多個相關因素,這裡的情況是,父結點10是其祖父結點1(父結點的父結點)的右孩子,當前結點9是其父結點10的左孩子,且沒有叔叔結點(父結點的兄弟結點),此時需要進行兩次旋轉,第一次,以父結點10右旋:

然後將父結點(此時是9)染為黑色,祖父結點1染為紅色,如下所示:

然後以祖父結點1左旋:

下一步,插入元素2,結果如下:

此時情況與上一步類似,區別在於父結點1是祖父結點9的左孩子,當前結點2是父結點的右孩子,且叔叔結點10是紅色的。這時需要先將叔叔結點10染為黑色,再進行下一步操作,具體做法是將父結點1和叔叔結點10染為黑色,祖父結點9染為紅色,如下所示:

由於結點9是根節點,必須為黑色,將它染為黑色即可:

下一步,插入元素3,如下所示:

這和我們之前插入元素10的情況一模一樣,需要將父結點2染為黑色,祖父結點1染為紅色,如下所示:

然後左旋:

下一步,插入元素8,結果如下:

此時和插入元素2有些類似,區別在於父結點3是右孩子,當前結點8也是右孩子,這時也需要先將叔叔結點1染為黑色,具體操作是先將1和3染為黑色,再將祖父結點2染為紅色,如下所示:

此時樹已經平衡了,不需要再進行其他操作了,現在插入元素7,如下所示:

這時和之前插入元素9時一模一樣了,先將7和8右旋,如下所示:

然後將7染為黑色,3染為紅色,再進行左旋,結果如下:

下一步要插入的元素是4,結果如下:

這裡和插入元素2是類似的,先將3和8染為黑色,7染為紅色,如下所示:

但此時2和7相鄰且顏色均為紅色,我們需要對它們繼續進行調整。這時情況變為了父結點2為紅色,叔叔結點10為黑色,且2為左孩子,7為右孩子,這時需要以2左旋。這時左旋與之前不同的地方在於結點7旋轉完成後將有三個孩子,結果類似於下圖:

這種情況處理起來也很簡單,只需要把7原來的左孩子3,變成2的右孩子即可,結果如下:

然後再把2的父結點7染為黑色,祖父結點9染為紅色。結果如下所示:


此時又需要右旋了,我們要以9右旋,右旋完成後7又有三個孩子,這種情況和上述是對稱的,我們把7原有的右孩子8,變成9的左孩子即可,如下所示:

下一個要插入的元素是5,插入後如下所示:

有了上述一些操作,處理5變得十分簡單,將3染為紅色,4染為黑色,然後左旋,結果如下所示:


最後插入元素6,如下所示:

又是叔叔結點3為紅色的情況,這種情況我們處理過多次了,首先將3和5染為黑色,4染為紅色,結果如下:

此時問題向上傳遞到了元素4,我們看2、4、7、9的顏色和位置關係,這種情況我們也處理過,先將2和9染為黑色,7染為紅色,結果如下:

最後7是根結點,染為黑色即可,最終結果如下所示:

可以看到,在插入元素時,叔叔結點是主要影響因素,待插入結點與父結點的關係決定了是否需要多次旋轉。

刪除

除了新增操作,紅黑樹的刪除也是很麻煩的…我們看看怎麼通過java去實現紅黑樹的刪除。具體程式碼如下:

  public V remove(Object key) {
        TreeMapEntry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);
        return oldValue;
    }

其內部是通過deleteEntry去進行刪除的。所以我們具體看看deleteEntry的實現。

 private void deleteEntry(TreeMapEntry<K,V> p) {
        modCount++;
        size--;

        if (p.left != null && p.right != null) {
            TreeMapEntry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } 

        TreeMapEntry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { 
            root = null;
        } else {
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

根據上述程式碼,我們可以看出,如果 p 有兩個孩子節點,則找到後繼節點,並把後繼節點的值複製到節點 P 中,並讓 p 指向其後繼節點。 然後將 replacement parent 引用指向新的父節點,同時讓新的父節點指向 replacement。

然後判斷如果刪除的節點 p 是黑色節點,則需要進行調整。刪除的是根結點並且樹中只有一個節點,我們將根結點置為null,否則,如果刪除的節點沒有子節點並且是黑色,則需要調整。最後將p從樹中移除。

刪除了一個元素,為了保證還是一個紅黑樹,我們需要將其進行調整,具體程式碼如下:

  /** From CLR */
    private void fixAfterDeletion(TreeMapEntry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))) {
                TreeMapEntry<K,V> sib = rightOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }

                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            } else { // symmetric
                TreeMapEntry<K,V> sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }

        setColor(x, BLACK);
}

如果替換節點是父節點的左節點,並且替換節點的兄弟節點是紅色,那我們需要將兄弟節點變成黑色,將父節點變成紅色,並且通過父節點進行左旋轉,然後將父節點的右節點設為兄弟節點。

如果兄弟節點的左右節點都是黑色的,那麼將兄弟節點置為紅色,並且將當前節點指向父節點。若兄弟節點的右節點是黑色,我們需要將兄弟節點的左節點設為黑色,將兄弟節點設為紅色,然後以兄弟節點進行右旋轉,然後更新兄弟節點。然後設定兄弟節點的顏色為右節點的顏色,然後將父節點和兄弟節點的左節點設為黑色,最後進行右旋轉。最後將根結點設為黑色。

下面我們依舊通過圖解來看看紅黑樹的刪除操作:要從一棵紅黑樹中刪除一個元素,主要分為三種情況。

待刪除元素沒有孩子

沒有孩子指的是沒有值不為NIL的孩子。這種情況下,如果刪除的元素是紅色的,可以直接刪除,如果刪除的元素是黑色的,就需要進行調整了。

例如我們從下圖中刪除元素1:

刪除元素1後,2的左孩子為NIL,這條支路上的黑色結點數就比其他支路少了,所以需要進行調整。

這時,我們的關注點從叔叔結點轉到兄弟結點,也就是結點4,此時4是紅色的,就把它染為黑色,把父結點2染為紅色,如下所示:

然後以2左旋,結果如下:

此時兄弟結點為3,且它沒有紅色的孩子,這時只需要把它染為紅色,父結點2染為黑色即可。結果如下所示:

待刪除元素有一個孩子

這應該是刪除操作中最簡單的一種情況了,根據紅黑樹的定義,我們可以推測,如果一個元素僅有一個孩子,那麼這個元素一定是黑色的,而且其孩子是紅色的。

假設我們有一個紅色節點,它是樹中的某一個節點,且僅有一個孩子,那麼根據紅色節點不能相鄰的條件,它的孩子一定是黑色的,如下所示:

但這個子樹的黑高卻不再平衡了(注意每個節點的葉節點都是一個NIL節點),因此紅色節點不可能只有一個孩子。

而若是一個黑色節點僅有一個孩子,如果其孩子是黑色的,同樣會打破黑高的平衡,所以其孩子只能是紅色的,如下所示:

只有這一種情況符合紅黑樹的定義,這時要刪除這個元素,只需要使用其孩子代替它,僅代替值而不代替顏色即可,上圖的情況刪除完後變為:


可以看到,樹的黑高並沒有發生變化,因此也不需要進行調整。

待刪除元素有兩個孩子

我們知道如果刪除一個有兩個孩子的元素,可以使用它的前驅或者後繼結點代替它。因為它的前驅或者後繼結點最多隻會有一個孩子,所以這種情況可以轉為上述兩種情況進行處理。

查詢

說了最複雜的新增和刪除,我們再來看看查詢,這裡就簡單很多了,具體程式碼如下:

public V get(Object key) {
    Entry<K,V> p = getEntry(key);
    return (p==null ? null : p.value);
}

final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if (comparator != null)
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;

    // 查詢操作的核心邏輯就在這個 while 迴圈裡
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}

這個流程算比較簡單了,上面註釋標明瞭,這邊就不再解釋了。

總結

這邊通過圖+程式碼的形式將紅黑樹的特點展現出來。可以通過上面描述可見,紅黑樹並沒有那麼難…

參考

相關推薦

真的那麼

寫本文的原由是昨晚做夢居然夢到了在看原始碼,於是便有了此文...... 雖然標題是關於紅黑樹的,不過此文是結合圖片,通過分析TreeMap的原始碼,讓你理解起來不是那麼枯燥(前方高能,此文圖片眾多,慎入)。 作者 | 馬雲飛 責編 | 胡巍巍

真的那麼

概述 TreeMap是紅黑樹的java實現,紅黑樹能保證增、刪、查等基本操作的時間複雜度為O(lgN)。 首先我們來看一張TreeMap的繼承體系圖: 還是比較直觀的,這裡來簡單說一下繼承體系中不常見的介面NavigableMap和SortedMa

JavaScript的執行上下文,那麼

> 作者:小土豆 > 部落格園:https://www.cnblogs.com/HouJiao/ > 掘金:https://juejin.im/user/2436173500265335 # 前言 在正文開始前,先來看兩個`JavaScript`程式碼片段。 #### 程式碼一 ```java

透徹了解

black ade 我們 工作 key 針對 otn strong lean 教你透徹了解紅黑樹 作者:July、saturnman 2010年12月29日 本文參考:Google、算法導論、STL源碼剖析、計算機程序設計藝術。 推薦閱讀: Left-

WeCode線上少兒程式設計|計算思維VS繪畫,程式設計那麼枯燥

01 — 初遇計算思維:它為什麼這麼重要? 21世紀,每個人都身處數字世界,我們的生活充斥著科技,我們的日常生活受到電腦科學的驅動。實際上,軟體和技術已經改變了從科學到藝術,甚至從醫學到心理學的每個工作領域。與此同時,一種被稱為“21世紀技能錦囊”的新型理念應運而生——計算思維。

大名鼎鼎的get了麼?2-3 絕對平衡 右旋轉 左旋轉 顏色反轉

  前言   11.1新的一月加油!這個購物狂歡的季節,一看,已囊中羞澀!趕緊來惡補一下紅黑樹和2-3樹吧!紅黑樹真的算是大名鼎鼎了吧?即使你不瞭解它,但一定聽過吧?下面跟隨我來揭開神祕的面紗吧!   一、2-3樹   1、搶了紅黑樹的光環?   今天的主角是紅黑樹,是無疑的,主角光環在呢!那2-3樹

透徹瞭解---第一篇

文章《教你透徹瞭解紅黑樹—第二篇》,主要說明了紅黑樹的旋轉、插入、刪除等操作。 1 背景知識 1.1 二叉樹 二叉樹是每個節點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right

一步一圖一程式碼,一定要讓真正徹底明白(平衡二叉

一步一圖一程式碼,一定要讓你真正徹底明白紅黑樹 作者:July   二零一一年一月九日 ----------------------------- 本文參考: I、  The Art of Computer Programming Volume I II、 I

一棵實現刪除的

除了刪除節點之外,把紅黑樹基本上看完了,順手用java實現了一棵紅黑樹import javax.swing.*; /** * Created by eminem on 16-11-15. */ public class brTree { private Nod

初步瞭解

                 教你初步瞭解紅黑樹作者:July、saturnman   2010年12月29日本文參考:Google、演算法導論、STL原始碼剖析、計算機程式設計藝術。推薦閱讀:一、紅黑樹的介紹先來看下演算法導論對R-B Tree的介紹:紅黑樹,一種二叉查

一步一圖一程式碼,一定要讓真正徹底明白 (July演算法!!!)

情況5: 兄弟S為黑色,S 的左兒子是紅色,S 的右兒子是黑色,而N是它父親的左兒子。//此種情況,最後轉化到下面的情況6。 [對應我第二篇文章中,情況3:x的兄弟w是黑色的,w的左孩子是紅色,w的右孩子是黑色。] void delete_case5(struct node *n) {         str

四面快手歸來,分享Java題及面經:策略模式++Java鎖+Redis+Kafka等分散式

看真題,瞭解差距,明確學習方向與目標。 看面經,提前準備,事半功倍。 ** 一面(一個小時十分鐘) ** 1.自我介紹 2.說說B+樹和B樹的區別,優缺點等? 3聊聊Spring,主要IOC等等 4多執行緒JUC包下的一些常見的類,比如CountDownLatch、Se

並沒有我們想象的那麼(下)

// sgi stl _Rb_tree 插入演算法 insert_equal() 實現. // 策略概述: insert_equal() 在紅黑樹找到自己的位置, // 然後交由 _M_insert() 來處理接下來的工作. // _M_insert() 會將節點插入紅黑樹中, 接著調整紅黑樹, // 維持性

,並非想象中的那麼複雜

紅黑樹是非常popular的一種self-adjusted的平衡二叉排序樹。通常他給我們的印象是很複雜,有很多case,要小心的旋轉。有人說,曾將在某公司的面試時,被要求實現紅黑樹。他覺得這很沒有道理,幾乎很少有人能在不參考教科書的情況下,記清楚那麼多的case。在這一章裡,

並沒有我們想象的那麼(上)

紅黑樹並沒有我們想象的那麼難 上、下兩篇已經完成, 希望能幫助到大家. 紅黑樹並沒有想象的那麼難, 初學者覺得晦澀難讀可能是因為情況太多. 紅黑樹的情況可以通過歸結, 通過合併來得到更少的情況, 如此可以加深對紅黑樹的理解. 網路上的大部分紅黑樹的講解因為沒有「合併」. 紅黑樹的五個

Web登入其實那麼簡單

1. 一個簡單的HTML例子看看使用者資訊保安標準的HTML語法中,支援在form表單中使用&l

Select—那麼複雜

  Select語句的基本結構如下:                      Select  [All | Distinct]  select_list                      

正則表示式並那麼

首先正則表示式幹嘛用的呢。沒錯就是用來匹配字串的。記得資料庫中的like關鍵字嗎關鍵字中的like ‘%a%’中的’%a%’其實就是一個類正則表示式。%是一個萬用字元。那麼正則表示式中的有哪些類似的這種符號呢? .  可以匹配所有字元,類似於上例中的%吧 \d ,[0-9]

React+Redux開發實戰專案【美團App】,那麼

README.md 前言 開始學習React的時候,在網上找了一些文章,讀了官網的一些文件,後來覺得React上手還是蠻簡單的, 然後就在網上找了一個React實戰的練手專案,個人學完之後覺