【數(shù)據(jù)結(jié)構(gòu)】并查集

1. 并查集

并查集解決的是不相交集合的合并及查詢問題仇让。并查集的本質(zhì)是一種樹形數(shù)據(jù)結(jié)構(gòu)躺翻,每個(gè)集合對(duì)應(yīng)一棵樹。當(dāng)需要執(zhí)行“合并兩個(gè)元素所在集合”類似操作時(shí)踊淳,則可能會(huì)用到并查集陕靠。

并查集中脱茉,每個(gè)節(jié)點(diǎn)指向其父節(jié)點(diǎn)垄开,而根節(jié)點(diǎn)的父節(jié)點(diǎn)為其自身,而根節(jié)點(diǎn)本身就可以代表整個(gè)集合虚吟。即签财,對(duì)于一個(gè)節(jié)點(diǎn),如果通過遞歸調(diào)用父節(jié)點(diǎn)唱蒸,最終可以追溯到某個(gè)根節(jié)點(diǎn),那么該節(jié)點(diǎn)就屬于這個(gè)根節(jié)點(diǎn)所代表的集合庆捺。

在執(zhí)行合并兩個(gè)元素所在集合(即合并兩個(gè)節(jié)點(diǎn)所在的樹)操作時(shí)滔以,并查集會(huì)首先檢查兩個(gè)節(jié)點(diǎn)所在樹的根節(jié)點(diǎn)是否是同一個(gè)氓拼;如果是的話,則說明不需要合并桃漾;反之,則使其中一個(gè)根節(jié)點(diǎn)成為另一個(gè)根節(jié)點(diǎn)的子節(jié)點(diǎn)适滓。

通俗地講恋追,并查集就相當(dāng)于若干“原始部落”,每個(gè)節(jié)點(diǎn)都是部落的成員苦囱,而根節(jié)點(diǎn)就是部落的“首領(lǐng)”。子節(jié)點(diǎn)的查詢就相當(dāng)于成員找到上級(jí)朽砰,上級(jí)又找到上級(jí)的上級(jí)喉刘,最后追溯到部落的首領(lǐng)。而集合的合并就相當(dāng)于部落的兼并造锅,兩個(gè)部落合并成一個(gè)廉邑,首領(lǐng)只剩一個(gè),另一個(gè)則俯首稱臣(成為另一個(gè)根的子節(jié)點(diǎn))糙箍。

1.1 并查集的特點(diǎn)

并查集有以下特點(diǎn):

  1. 包含一個(gè)或若干樹狀結(jié)構(gòu)深夯,每棵樹代表一個(gè)集合收奔,每個(gè)節(jié)點(diǎn)代表集合中的一個(gè)元素;
  2. 樹中節(jié)點(diǎn)只知道自己的父節(jié)點(diǎn)饰剥;
  3. 根節(jié)點(diǎn)的父節(jié)點(diǎn)為其自身;
  4. 可以執(zhí)行合并操作顾孽,將兩個(gè)節(jié)點(diǎn)所在的樹合并成一棵樹比规;合并之后测秸,一棵樹的根節(jié)點(diǎn)成為另一棵樹根節(jié)點(diǎn)的子節(jié)點(diǎn)。

1.2 并查集的一般形式

/**
 * 并查集
 *
 * @param <T> 節(jié)點(diǎn)類型
 */
public class UnionFind<T> {
    protected int size;
    protected final Map<T, T> parent = new HashMap<>();

    /**
     * @param orig 原始數(shù)據(jù)集
     */
    public UnionFind(T[] orig) {
        for (T t : orig) {
            parent.put(t, t); // 每個(gè)節(jié)點(diǎn)作為一個(gè)根節(jié)點(diǎn)
        }
        size = orig.length;
    }

    /**
     * 合并兩個(gè)節(jié)點(diǎn)所在的樹
     */
    public void union(T node1, T node2) {
        T root1 = find(node1);
        T root2 = find(node2);
        if (root1 != root2) {
            parent.put(root2, root1); // 使樹2成為樹1子樹
            size--;
        }
    }

    /**
     * 查詢節(jié)點(diǎn)node所在樹的根節(jié)點(diǎn)
     */
    public T find(T node) {
        if (!parent.containsKey(node)) return null;
        while (parent.get(node) != node) node = parent.get(node);
        return node;
    }

    public int size() {
        return size;
    }
}

2. 例題

2.1 例1 城市道路系統(tǒng)問題

輸入一個(gè)整型數(shù)組int[] cities铃拇,每個(gè)元素代表一個(gè)城市缠俺;
輸入若干對(duì)整數(shù)int[][] roads磷雇,每對(duì)整數(shù)表示對(duì)應(yīng)的兩個(gè)城市之間修建有道路
如果城市之間能通過道路到達(dá)睁本,那么稱這些城市處于同一個(gè)道路系統(tǒng)凡泣。
問:總共有多少個(gè)道路系統(tǒng)

假設(shè)城市數(shù)組為{1,2,3,4,5,6,7,8,9}猴誊,
道路為{1,2}, {3,4}, {4,5}, {6,8}, {1,6}, {9,5}

并查集的思路:
首先把每個(gè)原始數(shù)據(jù)各自分割成一個(gè)集合:{1}{2}{3}{4}{5}{6}{7}{8}{9}畏吓;
連通道路{1,2}(即合并1、2所在集合):{1,2}{3}{4}{5}{6}{7}{8}{9}粥谬;
連通道路{3,4}(即合并3、4所在集合):{1,2}{3,4}{5}{6}{7}{8}{9}持隧;
連通道路{4,5}(即合并4裂允、5所在集合):{1,2}{3,4,5}{6}{7}{8}{9}貌踏;
連通道路{6,8}(即合并6谬运、8所在集合):{1,2}{3,4,5}{6,8}{7}{9}
連通道路{1,6}(即合并1、6所在集合):{1,2,6,8}{3,4,5}{7}{9}勤哗;
連通道路{9,5}(即合并9民逼、5所在集合):{1,2,6,8}{3,4,5,9}{7}焚辅;

因?yàn)樽詈蠛喜⒊闪?個(gè)集合瘫析,故一共有3個(gè)道路系統(tǒng)。

例題代碼實(shí)現(xiàn):

    public int countRoadSystem(int[] cities, int[][] roads) {
        UnionFind<Integer> uf = new UnionFind<Integer>(cities);
        for (int[] road : roads) uf.union(road[0], road[1]);
        return uf.size();
    }

其中并查集的代碼為第一節(jié)中的一般形式奇适。

2.2 例2 Leetcode 947. 移除最多的同行或同列石頭

題目Leetcode 947. 移除最多的同行或同列石頭
題解Leetcode 947. 移除最多的同行或同列石頭

題目:

n 塊石頭放置在二維平面中的一些整數(shù)坐標(biāo)點(diǎn)上趋急。每個(gè)坐標(biāo)點(diǎn)上最多只能有一塊石頭键科。
如果一塊石頭的 同行或者同列 上有其他石頭存在,那么就可以移除這塊石頭茄厘。
給你一個(gè)長(zhǎng)度為 n 的數(shù)組 stones 吆录,其中 stones[i] = [xi, yi] 表示第 i 塊石頭的位置,返回 可以移除的石子 的最大數(shù)量侄柔。

示例 1:

輸入:stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
輸出:5
解釋:一種移除 5 塊石頭的方法如下所示:

  1. 移除石頭 [2,2] ,因?yàn)樗?[2,1] 同行。
  2. 移除石頭 [2,1] ,因?yàn)樗?[0,1] 同列。
  3. 移除石頭 [1,2] 乔宿,因?yàn)樗?[1,0] 同行。
  4. 移除石頭 [1,0] 详瑞,因?yàn)樗?[0,0] 同列。
  5. 移除石頭 [0,1] 泻帮,因?yàn)樗?[0,0] 同行驳庭。
    石頭 [0,0] 不能移除,因?yàn)樗鼪]有與另一塊石頭同行/列饲常。

示例 2:

輸入:stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
輸出:3
解釋:一種移除 3 塊石頭的方法如下所示:

  1. 移除石頭 [2,2] 贝淤,因?yàn)樗?[2,0] 同行。
  2. 移除石頭 [2,0] 播聪,因?yàn)樗?[0,0] 同列布隔。
  3. 移除石頭 [0,2] 衅檀,因?yàn)樗?[0,0] 同行霎俩。
    石頭 [0,0] 和 [1,1] 不能移除,因?yàn)樗鼈儧]有與另一塊石頭同行/列杉适。

示例 3:

輸入:stones = [[0,0]]
輸出:0
解釋:[0,0] 是平面上唯一一塊石頭柳击,所以不可以移除它。

提示:

  • 1 <= stones.length <= 1000
  • 0 <= xi, yi <= 104
  • 不會(huì)有兩塊石頭放在同一個(gè)坐標(biāo)點(diǎn)上

題解

容易得到:

  1. 對(duì)于兩個(gè)石子A和B蹬叭,如果二者同行/列哭靖,那么A和B屬于同一集合
  2. 對(duì)于石子A试幽、B铺坞、C洲胖,如果A、B屬于同一集合擒滑,且B叉弦、C屬于同一集合,那么A淹冰、C屬于同一集合
  3. 對(duì)于一個(gè)大小為 n 的石子集合柠衍,可以移除 n - 1 枚石子。

遍歷數(shù)組stones珍坊,對(duì)于每一個(gè)石子{x, y},天然都屬于兩個(gè)集合:第x行禽最、第y列。于是川无,將第x行與第y列的石子合并成一個(gè)新的集合即可虑乖。

    public int removeStones(int[][] stones) {
        UnionFind<Integer> uf = new UnionFind<>();
        for (int[] stone : stones) {
            int x = stone[0];
            int y = stone[1];
            uf.union(x, -y - 1); // 合并"第x行"與"第y列",其中用負(fù)數(shù)代替第y列
        }
        return stones.length - uf.size();
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仅叫,一起剝皮案震驚了整個(gè)濱河市糙捺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌坎缭,老刑警劉巖签钩,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異铅檩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拾给,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門蒋得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來粘拾,“玉大人,你說我怎么就攤上這事∽仿浚” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵殿雪,是天一觀的道長(zhǎng)丙曙。 經(jīng)常有香客問我其骄,道長(zhǎng),這世上最難降的妖魔是什么拯爽? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮逼肯,結(jié)果婚禮上桃煎,老公的妹妹穿的比我還像新娘。我一直安慰自己三椿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布赋续。 她就那樣靜靜地躺著,像睡著了一般蛾绎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上租冠,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音纤泵,去河邊找鬼镜粤。 笑死玻褪,一個(gè)胖子當(dāng)著我的面吹牛公荧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窟社,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼绪钥,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了匣吊?” 一聲冷哼從身側(cè)響起跪楞,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎甸祭,沒想到半個(gè)月后炒瘟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡校焦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了氛雪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡报亩,死狀恐怖井氢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情花竞,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布零远,位于F島的核電站,受9級(jí)特大地震影響俭嘁,放射性物質(zhì)發(fā)生泄漏服猪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一罢猪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粘捎,春花似錦危彩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拼坎。三九已至完疫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盛龄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工讯嫂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兆沙,地道東北人葛圃。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓库正,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親褥符。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容