java实现常用排序

分类:

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]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值