分类:
1)插入排序(直接插入排序、希尔排序)
2)选择排序(直接选择排序、堆排序)
3)交换排序(冒泡排序、快速排序)
4)归并排序
5)分配排序(桶排序、基数排序)
所需辅助空间最多:归并排序
所需辅助空间最少:堆排序
平均速度最快:快速排序
不稳定:快速排序,希尔排序,堆排序。
1、插入排序
1)直接插入排序
直接插入排序算法是一个对少量元素进行排序的有效算法。其工作原理与打牌时整理手中的牌的做法类似,开始摸牌时,我们的左手是空的,接着一次从桌上摸起一张牌,并将它插入到左手的正确位置。为了找到这张牌的正确位置,要将它与手中已有的牌从右到左进行比较,无论什么时候手中的牌都是排序好的。
package Sort;
/**
* 直接插入排序
* @author JOE
*
*/
public class InsertSort {
public static void insertSort(int[] data){
int temp,j;
for (int i = 1; i < data.length; i++) {
temp = data[i];
j = i - 1;
while(j >= 0 && data[j]>temp){
data[j + 1] = data[j];
j--;
}
data[j + 1] = temp;
}
}
public static void main(String[] args) {
int[] data = {12,21,32,1,4,2,5,3};
insertSort(data);
for (int j = 0; j < data.length; j++) {
System.out.println(data[j]);
}
}
}
2) 希尔排序(最小增量排序)
希尔排序( shell sort )是 D .L.希尔( D.L.Shell )提出的“缩小增量”的排序方法。它的作法不是每次一个元素挨一个元素的比较。而是初期选用大跨步(增量较大)间隔比较,使记录跳跃式接近它的排序位置;然后增量缩小;最后增量为 1 ,这样记录移动次数大大减少,提高了排序效率。希尔排序对增量序列的选择没有严格规定。
/**
* 希尔排序
* @author JOE
*
*/
public class ShellSort {
public static void shellSort(int[] data){
int gap = data.length / 2;//定义初始增量
for (;gap > 0; gap /= 2 ) {
for (int i = 0; i < gap; i++) {
for (int j = i + gap; j < data.length; j+=gap) {//每一组进行插入排序
if (data[j] < data[j - gap]) {
int temp = data[j];
int k = j - gap;
while(k >= 0 && data[k] > temp){
data[k + gap] = data[k];
k -= gap;
}
data[k + gap] = temp;
}
}
}
}
}
public static void main(String[] args) {
int[] data = {12,21,32,1,4,2,5,3};
shellSort(data);
for (int j = 0; j < data.length; j++) {
System.out.println(data[j]);
}
}
}
2、选择排序
1)直接选择排序
在要排序的一组数中,选出最小的一个数与第一个位置的数交换; 然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
/**
* 直接选择排序
* @author JOE
*
*/
public class SelectSort {
public static void selectSort(int[] data){
for (int i = 0; i < data.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < data.length; j++) {
if (data[minIndex] > data[j]) {
minIndex = j;
}
}
if (minIndex != i) {//如果最小的不是当前i,就交换
int temp = data[i];
data[i] = data[minIndex];
data[minIndex] = temp;
}
}
}
public static void main(String[] args) {
int[] data = {12,21,32,1,4,2,5,3};
selectSort(data);
for (int j = 0; j < data.length; j++) {
System.out.println(data[j]);
}
}
}
2)堆排序
堆排序是一种树形选择排序,是对直接选择排序的有效改进。n个关键字序列
K1,K2,…,Kn称为堆,当且仅当该序列满足(Ki<=K2i且Ki<=K2i+1)或(Ki>=K2i且Ki>=K2i+1),(1<=i<=n/2)。根结点(堆顶)的关键字是堆里所有结点关键字中最小者,称为小根堆;根结点的关键字是堆里所有结点关键字中最大者,称为大根堆。
若将此序列所存储的向量R[1..n]看作是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
堆排序的关键步骤有两个:一是如何建立初始堆;二是当堆的根结点与堆的最后一个结点交换后,如何对少了一个结点后的结点序列做调整,使之重新成为堆。堆排序的最坏时间复杂度为O(nlog2n),堆排序的平均性能较接近于最坏性能。由于建初始堆所需的比较 次数较多,所以堆排序不适宜于记录较少的文件。
/**
* 堆排序
*
* @author JOE
*
*/
public class HeapSort {
// 堆排序
public static void heapSort(int[] data) {
buildHeap(data);// 初始化最大堆
for (int i = data.length; i > 1; i--) {
// 交换堆顶和最后一个元素,即每次把剩余元素的最大值放到最后
int temp = data[0];
data[0] = data[i - 1];
data[i - 1] = temp;
// 堆的长度减少1,排除置换到最后位置的根节点
adjustHeap(data, 1, i - 1);
}
}
// 构建堆
public static void buildHeap(int[] data) {
// 非叶子节点最大序号为data.length/2
for (int i = data.length / 2; i > 0; i--) {
adjustHeap(data, i, data.length);
}
}
// 调整成最大堆,从index节点开始调整,size为节点总数 从0开始计算 index节点的子节点为 2*index+1, 2*index+2
public static void adjustHeap(int[] data, int parentNodeIndex, int size) {
// 左子节点索引
int leftChildNodeIndex = parentNodeIndex * 2 ;
// 右子节点索引
int rightChildNodeIndex = parentNodeIndex * 2 + 1;
// 最大节点索引
int largestNodeIndex = parentNodeIndex;
// 如果左子节点大于父节点,则将左子节点作为最大节点
if (leftChildNodeIndex <= size && data[leftChildNodeIndex - 1] > data[parentNodeIndex - 1]) {
largestNodeIndex = leftChildNodeIndex;
}
// 如果右子节点比最大节点还大,那么最大节点应该是右子节点
if (rightChildNodeIndex <= size && data[rightChildNodeIndex - 1] > data[largestNodeIndex - 1]) {
largestNodeIndex = rightChildNodeIndex;
}
// 最后,如果最大节点和父节点不一致,则交换他们的值
if (largestNodeIndex != parentNodeIndex) {
int tmp = data[parentNodeIndex - 1];
data[parentNodeIndex - 1] = data[largestNodeIndex - 1];
data[largestNodeIndex - 1] = tmp;
// 交换完父节点和子节点的值,对换了值的子节点检查是否符合最大堆的特性
adjustHeap(data, largestNodeIndex, size);
}
}
public static void main(String[] args) {
int[] data = { 6,5,3,1,8,7,2,4 };
heapSort(data);
for (int j = 0; j < data.length; j++) {
System.out.println(data[j]);
}
}
}
3、交换排序
1)冒泡排序
第一步,先比较k1和k2,若k1>k2,则交换k1和k2所在的记录,否则不交换。继续对k2和k3重复上述过程,直到处理完kn-1和kn。这时最大的排序码记录转到了最后位置,称第1次起泡,共执行n-1次比较。
与第一步类似,从k1和k2开始比较,到kn-2和kn-1为止,共执行n-2次比较。
依次类推,共做n-1次起泡,完成整个排序过程。
/**
* 冒泡排序
* @author JOE
*
*/
public class BubbleSort {
private static void bubbleSort(int[] data, int left, int right) {
for (int i = 1; i < data.length; i++) {
for(int j = 0; j < data.length - i; j++){
if (data[j + 1] < data[j]) {
int temp = data[j + 1];
data[j + 1] = data[j];
data[j] = temp;
}
}
}
}
public static void main(String[] args) {
int[] data = {12,21,32,1,4,2,5,3};
bubbleSort(data, 0, data.length - 1);
for (int j = 0; j < data.length; j++) {
System.out.println(data[j]);
}
}
}
2)快速排序
快速排序采用的思想是分治思想。
快速排序是找出一个元素(理论上可以随便找一个)作为基准(pivot),然后对数组进行分区操作,使基准左边元素的值都不大于基准值,基准右边的元素值 都不小于基准值,如此作为基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个元素都是在排序后的正 确位置,排序完成。所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。
/**
* 快速排序
* @author JOE
*
*/
public class QuickSort {
private static void quickSort(int[] data, int left, int right) {
if(left < right){
int key = data[left];
int low = left;
int high = right;
while(low < high){
while (low < high && data[high] > key) {
high--;
}
data[low] = data[high];
while(low < high && data[low] < key){
low++;
}
data[high] = data[low];
}
data[low] = key;
quickSort(data, left, low - 1);
quickSort(data, low + 1, right);
}
}
public static void main(String[] args) {
int[] data = {12,21,32,1,4,2,5,3};
quickSort(data, 0, data.length - 1);
for (int j = 0; j < data.length; j++) {
System.out.println(data[j]);
}
}
}
4、归并排序
归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取。
工作原理:
1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
2、设定两个指针,最初位置分别为两个已经排序序列的起始位置
3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
4、重复步骤3直到某一指针达到序列尾
5、将另一序列剩下的所有元素直接复制到合并序列尾
/**
* 归并排序
* @author JOE
*
*/
public class MergeSort {
public static void mergeSort(int[] data){
sort(data, 0, data.length - 1);
}
public static void merge(int[] data, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left;// 左指针
int j = mid + 1;// 右指针
int k = 0;
// 从两个数组中取出最小的放入临时数组
while(i <= mid && j <= right){
if (data[i] < data[j]) {
temp[k++] = data[i++];
} else {
temp[k++] = data[j++];
}
}
//上面while走完,表示已经有一边数全部放入临时数组,接下来把剩余一边的挨着存入临时数组
//把左边剩余的数移入数组
while(i <= mid){
temp[k++] = data[i++];
}
// 把右边边剩余的数移入数组
while(j <= right){
temp[k++] = data[j++];
}
// 把临时数组中的数替换掉data中的数
for (int t = 0; t < temp.length; t++) {
data[t + left] = temp[t];
}
}
public static void sort(int[] data, int left, int right) {
// 找出中间索引
int mid = (left + right) / 2;
if(left < right){
// 对左边数组进行递归
sort(data, left, mid);
// 对右边数组进行递归
sort(data, mid + 1, right);
// 合并
merge(data, left, mid, right);
// for (int j = 0; j < data.length; j++) {
// System.out.print(data[j] + " ");
// }
// System.out.println();
}
}
public static void main(String[] args) {
int[] data = {12,21,32,1,4,2,5,3 };
mergeSort(data);
for (int j = 0; j < data.length; j++) {
System.out.println(data[j]);
}
}
}
5、分配排序
桶排序和基数排序均属于分配排序。分配排序的基本思想:排序过程无须比较关键字,而是通过用额外的空间来”分配”和”收集”来实现排序,它们的时间复杂度可达到线性阶:O(n)。简言之就是:用空间换时间。
1)桶排序
简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。
例如要对大小为[1..1000]范围内的n个整数A[1..n]排序
首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B[i]存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。
然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。
最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。
假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果
对每个桶中的数字采用快速排序,那么整个算法的复杂度是
O(n + m * n/m*log(n/m)) = O(n + nlogn - nlogm)
从上式看出,当m接近n的时候,桶排序复杂度接近O(n)
当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的 ,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。
import java.util.ArrayList;
import java.util.Collections;
/**
* 桶排序
*
* @author JOE
*
*/
public class BucketSort {
public static final int BUCKET_SIZE = 10;
public static void bucketSort(int[] data) {
ArrayList<ArrayList<Integer>> bucket = new ArrayList<ArrayList<Integer>>();
for (int i = 0; i < BUCKET_SIZE; i++) {
bucket.add(new ArrayList<Integer>());
}
for (int i = 0; i < data.length; i++) {
int k = data[i] / 10;
bucket.get(k).add(data[i]);
}
for (ArrayList<Integer> list : bucket)
Collections.sort(list);
for (ArrayList<Integer> list : bucket)
System.out.print(list);//输出每个桶里的数
}
public static void main(String[] args) {
int[] data = { 92, 61, 32, 78, 4, 26, 52, 30 };
bucketSort(data);
}
}
2)基数排序
原理类似桶排序,这里总是需要10个桶,多次使用
首先以个位数的值进行装桶,即个位数为1则放入1号桶,为9则放入9号桶,暂时忽视十位数
例如
待排序数组[62,14,59,88,16]简单点五个数字
分配10个桶,桶编号为0-9,以个位数数字为桶编号依次入桶,变成下边这样
| 0 | 0 | 62 | 0 | 14 | 0 | 16 | 0 | 88 | 59 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶编号
将桶里的数字顺序取出来,
输出结果:[62,14,16,88,59]
再次入桶,不过这次以十位数的数字为准,进入相应的桶,变成下边这样:
由于前边做了个位数的排序,所以当十位数相等时,个位数字是由小到大的顺序入桶的,就是说,入完桶还是有序
| 0 | 14,16 | 0 | 0 | 0 | 59 | 62 | 0 | 88 | 0 |
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |桶编号
因为没有大过100的数字,没有百位数,所以到这排序完毕,顺序取出即可
最后输出结果:[14,16,59,62,88]