脫線MIN問題:
指令Insert(i):把元素i插入集合s中。
指令Extract_min:從集合S中找出最小元並進行刪除。
兩種指令的簡單表示法:用i表示Insert(i),用E表示Extract_min。
例:7,2,5,9,E,6,E,E,3,E,1,4,E
這種序列滿足兩個性質:
1) 任一i (1<=i<=n)在序列中最多出現一次(元素之間互不相同);
2)從左起任意一段中,插入指令條數大於等於E指令條數。(否則無元素可刪。)
算法結果:
給定一個Insert與Extract_min的指令序列之後,對在序列中出現的每個i,算法要輸出i是被第幾條E指令刪除的(對於序列中未出現的i,算法應輸出相應信息。)
上例中有:1(5), 2(1), 3(4), 4(未被刪除),5(2),6(3),7,9與4一樣未被刪除,8未出現。
脫線MIN算法思路:
算法開始之前,先把所有元素的所屬集合名NAME[i]置爲0(O(n));再掃描指令序列,把由E隔開的每段中的元素組成若干個集合並命名(O(n)):
e.g.: 1={2,5,7,9},2={6},3=null,4={3},5={1,4},6=null
用集合名(數字)來表示刪除i的E指令序號。
算法從i=1開始逐一檢查,找到1所在的元素集合名(5),輸出1是被第5條E指令刪除的;
輸出後用UNION算法把集合5與其後的集合6合併爲6:6={1,4}。
下一步看i=2,找到2所在的元素集合名(1),輸出2是被第1條E指令刪除的;
輸出後用UNION算法把集合1與其後的2合併,得到2={2,5,6,7,9}。
其次看i=3,找到3所在的元素集合名(4),輸出3是被第4條E指令刪除的;
輸出後用UNION算法把集合4與其後的集合6合併(此時集合5已經不存在了),得到6={1,3,4}。
i=4時,找到4所在的元素集合名(6),但6>E指令條數(只有5條),故輸出“4未被刪除”。
i=5時,找到5所在的元素集合名(2),輸出5是被第2條E指令刪除的;
輸出後用UNION算法把集合2與其後的集合3合併,得3={2,5,6,7,9}。
i=6時,找到6所在的元素集合名(3),輸出6是被第3條E指令刪除的;
輸出後用UNION算法把集合3與6合併,得6={1,2,3,4,5,6,7,9}其後的7,9執行Find後均得6,故與4一樣未被刪除,而8未在序列中出現,因Find(8)=0,故應輸出“8未出現”。
爲合併時方便地找到後繼集合,引入Pred和Succ 2個數組:
Pred[j]記錄了前一個集合的名稱(數字),初始時爲j-1,
Succ[j]記錄了後一個集合的名稱(數字),初始時爲j+1。
在Union-Find樹結構的基礎上,解決了脫線MIN問題。考慮了路徑壓縮。
測試數據:7,2,5,9,E,6,E,E,3,E,1,4,E
運行截圖:
輸入:
輸出:
算法:
for i=1 to n do
{
j←Find(i); /*找到i所屬集合名(數字)即刪除i的E指令序號*/
if j=0 then {輸出“i未在序列中出現”}
else if j>k then
{輸出“i未被刪除”}
else /* i確實被刪除了*/
{
輸出“i是被第j條E指令所刪除”;
UNION(j,Succ[j],Succ[j]);
Succ[Pred[j]]←Succ[j];/* 集合j不再存在*/
Pred[Succ[j]]←Pred[j]
}
}
算法的主要工作是執行O(n)條Find指令,(其餘工作在循環的每一輪都是常數時間)故該算法的時間複雜度爲O(n*G(n))。
因要對合並後的集合(樹結構)進行強制命名,故採用可強制命名爲k的UNION(i,j,k)算法:
- 數組元素ROOT[i]中存放集合名爲i的根結點編號;
- 數組元素COUNT[t]中存放編號t的結點爲根的子樹中的結點個數;
- 數組元素FATHER [t]中存放編號t的結點的父結點編號;
- 數組元素NAME[t]中存放t結點爲根的樹所對應的集合名。
wlg assume COUNT[ROOT[i]]<=COUNT[ROOT[j]]
(otherwise interchange i and j in the following lines)
LARGE←ROOT[j];
SMALL←ROOT[i];
FATHER[SMALL]←LARGE;
COUNT[LARGE]←COUNT[LARGE]+ COUNT[SMALL];
NAME[LARGE]←k;
ROOT[k]←LARGE
由於該算法不執行Find,故在O(1)時間裏即可完成。
#include <stdio.h>
#include <malloc.h> #define NUM 10 typedef struct Node//節點 { int name;//集合名 int value;//value,和index數值相同 int father;//父親節點 int rank;//秩 }TreeNode, *BiTree; typedef struct Sets//集合 { int num;//集合的根結點 int pre;//前面的集合名 int suc;//後面的集合名 int used; }SetNode, *BiSet; BiTree bt; BiSet bs; //創建集合 void creatSet(int assetNum){ if (assetNum>1) { bs[assetNum-1].suc=assetNum; bs[assetNum].pre=assetNum-1; } bs[assetNum].used=1; } //插入操作 void insert(int i,int assetNum){ if (bs[assetNum].num == 0) { bt[i].rank=0; bt[i].name=assetNum; bs[assetNum].num=i; }else{ bt[bs[assetNum].num].rank=1; }; bt[i].father=bs[assetNum].num; } //刪除最小操作 int extract_min(int assetNum){ assetNum++; creatSet(assetNum); return assetNum; } //初始化,輸入及構造數據結構 int initializeNode()//返回構造了幾個集合樹 { bs=(BiSet)malloc(NUM*sizeof(SetNode)); bt=(BiTree)malloc(NUM*sizeof(TreeNode)); for (int i=0;i<NUM;i++) { bt[i].name=0; bt[i].value=0; bt[i].rank=0; bt[i].father=0; bs[i].pre=0; bs[i].suc=0; bs[i].num=0; bt[i].value=i; bs[i].used=0; } int r=-2; int assetNum=1; creatSet(assetNum); while(r!=-1){ printf("請輸入一個不重複正數字,作爲insert操作。或者輸入0表示一個extract_min操作,-1表示退出輸入"); scanf("%d",&r); if (r>0) { insert(r,assetNum); } else if (r==0) { assetNum=extract_min(assetNum); } } //assetNum=extract_min(assetNum); return assetNum; } /************************************************************************ 找到某個節點的根節點 輸入參數: 輸出參數: index:根結點節點編號 ************************************************************************/ int find(int value){ BiTree t=&bt[value]; if (t->father!=value) { t->father=find(bt[bt[value].father].value); } return t->father; } //返回節點所在的集合 //即返回節點的根節點的name int find_setNum(int value){ int t=bt[find(value)].name; return t; } //輸入一個集合名,將這個集合和它的下一個集合合併,並命名爲下一個集合名 void funion(int setNum){ int index1=bs[setNum].num; int suc=bs[setNum].suc; int index2=bs[suc].num; if (bt[index1].rank>bt[index2].rank)//當 { bt[index2].father=index1; bs[suc].num=index1; bt[index1].name=suc; } else if (bt[index1].rank ==bt[index2].rank) { bt[index1].father=index2; bt[index2].rank++; } else{ bt[index1].father=index2; } bs[setNum].used=0; int pre=bs[setNum].pre; bs[pre].suc=suc; bs[suc].pre=pre; } //處理。輸出 void deal(int setsCount){ for (int i=1;i<NUM;i++) { if (bt[i].father==0) { printf("%d沒有出現在序列中\n",i); } else{ int setsNum=find_setNum(i); if (setsNum == setsCount) { printf("%d沒有被刪除\n",i); } else if (setsNum <setsCount) { printf("%d節點被第%d條指令刪除\n",i,setsNum); funion(setsNum); } else printf("err:根據節點查找到一個錯誤的集合名\n"); } } } void main(){ //初始化、輸入及構造數據結構 int setsCount=initializeNode(); //處理 deal(setsCount); //打印 for (int i=0;i<NUM;i++) { printf("index/value:%d, ",i); printf("name:%d, ",bt[i].name); printf("rank:%d, ",bt[i].rank); printf("father:%d\n ",bt[i].father); } printf("asset[name]:index\n"); for (int i=0;i<NUM;i++) { printf("set num:%d, ",i); printf("use:%d, ",bs[i].used); printf("num:%d, ",bs[i].num); printf("pre:%d, ",bs[i].pre); printf("suc:%d\n",bs[i].suc); } //等待顯示 printf("\nprint end"); int i; scanf("%d",&i); }