排序算法总结
一、排序算法总结
二、简单排序
2.1 冒泡排序
-
排序原理:
1)比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。
2)对每一对相邻元素做同样的工作,从开始第一对元素到末尾的最后一对元素。最终最后位置的元素就是最大值。
-
代码实现
public class Bubble{ public static void sort(Comparable[] a){ for(int i = a.length - 1; i > 0; i--){ for(int j = 0; j < i; j++){ if(greater[a[j], a[j + 1]]){ swap(a, j, j + 1); } } } } private static boolean greater(Comparable v, Comparable w){ return v.compareTo(w) > 0; } private static void swap(Comparable[] a, int i, int j){ Comparable t = a[i]; a[i] = a[j]; a[j] = t; } }
-
复杂度分析
-
时间复杂度
元素比较次数:n*(n-1)/2,元素交换次数:n*(n-1)/2,总执行次数:n2-n,
时间复杂度:O(n2)
-
空间复杂度
O(1)
-
稳定性
稳定,因为相等的时候不交换位置。
-
2.2 选择排序
-
排序原理
1)每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引处的值为最小值,记录该索引值
2)交换第一个索引值和记录的索引值
-
代码实现
public class Selection{ 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(greater(a[minIndex], a[j])){ minIndex = j; } } swap(a, i, minIndex); } } public static void greater(Comparable v, Comparable w){ return v.compareTo(w) > 0; } public static void swap(Comparable[] a, int i, int j){ Comparable t = a[i]; a[i] = a[j]; a[j] = t; } }
-
复杂度分析
-
时间复杂度
数据比较次数:n*(n-1)/2,数据交换次数:n-1,时间复杂度:O(n2)
-
空间复杂度
O(1)
-
稳定性
不稳定,排序过程中有当前选则的最小元素进行交换时,发生原来相等元素的位置顺序改变。
-
2.3 插入排序
-
排序原理
1)把所有的元素分为两组,已经排序的和未排序的
2)找到未排序的组中的第一个元素,向已经排序的组中进行插入
3)倒叙遍历已经排序的元素,依次和待插入元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待插入元素放到这个位置,其他的元素向后移动一位。
-
代码实现
public class Insertion{ public static void sort(Comparable[] a){ for(int i = 1; i < a.length; i++){ for(int j = i; j > 0; j--){ if(greater(a[j - 1], a[j])){ swap(a, j - 1, j); }else{ break; } } } } public static void greater(Comparable v, Comparable w){ return v.compareTo(w) > 0; } public static void swap(Comparable[] a, int i, int j){ Comparable t = a[i]; a[i] = a[j]; a[j] = t; } }
-
复杂度分析
-
时间复杂度
数据比较次数:n*(n-1)/2,数据交换次数:n*(n-1)/2,总执行次数:n2-n,
时间复杂度:O(n2)
-
空间复杂度
O(1)
-
稳定性
稳定,相等时,不改变元素位置顺序。
-
三、高级排序
3.1 希尔排序
-
排序原理
1)选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组
2)对分好组的每一组数据完成插入排序
3)减小增长量,最小减为1,重复第二部操作
-
代码实现
public class Shell{ public static void sort(Comparable[] a){ int N = a.length; int h = 1; while(h < N / 2){ h = h * 2 + 1; } while(h >= 1){ for(int i = h; i < N; i++){ for(int j = i; j >= h; j -= h){ if(greater(a[j - h], a[j])){ swap(a, j, j - h); }else{ break; } } } } } public static void greater(Comparable v, Comparable w){ return v.compareTo(w) > 0; } public static void swap(Comparable[] a, int i, int j){ Comparable t = a[i]; a[i] = a[j]; a[j] = t; } } //从本地读取文件测试希尔排序 public static void main(String[] args)throws Exception{ ArrayList<Integer> list = new ArrayList<>(); //读取本地文件 BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("rest.txt"))); String line = null; while((line = reader.readLine()) != null){ //把每一个数字存入到集合中 list.add(Integer.valueOf(line)); } reader.close(); //把集合转换成数组 Integer[] arr = new Integer[list.size()]; list.toArray(arr); long start = System.currentTimeMillis(); Shell.sort(arr); long end = System.currentTimeMillis(); System.out.println("使用希尔排序耗时:"+(end-start)); }
-
复杂度分析
-
时间复杂度
对于不同的递增序列可能导致不同的事前估计值,应该采用事后分析法。
-
空间复杂度
O(1)
-
稳定性
不稳定,多次插入排序有可能移动原来相等元素的位置顺序。
-
3.2归并排序
-
排序原理——分治思想
1)将数据拆分成两组,再递归的对子组继续拆分,直到每个子组中的元素个数为1;
2)将相邻的子组合并成一个更大的有序子组;
3)重复步骤2),直到将整组数组排好序。
-
代码实现
public class Merge { private static Comparable[] assist; //辅助数组 public static void sort(Comparable[] a){ assist = new Comparable[a.length]; int low = 0; int high = a.length - 1; sort(a, low, high); } private static void sort(Comparable[] a, int low, int high) { if(low >= high){ return; } int mid = (low + high) / 2; sort(a, low, mid); sort(a, mid + 1, high); merge(a, low, mid, high); } private static void merge(Comparable[] a, int low, int mid, int high) { int i = low; //assist数组移动指针 int left = low; //左半部分指针,初始指向首元素 int right = mid + 1; //右半部分指针,初始指向mid+1位置元素 while (left <= mid && right <= high){ if(less(a[left], a[right])){ assist[i++] = a[left++]; }else { assist[i++] = a[right++]; } } //左边部分未遍历完 while (left <= mid){ assist[i++] = a[left++]; } //右边部分未遍历完 while (right <= high){ assist[i++] = a[right++]; } //将排好序的辅助数组复制到目标数组中 System.arraycopy(assist, 0, a, 0, high); } private static boolean less(Comparable v, Comparable w) { return v.compareTo(w) < 0; } }
-
复杂度分析
-
时间复杂度
采用了分治策略,递归的拆分子组,时间复杂度为O(nlogn),证明见数据结构与算法一书。
-
空间复杂度
O(n),因为借助了辅助数组,典型的空间换时间。
-
稳定性
稳定,只有不等(本例为小于)时才会交换位置,相等元素不改变位置顺序。
-
3.3 快速排序
-
排序原理
1)首先设定一个分界值,通过该分界值将数组划分为左右两部分;
2)将大于或等于该分界值的数据放到数组右边,小于的放到数组左边;
3)递归对左右两边进行排序,重新选取分界值,重新划分;
4)重复上述处理,直到整体有序。
划分原理:
1)确定一个基准值,用两个指针分别指向数组的首尾;
2)分别从首尾开始探寻,从首部开始的,寻找比基准值大的,从尾部开始的,寻找比基准值小的,一旦找到立即停止探寻;——保证数组左边小于基准值,右边大于基准值
3)交换两指针元素;
4)重复2),3)步骤,直到左指针大于右指针。
-
代码实现
public class Quick { public static void sort(Comparable[] a){ int low = 0; int high = a.length - 1; sort(a, low, high); } private static void sort(Comparable[] a, int low, int high) { if(low >= high){ return; } //对当前数组进行划分 int partition = partition(a, low, high); sort(a, low, partition); sort(a, partition + 1, high); } private static int partition(Comparable[] a, int low, int high) { Comparable baseNum = a[low]; //默认把最左边元素当做基准值 int left = low; int right = high + 1; //划分 while (true){ //先从右往左扫描,找到一个比基准值小的元素 while (less(baseNum, a[--right])) { if(right == low){ break; //当扫描到最左边时,停止扫描 } } //从左往右扫描,找到一个比基准值大的元素 while (less(a[++left], baseNum)){ if(left == high){ break; } } if(left >= right){ break; //已经扫描完所有元素 }else{ swap(a, left, right); } } swap(a, low, right); return right; } private static boolean less(Comparable v, Comparable w) { return v.compareTo(w) < 0; } public static void swap(Comparable[] a, int i, int j){ Comparable t = a[i]; a[i] = a[j]; a[j] = t; } }
-
复杂度分析
-
时间复杂度
快速排序的一次切分从两头开始交替搜索,直到left和right重合,因此,一次切分算法的时间复杂度为O(n),但整个快速排序的时间复杂度和切分的次数相关。
最优情况下快速排序的时间复杂度为O(nlogn);最坏情况下,快速排序的时间复杂度为O(n2);平均时间复杂度为O(nlogn)。
-
空间复杂度
O(n)或者O(logn),因为递归造成栈空间的开销,最优(递归深度)为logn,即空间复杂度为:O(logn),最坏情况下需要进行n-1次递归调用,所以为O(n)。
-
稳定性
不稳定,基准值的比较机制会移动相等元素的位置顺序。
-