本篇博客给大家带来的是排序的知识点, 由于时间有限, 分两天来写, 中篇主要实现后三种排序算法: 冒泡排序,快速排序,下一篇讲 归并排序.
文章专栏: Java-数据布局
若有问题 批评区见
欢迎大家点赞 批评 收藏 分享
假如你不知道分享给谁,那就分享给薯条.
你们的支持是我不断创作的动力 .
1. 冒泡排序
1.1 算法思路
1. 将数组中相邻元素从前往后依次举行比较,假如前一个元素比后一个元素大,则交换,一趟下来后最大元素就在数组的末尾
2. 依次举行上述过程,直到数组中所有的元素都分列好
如下模仿动图:
1.2 具体步调:
1. 定义i = 0, j = 0, i 表现冒泡的趟数, j为起始位置的下标值.
2. 每一趟中遍历未排序数组, 比较[ j ] 与 [ j + 1 ] 的大小, 若[ j ] 较大, 则值交换, 否则 j++.
1.3 代码实现:
- // 交换下标分别为 i 和 j 的两个元素
- public static void swap(int[] array,int i,int j) {
- int tmp = array[i];
- array[i] = array[j];
- array[j] = tmp;
- }
- //冒泡排序
- public static void bubbleSort(int[] array) {
- for (int i = 0; i < array.length-1; i++) {
- for (int j = 0; j < array.length-1-i; j++) {
- if(array[j] > array[j+1]) {
- swap(array,j,j+1);
- }
- }
- }
- }
复制代码 1.4 优化冒泡排序:
如上图, 举行完第i = 2 趟之后,发现数组已经有序了,没有须要再举行下去了.
于是我们可以定义一个标志变量flag = false,假如发生交换就将flag = true.
优化代码如下:
- /**
- * 冒泡排序:
- * 时间复杂度:
- * 最坏情况: 没有优化的 O(N^2)
- * 最好情况: 优化过的O(N)
- * 空间复杂度: O(1)
- * 稳定性: 稳定
- * @param array
- */
- public static void bubbleSort(int[] array) {
- for (int i = 0; i < array.length-1; i++) {
- boolean flag = false;
- for (int j = 0; j < array.length-1-i; j++) {
- if(array[j] > array[j+1]) {
- swap(array,j,j+1);
- flag = true;
- }
- }
- if(!flag) {
- //没交换,说明已经有序
- return;
- }
- }
- }
- public static void swap(int[] array,int i,int j) {
- int tmp = array[i];
- array[i] = array[j];
- array[j] = tmp;
- }
复制代码 1.5 冒泡排序的特性总结:
1. 冒泡排序是一种非常轻易明白的排序
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳固性:稳固
2. 快速排序
快速排序是Hoare于1962年提出的一种二叉树布局的交换排序方法.
2.1 根本头脑:
任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都分列在相应位置上为止。
- [start,end]为待排序区间.
- private static void quick(int[] array,int start,int end) {
- if(start >= end) return;//start下标 与 end相遇说明基准值已经排好序了.
- //拿到基准值下标,将原数组划分为两个子序列.
- int pivot = partition(array,start,end);
- //递归排左序列
- quick(array,start,pivot-1);
- //递归排右序列
- quick(array,pivot+1,end);
- }
复制代码 上述为快速排序递归实现的主框架,发现与二叉树前序遍历规则非常像,在写快速排序递归框架时可想想二叉树前序遍历规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据举行分别的方式即可。
将区间按照基准值分别为左右两半部分的常见方式有:
2.2 具体实现:
2.2.1 Hoare版:
1. 将首元素作为关键字key, 先从数组最右边right开始,向左找到比key小的数 i .再从左left开始向右找比key大的数 j . i 和 j 交换.
2. 重复第一步直到left >= right.
3. 当left = right 时 交换 [left] 和 key. 此时left 和 right所在的位置即为基准值下标.
具体过程如下图:
代码如下:
- public static void swap(int[] array,int i,int j) {
- int tmp = array[i];
- array[i] = array[j];
- array[j] = tmp;
- }
- private static int partition(int[] array,int left,int right) {
- int key = array[left];
- int i = left;
- while(left < right) {
- //1. 为什么要先走右边而不是先走左边?
- //如果先走右边,最终pivot下标处的值一定比key(头元素)小,自己画图便知.
- //如果先走左边,最终pivot下标处的值一定比key(头元素)大,自己画图便知.
- //2. 为什么要 >= 和 <= 而不能是>和< 等号必须取,因为left一开始就在key下标,
- // 判断的时候,a[left]<key为false,a[right]>key为也为false,所以等号必取,否则left和right一开始就不动.
- while(left < right && array[right] >= key) {
- //如果所有元素确实都小于key,那么会越界,所以加上left<right条件
- right--;
- }
- while(left < right && array[left] <= key) {
- left++;
- }
- //走到这里说明right的元素值小于key,left的元素大于key,那么交换即可
- swap(array,left,right);
- }
- //走到这里,left=right,key与pivot交换,但是原先的key位置left已经改变了,
- //所以再left变之前先将其保存下来.
- swap(array,i,left);
- //返回基准值的下标.
- return left;
- }
复制代码 2.2.2 挖坑法:
1. 定义变量key=[left], 从右边right开始往左,找到比key小的第一个数 tmpR,tmpR的下标为 i , [left] = tmpR, 将 left位置覆盖, 此时 i 位置可看作一个空出来的坑位.
2. 再从左边left开始往右, 找到一个比key大的元素 tmpL, tmpL的位置为 j , [right] = tmpL, 此时 j 也空出来了.
3. 重复以上两步, 直到left >= right.
具体过程如下图:
代码如下:
- public static void quickSort(int[] array) {
- quick2(array,0,array.length-1);
- }
- private static void quick2(int[] array,int start,int end) {
- if(start >= end) return;
-
- int pivot = partition2(array,start,end);
- quick2(array,start,pivot-1);
- quick2(array,pivot+1,end);
- }
- private static int partition2(int[] array,int left,int right) {
- int key = array[left];
- while(left < right) {
- while(left < right && array[right] >= key) {
- right--;
- }
- //从右边开始第一个比key小的元素覆盖[left]
- array[left] = array[right];
- while(left < right && array[left] <= key) {
- left++;
- }
- //从左边开始第一个比key大的元素覆盖空位.
- array[right] = array[left];
- }
- //key填补最终的空位,此时left=right
- array[left] = key;
- return left;
- }
复制代码 2.2.3 前后指针法(了解):
自行画图即可:
代码如下:
- public static void swap(int[] array,int i,int j) {
- int tmp = array[i];
- array[i] = array[j];
- array[j] = tmp;
- }
- private static int partition(int[] array, int left, int right) {
- int prev = left ;
- int cur = left+1;
- while (cur <= right) {
- if(array[cur] < array[left] && array[++prev] != array[cur]) {
- swap(array,cur,prev);
- }
- cur++;
- }
- swap(array,prev,left);
- return prev;
- }
复制代码 递归快排模仿动图
2.2.4 快速排序的递归优化:
1. 当数据量很大的待排序数组本身是有序的时候, 递归快排会出现单分支的情况, 此时递归的次数最多, 所需的空间也最多, 怎么减小空间斲丧呢?
在递归快排之前, 先对待排序数组举行三数取中操作
三数取中: 拿出数组首尾中三个元素值, 取这三个数的中央值与0下标的值举行交换.
三数取中代码实现:
- //三数取中.
- private static int midOfThree(int[] array, int left, int right) {
- int mid = (left+right)/2;
- if(array[right] > array[left]) {
- if(array[mid] < array[left]) {
- return left;
- }else if(array[mid] > array[right]) {
- return right;
- }else {
- return mid;
- }
- }else {
- if(array[mid] > array[left]) {
- return left;
- }else if(array[mid] < array[right]) {
- return right;
- }else {
- return mid;
- }
- }
- }
- public static void quickSort(int[] array) {
- quick2(array,0,array.length-1);
- }
- private static void quick2(int[] array,int start,int end) {
- if(start >= end) return;
- //三数取中,优化空间复杂度
- int index = midOfThree(array,start,end);//三数取中,
- swap(array,index,start);//start下标数与中间数交换,确保start下标 是中间大的数.
- int pivot = partition2(array,start,end);
- quick2(array,start,pivot-1);
- quick2(array,pivot+1,end);
- }
- private static int partition2(int[] array,int left,int right) {
- int key = array[left];
- while(left < right) {
- while(left < right && array[right] >= key) {
- right--;
- }
- //从右边开始第一个比key小的元素覆盖[left]
- array[left] = array[right];
- while(left < right && array[left] <= key) {
- left++;
- }
- //从左边开始第一个比key大的元素覆盖空位.
- array[right] = array[left];
- }
- //key填补最终的空位,此时left=right
- array[left] = key;
- return left;
- }
复制代码 2.递归快排的第二次优化:
- //第二次优化快速排序.
- //递归得差不多了之后,使用直接插入排序效率可能会更高.
- public static void insertSortRange(int[] array,int begin,int end) {
- for (int i = begin+1; i <= end; i++) {
- int tmp = array[i];
- int j = i-1;
- for (; j >= begin; j--) {
- if(array[j] > tmp) { // >= 会变成不稳定的
- array[j+1] = array[j];
- }else {
- //array[j+1] = tmp; 执行了一次
- break;
- }
- }
- //当j=-1时,a[j+1]=tmp这条语句没被执行,所以再写一次
- array[j+1] = tmp; //执行了两次, 故把第一次屏蔽.
- }
- }
- public static void quickSort(int[] array) {
- quick2(array,0,array.length-1);
- }
- private static void quick2(int[] array,int start,int end) {
- if(start >= end) return;
- //第二次优化快速排序法.减少递归的次数,但是不一定是优化,可能反而会变慢.
- if(end - start+1 <= 15) {
- //直接插入排序
- insertSortRange(array,start,end);
- return;
- }
- //三数取中,优化空间复杂度
- int index = midOfThree(array,start,end);//三数取中,
- swap(array,index,start);//start下标数与中间数交换,确保start下标 是中间大的数.
- int pivot = partition2(array,start,end);
- quick2(array,start,pivot-1);
- quick2(array,pivot+1,end);
- }
复制代码 以递归快排的团体代码如下(以Hoare版为例):
- public static void quickSort(int[] array) {
- quick2(array,0,array.length-1);
- }
- private static void quick2(int[] array,int start,int end) {
- if(start >= end) return;
- //第二次优化快速排序法.减少递归的次数,但是不一定是优化,可能反而会变慢.
- if(end - start+1 <= 15) {
- //直接插入排序
- insertSortRange(array,start,end);
- return;
- }
- //三数取中,优化空间复杂度
- int index = midOfThree(array,start,end);//三数取中,
- swap(array,index,start);//start下标数与中间数交换,确保start下标 是中间大的数.
- int pivot = partition2(array,start,end);
- quick2(array,start,pivot-1);
- quick2(array,pivot+1,end);
- }
- private static int partition2(int[] array,int left,int right) {
- int key = array[left];
- while(left < right) {
- while(left < right && array[right] >= key) {
- right--;
- }
- //从右边开始第一个比key小的元素覆盖[left]
- array[left] = array[right];
- while(left < right && array[left] <= key) {
- left++;
- }
- //从左边开始第一个比key大的元素覆盖空位.
- array[right] = array[left];
- }
- //key填补最终的空位,此时left=right
- array[left] = key;
- return left;
- }
- //递归快排的第一次优化: 三数取中.
- private static int midOfThree(int[] array, int left, int right) {
- int mid = (left+right)/2;
- if(array[right] > array[left]) {
- if(array[mid] < array[left]) {
- return left;
- }else if(array[mid] > array[right]) {
- return right;
- }else {
- return mid;
- }
- }else {
- if(array[mid] > array[left]) {
- return left;
- }else if(array[mid] < array[right]) {
- return right;
- }else {
- return mid;
- }
- }
- }
- //第二次优化快速排序.
- //递归得差不多了之后,使用直接插入排序效率更高.
- public static void insertSortRange(int[] array,int begin,int end) {
- for (int i = begin+1; i <= end; i++) {
- int tmp = array[i];
- int j = i-1;
- for (; j >= begin; j--) {
- if(array[j] > tmp) { // >= 会变成不稳定的
- array[j+1] = array[j];
- }else {
- //array[j+1] = tmp; 执行了一次
- break;
- }
- }
- //当j=-1时,a[j+1]=tmp这条语句没被执行,所以再写一次
- array[j+1] = tmp; //执行了两次, 故把第一次屏蔽.
- }
- }
复制代码 快速排序的非递归实现在下篇 ↓
以上就是本篇文章的全部内容啦! 七大排序的难点在于明白, 只要明白了并且本身画图,写代码, 那么信赖这一块就没什么太大的问题啦,
感谢观看, 盼望我们都能在排序的学习之路上 秒杀"OJ排序题" .
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |