乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      排序算法一覽

       lchjczw 2013-01-07

      10.1基本概念
      排序(Sorting)是計算機程序設(shè)計中的一種重要操作,其功能是對一個數(shù)據(jù)元素集合或序列重新排列成一個按數(shù)據(jù)元素某個項值有序的序列。作為排序依據(jù)的數(shù)據(jù)項稱為排序碼,也即數(shù)據(jù)元素的關(guān)鍵碼。為了便于查找,通常希望計算機中的數(shù)據(jù)表是按關(guān)鍵碼有序的。如有序表的折半查找,查找效率較高。還有,二叉排序樹、B-樹和B+樹的構(gòu)造過程就是一個排序過程。若關(guān)鍵碼是主關(guān)鍵碼,則對于任意待排序序列,經(jīng)排序后得到的結(jié)果是唯一的;若關(guān)鍵碼是次關(guān)鍵碼,排序結(jié)果可能不唯一,這是因為具有相同關(guān)鍵碼的數(shù)據(jù)元素,這些元素在排序結(jié)果中,它們之間的的位置關(guān)系與排序前不能保持。
      若對任意的數(shù)據(jù)元素序列,使用某個排序方法,對它按關(guān)鍵碼進行排序:若相同關(guān)鍵碼元素間的位置關(guān)系,排序前與排序后保持一致,稱此排序方法是穩(wěn)定的;而不能保持一致的排序方法則稱為不穩(wěn)定的。

      排序分為兩類:內(nèi)排序和外排序。
      內(nèi)排序:指待排序列完全存放在內(nèi)存中所進行的排序過程,適合不太大的元素序列。
      外排序:指排序過程中還需訪問外存儲器,足夠大的元素序列,因不能完全放入內(nèi)存,只能使用外排序。

      10.2插入排序
      10.2.1
      直接插入排序
      設(shè)有n個記錄,存放在數(shù)組r中,重新安排記錄在數(shù)組中的存放順序,使得按關(guān)鍵碼有序。即
      r[1].key≤r[2].key≤……≤r[n].key

      先來看看向有序表中插入一個記錄的方法:
      設(shè)1<n,r[1].key≤r[2].key≤……≤r[j-1].key,將r[j]插入,重新安排存放順序,使得r[1].key≤r[2].key≤……≤r[j].key,得到新的有序表,記錄數(shù)增1。

      【算法10.1
      r[0]=r[j]; //r[j]r[0]中,使r[j]為待插入記錄空位
      i=j-1
      ; //從第i個記錄向前測試插入位置,用r[0]為輔助單元, 可免去測試i<1
      r[0].key≥r[i].key,轉(zhuǎn)。 //插入位置確定
      r[0].key < r[i].key時,
      r[i+1]=r[i]
      i=i-1;轉(zhuǎn) //調(diào)整待插入位置
      r[i+1]=r[0];結(jié)束。 //存放待插入記錄
      【例10.1】向有序表中插入一個記錄的過程如下:
      r[1] r[2] r[3] r[4] r[5]
      存儲單元
      2 10 18 25 9
      r[5]插入四個記錄的有序表中,j=5
      r[0]=r[j]
      ;i=j-1 初始化,設(shè)置待插入位置
      2 10 18 25 □ r[i+1]
      為待插入位置
      i=4
      ,r[0] < r[i]r[i+1]=r[i];i--; 調(diào)整待插入位置
      2 10 18 □ 25
      i=3
      ,r[0] < r[i],r[i+1]=r[i];i--; 調(diào)整待插入位置
      2 10 □ 18 25
      i=2
      ,r[0] < r[i]r[i+1]=r[i];i-- 調(diào)整待插入位置
      2 □ 10 18 25
      i=1
      ,r[0] ≥r[i]r[i+1]=r[0]; 插入位置確定,向空位填入插入記錄
      2 9 10 18 25
      向有序表中插入一個記錄的過程結(jié)束

      直接插入排序方法:僅有一個記錄的表總是有序的,因此,對n個記錄的表,可從第二個記錄開始直到第n個記錄,逐個向有序表中進行插入操作,從而得到n個記錄按關(guān)鍵碼有序的表。

      【算法10.2
      void InsertSort(S_TBL &p)
      { for(i=2
      ;i<=p->length;i++)
      if(p->elem[i].key < p->elem[i-1].key) /*
      小于時,需將elem[i]插入有序表*/
      { p->elem[0].key=p->elem[i].key
      ; /*為統(tǒng)一算法設(shè)置監(jiān)測*/
      for(j=i-1
      p->elem[0].key < p->elem[j].key;j--)
      p->elem[j+1].key=p->elem[j].key
      /*記錄后移*/
      p->elem[j+1].key=p->elem[0].key
      ; /*插入到正確位置*/
      }
      }
      【效率分析】
      空間效率:僅用了一個輔助單元。
      時間效率:向有序表中逐個插入記錄的操作,進行了n-1趟,每趟操作分為比較關(guān)鍵碼和移動記錄,而比較的次數(shù)和移動記錄的次數(shù)取決于待排序列按關(guān)鍵碼的初始排列。
      最好情況下:即待排序列已按關(guān)鍵碼有序,每趟操作只需1次比較2次移動。
      總比較次數(shù)=n-1
      總移動次數(shù)=2(n-1)
      最壞情況下:即第j趟操作,插入記錄需要同前面的j個記錄進行j次關(guān)鍵碼比較,移動記錄的次數(shù)為j+2次。
      平均情況下:即第j趟操作,插入記錄大約同前面的j/2個記錄進行關(guān)鍵碼比較,移動記錄的次數(shù)為j/2+2次。
      由此,直接插入排序的時間復(fù)雜度為O(n2)。是一個穩(wěn)定的排序方法。

      10.2.2折半插入排序
      直接插入排序的基本操作是向有序表中插入一個記錄,插入位置的確定通過對有序表中記錄按關(guān)鍵碼逐個比較得到的。平均情況下總比較次數(shù)約為n2/4。既然是在有序表中確定插入位置,可以不斷二分有序表來確定插入位置,即一次比較,通過待插入記錄與有序表居中的記錄按關(guān)鍵碼比較,將有序表一分為二,下次比較在其中一個有序子表中進行,將子表又一分為二。這樣繼續(xù)下去,直到要比較的子表中只有一個記錄時,比較一次便確定了插入位置。
      二分判定有序表插入位置方法:
      low=1;high=j-1r[0]=r[j]; // 有序表長度為j-1,第j個記錄為待插入記錄
      //
      設(shè)置有序表區(qū)間,待插入記錄送輔助單元
      low>high,得到插入位置,轉(zhuǎn)
      low≤high,m=(low+high)/2 // 取表的中點,并將表一分為二,確定待插入?yún)^(qū)間*/
      r[0].key<r[m].keyhigh=m-1 //插入位置在低半?yún)^(qū)
      否則,low=m+1; // 插入位置在高半?yún)^(qū)
      轉(zhuǎn)
      high+1即為待插入位置,從j-1high+1的記錄,逐個后移,r[high+1]=r[0];放置待插入記錄。

      【算法10.3
      void InsertSort(S_TBL *s)
      { /*
      對順序表s作折半插入排序 */
      for(i=2
      ;i<=s->lengthi++)
      { s->elem[0]=s->elem[i]
      ; /* 保存待插入元素 */
      low=i
      ;high=i-1; /* 設(shè)置初始區(qū)間 */
      while(low<=high) /*
      該循環(huán)語句完成確定插入位置 */
      { mid=(low+high)/2
      ;
      if(s->elem[0].key>s->elem[mid].key)
      low=mid+1
      ; /* 插入位置在高半?yún)^(qū)中 */
      else high=mid-1
      ; /* 插入位置在低半?yún)^(qū)中 */
      }/* while */
      for(j=i-1
      j>=high+1j--) /* high+1為插入位置 */
      s->elem[j+1]=s->elem[j]
      ; /* 后移元素,留出插入空位 */
      s->elem[high+1]=s->elem[0]
      /* 將元素插入 */
      }/* for */
      }/* InsertSort */
      【時間效率】
      確定插入位置所進行的折半查找,關(guān)鍵碼的比較次數(shù)至多為 ,次,移動記錄的次數(shù)和直接插入排序相同,故時間復(fù)雜度仍為O(n2)。是一個穩(wěn)定的排序方法。
      10.2.3
      表插入排序
      直接插入排序、折半插入排序均要大量移動記錄,時間開銷大。若要不移動記錄完成排序,需要改變存儲結(jié)構(gòu),進行表插入排序。所謂表插入排序,就是通過鏈接指針,按關(guān)鍵碼的大小,實現(xiàn)從小到大的鏈接過程,為此需增設(shè)一個指針項。操作方法與直接插入排序類似,所不同的是直接插入排序要移動記錄,而表插入排序是修改鏈接指針。用靜態(tài)鏈表來說明。
      #define SIZE 200
      typedef struct{
      ElemType elem
      ; /*元素類型*/
      int next
      /*指針項*/
      }NodeType
      ; /*表結(jié)點類型*/
      typedef struct{
      NodeType r[SIZE]
      /*靜態(tài)鏈表*/
      int length
      ; /*表長度*/
      }L_TBL
      /*靜態(tài)鏈表類型*/
      假設(shè)數(shù)據(jù)元素已存儲在鏈表中,且0號單元作為頭結(jié)點,不移動記錄而只是改變鏈指針域,將記錄按關(guān)鍵碼建為一個有序鏈表。首先,設(shè)置空的循環(huán)鏈表,即頭結(jié)點指針域置0,并在頭結(jié)點數(shù)據(jù)域中存放比所有記錄關(guān)鍵碼都大的整數(shù)。接下來,逐個結(jié)點向鏈表中插入即可。
      【例10.2】表插入排序示例

      MAXINT 49 38 65 97 76 13 27 49
      0 - - - - - - - -

      MAXINT 49 38 65 97 76 13 27 49
      1 0 - - - - - - -

      MAXINT 49 38 65 97 76 13 27 49
      2 0 1 - - - - - -

      MAXINT 49 38 65 97 76 13 27 49
      2 3 1 0 - - - - -

      MAXINT 49 38 65 97 76 13 27 49
      2 3 1 4 0 - - - -

      MAXINT 49 38 65 97 76 13 27 49
      2 3 1 5 0 4 - - -

      MAXINT 49 38 65 97 76 13 27 49
      6 3 1 5 0 4 2 - -

      MAXINT 49 38 65 97 76 13 27 49
      6 3 1 5 0 4 7 2 -

      MAXINT 49 38 65 97 76 13 27 49
      6 8 1 5 0 4 7 2 3

      10.1
      表插入排序得到一個有序的鏈表,查找則只能進行順序查找,而不能進行隨機查找,如折半查找。為此,還需要對記錄進行重排。
      重排記錄方法:按鏈表順序掃描各結(jié)點,將第i個結(jié)點中的數(shù)據(jù)元素調(diào)整到數(shù)組的第i個分量數(shù)據(jù)域。因為第i個結(jié)點可能是數(shù)組的第j個分量,數(shù)據(jù)元素調(diào)整僅需將兩個數(shù)組分量中數(shù)據(jù)元素交換即可,但為了能對所有數(shù)據(jù)元素進行正常調(diào)整,指針域也需處理。
      【算法10.3
      1. j=l->r[0].next
      ;i=1 //指向第一個記錄位置,從第一個記錄開始調(diào)整
      2.
      i=l->length時,調(diào)整結(jié)束;否則,
      a.
      i=j,j=l->r[j].next;i++;轉(zhuǎn)(2) //數(shù)據(jù)元素應(yīng)在這分量中,不用調(diào)整,處理下一個結(jié)點
      b.
      j>i,l->r[i].elem<-->l->r[j].elem; //交換數(shù)據(jù)元素
      p=l->r[j].next
      ; // 保存下一個結(jié)點地址
      l->r[j].next=l->[i].next
      ;l->[i].next=j; // 保持后續(xù)鏈表不被中斷
      j=p
      ;i++;轉(zhuǎn)(2) // 指向下一個處理的結(jié)點
      c.
      j<i,while(j<i) j=l->r[j].next//j分量中原記錄已移走,沿j的指針域找尋原記錄的位置
      轉(zhuǎn)到(a)
      【例10.3】對表插入排序結(jié)果進行重排示例

      MAXINT 49 38 65 97 76 13 27 49
      6 8 1 5 0 4 7 2 3

      MAXINT 13 38 65 97 76 49 27 49
      6 (6) 1 5 0 4 8 2 3

      MAXINT 13 27 65 97 76 49 38 49
      6 (6) (7) 5 0 4 8 1 3

      MAXINT 13 27 38 97 76 49 65 49
      6 (6) (7) (7) 0 4 8 5 3

      MAXINT 13 27 38 49 76 97 65 49
      6 (6) (7) (7) (6) 4 0 5 3

      MAXINT 13 27 38 49 49 97 65 76
      6 (6) (7) (7) (6) (8) 0 5 4

      MAXINT 13 27 38 49 49 65 97 76
      6 (6) (7) (7) (6) (8) (7) 0 4

      MAXINT 13 27 38 49 49 65 76 97
      6 (6) (7) (7) (6) (8) (7) (8) 0

      10.2
      【時效分析】
      表插入排序的基本操作是將一個記錄插入到已排好序的有序鏈表中,設(shè)有序表長度為i,則需要比較至多i+1次,修改指針兩次。因此,總比較次數(shù)與直接插入排序相同,修改指針總次數(shù)為2n次。所以,時間復(fù)雜度仍為O(n2)

      10.2.4希爾排序(Shell’s Sort)
      希爾排序又稱縮小增量排序,是1959年由D.L.Shell提出來的,較前述幾種插入排序方法有較大的改進。
      直接插入排序算法簡單,在n值較小時,效率比較高,在n值很大時,若序列按關(guān)鍵碼基本有序,效率依然較高,其時間效率可提高到O(n)。希爾排序即是從這兩點出發(fā),給出插入排序的改進方法。
      希爾排序方法:
      1.
      選擇一個步長序列t1,t2,,tk,其中ti>tj,tk=1;
      2.
      按步長序列個數(shù)k,對序列進行k趟排序;
      3.
      每趟排序,根據(jù)對應(yīng)的步長ti,將待排序列分割成若干長度為m的子序列,分別對各子表進行直接插入排序。僅步長因子為1時,整個序列作為一個表來處理,表長度即為整個序列的長度。

      【例10.4】待排序列為 39,80,76,4113,29,50,78,30,11100,741,86
      步長因子分別取5、3、1,則排序過程如下:
      p=5 39 80 76 41 13 29 50 78 30 11 100 7 41 86
      └─────────┴─────────┘
      └─────────┴──────────┘
      └─────────┴──────────┘
      └─────────┴──────────┘
      └─────────┘
      子序列分別為{39,29100},{8050,7}{7678,41},{41,30,86},{13,11}。

      第一趟排序結(jié)果:
      p=3 29 7 41 30 11 39 50 76 41 13 100 80 78 86
      └─────┴─────┴─────┴──────┘
      └─────┴─────┴─────┴──────┘
      └─────┴─────┴──────┘
      子序列分別為{29,3050,13,78},{7,1176,100,86}{41,39,41,80}
      第二趟排序結(jié)果:
      p=1 13 7 39 29 11 41 30 76 41 50 86 80 78 100
      此時,序列基本有序,對其進行直接插入排序,得到最終結(jié)果:
      7 11 13 29 30 39 41 41 50 76 78 80 86 100

      10.3

      【算法10.5
      void ShellInsert(S_TBL &p
      ,int dk)
      { /*
      一趟增量為dk的插入排序,dk為步長因子*/
      for(i=dk+1
      ;i<=p->lengthi++)
      if(p->elem[i].key < p->elem[i-dk].key) /*
      小于時,需elem[i]將插入有序表*/
      { p->elem[0]=p->elem[i]
      /*為統(tǒng)一算法設(shè)置監(jiān)測*/
      for(j=i-dk
      ;j>0&&p->elem[0].key < p->elem[j].key;j=j-dk)
      p->elem[j+dk]=p->elem[j]
      /*記錄后移*/
      p->elem[j+dk]=p->elem[0]
      ; /*插入到正確位置*/
      }
      }

      void ShellSort(S_TBL *p,int dlta[],int t)
      { /*
      按增量序列dlta[0,1…t-1]對順序表*p作希爾排序*/
      for(k=0
      ;k<t;t++)
      ShellSort(p
      ,dlta[k]); /*一趟增量為dlta[k]的插入排序*/
      }

      【時效分析】
      希爾排序時效分析很難,關(guān)鍵碼的比較次數(shù)與記錄移動次數(shù)依賴于步長因子序列的選取,特定情況下可以準(zhǔn)確估算出關(guān)鍵碼的比較次數(shù)和記錄的移動次數(shù)。目前還沒有人給出選取最好的步長因子序列的方法。步長因子序列可以有各種取法,有取奇數(shù)的,也有取質(zhì)數(shù)的,但需要注意:步長因子中除1外沒有公因子,且最后一個步長因子必須為1。希爾排序方法是一個不穩(wěn)定的排序方法。

      10.3 交換排序
      交換排序主要是通過兩兩比較待排記錄的關(guān)鍵碼,若發(fā)生與排序要求相逆,則交換之。
      10.3.1
      冒泡排序(Bubble Sort)
      先來看看待排序列一趟冒泡的過程:設(shè)1<j≤n,r[1],r[2],···,r[j]為待排序列,
      通過兩兩比較、交換,重新安排存放順序,使得r[j]是序列中關(guān)鍵碼最大的記錄。一趟冒泡方法為:
      i=1; //設(shè)置從第一個記錄開始進行兩兩比較
      i≥j,一趟冒泡結(jié)束。
      比較r[i].keyr[i+1].key,若r[i].key≤r[i+1].key,不交換,轉(zhuǎn)
      當(dāng)r[i].key>r[i+1].key時, r[0]=r[i];r[i]=r[i+1]r[i+1]=r[0]
      r[i]r[i+1]交換
      i=i+1; 調(diào)整對下兩個記錄進行兩兩比較,轉(zhuǎn)
      冒泡排序方法:對n個記錄的表,第一趟冒泡得到一個關(guān)鍵碼最大的記錄r[n],第二趟冒泡對n-1個記錄的表,再得到一個關(guān)鍵碼最大的記錄r[n-1],如此重復(fù),直到n個記錄按關(guān)鍵碼有序的表。
      【算法10.6
      j=n //n記錄的表開始
      j<2,排序結(jié)束
      i=1 //一趟冒泡,設(shè)置從第一個記錄開始進行兩兩比較,
      i≥j,一趟冒泡結(jié)束,j=j-1;冒泡表的記錄數(shù)-1,轉(zhuǎn)
      比較r[i].keyr[i+1].key,若r[i].key≤r[i+1].key,不交換,轉(zhuǎn)
      當(dāng)r[i].key>r[i+1].key時, r[i]<-->r[i+1]; r[i]r[i+1]交換
      i=i+1; 調(diào)整對下兩個記錄進行兩兩比較,轉(zhuǎn)
      【效率分析】
      空間效率:僅用了一個輔助單元。
      時間效率:總共要進行n-1趟冒泡,對j個記錄的表進行一趟冒泡需要j-1次關(guān)鍵碼比較。
      移動次數(shù):
      最好情況下:待排序列已有序,不需移動。
      10.3.2
      快速排序
      快速排序是通過比較關(guān)鍵碼、交換記錄,以某個記錄為界(該記錄稱為支點),將待排序列分成兩部分。其中,一部分所有記錄的關(guān)鍵碼大于等于支點記錄的關(guān)鍵碼,另一部分所有記錄的關(guān)鍵碼小于支點記錄的關(guān)鍵碼。我們將待排序列按關(guān)鍵碼以支點記錄分成兩部分的過程,稱為一次劃分。對各部分不斷劃分,直到整個序列按關(guān)鍵碼有序。
      一次劃分方法:
      設(shè)1≤p<q≤n,r[p],r[p+1],...,r[q]為待排序列
      low=phigh=q; //設(shè)置兩個搜索指針,low是向后搜索指針,high是向前搜索指針
      r[0]=r[low]
      ; //取第一個記錄為支點記錄,low位置暫設(shè)為支點空位
      low=high,支點空位確定,即為low。
      r[low]=r[0]
      //填入支點記錄,一次劃分結(jié)束
      否則,low<high,搜索需要交換的記錄,并交換之
      low<highr[high].key≥r[0].key //high所指位置向前搜索,至多到low+1位置
      high=high-1
      ;轉(zhuǎn) //尋找r[high].key<r[0].key
      r[low]=r[high]
      ; //找到r[high].key<r[0].key,設(shè)置high為新支點位置,
      //
      小于支點記錄關(guān)鍵碼的記錄前移。
      low<highr[low].key<r[0].key //low所指位置向后搜索,至多到high-1位置
      low=low+1
      ;轉(zhuǎn) //尋找r[low].key≥r[0].key
      r[high]=r[low]
      //找到r[low].key≥r[0].key,設(shè)置low為新支點位置,
      //
      大于等于支點記錄關(guān)鍵碼的記錄后移。
      轉(zhuǎn) //繼續(xù)尋找支點空位

      【算法10.7
      int Partition(S_TBL *tbl,int low,int high) /*
      一趟快排序*/
      { /*
      交換順序表tbl中子表tbl->[low…h(huán)igh]的記錄,使支點記錄到位,并反回其所在位置*/
      /*
      此時,在它之前()的記錄均不大()于它*/
      tbl->r[0]=tbl->r[low]; /*
      以子表的第一個記錄作為支點記錄*/
      pivotkey=tbl->r[low].key; /*
      取支點記錄關(guān)鍵碼*/
      while(low<higu) /*
      從表的兩端交替地向中間掃描*/
      { while(low<high&&tbl->r[high].key>=pivotkey) high--;
      tbl->r[low]=tbl->r[high]; /*
      將比支點記錄小的交換到低端*/
      while(low<high&&tbl-g>r[high].key<=pivotkey) low++;
      tbl->r[low]=tbl->r[high]; /*
      將比支點記錄大的交換到低端*/
      }
      tbl->r[low]=tbl->r[0]; /*
      支點記錄到位*/
      return low; /*
      反回支點記錄所在位置*/
      }
      【例10.5】一趟快排序過程示例
      r[1] r[2] r[3] r[4] r[5] r[6] r[7] r[8] r[9] r[10]
      存儲單元
      49 14 38 74 96 65 8 49 55 27
      記錄中關(guān)鍵碼
      low=1
      ;high=10; 設(shè)置兩個搜索指針, r[0]=r[low]; 支點記錄送輔助單元,
      □ 14 38 74 96 65 8 49 55 27
      ↑ ↑
      low high
      第一次搜索交換
      high向前搜索小于r[0].key的記錄,得到結(jié)果
      27 14 38 74 96 65 8 49 55 □
      ↑ ↑
      low high
      low向后搜索大于r[0].key的記錄,得到結(jié)果
      27 14 38 □ 96 65 8 49 55 74
      ↑ ↑
      low high
      第二次搜索交換
      high向前搜索小于r[0].key的記錄,得到結(jié)果
      27 14 38 8 96 65 □ 49 55 74
      ↑ ↑
      low high
      low向后搜索大于r[0].key的記錄,得到結(jié)果
      27 14 38 8 □ 65 96 49 55 74
      ↑ ↑
      low high
      第三次搜索交換
      high向前搜索小于r[0].key的記錄,得到結(jié)果
      27 14 38 8 □ 65 96 49 55 74
      ↑↑
      low high
      low向后搜索大于r[0].key的記錄,得到結(jié)果
      27 14 38 8 □ 65 96 49 55 74
      ↑↑
      low high
      low=high
      ,劃分結(jié)束,填入支點記錄
      27 14 38 8 49 65 96 49 55 74

      【算法10.8
      void QSort(S_TBL *tbl,int low,int high) /*
      遞歸形式的快排序*/
      { /*
      對順序表tbl中的子序列tbl->[low…h(huán)igh]作快排序*/
      if(low<high)
      { pivotloc=partition(tbl,low,high); /*
      將表一分為二*/
      QSort(tbl,low,pivotloc-1); /*
      對低子表遞歸排序*/
      QSort(tbl,pivotloc+1,high); /*
      對高子表遞歸排序*/
      }
      }


      【效率分析】
      空間效率:快速排序是遞歸的,每層遞歸調(diào)用時的指針和參數(shù)均要用棧來存放,遞歸調(diào)用層次數(shù)與上述二叉樹的深度一致。因而,存儲開銷在理想情況下為O(log2n),即樹的高度;在最壞情況下,即二叉樹是一個單鏈,為O(n)
      時間效率:在n個記錄的待排序列中,一次劃分需要約n次關(guān)鍵碼比較,時效為O(n),若設(shè)T(n)為對n個記錄的待排序列進行快速排序所需時間。
      理想情況下:每次劃分,正好將分成兩個等長的子序列,則

      T(n)≤cn+2T(n/2) c是一個常數(shù)
      ≤cn+2(cn/2+2T(n/4))=2cn+4T(n/4)
      ≤2cn+4(cn/4+T(n/8))=3cn+8T(n/8)
      ······
      ≤cnlog2n+nT(1)=O(nlog2n)

      最壞情況下:即每次劃分,只得到一個子序列,時效為O(n2)。
      快速排序是通常被認(rèn)為在同數(shù)量級(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按關(guān)鍵碼有序或基本有序時,快排序反而蛻化為冒泡排序。為改進之,通常以三者取中法來選取支點記錄,即將排序區(qū)間的兩個端點與中點三個記錄關(guān)鍵碼居中的調(diào)整為支點記錄。快速排序是一個不穩(wěn)定的排序方法。
      10.4
      選擇排序
      選擇排序主要是每一趟從待排序列中選取一個關(guān)鍵碼最小的記錄,也即第一趟從n個記錄中選取關(guān)鍵碼最小的記錄,第二趟從剩下的n-1個記錄中選取關(guān)鍵碼最小的記錄,直到整個序列的記錄選完。這樣,由選取記錄的順序,便得到按關(guān)鍵碼有序的序列。
      10.4.1
      簡單選擇排序
      操作方法:第一趟,從n個記錄中找出關(guān)鍵碼最小的記錄與第一個記錄交換;第二趟,從第二個記錄開始的n-1個記錄中再選出關(guān)鍵碼最小的記錄與第二個記錄交換;如此,第i趟,則從第i個記錄開始的n-i+1個記錄中選出關(guān)鍵碼最小的記錄與第i個記錄交換,直到整個序列按關(guān)鍵碼有序。

      【算法10.9
      void SelectSort(S_TBL *s)
      { for(i=1
      ;i<s->lengthi++)
      { /*
      length-1趟選取 */
      for(j=i+1
      ,t=i;j<=s->lengthj++)
      { /*
      i開始的length-n+1個記錄中選關(guān)鍵碼最小的記錄 */
      if(s->elem[t].key>s->elem[j].key)
      t=j
      ; /* t中存放關(guān)鍵碼最小記錄的下標(biāo) */
      }
      s->elem[t]<-->s->elem[i]
      /* 關(guān)鍵碼最小的記錄與第i個記錄交換 */
      }
      }

      從程序中可看出,簡單選擇排序移動記錄的次數(shù)較少,但關(guān)鍵碼的比較次數(shù)依然是

      10.4.2樹形選擇排序
      按照錦標(biāo)賽的思想進行,將n個參賽的選手看成完全二叉樹的葉結(jié)點,則該完全二叉樹有2n-22n-1個結(jié)點。首先,兩兩進行比賽(在樹中是兄弟的進行,否則輪空,直接進入下一輪),勝出的再兄弟間再兩兩進行比較,直到產(chǎn)生第一名;接下來,將作為第一名的結(jié)點看成最差的,并從該結(jié)點開始,沿該結(jié)點到根路徑上,依次進行各分枝結(jié)點子女間的比較,勝出的就是第二名。因為和他比賽的均是剛剛輸給第一名的選手。如此,繼續(xù)進行下去,直到所有選手的名次排定。

      【例10.616個選手的比賽(n=24)

      10.5


      10.6

      10.5中,從葉結(jié)點開始的兄弟間兩兩比賽,勝者上升到父結(jié)點;勝者兄弟間再兩兩比賽,直到根結(jié)點,產(chǎn)生第一名91。比較次數(shù)為 23+22+21+20=24-1=n-1。
      10.6中,將第一名的結(jié)點置為最差的,與其兄弟比賽,勝者上升到父結(jié)點,勝者兄弟間再比賽,直到根結(jié)點,產(chǎn)生第二名83。比較次數(shù)為4,即log2n次。其后各結(jié)點的名次均是這樣產(chǎn)生的,所以,對于n個參賽選手來說,即對1,故時間復(fù)雜度為O(nlog2n)。該方法占用空間較多,除需輸出排序結(jié)果的n個單元外,尚需n-1個輔助單元。-n+1)log2n-n個記錄進行樹形選擇排序,總的關(guān)鍵碼比較次數(shù)至多為(n

      10.4.3 堆排序(Heap Sort)
      設(shè)有n個元素的序列 k1,k2,kn,當(dāng)且僅當(dāng)滿足下述關(guān)系之一時,稱之為堆。


      10.7 兩個堆示例

      若以一維數(shù)組存儲一個堆,則堆對應(yīng)一棵完全二叉樹,且所有非葉結(jié)點的值均不大于(或不小于)其子女的值,根結(jié)點的值是最小(或最大)的。
      設(shè)有n個元素,將其按關(guān)鍵碼排序。首先將這n個元素按關(guān)鍵碼建成堆,將堆頂元素輸出,得到n個元素中關(guān)鍵碼最小(或最大)的元素。然后,再對剩下的n-1個元素建成堆,輸出堆頂元素,得到n個元素中關(guān)鍵碼次小(或次大)的元素。如此反復(fù),便得到一個按關(guān)鍵碼有序的序列。稱這個過程為堆排序。
      因此,實現(xiàn)堆排序需解決兩個問題:
      1.
      如何將n個元素的序列按關(guān)鍵碼建成堆;
      2.
      輸出堆頂元素后,怎樣調(diào)整剩余n-1個元素,使其按關(guān)鍵碼成為一個新堆。
      首先,討論輸出堆頂元素后,對剩余元素重新建成堆的調(diào)整過程。
      調(diào)整方法:設(shè)有m個元素的堆,輸出堆頂元素后,剩下m-1個元素。將堆底元素送入堆頂,堆被破壞,其原因僅是根結(jié)點不滿足堆的性質(zhì)。將根結(jié)點與左、右子女中較小(或小大)的進行交換。若與左子女交換,則左子樹堆被破壞,且僅左子樹的根結(jié)點不滿足堆的性質(zhì);若與右子女交換,則右子樹堆被破壞,且僅右子樹的根結(jié)點不滿足堆的性質(zhì)。繼續(xù)對不滿足堆性質(zhì)的子樹進行上述交換操作,直到葉子結(jié)點,堆被建成。稱這個自根結(jié)點到葉子結(jié)點的調(diào)整過程為篩選。
      【例10.6

      10.8自堆頂?shù)饺~子的調(diào)整過程

      再討論對n個元素初始建堆的過程。
      建堆方法:對初始序列建堆的過程,就是一個反復(fù)進行篩選的過程。n個結(jié)點的完全
      子樹成為堆,之后向前依次對各結(jié)點為根的子樹進行篩選,使之成為堆,直到根結(jié)點。
      【例10.7


      堆排序:對n個元素的序列進行堆排序,先將其建成堆,以根結(jié)點與第n個結(jié)點交換;調(diào)整前n-1個結(jié)點成為堆,再以根結(jié)點與第n-1個結(jié)點交換;重復(fù)上述操作,直到整個序列有序。
      【算法10.10
      void HeapAdjust(S_TBL *h
      ,int s,int m)
      {/*r[s…m]
      中的記錄關(guān)鍵碼除r[s]外均滿足堆的定義,本函數(shù)將對第s個結(jié)點為根的子樹篩選,使其成為大頂堆*/
      rc=h->r[s]
      ;
      for(j=2*s
      ;j<=mj=j*2) /* 沿關(guān)鍵碼較大的子女結(jié)點向下篩選 */
      { if(j<m&&h->r[j].key<h->r[j+1].key)
      j=j+1
      ; /* 為關(guān)鍵碼較大的元素下標(biāo)*/
      if(rc.key<h->r[j].key) break
      ; /* rc應(yīng)插入在位置s*/
      h->r[s]=h->r[j]
      s=j; /* 使s結(jié)點滿足堆定義 */
      }
      h->r[s]=rc
      /* 插入 */
      }

      void HeapSort(S_TBL *h)
      { for(i=h->length/2
      ;i>0;i--) /* r[1..length]建成堆 */
      HeapAdjust(h
      ,ih->length);
      for(i=h->length
      ;i>1i--)
      { h->r[1]<-->h->r[i]
      /* 堆頂與堆低元素交換 */
      HeapAdjust(h
      ,1i-1); /*r[1..i-1]重新調(diào)整為堆*/
      }
      }

      次,交換記錄至多k次。所以,在建好堆后,排序過程中的篩選次數(shù)不超過下式:
       + … +
      ?2)-log2(n? + ?1)-log2(n?( 2  )?log22 < nlog2n2
      而建堆時的比較次數(shù)不超過4n次,因此堆排序最壞情況下,時間復(fù)雜度也為O(nlog2n)。
      10.5
      二路歸并排序
      二路歸并排序的基本操作是將兩個有序表合并為一個有序表。
      設(shè)r[u…t]由兩個有序子表r[u…v-1]r[v…t]組成,兩個子表長度分別為v-u、t-v+1。合并方法為:
      i=uj=v;k=u; //置兩個子表的起始下標(biāo)及輔助數(shù)組的起始下標(biāo)
      i>v j>t,轉(zhuǎn) //其中一個子表已合并完,比較選取結(jié)束
      //選取r[i]r[j]關(guān)鍵碼較小的存入輔助數(shù)組rf
      如果r[i].key<r[j].key,rf[k]=r[i]; i++ k++; 轉(zhuǎn)
      否則,rf[k]=r[j]; j++; k++; 轉(zhuǎn)
      //將尚未處理完的子表中元素存入rf
      如果i<v,將r[i…v-1]存入rf[k…t] //前一子表非空
      如果j<=t,將r[i…v]存入rf[k…t] //后一子表非空
      合并結(jié)束。

      【算法10.11
      void Merge(ElemType *r
      ElemType *rfint u,int v,int t)
      {
      for(i=u
      j=v,k=u;i<v&&j<=tk++)
      { if(r[i].key<r[j].key)
      { rf[k]=r[i]
      ;i++}
      else
      { rf[k]=r[j]
      ;j++;}
      }
      if(i<v) rf[k…t]=r[i…v-1]
      ;
      if(j<=t) rf[k…t]=r[j…t]
      ;
      }

      .兩路歸并的迭代算法
      1
      個元素的表總是有序的。所以對n個元素的待排序列,每個元素可看成1個有序子

      表長度均為2。再進行兩兩合并,直到生成n個元素按關(guān)鍵碼有序的表。
      【算法10.12
      void MergeSort(S_TBL *p
      ElemType *rf)
      { /*
      *p表歸并排序,*rf為與*p表等長的輔助數(shù)組*/
      ElemType *q1
      ,*q2
      q1=rf
      ;q2=p->elem
      for(len=1
      ;len<p->lengthlen=2*len) /*q2歸并到q1*/
      { for(i=1
      ;i+2*len-1<=p->length;i=i+2*len)
      Merge(q2
      q1,i,i+len,i+2*len-1); /*對等長的兩個子表合并*/
      if(i+len-1<p->length)
      Merge(q2
      ,q1i,i+lenp->length); /*對不等長的兩個子表合并*/
      else if(i<=p->length)
      while(i<=p->length) /*
      若還剩下一個子表,則直接傳入*/
      q1[i]=q2[i]
      ;
      q1<-->q2
      ; /*交換,以保證下一趟歸并時,仍從q2歸并到q1*/
      if(q1!=p->elem) /*
      若最終結(jié)果不在*p表中,則傳入之*/
      for(i=1
      i<=p->length;i++)
      p->elem[i]=q1[i]

      }
      }

      .兩路歸并的遞歸算法
      【算法10.13
      void MSort(ElemType *p
      ,ElemType *p1int s,int t)
      { /*
      p[s…t]歸并排序為p1[s…t]*/
      if(s==t) p1[s]=p[s]
      else
      { m=(s+t)/2
      /*平分*p*/
      MSort(p
      ,p2s,m) /*遞歸地將p[s…m]歸并為有序的p2[s…m]*/
      MSort(p
      ,p2,m+1,t); /*遞歸地將p[m+1…t]歸并為有序的p2[m+1…t]*/
      Merge(p2
      ,p1s,m+1t); /*p2[s…m]p2[m+1…t]歸并到p1[s…t]*/
      }
      }

      void MergeSort(S_TBL *p)
      { /*
      對順序表*p作歸并排序*/
      MSort(p->elem
      ,p->elem,1p->length);
      }


      【效率分析】
      需要一個與表等長的輔助元素數(shù)組空間,所以空間復(fù)雜度為O(n)。
      n個元素的表,將這n個元素看作葉結(jié)點,若將兩兩歸并生成的子表看作它們的父結(jié)點,則歸并過程對應(yīng)由葉向根生成一棵二叉樹的過程。所以歸并趟數(shù)約等于二叉樹的高度-1,即log2n,每趟歸并需移動記錄n次,故時間復(fù)雜度為O(nlog2n)。

      10.6基數(shù)排序
      基數(shù)排序是一種借助于多關(guān)鍵碼排序的思想,是將單關(guān)鍵碼按基數(shù)分成多關(guān)鍵碼進行排序的方法。

      10.6.1 多關(guān)鍵碼排序
      撲克牌中52張牌,可按花色和面值分成兩個字段,其大小關(guān)系為:
      花色: 梅花 < 方塊 < 紅心 < 黑心
      面值: 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A

      若對撲克牌按花色、面值進行升序排序,得到如下序列:
      梅花2,3,...,A,方塊2,3,...,A,紅心2,3,...,A,黑心2,3,...,A
      即兩張牌,若花色不同,不論面值怎樣,花色低的那張牌小于花色高的,只有在同花色情況下,大小關(guān)系才由面值的大小確定。這就是多關(guān)鍵碼排序。
      為得到排序結(jié)果,我們討論兩種排序方法。
      方法1:先對花色排序,將其分為4個組,即梅花組、方塊組、紅心組、黑心組。再對每個組分別按面值進行排序,最后,將4個組連接起來即可。
      方法2:先按13個面值給出13個編號組(2號,3號,...,A),將牌按面值依次放入對應(yīng)的編號組,分成13堆。再按花色給出4個編號組(梅花、方塊、紅心、黑心),將2號組中牌取出分別放入對應(yīng)花色組,再將3號組中牌取出分別放入對應(yīng)花色組,……,這樣,4個花色組中均按面值有序,然后,將4個花色組依次連接起來即可。
      設(shè)n個元素的待排序列包含d個關(guān)鍵碼{k1,k2,kd},則稱序列對關(guān)鍵碼{k1k2,,kd}有序是指:對于序列中任兩個記錄r[i]r[j](1≤i≤j≤n)都滿足下列有序關(guān)系:


      其中k1稱為最主位關(guān)鍵碼,kd稱為最次位關(guān)鍵碼。
      多關(guān)鍵碼排序按照從最主位關(guān)鍵碼到最次位關(guān)鍵碼或從最次位到最主位關(guān)鍵碼的順序
      逐次排序,分兩種方法:
      最高位優(yōu)先(Most Significant Digit first)法,簡稱MSD法:先按k1排序分組,同一組中記錄,關(guān)鍵碼k1相等,再對各組按k2排序分成子組,之后,對后面的關(guān)鍵碼繼續(xù)這樣的排序分組,直到按最次位關(guān)鍵碼kd對各子組排序后。再將各組連接起來,便得到一個有序序列。撲克牌按花色、面值排序中介紹的方法一即是MSD法。
      最低位優(yōu)先(Least Significant Digit first)法,簡稱LSD法:先從kd開始排序,再對kd-1進行排序,依次重復(fù),直到對k1排序后便得到一個有序序列。撲克牌按花色、面值排序中介紹的方法二即是LSD法。

      10.6.2鏈?zhǔn)交鶖?shù)排序
      將關(guān)鍵碼拆分為若干項,每項作為一個關(guān)鍵碼,則對單關(guān)鍵碼的排序可按多關(guān)鍵碼排序方法進行。比如,關(guān)鍵碼為4位的整數(shù),可以每位對應(yīng)一項,拆分成4項;又如,關(guān)鍵碼由5個字符組成的字符串,可以每個字符作為一個關(guān)鍵碼。由于這樣拆分后,每個關(guān)鍵碼都在相同的范圍內(nèi)(對數(shù)字是09,字符是'a''z'),稱這樣的關(guān)鍵碼可能出現(xiàn)的符號個數(shù)為,記作RADIX。上述取數(shù)字為關(guān)鍵碼的10;取字符為關(guān)鍵碼的26?;谶@一特性,用LSD法排序較為方便。
      基數(shù)排序:從最低位關(guān)鍵碼起,按關(guān)鍵碼的不同值將序列中的記錄分配RADIX個隊列中,然后再收集之。如此重復(fù)d次即可。鏈?zhǔn)交鶖?shù)排序是用RADIX個鏈隊列作為分配隊列,關(guān)鍵碼相同的記錄存入同一個鏈隊列中,收集則是將各鏈隊列按關(guān)鍵碼大小順序鏈接起來。

      【例10.8】以靜態(tài)鏈表存儲待排記錄,頭結(jié)點指向第一個記錄。鏈?zhǔn)交鶖?shù)排序過程如下圖。
      (a):初始記錄的靜態(tài)鏈表。

      (b):第一趟按個位數(shù)分配,修改結(jié)點指針域,將鏈表中的記錄分配到相應(yīng)鏈隊列中。
      (c):第一趟收集:將各隊列鏈接起來,形成單鏈表。


      (d):第二趟按十位數(shù)分配,修改結(jié)點指針域,將鏈表中的記錄分配到相應(yīng)鏈隊列中。
      (e):第二趟收集:將各隊列鏈接起來,形成單鏈表。

      (f):第三趟按百位數(shù)分配,修改結(jié)點指針域,將鏈表中的記錄分配到相應(yīng)鏈隊列中。
      (g):第三趟收集:將各隊列鏈接起來,形成單鏈表。此時,序列已有序。

      10.10
      【算法10.14
      #define MAX_KEY_NUM 8 /*
      關(guān)鍵碼項數(shù)最大值*/
      #define RADIX 10 /*
      關(guān)鍵碼基數(shù),此時為十進制整數(shù)的基數(shù)*/
      #define MAX_SPACE 1000 /*
      分配的最大可利用存儲空間*/
      typedef struct{
      KeyType keys[MAX_KEY_NUM]
      /*關(guān)鍵碼字段*/
      InfoType otheritems
      ; /*其它字段*/
      int next
      /*指針字段*/
      }NodeType
      ; /*表結(jié)點類型*/
      typedef struct{
      NodeType r[MAX_SPACE]
      /*靜態(tài)鏈表,r[0]為頭結(jié)點*/
      int keynum
      ; /*關(guān)鍵碼個數(shù)*/
      int length
      ; /*當(dāng)前表中記錄數(shù)*/
      }L_TBL
      /*鏈表類型*/
      typedef int ArrayPtr[radix]
      ; /*數(shù)組指針,分別指向各隊列*/

      void Distribute(NodeType *s,int iArrayPtr *f,ArrayPtr *e)
      { /*
      靜態(tài)鏈表ltblr域中記錄已按(kye[0]keys[1],,keys[i-1])有序)*/
      /*
      本算法按第i個關(guān)鍵碼keys[i]建立RADIX個子表,使同一子表中的記錄的keys[i]相同*/
      /*f[0…RADIX-1]
      e[0…RADIX-1]分別指向各子表的第一個和最后一個記錄*/
      for(j=0
      ;j<RADIXj++) f[j]=0; /* 各子表初始化為空表*/
      for(p=r[0].next
      ;p;p=r[p].next)
      { j=ord(r[p].keys[i])
      ; /*ord將記錄中第i個關(guān)鍵碼映射到[0…RADIX-1]*/
      if(!f[j]) f[j]=p
      ;
      else r[e[j]].next=p

      e[j]=p
      ; /* p所指的結(jié)點插入到第j個子表中*/
      }
      }

      void Collect(NodeType *r,int iArrayPtr f,ArrayPtr e)
      {/*
      本算法按keys[i]自小到大地將f[0…RADIX-1]所指各子表依次鏈接成一個鏈表*e[0…RADIX-1]為各子表的尾指針*/
      for(j=0
      ;!f[j];j=succ(j)) /*找第一個非空子表,succ為求后繼函數(shù)*/
      r[0].next=f[j]
      ;t=e[j]; /*r[0].next指向第一個非空子表中第一個結(jié)點*/
      while(j<RADIX)
      { for(j=succ(j)
      j<RADIX-1&&!f[j];j=succ(j)); /*找下一個非空子表*/
      if(f[j]) {r[t].next=f[j]
      t=e[j];} /*鏈接兩個非空子表*/
      }
      r[t].next=0
      ; /*t指向最后一個非空子表中的最后一個結(jié)點*/
      }
      void RadixSort(L_TBL *ltbl)
      { /*
      ltbl作基數(shù)排序,使其成為按關(guān)鍵碼升序的靜態(tài)鏈表,ltbl->r[0]為頭結(jié)點*/
      for(i=0
      ;i<ltbl->length;i++) ltbl->r[i].next=i+1
      ltbl->r[ltbl->length].next=0
      ; /*ltbl改為靜態(tài)鏈表*/
      for(i=0
      i<ltbl->keynum;i++) /*按最低位優(yōu)先依次對各關(guān)鍵碼進行分配和收集*/
      { Distribute(ltbl->r
      ,i,fe); /*i趟分配*/
      Collect(ltbl->r
      i,fe); /*i趟收集*/
      }
      }
      【效率分析】
      時間效率:設(shè)待排序列為n個記錄,d個關(guān)鍵碼,關(guān)鍵碼的取值范圍為radix,則進行鏈?zhǔn)交鶖?shù)排序的時間復(fù)雜度為O(d(n+radix)),其中,一趟分配時間復(fù)雜度為O(n),一趟收集時間復(fù)雜度為O(radix),共進行d趟分配和收集。
      空間效率:需要2*radix個指向隊列的輔助空間,以及用于靜態(tài)鏈表的n個指針。

      10.7外排序
      10.7.1
      外部排序的方法
      外部排序基本上由兩個相互獨立的階段組成。首先,按可用內(nèi)存大小,將外存上含n個記錄的文件分成若干長度為k的子文件或段(segment),依次讀入內(nèi)存并利用有效的內(nèi)部排序方法對它們進行排序,并將排序后得到的有序子文件重新寫入外存。通常稱這些有序子文件為歸并段或順串;然后,對這些歸并段進行逐趟歸并,使歸并段(有序子文件)逐漸由小到大,直至得到整個有序文件為止。
      顯然,第一階段的工作已經(jīng)討論過。以下主要討論第二階段即歸并的過程。先從一個例子來看外排序中的歸并是如何進行的?

      假設(shè)有一個含 10000 個記錄的
      文件,首先通過10次內(nèi)部排序得到
      10
      個初始?xì)w并段 R1R10 ,其中每
      一段都含1000個記錄。然后對它們
      作如圖10.11所示的兩兩歸并,直至
      得到一個有序文件為止。


      從圖10.11可見,由10個初始?xì)w并段到一個有序文件,共進行了四趟歸并,每一趟

      將兩個有序段歸并成一個有序段的過程,若在內(nèi)存中進行,則很簡單,前面討論的2-路歸并排序中的Merge函數(shù)便可實現(xiàn)此歸并。但是,在外部排序中實現(xiàn)兩兩歸并時,不僅要調(diào)用Merge函數(shù),而且要進行外存的讀/寫,這是由于我們不可能將兩個有序段及歸并結(jié)果同時放在內(nèi)存中的緣故。對外存上信息的讀/寫是以物理塊為單位。假設(shè)在上例中每個物理塊可以容納200個記錄,則每一趟歸并需進行5050,四趟歸并加上內(nèi)部排序時所需進行的讀/寫,使得在外排序中總共需進行500次的讀/寫。
      一般情況下,外部排序所需總時間=
      內(nèi)部排序(產(chǎn)生初始?xì)w并段)所需時間 m*tis
      +
      外存信息讀寫的時間 d*tio
      +
      內(nèi)部歸并排序所需時間 s*utmg
      其中:tis是為得到一個初始?xì)w并段進行的內(nèi)部排序所需時間的均值;tio是進行一次外存讀/寫時間的均值;utmg是對u個記錄進行內(nèi)部歸并所需時間;m為經(jīng)過內(nèi)部排序之后得到的初始?xì)w并段的個數(shù);s為歸并的趟數(shù);d為總的讀/寫次數(shù)。由此,上例10000個記錄利用2-路歸并進行排序所需總的時間為:
      10*tis+500*tio+4*10000tmg
      其中tio取決于所用的外存設(shè)備,顯然,tiotmg要大的多。因此,提高排序效率應(yīng)主要著眼于減少外存信息讀寫的次數(shù)d
      下面來分析d歸并過程的關(guān)系。若對上例中所得的10個初始?xì)w并段進行5-平衡歸并(即每一趟將5個或5個以下的有序子文件歸并成一個有序子文件),則從下圖可見,僅需進行二趟歸并,外部排序時總的讀/寫次數(shù)便減少至2×100+100=300,比2-路歸并減少了200次的讀/寫。
      R1 R2 R3 R4 R5 R6 R7 R8 R9 R10
      └─┴─┼─┴─┘ └─┴─┼─┴─┘
      R1' R2'
      └────┬────┘
      有序文件
      10.12
      可見,對同一文件而言,進行外部排序時所需讀/寫外存的次數(shù)和歸并的趟數(shù)s成正比。而在一般情況下,對m個初始?xì)w并段進行k-路平衡歸并時,歸并的趟數(shù)
      可見,若增加k或減少m便能減少s。下面分別就這兩個方面討論之。
      10.7.2
      多路平衡歸并的實現(xiàn)
      從上式可見,增加k可以減少s,從而減少外存讀/寫的次數(shù)。但是,從下面的討論中又可發(fā)現(xiàn),單純增加k將導(dǎo)致增加內(nèi)部歸并的時間utmg。那末,如何解決這個矛盾呢?
      先看2-路歸并。令u個記錄分布在兩個歸并段上,按Merge函數(shù)進行歸并。每得到歸并后的含u個記錄的歸并段需進行u-1次比較。
      再看k-路歸并。令u個記錄分布在k個歸并段上,顯然,歸并后的第一個記錄應(yīng)是k個歸并段中關(guān)鍵碼最小的記錄,即應(yīng)從每個歸并段的第一個記錄的相互比較中選出最小者,這需要進行k-1次比較。同理,每得到歸并后的有序段中的一個記錄,都要進行k-1次比較。顯然,為得到含u個記錄的歸并段需進行(u-1)(k-1)次比較。由此,對n個記錄的文件進行外部排序時,在內(nèi)部歸并過程中進行的總的比較次數(shù)為s(k-1)(n-1)。假設(shè)所得初始?xì)w并段為m個,則可得內(nèi)部歸并過程中進行比較的總的次數(shù)為


      k
      而減少外存信息讀寫時間所得效益,這是我們所不希望的。然而,若在進行k-路歸并時利用敗者樹”(Tree of Loser),則可使在k個記錄中選出關(guān)鍵碼最小的記錄時僅需進

      它不再隨k的增長而增長。
      何謂敗者樹?它是樹形選擇排序的一種變型。相對地,我們可稱圖10.5和圖10.6中二叉樹為勝者樹,因為每個非終端結(jié)點均表示其左、右子女結(jié)點中勝者。反之,若在雙親結(jié)點中記下剛進行完的這場比賽中的敗者,而讓勝者去參加更高一層的比賽,便可得到一棵敗者樹。
      【例10.9


      (a) (b)
      10.13 實現(xiàn)5-路歸并的敗者樹

      10.13(a)即為一棵實現(xiàn)5-路歸并的敗者樹ls[0…4],圖中方形結(jié)點表示葉子結(jié)點(也可看成是外結(jié)點),分別為5個歸并段中當(dāng)前參加歸并的待選擇記錄的關(guān)鍵碼;敗者樹中根結(jié)點ls[1]的雙親結(jié)點ls[0]冠軍,在此指示各歸并段中的最小關(guān)鍵碼記錄為第三段中的記錄;結(jié)點ls[3]指示b1b2兩個葉子結(jié)點中的敗者即是b2,而勝者b1b3(b3是葉子結(jié)點b3、b4b0經(jīng)過兩場比賽后選出的獲勝者)進行比較,結(jié)點ls[1]則指示它們中的敗者為b1。在選得最小關(guān)鍵碼的記錄之后,只要修改葉子結(jié)點b3中的值,使其為同一歸并段中的下一個記錄的關(guān)鍵碼,然后從該結(jié)點向上和雙親結(jié)點所指的關(guān)鍵碼進行比較,敗者留在該雙親,勝者繼續(xù)向上直至樹根的雙親。如圖10.13(b)所示。當(dāng)?shù)?/span>3個歸并段中第2個記錄參加歸并時,選得最小關(guān)鍵碼記錄為第一個歸并段中的記錄。為了防止在歸并過程中某個歸并段變?yōu)榭眨梢栽诿總€歸并段中附加一個關(guān)鍵碼為最大的記錄。當(dāng)選出的冠軍記錄的關(guān)鍵碼為最大值時,表明此次歸并已完成。由于實現(xiàn)k-路歸并的敗者樹

      的初始化也容易實現(xiàn),只要先令所有的非終端結(jié)點指向一個含最小關(guān)鍵碼的葉子結(jié)點,然后從各葉子結(jié)點出發(fā)調(diào)整非終端結(jié)點為新的敗者即可。

      下面程序中簡單描述了利用敗者樹進行k-路歸并的過程,為了突出如何利用敗者樹進行歸并,避開了外存信息存取的細(xì)節(jié),可以認(rèn)為歸并段已存在。

      【算法10.15
      typedef int LoserTree[k]; /*
      敗者樹是完全二叉樹且不含葉子,可采用順序存儲結(jié)構(gòu)*/

      typedef struct{
      KeyType key;
      }ExNode,External[k]; /*
      外結(jié)點,只存放待歸并記錄的關(guān)鍵碼*/

      void K_Merge(LoserTree *ls,External *b) /*k-路歸并處理程序*/
      { /*
      利用敗者樹ls將編號從0k-1k個輸入歸并段中的記錄歸并到輸出歸并段*/
      /*b[0]
      b[k-1]為敗者樹上的k個葉子結(jié)點,分別存放k個輸入歸并段中當(dāng)前記錄的關(guān)鍵碼*/
      for(i=0;i<k;i++) input(b[i].key); /*
      分別從k個輸入歸并段讀入該段當(dāng)前第一個記錄的*/
      /*
      關(guān)鍵碼到外結(jié)點*/
      CreateLoserTree(ls); /*
      建敗者樹ls,選得最小關(guān)鍵碼為b[0].key*/
      while(b[ls[0]].key!=MAXKEY)
      { q=ls[0]; /*q
      指示當(dāng)前最小關(guān)鍵碼所在歸并段*/
      output(q); /*
      將編號為q的歸并段中當(dāng)前(關(guān)鍵碼為b[q].key的記錄寫至輸出歸并段)*/
      input(b[q].key); /*
      從編號為q的輸入歸并段中讀入下一個記錄的關(guān)鍵碼*/
      Adjust(ls,q); /*
      調(diào)整敗者樹,選擇新的最小關(guān)鍵碼*/
      }
      output(ls[0]); /*
      將含最大關(guān)鍵碼MAXKEY的記錄寫至輸出歸并段*/
      }


      void Adjust(LoserTree *ls,int s) /*
      選得最小關(guān)鍵碼記錄后,從葉到根調(diào)整敗者樹,選下一個最小關(guān)鍵碼*/
      { /*
      沿從葉子結(jié)點b[s]到根結(jié)點ls[0]的路徑調(diào)整敗者樹*/
      t=(s+k)/2; /*ls[t]
      b[s]的雙親結(jié)點*/
      while(t>0)
      { if(b[s].key>b[ls[t]].key) s<-->ls[t]; /*s
      指示新的勝者*/
      t=t/2;
      }
      ls[0]=s;
      }

      void CreateLoserTree(LoserTree *ls) /*建立敗者樹*/
      { /*
      已知b[0]b[k-1]為完全二叉樹ls的葉子結(jié)點存有k個關(guān)鍵碼,沿從葉子到根的k條路徑*/
      /*
      ls調(diào)整為敗者樹*/
      b[k].key=MINKEY; /*
      設(shè)MINKEY為關(guān)鍵碼可能的最小值*/
      for(i=0;i<k;i++) ls[i]=k; /*
      設(shè)置ls敗者的初值*/
      for(i=k-1;k>0;i--) Adjust(ls,i); /*
      依次從b[k-1],b[k-2],…,b[0]出發(fā)調(diào)整敗者*/
      }

      最后要提及一點,k值的選擇并非越大越好,如何選擇合適的k是一個需要綜合考慮的問題。

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多