java排序算法

目录

一 冒泡排序

二 选择排序

三 插入排序

四 希尔排序

五 快速排序

5.1 单边循环快速排序

5.2 双边循环快速排序

六 二分查找

七 总结


一 冒泡排序

  1. 依次比较数组中相邻的两个元素,若 arr[i] > arr[i+1],则交换两个元素,两两都比较一遍称为一轮冒泡,结果是最大的元素排在最后。
  2. 重复以上操作,直至整个数组有序。
  3. 每轮冒泡时,最后一次交换索引可以作为下一轮冒泡的比较次数,如果这个值为0,表示整个数组有序,直接退出外层循环。
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {5,9,7,4,1,2,8,3,6};
        bubble(arr);
    }

    public static void bubble(int[] arr) {
        int n = arr.length - 1;
        while(true) {
            int last = 0; // 最后一次交换索引位置
            for(int i=0;i<n;i++) {
                if(arr[i] > arr[i+1]) {
                    swap(arr, i, i+1);
                    last = i;
                } 
            }
            n = last;
            if(n == 0) {
                break;
            }
            System.out.println(Arrays.toString(arr));
        }  
    }

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

二 选择排序

  1. 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集。
  2. 重复以上操作,直至整个数组有序。
  3. 每轮可以先找到最小的索引,在每轮最后在交换元素。
public class SelectionSort {
    public static void main(String[] args) {
        int[] arr = {5,9,7,4,1,2,8,3,6};
        selection(arr);
    }

    public static void selection(int[] arr) {
        for(int i=0;i<arr.length-1;i++) {
            int s = i; // 记录最小元素索引
            for(int j=s+1;j<arr.length;j++) {
            	if(arr[s] > arr[j]) {
            		s=j;
            	}
            }
            if(s != i) {
                swap(arr, s, i);
            }
            System.out.println(Arrays.toString(arr));
        }
    }

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

三 插入排序

  1. 将数组分为两个区域,排序区和未排序区,每一轮从未排序区域选取第一个元素,插入到排序区域(需要保证顺序)。
  2. 重复以上操作,直至整个数组有序。
  3. 待插入元素进行比较时,遇到比自己小的元素,就代表找到了插入位置,无需进行后续比较。
public class InsertSort {
    public static void main(String[] args) {
        int[] arr = {5,9,7,4,1,2,8,3,6};
        insert(arr);
    }

    public static void insert(int[] arr) {
    	// i 代表待插入元素的索引
        for(int i=1;i<arr.length;i++) {
            int t = arr[i]; // 待插入元素的值
            int j = i-1; // 已排序元素的索引
            while(j >= 0) {
                if(t < arr[j]) {
                    arr[j+1] = arr[j];
                } else {
                    break; // 退出循环,减少比较次数
                }
                j--;
            }
            arr[j+1] = t;
            System.out.println(Arrays.toString(arr));
        }
    }
}

四 希尔排序

  1. 设置间隙序列。
  2. 将间隙相同元素划为一组,对每组使用插入排序。
  3. 重复以上操作,直至数组有序。
public class ShellSort {
	
	public static void main(String[] args) {
		int[] arr = {5,9,7,4,1,2,8,3,6};
		shell(arr);
	}
	
	public static void shell(int[] arr) {
		// 分而治之,循环为每次总数除二
		for (int gap = arr.length/2; gap > 0; gap /= 2) {
			// 循环分治的每一个分组使用插入排序
	        for (int i = gap; i < arr.length; i++) {
	            int t = arr[i];
	            int j = i-gap;
	            while (j >= 0) {
	            	if (t < arr[j]) {
	            		arr[j + gap] = arr[j];
	            	} else {
	            		break;
	            	}
	            	j -= gap;
	            }
	            arr[j+gap] = t;
	            System.out.println(Arrays.toString(arr));
	        }
	        System.out.println("gap="+gap+":"+Arrays.toString(arr));
		}
	}
	
}

五 快速排序

5.1 单边循环快速排序

  1. 选择最右边元素作为基准点元素。
  2. j 指针负责找到基准点小的元素,一旦找到则与 i 进行交换。
  3. i 指针维护小于基准点元素边界,也是每次交换的目标索引。
  4. 最后基准点与 i 交换,i 即为分区位置。
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {5,9,7,4,1,2,8,3,6};
        quick(arr, 0, arr.length-1);
    }

    public static void quick(int[] arr, int l, int h) {
        if(l >= h) {
            return;
        }
        int pv = partition(arr, l, h);
        quick(arr, l, pv-1); // 左边分区范围确定
        quick(arr, pv+1, h); // 右边分区范围确定
    }

    public static int partition(int[] arr, int l, int h) {
        int pv = arr[h];
        int i = l;
        for(int j=l;j<h;j++) {
            if(arr[j] < pv) {
                if(i != j) {
                    swap(arr, i, j);
                }
                i++;
            }
        }
        if(i != h) {
            swap(arr, h, i);
        }
        System.out.println(Arrays.toString(arr));
        return i;
    }

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

5.2 双边循环快速排序

  1. 选择最左边元素作为基准点元素。
  2. j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i、j 相交。
  3. 最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置。
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {5,9,7,4,1,2,8,3,6};
        quick(arr, 0, arr.length-1);
    }

    public static void quick(int[] arr, int l, int h) {
        if(l >= h) {
            return;
        }
        int pv = partition(arr, l, h);
        quick(arr, l, pv-1); // 左边分区范围确定
        quick(arr, pv+1, h); // 右边分区范围确定
    }

    public static int partition(int[] arr, int l, int h) {
        int pv = arr[l];
        int i = l;
        int j = h;
        while(i < j) {
            // j 从右找小的
            while(i < j && arr[j] > pv) {
                j--;
            }
            // i 从左找大的
            while(i < j && arr[i] <= pv) {
                i++;
            }
            swap(arr, i, j);
        }
        swap(arr, l, j);
        System.out.println(Arrays.toString(arr));
        return j;
    }

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

六 二分查找

1、前提:有已排序的数组 arr。

2、定义左边界 l,右边界 r,确定搜索范围,循环执行二分查找(3、4步)。

3、获取中间索引 m = Floor((l+r)/2)。

4、中间索引的值 arr[m] 与待搜索的值 t 进行比较。

  • arr[m] == t 表示找到,返回中间索引。
  • arr[m] > t,中间值右侧的其它元素都大于 t,无需比较,中间索引左边去找,m-1 设置为右边界,重新查找。
  • arr[m] < t,中间值左侧的其它元素都大于 t,无需比较,中间索引右边去找,m+1 设置为左边界,重新查找。

5、当 l > r,表示没有找到,结束循环。

public class BinarySearch {
    public static void main(String[] args) {
        int[] arr = {1, 5, 8, 11, 19, 22, 31, 35, 40, 45, 48, 49, 50};
        int t = 48;
        int index = binarySearch(arr, t);
            
        System.out.println(index);
    }

    public static int binarySearch(int[] arr, int t) {
        int l = 0, r = arr.length - 1, m;
        while(l <= r) {
            //m = l+(r-l)/2; // 解决整数溢出
            m = (l+r) >>> 1;
            if(arr[m] == t) {
                return m;
            } else if(arr[m] > t) {
                r = m-1;
            } else {
                l = m+1;
            }
        }

        return -1;
    }
}

6、面试题

  • 奇数二分取中间
  • 偶数二分取中间靠左

1、有一个有序表为[1,5,8,11,19,22,31,35,40,48,49,50],当二分查找值为48的节点时,需经过几次比较?

2、有一个有序表为[1,4,6,7,15,33,50,64,75,78,81,89,96],当二分查找值为81的节点时,需经过几次比较?

3、在拥有128个元素的数组中二分查找一个数,需要比较的次数最多不超过几次?

  •     \log_{10}128 / \log_{10}2
  •     整数,则该整数即为最终结果
  •     小数,舍去小数部分,整数+1即为最终结果

七 总结

  • int[] arr = {5,9,7,4,1,2,8,3,6} 
时间复杂度实现优化排序过程
冒泡排序O(n^{2})
  1. 依次比较数组中相邻的两个元素,若 arr[i] > arr[i+1],则交换两个元素,两两都比较一遍称为一轮冒泡,结果是最大的元素排在最后。
  2. 重复以上操作,直至整个数组有序。
  • 每轮冒泡时,最后一次交换索引可以作为下一轮冒泡的比较次数,如果这个值为0,表示整个数组有序,直接退出外层循环。
[5, 7, 4, 1, 2, 8, 3, 6, 9]
[5, 4, 1, 2, 7, 3, 6, 8, 9]
[4, 1, 2, 5, 3, 6, 7, 8, 9]
[1, 2, 4, 3, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
选择排序O(n^{2})
  1. 将数组分为两个子集,排序的和未排序的,每一轮从未排序的子集中选出最小的元素,放入排序子集。
  2. 重复以上操作,直至整个数组有序。
  • 每轮可以先找到最小的索引,在每轮最后在交换元素。
[1, 9, 7, 4, 5, 2, 8, 3, 6]
[1, 2, 7, 4, 5, 9, 8, 3, 6]
[1, 2, 3, 4, 5, 9, 8, 7, 6]
[1, 2, 3, 4, 5, 9, 8, 7, 6]
[1, 2, 3, 4, 5, 9, 8, 7, 6]
[1, 2, 3, 4, 5, 6, 8, 7, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
插入排序

O(n^{2})

  1. 将数组分为两个区域,排序区和未排序区,每一轮从未排序区域选取第一个元素,插入到排序区域(需要保证顺序)。
  2. 重复以上操作,直至整个数组有序。
  • 待插入元素进行比较时,遇到比自己小的元素,就代表找到了插入位置,无需进行后续比较。
  • 插入时可以直接移动元素,而不是交换元素。
[3, 5, 1, 7, 2, 9, 8, 4, 6]
[1, 3, 5, 7, 2, 9, 8, 4, 6]
[1, 3, 5, 7, 2, 9, 8, 4, 6]
[1, 2, 3, 5, 7, 9, 8, 4, 6]
[1, 2, 3, 5, 7, 9, 8, 4, 6]
[1, 2, 3, 5, 7, 8, 9, 4, 6]
[1, 2, 3, 4, 5, 7, 8, 9, 6]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
希尔排序
  1. 设置间隙序列
  2. 将间隙相同元素划为一组,对每组使用插入排序。
  3. 重复以上操作,直至数组有序。
[1, 9, 7, 4, 5, 2, 8, 3, 6]
[1, 2, 7, 4, 5, 9, 8, 3, 6]
[1, 2, 7, 4, 5, 9, 8, 3, 6]
[1, 2, 7, 3, 5, 9, 8, 4, 6]
[1, 2, 7, 3, 5, 9, 8, 4, 6]
gap=4:[1, 2, 7, 3, 5, 9, 8, 4, 6]
[1, 2, 7, 3, 5, 9, 8, 4, 6]
[1, 2, 7, 3, 5, 9, 8, 4, 6]
[1, 2, 5, 3, 7, 9, 8, 4, 6]
[1, 2, 5, 3, 7, 9, 8, 4, 6]
[1, 2, 5, 3, 7, 9, 8, 4, 6]
[1, 2, 5, 3, 7, 4, 8, 9, 6]
[1, 2, 5, 3, 6, 4, 7, 9, 8]
gap=2:[1, 2, 5, 3, 6, 4, 7, 9, 8]
[1, 2, 5, 3, 6, 4, 7, 9, 8]
[1, 2, 5, 3, 6, 4, 7, 9, 8]
[1, 2, 3, 5, 6, 4, 7, 9, 8]
[1, 2, 3, 5, 6, 4, 7, 9, 8]
[1, 2, 3, 4, 5, 6, 7, 9, 8]
[1, 2, 3, 4, 5, 6, 7, 9, 8]
[1, 2, 3, 4, 5, 6, 7, 9, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
gap=1:[1, 2, 3, 4, 5, 6, 7, 8, 9]
 
快速排序-单边循环

O(n^{2})

  1. 选择最右边元素作为基准点元素。
  2. j 指针负责找到基准点小的元素,一旦找到则与 i 进行交换。
  3. i 指针维护小于基准点元素边界,也是每次交换的目标索引。
  4. 最后基准点与 i 交换,i 即为分区位置。
[5, 4, 1, 2, 3, 6, 8, 7, 9]
[1, 2, 3, 4, 5, 6, 8, 7, 9]
[1, 2, 3, 4, 5, 6, 8, 7, 9]
[1, 2, 3, 4, 5, 6, 8, 7, 9]
[1, 2, 3, 4, 5, 6, 8, 7, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
快速排序-双边循环

平均:O(n\log_{2}n)

最坏:O(n^{2})

  1. 选择最左边元素作为基准点元素。
  2. j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i、j 相交。
  3. 最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置。
[1, 3, 2, 4, 5, 7, 8, 9, 6]
[1, 3, 2, 4, 5, 7, 8, 9, 6]
[1, 2, 3, 4, 5, 7, 8, 9, 6]
[1, 2, 3, 4, 5, 6, 7, 9, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值