北京大學C語言學習第14天

分治算法
分治的基本概念
 把一個任務,分成形式和原任務相同,但規模更小的
幾個部分任務(通常是兩個部分),分別完成,或只
需要選一部完成。然後再處理完成後的這一個或幾個
部分的結果,實現整個任務的完成。
5
分治的生活實例 --稱假幣
 16硬幣,有可能有1枚假幣,假幣比真幣輕。有一架天
平,用最少稱量次數確定有沒有假幣,若有的話,假
幣是哪一枚。
6
分治的生活實例 – 稱假幣
 8 – 8 一稱,發現無假幣,或假幣所在的那8枚
 4 – 4 一稱
 2 – 2 一稱
 1 – 1 一稱
7
歸併排序
信息科學技術學院
挪威天池
 數組排序任務可以如下完成:
1) 把前一半排序
2) 把後一半排序
3) 把兩半歸併到一個新的有序數組,然後再拷貝回原
數組,排序完成。
9
分治的典型應用:歸併排序
分治的典型應用:歸併排序

#include <iostream>
using namespace std;
void Merge(int a[],int s,int m, int e,int tmp[]) 
{//將數組a的局部a[s,m]和a[m+1,e]合併到tmp,並保證tmp有序,然後再拷貝回a[s,m]
//歸併操作時間複雜度:O(e-m+1),即O(n)
int pb = 0;
int p1 = s,p2 = m+1;
while( p1 <= m && p2 <= e) {
if( a[p1] < a[p2]) 
tmp[pb++] = a[p1++];
else 
tmp[pb++] = a[p2++];
}
10
while( p1 <= m) 
tmp[pb++] = a[p1++];
while( p2 <= e)
tmp[pb++] = a[p2++];
for(int i = 0;i < e-s+1; ++i)
a[s+i] = tmp[i];
}
void MergeSort(int a[],int s,int e,int tmp[])
{
if( s < e) {
int m = s + (e-s)/2;
MergeSort(a,s,m,tmp);
MergeSort(a,m+1,e,tmp);
Merge(a,s,m,e,tmp);
}
}
11
int a[10] = { 13,27,19,2,8,12,2,8,30,89};
int b[10]int main()
{
int size = sizeof(a)/sizeof(int);
MergeSort(a,0,size-1,b);
for(int i = 0;i < size; ++i)
cout << a[i] << ",";
cout << endl; 
return 0;
}

12
1 4 9 12
2 5 8 13
歸併排序的時間複雜度
對n個元素進行排序的時間:
T(n) = 2T(n/2) + an (a是常數,具體多少不重要)
= 2*(2T(n/4)+an/2)+an
= 4
T(n/4)+2an
= 4
(2T(n/8)+an/4)+2an
= 8T(n/8)+3an

= 2k T(n/2k)+ka
n
一直做到 n/2k = 1 (此時 k = log2n),
T(n)= 2k T(1)+kan = 2k T(1)+kan = 2k+kan
= n+a*(log2n)*n
複雜度O(nlogn)
14
快速排序
信息科學技術學院
挪威奧勒鬆
分治的典型應用:快速排序
 數組排序任務可以如下完成:
1)設k=a[0], 將k挪到適當位置,使得比k小的元素都
在k左邊,比k大的元素都在k右邊,和k相等的,不關心
在k左右出現均可 (O(n)時間完成)
2) 把k左邊的部分快速排序
3) 把k右邊的部分快速排序
7
1
3
8 12 11
2
9
K = 7 i j
7
1
3
8 12 11
2
9
K = 7 i j
2
1
3
8 12 11
7
9
K = 7 i j
2
1
3
8 12 11
7
9
K = 7 i j
2
1
3
8 12 11
7
9
K = 7 i j
2
1
3
8 12 11
7
9
K = 7 i j
2
1
3
7 12 11
8
9
K = 7 i j
2
1
3
7 12 11
8
9
K = 7 i j
2
1
3
7 12 11
8
9
K = 7 i j
分治的典型應用:快速排序

#include <iostream>
using namespace std;
void swap(int & a,int & b) //交換變量a,b值
{
int tmp = a;
a = b;
b = tmp;
}
void QuickSort(int a[],int s,int e)
{
if( s >= e)
return;
int k = a[s];
int i = s,j = e;
while( i != j ) {
while( j > i && a[j] >= k )
--j;
swap(a[i],a[j]);
while( i < j && a[i] <= k ) 
++i;
swap(a[i],a[j]);
} //處理完後,a[i] = k
QuickSort(a,s,i-1);
QuickSort(a,i+1,e);
}
int a[] = { 93,27,30,2,8,12,2,8,30,89};
int main()
{
int size = sizeof(a)/sizeof(int);
QuickSort(a,0,size-1);
for(int i = 0;i < size; ++i)
cout << a[i] << ",";
cout << endl; 
return 0;
}

例題
輸出前m大的數
信息科學技術學院
冰島火山口湖
例題:輸出前m大的數
描述
給定一個數組包含n個元素,統計前m大的數並且把這m個數從大到小
輸出。
輸入
第一行包含一個整數n,表示數組的大小。n < 100000。
第二行包含n個整數,表示數組的元素,整數之間以一個空格分開
。每個整數的絕對值不超過100000000。
第三行包含一個整數m。m < n。
輸出
從大到小輸出前m大的數,每個數一行。
例題:輸出前m大的數
排序後再輸出,複雜度 O(nlogn)
用分治處理:複雜度 O(n+mlogm)
思路:把前m大的都弄到數組最右邊,然後對這最右邊m個元素排序,
再輸出
關鍵 :O(n)時間內實現把前m大的都弄到數組最右邊
例題:輸出前m大的數
引入操作 arrangeRight(k): 把數組(或數組的一部分)前k大的
都弄到最右邊
如何將前k大的都弄到最右邊
1)設key=a[0], 將key挪到適當位置,使得比key小的元素都在
key左邊,比key大的元素都在key右邊(線性時間完成)
2) 選擇數組的前部或後部再進行 arrangeRight操作
例題:輸出前m大的數
2) 選擇數組的前部或後部再進行 arrangeRight操作
key
a 個
b 個
a = k done
a > k 對最右邊a-1個元素再進行arrangeRigth(k)
a < k 對左邊b個元素再進行arrangeRight(k-a)
例題:輸出前m大的數
將前m大的都弄到數組最右邊的時間:
T(n) = T(n/2) + an
= T(n/4) + a
n/2 + an
= T(n/8) + a
n/4 + an/2 + an
= …
= T(1) + … + an/8 + an/4 + an/2 + an
< 2an
即 O(n)
例題
求排列的逆序數
信息科學技術學院
冰島維克鎮黑沙灘
例題:求排列的逆序數
考慮1,2,…,n (n <= 100000)的排列i1,i2,…,in,如果其中存在j,k,滿足 j <
k 且 i
j > ik, 那麼就稱(ij
,ik
)是這個排列的一個逆序。
一個排列含有逆序的個數稱爲這個排列的逆序數。例如排列 263451 含有8個
逆序(2,1),(6,3),(6,4),(6,5),(6,1),(3,1),(4,1),(5,1),因此該排列的逆序數就是8。
現給定1,2,…,n的一個排列,求它的逆序數。
例題:求排列的逆序數
笨辦法:O(n2
)
分治O(nlogn):
1) 將數組分成兩半,分別求出左半邊的逆序數和右半邊的逆序數
2) 再算有多少逆序是由左半邊取一個數和右半邊取一個數構成(要求O(n)實
現)
例題:求排列的逆序數
2) 的關鍵:左半邊和右半邊都是排好序的。比如,都是從大到小排序的。這
樣,左右半邊只需要從頭到尾各掃一遍,就可以找出由兩邊各取一個數構成的
逆序個數
10 8 7 3 12 11 5 2
i j
例題:求排列的逆序數
2) 的關鍵:左半邊和右半邊都是排好序的。比如,都是從大到小排序的。這
樣,左右半邊只需要從頭到尾各掃一遍,就可以找出由兩邊各取一個數構成的
逆序個數
10 8 7 3 12 11 5 2
i j
例題:求排列的逆序數
2) 的關鍵:左半邊和右半邊都是排好序的。比如,都是從大到小排序的。這
樣,左右半邊只需要從頭到尾各掃一遍,就可以找出由兩邊各取一個數構成的
逆序個數
10 8 7 3 12 11 5 2
i j
例題:求排列的逆序數
2) 的關鍵:左半邊和右半邊都是排好序的。比如,都是從大到小排序的。這
樣,左右半邊只需要從頭到尾各掃一遍,就可以找出由兩邊各取一個數構成的
逆序個數
10 8 7 3 12 11 5 2
i j
例題:求排列的逆序數
2) 的關鍵:左半邊和右半邊都是排好序的。比如,都是從大到小排序的。這
樣,左右半邊只需要從頭到尾各掃一遍,就可以找出由兩邊各取一個數構成的
逆序個數
10 8 7 3 12 11 5 2
i j
例題:求排列的逆序數
2) 的關鍵:左半邊和右半邊都是排好序的。比如,都是從大到小排序的。這
樣,左右半邊只需要從頭到尾各掃一遍,就可以找出由兩邊各取一個數構成的
逆序個數
10 8 7 3 12 11 5 2
i j
例題:求排列的逆序數
2) 的關鍵:左半邊和右半邊都是排好序的。比如,都是從大到小排序的。這
樣,左右半邊只需要從頭到尾各掃一遍,就可以找出由兩邊各取一個數構成的
逆序個數
10 8 7 3 12 11 5 2
i j
例題:求排列的逆序數
總結:
由歸併排序改進得到,加上計算逆序的步驟
MergeSortAndCount: 歸併排序並計算逆序數
快速冪
信息科學技術學院
冰島天然玄武岩長城
快速冪

int Pow(int a,int b)
{ //快速求a^b ,複雜度 log(b) 
if(b == 0)
return 1; 
if(b & 1) { //b是奇數
return a * Pow(a,b-1);
}
else {
int t = Pow(a,b/2);
return t * t;
}
}

47
快速冪

int Pow(int a,int b)
{ //快速求a^b ,複雜度 log(b) 
int result = 1;
int base = a;
while(b) {
if( b & 1) 
result *= base;
base *= base;
b >>= 1;
}
return result;
}
48
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章