这一章首先以一个插入排序算法开始,以此为切入点分析算法。书上以伪代码来介绍算法,但是在这里我基本上会用c语言实现一遍,并用自己的语言复述一遍算法的思想。
首先附上插入排序的具体实现。
<span style="font-size:18px;"><span style="font-size:18px;">void insert_sort (EleType *arr , int start , int end) {
int i;
int j;
EleType temp;
for (i = start + 1 ; i <= end ; i++) {
temp = arr[i];
j = i - 1;
for (; j >= start ; j--) {
if (arr[j] > temp)
arr[j+1] = arr[j];
else
break;
}
arr[j+1] = temp;
}
}</span>
</span>
重要思路:运用循环不变式的思想,第一次循环开始,我们不必对只含有一个元素的数组进行排序,因为其本身就是排序好的。接下来是保持,我们首先用temp变量保存arr[i],这个是要插入元素,在已经排好序的数组查找大于temp的元素,并把它向后移动。最后是终止,将i = end带入循环不变式,我们可以看见,最后一个元素插入到之前已经排好序的数组当中,所以这个算法是正确的。
对插入排序的分析,由于算法的时间主要是集中在循环上面,所以我们集中精力看循环就可以,当出现最好的情况的时候,arr数组已经排好序了,那么时间复杂度是n,当出现最坏的情况的时候,时间复杂度是n的平方。(这里就不在赘述书上的过程了,很简单)。
下面作者设计了一个分治的排序算法——归并排序算法。先附上C语言的实现。
<span style="font-size:18px;">void merge (EleType *arr , int start , int mid , int end) {
EleType temp[end - start + 1];
int n1 = 0;
int n2 = 0;
int ntemp = 0;
int i;
while ((n1 != (mid - start + 1)) && (n2 != (end - mid))) {
if (arr[start + n1] < arr[mid+1+n2]) {
temp[ntemp] = arr[start+n1];
ntemp++;
n1++;
}else {
temp[ntemp] = arr[mid+1+n2];
ntemp++;
n2++;
}
}
if (n1 == (mid - start + 1)) {
for (i = mid+1+n2 ; i <= end ; i++) {
temp[ntemp] = arr[i];
ntemp++;
}
}else if (n2 == (end - mid)) {
for (i = n1+start ; i <= mid ; i++) {
temp[ntemp] = arr[i];
ntemp++;
}
}
for (i = start ; i <= end ; i++)
arr[i] = temp[i-start];
}
void merge_sort (EleType *arr , int start , int end) {
int mid;
int i;
if ((end - start) > 0) {
mid = (end + start) / 2;
merge_sort (arr , start , mid);
merge_sort (arr , mid + 1 , end);
merge (arr , start , mid , end);
}
/*for (i = start ; i <= end ; i++)
printf ("%d " , arr[i]);
printf ("\n");
printf ("start = %d , end = %d , mid = %d\n" , start , end , mid);
printf ("\n");*/
}
</span>
重要思路:首先设计归并算法,将两个已经排好序的子数组合并成一个排好序的数组。书上在设计这个算法的时候使用了哨兵值,但是在这里我变通了一下,判断其中一个子数组是否已经空了,如果空了,就跳出循环,直接把另外一个子数组中的剩下的值,转移到一个数组当中。再使用递归算法排序。因为一个子数组中如果只有一个元素的话,那么这个子数组其实就是排好序的。利用这个性质作为递归的出口,设计出递归算法。
算法分析:这里书上介绍了递归表达式,利用递归表达式,使用以后学习的知识(主方法)可以方便的求出,这个递归算法的时间复杂度。这里我们观察merge这个函数的复杂度为n。我们在这里可以看到,归并算法把整个算法分治为两个部分,每个部分可以处理二分之一的规模(这里规模定义为数组的大小)
上图就是递归表达式。
我们如果使用书上画图的方法可以求出这个表达式的时间复杂度为n*lgn。