posts - 195, comments - 34, trackbacks - 0, articles - 1


          /*Problem: 1509  User: Uriel 
             Memory: 144K  Time: 16MS 
             Language: C  Result: Accepted
          */
           

          #include
          <stdio.h>
          #include
          <string.h>

          int min(int a, int b)
          {
              
          return a <= b ? a : b;
          }


          int MinimumRepresentation(char *s, int l)
          {
              
          int i = 0, j = 1, k = 0, t;
              
          while (i < l && j < l && k < l)
              
          {
                  t 
          = s[(i + k)%l] - s[(j + k)%l];
                  
          if (!t) ++ k;
                  
          else
                  
          {
                      
          if (t > 0) i = i + k + 1;
                      
          else j = j + k + 1;
                      
          if (i == j) ++j;
                      k 
          = 0;
                  }

              }

              
          return min(i,j);
          }


          int x,len,i,t;
          char str[10010];
          int main()
          {
              scanf(
          "%d",&t);
              getchar();
              
          while(t--)
              
          {
                  memset(str,
          0x00,sizeof(str));
                  scanf(
          "%s",str);
                  len
          =strlen(str);
                  x
          =MinimumRepresentation(str,len);
                  printf(
          "%d\n",x+1);
              }

              
          return 0;
          }


          //串的同構(gòu)是,在若干次循環(huán)位移后可以變成相同
              static boolean isIsomorphism(String s1, String s2)
              
          {
                  
          char[] u = (s1+s1).toCharArray();
                  
          char[] w = (s2+s2).toCharArray();
                  
                  
          int i = 0;
                  
          int j = 0;
                  
          int len = s1.length();
                  
          while(i < u.length && j < w.length)
                  
          {
                      
          int k = 0;
                      
          while((i+k) < u.length && (j+k)<u.length  && u[i+k] == w[j+k])k++;//&& k < len
                      System.out.println(k);
                      
          if(k >= len) return true;
                      
                      
          if(u[i+k] > w[j+k])i = i+k+1;
                      
          else j = j+k+1;
                  }

                  
          return false;
              }

          posted @ 2009-11-02 18:13 小強摩羯座 閱讀(410) | 評論 (0)編輯 收藏

          排序算法、時間復雜度與信息熵
          icon2 Program Impossible | icon4 2008-05-30 13:23| icon317 Comments | 本文內(nèi)容遵從CC版權(quán)協(xié)議 轉(zhuǎn)載請注明出自matrix67.com

              在這篇文章里,我們從信息論的角度證明了,基于比較的排序算法需要的比較次數(shù)(在最壞情況下)至少為log2(n!),而log(n!)=Θ(nlogn),這給出了比較排序的一個下界。但那里我們討論的只是最理想的情況。一個事件本身所含的信息量是有大小之分的。看到這篇文章之后,我的思路突然開闊了不少:信息論是非常強大的,它并不只是一個用來分析理論最優(yōu)決策的工具。從信息論的角度來分析算法效率是一件很有趣的事,它給我們分析排序算法帶來了一種新的思路。

              假如你手里有一枚硬幣。你希望通過拋擲硬幣的方法來決定今天晚上干什么,正面上網(wǎng)反面看電影。投擲硬幣所產(chǎn)生的結(jié)果將給你帶來一些“信息”,這些信息的多少就叫做“信息量”。如果這個硬幣是“公正”的,正面和反面出現(xiàn)的概率一樣,那么投擲硬幣后不管結(jié)果咋樣,你都獲得了1 bit的信息量。如果你事先就已經(jīng)知道這個硬幣并不是均勻的,比如出現(xiàn)正面的概率本來就要大得多,這時我們就說事件結(jié)果的不確定性比剛才更小。如果投擲出來你發(fā)現(xiàn)硬幣果然是正面朝上,這時你得到的信息量就相對更小(小于1 bit);反之如果投擲出來居然反面朝上了,那你就得到了一個相對較大的信息量(大于1 bit)。但平均下來,我們得到的信息量是小于1 bit的,因為前者發(fā)生的可能性畢竟要大一些。最極端的情況就是,這是一枚被搗了鬼的魔術(shù)硬幣,你怎么投都是正面。此時,你投了硬幣等于沒投,反正結(jié)果都是正面朝上,你得到的信息量永遠為0。
              這個理論是很符合生活實際的。昨天晚上我出去吃飯時,坐在我后面的那個人是男的還是女的?這種問題就比較有價值,因為大家都猜不到答案究竟是什么;但要問我昨天跟誰一起出去上自習去了,問題的答案所含的信息量就變小了,因為大家都知道如果我破天荒地跑去自習了的話多半是有MM陪著一起去的。如果有網(wǎng)友問我是男的還是女的,那就更不可思議了,因為我不但多次在這個Blog里提到我一直想找一個合適的MM,還在AboutMe里面發(fā)了我的照片。如果某人剛操完一個MM,突然扭過頭去問“對了,你是男的還是女的呀”,那這個人絕對是一個不折不扣的大傻B,因為這個問題所能帶來的信息量幾乎為0。
              總之,當每種結(jié)果出現(xiàn)的概率都相等,事件的不確定性達到最大,其結(jié)果最難預測時,事件的發(fā)生將會給我們帶來最大的信息量。我們把一個事件的不確定程度叫做“熵”,熵越大表明這個事件的結(jié)果越難以預測,同時事件的發(fā)生將給我們帶來越多的信息。如果在排序算法里每次比較的熵都是最大的,理論上來說這種(基于比較的)排序算法就應當是最優(yōu)的。但我們一會兒將看到,我們已知的排序算法總是不完美的,每種算法都會或多或少地存在一些價值明顯不大的比較。

              首先我們來看三種經(jīng)典的平方復雜度算法。它們的效率并不高,原因就在于算法過程中會出現(xiàn)越來越多概率嚴重不均的比較。隨著冒泡排序的進行,整個序列將變得越來越有序,位置顛倒的泡泡將越來越少;選擇排序的每一趟選擇中,你都會不斷得到越來越大的數(shù),同時在以后的比較中找到更大的數(shù)的概率也越來越低;在插入排序中,你總是把新的數(shù)與已經(jīng)排好的數(shù)按從大到小的順序依次進行比較,可以想到新的數(shù)一開始就比前面所有的數(shù)中最大的那個還大的概率是相當小的。受此啟發(fā),我們可以很自然地想到一個插入排序的改進:處理一個新的數(shù)時,為何不一開始就與前面處理過的數(shù)中的中位數(shù)進行比較?這種比較的熵顯然更大,能獲取的信息量要大得多,明顯更有價值一些。這就是插入排序的二分查找改進。

              下面我們再來看一看幾種O(nlogn)的排序算法。在快速排序算法中,比較的信息熵不會因為排序算法的進行而漸漸減小,這就是快速排序比上面幾個排序算法更優(yōu)秀的根本原因。仔細回顧快速排序算法的過程,我們立即看出,每次比較的兩種結(jié)果出現(xiàn)的概率完全由這一趟劃分過程所選擇的基準關鍵字決定:選擇的基準關鍵字剛好是當前處理的數(shù)字集合的中位數(shù),則比較結(jié)果的不確定性達到最大;如果選擇的基準關鍵字過大或過小,都會出現(xiàn)比較產(chǎn)生的結(jié)果不均等的情況,這使得每次比較平均帶來的信息量大大減少。因此,快速排序算法是很看人品的:如果基準選的好,算法完全有可能達到理論上的最優(yōu);如果基準選的不好,復雜度很容易退化到O(n^2)。
              堆排序所需要的比較次數(shù)更多,因為在堆的刪除操作中有一種明顯不平衡的比較。在刪除操作中,我們把根節(jié)點用整個堆的最末一個節(jié)點來代替,然后不斷下沉直到它的兒子都比它大。判斷它的兩個兒子是否比它大,其信息熵是相當小的,因為這個節(jié)點本身就來自堆的底部,除非這個節(jié)點已經(jīng)沉到很底下了,否則兒子比它大的概率是很小的。因此,我們想到了一個堆排序的優(yōu)化:反正堆建好了以后不需要再插入新元素了,為何不舍棄堆的完全二叉樹性質(zhì)?我們可以直接把根元素改成無窮大,讓它沉到底,不用再考慮兒子比它大的問題了,也不再顧及堆的形狀。這樣的話,堆排序是否就完美了呢?仔細想想你會發(fā)現(xiàn),改進之后的比較操作仍然是不對稱的。這種不對稱主要來自兩個方面:左子樹和右子樹的節(jié)點個數(shù)不同,以及被刪除的根節(jié)點原先是來自左子樹還是右子樹。比方說,根節(jié)點原本就是從右子樹提上來的,現(xiàn)在刪除了根節(jié)點后,左子樹的最小值比右子樹的最小值更小的概率就偏大一些;此時萬一右子樹節(jié)點本來就比左邊少,這樣的話這個比較的熵就更小了。
              最后看一下歸并排序。在有序隊列的合并操作中,絕大多數(shù)情況下的比較操作都是比較平衡的。左邊一半中的最小值和右邊一半中的最小值進行比較,結(jié)果顯然是等概率的。當然,隨后將發(fā)生其中一邊的最小值與另一邊的次小值進行比較,這時的比較操作略微有了一些不平衡,并存在較小的可能使得比較操作變得更加不平衡(最小值與第三小的值相比)。有趣的是,比較越是不平衡,重新歸于平衡的概率就越大,就好像歸并排序中的信息熵會自動調(diào)整一樣。這就是歸并排序比平方復雜度的排序算法效率更高的原因。當然,完全有可能出現(xiàn)這樣的情況:右邊的數(shù)奇小無比,左邊的最小值比右邊的所有值都大。結(jié)果最后右邊的隊列都處理完了左邊還沒開始取數(shù),此時合并操作提前結(jié)束,所花費的比較次數(shù)出人意料地少。從信息熵的角度來看,這種“比較提前結(jié)束”的現(xiàn)象是非常自然的:這種情況畢竟是“出人意料”的,事實越出人意料,獲得的信息量就越大,因此算法就提前結(jié)束了。但這種情況畢竟是相當罕見的,平均情況下每次比較的信息量仍然不足1 bit。

              最后,為什么線性排序的算法可以達到O(n)的復雜度?這是因為,線性排序算法并不是基于比較的。一次比較事件(假設沒有相等的情況)所能產(chǎn)生的信息量最多1 bit,而一次Hash分類可以獲得的信息量遠遠超過了1 bit,因為它可以一次確定出n種等概率的可能情況。

          Matrix67原創(chuàng),轉(zhuǎn)貼請注明出處~~

          posted @ 2009-11-02 16:19 小強摩羯座 閱讀(307) | 評論 (0)編輯 收藏

          傳說中效率最高的最大流算法(Dinic)

          呵呵,又從DK那偷代碼了,好興奮哈,以下是這個算法的簡單介紹,不過我用它去解決HDU的1532 竟然TLE,郁悶.到時候再繼續(xù)問問DK吧...so 煩躁.

          哈哈 終于經(jīng)過大牛的指點 原來本算法是從0開始標號的......

          Dinic是個很神奇的網(wǎng)絡流算法。它是一個基于“層次圖”的時間效率優(yōu)先的最大流算法。

          層次圖是什么東西呢?層次,其實就是從源點走到那個點的最短路徑長度。于是乎,我們得到一個定理:從源點開始,在層次圖中沿著邊不管怎么走,經(jīng)過的路徑一定是終點在剩余圖中的最短路。(摘自WC2007王欣上論文)注意,這里是要按照層次走。

          那么,MPLA(最短路徑增值)的一大堆復雜的證明我就略掉了,有興趣的請自行參閱WC2007王欣上神牛的論文。

          首先我們得知道,Dinic的基本算法步驟是,先算出剩余圖,然后用剩余圖算層次圖,然后在層次圖里找增廣路。不知道你想到?jīng)]有,這個層次圖找增廣路的方法,恰恰就是Ford-Fulkerson類算法的時間耗費最大的地方,就是找一個最短的增廣路。所以呢,層次圖就相當于是一個已經(jīng)預處理好的增廣路標志圖。

          如何實現(xiàn)Dinic呢?

          首先我們必然要判一下有沒有能到達終點的路徑(判存在增廣路與否),在這個過程中我們順便就把層次圖給算出來了(當然不用算完),然后就沿著層次圖一層一層地找增廣路;找到一條就進行增廣(注意在沿著層次圖找增廣路的時候使用棧的結(jié)構(gòu),把路徑壓進棧);增廣完了繼續(xù)找,找不到退棧,然后繼續(xù)找有沒有與這個結(jié)點相連的下一層結(jié)點,直到棧空。如果用遞歸實現(xiàn),這個東西就很好辦了,不過我下面提供的程序是用了模擬棧,當然這樣就不存在結(jié)點數(shù)過多爆棧的問題了……不過寫起來也麻煩了一些,對于“繼續(xù)找”這個過程我專門開了一個數(shù)組存當前搜索的指針。

          上面拉拉雜雜說了一大堆,實際上在我的理解中,層次圖就是一個流從高往低走的過程(這玩意兒有點像預流推進的標號法……我覺得),在一條從高往低的路徑中,自然有些地方會有分叉;這就是Dinic模擬棧中退棧的精華。這就把BFS的多次搜索給省略了不說,時間效率比所謂的理論復雜度要高得多。

          這里有必要說到一點,網(wǎng)絡流的時間復雜度都是很悲觀的,一般情況下絕對沒有可能到達那個復雜度的。

           

          #include<iostream>
          using namespace std;
          const long maxn=300;
          const long maxm=300000;
          const long inf=0x7fffffff;
          struct node
          {
              
          long v,next;
              
          long val;
          }s[maxm
          *2];
          long level[maxn],p[maxn],que[maxn],out[maxn],ind;
          void init()
          {
              ind
          =0;
              memset(p,
          -1,sizeof(p));
          }
          inline 
          void insert(long x,long y,long z)
          {
              s[ind].v
          =y;
              s[ind].val
          =z;
              s[ind].next
          =p[x];
              p[x]
          =ind++;
              s[ind].v
          =x;
              s[ind].val
          =0;
              s[ind].next
          =p[y];
              p[y]
          =ind++;
          }
          inline 
          void insert2(long x,long y,long z)
          {
              s[ind].v
          =y;
              s[ind].val
          =z;
              s[ind].next
          =p[x];
              p[x]
          =ind++;
              s[ind].v
          =x;
              s[ind].val
          =z;
              s[ind].next
          =p[y];
              p[y]
          =ind++;
          }
          long max_flow(long n,long source,long sink)
          {
              long
          ret=0;
              long
           h=0,r=0;
              
          while(1)
              {
                  
          long i;
                  
          for(i=0;i<n;++i)
                      level[i]
          =0;
                  h
          =0,r=0;
                  level[source]
          =1;
                  que[
          0]=source;
                  
          while(h<=r)
                  {
                      
          long t=que[h++];
                      
          for(i=p[t];i!=-1;i=s[i].next)
                      {
                          
          if(s[i].val&&level[s[i].v]==0)
                          {
                              level[s[i].v]
          =level[t]+1;
                              que[
          ++r]=s[i].v;
                          }
                      }
                  }
                  
          if(level[sink]==0)break;
                  
          for(i=0;i<n;++i)out[i]=p[i];
                  
          long q=-1;
                  
          while(1)
                  {
                      
          if(q<0)
                      {
                          
          long cur=out[source];
                          
          for(;cur!=-1;cur=s[cur].next)
                          {
                              
          if(s[cur].val&&out[s[cur].v]!=-1&&level[s[cur].v]==2)
                              {
                                  
          break;
                              }
                          }
                          
          if(cur>=0)
                          {
                              que[
          ++q]=cur;
                              
          out[source]=s[cur].next;
                          }
                          
          else
                          {
                              
          break;
                          }
                      }
                      
          long u=s[que[q]].v;
                      
          if(u==sink)
                      {
                          
          long dd=inf;
                          
          long index=-1;
                          
          for(i=0;i<=q;i++)
                          {
                              
          if(dd>s[que[i]].val)
                              {
                                  dd
          =s[que[i]].val;
                                  index
          =i;
                              }
                          }
                          ret
          +=dd;
                          
          //cout<<ret<<endl;
                          for(i=0;i<=q;i++)
                          {
                              s[que[i]].val
          -=dd;
                              s[que[i]
          ^1].val+=dd;    
                          }
                          
          for(i=0;i<=q;i++)
                          {
                              
          if(s[que[i]].val==0)
                              {
                                  q
          =index-1;
                                  
          break;
                              }
                          }
                      }
                      
          else
                      {
                          
          long cur=out[u];
                          
          for(;cur!=-1;cur=s[cur].next)
                          {
                              
          if(s[cur].val&&out[s[cur].v]!=-1&&level[u]+1==level[s[cur].v])
                              {
                                  
          break;
                              }
                          }
                          
          if(cur!=-1)
                          {
                              que[
          ++q]=cur;
                              
          out[u]=s[cur].next;
                          }
                          
          else
                          {
                              
          out[u]=-1;
                              q
          --;
                          }
                      }
                  }
              }
              
          return ret;
          }

          long m,n;

          int main()
          {

              
          while(scanf("%ld %ld",&m,&n)!=EOF)
              {
                  init();
                  
          for(int i=0;i<n;i++)
                  {
                      
          long from,to,cost;
                      scanf(
          "%ld %ld %ld",&from,&to,&cost);
                      insert(--from,--to,cost);
                  }
                  
          long Start,End;
                  scanf(
          "%ld %ld",&Start,&End);
                  printf(
          "%ld\n",max_flow(n,--Start,--End));
              }
              
          return 0;
          }
          « 上一篇:KM算法(轉(zhuǎn))» 下一篇:字典樹

          posted @ 2009-10-30 14:37 小強摩羯座 閱讀(758) | 評論 (0)編輯 收藏

               摘要: 最短路徑 之 SPFA算法 (轉(zhuǎn)載)(2009-05-06 20:41:51) var $tag='spfa,雜談'; var $tag_code='0c0816ca8a11d99e776ffbef47dd2fd0'; 標簽:spfa 雜談  ...  閱讀全文

          posted @ 2009-10-30 14:00 小強摩羯座 閱讀(2960) | 評論 (0)編輯 收藏

          #include  <stdio.h>
          int add(int x,int y)  {return x+y;}
          int sub(int x,int y)  {return x-y;}
          int mul(int x,int y)  {return x*y;}
          int div(int x,int y)  {return x/y;}
          int (*func[])()={add,sub,mul,div};
          int num,curch;
          char chtbl[]="+-*/()=";
          char corch[]="+-*/()=0123456789";
          int getach()  {
              int i;
              while(1)  {
                  curch=getchar();
                  if(curch==EOF)  return -1;
                  for(i=0;corch[i]&&curch!=corch[i];i++);
                  if(i<strlen(corch))  break;
              }
              return curch;
          }

          int getid()  {
              int i;
              if(curch>='0'&&curch<='9')  {
                  for(num=0;curch>='0'&&curch<='9';getach())    num=10*num+curch-'0';
                  return -1;
              }
              else  {
                  for(i=0;chtbl[i];i++) if(chtbl[i]==curch)  break;
                      if(i<=5)  getach();
                      return i;
              }
          }

          int cal()  {
              int x1,x2,x3,op1,op2,i;
              i=getid();
              if(i==4)    x1=cal();    else  x1=num;
              op1=getid();
              if(op1>=5)  return x1;
              i=getid();
              if(i==4)  x2=cal();    else  x2=num;
              op2=getid();
              while(op2<=4)  {
                  i=getid();
                  if(i==4)  x3=cal();  else  x3=num;
                  if((op1/2==0)&&(op2/2==1))    x2=(*func[op2])(x2,x3);
                  else  {
                      x1=(*func[op1])(x1,x2);
                      x2=x3;
                      op1=op2;
                  }
                  op2=getid();
              }
              return (*func[op1])(x1,x2);
          }

          void main(void)  {
              int value;
              printf("Please input an expression:\n");
              getach();
              while(curch!='=')  {
                  value=cal();
                  printf("The result is:%d\n",value);
                  printf("Please input an expression:\n");
                  getach();
              }
          }

          posted @ 2009-10-30 00:36 小強摩羯座 閱讀(265) | 評論 (0)編輯 收藏

          (一)簡單的函數(shù)指針的應用。
          //形式1:返回類型(*函數(shù)名)(參數(shù)表)
          char (*pFun)(int);
          char glFun(int a){ return;}
          void main()
          {
              pFun = glFun;
              (*pFun)(2);
          }

                  第一行定義了一個指針變量pFun。首先我們根據(jù)前面提到的“形式1”認識到它是一個指向某種函數(shù)的指針,這種函數(shù)參數(shù)是一個int型,返回值是char類型。只有第一句我們還無法使用這個指針,因為我們還未對它進行賦值。
                  第二行定義了一個函數(shù)glFun()。該函數(shù)正好是一個以int為參數(shù)返回char的函數(shù)。我們要從指針的層次上理解函數(shù)——函數(shù)的函數(shù)名實際上就是一個指針,函數(shù)名指向該函數(shù)的代碼在內(nèi)存中的首地址。
                  然后就是可愛的main()函數(shù)了,它的第一句您應該看得懂了——它將函數(shù)glFun的地址賦值給變量pFun。main()函數(shù)的第二句中“*pFun”顯然是取pFun所指向地址的內(nèi)容,當然也就是取出了函數(shù)glFun()的內(nèi)容,然后給定參數(shù)為2。
          (二)使用typedef更直觀更方便。
          //形式2:typedef 返回類型(*新類型)(參數(shù)表)
          typedef char (*PTRFUN)(int);
          PTRFUN pFun;
          char glFun(int a){ return;}
          void main()
          {
              pFun = glFun;
              (*pFun)(2);
          }

                  typedef的功能是定義新的類型。第一句就是定義了一種PTRFUN的類型,并定義這種類型為指向某種函數(shù)的指針,這種函數(shù)以一個int為參數(shù)并返回char類型。后面就可以像使用int,char一樣使用PTRFUN了。
                  第二行的代碼便使用這個新類型定義了變量pFun,此時就可以像使用形式1一樣使用這個變量了。
          (三)在C++類中使用函數(shù)指針。
          //形式3:typedef 返回類型(類名::*新類型)(參數(shù)表)
          class CA
          {
           public:
              char lcFun(int a){ return; }
          };
          CA ca;
          typedef char (CA::*PTRFUN)(int);
          PTRFUN pFun;
          void main()
          {
              pFun = CA::lcFun;
              ca.(*pFun)(2);
          }

                  在這里,指針的定義與使用都加上了“類限制”或“對象”,用來指明指針指向的函數(shù)是那個類的這里的類對象也可以是使用new得到的。比如:
          CA *pca = new CA;
          pca->(*pFun)(2);
          delete pca;

                  而且這個類對象指針可以是類內(nèi)部成員變量,你甚至可以使用this指針。比如:
                  類CA有成員變量PTRFUN m_pfun;
          void CA::lcFun2()

             (this->*m_pFun)(2);
          }

                  一句話,使用類成員函數(shù)指針必須有“->*”或“.*”的調(diào)用。

           

           

          作者:csumck   更新日期:2004-11-07
          來源:CSDN   瀏覽次數(shù):

          posted @ 2009-10-30 00:35 小強摩羯座 閱讀(152) | 評論 (0)編輯 收藏

          系統(tǒng)的可靠度計算公式 收藏
          并聯(lián):1-(1-p1)(1-p2)


          串聯(lián):p1p2


          p1,p2分別為部件1和部件2的可靠度.
          ---------------------------------------------------------------------------
          eg:
          某計算機系統(tǒng)的可靠性結(jié)構(gòu)是如下圖所示的雙重串并聯(lián)結(jié)構(gòu),若所構(gòu)成系統(tǒng)的每個部件的可靠度為0.9 ,即R=0.9 ,則系統(tǒng)的可靠度為()?

          |---(R)————(R)---|
          ———| |--
          |---(R)----(R)---|

          類似于串兩個電阻,在并兩個電阻的圖。問怎樣計算?
           
          最佳答案
          串聯(lián)的可靠度P1=R1×R1 =0.81
          并聯(lián)起來時可靠度P2=1-(1-P1)×(1-P1)=0.9639

          本文來自CSDN博客,轉(zhuǎn)載請標明出處:http://blog.csdn.net/zwhfyy/archive/2009/04/02/4042513.aspx

          posted @ 2009-10-29 11:05 小強摩羯座 閱讀(830) | 評論 (0)編輯 收藏

               摘要: 背包問題 1.引子   我們?nèi)祟愂且环N貪婪的動物,如果給您一個容量一定的背包和一些大小不一的物品,裝到背包里面的物品就歸您,遇到這種好事大家一定不會錯過,用力塞不一定是最好的辦法,用腦子才行,下面就教您如何解決這樣的問題,以獲得更多的獎品。 2.應用場景   在一個物品向量中找到一個子集滿足條件如下 :    1)這個子集加起來的體積大小不能...  閱讀全文

          posted @ 2009-10-28 21:01 小強摩羯座 閱讀(393) | 評論 (0)編輯 收藏

          在今年的信息學冬令營上,陳啟峰提出了一個自己創(chuàng)造的BST數(shù)據(jù)結(jié)構(gòu)—Size Balanced Tree。這個平衡二叉樹被全世界內(nèi)的許多網(wǎng)站所討論,大家討論的主題也只有一個—SBT能夠取代Treap嗎?本文詳細介紹SBT樹的性質(zhì),以及一些常用的操作,最后證明SBT是一顆高度平衡的二分查找樹。

          一.        介紹

          眾所周知,BST能夠快速的實現(xiàn)查找等動態(tài)操作。但是在某些情況下,比如將一個有序的序列依次插入到BST中,則BST會退化成為一條鏈,效率非常之低。由此引申出來很多平衡BST,比如AVL樹,紅黑樹,treap樹等。這些數(shù)據(jù)結(jié)構(gòu)都是通過引入其他一些性質(zhì)來保證BST的高度在最壞的情況下都保持在O(log n)。其中,AVL樹和紅黑樹的很多操作都非常麻煩,因此實際應用不是很多。而treap樹加入了一些隨機化堆的性質(zhì),實際應用效果非常好,實現(xiàn)起來很簡單,一直以來受到很多人的青睞。本文介紹一種新的平衡BST樹,實現(xiàn)起來也是非常之簡單,并且能夠支持更多的操作,實際評測效率跟treap也不差上下。

          在介紹SBT之前,先介紹一下BST以及在BST上的旋轉(zhuǎn)操作。

          1.      Binary Search Tree

          BST是一種高級的數(shù)據(jù)結(jié)構(gòu),它支持很多動態(tài)操作,包括查找,求最小值,最大值,前驅(qū),后繼,插入和刪除,能夠用于字典以及優(yōu)先隊列。

                 BST是一棵二叉樹,每個結(jié)點最多有2個兒子。每個結(jié)點都有個鍵值,并且鍵值必須滿足下面的條件:

                 如果xBST中的一個結(jié)點。那么x的鍵值不小于其左兒子的鍵值,并且不大于其右兒子的鍵值。

                 對于每個結(jié)點t,用left[t]right[t]分別來存放它的兩個兒子,ket[t]存放該結(jié)點的鍵值。另外,在SBT中,要增加s[t],用來保存以t為根的子樹中結(jié)點的個數(shù)。

          2.      旋轉(zhuǎn)

          為了保證BST的平衡(不會退化成為一條鏈),通常通過旋轉(zhuǎn)操作來改變BST的結(jié)構(gòu)。旋轉(zhuǎn)操作不會影響binary-search-tree的性質(zhì)!


           

                 2.1右旋操作的偽代碼

                 右旋操作必須保證左兒子存在

                 Right-Rotate(t)

                        k←left[t]

                        left[t]←right[k]

                        right[k]←t

                        s[k]←s[t]

                        s[t]←s[left[t]]+s[right[t]]+1

                        t←k

                 2.2 左旋操作的偽代碼

                 左旋操作必須保證右兒子存在

                 Left-Rotate(t)

                        k←right[t]

                        right[t]←left[k]

                        left[k]←t

                        s[k]←s[t]

                        s[t]←s[left[t]]+s[right[t]]+1

                        t←k

          二.Size Balanced Tree

          Size Balanced Tree(簡稱SBT)是一種平衡二叉搜索樹,它通過子樹的大小s[t]來維持平衡性質(zhì)。它支持很多動態(tài)操作,并且都能夠在O(log n)的時間內(nèi)完成。

          Insert(t,v)

          將鍵值為v的結(jié)點插入到根為t的樹中

          Delete(t,v)

          在根為t的樹中刪除鍵值為v的結(jié)點

          Find(t,v)

          在根為t的樹中查找鍵值為v的結(jié)點

          Rank(t,v)

          返回根為t的樹中鍵值v的排名。也就是樹中鍵值比v小的結(jié)點數(shù)+1

          Select(t,k)

          返回根為t的樹中排名為k的結(jié)點。同時該操作能夠?qū)崿F(xiàn)Get-min,Get-max,因為Get-min等于Select(t,1),Get-max等于Select(t,s[t])

          Pred(t,v)

          返回根為t的樹中比v小的最大的鍵值

          Succ(t,v)

          返回根為t的樹中比v大的最小的鍵值

          SBT樹中的每個結(jié)點都有leftrightkey以及前面提到的size域。SBT能夠保持平衡性質(zhì)是因為其必須滿足下面兩個條件:

          對于SBT中的每個結(jié)點t,有性質(zhì)(a)(b)

          (a). s[right[t]]≥s[left[left[t]]],s[right[left[t]]]

          (b). s[left[t]]≥s[right[right[t]]],s[left[right[t]]]


           

          即在上圖中,有s[A],s[B]≤s[R]&s[C],s[D] ≤s[L]

          三.              Maintain

          假設我們要在BST中插入一個鍵值為v的結(jié)點,一般是用下面這個過程:

          Simple-Insert(t,v)

                  If t=0 then

                      t←NEW-NODE(v)

                        Else

                               s[t]←s[t]+1

                               If v<key[t] then

                                      Simple-Insert(left[t],v)

                               Else

                                      Simple-Insert(right[t],v)

          執(zhí)行完操作Simple-Insert后,SBT的性質(zhì)(a)(b)就有可能不滿足了,這是我們就需要修復(Maintain)SBT

          Maintain(t)用來修復根為tSBT,使其滿足SBT性質(zhì)。由于性質(zhì)(a)(b)是對稱的,下面僅討論對性質(zhì)(a)的修復。

          Case 1s[left[left[t]]]>s[right[t]]


          這種情況下可以執(zhí)行下面的操作來修復SBT

          執(zhí)行Right-Rotate(T)

           

          有可能旋轉(zhuǎn)后的樹仍然不是SBT,需要再次執(zhí)行Maintain(T)

          由于L的右兒子發(fā)生了變化,因此需要執(zhí)行Maintain(L)

          Case 2s[right[left[t]]]>s[right[t]]

          這種情況如下圖所示:


           

          需要執(zhí)行一下步驟來修復SBT

          執(zhí)行Left-Rotate(L)。如下圖所示


           

          執(zhí)行Right-Rotate(T)。如下圖所示


           

          當執(zhí)行完(1)(2)后,樹的結(jié)構(gòu)變得不可預測了。但是幸運的是,在上圖中,A,E,F,R子樹仍然是SBT。因此我們可以執(zhí)行Maintain(L)Maintain(T)來修復B的子樹。

          Case 3

          這種情況和case 1是對稱的

          Case 4

          這種情況和case 2是對稱的

          Maintain操作的偽代碼:

          Maintain過程中,用一個變量flag來避免額外的檢查。當flagfalse時,代表case 1case 2需要被檢查,否則case 3case 4需要被檢查。

          Maintain (t,flag)

          If flag=false then

                       If s[left[left[t]]>s[right[t]] then

                              Right-Rotate(t)

                       Elseif s[right[left[t]]>s[right[t]] then

                              Left-Rotate(left[t])

                              Right-Rotate(t)

                       Else exit

                Elseif s[right[right[t]]>s[left[t]] then

                       Left-Rotate(t)

                Elseif s[left[right[t]]>s[left[t]] then

                       Right-Rotate(right[t])

                       Left-Rotate(t)

                Else exit

                Maintain(left[t],false)

          Maintain(right[t],true)

          Maintain(t,false)

                Maintain(t,true)

          四.常用操作

          插入操作

          SBT和插入操作和BST的基本相同,只是在插入之后需要執(zhí)行下Maintain操作。

          Insert (t,v)

          If t=0 then

          t←NEW-NODE(v)

          Else

          s[t] ←s[t]+1

          If v<key[t] then

          Simple-Insert(left[t],v)

          Else

          Simple-Insert(right[t],v)

          Maintain(t,v≥key[t])

          刪除操作

          如果沒有找到要刪除的結(jié)點,那么就刪除最后一個訪問的結(jié)點并記錄。

          Delete (t,v)

          If s[t]2 then

          record←key[t]

          t←left[t]+right[t]

          Exit

          s[t] ←s[t]1

          If v=key[t] then

          Delete(left[t],v[t]+1)

          Key[t] ←record

          Maintain(t,true)

          Else

          If v<key[t] then

          Delete(left[t],v)

          Else

          Delete(right[t],v)

          Maintain(t,v<key[t])

          另外,由于SBT的平衡性質(zhì)是靠size域來維護的,而size域本身(子樹所含節(jié)點個數(shù))對于很多查詢算法都特別有用,這樣使得查詢集合里面的譬如第n小的元素,以及一個元素在集合中的排名等操作都異常簡單,并且時間復雜度都穩(wěn)定在O(log n)。下面僅介紹下上表提到的select(t,k)操作和rank(t,v)操作。

                 由于SBT的性質(zhì)(結(jié)點t的關鍵字比其左子樹中所有結(jié)點的關鍵字都大,比其左子樹中所有的關鍵字都小),理解下面的算法非常容易。

          3Select操作

          Select(t,k)

                 If k=s[left[t]]+1 then

                        return key[t]

                 If k<=s[left[t]] then

                        return Select(left[t],k)

                 Else

                        return Select(right[t],k-1-s[left[t]])

          4Rank操作

          Rank(t,v)

                 If t=0 then

                        return 1

                 If v<=key[t] then

                        return rank(left[t],v)

                 Else

                        return s[left[t]]+1+rank(right[t],v)

          同樣,求前驅(qū)結(jié)點的操作Pred和后繼結(jié)點的操作都很容易通過size域來實現(xiàn)。

           

          五.相關證明分析

          顯然Maintain操作是一個遞歸過程,可能你會懷疑它是否會結(jié)束。下面我們可以證明Maintain操作的平攤時間復雜度為O(1)

          1.關于樹的高度的分析

          f[h]表示高度為hSBT中結(jié)點數(shù)目的最小值,則有

                                                                         (h=0)

          f[h]=                                         (h=1)

                     f[h-1]+f[h-2]+1                  (h>1)

          a.證明:

          (1)       很明顯f[0]=1,f[1]=2

          (2)       首先,對于任意h>1,我們假設t是一顆高度為hSBT的根結(jié)點,則這顆SBT包含一顆高度為h-1的子樹。不妨假設t的左子樹的高度為h-1,根據(jù)f[h]的定義,有

          s[left[t] ]≥f[h-1],同樣的,左子樹中有一顆高度為h-2的子樹,換句話說,左子樹中含有一顆結(jié)點數(shù)至少為f[h-2]的子樹。由SBT的性質(zhì)(b),可知s[right[t]] ≥f[h-2]。因此我們有s[t]=s[left[t]]+s[right[t]]+1≥f[h-1]+f[h-2]+1。

          另外一方面,我們可以構(gòu)造一顆高度為h,并且結(jié)點數(shù)正好為f[h]SBT,稱這樣的SBTtree[t]。可以這樣來構(gòu)造tree[h]

                                       含有一個結(jié)點的SBT                                    (h=0)

          tree[h]=     含有2個結(jié)點的任意SBT                             (h=1)

                   左子樹為tree[h-1],右子樹為tree[h-2]SBT    (h>1)

          f[h]的定義可知f[h] f[h-1]+f[h-2]+1(h>1)。因此f[h]的上下界都為f[h-1]+f[h-2]+1,因此有f[h]=f[h-1]+f[h-2]+1

          b.最壞情況下的高度

          事實上f[h]是一個指數(shù)函數(shù),通過f[h]的遞推可以計算出通項公式。


           

          定理:

          含有n個結(jié)點的SBT在最壞情況下的高度是滿足f[h] n的最大的h值。

          假設maxh為含有n個結(jié)點的SBT的最壞情況下的高度。由上面的定理,有

                                            

          于是很明顯SBT的高度為O(logn),是一顆高度平衡的BST

          2.對Maintain操作的分析

          通過前面的計算分析我們能夠很容易分析出Maintain操作是非常高效的。

          首先,有一個非常重要的值來評價一顆BST的好壞:所有結(jié)點的平均深度。它是通過所有結(jié)點的深度之和SD除以結(jié)點個數(shù)n計算出來的。一般來說,這個值越小,這顆BST就越好。由于對于一顆BST來說,結(jié)點數(shù)n是一個常數(shù),因此我們期望SD值越小越好。

          現(xiàn)在我們集中來看SBTSD值,它的重要性在于能夠制約Maintain操作的執(zhí)行時間。回顧先前提到的BST中的旋轉(zhuǎn)操作,有個重要的性質(zhì)就是:每次執(zhí)行旋轉(zhuǎn)操作后,SD值總是遞減的!

          由于SBT樹的高度總是O(log n),因此SD值也總是保持在Olog n)。并且SD僅在插入一個結(jié)點到SBT后才增加,因此(TMaintain操作中執(zhí)行旋轉(zhuǎn)的次數(shù))


           

          Maintain操作的次數(shù)等于T加上不需要旋轉(zhuǎn)操作的Maintain操作的次數(shù)。由于后者為O(nlogn)+O(T),因此Maintain的平攤分析時間復雜度為:


           

          對各個操作時間復雜度的分析

          現(xiàn)在我們知道了SBT的高度為O(log n),并且 Maintain操作的平攤分析時間復雜度為O(1),因此對于所有的常用操作,時間復雜度都穩(wěn)定在O(log n)

          posted @ 2009-10-28 01:07 小強摩羯座 閱讀(480) | 評論 (0)編輯 收藏

               摘要: 目前最快的數(shù)獨求解程序 - 實現(xiàn)了Knuth的Dancing Links+Algorithm X算法 C++語言: 目前最快的數(shù)獨求解程序 - 實現(xiàn)了Knuth的Dancing Links+Algorithm X算法 //from: http://code.google.com/p/klsudoku/source/checkout //半瓶墨水修改于 2009 Sept 18 //R...  閱讀全文

          posted @ 2009-10-26 23:11 小強摩羯座 閱讀(1204) | 評論 (0)編輯 收藏

          僅列出標題
          共20頁: 上一頁 1 2 3 4 5 6 7 8 9 下一頁 Last 
          主站蜘蛛池模板: 米泉市| 汉沽区| 娄底市| 绥滨县| 华容县| 都安| 苗栗县| 兴宁市| 龙口市| 蚌埠市| 武功县| 墨脱县| 扎囊县| 南召县| 全椒县| 泸定县| 宁武县| 五华县| 大埔区| 涟源市| 共和县| 南康市| 从江县| 乐清市| 铜川市| 中卫市| 诸暨市| 江阴市| 沅江市| 古丈县| 湘潭市| 红河县| 子长县| 隆德县| 晋宁县| 开化县| 于田县| 潜江市| 博兴县| 肃宁县| 昌都县|