主要参考Java 八大排序算法
排序算法总结
排序分内部排序和外部排序。
- 外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。
- 内部排序(常见8种)
排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 | 复杂性 |
---|---|---|---|---|---|---|
直接插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 | 简单 |
希尔排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 不稳定 | 较复杂 |
直接选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 | 简单 |
堆排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( 1 ) O(1) O(1) | 不稳定 | 较复杂 |
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 | 简单 |
快速排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n 2 ) O(n^2) O(n2) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | 不稳定 | 较复杂 |
归并排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n ) O(n) O(n) | 稳定 | 较复杂 |
基数排序 | O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) | O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) | O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)) | O ( n + r ) O(n+r) O(n+r) | 稳定 | 较复杂 |
插入排序
直接插入排序
private static void insertSort(int[] a){
for (int i = 1; i < a.length; i++) {
// 待插入元素
int temp = a[i];
int j;
for (j = i - 1; j >= 0 && a[j] > temp; j--) {
// 将大于temp的往后移动一位
a[j + 1] = a[j];
}
a[j + 1] = temp;
}
}
二分插入排序
对直接插入排序的优化,寻找插入位置时,由于被插入部分是有序的,则可以二分查找。
private static void insertSortBin(int[] a){
for (int i = 1; i < a.length; i++) {
// 待插入元素
int temp = a[i];
int j;
// 二分查找temp元素应插入的位置
int start = 0;
int end = i-1;
int mid ;
while (start<=end){
mid = start+(end-start>>1);
if (temp<=a[mid]){
end = mid-1;
}else {
start = mid+1;
}
}
for (j = i - 1; j >= start; j--) {
// 将大于temp的往后移动一位
a[j + 1] = a[j];
}
a[j + 1] = temp;
}
}
希尔排序
对以增量dt为间隔的数组,进行插入排序。(分组插入)
调整间隔,直到为1。
private static void shellSort(int[] a){
int dk = a.length/2;
while (dk>=1){
shellInsertSortDk(a,dk);
dk /= 4;
}
}
private static void shellInsertSortDk(int[] a,int dk){
for (int i = dk; i < a.length; i++) {
// 待插入元素
int temp = a[i];
int j;
// 从 i-dk 开始,到0结束,即在0~i之间,以dk为间隔
for (j = i - dk; j >= 0 && a[j] > temp; j-=dk) {
// 将大于temp的往后移动一位
a[j + dk] = a[j];
}
a[j + dk] = temp;
}
}
选择排序
简单选择排序
/**
* 简单选择排序:
* 在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
* @param a
*/
public static void selectSort(int[] a){
for (int i = 0; i < a.length; i++) {
int min = Integer.MAX_VALUE;
int ind = -1;
for (int j = i; j < a.length; j++) {
if (a[j]<min){
ind = j;
min = a[j];
}
}
// 交换
a[ind] = a[i];
a[i] = min;
}
}
二元选择排序
/**
* 二元选择排序,同时选择最大和最小,分别放到两侧
* @param a
*/
public static void selectDoubleSort(int[] a){
int end = a.length-1;
for (int i = 0; i <= end; i++,end--) {
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
int indMin = -1;
int indMax = -1;
for (int j = i; j <= end; j++) {
if (a[j]<min){
indMin = j;
min = a[j];
}
if (a[j]>max){
indMax = j;
max = a[j];
}
}
// 交换
a[indMin] = a[i];
a[i] = min;
// indMIN和i交换了,如果再用indMax和i或交换的话,就会错误
if (indMax==i) indMax = indMin;
else if (indMax==indMin) indMax = i;
a[indMax] = a[end];
a[end] = max;
}
}
堆排序
注意堆排序的一种错误写法(这种堆排序运行太慢了,比简单和二元花的时间还多)
/**
* 堆排序
* @param a
*/
public static void selectHeapSort(int[] a){
// 把最大的放到末尾
for (int i = 0, end = a.length-1; i <= end ; end--) {
// 把最大的元素调到a[0]
for (int j = (int)(end-0.5)/2 ; j >= 0 ; j--) {
int temp = 2*j+1;
if (2*j+2<=end && a[temp]<a[2*j+2]){
temp = 2*j+2;
}
if (2*j+1<=end && a[temp]>a[j]){
swap(a,temp,j);
}
}
swap(a,end,0);
}
}
- 问题就在于,每次选取最大元素放到末尾之后,要从最后一个节点(叶子节点)的父节点开始搜索,一直到序号为0的节点,也就是这个过程相当于是 end/2*2(比对两次,两个子节点比较一次,父节点和较大的子节点比较一次。),而end是从末尾到0,整个过程也就是 O ( 1 2 n 2 ) O(\frac{1}{2}n^2) O(21n2)
- 真正的堆排序,先初始化堆,让整个树状数组满足堆的特性(任意父节点大于子节点,小顶堆相反)后,再把最大元素放到末尾,也即和末尾元素交换之后,只需从新的根节点出发,遇到父节点小于子节点的情况,交换二者,并继续比较交换之后的子节点作为父节点来说有没有出现上述情况。当没有上述情况时,停止。那这一过程,不会大于树高,也即是 O ( l o g 2 n ) O(log_2n) O(log2n)。
- 整体堆排序也就是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
public static void selectHeapSort2(int[] a){
createHeap(a,a.length-1);
for (int i = 0; i < a.length; i++) {
scanOnce(a,0,a.length-1-i);
swap(a,0,a.length-1-i);
}
}
/**
* 建堆
* 从倒数第二层元素开始遍历,凡是子节点大于父节点的,交换顺序
* @param a
* @param end
*/
private static void createHeap(int[] a,int end){
for (int j = (int)(end-1)/2 ; j >= 0 ; j--) {
int temp = 2*j+1;
if (2*j+2<=end && a[temp]<a[2*j+2]){
temp = 2*j+2;
}
if (2*j+1<=end && a[temp]>a[j]){
swap(a,temp,j);
scanOnce(a,temp,end);
}
}
}
/**
* 扫描,从beg开始,扫描它的孩子,如果大于两孩子,停止;如果小于,交换,继续扫描;直到结束。
* @param a
* @param beg
* @param end
*/
private static void scanOnce(int[] a,int beg,int end){
// a[0] >max(a[1],a[2]) return
// a[0]<max(a[1],a[2]), swap(a,0,maxInd)
// 继续比较a[maxInd],维护堆的特性
int j=beg;
while (2*j+1<=end){
int temp = 2*j+1;
if (2*j+2<=end && a[temp]<a[2*j+2]){
temp = 2*j+2;
}
if ( a[temp]>a[j]){
swap(a,temp,j);
j = temp;
}else {
return;
}
}
}
交换排序
冒泡排序(略)
快速排序
private static void quickSort(int[] arr) {
quickSortDiv(arr,0,arr.length-1);
}
private static void quickSortDiv(int[] a,int low ,int high){
if(low < high){ //如果不加这个判断递归会无法退出导致堆栈溢出异常
int middle = partition(a, low, high);
quickSortDiv(a, low, middle-1); //递归对低子表递归排序
quickSortDiv(a, middle + 1, high); //递归对高子表递归排序
}
}
private static int partition(int a[], int low, int high) {
int privotKey = a[low]; //基准元素
while(low < high){ //从表的两端交替地向中间扫描
while(low < high && a[high] >= privotKey) { //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端
high--;
}
swap(a, low, high);
while(low < high && a[low] <= privotKey ) {
low++;
}
swap(a, low, high);
}
return low;
}
快速排序+插入排序
在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。实践证明,改进后的算法时间复杂度有所降低,且当k取值为 8 左右时,改进算法的性能最佳。
/**
* 快速排序改进
* 快速排序+插入排序
* @param arr
*/
private static void quickSortAdv(int[] arr) {
quickSortDivK(arr,0,arr.length-1,8);
//再用插入排序对基本有序序列排序
for(int i = 1; i < arr.length; i++){
int temp = arr[i];
int j;
for (j = i - 1; j >= 0 && arr[j] > temp; j--) {
// 将大于temp的往后移动一位
arr[j + 1] = arr[j];
}
arr[j + 1] = temp;
}
}
private static void quickSortDivK(int[] a,int low ,int high,int k){
if(low+k < high){ //如果不加这个判断递归会无法退出导致堆栈溢出异常
int middle = partition(a, low, high);
quickSortDivK(a, low, middle-1,k); //递归对低子表递归排序
quickSortDivK(a, middle + 1, high,k); //递归对高子表递归排序
}
}
/**
* 工具方法
* @param arr
* @param i
* @param j
*/
private static void swap(int[] arr,int i,int j ){
if(i==j) return;
arr[i] ^= arr[j];
arr[j] ^= arr[i];
arr[i] ^= arr[j];
}
private static int partition(int a[], int low, int high) {
int privotKey = a[low]; //基准元素
while(low < high){ //从表的两端交替地向中间扫描
while(low < high && a[high] >= privotKey) { //从high 所指位置向前搜索,至多到low+1 位置。将比基准元素小的交换到低端
high--;
}
swap(a, low, high);
while(low < high && a[low] <= privotKey ) {
low++;
}
swap(a, low, high);
}
return low;
}
归并排序
/**
* 归并排序
*/
public static void mergeSort(int[] a){
int[] ano = new int[a.length];
mergeSort(a,0,a.length-1,ano);
}
private static void mergeSort(int[] a,int start,int end,int[] ano){
if (start>=end) return;
int mid = start+(end-start)/2;
int pl = start;
int pr = mid+1;
mergeSort(a,start,mid,ano);
mergeSort(a,mid+1,end,ano);
//方式一
// for (int i = 0; i < end - start+1; i++) {
// if (pl>mid || pr<=end && a[pl]>a[pr]){
// ano[i] = a[pr++];
// }else {
// ano[i] = a[pl++];
// }
// }
//方式二(copy)
int pn = 0;
// 把较小的数先移到新数组中
while (pl <= mid && pr <= end) {
if (a[pl] < a[pr]) {
ano[pn++] = a[pl++];
} else {
ano[pn++] = a[pr++];
}
}
// 把左边剩余的数移入数组
while (pl <= mid) {
ano[pn++] = a[pl++];
}
// 把右边边剩余的数移入数组
while (pr <= end) {
ano[pn++] = a[pr++];
}
//复制排好序的回去
pl = start;
for (int i = 0; i < end - start+1; i++) {
a[pl++] = ano[i];
}
}
基数(桶)排序
/**
* 基数排序/桶排序
*
* @param a
*/
public static void radixSort(int[] a){
// 找到最大数,确定要排序几趟
int max = 0;
for (int i = 0; i < a.length; i++) {
if (max < a[i]) {
max = a[i];
}
}
// 判断位数
int times = 0;
while (max > 0) {
max = max / 10;
times++;
}
// 建立十个队列
Queue<Integer>[] queues = new Queue[10];
for (int i = 0; i < 10; i++) {
queues[i] = new LinkedList<Integer>();
}
for (int i = 0; i < times; i++) {
// 分桶放入
for (int j = 0; j < a.length; j++) {
int ind = a[j]/(int)Math.pow(10,i)%10;
Queue queue = queues[ind];
queue.offer(a[j]);
}
int count = 0;
//依次拿出放入数组中,因为桶要再利用
for (int j = 0; j < 10; j++) {
while (queues[j].size()>0){
a[count++]=queues[j].poll();
}
}
}
}