Java知识梳理之九大排序(七)

92 阅读6分钟

        部分代码的Github地址为:github.com/hzka/JavaBo…

(一)基础知识
1.插入排序:
1.1算法思想:

当插入第i(i>=1)个元素时,前面的V[0],…,V[i-1]等i-1个 元素已经有序。这时,将第i个元素与前i-1个元素V[i-1],…,V[0]依次比较,找到插入位置即将V[i]插入,同时原来位置上的元素向后顺移。在这里,插入位置的查找是顺序查找。直接插入排序是一种稳定的排序算法。时间复杂度为O(n^2)。
1.2比较步骤:

****

1.3代码实现:

private static int[] InsetSort(int[] lists) {
        //1.开始遍历
        for (int i = 1; i < lists.length; i++) {
            //2.保存当前排序节点
            int currentElement = lists[i];
            int k;
            //3.从当前节点的上一节点开始检查,如果当前节点大于等于零,且上一节点的值大于当前节点的值,赋值。
            for(k=i-1;k>=0 && lists[k]>currentElement;k--){
                lists[k+1] = lists[k];
            }
            //4.否则,对于lists[k+1]进行赋值
            lists[k+1] = currentElement;
        }
        //5.返回数组。
        return lists;
}

2.冒泡排序:
2.1算法思想

(1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
(2)对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
(3)针对所有的元素重复以上的步骤,除了最后一个。
(4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
最佳情况O(n),最差情况O(n^2)。
2.2比较步骤

****

2.3代码示例

private static int[] BubbleSort(int[] lists) {
        //1.若一次循环没有发生交换,那么后面的也就不用做了。
        boolean nedNextPass = true;
        //2.双重循环遍历,进行交换。确定从小到大的顺序。
        for (int k = 0; k < lists.length && nedNextPass; k++) {
            nedNextPass =false;
            for (int i = k; i < lists.length - 1; i++) {
                int j = i + 1;
                if (lists[i] > lists[j]) {
                    int tmp = lists[j];
                    lists[j] = lists[i];
                    lists[i] = tmp;
                    nedNextPass =true;
                }
            }
        }
        return lists;
}

3.归并排序
3.1算法思想:

(1)申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
(2)设定两个指针,最初位置分别为两个已经排序序列的起始位置
(3)比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
(4)重复步骤3直到某一指针超出序列尾
(5)将另一序列剩下的所有元素直接复制到合并序列尾
归并排序是稳定的排序.归并时间复杂度为O(nlogn),由于插入、选择和冒泡。
3.2比较步骤

                                              

                                  

3.3代码示例

 private static void merageSort(int[] lists) {
        if(lists.length>1){
            //1.拆分前面一半
            int [] firstHalf = new int[lists.length/2];
            System.arraycopy(lists,0,firstHalf,0,lists.length/2);
            merageSort(firstHalf);
            //2.拆分前面一半
            int secondHalfLength = lists.length - lists.length/2;
            int [] secondHalf = new int[secondHalfLength];
            System.arraycopy(lists,lists.length/2,secondHalf,0,secondHalfLength);
            merageSort(secondHalf);
            //3.合并
            merage(firstHalf,secondHalf,lists);
        }
    }
    private static void merage(int[] list1, int[] list2, int[] temp) {
        int current1 = 0;//list1开始位置
        int current2 = 0;//list2开始位置
        int current3 = 0;//list3开始位置

        //比较累加填充,有意思。
        while (current1<list1.length && current2<list2.length){
            if(list1[current1]<list2[current2]){
                temp[current3++] = list1[current1++];
            }else{
                temp[current3++] = list2[current2++];
            }
        }

        while (current1<list1.length)
            temp[current3++] = list1[current1++];

        while (current2<list2.length)
            temp[current3++] = list2[current2++];
}

4.快速排序
4.1算法思想:

一趟快速排序:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]的值交换;
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]的值交换;
5)重复第3、4步,直到i=j;3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动。时间复杂度为:O(n) =O(nlogn)
链接:jingyan.baidu.com/article/d45…  www.cnblogs.com/surgewong/p…
4.2比较步骤: (好像比较方法有很多种,故依次排序后情况可能不一样。)

                          
4.3代码示例:

   private static void quick_sort(int s[], int l, int r) {
        if (l < r) {
            //Swap(s[l], s[(l + r) / 2]); //将中间的这个数和第一个数交换 参见注1
            int i = l, j = r, x = s[l];
            while (i < j) {
                while (i < j && s[j] >= x) // 从右向左找第一个小于x的数
                    j--;
                if (i < j)
                    s[i++] = s[j];
                while (i < j && s[i] < x) // 从左向右找第一个大于等于x的数
                    i++;
                if (i < j)
                    s[j--] = s[i];
            }
            s[i] = x;
            quick_sort(s, l, i - 1); // 递归调用
            quick_sort(s, i + 1, r);
        }
    }

5.堆排序
5.1算法思想:

二叉堆:(1)完全二叉树;(2)每个节点大于等于它的任意一个孩子。堆及堆的表示、添加新节点、删除新节点以及代码参考我的上一个帖子:blog.csdn.net/weixin_3824…
堆的时间复杂度:O(nlogn),相比于归并排序,堆排序不需要额外空间。
5.2代码示例:

public class Heap {
    private ArrayList list = new ArrayList();
    public Heap() {
    }
    public Heap(Object[] objects) {
        for (int i = 0; i < objects.length; i++) {
            add(objects[i]);
        }
    }
    public void add(Object newobject) {
        //1.先在队尾添加新结点。
        list.add(newobject);
        //2.确定当前的下标。更新结点的值。原理很简单,只是Arraylist的简单拓展。
        int currentIndex = list.size() - 1;
        while (currentIndex > 0) {
            int parentIndex = (currentIndex - 1) / 2;
            if (((Comparable) (list.get(currentIndex))).compareTo(list.get(parentIndex)) > 0) {
                Object temp = list.get(currentIndex);
                list.set(currentIndex, list.get(parentIndex));
                list.set(parentIndex, temp);
            } else break;
            currentIndex = parentIndex;
        }
    }
    public Object remove() {
        if (list.size() == 0) return null;
        //1.获取第零个元素,将最后一个元素放在第零个元素处。然后移除掉最后一个元素。
        Object removeObject = list.get(0);
        list.set(0, list.get(list.size() - 1));
        list.remove(list.size() - 1);
        int currentIndex = 0;
        while (currentIndex < list.size() - 1) {
            int leftchildIndex = currentIndex * 2 + 1;
            int rightchildIndex = currentIndex * 2 + 2;
            //2.判断准备替换哪一个结点,替换左孩子结点还是右孩子节点。
            if (leftchildIndex >= list.size()) break;
            int maxIndex = leftchildIndex;
            if (rightchildIndex < list.size()) {
                if (((Comparable) (list.get(maxIndex))).compareTo(list.get(rightchildIndex)) < 0) {
                    maxIndex = rightchildIndex;
                }
            }
            //3.和左右节点的最大值进行交换。
            if (((Comparable) (list.get(currentIndex))).compareTo(list.get(maxIndex)) < 0) {
                Object temp = list.get(maxIndex);
                list.set(maxIndex,list.get(currentIndex));
                list.set(currentIndex,temp);
                currentIndex = maxIndex;
            }else{break;}
        }
        return removeObject;
    }
    public int getSize(){
        return list.size();
    }
}

6.选择排序:
6.1算法思想:

       每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。 
6.2比较步骤:

****

6.3代码实现:

    public static int[] SimpleSelctSort(int []lists){
        for(int i= 0;i<lists.length-1;i++){
            int min = i;
            for(int j = i+1;j<lists.length;j++){
                if(lists[j] < lists[min]){
                    min = j;
                }
            }
            int tmp = lists[i];
            lists[i] = lists[min];
            lists[min] = tmp;
        }
        return lists;
    }

7.希尔排序:
7.1算法思想:

设待排序元素序列有n个元素,首先取一个整数increment(小于n)作为间隔将全部元素分为increment个子序列,所有距离为increment的元素放在同一个子序列中,在每一个子序列中分别实行直接插入排序。然后缩小间隔increment,重复上述子序列划分和排序工作。直到最后取increment=1,将所有元素放在同一个子序列中排序为止。 
那么增量该如何取?一般的初次取序列的一半为增量,以后每次减半,直到增量为1。
参考链接:blog.csdn.net/weixin_3781…
7.2比较步骤:

****

7.3代码实现:

public static void sort(int[] arrays) {
        if (arrays == null || arrays.length <= 1) {
            return;
        }
        //增量
        int incrementNum = arrays.length / 2;
        while (incrementNum >= 1) {
            for (int i = 0; i < arrays.length; i++) {
                //进行插入排序
                for (int j = i; j < arrays.length - incrementNum; j = j + incrementNum) {
                    if (arrays[j] > arrays[j + incrementNum]) {
                        int temple = arrays[j];
                        arrays[j] = arrays[j + incrementNum];
                        arrays[j + incrementNum] = temple;
                    }
                }
            }
            //设置新的增量
            incrementNum = incrementNum / 2;
        }
    }

8.桶排序与基数排序
对整数排序的高效算法。
8.1桶排序
(1)设置一个定量的数组当作空桶子。
(2)寻访序列,并且把项目一个一个放到对应的桶子去。
(3)对每个不是空的桶子进行排序。
(4)从不是空的桶子里把项目再放回原来的序列中。
参考链接:blog.csdn.net/developer10…
8.2基数排序
一共只有十个桶,反向放置遍历。

                                    

9.外部排序:
用途: 主要用作对大容量数据进行排序。
算法思想
外部排序指的是大文件的排序,即待排序的记录存储在外存储器上,待排序的文件无法一次装入内存,需要在内存和外部存储器之间进行多次数据交换,以达到排序整个文件的目的。外部排序最常用的算法是多路归并排序,即将原文件分解成多个能够一次性装入内存的部分,分别把每一部分调入内存完成排序。然后,对已经排序的子文件进行多路归并排序。先拆分和内存大小的文件,载入进行内部排序,将有序的子文件重新写入外存,对这些有序子文件进行逐趟归并,使其逐渐由小到大,直至得到整个有序文件为止。
链接:blog.csdn.net/jeason29/ar…
外部排序容易出现的问题及相应的解决方法: 1.缓存。加快数据读入到内存中。 2.流水线。将排序过程分为,读取数据→排序数据→写数据。3条线程并行处理。所以你在将数据分解到每一个文件中的时候,确保每个文件的大小最好是所给内存的1/3。 3.堆排序。根据堆排序的插入logn以及返回最小值o(1)。单个文件排序用快排,最后的外部排序用堆排序。
(二)总结:

****

补充:
1.所有排序算法的时间复杂度及稳定与否?

所谓排序算法稳定性,即假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
记住:希快选堆不稳定(希尔排序、快速排序、选择排序以及堆排序)

                               

      此图原图链接为:blog.csdn.net/weixin_3859….
2.什么时候该用什么样的排序算法?
1. 冒泡,插入,选择三种排序中,当数据量很大时,选择排序性能会更好; 
2. 堆排,希尔,归并,快排几种排序算法也表现不错,源于其时间复杂度达到了O(nlogn)O(nlogn); 
3. 随机快速排序性能确实表现十分亮眼,甚至有时比基数排序和桶排序还好。 

      4.插入排序适用于少量数据的排序,其时间复杂度为O(n^2)。
5.二分排序则是利用了数组能够快速定位的特点,时间复杂度也是O(n^2),适用于较大数据排序
6.希尔排序的实质是分组再进行直接插入,开始时增量较大时,插入排序的数量少,所以前期会很快。当增量变小时,数据也基本有序了。所以希尔排序的时间复杂度要比O(n^2)要好很多。
参考链接:www.cnblogs.com/luoahong/p/…