您的位置:首页 > 编程语言 > Java开发

深入浅出内部排序中的八大排序算法(java版)

2017-09-29 13:26 309 查看
概述

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。 这里说说八大排序就是内部排序。

内部排序

|———–选择类排序(每趟排序从序列中选择出最大或最小的元素与序列中最后或者第一个元素交换,最小或最大的元素到位)

| |———堆排序

| |———选择排序

|———–插入类排序(向已经有序序列中插入新元素)

| |———插入排序(包括直接插入排序和折半插入排序)

| |———希尔排序(缩小增量的插入排序)

|———–交换类排序(每次”交换”使得一个元素排到它最终的位置)

| |———冒泡排序

| |———快速排序

|———–归并类排序(每次”交换”使得一个元素排到它最终的位置)

| |———归并排序(包括二路归并排序和自顶向下的归并排序)

|———–基数类排序(多关键字排序)

| |———基数排序

内部排序具体java代码实现:

0 排序工具类介绍

/**
* 排序算法中用到的工具类
*
* @author William Python
*
*/
public class SortUtils {
/**
* 判断V是否小于W
*
* @param v
* @param w
* @return
*/
public static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
/**
* 判断V是否大于W
*
* @param v
* @param w
* @return
*/
public static boolean equal(Comparable v, Comparable w) {
return v.compareTo(w) == 0;
}
/**
* 交换a[i]与a[j]的值
*
* @param a
* @param i
* @param j
*/
public static void exch(Comparable[] a, int i, int j) {
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static int min(int a, int b) {
return (a < b) ? a : b;
}
}


1 插入排序

1.1 直接插入排序

*先找到待插入元素的插入位置,先移位再赋值插入

/**
* 插入排序的核心版本
*
* @param a
* @param lo
* @param hi
*/
public static void sort(Comparable[] a, int lo, int hi) {
Comparable temp = null;
for (int i = lo + 1; i <= hi; i++) {
if (SortUtils.less(a[i], a[i - 1])) {// //判断一下提高效率
temp = a[i];// 复制为哨兵,即存储待排序元素
int j;
for (j = i; j > 0 && SortUtils.less(temp, a[j - 1]); j--) {
a[j] = a[j - 1];//移位操作
}
a[j] = temp;
}
}
}
/**
* 给外部使用的插入排序接口
*
* @param a
*/
public static void sort(Comparable[] a) {
sort(a, 0, a.length - 1);
}


1.2 折半插入排序

*使用折半查找的方法找到待插入元素的插入位置,找到了说明有重复元素,插入到第一个重复元素的后面(保证排序的稳定性),查找失败则插入到hi+1位置上即可

/**
* 折半插入排序
*
* @param a
*/
public static void sort(Comparable[] a) {
Comparable temp = null;
for (int i = 1; i < a.length; i++) {
if (SortUtils.less(a[i], a[i - 1])) {
temp = a[i];// 复制为哨兵,即存储待排序元素
int mid, lo = 0, hi = i - 1;
while (lo <= hi) {
mid = (lo + hi) / 2;
if (SortUtils.equal(a[mid], a[i])) {// 有重复情况
for (int j = i - 1; j > mid; j--) {
a[j + 1] = a[j];
}
a[mid + 1] = temp;
break;
} else if (SortUtils.less(a[i], a[mid])) {
hi = mid - 1;
} else {
lo = mid + 1;
}
}
// 当lo>hi时,将待插入的元素放入到hi+1下标处
if (lo > hi) {
for (int j = i - 1; j > hi; j--) {
a[j + 1] = a[j];
}
a[hi + 1] = temp;
}
}
}
}


1.3希尔排序(缩小增量的插入排序)

public static void sort(Comparable[] a) {
int n = a.length;// 数组长度
int h = 1;// 增量(要求增量变化的值之间互质,最小的数值必须是1)
int k = 3;// 保证h个子序列中的每个序列中至少有k个元素
while (h < n / k) {// h=1,4,13,40...;这里只是h的一种取法,不止一种
h = 3 * h + 1;
}
while (h >= 1) {
Comparable temp = null;
for (int i = h; i < n; i++) {
if (SortUtils.less(a[i], a[i - h])) {
temp = a[i];
int j;
for (j = i; j >= h && SortUtils.less(temp, a[j - h]); j -= h) {
a[j] = a[j - h];
}
a[j] = temp;
}
}
h = h / 3;
}
}


2 选择类排序

2.1 简单选择排序

public static void sort(Comparable[] a) {
int n =a.length;

for(int i=0;i<n-1;i++){
int min =i;
for(int j=i+1;j<n;j++){
if(SortUtils.less(a[j],a[min])){
min =j;
}
}
if(min!=i){
SortUtils.exch(a, i, min);
}
}

}


2.2 推排序

堆的存储: 一般都用数组来表示堆,如果数组下标0处开始存放数据,那么i结点的父结点下标就为(i – 1) / 2, 它的左右子结点下标分别为(2 * i+1)和(2 * i + 2)。

(下面代码解释中节点和元素的意思一样)

public static void sort(Comparable[] a) {
// 1,建立初始堆,因为可能有多个元素不符合堆的定义
int N = a.length;
//从右往左,从下到上调整(下沉)非叶子节点
for (int i = ((N - 1) - 1) / 2; i >= 0; i--) {
sink(a, i, N);
}
// 2,与首元素交换后下沉首元素调整堆,因为只有首元素不满足堆的定义,下沉首元素即可
while (N > 1) {
//2.1 交换元素
SortUtils.exch(a, 0, --N);
//2.2 首元素下沉排序
sink(a, 0, N);
}
}
/**
* 下沉排序(堆排序的原子操作)
*
* @param a
*            堆化数组
* @param k
*            下沉元素数组下标
* @param N
*            堆的有效节点数
*/
public static void sink(Comparable[] a, int k, int N) {
while ((2 * k + 1) <= N - 1) {//保证下沉元素的下沉操作合法(即下沉元素至少左孩子不能越界,否者不需要进行下沉操作)
int j = 2 * k + 1;//记录下沉元素下沉后的数组脚标
if (j < (N - 1) && SortUtils.less(a[j], a[j + 1])) {//j<(N-1)是保证右节点存在
j++;
}
if (SortUtils.less(a[j], a[k])) {
break;
}
SortUtils.exch(a, k, j);
k = j;
}
}


3 交换类排序

3.1 冒泡排序

public static void sort(Comparable[] a) {
int n = a.length;
for (int i = 0; i < n - 1; i++) {
int flag = 0;// 标志一趟冒泡是否发生了交换,提高效率
for (int j = 0; j < n - 1 - i; j++) {
if (SortUtils.less(a[j + 1], a[j])) {
SortUtils.exch(a, j + 1, j);
flag = 1;
}
}
if (flag == 0) {
break;
}
}
}


3.2 快速排序

/**
* 快速排序(从交替进行扫描和"交换"并确定枢轴元素的最终位置(切分原子操作),这里的"交换"是指赋值)
*
* @author William Python
*
*/
public class Quick {
public static void sort(Comparable[] a) {
sort(a, 0, a.length - 1);
}
/**
* 快速排序外部接口
*
* @param a
* @param lo
* @param hi
*/
public static void sort(Comparable[] a, int lo, int hi) {
if (lo >= hi)
return;
int j = partition(a, lo, hi);// 快排的原子操作
sort(a, lo, j - 1);
sort(a, j + 1, hi);
}
/**
* 切分函数:找到枢轴元素(即是满足左边都不大于它,右边都不小于它的元素)最终所在的位置,将数组切分成两部分
*
* @param a
* @param lo
* @param hi
* @return 枢轴元素最终所在的位置
*/
private static int partition(Comparable[] a, int lo, int hi) {
int i = lo, j = hi;
Comparable temp = a[lo];// 每次取第一个元素为枢轴元素
while (i < j) {//每次循环条件和内部操作必须判断i<j
while (i < j && SortUtils.less(temp, a[j])) {
j--;
}
if (i < j) {
a[i++] = a[j];
}
while (i < j && SortUtils.less(a[i], temp)) {
i++;
}
if (i < j) {
a[j--] = a[i];
}
}
a[j] = temp;
return j;
}

}


4 归并排序

/**
* 实现归并排序的细节(归并两个有序的子序列为一个大序列)的工具类
*/
public class MergeUtils {

public static void merge(Comparable[] a, Comparable[] aux, int lo, int mid,
int hi) {

int i = lo, j = mid + 1;

for (int t = lo; t <= hi; t++) {
aux[t] = a[t];
}

for (int k = lo; k <= hi; k++) {
if (i > mid) {
a[k] = aux[j++];
} else if (j > hi) {
a[k] = aux[i++];
} else if (SortUtils.less(a[i], a[j])) {
a[k] = aux[i++];
} else {
a[k] = aux[j++];
}
}
}
}


4.1 二路归并排序(自底向上的归并排序,迭代方式)

public static void sort(Comparable[] a) {
int n = a.length;
Comparable[] aux = new Comparable
;// 辅助数组,转存整个待排序序列
sort(a, aux, 0, n - 1);
}


/**
* 归并排序(自底向上)外部调用接口
*
* @param a
* @param aux
* @param lo
* @param hi
*/
public static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {

int N = a.length;

for (int size = 1; size < N; size *= 2) {
//将两个size大小的相邻子数组合并为有序的大数组,但是最后一次归并时的第二个子数组可能比第一个子数组短
for (int i = lo; i <= hi - size; i += 2 * size) {//i <= hi - size是为了是的第二个子数组存在,不存在不用这一次归并
MergeUtils.merge(a, aux, i, i + size - 1,
SortUtils.min(i + 2 * size - 1, N - 1));
}
}
}


4.2 自顶向下的归并排序(递归方式)

public static final int M = 5;// 进行插入排序的小数组的长度

public static void sort(Comparable[] a) { int n = a.length; Comparable[] aux = new Comparable ;// 辅助数组,转存整个待排序序列 sort(a, aux, 0, n - 1); }

/**
* 归并排序(自顶向下)外部调用接口
*
* @param a
* @param aux
* @param lo
* @param hi
*/
public static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
//方案一:
/*
* if (lo >= hi) { return; }
*/
//方案二:
if (lo + M >= hi) {
Insertion.sort(a, lo, hi);
return;
}
//无论何种方案,下面代码一致

int mid = (lo + hi) / 2;

sort(a, aux, lo, mid);
sort(a, aux, mid + 1, hi);

MergeUtils.merge(a, aux, lo, mid, hi);
}


5 基数排序(桶排序)

基于LSD方法的链式基数排序的基本思想:

“多关键字排序”的思想实现“单关键字排序”。对数字型或字符型的单关键字,可以看作由多个数位或多个字符构成的多关键字,此时可以采用“分配-收集”的方法进行排序,这一过程称作基数排序法,其中每个数字或字符可能的取值个数称为基数。

比如,扑克牌的花色基数为4,面值基数为13。在整理扑克牌时,既可以先按花色整理,也可以先按面值整理。按花色整理时,先按红、黑、方、花的顺序分成4摞(分配),再按此顺序再叠放在一起(收集),然后按面值的顺序分成13摞(分配),

再按此顺序叠放在一起(收集),如此进行二次分配和收集即可将扑克牌排列有序。

基数排序:是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

基数排序基于分别排序,分别收集,所以是稳定的。

算法思想:依次按个位、十位…来排序,每一个pos都有分配过程和收集过程,二维数组的每一行就是一个桶,其中array[i][0]记录第i行数据的个数。

/**
* 基数排序

* 平均O(d(n+r)),最好O(d(n+r)),最坏O(d(n+r));空间复杂度O(n+r);稳定;较复杂

* d为位数,r为分配后链表的个数

*

*/

public class RadixSort {
//pos=1表示个位,pos=2表示十位

public static int getNumInPos(int num, int pos) {
int tmp = 1;
for (int i = 0; i < pos - 1; i++) {
tmp *= 10;
}

return (num / tmp) % 10;
}

//求得最大位数d

public static int getMaxWeishu(int[] a) {

int max = a[0];

for (int i = 0; i < a.length; i++) {
if (a[i] > max)
max = a[i];
}

int tmp = 1, d = 1;

while (true) {

tmp *= 10;

if (max / tmp != 0) {
d++;
} else
break;
}
return d;
}

public static void radixSort(int[] a, int d) {
int[][] array = new int[10][a.length + 1];

for (int i = 0; i < 10; i++) {
array[i][0] = 0;// array[i][0]记录第i行数据的个数
}

for (int pos = 1; pos <= d; pos++) {
for (int i = 0; i < a.length; i++) {// 分配过程
int row = getNumInPos(a[i], pos);
int col = ++array[row][0];
array[row][col] = a[i];
}

for (int row = 0, i = 0; row < 10; row++) {// 收集过程

for (int col = 1; col <= array[row][0]; col++) {
a[i++] = array[row][col];

}
array[row][0] = 0;// 复位,下一个pos时还需使用

}

}

}

public static void main(String[] args) {
int[] a = { 49, 38, 65, 197, 76, 213, 27, 50 };
radixSort(a, getMaxWeishu(a));
for (int i : a)
System.out.print(i + " ");
}

public static void main(String[] args) {

int[] a = { 49, 38, 65, 197, 76, 213, 27, 50 };

radixSort(a, getMaxWeishu(a));

for (int i : a)
System.out.print(i + " ");
}

}


小结:

1,排序过程中的原子操作无非包括交换、赋值和移位操作;

2,排序一般都有基础操作,如快排的找到枢轴元素位置、确定该元素左右”有序”的切分函数Partition(…),

归并的将两个相邻子数组合并为有序的大数组Merge(…)还有堆排序的下沉排序sink(…)等。

3.时空复杂度和一些补充:http://blog.csdn.net/hguisu/article/details/7776068重点内容
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: