posts - 403, comments - 310, trackbacks - 0, articles - 7
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          APUE上的例程,稍微改了下,在進程開始和結束的時候會分別顯示當前的PID和退出狀態(tài)。
          不支持參數
          1. fgets命令從標準輸入讀入命令,當鍵入文件結束字符(通常是Ctrl+D)時進程結束
          2. 以\0替換輸入命令的最后一個字符,即去掉換行符,使得execlp能夠處理
          3. fork函數創(chuàng)建一個新進程,對父進程返回新的子進程的非負PID,對子進程返回0
          4. 在子進程中,調用execlp以執(zhí)行從標準輸入讀入的命令。fork和exec的組合產生了一個新進程
          5. 新的子進程開始執(zhí)行后,父進程等待子進程的終止,這一要求由waitpid實現
          6. 執(zhí)行這個程序后還可以在這個簡易shell中創(chuàng)建新的自身的進程

          #include <sys/types.h>
          #include 
          <sys/wait.h>
          #include 
          "ourhdr.h"

          int main(void)
          {
              
          char    buf[MAXLINE];
              pid_t   pid;
              
          int     status;

              printf(
          "%% ");
              
          while (fgets(buf, MAXLINE, stdin) != NULL)
              {
                  buf [strlen(buf) 
          - 1= 0;

                  
          if ( (pid = fork()) < 0)
                      err_sys(
          "fork error");
                  
          else if (pid == 0)
                  {
                      execlp(buf, buf, (
          char *0);
                      err_ret(
          "couldn't execute: %s", buf);
                      exit(
          127);
                  }

                  printf(
          "*** %d ***\n", status);

                  
          /* parent */
                  
          if ( (pid = waitpid(pid, &status, 0)) < 0)
                      err_sys(
          "waitpid error");

                  printf(
          "*** %d ***\n", pid);
                  printf(
          "%% ");
              }
              exit(
          0);
          }

          posted @ 2007-08-04 00:20 ZelluX 閱讀(576) | 評論 (0)編輯 收藏

          將兩個文件放在同一目錄中,修改gpa.sh中的賬號密碼,并用chmod設置為可執(zhí)行,運行即可。
          寫得不怎么樣,像URPParser里處理標簽的時候直接輸出了,很不規(guī)范,不過懶得改了
          urpparser.py:
          from sgmllib import SGMLParser

          class URPParser(SGMLParser):
              
          def reset(self):
                  self.tdOpen 
          = 0
                  self.colCount 
          = -1
                  self.firstRow 
          = 1
                  self.pieces 
          = []
                  SGMLParser.reset(self)

              
          def start_td(self, attrs):
                  
          """
                      When encountered with tag td, check whether there's
                      an align property in the tag, which will distinguish
                      score table from others.
                  
          """
                      
                  
          for (k, v) in attrs:
                      
          if (k == "align"):
                          self.tdOpen 
          = 1
                          
          break

              
          def end_td(self):
                  self.tdOpen 
          = 0

              
          def handle_data(self, text):
                  
          if (self.tdOpen > 0):
                      
          if (len(text.strip()) > 0):
                          self.colCount 
          += 1
                          
          if (self.colCount > 6):
                              self.colCount 
          = 0
                              self.firstRow 
          = 0
                              
          print
                          
          if (self.firstRow):
                              
          return
                          
          if (self.colCount == 2):
                              
          print "\t",
                          
          else:
                              
          print text.strip(),"\t",

          gpa.sh:
          #!/usr/bin/python
          import urllib, cookielib, urllib2

          loginURL 
          = "http://fdis.fudan.edu.cn:58080/amserver/UI/Login?" +\
                     
          "goto=http%3A%2F%2Fwww.urp.fudan.edu.cn%3A84%2Feps" +\
                     
          "tar%2Fapp%2Ffudan%2FframeSub.jsp%3FaffairNO%3D035067"
          scoreURL 
          = "http://www.urp.fudan.edu.cn:84/epstar/app/fudan/S" +\
                     
          "coreManger/ScoreViewer/Student/Course.jsp"
          logoutURL 
          = "http://www.urp.fudan.edu.cn/logout.jsp"

          cookie 
          = cookielib.CookieJar()
          opener 
          = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
          urllib2.install_opener(opener)
          str 
          = urllib.urlencode({'Login.Token1''06301000000''Login.Token2'"yourpassword"})
          sock1 
          = urllib2.urlopen(loginURL, str)
          loginHTML 
          = sock1.read()
          sock1.close()

          sock2 
          = urllib2.urlopen(scoreURL)
          scoreHTML 
          = sock2.read()
          sock2.close()

          sock3 
          = urllib2.urlopen(logoutURL)
          sock3.close()

          from urpparser import URPParser
          parser 
          = URPParser()
          parser.feed(scoreHTML)
          print



          posted @ 2007-08-03 19:50 ZelluX 閱讀(567) | 評論 (0)編輯 收藏

          如果直接編譯myls.c,會報錯
          /tmp/cceQcwN0.o: In function `main':
          myls.c:(.text+0x24): undefined reference to `err_quit'
          myls.c:(.text+0x5b): undefined reference to `err_sys'
          collect2: ld 返回 1

          需要下載隨書附帶的源代碼并編譯所需的庫文件
          源代碼下載:http://www.aygfsteel.com/Files/zellux/apue.linux3.tar.Z.zip
          (下載后去掉.zip后綴)

          apue/README 文件中有具體的編譯方法,對于Linux系統(tǒng)比較簡單,進入lib.rhlin目錄,然后運行make,就會在apue目錄下生成libmisc.a,以后編譯的時候連接這個庫就行了。

          以書中的第一個程序myls.c為例
          #include <sys/types.h>
          #include 
          <dirent.h>

          #include 
          "ourhdr.h"

          int main(int argc, char* argv[])
          {
              DIR             
          *dp;
              
          struct dirent   *dirp;

              
          if (argc != 2)
                  err_quit(
          "a single argument (the directory name) is required");

              
          if ( (dp = opendir(argv[1])) == NULL)
                  err_sys(
          "can't open %s", argv[1]);

              
          while ( (dirp = readdir(dp)) != NULL)
                  printf(
          "%s\n", dirp->d_name);

              closedir(dp);
              exit(
          0);
          }

          $ gcc myls.c libmisc.a
          出現幾個Warning:
          libmisc.a(strerror.o): In function `strerror':
          strerror.c:(.text+0x18): warning: `sys_errlist' is deprecated; use `strerror' or `strerror_r' instead
          strerror.c:(.text+0xf): warning: `sys_nerr' is deprecated; use `strerror' or `strerror_r' instead
          看來這本書的確太老了
          $ ./a.out .
          就會列出當前目錄下的所有文件

          posted @ 2007-08-03 01:13 ZelluX 閱讀(2236) | 評論 (2)編輯 收藏

          《竊聽風暴》的男主角烏爾里希·穆埃(Ulrich Mühe) 病逝了。。。
          好片子,好演員,可惜了。。。

          CSAPP 第七章Linking太枯燥了  啃了半天總算看到一點實際經歷中遇到過的。

          在編譯階段,編譯器把全局變量標記為strong或者weak,并導出到匯編程序中,由匯編程序把這些信息隱式地添加到relocatable object file的符號表(symbol table)中。
          函數和被初始化的全局變量被標記為strong,未初始化的全局變量被標記為weak。
          Unix連接器(linker)使用下面的規(guī)則來處理多個符號的情況:
          1. 不允許多個strong symbol的存在
          2. 如果有一個strong symbol和若干個weak symbol,使用strong symbol
          3. 只有若干個weak symbol,則使用其中任意一個

          幾個例子(未特殊說明的情況,變量定義均在全局范圍):
          1. foo1.c和bar1.c中都有int main()方法,即存在了兩個strong symbol,連接器就會產生一條錯誤信息。
          2.
          /* foo3.c */
          #include 
          <stdio.h>
          void f(void);

          int x = 15213;

          int main()
          {
              f();
              printf(
          "x = %d\n", x);
              
          return 0;
          }

          /* bar3.c */
          int x;

          void f()
          {
              x 
          = 15212;
          }
          main()方法調用f()后,x變?yōu)?5212并被輸出。
          注意這可能不是main()方法的作者原來的意圖。
          類似的情況也可能發(fā)生在兩個weak symbol同名的時候。

          3. 全局變量類型不同的情況:
          /* foo5.c */
          #include 
          <stdio.h>
          void f(void);

          int x = 15213;
          int y = 15212;

          int main()
          {
              f();
              printf(
          "x = 0x%x  y = 0x%x \n", x, y);
              
          return 0;
          }

          /* bar4.c */
          double x;

          void f()
          {
              x 
          = -0.0;
          }
          根據書上的內容,
          linux> gcc -o foobar5 foo5.c bar5.c
          linux> ./foobar5
          結果應該是
          x = 0x0  y = 0x80000000

          但是在自己機器上編譯時報錯了,可能連接器版本較高,會自動找出這種錯誤
          /usr/bin/ld: Warning: alignment 4 of symbol `x' in /tmp/ccupQXSG.o is smaller than 8 in /tmp/ccNNG9XZ.o
          是double和int大小不義導致的對齊問題


          這些問題都比較細小難以被查覺,通常在程序執(zhí)行了一段時間后才出現較嚴重的問題,因此很難被修復,尤其當許多程序員不清楚連接器的工作方式的時候。
          另外可以使用GCC的-warn-common標記(flag),使得它在解析多個同名的全局變量時發(fā)出警告。
          試了下沒成功@@
          gcc --warn-common提示無法識別的命令行選項,gcc -Wall則不會發(fā)出警告。

          posted @ 2007-08-02 23:45 ZelluX 閱讀(874) | 評論 (0)編輯 收藏

          1.基于dictionary的字符串格式化
          >>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"}
          >>> "%(pwd)s" % params
          'secret'
          這個東西的用處在于和locals的搭配使用,比如樣例程序中
          def handle_comment(self, text):
              self.pieces.append(
          "<!--%(text)s-->" % locals()) 

          就讀取了text變量的內容。
          不過這樣和直接用text變量有什么區(qū)別呢?貌似"<!--%s-->" % text也可以啊
          水木上問了一下,得到的答案是
          發(fā)信人: Essien5 (寶貝晶~), 信區(qū): Python
          標  題: Re: 關于locals()的用處
          發(fā)信站: 水木社區(qū) (Thu Aug  2 11:16:37 2007), 轉信

          好處就是多個變量是代碼很好維護,一一對應
          '%s%s.......%s'%(a,b,c,d,....,z)
          '%(a)s%(b)s......%(z)s'%locals()
          第一種寫法前面的%s和后面的變量很難對應起來,bug的源泉
          后一個就非常直觀了
          而且要往中間再隨便插變量也方便


          2. 自己的類繼承了SGMLParser后,需要對特殊標記處理,可以以start_或do_開始命名相關函數。
          可以這樣做的原因在于python的自醒機制(introspection)

              def finish_starttag(self, tag, attrs):
                  
          try:
                      method 
          = getattr(self, 'start_' + tag)
                  
          except AttributeError:
                      
          try:
                          method 
          = getattr(self, 'do_' + tag)
                      
          except AttributeError:
                          self.unknown_starttag(tag, attrs)
                          
          return -1
                      
          else:
                          self.handle_starttag(tag, method, attrs)
                          
          return 0
                  
          else:
                      self.stack.append(tag)
                      self.handle_starttag(tag, method, attrs)
                      
          return 1
          程序首先嘗試獲得start_tagname的方法,如果失敗則繼續(xù)嘗試獲得do_tagname,如果仍然不能找到,則調用unknown_starttag方法。
          感覺這和Java的反射機制很相似,例如Javabean中的getter setter方法,也是通過特殊命名的形式讓其他對象了解自己的。

          3. import 語句可以寫在任何地方。

          posted @ 2007-08-02 00:30 ZelluX 閱讀(336) | 評論 (0)編輯 收藏

          Software版進不去了,一進去就會自動退出,WEB模式下一看是有個名字亂碼的文章

          2976  plantxue  Aug 1 19:54 ?鶳鹛鶳鷴鶳鵂遭遇文件名不合法 (85)
          2977  basic  Aug 1 20:08  Adobe.CS3.Production.Premium.Activation-AGAiN (55)
          2978  basic  Aug 1 20:09  ?鶳鹛鶳鷴鶳鵂遭遇文件名不合法 (49)

          看來web模式下發(fā)主題帶有特殊字符的帖子可以起到一定的效果的

          posted @ 2007-08-01 20:21 ZelluX 閱讀(343) | 評論 (0)編輯 收藏

          前 言
          軟件質量是被大多數程序員掛在嘴上而不是放在心上的東西!
          除了完全外行和真正的編程高手外,初讀本書,你最先的感受將是驚慌:“哇!我以前捏造的C++/C 程序怎么會有那么多的毛病?”
          別難過,作者只不過比你早幾年、多幾次驚慌而已。
          請花一兩個小時認真閱讀這本百頁經書,你將會獲益匪淺,這是前面N-1 個讀者的建議。
          http://www.aygfsteel.com/Files/zellux/c.zip

          posted @ 2007-08-01 18:16 ZelluX 閱讀(395) | 評論 (0)編輯 收藏

          轉一篇wikipedia上的文章
          http://zh.wikipedia.org/wiki/%E7%B4%85%E9%BB%91%E6%A8%B9

          紅黑樹是一種自平衡二叉查找樹,是在計算機科學中用到的一種數據結構,典型的用途是實現關聯(lián)數組。它是在1972年Rudolf Bayer發(fā)明的,他稱之為"對稱二叉B樹",它現代的名字是在 Leo J. Guibas 和 Robert Sedgewick1978年寫的一篇論文中獲得的。它是復雜的,但它的操作有著良好的最壞情況運行時間,并且在實踐中是高效的: 它可以在O(log n)時間內做查找,插入和刪除,這里的n是樹中元素的數目。

          用途和好處

          紅黑樹和AVL樹一樣都對插入時間、刪除時間和查找時間提供了最好可能的最壞情況擔保。這不只是使它們在時間敏感的應用如即時應用(real time application)中有價值,而且使它們有在提供最壞情況擔保的其他數據結構中作為建造板塊的價值;例如,在計算幾何中使用的很多數據結構都可以基于紅黑樹。

          紅黑樹在函數式編程中也特別有用,在這里它們是最常用的持久數據結構之一,它們用來構造關聯(lián)數組集合,在突變之后它們能保持為以前的版本。除了O(log n)的時間之外,紅黑樹的持久版本對每次插入或刪除需要O(log n)的空間。

          紅黑樹是 2-3-4樹的 一種等同。換句話說,對于每個 2-3-4 樹,都存在至少一個數據元素是同樣次序的紅黑樹。在 2-3-4 樹上的插入和刪除操作也等同于在紅黑樹中顏色翻轉和旋轉。這使得 2-3-4 樹成為理解紅黑樹背后的邏輯的重要工具,這也是很多介紹算法的教科書在紅黑樹之前介紹 2-3-4 樹的原因,盡管 2-3-4 樹在實踐中不經常使用。



          性質

          紅黑樹是每個節(jié)點都帶有顏色屬性的二叉查找樹,顏色或紅色黑色。在二叉查找樹強制一般要求以外,對于任何有效的紅黑樹我們增加了如下的額外要求:

          性質1. 節(jié)點是紅色或黑色。

          性質2. 根是黑色。

          性質3. 所有葉子都是黑色(包括NIL)。

          性質4. 每個紅色節(jié)點的兩個子節(jié)點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續(xù)的紅色節(jié)點)

          性質5. 從任一節(jié)點到其每個葉子的所有路徑都包含相同數目的黑色節(jié)點。

          An example of a red-black tree

          這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑不多于最短的可能路徑的兩倍長。結果是這個樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的 高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同于普通的二叉查找樹。

          要知道為什么這些特性確保了這個結果,注意到屬性4導致了路徑不能有兩個毗連的紅色節(jié)點就足夠了。最短的可能路徑都是黑色節(jié)點,最長的可能路徑有交替的紅色和黑色節(jié)點。因為根據屬性5所有最長的路徑都有相同數目的黑色節(jié)點,這就表明了沒有路徑能多于任何其他路徑的兩倍長。

          在很多樹數據結構的表示中,一個節(jié)點有可能只有一個子節(jié)點,而葉子節(jié)點包含數據。用這種范例表示紅黑樹是可能的,但是這會改變一些屬性并使算法復 雜。為此,本文中我們使用 "nil 葉子" 或"空(null)葉子",如上圖所示,它不包含數據而只充當樹在此結束的指示。這些節(jié)點在繪圖中經常被省略,導致了這些樹好象同上述原則相矛盾,而實際 上不是這樣。與此有關的結論是所有節(jié)點都有兩個子節(jié)點,盡管其中的一個或兩個可能是空葉子。


          操作

          因為每一個紅黑樹也是一個特化的二叉查找樹,因此紅黑樹上的只讀操作與普通二叉查找樹上的只讀操作相同。然而,在紅黑樹上進行插入操作和刪除操作會導致不再符合紅黑樹的性質?;謴图t黑樹的屬性需要少量(O(log n))的顏色變更(實際是非??焖俚?和不超過三次樹旋轉(對于插入操作是兩次)。雖然插入和刪除很復雜,但操作時間仍可以保持為 O(log n) 次。

          插入

          我們首先以二叉查找樹的方法增 加節(jié)點并標記它為紅色。(如果設為黑色,就會導致根到葉子的路徑上有一條路上,多一個額外的黑節(jié)點,這個是很難調整的。但是設為紅色節(jié)點后,可能會導致出 現兩個連續(xù)紅色節(jié)點的沖突,那么可以通過顏色調換(color flips)和樹旋轉來調整。) 下面要進行什么操作取決于其他臨近節(jié)點的顏色。同人類的家族樹中一樣,我們將使用術語叔父節(jié)點來指一個節(jié)點的父節(jié)點的兄弟節(jié)點。注意:

          • 性質1[1]和性質3[2]總是保持著。
          • 性質4[3]只在增加紅色節(jié)點、重繪黑色節(jié)點為紅色,或做旋轉時受到威脅。
          • 性質5[4]只在增加黑色節(jié)點、重繪紅色節(jié)點為黑色,或做旋轉時受到威脅。

          在下面的示意圖中,將要插入的節(jié)點標為N,N的父節(jié)點標為P,N的祖父節(jié)點標為G,N的叔父節(jié)點標為U。在圖中展示的任何顏色要么是由它所處情形所作的假定,要么是這些假定所暗含的。

          對于每一種情況,我們將使用 C示例代碼來展示。通過下列函數,可以找到一個節(jié)點的叔父和祖父節(jié)點:

          node grandparent(node n) {
          return n->parent->parent;
          }

          node uncle(node n) {
          if (n->parent == grandparent(n)->left)
          return grandparent(n)->right;
          else
          return grandparent(n)->left;
          }

          情形1: 新節(jié)點N位于樹的根上,沒有父節(jié)點。在這種情形下,我們把它重繪為黑色以滿足性質2[5]。因為它在每個路徑上對黑節(jié)點數目增加一,性質5[4]符合。

          void insert_case1(node n) {
          if (n->parent == NULL)
          n->color = BLACK;
          else
          insert_case2(n);
          }

          情形2: 新節(jié)點的父節(jié)點P是黑色,所以性質4[3]沒有失效(新節(jié)點是紅色的)。在這種情形下,樹仍是有效的。性質5[4]受到威脅,因為新節(jié)點N有兩個黑色葉子兒子;但是由于新節(jié)點N是紅色,通過它的每個子節(jié)點的路徑就都有同通過它所取代的黑色的葉子的路徑同樣數目的黑色節(jié)點,所以這個性質依然滿足。

          void insert_case2(node n) {
          if (n->parent->color == BLACK)
          return; /* 樹仍舊有效 */
          else
          insert_case3(n);
          }

          注意: 在下列情形下我們假定新節(jié)點有祖父節(jié)點,因為父節(jié)點是紅色;并且如果它是根,它就應當是黑色。所以新節(jié)點總有一個叔父節(jié)點,盡管在情形4和5下它可能是葉子。

          情況 3 示意圖

          情形3: 如果父節(jié)點P和叔父節(jié)點U二者都是紅色,則我們可以將它們兩個重繪為黑色并重繪祖父節(jié)點G為紅色(用來保持性質5[4])。現在我們的新節(jié)點N有了一個黑色的父節(jié)點P。因為通過父節(jié)點P或叔父節(jié)點U的任何路徑都必定通過祖父節(jié)點G,在這些路徑上的黑節(jié)點數目沒有改變。但是,紅色的祖父節(jié)點G的父節(jié)點也有可能是紅色的,這就違反了性質4[3]。為了解決這個問題,我們在祖父節(jié)點G上遞歸地進行情形1的整個過程。

          void insert_case3(node n) {
          if (uncle(n) != NULL && uncle(n)->color == RED) {
          n->parent->color = BLACK;
          uncle(n)->color = BLACK;
          grandparent(n)->color = RED;
          insert_case1(grandparent(n));
          }
          else
          insert_case4(n);
          }

          注意: 在余下的情形下,我們假定父節(jié)點P 是其父親G 的左子節(jié)點。如果它是右子節(jié)點,情形4情形5中的應當對調。

          情況 4 示意圖

          情形4: 父節(jié)點P是紅色而叔父節(jié)點U是黑色或缺少; 還有,新節(jié)點N是其父節(jié)點P的右子節(jié)點,而父節(jié)點P又是其父節(jié)點的左子節(jié)點。在這種情形下,我們進行一次左旋轉調換新節(jié)點和其父節(jié)點的角色; 接著,我們按情形5處理以前的父節(jié)點P。這導致某些路徑通過它們以前不通過的新節(jié)點N或父節(jié)點P中的一個,但是這兩個節(jié)點都是紅色的,所以性質5[4]沒有失效。

          void insert_case4(node n) {
          if (n == n->parent->right && n->parent == grandparent(n)->left) {
          rotate_left(n->parent);
          n = n->left;
          } else if (n == n->parent->left && n->parent == grandparent(n)->right) {
          rotate_right(n->parent);
          n = n->right;
          }
          insert_case5(n);
          }
          情況 5 示意圖

          情形5: 父節(jié)點P是紅色而叔父節(jié)點U 是黑色或缺少,新節(jié)點N 是其父節(jié)點的左子節(jié)點,而父節(jié)點P又是其父節(jié)點G的左子節(jié)點。在這種情形下,我們進行針對祖父節(jié)點P 的一次右旋轉; 在旋轉產生的樹中,以前的父節(jié)點P現在是新節(jié)點N和以前的祖父節(jié)點G 的父節(jié)點。我們知道以前的祖父節(jié)點G是黑色,否則父節(jié)點P就不可能是紅色。我們切換以前的父節(jié)點P和祖父節(jié)點G的顏色,結果的樹滿足性質4[3]。性質5[4]也仍然保持滿足,因為通過這三個節(jié)點中任何一個的所有路徑以前都通過祖父節(jié)點G ,現在它們都通過以前的父節(jié)點P。在各自的情形下,這都是三個節(jié)點中唯一的黑色節(jié)點。

          void insert_case5(node n) {
          n->parent->color = BLACK;
          grandparent(n)->color = RED;
          if (n == n->parent->left && n->parent == grandparent(n)->left) {
          rotate_right(grandparent(n));
          } else {
          /* Here, n == n->parent->right && n->parent == grandparent(n)->right */
          rotate_left(grandparent(n));
          }
          }

          注意插入實際上是原地算法,因為上述所有調用都使用了尾部遞歸

          [編輯] 刪除

          如果需要刪除的節(jié)點有兩個兒子,那么問題可以被轉化成刪除另一個只有一個兒子的節(jié)點的問題(為了表述方便,這里所指的兒子,為非葉子節(jié)點的兒子)。對于二叉查找樹,在刪除帶有兩個非葉子兒子的節(jié)點的時候,我們找到要么在它的左子樹中的最大元素、要么在它的右子樹中的最小元素,并把它的值轉移到要刪除的節(jié)點中(如在這里所展示的那樣)。我們接著刪除我們從中復制出值的那個節(jié)點,它必定有少于兩個非葉子的兒子。因為只是復制了一個值而不違反任何屬性,這就把問題簡化為如何刪除最多有一個兒子的節(jié)點的問題。它不關心這個節(jié)點是最初要刪除的節(jié)點還是我們從中復制出值的那個節(jié)點。

          在本文余下的部分中,我們只需要討論刪除只有一個兒子的節(jié)點(如果它兩個兒子都為空,即均為葉子,我們任意將其中一個看作它的兒 子)。如果我們刪除一個紅色節(jié)點,它的父親和兒子一定是黑色的。所以我們可以簡單的用它的黑色兒子替換它,并不會破壞屬性3和4。通過被刪除節(jié)點的所有路 徑只是少了一個紅色節(jié)點,這樣可以繼續(xù)保證屬性5。另一種簡單情況是在被刪除節(jié)點是黑色而它的兒子是紅色的時候。如果只是去除這個黑色節(jié)點,用它的紅色兒 子頂替上來的話,會破壞屬性4,但是如果我們重繪它的兒子為黑色,則曾經通過它的所有路徑將通過它的黑色兒子,這樣可以繼續(xù)保持屬性4。

          需要進一步討論的是在要刪除的節(jié)點和它的兒子二者都是黑色的時候,這是一種復雜的情況。我們首先把要刪除的節(jié)點替換為它的兒子。出于方便,稱呼這個兒子為N,稱呼它的兄弟(它父親的另一個兒子)為S。在下面的示意圖中,我們還是使用P稱呼N的父親,SL稱呼S的左兒子,SR稱呼S的右兒子。我們將使用下述函數找到兄弟節(jié)點:

          node sibling(node n) {
          if (n == n->parent->left)
          return n->parent->right;
          else
          return n->parent->left;
          }

          我們可以使用下列代碼進行上述的概要步驟,這里的函數 replace_node 替換 childn 在樹中的位置。出于方便,在本章節(jié)中的代碼將假定空葉子被用不是 NULL 的實際節(jié)點對象來表示(在插入章節(jié)中的代碼可以同任何一種表示一起工作)。

          void delete_one_child(node n) {
          /* Precondition: n has at most one non-null child */
          node child = (is_leaf(n->right)) ? n->left : n->right;
          replace_node(n, child);
          if (n->color == BLACK) {
          if (child->color == RED)
          child->color = BLACK;
          else
          delete_case1(child);
          }
          free(n);
          }

          如果 N 和它初始的父親是黑色,則刪除它的父親導致通過 N 的路徑都比不通過它的路徑少了一個黑色節(jié)點。因為這違反了屬性 4,樹需要被重新平衡。有幾種情況需要考慮:

          情況 1: N 是新的根。在這種情況下,我們就做完了。我們從所有路徑去除了一個黑色節(jié)點,而新根是黑色的,所以屬性都保持著。

          void delete_case1(node n) {
          if (n->parent == NULL)
          return;
          else
          delete_case2(n);
          }

          注意: 在情況2、5和6下,我們假定 N 是它父親的左兒子。如果它是右兒子,則在這些情況下的應當對調。

          情況 2 示意圖

          情況 2: S 是紅色。在這種情況下我們在N的父親上做左旋轉,把紅色兄弟轉換成N的祖父。我們接著對調 N 的父親和祖父的顏色。盡管所有的路徑仍然有相同數目的黑色節(jié)點,現在 N 有了一個黑色的兄弟和一個紅色的父親,所以我們可以接下去按 4、5或6情況來處理。(它的新兄弟是黑色因為它是紅色S的一個兒子。)

          void delete_case2(node n) {
          if (sibling(n)->color == RED) {
          n->parent->color = RED;
          sibling(n)->color = BLACK;
          if (n == n->parent->left)
          rotate_left(n->parent);
          else
          rotate_right(n->parent);
          }
          delete_case3(n);
          }
          情況 3 示意圖

          情況 3: N 的父親、S 和 S 的兒子都是黑色的。在這種情況下,我們簡單的重繪 S 為紅色。結果是通過S的所有路徑, 它們就是以前通 過 N 的那些路徑,都少了一個黑色節(jié)點。因為刪除 N 的初始的父親使通過 N 的所有路徑少了一個黑色節(jié)點,這使事情都平衡了起來。但是,通過 P 的所有路徑現在比不通過 P 的路徑少了一個黑色節(jié)點,所以仍然違反屬性4。要修正這個問題,我們要從情況 1 開始,在 P 上做重新平衡處理。

          void delete_case3(node n) {
          if (n->parent->color == BLACK &&
          sibling(n)->color == BLACK &&
          sibling(n)->left->color == BLACK &&
          sibling(n)->right->color == BLACK)
          {
          sibling(n)->color = RED;
          delete_case1(n->parent);
          }
          else
          delete_case4(n);
          }
          情況 4 示意圖

          情況 4: S 和 S 的兒子都是黑色,但是 N 的父親是紅色。在這種情況下,我們簡單的交換 N 的兄弟和父親的顏色。這不影響不通過 N 的路徑的黑色節(jié)點的數目,但是它在通過 N 的路徑上對黑色節(jié)點數目增加了一,添補了在這些路徑上刪除的黑色節(jié)點。

          void delete_case4(node n) {
          if (n->parent->color == RED &&
          sibling(n)->color == BLACK &&
          sibling(n)->left->color == BLACK &&
          sibling(n)->right->color == BLACK)
          {
          sibling(n)->color = RED;
          n->parent->color = BLACK;
          }
          else
          delete_case5(n);
          }
          情況 5 示意圖

          情況 5: S 是黑色,S 的左兒子是紅色,S 的右兒子是黑色,而 N 是它父親的左兒子。在這種情況下我們在 S 上做右旋轉,這樣 S 的左兒子成為 S 的父親和 N 的新兄弟。我們接著交換 S 和它的新父親的顏色。所有路徑仍有同樣數目的黑色節(jié)點,但是現在 N 有了一個右兒子是紅色的黑色兄弟,所以我們進入了情況 6。N 和它的父親都不受這個變換的影響。

          void delete_case5(node n) {
          if (n == n->parent->left &&
          sibling(n)->color == BLACK &&
          sibling(n)->left->color == RED &&
          sibling(n)->right->color == BLACK)
          {
          sibling(n)->color = RED;
          sibling(n)->left->color = BLACK;
          rotate_right(sibling(n));
          }
          else if (n == n->parent->right &&
          sibling(n)->color == BLACK &&
          sibling(n)->right->color == RED &&
          sibling(n)->left->color == BLACK)
          {
          sibling(n)->color = RED;
          sibling(n)->right->color = BLACK;
          rotate_left(sibling(n));
          }
          delete_case6(n);
          }
          情況 6 示意圖

          情況 6: S 是黑色,S 的右兒子是紅色,而 N 是它父親的左兒子。在這種情況下我們在 N 的父親上做左旋轉,這樣 S 成為 N 的父親和 S 的右兒子的父親。我們接著交換 N 的父親和 S 的顏色,并使 S 的右兒子為黑色。子樹在它的根上的仍是同樣的顏色,所以屬性 3 沒有被違反。但是,N 現在增加了一個黑色祖先: 要幺 N 的父親變成黑色,要么它是黑色而 S 被增加為一個黑色祖父。所以,通過 N 的路徑都增加了一個黑色節(jié)點。

          此時,如果一個路徑不通過 N,則有兩種可能性:

          • 它通過 N 的新兄弟。那么它以前和現在都必定通過 S 和 N 的父親,而它們只是交換了顏色。所以路徑保持了同樣數目的黑色節(jié)點。
          • 它通過 N 的新叔父,S 的右兒子。那么它以前通過 S、S 的父親和 S 的右兒子,但是現在只通過 S,它被假定為它以前的父親的顏色,和 S 的右兒子,它被從紅色改變?yōu)楹谏:铣尚Ч沁@個路徑通過了同樣數目的黑色節(jié)點。

          在任何情況下,在這些路徑上的黑色節(jié)點數目都沒有改變。所以我們恢復了屬性 4。在示意圖中的白色節(jié)點可以是紅色或黑色,但是在變換前后都必須指定相同的顏色。

          void delete_case6(node n) {
          sibling(n)->color = n->parent->color;
          n->parent->color = BLACK;
          if (n == n->parent->left) {
          /* Here, sibling(n)->color == BLACK &&
          sibling(n)->right->color == RED */
          sibling(n)->right->color = BLACK;
          rotate_left(n->parent);
          }
          else
          {
          /* Here, sibling(n)->color == BLACK &&
          sibling(n)->left->color == RED */
          sibling(n)->left->color = BLACK;
          rotate_right(n->parent);
          }
          }

          同樣的,函數調用都使用了尾部遞歸,所以算法是就地的。此外,在旋轉之后不再做遞歸調用,所以進行了恒定數目(最多 3 次)的旋轉。




          漸進邊界的證明

          包含n個內部節(jié)點的紅黑樹的高度是 O(log(n))。

          定義:

          • h(v) = 以節(jié)點v為根的子樹的高度。
          • bh(v) = 從v到子樹中任何葉子的黑色節(jié)點的數目(如果v是黑色則不計數它)(也叫做黑色高度)。

          引理: 以節(jié)點v為根的子樹有至少2bh(v) − 1個內部節(jié)點。

          引理的證明(通過歸納高度):

          基礎: h(v) = 0

          如果v的高度是零則它必定是 nil,因此 bh(v) = 0。所以:

          2bh(v) − 1 = 20 − 1 = 1 − 1 = 0

          歸納假設: h(v) = k 的v有 2bh(v) − 1 − 1 個內部節(jié)點暗示了 h(v') = k+1 的 v'有2bh(v') − 1 個內部節(jié)點。

          因為 v' 有 h(v') > 0 所以它是個內部節(jié)點。同樣的它有黑色高度要么是 bh(v') 要么是 bh(v')-1 (依據v'是紅色還是黑色)的兩個兒子。通過歸納假設每個兒子都有至少 2bh(v') − 1 − 1 個內部接點,所以 v' 有:

          2bh(v') − 1 − 1 + 2bh(v') − 1 − 1 + 1 = 2bh(v') − 1

          個內部節(jié)點。

          使用這個引理我們現在可以展示出樹的高度是對數性的。因為在從根到葉子的任何路徑上至少有一半的節(jié)點是黑色(根據紅黑樹屬性4),根的黑色高度至少是h(root)/2。通過引理我們得到:

          n \geq 2^{{h(root) \over 2}} - 1 \leftrightarrow \; \log{(n+1)} \geq {h(root) \over 2} \leftrightarrow \; h(root) \leq 2\log{(n+1)}

          因此根的高度是O(log(n))。

          posted @ 2007-08-01 00:01 ZelluX 閱讀(489) | 評論 (0)編輯 收藏

          from smth.org

          = malloc(n * sizeof(int));
          for (i = 0; i < n; i++)
             p[i] 
          = i;

          output(p, n);

          for (i = n - 1; i > 0; i--)
             
          if (p[i] > p[i - 1])
             {
                
          for (j = n - 1; p[j] < p[i - 1]; j--);
                swap(
          &(p[i - 1]), &(p[j]));

                
          for (j = i, k = n - 1; j < k; j++, k--)
                   swap(
          &(p[j]), &(p[k]));

                ouput(p, n);
                i 
          = n;
             }

          free(p);

          posted @ 2007-07-31 11:58 ZelluX 閱讀(475) | 評論 (0)編輯 收藏

          把總長度求出來,然后用和背包問題類似的dp算法求是否可以達到這個總長度的一半,但是超時了。
          找到一個優(yōu)化方案,結果0ms就過了,orz
          http://162.105.81.202/course/problemSolving/dividingProve.doc
          以下論證均轉自這篇文章。
           結論 對于任意一種珠寶的個數n,如果n>=8, 可以將n改寫為 11(n為奇數) 或 12(n為偶數)。

          證明:

          對于任意一組數,6的個數為n(n>=8)

          一、如果可以分成兩堆,我們可以分成兩種情況:
             1.
                兩堆里都有6,那么我們可知:把n改為n-2,仍然可分。
          (兩堆各減一個6)
          2. 只有一堆里有6,設為左邊,那么左邊的總和不小于6*8=48。
          我們觀察,5*6=6*5 ,4*3=6*2 , 3*2=6 , 2*3=6 , 1*6=6
          而 5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45 < 48
          由抽屜原理右邊必然存在
          (多于5個的5 或者 多于2個的4 或者 多于1個的3
          或者 多于2個的2 或者 多于5個的1)
          即右邊至少存在一組數的和等于若干個6,比如右邊有3個4,這樣把左邊的2個6與右邊的3個4交換,則又出現左右都有6的情況。 根據1,我們還是可以把n改為n-2且可分的狀態(tài)不變。
          綜合1,2。我們可以看出只要原來n的個數=8,我們就可以把它改為n-2,這樣操作一直進行到n<8。我們可以得出結論,對于大于等于8的偶數,可以換成6。
          對于大于8的奇數,可以換成7。換完之后仍然可分。

          二、如果不能分成兩堆:
          顯然改為n-2時同樣也不能分,那么對于大于等于8的偶數,可以換成6;對于大于8的奇數,可以換成7。換完之后仍然不可分。

          綜合一、二,我們得出結論把不小于8的偶數改為8,大于8的奇數改為7,原來可分與否的性質不會改變。

          以上是對6的討論,同樣的方法可以推出
          5的個數 6*4 + 4*4 + 3*4 + 2*4 + 1*4 = 64 < 5*13
          即5的個數多于12時,偶數換為12,奇數換為11
          4的個數 6*1 + 5*3 + 3*3 + 2*1 + 1*3 = 35 < 4*9
          即4的個數多于8時,偶數換為8,奇數換為7
          3的個數 5*2 + 4*2 + 2*2 + 1*2 = 24 < 3*9
          即3的個數多于8時,偶數換為8,奇數換為7
          2的個數 5*1 + 3*1 + 1*1 = 9 < 2*5
          即2的個數多于4時,偶數換為4,奇數換為3
          1的個數 多于5則必然可分(在總數是偶數的前提下)

          綜上所述,
          對于任意一種珠寶的個數n,如果n>=8, 可以將n改寫為 11(n為奇數) 或 12(n為偶數)。

          進一步分析:
          對每個數(1-6),以上只是粗略的估計,可以進一步減少其最大有效取值,例如,
          對于6,5*5 + 4*2 + 3*1 + 2*2 + 1*5 = 25 + 8 + 3 + 4 + 5 = 45
          就有4和2不能同時出現,5和1不能同時出現,3個5和1個3不能同時出現,4個5不能和1個4同時出現等等,所以組合不出6的整數倍的情況的總價值至多為25,所以當6的個數大于6時,奇數可改為5,偶數可改為6。
          1-5 也有類似情況。

          為了得出精確值,下面先我們討論這樣一個數論命題。

          命題:
          可重復的從自然數集中取出n個數(n>=2),其中必有若干個數之和能被n整除。

          證明:設取出的n個自然數為a1,a2,a3,.....an

          考慮這樣的n+1個數 0, a1, a1+a2 , a1+a2+a3 , ...... , a1+a2+a3+...+an, 由于自然數模n的剩余類有n個,所以以上n+1個數中必有兩個同余。 這兩個數的差必被n整除,而且這兩個數的差就是原來的n個數中的一些數的和。
          這就證明了命題。

          由以上命題
          對于6而言,我們至多從{1,2,3,4,5}中可重復的找出5個數使它們不能組合成6的倍數。
          所以這些數的和小于等于5*5=25
          對于5而言,我們至多從{1,2,3,4,6}中可重復的找出4個數使它們不能組合成5的倍數。
          所以這些數的和小于等于6*4=24
          對于4而言,我們至多從{1,2,3,5,6}中可重復的找出3個數使它們不能組合成4的倍數。
          所以這些數的和小于等于3*6=18 , 然而,兩個6就是4的倍數, 所以最多有一個6
          此時不能有兩個5(2*5+6=16是4的倍數), 最多才6 + 5 + 3 = 14 < 3*5 =15
          所以這些數的和小于等于3*5=15
          對于3而言,我們至多從{1,2,4,5,6}中可重復的找出2個數使它們不能組合成3的倍數。
          所以這些數的和小于等于2*5=10

          (6就是3的倍數,所以不能取6)

          對于2而言,我們至多從{1,3,4,5,6}中可重復的找出1個數使它們不能組合成6的倍數。

          所以這些數的和小于等于1*5=5



          考慮到 4*6 < 25 < 5*6 , 我們可以算出6的最大有效個數為5 。

          考慮到 4*5 < 24 < 5*5 , 我們可以算出5的最大有效個數為5 。但是其實應該修正為6, 如果遇到如下特殊情況,左邊5個6,右邊6個5。此時雖然左右可以交換,但是交換后仍然只有一邊有5,與(一、2)中討論情況不符。

          考慮到 3*4 < 15 < 4*4 , 我們可以算出5的最大有效個數為4 。但是其實應該修正為5, 如果遇到如下特殊情況,左邊4個5,右邊5個4。此時雖然左右可以交換,但是交換后仍然只有一邊有4,與(一、2)中討論情況不符。

          考慮到 3*3 < 10 < 4*3 , 我們可以算出5的最大有效個數為4 。但是其實應該修正為5, 如果遇到如下特殊情況,左邊3個5,右邊5個3。此時雖然左右可以交換,但是交換后仍然只有一邊有3,與(一、2)中討論情況不符。

          考慮到 2*2 < 5 < 3*2 , 我們可以算出5的最大有效個數為3 。 但是其實應該修正為4,如果遇到如下特殊情況,左邊1個3和1個5,右邊4個2。此時雖然左右可以交換,但是交換后仍然只有一邊有2,與(一、2)中討論情況不符。


          我們得出最后的精確結論:

          奇數改為 偶數改為
          6的個數大于5 5 4
          5的個數大于6 5 6
          4的個數大于5 5 4
          3的個數大于5 5 4
          2的個數大于4 3 4 



          優(yōu)化后的代碼:
          #include <iostream>
          using namespace std;

          long n[6];
          long sum;
          const long MAX_N = 60000;

          int dividable()
          {
              
          int f[MAX_N];
              
          for (int i = 0; i <= sum; i++)
                  f[i] 
          = 0;
              f[
          0= 1;
              
          for (int i = 0; i < 6; i++)
              {
                  
          for (int j = 1; j <= n[i]; j++)
                  {
                      
          int base = j * (i + 1);
                      
          if (base > sum) break;
                      
          for (int k = sum - (i+1); k >= base - i - 1; k--)
                          
          if (f[k])
                              f[k 
          + i + 1= 1;
                      
          if (f[sum]) return 1;
                  }
              }
              
          return f[sum];
          }

          int main()
          {
              
          long cases = 0;
              
          while (true)
              {
                  sum 
          = 0;
                  
          for (long i = 0; i < 6; i++)
                  {
                      cin 
          >> n[i];
                  }
                  
          if (n[5> 5) n[5= 4 + n[5% 2;
                  
          if (n[4> 6) n[4= 6 - n[4% 2;
                  
          if (n[3> 5) n[3= 4 + n[3% 2;
                  
          if (n[2> 5) n[2= 4 + n[2% 2;
                  
          if (n[1> 4) n[1= 4 - n[1% 2;
                  
          for (long i = 0; i < 6; i++)
                  {
                      sum 
          += n[i] * (i + 1);
                  }
                  
          if (sum == 0)
                      
          break;
                  cases
          ++;
                  cout 
          << "Collection #" << cases << ":\n";
                  
          if (sum % 2 != 0)
                  {
                      cout 
          << "Can't be divided.\n\n";
                      
          continue;
                  }
                  sum 
          /= 2;
                  
          if (dividable())
                      cout 
          << "Can be divided.\n";
                  
          else
                      cout 
          << "Can't be divided.\n";
                  cout 
          << endl;
              }
              
          return 0;
          }


          posted @ 2007-07-30 19:59 ZelluX 閱讀(2788) | 評論 (3)編輯 收藏

          僅列出標題
          共39頁: First 上一頁 17 18 19 20 21 22 23 24 25 下一頁 Last 
          主站蜘蛛池模板: 龙海市| 沙洋县| 丹巴县| 新建县| 南郑县| 河曲县| 五家渠市| 西畴县| 哈尔滨市| 扎囊县| 屯留县| 镇平县| 荥经县| 华宁县| 临夏市| 潼南县| 金川县| 云龙县| 宜宾市| 洞口县| 濉溪县| 闸北区| 托里县| 武隆县| 苏州市| 凤山市| 麻城市| 朝阳区| 汶上县| 册亨县| 东海县| 常德市| 黄龙县| 金湖县| 光泽县| 贵阳市| 静海县| 梅河口市| 邓州市| 林周县| 富民县|