交换排序
交换排序的基本思想:两两比较待排序元素的关键字,发现两个元素的次序相反时即进行交换,直到没有反序的元素为止。冒泡排序是一种典型的交换排序的方法,而快速排序是通过交换方式以基准元素为分界线将待排数据序列一分为二的。
冒泡排序
- 基本思想
冒泡排序在众多排序算法中算比较简单的一个。在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒(反之亦可)。即:每当两相邻的关键字比较后发现它们的排序与排序要求相反时,就将它们互换。 使得经过一次冒泡排序后,关键字最小(最大的元素到达最上端)。接着再在剩下的元素中找关键字次小(次大)的元素,并把它换在第二个位置上。依次类推,一直到所有元素都有序为止。 - 例子
{10,6,5,3,8,9,1,4,2,7}
第一次
10,6,5,3,8,9,1,4,2,7——比较关键字 2,7
10,6,5,3,8,9,1,2,4,7——比较关键字 4,2 交换
10,6,5,3,8,9,1,2,4,7——比较关键字 1,2
10,6,5,3,8,1,9,2,4,7——比较关键字 9,1 交换
10,6,5,3,1,8,9,2,4,7——比较关键字 8,1 交换
10,6,5,1,3,8,9,2,4,7——比较关键字 3,1 交换
10,6,1,5,3,8,9,2,4,7——比较关键字 5,1 交换
10,1,6,5,3,8,9,2,4,7——比较关键字 6,1 交换
1,10,6,5,3,8,9,2,4,7——比较关键字 10,1 交换
第二次排序结果: 1,2,10,6,5,3,8,9,4,7
第三次排序结果: 1,2,3,10,6,5,4,8,9,7
第四次排序结果: 1,2,3,4,10,6,5,7,8,9
第五次排序结果: 1,2,3,4,5,10,6,7,8,9
第六次排序结果: 1,2,3,4,5,6,10,7,8,9
第七次排序结果: 1,2,3,4,5,6,7,10,8,9
第八次排序结果: 1,2,3,4,5,6,7,8,10,9
第九次排序结果: 1,2,3,4,5,6,7,8,9,10 - 算法分析
空间复杂度 O(1)
时间复杂度:最好情况,若初始序列为正序,则一次扫描即可完成排序,比较次数n-1,移动次数0,时间复杂度为O(n)。
最坏情况 ,初始序列为逆序,进行n-1趟排序,每趟进行n-i+1次比较,移动次数为n(n-1)/2 最坏的时间复杂度为O(n²)。
平均时间复杂度为O(n²)。
另外,当i>j且arr[i] = arr[j]时,两者没有逆序,不会发生交换。所以说冒泡排序是一种稳定的排序方法。 - Java代码实现
package com.gray;
import java.util.Arrays;
public class BubbleSort {
public static void main(String args[]) {
int a[] = {10, 6, 5, 3, 8, 9, 1, 4, 2, 7};
function(a);
System.out.println("排序后:" + Arrays.toString(a));
}
public static void function(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = arr.length - 1; j > i; j--) {
if (arr[j] < arr[j - 1]) {
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
}
}
}
}
}
快速排序
快速排序(Quicksort)是对冒泡排序的一种改进。采用”分而治之“的思想所以也被称为分治法。但更好理解的应该是把它看做是挖坑填坑的过程。
- 基本思想
在待排序的n个元素中任取一个元素作为基准(一般取第一个元素),通过扫描序列被其分成两部分,一部分比它小的放左边,一部分比它大的放右边,这个过程就叫一趟快速排序。以后对所有的两部分分别重复上述过程(对了。。没错递归来了),直至每部分内只有一个元素或为空为止。 - 例子
{6,10,5,3,8,9,1,4,2,7}
第一次
6拿出来当基准,挖了一个坑,想象一下有两个指针,i从左边开始,j从右边开始
空,10,5,3,8,9,1,4,2,7——j从右边开始
空,10,5,3,8,9,1,4,2,7——7比6大 继续 j–
2,10,5,3,8,9,1,4,空,7——2比6小,填坑挖新坑 i从左边开始
2,空,5,3,8,9,1,4,10,7——10比6大,填坑挖新坑 j–
2,4,5,3,8,9,1,空,10,7——4比6小,填坑挖新坑 i++
2,4,5,3,8,9,1,空,10,7——5比6小 i++
2,4,5,3,8,9,1,空,10,7——3比6小 i++
2,4,5,3,空,9,1,8,10,7——8比6大 填坑挖新坑 j–
2,4,5,3,1,9,空,8,10,7——1比6小 填坑挖新坑 i++
2,4,5,3,1,空,9,8,10,7——9比6大 填坑挖新坑 i=j结束 6填会到坑里面
[2,4,5,3,1]6[9,8,10,7]
第一次排序结束后形成被基准6分割的局面,左边比6小,右边比6大。
之后分别对两边的无序序列进行同样的操作。 - 算法分析
快速排序算法的时间主要耗费在划分操作上,对长度为n的区间进行划分,共需要n-1次关键字的比较。最坏的情况是每次划分的基准都是当前无序区中关键字最小(或最大)的元素,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中元素的数目,仅仅比划分前的无序区中元素个数减少一个,因此,快速排序必须做n-1次划分,第i次划分开始时区间长度为n-i+1,所需的比较次数为n-i,故总的比较次数达到最大值n(n-1)/2 = O(n²)。
最好的情况下,每次划分所取得基准都是当前无序区的“中值”元素,划分的结果是基准的左、右两个无序子区间的长度大致相等。设C(n)表示对长度为n的表进行快速排序所需的比较次数,比较次数为n-1,然后递归进行排序的比较次数为2C(n/2),假设表长度为n=2^k,C(n)=n1+2C(n/2),展开此递归关系式即可求得
C(n)<=n+2C(n/2)<=n+2(n/2+2C(n/2²))
<=…<=kn+2^kC(n/2^k)
=nlog2n+nC(1)=O(nlog2n)
所以快速排序算法的最好时间复杂度为O(nlog2n)。
快速排序算法的平均时间复杂度为O(nlog2n)。
若每一趟排序都将元素序列均匀地分割成两个长度接近的子序列时,其深度是O(log2n),所需栈空间为O(log2n)。但是在最坏的情况下栈空间为O(n),平均所需栈空间为O(log2n),所以快速排序的空间复杂度O(log2n)。
在快速排序挖坑填坑的过程中,如上例子所示,10会跑到7的前面,本来10是应该在7的后面的,现在关键字的相对位置改变了,所以说快速排序是一种不稳定的排序方法。 - Java代码实现
1.递归实现
package com.gray;
import java.util.Arrays;
import java.util.Stack;
public class Quicksort {
public static void main(String args[]) {
int a[] = {6, 10, 5, 3, 8, 9, 1, 4, 2, 7};
function(a);
System.out.println("排序后:" + Arrays.toString(a));
}
public static void function(int[] arr) {
quickSort(arr, 0, arr.length - 1);
}
private static void quickSort(int[] arr, int s, int t) {
int i = s;
int j = t;
int temp;
if (s < t) {
temp = arr[s];
while (i != j) {
while (j > i && arr[j] > temp)
j--;
arr[i] = arr[j];
while (i < j && arr[i] < temp)
i++;
arr[j] = arr[i];
}
arr[i] = temp;
quickSort(arr, s, i - 1);
quickSort(arr, i + 1, t);
}
}
}
2.非递归实现(没错,就是栈啦)
package com.gray;
import java.util.Arrays;
import java.util.Stack;
public class Quicksort {
public static void main(String args[]) {
int a[] = {6, 10, 5, 3, 8, 9, 1, 4, 2, 7};
function(a);
System.out.println("排序后:" + Arrays.toString(a));
}
private static void function(int[] arr) {
Stack<Integer> stack = new Stack<Integer>();
stack.push(0);
stack.push(arr.length - 1);
while (!stack.empty()) {
int j = stack.pop();
int i = stack.pop();
if (i < j) {
int k = quickSortNoRec(arr, i, j);
if (i < k - 1) {
stack.push(i);
stack.push(k - 1);
}
if (j > k + 1) {
stack.push(k + 1);
stack.push(j);
}
}
}
}
private static int quickSortNoRec(int[] arr, int s, int t) {
int i = s;
int j = t;
int temp;
if (s < t) {
temp = arr[s];
while (i != j) {
while (j > i && arr[j] > temp)
j--;
arr[i] = arr[j];
while (i < j && arr[i] < temp)
i++;
arr[j] = arr[i];
}
arr[i] = temp;
}
return i;
}
}