算法——排序之简单排序
2017-05-14 20:23
204 查看
排序是将一组对象按照某个逻辑顺序重新排列的过程。现在计算机的广泛使用使得数据无处不在,整理数据就变得非常重要了。而整理数据的第一步往往都是进行排序。
简单排序
冒泡排序
原理:
将所有临近的两个的对象按照某个逻辑一一进行比较,通常是由大到小或小到大,比较之后交换两个对象的位置。
反复操作,最终即可排序成功。
如上图所示,我们发现,进行一次循环,冒泡排序只能确定一个对象的位置。第一次循环只能够确定一个对象的位置。所以我们需要多少次循环呢?我们就可以的得出,我们总共需要n-1次循环。因为n-1次循环能确定n-1个对象的位置,这时候自然最后一个对象的位置也确定了。
代码如下:
冒泡排序的优点就是简单,空间复杂度低。缺点就是慢!无用功多。
时间复杂度O(n^2)
选择排序
原理:
首先找到当前数组中最小的元素,将这个元素和第一个元素进行交换位置。其次,在剩下的数组中找到最小的元素,将它和数组中第二个元素进行位置的交换。依次类推,就可以达到排序的效果。简单来说就是每次都选最小的那个,放在前面。
代码:
选择排序将数组分成两个部分,已经排序好的和未排序两部分。每次都从未排序部分中找到最小的元素,增加到排好序的末尾。
同样的,外层循环控制次数,内层循环找到最小的元素。外层循环,因为只要找到n-1个元素的位置,第n个元素的位置就直接确定了。所以外层循环需要n-1次。
我个人认为选择排序是最简单理解的排序之一。
时间复杂度O(n^2)
插入排序
插入排序就和平常打扑克牌一样。整理扑克牌的一般使用的就是插入排序。整理方法是一张一张来,将每一张牌插入其他已经有序的牌中的适当位置。当然,在数组操作中,如果需要给某个元素插入,腾出空间,则需要将其他元素向后移动一位。
代码“:
改变:
和选择排序不同,插入排序所需的时间和取决于输入元素的初始顺序。对一个已经接近有序的数组进行排序,会比对顺序随机的数组进行排序快得多。
平均时间复杂度:O(n^2)。最好情况时间复杂度O(n)。
插入排序对于部分有序的数组来说非常高效,这是它最大的优点。
希尔排序
希尔排序是对插入排序的一个优化。对于大规模的乱序数组,插入排序会比较缓慢,因为他需要慢慢的向前找到位置,所以元素只能一点一点的从数组中移动到另一端。
希尔排序本质就是分组的插入排序,但是它并不需要一个一个比较,而是能将元素一下子移到一个比较远的地方。
原理:
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
上图中,方框指向的是同一组,也就是将同一组数据排序。当分组越来越少,一组中元素越来越多的时候,排序就基本完成了。而最终,当所有元素都在同一组中的时候,就是简单插入排序。因为这个时候数组已经接近有序了,所以排序起来非常快。
代码:
比较:
代码:
当数据量不大的时候,插入排序比交换排序快。而数据量大起来,交换排序就比插入排序更加快了。
当然,如果数组是接近有序的,插入排序的性能会非常强大。
冒泡排序比较缓慢。
而希尔排序的性能非常不错。即使数据量非常大,希尔排序的性能也是不错的。希尔排序的平均时间复杂度是O(n^1.3)左右。相对简单插入排序来说确实是极大的提升。并且希尔排序的代码量并不大。
简单排序
冒泡排序
原理:
将所有临近的两个的对象按照某个逻辑一一进行比较,通常是由大到小或小到大,比较之后交换两个对象的位置。
反复操作,最终即可排序成功。
如上图所示,我们发现,进行一次循环,冒泡排序只能确定一个对象的位置。第一次循环只能够确定一个对象的位置。所以我们需要多少次循环呢?我们就可以的得出,我们总共需要n-1次循环。因为n-1次循环能确定n-1个对象的位置,这时候自然最后一个对象的位置也确定了。
代码如下:
public static void sort(Comparable[] a) { for (int i = 0; i < a.length - 1; i++) { for (int j = 0; j < a.length - 1; j++) { if (less(a[j + 1], a[j])) { swap(a, j, j + 1); } } } }实际也就是依靠两层循环,外层控制循环次数,内层逐个进行比较。
冒泡排序的优点就是简单,空间复杂度低。缺点就是慢!无用功多。
时间复杂度O(n^2)
选择排序
原理:
首先找到当前数组中最小的元素,将这个元素和第一个元素进行交换位置。其次,在剩下的数组中找到最小的元素,将它和数组中第二个元素进行位置的交换。依次类推,就可以达到排序的效果。简单来说就是每次都选最小的那个,放在前面。
代码:
public static void sort(Comparable[] a) { for (int i = 0; i < a.length - 1; i++) { int minIndex = i; for (int j = i + 1; j < a.length; j++) { if (less(a[j], a[minIndex])) { minIndex = j; } } swap(a, minIndex, i); } }
选择排序将数组分成两个部分,已经排序好的和未排序两部分。每次都从未排序部分中找到最小的元素,增加到排好序的末尾。
同样的,外层循环控制次数,内层循环找到最小的元素。外层循环,因为只要找到n-1个元素的位置,第n个元素的位置就直接确定了。所以外层循环需要n-1次。
我个人认为选择排序是最简单理解的排序之一。
时间复杂度O(n^2)
插入排序
插入排序就和平常打扑克牌一样。整理扑克牌的一般使用的就是插入排序。整理方法是一张一张来,将每一张牌插入其他已经有序的牌中的适当位置。当然,在数组操作中,如果需要给某个元素插入,腾出空间,则需要将其他元素向后移动一位。
代码“:
public static void sort(Comparable[] a) { for (int i = 1; i < a.length; i++) { for (int j = i - 1; j >= 0 && less(a[j+1], a[j]); j--) { swap(a, j, j + 1); } } }这个代码简洁,但是却不容易让人看懂。不个人不推荐这么做。而是应该将思路表达的清晰。
改变:
public static void sort(Comparable[] a) { for (int i = 1; i < a.length; i++) { int position = findPosition(a, i); // 找到应该插入的位置 Comparable tempI = a[i]; for (int j = i - 1; j >= position; j--) { // 为插入的位置腾出空间,向后挪动 a[j + 1] = a[j]; } a[position] = tempI; // 插入值 } } public static int findPosition(Comparable[] a, int i) { for (int j = i - 1; j >= 0; j--) { if (less(a[j], a[i])) return j + 1; } return 0; }
和选择排序不同,插入排序所需的时间和取决于输入元素的初始顺序。对一个已经接近有序的数组进行排序,会比对顺序随机的数组进行排序快得多。
平均时间复杂度:O(n^2)。最好情况时间复杂度O(n)。
插入排序对于部分有序的数组来说非常高效,这是它最大的优点。
希尔排序
希尔排序是对插入排序的一个优化。对于大规模的乱序数组,插入排序会比较缓慢,因为他需要慢慢的向前找到位置,所以元素只能一点一点的从数组中移动到另一端。
希尔排序本质就是分组的插入排序,但是它并不需要一个一个比较,而是能将元素一下子移到一个比较远的地方。
原理:
先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
上图中,方框指向的是同一组,也就是将同一组数据排序。当分组越来越少,一组中元素越来越多的时候,排序就基本完成了。而最终,当所有元素都在同一组中的时候,就是简单插入排序。因为这个时候数组已经接近有序了,所以排序起来非常快。
代码:
public static void sort(Comparable[] a) { int gap = 1; while (gap < a.length /3) gap = 3*gap + 1; while (gap >= 1) { for (int i = 0; i < gap; i++) { for (int j = i + gap; j < a.length; j += gap) { int position = findPosition(a, i, j, gap); Comparable tempJ = a[j]; for (int k = j - gap; k >= position; k -= gap) { a[k + gap] = a[k]; } a[position] = tempJ; } } gap /= 3; } } public static int findPosition(Comparable[] a, int begin, int current, int gap) { for (int i = current - gap; i >= begin; i -= gap) { if (less(a[i], a[current])) return i + gap; } return begin; }和插入排序非常类似,仅仅是分组的插入排序。
比较:
代码:
public static void main(String[] args) { final int NUM = 10000; Integer[] a1 = new Integer[NUM]; Integer[] a2 = new Integer[NUM]; Integer[] a3 = new Integer[NUM]; Integer[] a4 = new Integer[NUM]; for (int i = 0; i < NUM; i++) { a1[i] = (int)(Math.random() * NUM); a2[i] = a1[i]; a3[i] = a1[i]; a4[i] = a1[i]; } long startTime; long endTime; /* * 交换 * */ startTime = System.currentTimeMillis(); //获取开始时间 switchSort.sort(a2); assert isSorted(a2); endTime = System.currentTimeMillis(); System.out.println("交换排序cost: " + (endTime - startTime) + " ms"); /* * 插入 * */ startTime = System.currentTimeMillis(); //获取开始时间 InsertSort.sort(a3); assert isSorted(a3); endTime = System.currentTimeMillis(); System.out.println("插入排序cost: " + (endTime - startTime) + " ms"); /* * 冒泡 * */ startTime = System.currentTimeMillis(); //获取开始时间 bubbleSort.sort(a1); assert isSorted(a1); endTime = System.currentTimeMillis(); System.out.println("冒泡排序cost: " + (endTime - startTime) + " ms"); /* * shell * */ startTime = System.currentTimeMillis(); //获取开始时间 ShellSort.sort(a4); assert isSorted(a4); endTime = System.currentTimeMillis(); System.out.println("希尔排序cost: " + (endTime - startTime) + " ms"); }运行多次,我们会发现:
当数据量不大的时候,插入排序比交换排序快。而数据量大起来,交换排序就比插入排序更加快了。
当然,如果数组是接近有序的,插入排序的性能会非常强大。
冒泡排序比较缓慢。
而希尔排序的性能非常不错。即使数据量非常大,希尔排序的性能也是不错的。希尔排序的平均时间复杂度是O(n^1.3)左右。相对简单插入排序来说确实是极大的提升。并且希尔排序的代码量并不大。
相关文章推荐
- 用php实现选择排序(简单排序)算法
- Java数据结构和算法-简单排序(4-对象排序及几种排序的比较)
- 让程序员抓狂的排序算法教学视频
- Python写算法(一)插入排序
- 算法--排序(冒泡,选择,插入,快速)
- 算法 -- 几种基本排序深入探究
- Java算法排序之--直接插入排序
- 算法【8】:快速排序
- 2016.7.26 排序,查找 算法
- 让程序员抓狂的排序算法教学视频
- 算法(1)插入排序
- 数据结构&算法实践—【排序|交换排序】Bogo排序
- 数据结构&算法实践—【排序|选择排序】选择排序
- 排序算法之快速排序
- 数据结构&算法学习笔记: 快速排序
- 基本的排序算法之——插入排序法(稳定)(对于固定的空间数组或者顺序表)
- 算法——排序——选择排序
- 第十六周--项目5算法验证基数排序
- 第16周项目1-验证算法(5)直接选择排序
- 数据结构&算法实践—【排序|选择排序】堆排序