1.冒泡排序
起泡排序的思想非常简单。首先,将 n 个元素中的第一个和第二个进行比较,如果两个元素的位置为逆序,则交换两个元素的位置;进而比较第二个和第三个元素关键字,如此类推,直到比较第 n-1 个元素和第 n 个元素为止;上述过程描述了起泡排序的第一趟排序过程,在第一趟排序过程中,我们将关键字最大的元素通过交换操作放到了具有 n 个元素的序列的最一个位置上。然后进行第二趟排序,在第二趟排序过程中对元素序列的前 n-1 个元素进行相同操作,其结果是将关键字次大的元素通过交换放到第 n-1 个位置上。一般来说,第 i 趟排序是对元素序列的前 n-i+1 个元素进行排序,使得前 n-i+1 个元素中关键字最大的元素被放置到第 n-i+1 个位置上。排序共进行 n-1 趟,即可使得元素序列按关键字有序。
public void Bubble_Sort(int A[], int N) { for (int P = N - 1; P >= 0; P--) { int flag = 0; for (int i = 0; i < P; i++) { if (A[i] > A[i + 1]) { int temp = A[i]; A[i] = A[i + 1]; A[i + 1] = temp; flag = 1; } } if (flag == 0) break; } for (int i = 0; i < N; i++) { System.out.print(A[i] + " "); } }
【效率分析】
空间效率: 仅使用一个辅存单元。
时间效率: 假设待排序的元素个数为 n,则总共要进行 n-1 趟排序,对 j 个元素的子序列进行一趟起泡排序需要进行 j-1 次关键字比较。由此,起泡排序的总比较次数为
因此,起泡排序的时间复杂度为Ο(n~2)。
2.快速排序
它的魅力之处在于它能在每次partition(排序算法的核心所在)都能为一个数组元素确定其排序最终正确位置(一次就定位准,下次循环就不考虑这个元素了)。
快速排序是将分治法运用到排序问题中的一个典型例子,快速排序的基本思想是:通过一个枢轴(pivot)元素将
n 个元素的序列分为左、右两个子序列 Ll 和 Lr,其中子序列 Ll中的元素均比枢轴元素小,而子序列 Lr 中的元素均比枢轴元素大,然后对左、右子序列分别进行快速排序,在将左、右子序列排好序后,则整个序列有序,而对左右子序列的排序过程直到子序列中只包含一个元素时结束,此时左、右子序列由于只包含一个元素则自然有序。
用分治法的三个步骤来描述快速排序的过程如下:
划分步骤:通过枢轴元素 x 将序列一分为二, 且左子序列的元素均小于 x,右子序列的元素均大于 x;
治理步骤:递归的对左、右子序列排序;
组合步骤:无
从快速排序算法的描述中我们看到,快速排序算法的实现依赖于按照枢轴元素x对待排序序列进行划分的过程。
public static void Quick_Sort(int[] A, int begin, int end) { if (begin < end) { // 枢轴选定后永远不变,最终在中间,前小后大 int key = A[begin]; int i = begin; int j = end; // 大的元素放在右边,小的元素放在左边,来实现子集划分。 while (i < j) { // 两端交替向内部扫描。 while (i < j && A[j] > key) { // 当右侧元素大于枢轴元素,符合条件则指针左移。 j--; } if (i < j) { // 当满足上面条件时,将两个元素倒换位置,并使指针从开始位置右移。 A[i] = A[j]; i++; } while (i < j && A[i] < key) { // 当右侧元素大于枢轴元素,符合条件则指针右移。 i++; } if (i < j) { A[j] = A[i]; j--; } } A[i] = key; System.out.println(i); Quick_Sort(A, begin, i - 1); Quick_Sort(A, i + 1, end); } } public static void quickSort(int[] n, int left, int right) { int pivot; if (left < right) { // pivot作为枢轴,较之小的元素在左,较之大的元素在右 pivot = partition(n, left, right); // 对左右数组递归调用快速排序,直到顺序完全正确 quickSort(n, left, pivot - 1); quickSort(n, pivot + 1, right); } } public static int partition(int[] n, int left, int right) { int pivotkey = n[left]; // 枢轴选定后永远不变,最终在中间,前小后大 while (left < right) { while (left < right && n[right] >= pivotkey) --right; // 将比枢轴小的元素移到低端,此时right位相当于空,等待低位比pivotkey大的数补上 n[left] = n[right]; while (left < right && n[left] <= pivotkey) ++left; // 将比枢轴大的元素移到高端,此时left位相当于空,等待高位比pivotkey小的数补上 n[right] = n[left]; } // 当left == right,完成一趟快速排序,此时left位相当于空,等待pivotkey补上 n[left] = pivotkey; return left; }
时间效率:快速排序算法的运行时间依赖于划分是否平衡,即根据枢轴元素 pivot 将序列划分为两个子序列中的元素个数,而划分是否平衡又依赖于所使用的枢轴元素。
在平均情况下,经验证明,在所有同数量级的排序方法中,快速排序的常数因子
k 是最小的。因此就平均时间而言,快速排序被认为是目前最好的一种内部排序方法。
快速排序的平均性能最好,但是,若待排序序列初始时已按关键字有序或基本有序,则快速排序蜕化为起泡排序,其时间复杂度为Ο(n2)。为改进之,可以采取随机选择枢轴元素pivot的方法,具体做法是,在待划分的序列中随机选择一个元素然后与r[low]交换,再将r[low]作为枢轴元素,作如此改进之后将极大改进快速排序在序列有序或基本有序时的性能,在待排序元素个数n较大时,其运行过程中出现最坏情况的可能性可以认为不存在。
空间效率:虽然从时间上看快速排序的效率优于前述算法,然而从空间上看,在前面讨论的算法中都只需要一个辅助空间,而快速排序需要一个堆栈来实现递归。若每次划分都将序列均匀分割为长度相近的两个子序列,则堆栈的最大深度为 log
n,但是,在最坏的情况下,堆栈的最大深度为 n。