1. 冒泡排序
算法介绍:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从末尾一对到开头一对。经过该循环,头元素成为最小的数。
- 对头元素以外的所有元素,重复上述操作。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
代码:
/**
* @Description:冒泡排序
* @author Rain
* @time 2017-2-9 下午2:18:59
*/
public class BubbleSort {
public static void bubbleSort(int[] arr) {
if(arr == null || arr.length == 0) return;
for(int i=0; i<arr.length-1; i++) {
for(int j=arr.length-1; j>i; j--) {
if(arr[j] < arr[j-1])
swap(arr, j-1, j);
}
}
}
public static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
时间复杂度
- 若初始状态是正序的,一次循环即可完成排序。所需的关键字比较次数C和记录移动次数M 均达到最小值n。
冒泡排序最好的时间复杂度为O(n)。(算法中需加入一个标识位进行判断,否则时间复杂度为O(n^2)) - 若初始文件是反序的,需要进行n-1趟排序。每趟排序要进行n-i次关键字的比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:
C=n*(n-1)/2=O(n^2)
M=3n*(n-1)/2=O(n^2)
- 综上,因此冒泡排序总的平均时间复杂度为O(n^2)。
2. 直接选择排序
算法介绍:
每一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
代码:
/**
* @Description: 直接选择排序
* @author Rain
* @time 2017年2月10日 下午1:47:28
*/
public class SelectSort {
public static void selectSort(int[] arr)
{
for(int i = 0; i < arr.length - 1; i++) {
int key = arr[i];
for(int j = i + 1; j < arr.length; j++) {
if(arr[j] < key) {
arr[i] = arr[j];
arr[j] = key;
key = arr[i];
}
}
}
}
}
3. 直接插入排序
算法介绍:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的纪录插入完为止,得到一个新的有序序列。
1. 设置标志位target,将待插入纪录arr[i]的值赋值给target;
2. 设置开始查找比较的位置j (j = i -1);
3. 在数组中进行比较,若target
<
<script type="math/tex" id="MathJax-Element-1"><</script> arr[j],则将第j个纪录后移,直至target找到正确的位置(target ≥ arr[j])为止;
4. 将target插入到arr[j+1];
5. 重复上述循环,直到所有待插入纪录arr[i]插入完毕;
代码:
/**
* @Description: 直接插入排序
* @author Rain
* @time 2017年2月12日 下午4:22:10
*/
public class InsertSort {
public static void insertSort(int[] arr)
{
for(int i = 1; i < arr.length; i++) {
int target = arr[i];
int j = i -1;
while(j >= 0 && target < arr[j]) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = target;
}
}
}
4. 快速排序
算法介绍:
首先任意选取一个数据作为关键数据key,将所有比它小的放在前面,比它大的放在后面。因此分为前后两个部分,然后再按此方法对这两部分分别进行递归排序。
1. 设置两个变量i(i=0), j(j=n-1);
2. 以第一个元素作为关键数据key;
3. 从j开始向前搜索,直到arr[j]
<
<script type="math/tex" id="MathJax-Element-2"><</script> key,然后交换arr[j]和arr[i];
4. 从i开始向后搜索,直到arr[i] > key;然后交换arr[i]和arr[j];
5. 重复3、4步,直到i=j。此时序列已被分为两部分;
6. 对两部分分别进行递归,重复上述排序过程;
代码:
/**
* @Description: 快速排序
* @author 冯雨威
* @time 2017年2月13日 下午12:28:23
*/
public class QuickSort {
public static int partition(int[] arr, int i, int j) {
int key = arr[i];
while( i < j ) {
while( i < j && arr[j] >= key )
j--;
arr[i] = arr[j];
while( i < j && arr[i] <= key )
i++;
arr[j] = arr[i];
}
arr[i] = key;
return i;
}
public static void quickSort(int[] arr, int i, int j) {
if( i >= j ) return;
int target = partition( arr, i, j );
quickSort( arr, i, target - 1 );
quickSort( arr, target + 1, j );
}
}
5. 堆排序
- 堆:有序存储的完全二叉树
- 小顶堆:其中每个非终端结点的关键字都不大于其孩子结点的关键字
- 大顶堆:其中每个非终端结点的关键字都不小于其孩子结点的关键字
算法介绍:
利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录),从而进行排序。
1. 初始化待排序关键字序列为大顶堆,此堆为初始的无序区;
2. 将堆顶元素R[1](最大关键字)与最后一个元素R[n]交换。得到新的无序区R[1…n-1]和有序区R[n]。
3. 再将当前无序区R[1…n-1]调整为新的大顶堆。
4. 重复2、3两步,直到无序区只剩一个元素,排序完成。
代码:
/**
* @Description: 堆排序
* @author 冯雨威
* @time 2017年2月15日 下午5:01:46
*/
public class HeapSort {
/**
* @description:heapAdjust调整大顶堆函数,执行时假设以i结点的左右孩子为根结点的二叉树已经为大顶堆
* @param arr 传入待调整的新堆
* @param i 当前堆arr的根结点
* @param length 当前堆arr的长度
*/
public static void heapAdjust(int[] arr, int i, int length) {
int temp = arr[i]; //i为当前根结点
int child = 2 * i + 1; //初始child为i结点的左孩子,child + 1为其右孩子
while(child < length) {
if(child + 1 < length && arr[child + 1] > arr[child])
child++; //child取左右孩子中最大的一个
if(temp > arr[child])
break; //若i结点的值已经大于其左右孩子,则i为根结点的二叉树已为大顶堆
arr[i] = arr[child]; //若孩子结点大于i结点,则i结点与孩子结点交换
arr[child] = temp;
i = child; //i结点下移,继续调整子树
child = 2 * i + 1;
}
}
public static void heapSort(int[] arr) {
for(int i = arr.length / 2; i >= 0; i-- ) //初始化待排序数组为大顶堆,初始无序区
heapAdjust(arr, i, arr.length);
for(int i = 0; i < arr.length - 1; i++) {
int t = arr[0];
arr[0] = arr[arr.length - i - 1];
arr[arr.length - i -1] = t; //交换堆顶和堆尾元素,将堆顶元素(最大值)移入有序区
heapAdjust(arr, 0, arr.length - i -1); //调整新堆为大顶堆
}
}
}
6. 希尔排序
算法介绍:
希尔排序也称缩小增量排序,是插入排序的一种。选取增量,待排数组根据增量进行分组,对每组数据进行直接插入排序,然后缩小增量,重现分组排序,直到增量缩小为1.
1. 取增量d=n/2,将间隔为d的元素分为一组;
2. 对每组元素进行直接插入排序;
3. 增量d减半(d=d/2),再根据增量d进行分组;
4. 重复2、3两步,直到增量d<1,排序完成。
代码:
/**
* @Description: 希尔排序
* @author 冯雨威
* @time 2017年2月17日 上午10:22:11
*/
public class ShellSort {
public static void shellSort(int[] arr) {
int d = arr.length / 2;
int j = 0;
while( d >= 1 )
{
for(int i = d; i < arr.length; i++) { //以d为间隔分组
int target = arr[i]; //待插入元素
for(j = i - d; j >= 0; j = j - d) { //对分组进行插入排序
if(arr[j] > target)
arr[j+d] = arr[j];
else break;
}
arr[j+d] = target;
}
d = d / 2;
}
}
}
7. 计数排序
算法介绍:
对于待排序数组中的每个元素x,确定出小于等于该元素x的元素个数。根据该信息再将x放到最终输出数组的正确位置。
1. 创建数组C[0..k],使待排数组A[0..n-1]中每个元素值都是0~k区间内的一个整数;
2. 设置数组C[0..k]的值,如C[i]为A中等于i的元素的个数;
3. 通过加总计算,确定A中小于等于i的元素的个数,保存到C[i]中;
4. 根据数组C的信息,将A中的元素放到输出数组B的正确位置,排序完成;
代码:
/**
* @Description: 计数排序
* @author 冯雨威
* @time 2017年2月20日 下午3:36:07
*/
public class CountingSort {
public static int[] countingSort(int[] arr)
{
int k = arr[0];
for(int i = 1; i < arr.length; i++) {
if(k < arr[i])
k = arr[i];
} //k为待排数组中最大值
int c[] = new int[k + 1]; //初始化数组c
//c[i]中保存arr中等于i的元素的个数
for(int i : arr) {
c[i]++;
}
//加总计算,c[i]中保存arr中小于等于i的元素的个数
for(int i = 1; i < c.length; i++) {
c[i] = c[i-1] + c[i];
}
//创建输出数组b,将arr[i]放入b中的正确位置
int b[] = new int[arr.length];
for(int i : arr) {
b[c[i] - 1] = i;
c[i]--;
}
return b;
}
}
8. 桶排序
算法介绍:
根据待排序列,划分出M个子区间(M个桶),通过某种映射函数,将待排序列元素依次放入对应区间内。再对每个桶内的元素进行比较排序。最后依次取出各桶中的元素。
1. 以待排序列元素值全在1-100之间为例。创建映射函数f(k) = k / 10,划分出10个桶;
2. 创建数组buckets,每个数组元素buckets(i)为一个桶;
3. 每个buckets(i)中存放一个链表,通过映射函数将待排元素放到相应的链表内;
4. 对每个buckets(i)中的链表进行快速排序;
5. 依次取出每个buckets(i)中的元素,排序完成;
代码:
/**
* @Description: 桶排序(待排序列取值范围1-100,以10为间隔分为10个桶)
* @author Rain
* @time 2017-2-21 下午3:24:31
*/
public class BucketSort {
public static void bucketSort(int[] arr)
{
int bucketnum = 10; //桶的个数为10
List<List<Integer>> buckets = new ArrayList<List<Integer>>();
//每个桶中存放一个链表
for(int i = 0; i < bucketnum; i++) {
buckets.add(new LinkedList<Integer>());
}
//将待排元素依次放入到相应的桶内
for(int i = 0; i < arr.length; i++) {
buckets.get( f(arr[i]) ).add(arr[i]);
}
//对每个桶内的元素进行比较排序
for(List<Integer> list : buckets) {
if(list.isEmpty())
break;
else Collections.sort(list);
}
//依次取出桶中的元素到arr中
int k = 0;
for(List<Integer> list : buckets) {
for(int j : list ) {
arr[k] = j;
k++;
}
}
}
//f(k)为映射函数
public static int f(int k)
{
int d = k / 10; //10 = 待排序列取值范围 / 桶的个数
return d;
}
}
总结
类别 | 排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|
插入排序 | 直接插入排序 | O(n2) | O(1) | 稳定 |
插入排序 | 希尔排序 | 不确定 | O(1) | 不稳定 |
选择排序 | 直接选择排序 | O(n2) | O(1) | 不稳定 |
选择排序 | 堆排序 | O(nlog2n) | O(1) | 不稳定 |
交换排序 | 冒泡排序 | O(n2) | O(1) | 稳定 |
交换排序 | 快速排序 | O(nlog2n) | O(nlog2n) | 不稳定 |
计数排序 | 计数排序 | O(n+k) | O(k) | 稳定 |
桶排序 | 桶排序 | O(n+c) | O(n+m) | 稳定 |
- n为待排关键字个数;k为待排序列整数取值范围;m为共划分的桶的个数;桶排序时间复杂度=O(n)+O( m(n/m)log(n/m) )=O( n+n(logn-logm) )=O(n+c),其中c=n( logn-logm )
注明:转载请提示出处:http://blog.csdn.net/f18_1_9_14/article/details/61918579