精华内容
下载资源
问答
  • Java排序算法

    2018-09-03 20:29:58
    尚硅谷Java排序算法PPT
  • java排序算法

    2018-09-10 19:42:38
    java实现的常用的几种基本排序算法,插入、交换、选择、归并
  • Java 排序算法

    2015-06-02 10:42:29
    Java 排序算法在讨论排序算法之前,先来看一个问题:从根目录查找某个文件,要用非递归的方式 (为神马不用递归方式?),下面给出程序截图: 图片来自博客:...

    Java 排序算法


    在讨论排序算法之前,先来看一个问题:从根目录查找某个文件,要用非递归的方式 (为神马不用递归方式?),下面给出程序截图:

    此处输入图片的描述
    图片来自博客:http://blog.csdn.net/wangchun8926/article/details/8680219

    接下来我们不打算详细的讨论典型的排序算法,比如快速排序、归并排序、堆排序、插入排序、冒泡排序等等,对于这些排序算法已经有很好的博客可以参考了:http://blog.csdn.net/bruce_6/article/details/38728493

    那该博文要讨论神马? 我们要讨论Java SDK封装好的排序算法,看看SDK是如何提高排序效率的。

    Java SDK 涉及排序的类主要有 Collections类、Array类、Comparator接口,而DualPivotQuicksort类从Java 1.7开始引入,这个多路快速排序效率自然要比快速排序要高,所以我们一般建议直接用SDK为我们封装好的算法,而不是自己去写算法,除非有特殊的需求。

    ###Collections

    Collections要严格区别于Collection,前者是排序工具类,而后者是Java容器的5大接口其中的一个接口,所以为了很好的区别他们,这里也顺带说一下和这个排序不太相关的容器接口

    ####Java容器接口
    以下是Java容器十分重要的5大接口,下面只是给出了最基本的描述
    #####Collection

    这是Collections库中所有对象的根类型。Collection表示一组对象,这些对象不一定是有序的,也不一定是可访问的,还可能包含重复对象。在Collection中,可以增加和删除对象,获取其大小并对它执行遍历(iterate)操作

    #####List

    List是一种有序的集合。List中的对象和整数从0到length-1一一映射。在List中,可能存在重复元素。List支持Collection的所有操作。此外,在List中,可以通过get方法获取索引对应的对象,反之,也可以通过indexOf方法获取某个对象的索引。还可以用add(index,e)方法改变某个特定索引所对应的元素。List的iterator(迭代器)按序依次返回各个元素。

    #####Map

    Map和List类似,其区别在于List把一组整数映射到一组对象中,而Map把一组key对象映射到一组value对象。与其他集合类一样,在Map中,可以增加和删除key-value对(键值对),获取其大小并对它执行遍历操作。Map的具体例子包括:把单词和单词定义的映射,日期和事件的映射,或URL和缓存内容的映射等。

    #####Set

    Set是一个无序集合,它不包含重复元素。Set也支持Collection的所有操作。但是,如果在Set中添加的是一个已经存在的元素,则Set的大小并不会改变。

    #####Iterator

    Iterator(迭代器)返回集合中的元素,其通过next方法,每次返回一个元素。Iterator是对集合中所有元素进行操作的一种较好的方式。一般不建议使用下面这种方式遍历:

    接下来讨论我们的主题,Collections 这里重点讨论这个类中的sort()方法,先看个Demo

    List<String> stringList = new ArrayList<String>();  
    stringList.add("nice");  
    stringList.add("delicious");  
    stringList.add("able");  
    stringList.add("moon");  
    stringList.add("try");  
    stringList.add("friend");  
      
    Collections.sort(stringList);  
      
    for (String str : stringList) {  
        System.out.println(str);  
    } 
    

    我们追踪Collections.sort(stringList)方法

    @SuppressWarnings("unchecked")  
    public static <T extends Comparable<? super T>> void sort(List<T> list) {  
        Object[] array = list.toArray();  
        Arrays.sort(array);  
        int i = 0;  
        ListIterator<T> it = list.listIterator();  
        while (it.hasNext()) {  
            it.next();  
            it.set((T) array[i++]);  
        }  
    }  
    

    然后进入主体Arrays.sort(array)

    ###Array

    public static void sort(Object[] array) {
        ComparableTimSort.sort(array);
    }
    

    继续追踪下去,进入ComparableTimSort

    static void sort(Object[] a, int lo, int hi) {
            Arrays.checkStartAndEnd(a.length, lo, hi);
        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted
    
        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi);
            binarySort(a, lo, hi, lo + initRunLen);
            return;
        }
    
        /**
         * March over the array once, left to right, finding natural runs,
         * extending short natural runs to minRun elements, and merging runs
         * to maintain stack invariant.
         */
        ComparableTimSort ts = new ComparableTimSort(a);
        int minRun = minRunLength(nRemaining);
        do {
            // Identify next run
            int runLen = countRunAndMakeAscending(a, lo, hi);
    
            // If run is short, extend to min(minRun, nRemaining)
            if (runLen < minRun) {
                int force = nRemaining <= minRun ? nRemaining : minRun;
                binarySort(a, lo, lo + force, lo + runLen);
                runLen = force;
            }
    
            // Push run onto pending-run stack, and maybe merge
            ts.pushRun(lo, runLen);
            ts.mergeCollapse();
    
            // Advance to find next run
            lo += runLen;
            nRemaining -= runLen;
        } while (nRemaining != 0);
    
        // Merge all remaining runs to complete sort
        if (DEBUG) assert lo == hi;
        ts.mergeForceCollapse();
        if (DEBUG) assert ts.stackSize == 1;
    }
    

    这个代码片段重点看一下:

    1. 传入的待排序数组若小于阈值MIN_MERGE(Java实现中为32,Python实现中为64),则调用 binarySort,这是一个不包含合并操作的 mini-TimSort。

    a) 从数组开始处找到一组连接升序或严格降序(找到后翻转)的数
    b) Binary Sort:使用二分查找的方法将后续的数插入之前的已排序数组,binarySort 对数组 a[lo:hi] 进行排序,并且a[lo:start] 是已经排好序的。算法的思路是对a[start:hi] 中的元素,每次使用binarySearch 为它在 a[lo:start] 中找到相应位置,并插入。

    1. 开始真正的TimSort过程:

    选取minRun大小,之后待排序数组将被分成以minRun大小为区块的一块块子数组
    a) 如果数组大小为2的N次幂,则返回16(MIN_MERGE / 2)
    b) 其他情况下,逐位向右位移(即除以2),直到找到介于16和32间的一个数

    接下来过程比较复杂,可以参考 DualPivotQuicksort 这个算法。

    ###Comparator
    这个接口上面的代码段也有提及,关于这个接口给个Demo直观的看一下:

    public class MainTest {  
      
        public static void main(String[] args) {  
            List<User> userList = new ArrayList<User>();  
            userList.add(new User("Lucy", 19));  
            userList.add(new User("Jack", 19));  
            userList.add(new User("Jim", 19));  
            userList.add(new User("James", 19));  
            userList.add(new User("Herry", 19));  
            userList.add(new User("Luccy", 19));  
            userList.add(new User("James", 18));  
            userList.add(new User("Herry", 20));  
      
            Collections.sort(userList);  
      
            for (User user : userList) {  
                System.out.println(user.getName() + "\t\t" + user.getAge());  
            }  
        }  
      
        private static class User implements Comparable<User> {  
      
            private String name;  
            private int    age;  
      
            public User(String name, int age){  
                this.name = name;  
                this.age = age;  
            }  
      
            @Override  
            public int compareTo(User another) {  
                int compareName = this.name.compareTo(another.getName());  
                if (compareName == 0) {  
                    return (this.age == another.getAge() ? 0 : (this.age > another.getAge() ? 1 : -1));  
                }  
                return compareName;  
            }  
      
            public String getName() {  
                return name;  
            }  
      
            public int getAge() {  
                return age;  
            }  
        }  
    } 
    

    上面Demo简单的演示了Comparable接口的用法,在使用Collections这个工具类之前,需要告诉这个工具你是如何看待自定义Object的比较的

    运行结果:

    Herry       19  
    Herry       20  
    Jack        19  
    James       18  
    James       19  
    Jim     19  
    Luccy       19  
    Lucy        19 
    

    当然上面这个Demo也可以这样:运行结果和上面是保持一致的

    public class MainTest {  
      
        public static void main(String[] args) {  
            List<User> userList = new ArrayList<User>();  
            userList.add(new User("Lucy", 19));  
            userList.add(new User("Jack", 19));  
            userList.add(new User("Jim", 19));  
            userList.add(new User("James", 19));  
            userList.add(new User("Herry", 19));  
            userList.add(new User("Luccy", 19));  
            userList.add(new User("James", 18));  
            userList.add(new User("Herry", 20));  
      
            Collections.sort(userList, new Comparator<User>() {  
      
                public int compare(User user1, User user2) {  
                    int compareName = user1.getName().compareTo(user2.getName());  
                    if (compareName == 0) {  
                        return (user1.getAge() == user2.getAge() ? 0 : (user1.getAge() > user2.getAge() ? 1 : -1));  
                    }  
                    return compareName;  
                }  
            });  
      
            for (User user : userList) {  
                System.out.println(user.getName() + "\t\t" + user.getAge());  
            }  
        }  
      
        private static class User {  
      
            private String name;  
            private int    age;  
      
            public User(String name, int age){  
                this.name = name;  
                this.age = age;  
            }  
      
            public String getName() {  
                return name;  
            }  
      
            public int getAge() {  
                return age;  
            }  
        }  
    }  
    
    展开全文
  • Java排序算法实现

    2018-03-06 19:52:59
    Java排序算法实现 Java排序算法实现 Java排序算法实现
  • 主要介绍了Java排序算法之归并排序简单实现,具有一定借鉴价值,需要的朋友可以参考下。
  • java排序算法总结

    千次阅读 多人点赞 2019-04-19 12:30:20
    java排序算法总结 排序,这是一个很古老但是又很经典的问题,世界上有很多中优秀排序算法的实现,在这里,我总结了其他比较常用的几种排序算法 1.java排序算法一览 冒泡排序和基数排序 桶排序和计数排序 归并排序和...

    java排序算法总结

    排序,这是一个很古老但是又很经典的问题,世界上有很多中优秀排序算法的实现,在这里,我总结了其他比较常用的几种排序算法

    1.java排序算法一览

    1. 冒泡排序和基数排序
    2. 桶排序和计数排序
    3. 归并排序和快速排序
    4. 堆排序
    5. 插入排序和希尔排序

    2.分类

    在这里插入图片描述

    3.比较

    1.时间复杂度比较

    算法 乱序时间复杂度 有序时间复杂度
    插入排序 O(N^2) O(N)
    希尔排序 O(N^2) O(N)
    冒泡排序 O(N^2) O(N)
    基数排序 O(N) O(N)
    桶排序 O(N)
    计数排序 O(N)
    归并排序 O(NlogN) O(NlogN)
    快速排序 O(NlogN) O(N^2)
    堆排序 O(NlogN)

    2.空间复杂度比较

    算法 空间复杂度
    插入排序 O(1)
    希尔排序 O(1)
    冒泡排序 O(1)
    基数排序 O(N)
    桶排序 O(N+M) M是额外辅助空间(桶数量)
    计数排序 O(N)
    归并排序 O(N)
    快速排序 O(1)
    堆排序 O(1)
    展开全文
  • java排序算法汇总

    千次阅读 2017-02-07 12:18:59
    java排序算法 直接插入排序、 希尔排序、 简单选择排序、 java实现堆排序 堆排序java实现 冒泡排序 快速排序 归并排序 技术排序

    排序算法分类

    这里写图片描述

    各种排序算法比较

    在这里插入图片描述
    在这里插入图片描述
    相关概念:

    1、时间复杂度

     时间复杂度可以认为是对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
     常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n), 线性对数阶O(nlog2n),平方阶O(n2)
     时间复杂度O(1):算法中语句执行次数为一个常数,则时间复杂度为O(1),
    

    2、空间复杂度

    空间复杂度是指算法在计算机内执行时所需存储空间的度量,它也是问题规模n的函数
    空间复杂度O(1):当一个算法的空间复杂度为一个常量,即不随被处理数据量n的大小而改变时,可表示为O(1)
    空间复杂度O(log2N):当一个算法的空间复杂度与以2为底的n的对数成正比时,可表示为O(log2n)
                                 ax=N,则x=logaN,
    空间复杂度O(n):当一个算法的空间复杂度与n成线性比例关系时,可表示为0(n).
    

    直接插入排序

    基本思想

    每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置(从后向前找到合适位置后),直到全部插入排序完为止。
    这里写图片描述

    代码实现

    public static void sort(int [] ages){
        for(int i = 1 ; i < ages.length ; i ++){
            for(int j = 0 ; j < i ; j ++){//与前面排好序的数字进行逐个比较,寻找合适的位置进行插入
                if(ages[j] > ages[i]){//全部移位
                    int temp = ages[i];
                    int tempIndex = i;
                    while(tempIndex>j){
                        ages[tempIndex] = ages[tempIndex - 1];
                        tempIndex --;
                    }
                    ages[j] = temp;
                }
            }
        }
    }
    

    另一种写法:

    public static void sort1(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            int moveindes = i;
            for (int j = i - 1; j >= 0; j--) {
                if (nums[moveindes] < nums[j]) {
                    int temp = nums[moveindes];
                    nums[moveindes] = nums[j];
                    nums[j] = temp;
                    moveindes--;
                } else {
                    break;
                }
            }
        }
    }
    

    分析
    直接插入排序是稳定的排序。
    文件初态不同时,直接插入排序所耗费的时间有很大差异。若文件初态为正序,则每个待插入的记录只需要比较一次就能够找到合适的位置插入,故算法的时间复杂度为O(n),这是最好的情况。若初态为反序,则第i个待插入记录需要比较i+1次才能找到合适位置插入,故时间复杂度为O(n2),这是最坏的情况。
    直接插入排序的平均时间复杂度为O(n2)。而且还要对数组中的数字进行逐个移位,所以性能比较差。

    二分插入排序

    基本思想
    二分法插入排序的思想和直接插入一样,只是找合适的插入位置的方式不同,这里是按二分法找到合适的位置,可以减少比较的次数。

    代码实现

    /**
     * 二分查找算法, 找到返回对应的index,没找到返回比target大的第一个数的index
     * @param arrays
     * @param target
     * @return
     */
    public static int query(int[] nums, int target, int begin, int end) {
        if (nums[end] < target) return -1;//全部比目标值小
        while (end >= begin) {
            int middle = (begin + end) / 2;
            if (nums[middle] == target) return middle;
            if (nums[middle] > target) {
                end = middle - 1;
            } else {
                begin = middle + 1;
            }
        }
        return begin;
    }
    
    /**
     * 直接插入排序
     * @param ages
     */
    public static void sort2(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            int index = query(nums, nums[i], 0, i - 1);
            if (index == -1) continue;
            int temp = nums[i];
            for (int j = i; j > index; j--) {
                nums[j] = nums[j - 1];
            }
            nums[index] = temp;
        }
    }
    

    分析
    这里只是对插入排序的一点点优化,平均时间复杂度还是O(n2)

    希尔排序

    基本思想
    先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。该方法实质上是一种分组插入方法。
    这里写图片描述
    代码实现

    public static void shellSort(int [] a){
        int i, j, gap,n = a.length;
    
        for (gap = n / 2; gap > 0; gap /= 2) //步长
            for (i = 0; i < gap; i++){        //直接插入排序
                for (j = i + gap; j < n; j += gap)
                    if (a[j] < a[j - gap]){
                        int temp = a[j];
                        int k = j - gap;
                        while (k >= 0 && a[k] > temp) {
                            a[k + gap] = a[k];
                            k -= gap;
                        }
                        a[k + gap] = temp;
                    }
            }
    }
    
    //第二种写法:
    public static void sort4(int[] a) {
        int i, j, step, temp;
        int x = 0;//统计交换次数
        for (step = a.length / 2; step >= 1; step /= 2) {
            for (i = step; i < a.length; i++) {
                temp = a[i];
                j = i - step;
                while (j >= 0 && temp < a[j]) {
                    a[j + step] = a[j];
                    j -= step;
                    x++;
                }
                a[j + step] = temp;
            }
        }
        System.out.println(x);
    }
    

    分析
    上面两种实现,交换次数相同。
    我们知道一次插入排序是稳定的,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以希尔排序是不稳定的。

    希尔排序的时间性能优于直接插入排序,原因如下:

    (1)当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
      (2)当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
      (3)在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
      因此,希尔排序在效率上较直接插人排序有较大的改进。
      希尔排序的平均时间复杂度为O(nlogn)。

    简单选择排序

    基本思想
    在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。
    这里写图片描述
    代码实现

    /**
         * 简单选择排序
         * @param ages
         */
        public static void sort(int [] ages){
            for(int i = 0 ; i < ages.length ; i ++){
                for(int j = i + 1 ; j < ages.length ; j ++){
                    int minIndex = i;
                    if(ages[j] < ages[minIndex]) minIndex = j;
    
                    if(minIndex != i){
                        int temp = ages[i];
                        ages[i] = ages[minIndex];
                        ages[minIndex] = temp;
                    }
                }
            }
        }
    

    分析
    简单选择排序是不稳定的排序。
    时间复杂度:T(n)=O(n2)。

    堆排序

    基本概念

    二叉树相关概念
    二叉树相关概念请参考博主另一篇文章 java遍历二叉树

    堆排序是一种树形选择排序,是对直接选择排序的有效改进。

    堆的定义下:具有n个元素的序列 (h1,h2,…,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,…,n/2)时称之为堆。在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。完全二 叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。

    堆排序的思想

    利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

    其基本思想为(大顶堆):

    1. 将初始待排序关键字序列(R1,R2…Rn)构建成大顶堆,此堆为初始的无序区;
    2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,…Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
    3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,…Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2…Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

    操作过程如下:

    1. 初始化堆:将R[1…n]构造为堆;

    2. 将当前无序区的堆顶元素R[1]同该区间的最后一个记录交换,然后将新的无序区调整为新的堆。

      因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。

    这里写图片描述

    动画演示:http://upload.wikimedia.org/wikipedia/commons/4/4d/Heapsort-example.gif
    这里写图片描述

    代码实现

    public static void heapSort(int[] array) {
        if (array == null || array.length == 0) return;
    
        buildMaxHeap(array);//先构造一次大顶堆
    
        //升序排序:逐个取最大值移动到末尾
        for (int i = array.length - 1; i > 0; i--) {
            switchNode(array, 0, i);
            maxHeap(array, 0, i);
        }
    }
    
    /**
     * 构建大顶堆
     * 
     * 注意:构建大顶堆时,这里是从数组的中间开始的,之后index--。原因是(arrayLength/2)位置后的节点都是叶子节点,没必要和子节点比较
     * 
     * @param array
     */
    private static void buildMaxHeap(int[] array) {
        int half = array.length / 2;
        for (int i = half; i >= 0; i--) {
            maxHeap(array, i, array.length);
        }
    }
    
    private static void maxHeap(int[] array, int parentIndex, int arrayLength) {
        int left = parentIndex * 2 + 1;
        int right = parentIndex * 2 + 2;
    
        if (left < arrayLength && array[left] > array[parentIndex]) {
            switchNode(array, left, parentIndex);
            maxHeap(array, left, arrayLength);
        }
        if (right < arrayLength && array[right] > array[parentIndex]) {
            switchNode(array, right, parentIndex);
            maxHeap(array, right, arrayLength);
        }
    }
    
    private static void switchNode(int[] array, int a, int b) {
        int temp = array[a];
        array[a] = array[b];
        array[b] = temp;
    }
    @Test
    public void test8() {
        int[] array = {4, 3, 5, 2, 1, 6, 0, 11, 66, 33, 22, 34, 66, 98, 12, 45, 23, 34, 32};
        heapSort(array);
        System.out.println(Arrays.toString(array));
    }
    

    输出结果:

    [0, 1, 2, 3, 4, 5, 6, 11, 12, 22, 23, 32, 33, 34, 34, 45, 66, 66, 98]
    

    分析

    • 时间复杂度

    堆排序的时间复杂度,主要在初始化堆过程和每次选取最大数后重新建堆的过程;
    初始化建堆过程时间:O(n)
    推算过程:
    首先要理解怎么计算这个堆化过程所消耗的时间,可以直接画图去理解;
    假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;
    那么总的时间计算为:s = 2^( i - 1 ) * ( k - i );其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要比较的次数,如果在最差的条件下,就是比较次数后还要交换;因为这个是常数,所以提出来后可以忽略;
    S = 2^(k-2) * 1 + 2^(k-3)*2.....+2*(k-2)+2^(0)*(k-1) ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;
    这个等式求解,我想高中已经会了:等式左右乘上2,然后和原来的等式相减,就变成了:
    S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)
    除最后一项外,就是一个等比数列了,直接用求和公式:S = { a1[ 1- (q^n) ] } / (1-q)
    S = 2^k -k -1;又因为k为完全二叉树的深度,所以(2^k) <= n < (2^k -1 ),总之可以认为:k = logn (实际计算得到应该是 log(n+1) < k <= logn );
    综上所述得到:S = n - longn -1,所以时间复杂度为:O(n)

    更改堆元素后重建堆时间:O(nlogn)
    推算过程:
    循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:logn(n-1) = nlogn - logn ;

    综上所述:堆排序的时间复杂度为:O(nlogn)

    冒泡排序

    冒泡排序是比较简单容易理解的,基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
    这里写图片描述

    public static void bubbleSort(int [] ages){
        for(int i = 0 ; i < ages.length ; i ++){
            for (int j = 0 ; j < ages.length - i - 1; j ++){
                if(ages[j] > ages[j + 1]){
                    int temp = ages[j];
                    ages[j] = ages[j + 1];
                    ages[j + 1] = temp;
                }
            }
        }
    }
    

    分析
    冒泡排序是稳定的排序。时间复杂度 n + (n-1) + (n-2) + … = (1+n)*n/2, 也就是 O(n2)。

    快速排序

    基本思想
    先找一个基准值,一般使用第一个数据即可。第一次排序完成后,基准值左侧的都比基准值小,右侧的都比基准值大(升序情况下)。然后递归对两个分组进行排序即可。
    基本步骤:先从右侧开始,逐个查找直至找到一个比基准值小的元素,交换。然后从左侧查询,直至找到一个比基准值大的元素,交换。循环该步骤即可。
    代码实现

    public static void sort7(int[] nums, int begin, int end) {
        if (begin > end) return;
        int base = nums[begin];
        int beginTemp = begin, endTemp = end;
        while (begin < end) {
            while (nums[end] > base && begin < end) {
                end--;
            }
            nums[begin] = nums[end];
            while (nums[begin] < base && begin < end) {
                begin++;
            }
            nums[end] = nums[begin];
        }
        nums[begin] = base;
    
        sort7(nums, begin + 1, endTemp);
        sort7(nums, beginTemp, end - 1);
    }
    

    分析
    时间复杂度:O (nlogn)

    归并排序

    基本思想

    在这里插入图片描述
    在这里插入图片描述

    代码实现
    递归:

    /**
         * 归并排序
         *
         * @param a
         * @param left
         * @param right
         */
        private static void guibingSort(int[] a, int left, int right) {
            if(left>=right)
                return;
    
            int mid = (left + right) / 2;
            //二路归并排序里面有两个Sort,多路归并排序里面写多个Sort就可以了
            guibingSort(a, left, mid);
            guibingSort(a, mid + 1, right);
            merge(a, left, mid, right);
        }
    
        private static void merge(int[] a, int left, int mid, int right) {
    
            int[] tmp = new int[a.length];
            int r1 = mid + 1;
            int tIndex = left;
            int cIndex=left;
            // 逐个归并
            while(left <=mid && r1 <= right) {
                if (a[left] <= a[r1])
                    tmp[tIndex++] = a[left++];
                else
                    tmp[tIndex++] = a[r1++];
            }
            // 将左边剩余的归并
            while (left <=mid) {
                tmp[tIndex++] = a[left++];
            }
            // 将右边剩余的归并
            while ( r1 <= right ) {
                tmp[tIndex++] = a[r1++];
            }
    
    
    
    
            System.out.println("第"+(++number)+"趟排序:\t");
            // TODO Auto-generated method stub
            //从临时数组拷贝到原数组
            while(cIndex<=right){
                a[cIndex]=tmp[cIndex];
                //输出中间归并排序结果
                System.out.print(a[cIndex]+"\t");
                cIndex++;
            }
            System.out.println();
        }
        static int number=0;
        @Test
        public void test9() {
            int[] nums = {14,12,15,13,11,16,4};
            guibingSort(nums, 0, nums.length-1);
            System.out.println(Arrays.toString(nums));
        }
    

    分析

    基数排序

    基本思想

    代码实现

    分析

    稳定排序和不稳定排序

    概念描述

    稳定排序就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj,Ai原来在位置前,排序后Ai还是要在Aj位置前。

    说一下稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,对基于比较的排序算法而言,元素交换的次数可能会少一些(个人感觉,没有证实)。

    各个排序算法的稳定性分析

    (1)冒泡排序

    冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

    (2)选择排序

    选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9,我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

    (3)插入排序
    插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

    (4)快速排序
    快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j,交换a[i]和a[j],重复上面的过程,直到i > j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为5 3 3 4 3 8 9 10 11,现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻。

    (5)归并排序
    归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。

    (6)基数排序
    基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。

    (7)希尔排序(shell)
    希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比O(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

    (8)堆排序
    我们知道堆的结构是节点i的孩子为2 * i和2 * i + 1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n / 2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n / 2 - 1, n / 2 - 2, … 1这些个父节点选择元素时,就会破坏稳定性。有可能第n / 2个父节点交换把后面一个元素交换过去了,而第n / 2 - 1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。

    综上,得出结论: 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法

    展开全文
  • java排序算法

    千次阅读 2008-04-07 06:57:00
    1.定义通过比较来确定输入序列1,a2,..,an>的元素间相对次序的排序算法称为比较排序算法。2.算法解释 (1) 选择排序: 选择排序的基本思想是对待排序的记录序列进行n-1遍的处理,第i遍处理是将L[i..n]中最小者与L[i]...
    1.定义
    通过比较来确定输入序列<a1,a2,..,an>的元素间相对次序的排序算法称为比较排序算法。


    2.算法解释
      (1)  选择排序:
         选择排序的基本思想是对待排序的记录序列进行n-1遍的处理,第i遍处理是将L[i..n]中最小者与L[i]交换位置。这样,经过i遍处理之后,前i个记录的位置已经是正确的了。
      (2): 冒泡排序
      最简单的排序方法是冒泡排序方法。这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。在冒泡排序算法中我 们要对这个“气泡”序列处理若干遍。所谓一遍处理,就是自底向上检查一遍这个序列,并时刻注意两个相邻的元素的顺序是否正确。如果发现两个相邻元素的顺序 不对,即“轻”的元素在下面,就交换它们的位置。显然,处理一遍之后,“最轻”的元素就浮到了最高位置;处理二遍之后,“次轻”的元素就浮到了次高位置。 在作第二遍处理时,由于最高位置上的元素已是“最轻”元素,所以不必检查。一般地,第i遍处理时,不必检查第i高位置以上的元素,因为经过前面i-1遍的 处理,它们已正确地排好序
     (3)  插入排序
    插入排序的基本思想是,经过i-1遍处理后,L[1..i-1]己排好序。第i遍处理仅将L[i]插入L[1..i-1]的适当位置,使得L[1..i]又是排好序的序列。要达到这个目的,我们可以用顺序比较的方法。首先比较L[i]和L[i-1],如果L[i-1]≤ L[i],则L[1..i]已排好序,第i遍处理就结束了;否则交换L[i]与L[i-1]的位置,继续比较L[i-1]和L[i-2],直到找到某一个位置j(1≤j≤i-1),使得L[j] ≤L[j+1]时为止
    (4)  快速排序

            快速排序的基本思想是基于分治策略的。对于输入的子序列L[p..r],如果规模足够小则直接进行排序,否则分三步处理:

    • 分解(Divide):将输入的序列L[p..r]划分成两个非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。
    • 递归求解(Conquer):通过递归调用快速排序算法分别对L[p..q]和L[q+1..r]进行排序。
    • 合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在L[p..q]和L[q+1..r]都排好序后不需要执行任何计算L[p..r]就已排好序。
    public class Sort {

        
    /*
         * 交换算法
         
    */

        
    public static void swap(int a[], int i, int j) {
            
    int tmp =
     a[i];
            a[i] 
    =
     a[j];
            a[j] 
    =
     tmp;
        }


        
    // 选择排序法
        public void sortSelection(int[] vec) {
            
    long begin =
     System.currentTimeMillis();
            
    // k次循环,增加运算时间.

            for (int k = 0; k < 1000000; k++{
                
    for (int i = 0; i < vec.length; i++
    {
                    
    for (int j = i; j < vec.length; j++
    {
                        
    if (vec[j] < vec[i]) 
    {
                            swap(vec, i, j);
                        }

                    }

                }

            }

            
    long end = System.currentTimeMillis();
            System.out.println(
    "选择法用时为:" + (end -
     begin));
            
    for (int i = 0; i < vec.length; i++
    {
                System.out.println(vec[i]);
            }

        }


        
    // 冒泡排序法
        public void sortBubble(int[] vec) {
            
    long begin =
     System.currentTimeMillis();
            
    for (int k = 0; k < 1000000; k++
    {
                
    for (int i = 0; i < vec.length; i++
    {
                    
    for (int j = i; j < vec.length - 1; j++
    {
                        
    if (vec[j + 1< vec[j]) 
    {
                            swap(vec, j 
    + 1
    , j);
                        }

                    }

                }

            }

            
    long end = System.currentTimeMillis();
            System.out.println(
    "冒泡法用时为:" + (end -
     begin));
            
    for (int i = 0; i < vec.length; i++
    {
                System.out.println(vec[i]);
            }

        }


        
        
    // 插入排序法
        public void sortInsertion(int[] vec) {
            
    long begin =
     System.currentTimeMillis();
            
    for (int k = 0; k < 1000000; k++
    {
                
    for (int i = 1; i < vec.length; i++
    {
                    
    int j =
     i;
                    
    while (vec[j - 1> vec[j]) 
    {
                        vec[j] 
    = vec[j - 1
    ];
                        j
    --
    ;
                        
    if (j <= 0
    {
                            
    break
    ;
                        }

                    }

                    vec[j] 
    = vec[i];
                }

            }

            
    long end = System.currentTimeMillis();
            System.out.println(
    "插入法用时为:" + (end -
     begin));
            
    for (int i = 0; i < vec.length; i++
    {
                System.out.println(vec[i]);
            }

        }

        
        
    public int partition(int a[],int low,int high){
            
    int
     pivot,p_pos,i;
            p_pos
    =
    low;
            pivot
    =
    a[p_pos];
            
    for(i=low+1;i<=high;i++)
    {
                
    if(a[i]<pivot)
    {
                    p_pos
    ++
    ;
                    swap(a,p_pos,i);
                }

            }

            swap(a,low,p_pos);
            
    return p_pos;
        }


        
    public void quicksort(int a[],int low,int high){
            
    int
     pivot;
            
    if(low<high)
    {
                pivot
    =
    partition(a,low,high);
                quicksort(a,low,pivot
    -1
    );
                quicksort(a,pivot
    +1
    ,high);
            }

        }

        
    //快速排序法
        public void sortQuick(int[] vec){
            
    long begin=
    System.currentTimeMillis();
            
    for(int k=0;k<1000000;k++)
    {
                quicksort(vec,
    0,5
    );
            }

            
    long end=System.currentTimeMillis();
            System.out.println(
    "快速法用时为:" + (end -
     begin));
            
    for (int i = 0; i < vec.length; i++
    {
                System.out.println(vec[i]);
            }

        }

        
    /**
         * 
    @param args
         
    */

        
    public static void main(String[] args) {
            
    // TODO Auto-generated method stub

            int[] vec = new int[] 374723-51956 };
            Sort sort 
    = new
     Sort();
            sort.sortSelection(vec);
            sort.sortBubble(vec);
            sort.sortInsertion(vec);
            sort.sortQuick(vec);

        }


    }
     
    展开全文
  • java 排序算法可视化 演示程序。 用java做的一个小的排序算法演示程序,用线程控制访问,共7个算法,包括冒泡,选择,希尔,插入,归并,堆,快排。。 算法排序
  • 常见几种java排序算法

    万次阅读 多人点赞 2019-02-18 18:08:16
    1.插入排序 public class InsertSort { public static void sort(int[] arr) { if (arr.length &amp;amp;amp;gt;= 2) { for (int i = 1; i &amp;amp;amp;lt; arr.length; i++) { //挖出一个要用来插入...
  • Java 排序算法-冒泡排序及其优化

    千次阅读 2020-03-15 01:11:06
    Java 排序算法-冒泡排序及其优化 Java 排序算法-冒泡排序及其优化什么是冒泡排序基本写法优化后写法终极版本源码及测试 什么是冒泡排序 这里引用一下百度百科上的定义: 冒泡排序(Bubble Sort),是一种计算机科学...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 27,811
精华内容 11,124
关键字:

java排序算法

java 订阅