排序算法
来源:互联网 发布:js水泥基防水 编辑:程序博客网 时间:2024/06/05 17:52
一:分类
排序大的分类可以分为两种:内排序和外排序。
在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序。本章内的排序都是属于内排序。
内排序可以分为以下几类:
(1) 插入排序:直接插入排序、二分法插入排序、希尔排序。
(2) 选择排序:简单选择排序、堆排序。
(3) 交换排序:冒泡排序、快速排序。
(4) 归并排序
(5) 基数排序
二:插入排序
- 思想:
- 核心 在已经插入的有序序列中找到要插入的位置
插入的一定是有序序列。(插入第n个数据,则前面n-1的数据一定是排好序的,每插入一个数据的同时就是给插入的序列排序,插入N完成后N之前的数据一定是有序序列)
已经插入完的数都是有序的,未插入的数都是无序的
注意每次插入时比较的不是全部的数据,而是已经插入到有序序列中的数据,所以每次循环比较的基数不一样。
- 方法
思想都一样,就是在有序序列中查找插入元素的位置的方法不一样
分为:
直接插入排序 : 从后向前比较每个元素,每次比较的同时移动元素 O(n2)
二分插入排序: 先二分查找到插入的索引位置,再统一移动元素 O(n2)
希尔排序 : 分为多组,每组内进行直接插入排序, O(nlogn)
1. 直接插入排序 (从后向前查找有序序列)
- 基本思想
- 实例
- JAVA算法
public class Sort {public static void main(String[] args) {int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1 };// 外循环的执行次数取决于待插入元素数组的大小,要插入多少数据就需要循环比较多少次。for (int i = 1; i < a.length; i++) { //从第二个元素开始比较,一共比较i-1次。// 保存待插入的元素int temp = a[i];int j;// 内循环的执行次数取决于已经插入到有序数列中的元素,i是要插入的元素在原数组中的坐标,则从i-1位置的元素开始倒着查找一直到0,for (j = i - 1; j >= 0; j--) {// a[j]是有序序列中当前的元素,大于temp的话往后移动一位,i-1的元素如果大于插入的i,则移动到i的位置,if (a[j] > temp) {a[j + 1] = a[j]; } else {break; // 找到合适的插入位置了,结束循环}}a[j + 1] = temp; }}}
算法分析:
外循环:
执行次数: N个元素的数组,需要插入从1到n-1的元素,次数固定
结束条件: 需要把N-1的元素全部插入完成,自然结束循环
内循环:
执行次数: 每次需要执行从n-1的位置到0,次数不固定
结束条件:1是全部元素比较完毕自然结束循环
a[0]>temp,然后j=-1.结束循环,则temp就是保存到a[0]的位置
2是提前找到了插入的位置
a[j]<temp则提前退出了循环,以前的j+1位置的元素肯定是大于temp的,已经向后移了一位,则j+1就是保存temp的位置
- 时间复杂度分析
与待排序记录的初始状态有关
最优情况,如果待排序的元素为正序,则每插入的元素就只需要和i-1的元素比较一次就可以了。 内循环都是1次,外循环是n-1次,则时间复杂度为O(n)
最坏情况,如果待排序的元素为倒序,则每插入的元素就需要从i-1一直比较到0,内循环每次都要比较N次。则时间复杂度为O(n2)
平均时间复杂度为O(n2)
2 二分法插入排序 (按二分法在有序序列中找到插入位置)
- 基本思想
在有序队列中查找插入位置的时候是按照二分法查找的,可以减少比较的次数
- 实例
第一次查找:中点是36,42>mid,更改low值
第二次查找,low=mid+1, 中点是low 53, 42<mid,更改high值
第三次查找,high=mid-1, 此时high<low,循环结束,插入位置是low或者high+1,需要把42插入到53的位置,所以53往后的元素都要往后移动一位
- java算法
public static void main(String[] args) { int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1 }; // 外循环也是执行i-1次,需要插入i-1个元素。 for (int i=1; i< a.length; i++) { // 待插入的元素 int temp = a[i]; // 初始化查找的区间 int low = 0; int high = i-1; int mid = 0; // 循环结束条件,low>high时 while (low <= high) { mid = (low + high)/2; // 比较插入元素和中点元素的大小,更改查询区间 if (temp<a[mid]) { high = mid -1; } else { low = mid + 1; } } // 循环结束的时候,只是找到了要插入的元素的位置是low // 则low到i-1的所有元素都要往后移动一位 for (int j = i-1; j>=low;j--) { a[j+1] = a[j]; } // 插入元素到low if (low !=i) { a[low] = temp; } }}
- 时间复杂度
与待排序记录的初始状态无关,仅依赖于记录的个数。
不一定比直接插入排序效率高,如果直接插入排序是正序的话每个数据就比较一次,而二分次数反而多
最坏的情况为n2/2,最好的情况为n,平均移动次数为n2.
外循环:
和插入排序一样。N-1次
内循环:
结束条件: low > high
3: 希尔排序
- 基本思想
- 实例
- java算法
public static void main(String[] args) {int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1 };int d = a.length;// d为1时退出循环while(d!=1){ // 每次分为d组 d = d / 2; // 外循环就是分为的d组,0-d-1的元素分别就是这d组中的每一组的第一个元素,因为每隔d长度的元素就放到同一个组里 for(int x=0;x<d;x++){ // 每次循环找到属于当前分组中的所有元素,从x+d开始,每隔d长度的数据 for(int i=x+d;i<a.length;i=i+d){ // 每个分组中要插入的数据 int temp = a[i]; int j; // 每个分组中要插入的数据和这个分组中之前已经排好序的数据进行直接插入排序,找到这个分组中最后一个元素,就是i-d,倒着查找 for(j=i-d;j>=0&&a[j]>temp;j=j-d){ // 往后移动d位,就是分组中移动一位 a[j+d] = a[j]; } a[j+d] = temp; } }}}
- 时间复杂度
希尔排序的时间性能优于直接插入排序,原因如下:
三: 选择排序
- 思想
- 关键
1. 直接选择排序 O(n2)
- 思想
- 实例
- java算法
public class Hello {public static void main(String[] args) {int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1 };// 从0开始查找,因为是按照位置选元素,第0个位置的元素可能会变化for (int i = 0; i< a.length; i++) {// 给内循环的变量赋初始值,一个是记录最小的值,一个是记录最小值的索引int min = a[i];int n=i;// 第i个位置选择元素每次从i+1的位置开始查找,找到最小的值for(int j=i+1;j<a.length;j++){if(a[j]<min){min = a[j];n = j;}}//把当前位置的元素和最小值进行交换a[n] = a[i];a[i] = min;}}
四:交换排序
1. 冒泡排序
- 思想
- 实例
- Java算法
public static void main(String[] args) {int[] a = { 49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1 };// 最大数向下沉,第一次排序后最大数在最后一位,所以正向比较for (int i = 0; i < a.length; i++) {// 第i次排序后,则后i个数已经是有序的了,不需要再进行比较,比较的是未排序的数据for (int j = 0; j < a.length - i - 1; j++) {// 核心算法,比较相邻的2个数的大小交换if (a[j] > a[j + 1]) {int temp = a[j];a[j] = a[j + 1];a[j + 1] = temp;}}}}<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
public static void main(String[] args) {int[] a = { 49, 38, 65, 97, 1, 7, 45, 23, 4 };// 最小数向上冒,第一次排序后最小数在第一位,所以要逆向比较for (int i = a.length - 1; i >= 0; i--) {// 第i次循环后,前面的数是不需要再比较了,注意这个边界for (int j = a.length - 1; j >= a.length - i; j--) {if (a[j] < a[j - 1]) {int temp = a[j];a[j] = a[j - 1];a[j - 1] = temp;}}}}
算法核心:
- 时间复杂度
2. 快速排序
- 思想
- 实例
- Java算法
private static void quickSort(int[]a, int low, int high) {// 递归结束的条件,否则会堆栈溢出if (low < high) {// 一次排序的过程,返回这个区间上基准元素也就是第一个元素最终在序列中的位置 int middle = getMiddle(a,low,high); quickSort(a, 0, middle-1); quickSort(a, middle+1, high); }} private static int getMiddle(int[] a, int low, int high) { // 取排序区间的第一个位置的元素为基准元素 int temp = a[low]; while(low<high){ //基准元素是第一个元素,先从最后一个元素high进行比较,如果high的元素大于基准元素,则不发生位置交换,high减1再比较倒数第2个元素 while(low<high && a[high]>=temp){ high--; } // 如果high的元素小于基准元素,则位置互换,高位的元素放到当前基准元素的位置 a[low] = a[high]; // 位置发生变化后,就应该从low位开始往上查找了,如果小于基准元素,则只移动指针,否则元素发生交换 while(low<high && a[low]<=temp){ low++; } a[high] = a[low]; } // 循环结束后,low的位置就是基准元素的位置 a[low] = temp; return low;}
- 时间复杂度
快速排序是不稳定的排序。
快速排序的时间复杂度为O(nlogn)。
当n较大时使用快排比较好,当序列基本有序时用快排反而不好。
五: 归并排序
- 思想
- 实例
- java算法
private static void quick(int[] a) {if (a.length > 0) {mergeSort(a,0,a.length-1);}}private static void mergeSort(int[] a, int low, int high) {// 递归结束的条件,否则会堆栈溢出if (low < high) { int middle = (low+high)/2; mergeSort(a, low, middle); mergeSort(a, middle+1, high); // 类似于合并2个有序链表的算法,合并的时候,左右2个区间的数据已经各自有序了 merge(a,low,middle,high);}}private static void merge(int[] a, int low, int middle, int high) {// 申请临时空间保存2个区间合并后的元素int[] tmpArr = new int[a.length];// 右边区间的启始位置int rightstart = middle+1; int tmp = low;// 临时空间当前元素的启始位置int third = low;// 比较2个区间中每个元素的大小放入临时空间,直到有一个区间元素结束while(low<=middle && rightstart<=high){ if(a[low]<=a[rightstart]){ // 如果左边区间的元素小于右边,则把左边的元素保存到临时空间,保存空间和左边区间的指针加1 tmpArr[third] = a[low]; third++; low++; }else{ tmpArr[third++] = a[rightstart++]; } }// 循环结束后,肯定是有一个区间的元素都比较完了。如果左边还有元素则把剩下的元素直接保存到临时空间,因为这些元素都已经是有序的了while(low<=middle){ tmpArr[third++] = a[low++];}// 右边空间还有剩余元素的话while(rightstart<=high){ tmpArr[third++] = a[rightstart++]; }// 把合并后的临时空间的元素复制回原数组while(tmp<=high){ a[tmp] = tmpArr[tmp++];}}
mergeSort这个函数相当于递归把全序列的元素不断的找到中点,一分为二,进行分组
- 时间复杂度
归并排序是稳定的排序方法。
归并排序的时间复杂度为O(nlogn)。
速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列。
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- 排序算法
- SharedPreference跨进程数据刷新不及时
- MapReduce --InputFormat
- SQLServer时间相关 - SQL日期,时间比较
- 生成和解析json
- The SDK platform-tools version (20) is too old to check APIs compiled with API(x)
- 排序算法
- java反射+枚举+泛型
- PHP连接MySql报SQLSTATE[HY000] [2002] No such file or directory
- ios中闹钟和播放系统声音
- RequireJS入门(一)
- log4j参考资料
- 地图和语音.so库的问题
- Win7下安装配置WAMP:Apache + php + mysql
- 互联网金融的戈尔迪之结