脫線MIN問題及源代碼——Union-Find算法的應用與推廣

脫線MIN問題:

指令Insert(i):把元素i插入集合s中。

指令Extract_min:從集合S中找出最小元並進行刪除。

兩種指令的簡單表示法:用i表示Insert(i),用E表示Extract_min

例:7259E6EE3E14E

這種序列滿足兩個性質:

1 任一i (1<=i<=n)在序列中最多出現一次(元素之間互不相同);

2)從左起任意一段中,插入指令條數大於等於E指令條數。(否則無元素可刪。)

算法結果:

給定一個InsertExtract_min的指令序列之後,對在序列中出現的每個i,算法要輸出i是被第幾條E指令刪除的(對於序列中未出現的i,算法應輸出相應信息。)

上例中有:1(5), 2(1), 3(4), 4(未被刪除)5(2)6(3)794一樣未被刪除,8未出現。


脫線MIN算法思路:

算法開始之前,先把所有元素的所屬集合名NAME[i]置爲0O(n));再掃描指令序列,把由E隔開的每段中的元素組成若干個集合並命名(O(n)):

e.g.: 1={2,5,7,9}2={6}3=null4={3}5={1,4}6=null

用集合名(數字)來表示刪除iE指令序號。

算法從i=1開始逐一檢查,找到1所在的元素集合名(5),輸出1是被第5E指令刪除的;

輸出後用UNION算法把集合5與其後的集合6合併爲66={1,4}

下一步看i=2,找到2所在的元素集合名(1),輸出2是被第1E指令刪除的;

輸出後用UNION算法把集合1與其後的2合併,得到2={2,5,6,7,9}

其次看i=3,找到3所在的元素集合名(4),輸出3是被第4E指令刪除的;

輸出後用UNION算法把集合4與其後的集合6合併(此時集合5已經不存在了),得到6={1,3,4}

i=4時,找到4所在的元素集合名(6),但6>E指令條數(只有5條),故輸出“4未被刪除

i=5時,找到5所在的元素集合名(2),輸出5是被第2E指令刪除的;

輸出後用UNION算法把集合2與其後的集合3合併,得3={2,5,6,7,9}

i=6時,找到6所在的元素集合名(3),輸出6是被第3E指令刪除的;

輸出後用UNION算法把集合36合併,得6={1,2,3,4,5,6,7,9}其後的79執行Find後均得6,故與4一樣未被刪除,而8未在序列中出現,因Find(8)=0,故應輸出“8未出現

爲合併時方便地找到後繼集合,引入PredSucc 2個數組:

Pred[j]記錄了前一個集合的名稱(數字),初始時爲j-1

Succ[j]記錄了後一個集合的名稱(數字),初始時爲j+1

 

 

Union-Find樹結構的基礎上,解決了脫線MIN問題。考慮了路徑壓縮。

測試數據:7259E6EE3E14E

運行截圖:

輸入:



 

輸出:


算法:

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))

因要對合並後的集合(樹結構)進行強制命名,故採用可強制命名爲kUNION(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);
}
 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章