C#数据结构与算法揭秘16
这节我们就用的最多的算法——排序发起重点的讨论。
常见的排序分为冒泡排序,快速排序,直接插入排序 ,希尔排序,基数排序 ,简单选择排序 ,堆排序 等等。
一、冒泡排序
冒泡排序(Bubble Sort)的基本思想是:将相邻的记录的关键码进行比较,若前面记录的关键码大于后面记录的关键码,则将它们交换,否则不交换。
设待排序的顺序表 sqList 中有 n 个记录,冒泡排序要进行 n-1 趟,每趟循环均是从最后两个记录开始。 第 1 趟循环到第 2 个记录的关键码与第 1 个记录的关键码比较后终止, 第 2 趟循环到第 3 个记录的关键码与第 2 个记录的关键码比较结束后终止。一般地,第 i 趟循环到第 i+1 个记录的关键码与第 i 个记录的关键码比较后终止,所以,第 n-1 趟循环到第 n 个记录的关键码与第 n-1 个记录的关键码比较后终止。
冒泡排序算法的实现如下所示,算法中记录的比较表示记录关键码的比较,顺序表中只存放了记录的关键码: 实现的源代码如下:
//进行冒泡排序的方法 public void BubbleSort(SeqList<int> sqList) { ///进行交换的变量 int tmp; for (int i = 0; i < sqList.Last; ++i) { for (int j = sqList.Last - 1; j >= i; --j) { //如果后面比前面大, 就交换 if (sqList[j + 1] < sqList[j]) { tmp = sqList[j + 1]; sqList[j + 1] = sqList[j]; sqList[j] = tmp; } } } } //由于是双层循环 算法的时间的复杂度是O(n^2)
冒泡排序算法的最好情况是记录已全部排好序,这时,循环 n-1 次,每次循环都因没有数据交换而退出。因此,冒泡排序算法在最好情况下的时间复杂度为O(n)。
总的移动次数为比较次数的 3 倍, 因为被进行一次比较, 需要进行 3 次移动。因此,冒泡排序算法在最坏情况下的时间复杂度为O(n2)。
冒泡排序算法只需要一个辅助空间用于交换记录,所以,冒泡排序算法是一种稳定的排序方法。什么是稳定的排序算法,就是前面与后面的数字相等的话,不用交换。
二、快速排序
快速排序(Quick Sort)的基本思想是:通过不断比较关键码,以某个记录为界(该记录称为支点) ,将待排序列分成两部分。其中,一部分满足所有记录的关键码都大于或等于支点记录的关键码, 另一部分记录的关键码都小于支点记录的关键码。把以支点记录为界将待排序列按关键码分成两部分的过程,称为一次划分。对各部分不断划分,直到整个序列按关键码有序为止。
设待排序的顺序表 sqList 中有 n 个记录,一般地,第一次划分把第一个记录作为支点。首先,把支点复制到一个临时存储空间中,并设两个指示器,一个指示器 low,指向顺序表的低端(第一个记录所在位置) ,一个指示器 high,指向顺序表的高端(最后一个记录所在位置) 。然后,从 high 所指向的记录开始,将记录的关键码与支点(在临时存储空间中)的关键码进行比较,如果 high 所指向的记录的关键码大于支点的关键码,high 指示器向顺序表的低端方向移动一个记录的位置,否则,将 high 所指的记录复制到 low 所指的存储空间中。接着,又将 low 移到下一个记录,从 low 所指向的记录开始,将记录的关键码与临时存储空间的记录的关键码进行比较, 如果 low 所指向的记录的关键码小于临时存储空间的记录的关键码,low 指示器向顺序表的高端方法移动一个记录的位置,否则,将 low 所指的记录复制到 high 所指的存储空间中,high 指示器向顺序表的低端移动一个记录的位置。如此重复,直到 low 和 high 指示器指向同一个记录,
将临时空间的记录赋给 low 所指向的存储空间,第一次划分结束。如图所示:
快速排序的算法实现如下所示,算法中记录的比较表示记录关键码的比较顺序表中只存放了记录的关键码。相应源代码如下:
1 public void QuickSort(SeqList<int> sqList, int low, int high) 2 { 3 //头指针 4 int i = low; 5 //尾指针 6 int j = high; 7 //关键码 8 int tmp = sqList[low]; 9 //头指针尾指针位置的变换 10 while (low < high) 11 { 12 while ((low < high) && (sqList[high] >= tmp)) 13 { 14 --high; 15 } 16 sqList[low] = sqList[high]; 17 ++low; 18 while ((low < high) && (sqList[low] <= tmp)) 19 { 20 ++low; 21 } 22 sqList[high] = sqList[low]; 23 --high; 24 } 25 sqList[low] = tmp; 26 //新一轮的变换 27 if (i < low-1) 28 { 29 QuickSort(sqList, i, low-1); 30 } 31 //新一轮的递归 32 if (low+1 < j) 33 { 34 QuickSort(sqList, low+1, j); 35 } 36 } 37 } 38 //由于用到递归 循环 时间复杂度是O(nlog2n)
快速排序算法的时间复杂度和每次划分的记录系很大。 如果每次选取的记录都能均分成两个相等的子序列,这样的快速排序过程是一棵完全二叉树结构(即每个结点都把当前待排序列分成两个大小相当的子序列结点,n个记录待排序列的根结点的分解次数就构成了一棵完全二叉树) ,这时分解次数等于完全二叉树的深度log2n。每次快速排序过程无论把待排序列这样划分,全部的比较次数都接近于n-1 次,所以,最好情况下快速排序的时间复杂度为O(nlog2n) 。快速排序算法的最坏情况是记录已全部有序,此时n个记录待排序列的根结点的分解次数就构成了一棵单右支二叉树。 所以在最坏情况下快速排序算法的时间复杂度为O(n2)。一般情况下,记录的分布是随机的,序列的分解次数构成一棵二叉树,这样二叉树的深度接近于log2n,所以快速排序算法在一般情况下的时间复杂度为O(nlog2n) 。 另外,快速排序算法是一种不稳定的排序的方法。不稳定排序的算法 ,前后数字相等要交换。
这节,我们介绍了快速排序和冒泡排序。下节,我们介绍直接插入排序和希尔排序。嘎嘎。