查找
1 线性查找
线性查找就是在序列中逐个进行比较看是否为查找的值。时间复杂度为O(n),对于随机的序列,线性查找是一种较好的查找算法。
在a[]中线性查找key的AWK代码
1. function line_search(a[], n, key){
2. for(i = 0; i < n; ++i)
3. if(key == a[i]) return i;
4. return -1;
5. }
2 折半查找
折半查找算法针对排好序的序列(也不一定非要排序已好的,如位二分)。
在从小到大序列a[]中查找key的折半查找算法AWK伪码
1. function binary_search(a[], n, key){
2. start = 0 end = n -1
3. wihle(start <= end)
4. mid = (start + end) / 2
5. if(a[mid] < key) start = mid + 1
6. else if(a[mid] > key) end = mid – 1
7. else return mid
8. return -1
9. }
折半查找法大约要执行log2n次比较操作,时间复杂度为O(log2n),由于线性查找法。n与log2n,看看math。
排序
1 插入排序
插入排序是根据插入元素[i]在有序序列中查找插入位置并腾出这个位置的过程。
(1) 直接插入排序
[个人理解]
初始默认序列的第一个元素为有序序列,然后逐个插入未序序列中的元素到有序序列中的正确位置之上。因为整个序列占的存储空间是固定的,在不直接交换两个存储空间值得情况下需要辅助空间来保存某个元素,以让保存整个序列的空间“腾出”空位来进行操作。
Table 1. AWK简单插入排序伪码(从小到大方式排序)
[1] 边比较插入元素与当前元素的大小并根据结果决定是否移位,然后在正确的位置插入新元素 1. function insert_once(a[], n){ 2. for(i = 1; i < n; ++i){ //使a[0..i-1]成为有序序列 3. temp = a[i] //让&a[i]成为可用的存储空间 4. for(j = i – 1; temp < a[j] && j >= 0; --j) 5. a[j+1] = a[j] //在有序序列中找到一个比temp小的元素就将其往后移动 6. a[j+1] = temp //将temp插入到有序序列中的正确位置上 7. } 8. } |
[2] 通过比较插入元素与各位置上元素的大小找到插入的位置,再移位,再插入新元素 1. funciotn insert_location(a[], n){ 2. for(i = 1; i < n; ++i){ 3. temp = a[i] 4. k = j = i – 1 5. while(temp < a[j] && j >= 0) 6. --j;//循环结束后,j表示temp插入位置的前一个位置 7. while(k > j)//移位 8. a[k+1] = a[k] 9. --k 10. a[j+1] = temp 11. } 12. } |
《算法导论》用过循环不变式[p.10]的三条准则来验证此算法的正确性。对于AWK程序,可以再用AWK程序搭建一个脚手架程序来随机生成一串序列来充当a,以验证编写的程序是否正确。
[时间复杂度]
平均及最坏情况为O(n2),最好情况为O(n)。
以下代码未搭建AWK脚手架来测试其正确性。
1. /* @brief: Easy sort number from small to big by insert method
2. * @arg: a is the array which will be sorted, n is the length of the a
3. * @rel: none
4. */
5. void insert_once(INT32 *a, INT32 n)
6. {
7. int i, j, k;
8. INT32 temp = 0;
9.
10. for(i = 1; i < n; ++i){
11. temp = a[i];
12. for(j = i - 1; temp < a[j] && j >= 0; --j){
13. a[j+1] = a[j];
14. }
15. a[j+1] = temp;
16.
17.
18. //Print on screen
19. for(k = 0; k < n; ++k){
20. printf("%d ", a[k]);
21. }
22. printf("\n");
23. }
24. }
(2) 折半插入排序
直接插入排序需要线性查找插入的位置。折半插入排序实际是将线性查找改为折半查找。但元素后移的操作还是跟直接插入一样。时间复杂度还是O(n),对于直接插入排序来讲,时间复杂度没有宏观上较大的改进。
折半插入排序AWK伪码
1. function b_insert(a[], n){
2. for(i = 1; i < n; ++i){
3. temp = a[i]
4. low = 0
5. high = i -1 //low 与high作为有序序列的下标的下上限
6. while(low <= high){ //找到temp(a[i])应该插入的位置high+1
7. m = (low + high) /2
8. if(temp < a[m]) high = m -1
9. else low = m + 1
10. }
11. for(j = i – 1; j > high; --j){//依次往后移动比temp大的元素
12. a[j + 1] = a[j]
13. }
14. a[high+1] = temp //插入到正确位置
15. }
16. }
(3) shell排序
对于直接插入排序来说,若排序列为“正序”时,其时间复杂度可为O(n);如果n很小,直接插入排序的效率也比较高。希尔排序正是从这两点分析得出的一种排序方法,它的基本思想为:现将整个待排记录分割成为若干子序列分别进行直接插入排序,待整个序列“基本有序”时,再对全体记录进行一次直接插入排序。[摘自《数据结构.严蔚敏吴伟民》]
选取“合适增量序列”排序的shell排序伪码
1. function shell_sort(a[], inc[], n){
2. for(i = 0; i < n; ++i)
3. shell_insert(a, inc[i]) //inc[i]是插入排序的增量,最后一个inc[n-1]为1
4. }
一趟shell排序
1. function shell_insert(a[], inc, n){
2. for(i = inc + 1; i < n; ++i){ //待插入元素为a[inc+1],与首元素相隔inc个元素
3. if(a[i] < a[i - inc]) temp = a[i] //分割的子序列已经有序,故而需要判断
4. for(j = i – inc; temp < a[j] && j > 0; j -= inc) //需要以inc增量继续往前寻找temp插入的位置
5. a[j+inc] = a[j]
6. a[j+inc] = temp //在正确的位置插入temp
7. }
8. }
Shell排序的分析是一个复杂的问题,因为它的时间是获取增量序列的函数。到目前为止尚未有人求得一种最好的增量序列。增量序列可以有各种取法,但需注意:应使增量序列中的值没有除1之外的公因子,并且最后一个增量值必须等于1。
2 选择排序
(1) 简单选择排序
简单选择排序是在n – i + 1个元素中通过n – i次比较找出最小(大)的元素,并将这个元素与第i个元素交换。(1 <= i <= n,i可以被理解成第i个最小(大)的元素,i前面的元素已经有序)。
[AWK码]
简单选择排序AWK伪码
1. function select_sort(a[], n){
2. for(j = 0; j < n - 1; ++j)
3. t = j
4. for(k = j + 1; k < n; ++k)
5. if(a[t] > a[k]) t = k
6. swap(j,k ) //交换a[j]和a[t]的值
7. }
平均、最差和最好的复杂级别:O(n2)。是n – i(n – 1 - j)次比较与外层循环的n构成了O(n2)。
(2) 堆排序
堆排序的笔记:堆及对排序。操作过程是“建堆”(《编程珠玑》内siftup方法比《数据结构》严蔚敏 吴伟民中介绍的建堆方法要好)。“堆”的根元素具有最小(或大)的属性,将“堆”的最后一个元素和根元素交换,将最小和最大元素保存在“最后位置上”。然后再对剩余的元素进行“建堆”操作,再满足“堆”属性的序列的首元素和最后一个元素交换…….直至剩下最后一个元素。时间复杂度不会超过O(nlog2n)。
3 归并排序
归并排序的时间复杂度为O(nlgn)。内部排序算法2.4中有点记录。
4 快速排序
快速排序是对气泡排序的一种改进。它的基本思想是,通过一趟快速排序将待排序列分割成独立的两部分,其中一部分序列的均比另一部分序列小。再分别对独立的两部分序列按照一趟快速排序的方式进行分割……。当将序列分割成1个元素时,就不必再进行分割。
对于一趟快速排序可以采用Bob Sedgewick方法(见快速排序的层次改进1.3,次笔记来自编程珠玑11章)。
当被排序列相等或已经有序时快速排序运行的时间复杂度为最坏情况:O(n2)。可以再改进一趟快速排序代码使得快速排序在这两种情况下的时间复杂度仍为O(n2),见快速排序的层次改进2.1。
依靠(改进的)一趟快速排序的快速排序算法有递归和非递归两种方法,见内部排序算法笔记。
递归一趟快速排序或者使用迭代法调用一趟快速排序就可以得到快速排序,时间复杂度为O(nlgn)。使用改进的一趟快速排序代码的快速排序程序就可以当成产品级别的了(效率上)。