文章目录
排序算法总览
希尔排序
定义: 希尔排序是插入排序的一种,又称缩小增量排序,是插入排序算法的一种更高效的改进版
需求来源
- 插入排序不足,已排序的分组元素为
{2,5,7,9,10}
,未排序的分组元素为{1,8}
,那么下一个待插入元素为1,我们需要拿着1从后往前,依次和10,9,7,5,2
进行交换位置,才能完成真正的插入,每次交换只能和相邻的元素交换位置。那如果我们要提高效率,直观的想法就是一次交换,能把1放到更前面的位置,比如一次交换就能把1插到2和5之间,这样一次交换1就向前走了5个位置,可以减少交换的次数。
增量h的定义
int h = 1 ;
while(h < N/2){ // N 为待排序总数
h = 2h + 1;
}
// 循环结束后确定 h 的最大值
// h 的 减小规则 向下取整
h = h/2
算法图解
Java 代码
package Sort;
import java.util.Arrays;
public class ShellSort {
public static void sort(int [] arr) {
// 计算 h
int n = arr.length ;
int h = 1 ;
while( h < n/2) {
h = 2*h + 1;
}
// 开始排序
while (h >= 1) {
for (int i = h ; i < n ; i++) {
// 这里 arr[i] 为待插入的元素
for( int j = i ; j >= h ; j = j - h) {
// 这里 arr[j] 为待插入的元素
if( greater(arr[j - h],arr[j])) {
exch(arr, j, j-h ) ;
} else {
break ;
}
}
}
}
}
private static boolean greater(int v ,int w) {
return v > w ;
}
private static void exch(int[] a, int i , int j) {
int temp = a[i] ;
a[i] = a[j] ;
a[j] = temp ;
}
}
class Test4{
public static void main(String [] args) {
int a [] = {9,1,2,5,7,4,8,6,3,5} ;
SelectionSort.sort(a) ; // 注意外层有 主函数
System.out.println(Arrays.toString(a));
}
}
希尔与插入排序比较
归并排序
- 归并排序是建立在递归操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列,即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并
排序原理
- 尽可能的将一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止。
- 将相邻的两个子组进行合并成一个有序的大组
- 不断的重复步骤2,直到最终只有一个组为止。
算法图解
Java 代码
package Sort;
import java.util.Arrays;
/**
* @author LZH.create
* Date : 2021.3.14
* 好好学习,天天向上
* lo --> low hi --. high
*/
public class MergeSort {
// 辅助数组
private static int [] assets ;
// 对数组内的元素进行排序
public static void sort(int [] a) {
// 初始化辅助数组
assets = new int[a.length] ;
// 获取 lo 与 hi
int lo = 0;
int hi = a.length - 1 ;
// 进行排序
sort(a,lo,hi) ;
}
// 对数组 a 从索引lo 到 索引hi 之间的元素进行排序 ( 对区间内的元素进行排序 )
private static void sort(int [] a,int lo ,int hi) {
if( hi <= lo) {
return ;
}
int mid = lo + (hi - lo) / 2 ; // int mid = (hi + lo) / 2 ;
sort(a,lo,mid) ;
sort(a,mid + 1 , hi) ;
merge(a ,lo ,mid ,hi ) ;
}
// 从索引 lo 到 mid 为一个 子组 ,索引 mid + 1 到 hi 为一个子组 ,把数组a 这两个子组合并为一个有序的大组
private static void merge(int [] a,int lo ,int mid ,int hi ) {
int i = lo ;
int p1 = lo ;
int p2 = mid + 1 ;
while( p1 <= mid && p2 <= hi) {
if(less(a[p1],a[p2])) {
assets[i++] = a[p1++] ;
} else {
assets[i++] = a[p2++] ;
}
}
// 循环上述还有多余的处理
while( p1 <= mid) {
// 左子树没循环完
assets[i++] = a[p1++] ;
}
while(p2 <= hi) {
// 右子树没循环完
assets[i++] = a[p2++] ;
}
// 将辅组数组的值复制到 原数组中
for (int j = lo ; j <= hi ; j++ ) {
a[j] = assets[j] ;
}
}
// 判断 v 是否 小于 w
private static boolean less(int v ,int w) {
return v < w ;
}
private static void exch(int[] a, int i , int j) {
int temp = a[i] ;
a[i] = a[j] ;
a[j] = temp ;
}
}
class Test5{
public static void main(String [] args) {
int a [] = {8,4,5,7,1,3,6,2} ;
MergeSort.sort(a) ; // 注意外层有 主函数
System.out.println(Arrays.toString(a));
}
}
算法特点
因为需要申请额外的数组空间,典型的以空间换取时间
快速排序
排序原理
- 首先设定一个分界值,通过该分界值将数组分成左右两部分
- 将大于或等于分界值的数据放到到数组右边,小于分界值的数据放到数组的左边。此时左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值
- 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
- 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。
算法图解
详细步骤
- 从右往左找到一个比基准值小的数据
- 从左往右找到一个比基准值大的数据
1、 首先进行如下步骤
注意 : right 从下图 8 之后的位置开始
2、其次进行如下步骤
在此基础上进行分治
Java 代码
package Sort;
import java.util.Arrays;
/**
* @author LZH.create
* Date : 2021.3.16
* 静心学习
*
*/
public class QuickSort {
// 对数组内的元素进行排序
public static void sort(int [] a) {
// 获取 lo 与 hi
int lo = 0 ;
int hi = a.length - 1 ;
sort(a,lo,hi) ;
}
// 对数组 a 从索引lo 到 索引hi 之间的元素进行排序 ( 对区间内的元素进行排序 )
private static void sort(int [] a,int lo ,int hi) {
// 递归的终止条件
if( hi < lo) {
return ;
}
// 对 a 数组中 从 lo 到 hi 进行切分
int p = partition(a,lo,hi) ;
// 对左边分组的元素进行排序
sort(a,lo,p-1) ;
sort(a,p + 1,hi) ;
}
// 核心代码
private static int partition(int [] a , int lo , int hi) {
// 定义基准 左右 指针
int key = a[lo] ;
int left = lo ;
int right = hi + 1 ; / 一定要加 1 超过
while(true) {
// 从右往左扫描
while(less(key,a[--right])) {
if(right == lo) {
break ;
}
}
// 从左往右扫描
while(less(key,a[++left])) {
if(left == hi) {
break ;
}
}
if(left >= right) {
// 扫描完所有元素,结束循环
break ;
} else {
exch(a,left ,right) ;
}
}
// 最后交换 right 和 基准处
exch(a,lo,right) ;
return right ;
}
// 判断 v 是否 小于 w
private static boolean less(int v ,int w) {
return v < w ;
}
private static void exch(int[] a, int i , int j) {
int temp = a[i] ;
a[i] = a[j] ;
a[j] = temp ;
}
}
class Test6{
public static void main(String [] args) {
int a [] = {6,1,2,7,9,3,4,5,8} ;
MergeSort.sort(a) ; // 注意外层有 主函数
System.out.println(Arrays.toString(a));
}
}
关于快排的改进
- 代码整合
- 效率改进(从左边找到一个大于等于基准值的数,从右边找到一个小于等于基准值的数,然后直接交换两个数的位置)
package Sort;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author LZH.create
* 关于快排的改进
*/
public class QuickSort_plus {
static int[] arr = new int[100010];
public static void q_sort(int l,int r){
if(l>=r)return;
int i = l - 1;
int j = r + 1;
int x = arr[l + r >> 1];
while(i < j){
do i++;while(arr[i] < x);
do j--;while(arr[j] > x);
if(i<j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
q_sort(l,j);
q_sort(j+1,r);
}
public static void main(String[] args) throws IOException{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(in.readLine());
String s[] = in.readLine().split(" ");
for(int i = 0; i < s.length; i++){
arr[i] = Integer.parseInt(s[i]);
}
q_sort(0, n-1);
for(int i = 0; i < n; i++){
System.out.print(arr[i]+" ");
}
}
}
快速排序与归并排序的区别
-
快速排序是另外一种分治的排序算法,它将一个数组分成两个子数组,将两部分独立的排序。
-
快速排序和归并排序是互补的:归并排序将数组分为两个子数组分别排序,并将有序的子数组归并从而将整个数组排序,而快速排序的方式则是当两个数组都有序时,整个数组自然就有序了。
-
在归并排序中,一个数组被等分为两半,归并调用发生在处理整个数组之前,在快速排序中,切分数组的位置取决于数组的内容,递归调用发生在处理整个数组之后。
-
快速排序最优情况如下图
-
快速排序最坏情况如下图