算法设计与分析课程记录week3
PS:此文章仅作为个人课程期间的学习记录
分治法
1 分治法概述
1.1 分治法的设计思想
对于一个规模为n的问题:若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
分治法所能解决的问题一般具有以下几个特征:
- 该问题的规模缩小到一定的程度就可以容易地解决。
- 该问题可以分解为若干个规模较小的相同问题。
- 利用该问题分解出的子问题的解可以合并为该问题的解。
- 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
2 求解排序问题
2.1 快速排序
基本思想:在待排序的n个元素中任取一个元素(通常取第一个元素)作为基准,把该元素放入最终位置后,整个数据序列被基准分割成两个子序列,所有小于基准的元素放置在前子序列中,所有大于基准的元素放置在后子序列中,并把基准排在这两个子序列的中间,这个过程称作划分。
然后对两个子序列分别重复上述过程,直至每个子序列内只有一个记录或空为止。
快速排序算法:
//划分算法
int Partition(int a[],int s,int t)
{ int i=s,j=t;
int tmp=a[s]; //用序列的第1个记录作为基准
while (i!=j) //从序列两端交替向中间扫描,直至i=j为止
{ while (j>i && a[j]>=tmp)
j--; //从右向左扫描,找第1个关键字小于tmp的a[j]
a[i]=a[j]; //将a[j]前移到a[i]的位置
while (i<j && a[i]<=tmp)
i++; //从左向右扫描,找第1个关键字大于tmp的a[i]
a[j]=a[i]; //将a[i]后移到a[j]的位置
}
a[i]=tmp;
return i;
}
//快速排序
void QuickSort(int a[],int s,int t)
//对a[s..t]元素序列进行递增排序
{ if (s<t) //序列内至少存在2个元素的情况
{ int i=Partition(a,s,t);
QuickSort(a,s,i-1); //对左子序列递归排序
QuickSort(a,i+1,t); //对右子序列递归排序
}
}
时间复杂度
当初始排序数据正序或反序时,此时的递归树高度为n,快速排序呈现最坏情况,即最坏情况下的时间复杂度为O(n²);
当初始排序数据随机分布,使每次分成的两个子区间中的记录个数大致相等,此时的递归树高度为
l
o
g
2
n
log_{2} n
log2n,快速排序呈现最好情况,即最好情况下的时间复杂度为
O
(
n
l
o
g
2
n
)
O(nlog_{2} n)
O(nlog2n)。快速排序算法的平均时间复杂度也是
O
(
n
l
o
g
2
n
)
O(nlog_{2} n)
O(nlog2n)。
2.2 归并排序
归并排序的基本思想是:首先将a[0…n-1]看成是n个长度为1的有序表,将相邻的k(k≥2)个有序子表成对归并,得到n/k个长度为k的有序子表;然后再将这些有序子表继续归并,得到n/k2个长度为k2的有序子表,如此反复进行下去,最后得到一个长度为n的有序表。
若k=2,即归并在相邻的两个有序子表中进行的,称为二路归并排序。若k>2,即归并操作在相邻的多个有序子表中进行,则叫多路归并排序。
归并排序算法代码
void Merge(int a[],int low,int mid,int high)
//a[low..mid]和a[mid+1..high]→a[low..high]
{ int *tmpa;
int i=low,j=mid+1,k=0;
tmpa=(int *)malloc((high-low+1)*sizeof(int));
while (i<=mid && j<=high)
if (a[i]<=a[j]) //将第1子表中的元素放入tmpa中
{ tmpa[k]=a[i]; i++; k++; }
else //将第2子表中的元素放入tmpa中
{ tmpa[k]=a[j]; j++; k++; }
while (i<=mid) //将第1子表余下部分复制到tmpa
{ tmpa[k]=a[i]; i++; k++; }
while (j<=high) //将第2子表余下部分复制到tmpa
{ tmpa[k]=a[j]; j++; k++; }
for (k=0,i=low;i<=high;k++,i++) //将tmpa复制回a中
a[i]=tmpa[k];
free(tmpa); //释放tmpa所占内存空间
}
void MergePass(int a[],int length,int n)
//一趟二路归并排序
{ int i;
for (i=0;i+2*length-1<n;i=i+2*length) //归并length长的两相邻子表
Merge(a,i,i+length-1,i+2*length-1);
if (i+length-1<n) //余下两个子表,后者长度小于length
Merge(a,i,i+length-1,n-1); //归并这两个子表
}
//二路归并算法
void MergeSort(int a[],int n)
{ int length;
for (length=1;length<n;length=2*length)
MergePass(a,length,n);
}
对于上述二路归并排序算法,当有n个元素时,需趟归并,每一趟归并,其元素比较次数不超过n-1,元素移动次数都是n,因此归并排序的时间复杂度为
O
(
n
l
o
g
2
n
)
O(nlog_{2} n)
O(nlog2n)。