排序总结——Java语言描述
各种排序方法Java源代码链接:各种排序方法Java源代码链接
一 排序概述
1.1 排序的定义
排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。
1.2 排序的分类
排序分为内部排序
和外部排序
内部排序
:若整个排序过程不需要访问外存便能完成(如软盘、硬盘),则称此类排序问题为内部排序
;
外部排序
:若参加排序的记录数量很大,整个序列的排序过程**不可能在内存中
完成**,则称此类排序问题为外部排序
。
二 内部排序
2.1 内部排序概述
内部排序的过程是一个逐步扩大记录的有序序列长度的过程。基于不同的“扩大” 有序序列长度的方法,内部排序方法大致可分下列几种类型:
1**插入类**:将无序子序列中的一个或几个记录插入
到有序序列中,从而增加记录的有序子序列的长度。
1)直接插入排序(基于顺序查找)
2) 折半插入排序(基于折半查找)
3) 希尔插入排序(基于逐趟缩小增量)
2 交换类:通过“交换”无序序列中的记录从而得到其中关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度
1) 冒泡排序
2) 快速排序
3 选择类:从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
1) 简单选择排序
2) 树形选择排序
3) 堆排序
4**归并类**:通过“归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长度。
1) 归并排序
5 其他类
2.2 各种排序方法详解
插入类排序
2.2.1 直接插入排序法
a) 思想:利用 “顺序查找”实现“在R[1..i-1]中查找R[i]的插入位置”
b) Java语言描述的关键代码
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @Description 直接插入排序(基于顺序查找)
* @author zhiman in 2017年10月7日 下午5:26:45 mail-zhimanma@gmail.com
* @version V1.0
*
*/
public class SimpleInsertionSort {
public static void main(String[] args) {
ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("请输入待排数据:");
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arrs.add(temp);
}
scanner.close();
Integer[] arr = new Integer[num];
arrs.toArray(arr);
//泛型方法调用
System.out.println("排序完成后的数据:");
SimpleInsertionSort.<Integer>insertionSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 直接插入排序核心代码.
* @author zhiman in 2017年10月7日 下午5:28:56
*/
public static <AnyType extends Comparable<? super AnyType>>
void insertionSort(AnyType[] data) {
//暂存带排序的元素
int i;
for(int pointer = 1; pointer < data.length; pointer++) {
AnyType temp = data[pointer];
for (i = pointer; i > 0 && temp.compareTo(data[i - 1]) < 0; i--) {
//if (temp.compareTo(data[i-1]) < 0)
data[i] = data[i - 1];
}
data[i] = temp;
}
}
}
c) 算法分析
直接插入排序法 是稳定的排序排序算法
,时间复杂度为
O(n2)
2.2.2 折半插入排序法
a) 思想:利用 “折半查找”实现“在R[1..i-1]中查找R[i]的插入位置”
b) Java语言描述的关键代码
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @Description TODO 折半插入排序法
* @author zhiman in 2017年10月7日 下午9:49:48 mail:zhimanma@gmail.com
* @version V1.0
*
*/
public class BinaryInsertionSort {
public static void main(String[] args) {
ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("折半插入排序——请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("折半插入排序——请输入待排数据:");
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arrs.add(temp);
}
scanner.close();
Integer[] arr = new Integer[num];
arrs.toArray(arr);
//泛型方法调用
System.out.println("折半插入排序——排序完成后的数据:");
new BinaryInsertionSort().<Integer>binaryInsert(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
public <AnyType extends Comparable<? super AnyType>>
void binaryInsert(AnyType[] data) {
int low,high,mid;
for (int pointer = 1; pointer < data.length; pointer++) {
AnyType temp = data[pointer];
low = 0;
high = pointer - 1;
//查找元素插入位置
while (low <= high) {
mid = (low + high) / 2;
if (temp.compareTo(data[mid]) < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
//移动元素
for (int j = pointer; j > high + 1; j--) {
data[j] = data[j - 1];
}
//将待排元素放到合适位置,即high+1的位置
data[high + 1] = temp;
}
}
}
c) 算法分析
折半插入排序法 是稳定的排序排序算法
,时间复杂度为
O(n2)
2.2.3 希尔排序法
a) 算法思想:先将整个待排记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。
b)算法描述
1、选择一个步长序列 t1,t2,…,tk,其中ti>ti+1,tk=1 ;
2、 按步长序列个数k,对序列进行k趟排序;
3、每趟排序,根据对应的步长 ti ,将待排序列分割成若干长度为m的子序列,分别对各子表进行直接插入排序。仅步长因子为1时,整个序列作为一个表来处理,表长度即为整个序列的长度。
c)关键代码
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
public class ShellInsertionSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("希尔插入排序——请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("希尔插入排序——请输入待排数据:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法调用
System.out.println("希尔插入排序——排序完成后的数据:");
new ShellInsertionSort().<Integer>shellInsert(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 希尔排序关键代码
* @param data 待排数据
* @author zhiman in 2017年10月8日 上午11:15:54
*/
public <AnyType extends Comparable<? super AnyType>>
void shellInsert( AnyType[] data ) {
//初始增量选择data.length/2,后面增量依次选择上次增量的0.5倍
//这种增量叫做希尔增量
for ( int gap = data.length/2; gap > 0; gap /= 2 ) {
for ( int i = gap; i < data.length; i++ ) {
AnyType temp = data[i];
int j;
for ( j = i;j >= gap/*&& temp.compareTo( data[ j - gap ] ) < 0*/; j -= gap ) {
if ( temp.compareTo( data[ j - gap ] ) < 0 ) {
data[ j ] = data[ j - gap ];
} else
break;
}
data[ j ] = temp;
}
}
}
}
d) 算法分析
1.希尔排序方法是一个不稳定的排序方法
2.关键字的比较次数与记录移动次数依赖于步长因子序列的选取
3.使用希尔增量时,希尔排序最差时间复杂度为$O(n^2)$
交换类排序
2.2.4 冒泡排序法
a) 思想:它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
b) Java语言描述的关键代码
package exchangeCategorySort;
import java.util.Scanner;
public class BubbleSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("冒泡排序——请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("冒泡排序——请输入待排数据:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法调用
System.out.println("冒泡排序——排序完成后的数据:");
new BubbleSort().<Integer>bubbleSortMethod(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 冒泡排序核心代码
* @param data 待排数据
* @author zhiman in 2017年10月8日 下午10:14:11
*/
public <AnyType extends Comparable<? super AnyType>>
void bubbleSortMethod(AnyType[] data) {
//改进算法,设置标志位
boolean flag = true;
for (int i = 0; i < data.length - 1 && flag ; i++) {
flag = false;
for (int j = 0; j < data.length - i - 1; j++) {
if (data[j].compareTo(data[j+1]) > 0) {
AnyType temp = data[j+1];
data[j+1] = data[j];
data[j] = temp;
//如果某次循环,没有交换,则排序完成
flag = true;
}
}
}
}
}
b) 算法分析
1. 上述改进后的冒泡排序算法,当待排数据顺序有序时,有最好的时间复杂 O(n) ,当待排数据逆序有序时,有最差的时间复杂度 O(n2)
2. 冒泡排序是稳定的排序算法
2.2.4 快速排序法
a) 思想:快速排序(quick sort)是对起泡排序的一种改进。通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后分别对这两部分记录继续进行排序,直到整个序列有序。
b) Java语言描述的关键代码
javapackage exchangeCategorySort;
import java.util.*;
public class QuickSort {
public static void main(String[] args) {
List<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("快速排序——请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("快速排序——请输入待排数据:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
arrs.add(temp);
}
scanner.close();
//arrs.toArray(arr);
//泛型方法调用
System.out.println("快速排序——排序完成后的数据:");
QuickSort qs = new QuickSort();
//调用快排算法一
qs.<Integer>quickSortOne(arr,0,arr.length - 1);
for(Integer in:arr) {
System.out.println(in);
}
//调用快排算法二
qs.quickSortTWo(arrs);
for(Integer in:arrs) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 第一种快速排序方法
* @param data 待排数据
* @param low 待排数据低下标
* @param high 待排数据高下标
* @author zhiman in 2017年10月9日 下午4:17:23
*/
public <AnyType extends Comparable<? super AnyType>>
void quickSortOne(AnyType[] data , int low , int high) {
int keyElementPosition;
//此处必须是if判断,否则无限循环
if (low < high) {
keyElementPosition = doPartition(data,low,high);
//对一次划分的左边元素快速排序
quickSortOne(data,low,keyElementPosition - 1);
//对一次划分的右边元素快速排序
quickSortOne(data,keyElementPosition + 1,high);
}
}
/**
* TODO(方法功能描述) 一次划分
* @param data 待排数据
* @param low 待排数据低下标
* @param high 待排数据高下标
* @return 枢纽元素插入位置
* @author zhiman in 2017年10月9日 下午3:43:00
*/
private <AnyType extends Comparable<? super AnyType>>
int doPartition(AnyType[] data , int low , int high) {
//获取low high 和(low + high)/2中间元素,并将其置于low所指位置
getMidian(data,low,high);
AnyType key = data[low];//选取数组下标为low元素作为枢纽
//直到low = high 时循环结束
while (low < high) {
//循环直到找到一个比枢轴元素小的元素
while ( low < high && key.compareTo( data[high] ) < 0 ) {
high--;
}
data[low] = data[high];
//循环直到找到一个比枢轴元素大的元素
while ( low < high && key.compareTo( data[low] ) > 0 ) {
low++;
}
data[high] = data[low];
}
data[low] = key;
return low;
}
/**
* TODO(方法功能描述) 一种简单实现快排的方法
* @param items List集合
* @author zhiman in 2017年10月9日 下午8:19:43
*/
public void quickSortTWo(List<Integer> items) {
if (items.size() > 1) {
List<Integer> smaller = new ArrayList<Integer>();
List<Integer> same = new ArrayList<Integer>();
List<Integer> larger = new ArrayList<Integer>();
//得到枢轴元素
Integer keyItem = items.get(items.size() / 2);
for (Integer it:items) {
if (it < keyItem) {
smaller.add(it);
} else if (it > keyItem) {
larger.add(it);
} else {
same.add(it);
}
}
quickSortTWo(smaller);
quickSortTWo(larger);
items.clear();
items.addAll(smaller);
items.addAll(same);
items.addAll(larger);
}
}
/**
* TODO(方法功能描述) 获取中值,并将其放到left所指位置
* @param a 待排数据
* @param left 待排数据低索引
* @param right 待排数据高索引
* @return 枢轴元素
* @author zhiman in 2017年10月10日 下午3:40:51
*/
private <AnyType extends Comparable<? super AnyType>>
void getMidian(AnyType[] a, int left, int right){
int mid = (left + right) / 2;
if (a[mid].compareTo(a[left]) < 0)
swap(a,left,mid);
if (a[right].compareTo(a[left]) < 0)
swap(a,left,right);
if (a[right].compareTo(a[mid]) < 0)
swap(a,mid,right);
//让中间值位于下标为right - 1的位置
swap(a , mid, left);
}
private <AnyType extends Comparable<? super AnyType>>
void swap(AnyType[] a, int left, int right) {
AnyType temp = a[left];
a[left] = a[right];
a[right] = temp;
}
}
c) 算法分析
1. 快速排序是不稳定的排序方法
2. 快速排序平均时间复杂度为 O(nlogn) —牺牲稳定性换来效率提升
3. 当待排序列逆序有序时,快速排序退化为冒泡排序,有最差时间复杂度 O(n2)
选择类排序
2.2.5 简单选择排序法
a) 思想:
第 1 趟选择: 从 1—n 个记录中选择关键字最小的记录,并和第 1 个记录交换。
第 2 趟选择:从 2—n 个记录中选择关键字最小的记录,并和第 2 个记录交换。
*
*
*
第 n 趟选择:从 (n-1)—n 个记录中选择关键字最小的记录,并和第 n-1 个记录交换。
b) Java语言描述的关键代码
package insertCategorySort;
import java.util.ArrayList;
import java.util.Scanner;
/**
* @Description 直接插入排序(基于顺序查找)
* @author zhiman in 2017年10月7日 下午5:26:45 mail-zhimanma@gmail.com
* @version V1.0
*
*/
public class SimpleInsertionSort {
public static void main(String[] args) {
ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("请输入待排数据:");
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arrs.add(temp);
}
scanner.close();
Integer[] arr = new Integer[num];
arrs.toArray(arr);
//泛型方法调用
System.out.println("排序完成后的数据:");
new SimpleInsertionSort().<Integer>insertionSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 直接插入排序核心代码.
* @author zhiman in 2017年10月7日 下午5:28:56
*/
public <AnyType extends Comparable<? super AnyType>>
void insertionSort(AnyType[] data) {
//暂存带排序的元素
int i;
for(int pointer = 1; pointer < data.length; pointer++) {
AnyType temp = data[pointer];
for (i = pointer; i > 0 /*&& temp.compareTo(data[i - 1]) < 0*/; i--) {
if (temp.compareTo(data[i-1]) < 0)
data[i] = data[i - 1];
else
break;
}
data[i] = temp;
}
}
}
c) 算法分析: 时间复杂度: O(n2) ,且是不稳定的排序法。
2.2.6 堆排序
a) 思想:在大顶堆中,将堆顶和最后一个记录交换,即得第一趟的结果;再使剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次大值。如此反复执行,便能得到一个有序序列,这个过程称为堆排序。
b) Java语言描述的关键代码
package selectCategorySort;
import java.util.Scanner;
/**
* @Description 对用数组存储的堆进行排序
* @author zhiman in 2017年10月10日 下午9:42:08 mail-zhimanma@gmail.com
* @version V1.0
*
*/
public class HeapSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("堆排序——请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("堆排序——请输入待排数据:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法调用
System.out.println("堆排序——排序完成后的数据:");
new HeapSort().<Integer>heapSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 堆排序关键代码
* @param data 待排数据
* @author zhiman in 2017年10月10日 下午9:25:07
*/
public <AnyType extends Comparable<? super AnyType>>
void heapSort(AnyType[] data) {
//建堆,对于有n个元素的堆,从n/2处开始调整堆,数组从0下标开始,所以此处需再减一
for (int i = data.length / 2 - 1; i >= 0; i--) {
percDown(data,i,data.length);
}
//删除堆顶最大元素
for (int i = data.length - 1; i > 0; i--) {
//swap(data,0,i)中i代表元素下标
swap(data,0,i);
//percDown(data,0,i)中i代表堆中元素个数
percDown(data,0,i);
}
}
/**
* TODO(方法功能描述) 删除堆顶元素并重建堆
* @param a 待排数据
* @param i
* @param n 堆元素个数
* @author zhiman in 2017年10月10日 下午9:31:13
*/
public <AnyType extends Comparable<? super AnyType>>
void percDown(AnyType[] a,int i, int n) {
int child;
AnyType temp;
for (temp = a[i]; getLeftChild(i) < n; i = child) {
child = getLeftChild(i);
//n-1为数组表示的堆得最后一个元素的下标,让child指向左右孩子中较大的元素下标
if ( child != n-1 && a[child].compareTo( a[child + 1] ) < 0 ) {
child++;
}
//将结点的值与该节点左右孩子中较大的值比较,并调整堆
if( temp.compareTo( a[child] ) < 0) {
a[i] = a[child];
} else
break;
}
a[i] = temp;
}
/**
* TODO(方法功能描述) 得到元素i左孩子下标
* @param i
* @return 元素i左孩子下标
* @author zhiman in 2017年10月10日 下午9:27:24
*/
private int getLeftChild (int i) {
//用数组来存储堆,堆顶元素下标为0,所以左孩子下标为2*i+1;
return 2 * i + 1;
}
/**
* TODO(方法功能描述) 交换数组元素
* @param a
* @param left
* @param right
* @author zhiman in 2017年10月10日 下午9:48:59
*/
private <AnyType extends Comparable<? super AnyType>>
void swap(AnyType[] a, int left, int right) {
AnyType temp = a[left];
a[left] = a[right];
a[right] = temp;
}
}
c) 算法分析
1. 堆排序最坏情况下,时间复杂度也为 O(nlogn2) 。
2. 堆排序是不稳定的排序算法。
3. 堆排序相对简单选择排序而言牺牲了空间换取效率的提升。
归并类排序
2.2.7 归并排序法
a) 思想: 将两个或两个以上的有序子序列 “归并” 为一个有序序列。
b) Java语言描述的关键代码
package mergeCategorySort;
import java.util.Scanner;
public class MergeSort {
public static void main(String[] args) {
//ArrayList<Integer> arrs = new ArrayList<Integer>();
Scanner scanner = new Scanner(System.in);
System.out.println("归并排序——请输入待排序数的个数:");
int num = scanner.nextInt();
System.out.println("归并排序——请输入待排数据:");
Integer[] arr = new Integer[num];
for(int i = 0; i < num; i++){
int temp = scanner.nextInt();
arr[i] = temp;
}
scanner.close();
//arrs.toArray(arr);
//泛型方法调用
System.out.println("归并排序——排序完成后的数据:");
new MergeSort().<Integer>mergeSort(arr);
for(Integer in:arr) {
System.out.println(in);
}
}
/**
* TODO(方法功能描述) 重载方法
* @param a 待排数据
* @author zhiman in 2017年10月10日 下午11:18:18
*/
public <AnyType extends Comparable<? super AnyType>>
void mergeSort(AnyType[] a) {
@SuppressWarnings("unchecked")
AnyType[] tempArray = (AnyType[])new Comparable[a.length];
mergeSort(a, tempArray, 0, a.length - 1);
}
/**
* TODO(方法功能描述) 归并排序关键代码
* @param a 待排数据
* @param tempArray 暂存数组
* @param left 下标
* @param right 下标
* @author zhiman in 2017年10月10日 下午11:11:45
*/
public <AnyType extends Comparable<? super AnyType>>
void mergeSort(AnyType[] a, AnyType[] tempArray, int left, int right) {
if (left < right) {
int center = (left + right) / 2;
mergeSort(a, tempArray, left, center);
mergeSort(a, tempArray, center + 1, right);
merge(a, tempArray, left, center + 1, right);
}
}
/**
* TODO(方法功能描述) 归并元素
* @param a 待排数据
* @param tempArray 暂存数组
* @param leftPos 左表起始位置
* @param rightPos 右表起始位置
* @param rightEnd 右表终止位置
* @author zhiman in 2017年10月10日 下午11:23:01
*/
private <AnyType extends Comparable<? super AnyType>>
void merge(AnyType[] a, AnyType[] tempArray, int leftPos, int rightPos, int rightEnd) {
//左表终止位置
int leftEnd = rightPos - 1;
int tempPos = leftPos;
//元素总个数
int numElements = rightEnd - leftPos + 1;
//循环归并直到两个表中一个表元素全部归并到新表
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if ( a[leftPos].compareTo( a[rightPos] ) <= 0 ) {
tempArray[tempPos++] = a[leftPos++];
} else {
tempArray[tempPos++] = a[rightPos++];
}
}
//若右表归并完,则复制左表剩余元素到tempArray
while ( leftPos <= leftEnd ) {
tempArray[tempPos++] = a[leftPos++];
}
//若左表归并完,则复制右表剩余元素到tempArray
while ( rightPos <= rightEnd ) {
tempArray[tempPos++] = a[rightPos++];
}
//将tempArray复制到a
for (int i = 0; i < numElements; i++ , rightEnd--) {
//why
a[rightEnd] = tempArray[rightEnd];
}
}
}