伪代码:
C++模板:
template <typename T>
void Insertion_Sort(T *array, size_t length) {
if (length <= 1) {
return;
} else {
for (int i = 1; i != length; i++) {
int j = i - 1;
T key = array[i];
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
}
}
证明算法正确性:
循环不变式:在每次循环开始前,A[1…i-1]包含了原来的A[1…i-1]的元素,并且已排序。
初始:i=2,A[1…1]已排序,成立。
保持:在迭代开始前,A[1…i-1]已排序,而循环体的目的是将A[i]插入A[1…i-1]中,使得A[1…i]排序,因此在下一轮迭代开 始前,i++,因此现在A[1…i-1]排好序了,因此保持循环不变式。
终止:最后i=n+1,并且A[1…n]已排序,而A[1…n]就是整个数组,因此证毕。
伪代码:
C++模板:
template <typename T>
void Bubble_Sort(T *array, size_t length) {
for (int i = 0; i != length - 1; i++) {
for (int j = 0; j + i != length - 1; j++) {
if (array[j] > array[j + 1]) {
T temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}
证明算法正确性:
运用两次循环不变式,先证明第4-6行的内循环,再证明外循环。
内循环不变式:在每次循环开始前,A[j]是A[j…n]中最小的元素。
初始:j=n,因此A[n]是A[n…n]的最小元素。
保持:当循环开始时,已知A[j]是A[j…n]的最小元素,将A[j]与A[j-1]比较,并将较小者放在j-1位置,因此能够说明A[j-1]是A[j-1…n]的最小元素,因此循环不变式保持。
终止:j=i,已知A[i]是A[i…n]中最小的元素,证毕。
接下来证明外循环不变式:在每次循环之前,A[1…i-1]包含了A中最小的i-1个元素,且已排序:A[1]<=A[2]<=…<=A[i-1]。
初始:i=1,因此A[1..0]=空,因此成立。
保持:当循环开始时,已知A[1…i-1]是A中最小的i-1个元素,且A[1]<=A[2]<=…<=A[i-1],根据内循环不变式,终止时A[i]是A[i…n]中最小的元素,因此A[1…i]包含了A中最小的i个元素,且A[1]<=A[2]<=…<=A[i-1]<=A[i]
终止:i=n+1,已知A[1…n]是A中最小的n个元素,且A[1]<=A[2]<=…<=A[n],得证。
在算法导论思考题2-2中又问了”冒泡排序和插入排序哪个更快“呢?
一般的人回答:“差不多吧,因为渐近时间都是O(n^2)”。
但是事实上不是这样的,插入排序的速度直接是逆序对的个数,而冒泡排序中执行“交换“的次数是逆序对的个数,因此冒泡排序执行的时间至少是逆序对的个数,因此插入排序的执行时间至少比冒泡排序快。
伪代码:
C++模板:
template <typename T>
void Selection_Sort(T *array, size_t length) {
for (int i = 0; i != length; i++) {
int min = i;
for (int j = i + 1; j != length; j++) {
if (array[min] > array[j]) {
min = j;
}
}
T temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
证明算法正确性:
循环不变式:A[1…i-1]包含了A中最小的i-1个元素,且已排序。
初始:i=1,A[1…0]=空,因此成立。
保持:在某次迭代开始之前,保持循环不变式,即A[1…i-1]包含了A中最小的i-1个元素,且已排序,则进入循环体后,程序从 A[i…n]中找出最小值放在A[i]处,因此A[1…i]包含了A中最小的i个元素,且已排序,而i++,因此下一次循环之前,保持 循环不变式:A[1..i-1]包含了A中最小的i-1个元素,且已排序。
终止:i=n,已知A[1…n-1]包含了A中最小的i-1个元素,且已排序,因此A[n]中的元素是最大的,因此A[1…n]已排序,证毕。
分治法介绍:分治法就是将原问题分解为多个独立的子问题,且这些子问题的形式和原问题相似,只是规模上减少了,求解完子问题后合并结果构成原问题的解。
分治法通常有3步:Divide(分解子问题的步骤) 、 Conquer(递归解决子问题的步骤)、 Combine(子问题解求出来后合并成原问题解的步骤)。
假设Divide需要f(n)时间,Conquer分解为b个子问题,且子问题大小为a,Combine需要g(n)时间,则递归式为:
T(n)=bT(n/a)+f(n)+g(n)
算法导论思考题4-3(参数传递)能够很好的考察对于分治法的理解。
就如归并排序,Divide的步骤为m=(p+q)/2,因此为O(1),Combine步骤为merge()函数,Conquer步骤为分解为2个子问题,子问题大小为n/2,因此:
归并排序的递归式:T(n)=2T(n/2)+O(n)
而求解递归式的三种方法有:
伪代码:
C++模板:
template <typename T>
void Merge(T *sourceArray, T *temp, int Start_Index, int Mid_Index, int End_Index) {
int i = Start_Index, j = Mid_Index + 1, k = Start_Index;
while (i != Mid_Index + 1 && j != End_Index + 1) {
if (sourceArray[i] > sourceArray[j]) {
temp[k++] = sourceArray[j++];
} else {
temp[k++] = sourceArray[i++];
}
}
while (i != Mid_Index + 1) {
temp[k++] = sourceArray[i++];
}
while (j != End_Index + 1) {
temp[k++] = sourceArray[j++];
}
for (int i = Start_Index; i != End_Index + 1; i++) {
sourceArray[i] = temp[i];
}
}
template <typename T>
void Merge_Sort(T *sourceArray, T *temp, int Start_Index, int End_Index) {
if (Start_Index < End_Index) {
int Mid_Index = (Start_Index + End_Index) / 2;
Merge_Sort(sourceArray, temp, Start_Index, Mid_Index);
Merge_Sort(sourceArray, temp, Mid_Index + 1, End_Index);
Merge(sourceArray, temp, Start_Index, Mid_Index, End_Index);
}
}
C++ 链表的归并排序法:
// 链表的归并排序。
void LinkedList::sort(void) {
if (this->size() > 1) {
node* fast = this->head;
node* slow = this->head;
LinkedList li_left;
LinkedList li_right;
li_left.head = this->head;
while (fast != NULL && fast->next != NULL) {
li_left._size++;
fast = fast->next->next;
slow = slow->next;
}
li_left.tail = slow->prev;
li_left.tail->next = NULL;
li_right.head = slow;
li_right.head->prev = NULL;
li_right.tail = this->tail;
li_right._size = this->_size - li_left._size;
this->head = NULL;
this->tail = NULL;
li_left.sort();
li_right.sort();
node* pointer_left = li_left.head;
node* pointer_right = li_right.head;
node* pointer_head = NULL;
node* pointer_tail = NULL;
while (pointer_left != NULL && pointer_right != NULL) {
node* temp;
if (pointer_left->data <= pointer_right->data) {
temp = pointer_left;
pointer_left = pointer_left->next;
} else {
temp = pointer_right;
pointer_right = pointer_right->next;
}
if (pointer_head == NULL) {
pointer_head = pointer_tail = temp;
} else {
pointer_tail->next = temp;
temp->prev = pointer_tail;
pointer_tail = temp;
}
pointer_head->prev = NULL;
pointer_tail->next = NULL;
}
while (pointer_left != NULL) {
pointer_tail->next = pointer_left;
pointer_left->prev = pointer_tail;
pointer_tail = pointer_left;
pointer_left = pointer_left->next;
}
while (pointer_right != NULL) {
pointer_tail->next = pointer_right;
pointer_right->prev = pointer_tail;
pointer_tail = pointer_right;
pointer_right = pointer_right->next;
}
this->head = pointer_head;
this->tail = pointer_tail;
li_left.head = li_left.tail = NULL;
li_right.head = li_right.tail = NULL;
}
举例说明:
问:归并排序的缺点是什么?
答:他是Out-place sort,因此相比快排,需要很多额外的空间。
问:为什么归并排序比快速排序慢?
答:虽然渐近复杂度一样,但是归并排序的系数比快排大。
问:对于归并排序有什么改进?
答:就是在数组长度为k时,用插入排序,因为插入排序适合对小数组排序。在算法导论思考题2-1中介绍了。复杂度为O(nk+nlg(n/k)) ,当k=O(lgn)时,复杂度为O(nlgn)
算法介绍:
设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。
QUICK_SORT(A,p,r)
if pA,p,r)
QUICKSORT(A,p,q-1)
QUICKSORT(A,q+1,r)
/*
为排序一个完整的数组A,最初的调用是QUICKSORT(A,1,length[A])。
快速排序算法的关键是PARTITION过程,它对子数组A[p..r]进行就地重排:
*/
PARTITION(A,p,r)
x←A[r]
i←p-1
for j←p to r-1
do if A[j]≤x
then i←i+1
exchange A[i]←→A[j]
exchange A[i+1]←→A[r]
return i+1[2]
C++模板:
template
void Quick_Sort(T *array, int Start_Index, int End_Index) {
if (End_Index >= Start_Index) {
int first = Start_Index;
int last = End_Index;
T key = array[first];
while (first < last) {
while (first < last && array[last] >= key) {
last--;
}
array[first] = array[last];
while (first < last && array[first] <= key) {
first++;
}
array[last] = array[first];
}
array[first] = key;
Quick_Sort(array, Start_Index, first - 1);
Quick_Sort(array, first + 1, End_Index);
}
}