排序

  • 插入排序
    • 1.直接插入排序
    • 2.希尔排序
  • 选择排序
    • 3.直接选择排序
    • 4.堆排序
  • 交换排序
    • 5.冒泡排序
    • 6.快排
  • 归并排序
    • 7.归并排序
  • 稳定性
  • 快排和归并排序的非递归实现

插入排序

1.直接插入排序

动图传送门.
思想: 类比打扑克时,一张张揭牌并从后向前找到插入位置,让手中的牌永远为有序状态

// 1.直接插入
public static void insertSort(int[] array) {// 1.一张张揭牌,手中的牌一直是有序的for (int i = 1; i < array.length; i++) {// 揭到第 i 张牌int curNum = array[i];// 2.向前寻找可插入的地方int j = i - 1;while (j >= 0 && curNum < array[j]) {// 还没有到插入的位置,第j位置向后移动,留出空位array[j + 1] = array[j];j --;}// 3.插入揭到的牌array[j + 1] = curNum;}
}

时间复杂度:

时间复杂度 平均:O(n^2) 最好(有序时):O(n) 最坏(逆序时):O(n^2)
空间复杂度:O(1)
稳定性:稳定
优化:折半查找,先用折半查找到队对应位置,再搬移

2.希尔排序

图解:

思想: 直接插入排序在有序时时间复杂度为 O(n),希尔排序的思想是先让数组趋于有序,再进行直接插入排序。
具体做法:把待排序数组中所有距离为 n 的数记录分在同一组内,并对每一组内的记录进行排序。这样距离为 n 的数边为有序,再缩小 n 值重复上述操作,整个数组逐渐趋于有序,最后 n = 1时,也就是直接插入排序。

// 2.希尔排序
public static void shellSort(int[] array) {int gap = array.length;while (gap > 0) {insertSortGap(array, gap);// 缩小增量公式gap = gap /3 + 1;}// 此时数组已经趋于有序, gap = 1 时,相当于直接插入排序insertSortGap(array,1);
}private static void insertSortGap(int[] array, int gap) {// 对 array 中相隔 gap 的数进行排序,类比直接插入排序for (int i = gap; i < array.length; i += gap) {int curNum = array[i];int j = i - gap;while (j >= 0 && curNum < array[j]) {array[j + gap] = array[j];j -= gap;}array[j + gap] = curNum;}
}

时间复杂度:

时间复杂度 平均:O(n^1.3) 最好:O(n) 最坏:O(n^2)
空间复杂度:O(1)
稳定性:不稳定

选择排序

3.直接选择排序

思想:每轮从无序区间选择最小的数交换到最前边

// 3.直接选择排序
public static void selectSort(int[] array) {// 从无序区间选择最小数交换到最无序区间的最前边  有序区间[0,i)   无序区间[i,len]for (int i = 0; i < array.length; i++) {int minIndex = i;// 寻找最小值下标for (int j = i + 1; j < array.length; j++) {if (array[j] < array[minIndex]) {minIndex = j;}}// 找到后,交换位置int tmp = array[i];array[i] = array[minIndex];array[minIndex] = tmp;}
}

时间复杂度:每轮都要从头到尾遍历,对数据不敏感,O(n^2)
空间复杂度: O(1)
稳定性:不稳定
优化:双向选择排序,每次从无序区间找出最大和最小的元素放到最前和最后

4.堆排序

动画演示.
思想:同直接选择排序,只是不在使用每轮遍历查找无序区间的最小数放在最前边,而是通过创建大根堆每轮来选择最大值(堆顶的数)放在最后边
注意: 排升序要建大堆;排降序要建小堆

// 4.堆排
public static void heapSort(int[] array) {// 1.创建大根堆int end = array.length - 1;for (int i = (end - 1) / 2; i >= 0 ; i--) {// 从最后一个非子叶结点开始向下调整adjustDown(array, i, end);}// 2. 选择最大数交换位置while (end > 0) {// 将最大数换到最后边swap(array, 0, end);// 缩小无序区间end --;//再向下调整adjustDown(array, 0, end);}
}
// 向下调整:调整为父亲结点值大于子结点,在找到最大子结点后与父亲结点对比交换
private static void adjustDown(int[] array, int parent, int end) {// 默认最大子结点int child = 2 * parent + 1;// 有可能要调整的父亲结点是个很小的数,与最大子结点交换后依然小于子结点,还需继续调整。所以要 while 循环里,直到大于子结点或者没有子结点结束循环while (child <= end) {// 如果存在右孩子,并且右孩子大于左孩子,更新最大孩子结点if (child + 1 <= end && array[child + 1] > array[child]) {child += 1;}// 此时的 child 已经为为最大子结点if (array[child] > array[parent]) {// 如果最大孩子结点大于父亲结点,交换swap(array, child, parent);// 从子结点开始下一轮调整parent = child;child = 2 * parent + 1;} else {// 大于子结点,跳出循环break;}}
}
// 交换位置
private static void swap(int[] array, int i, int j) {int temp = array[i];array[i] = array[j];array[j] = temp;
}

时间复杂度:O(logn)
空间复杂度:O(1)
稳定性:不稳定

交换排序

5.冒泡排序

思想:在无序区间,通过相邻数的比较,将最大的数冒泡到无序区间的最后

// 5.冒泡排序
public static void bubbleSort(int[] array) {// 无序区间[0,len-i], i表示有序区间 数的个数for (int i = 0; i < array.length; i++) {// 遍历无序区间for (int j = 0; j < array.length - i - 1; j++) {// 对比交换相邻两个数较大的数if (array[j] > array[j + 1]) {// 交换位置int tmp = array[j];array[j] = array[j + 1];array[j + 1] = tmp;}}}
}

时间复杂度:O(n^2)
空间复杂度: O(1)
稳定性:稳定

6.快排

思想:选定一个数作为基准,一轮遍历确定基准数位置(数组有序后基准的位置),并保证左边全是小于基准数,右边全是大于基准数,再对基准数两边进行同样的操作。即每轮将待排序区间分割为大小两个区间,中间的分割点就是基准数。

    // 6.快排public static void quickSort(int[] array) {quick(array, 0, array.length - 1);}// 递归private static void quick(int[] array, int low, int high) {// 结束条件if (low >= high) return;// 确定大小两区间的 分割点int piv = pivot(array, low, high);// 对两边区间递归quick(array, low, piv - 1);quick(array, piv + 1, high);}// 划分大小区间,并返回分割点下标private static int pivot(int[] array, int start, int end) {// 将待排序区间的首位 确定为基准数int piv = array[start];// 开始寻找基准数该在的位置while (start < end) {// 从右开始找小于基准数的数while (start < end && array[end] >= piv) {end --;}// 放在前边,因为小于基准数的数不应该出现在基准数之后array[start] = array[end];// 从左开始找大于基准数的数while (start < end && array[start] <= piv) {start ++;}// 放在后边,因为大于基准数的数不应该出现在基准数之前array[end] = array[start];}// 左右指针相遇位置,就是数组有序后基准数应该在的位置。因为上边一轮交换后,该位置左边全都小于基准数,右边全大于基准数array[end] = piv;// 返回基准数应该在的位置,及数组有序后基准数在的位置return end;}

时间复杂度:

时间复杂度 平均:O(n * log(n)) 最好(均匀划分):O(n * log(n)) 最坏(有序的情况):O(n^2)
若均匀划分,递归 logn 层你,每次遍历 n 个数,故时间复杂度为 O(nlogn),若有序,则基准位置不需要动,每次都会以第一个位置划分区间,相当于没有划分区间,需要递归 n层,故 时间复杂度为 O(n^2)。

空间复杂度:

时间复杂度 平均:O(log(n)) 最好(均匀划分):O(log(n)) 最坏(有序的情况):O(n)
原因同上,同递归的层数。

稳定性: 不稳定
优化: 以更均匀的划分区间为目的

  1. 随机选取基准
  2. 三数取中法:选3个数(最左、最右、中间)把中间大小的数和第一个数交换

归并排序

7.归并排序

图解:

思想:如果数组只有一个数,那么肯定有序。归并排序,先将数组分解为最小子序列,再俩俩合并,将问题转化为合并两个有序序列。这里同快排都用到了 分治思想(先放个连接回头聊).

// 7.归并排序
public static void mergeSort(int[] array) {mergeSortInternal(array, 0, array.length - 1);
}
// 递归
private static void mergeSortInternal(int[] array, int low, int high) {// 结束条件if (low >= high) return;int mid = (low + high) / 2;// 递归左右两部分mergeSortInternal(array, low, mid);mergeSortInternal(array, mid + 1, high);// 合并merge(array, low, mid, high);
}
// 合并两个有序数组 数组1:[low, mid]  数组2:[mid+1, high]
private static void merge(int[] array, int low, int mid, int high) {// 两个区间的起始位置int s1 = low;int s2 = mid + 1;// 归并结果接受数组int tmp[] = new int[high - low + 1];int k = 0;// s1 和 s2都不出界时,对比较小的数放在前边while (s1 <= mid && s2 <= high) {if (array[s1] <= array[s2]) {tmp[k++] = array[s1++];}if (array[s2] <= array[s1]) {tmp[k++] = array[s2++];}}// 两个区间剩余部分,添加到后边即可while (s1 <= mid) {tmp[k++] = array[s1++];}while (s2 <= high) {tmp[k++] = array[s2++];}// 拷贝回原数组for (int i = 0; i < tmp.length; i++) {// 注意拷贝的区间是 low 开始array[low + i] = tmp[i];}
}

归并排序对数据不敏感,时间复杂度和空间复杂度固定不变
时间复杂度:类比快排,O(n * log(n))
空间复杂度: O(n)
稳定性:稳定

归并排序时间复杂度和快排相似,空间复杂度又不如快排,归并排序就不如快排吗?
非也,归并排序可用于解决海量数据的排序问题(外部排序)
场景:内存只有 1G,需要排序的数据有 100G

因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
  3. 进行 200 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

稳定性

两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则我们称该算法是具备稳定性的排序算
法。

快排和归并排序的非递归实现

快排:

public static void quickSort2(int[] array) {Stack<Integer> stack = new Stack<>();int low = 0;int high = array.length - 1;int piv = pivot(array, low, high);if (piv > low + 1) {  //判断左边至少有两个元素stack.push(low);stack.push(piv - 1);}if (piv < high - 1) {stack.push(piv + 1);stack.push(high);}while (!stack.empty()) {high = stack.pop();low = stack.pop();piv = pivot(array, low, high);if (piv > low + 1) {stack.push(low);stack.push(piv - 1);}if (piv < high - 1) {stack.push(piv + 1);stack.push(high);}}
}

归并排序:

public static void mergeSort1(int[] array) {for (int i = 1; i < array.length; i *= 2) {merge1(array, i);}
}
private static void merge1(int[] array, int gap) {int s1 = 0;int e1 = s1 + gap - 1;int s2 = e1 + 1;int e2 = s2 + gap - 1 < array.length ? s2 + gap - 1 : array.length - 1;  // 防止越界int[] tmp = new int[array.length];int k = 0;while (s2 < array.length) {   // 有两个归并段while (s1 <= e1 && s2 <= e2) {if (array[s1] <= array[s2]) {tmp[k++] = array[s1++];}if (array[s2] <= array[s1]) {tmp[k++] = array[s2++];}}while (s1 <= e1) {tmp[k++] = array[s1++];}while (s2 <= e2) {tmp[k++] = array[s2++];}s1 = e2 + 1;e1 = s1 + gap - 1;s2 = e1 + 1;e2 = s2 + gap - 1 < array.length ? s2 + gap - 1 : array.length - 1;}while (s1 < array.length) {tmp[k++] = array[s1++];}for (int i = 0; i < tmp.length; i++) {array[i] = tmp[i];}}

七大排序的 java 实现和理解相关推荐

  1. 最详细的排序解析,理解七大排序

    最详细的排序解析,理解七大排序 mp.weixin.qq.com 点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 注: lgN在这里为1og2N简 ...

  2. Java排序(七大排序合集)

    七大排序 1.冒泡排序 1.1.排序过程图 1.2.排序思想 1.3.排序代码 1.4.代码改进 2.选择排序 2.1.排序过程图 2.2.排序思想 2.3.排序代码 2.4.代码改进--双向选择排序 ...

  3. 七大排序的个人总结(一)

    今天花了点时间把七个常见的内部排序重新复习了一遍,总结一下,也算是验证一下自己有没有真正理解. 冒泡排序(Bubble Sort): 很多人听到排序第一个想到的应该就是冒泡排序了.也确实,冒泡排序的想 ...

  4. 七大排序算法的个人总结(一)

    冒泡排序(Bubble Sort): 很多人听到排序第一个想到的应该就是冒泡排序了.也确实,冒泡排序的想法非常的简单:大的东西沉底,汽泡上升.基于这种思想,我们可以获得第一个版本的冒泡: public ...

  5. 几种经典的数据排序及其Java实现

    选择排序 思想 n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果: ①初始状态:无序区为R[1..n],有序区为空. ②第1趟排序 在无序区R[1..n]中选出关键字最小的记录R[k ...

  6. Java 多线程 —— 深入理解 volatile 的原理以及应用

    转载自  Java 多线程 -- 深入理解 volatile 的原理以及应用 推荐阅读:<java 多线程-线程怎么来的> 这一篇主要讲解一下volatile的原理以及应用,想必看完这一篇 ...

  7. 七大排序的个人总结(二) 归并排序(Merge

    七大排序的个人总结(二) 归并排序(Merge  归并排序(Merge Sort): 归并排序是一个相当"稳定"的算法对于其它排序算法,比如希尔排序,快速排序和堆排序而言,这些算法 ...

  8. java中的排序方法,Java中的排序比较方式:自然排序和比较器排序

    这里所说到的Java中的排序并不是指插入排序.希尔排序.归并排序等具体的排序算法.而是指执行这些排序算法时,比较两个对象"大小"的比较操作.我们很容易理解整型的 i>j 这样 ...

  9. 万字手撕七大排序(代码+动图演示)

    万字拿捏七大排序 1.排序的概念及其运用 1.1排序的概念 1.2 排序的运用 1.3 常见的排序算法 2. 常见排序算法的实现 2.1 插入排序 2.1.1 基本思想 2.1.2直接插入排序 2.1 ...

  10. Java集合排序及java集合类详解

    Java集合排序及java集合类详解 (Collection, List, Set, Map) 摘要内容 集合是Java里面最常用的,也是最重要的一部分.能够用好集合和理解好集合对于做Java程序的开 ...

最新文章

  1. 修改AspNetSqlMembershipProvider的密码规则
  2. 【特征工程】17种将离散特征转化为数字特征的方法
  3. [HAOI2014]贴海报
  4. AngularJS之watch
  5. E - Another Postman Problem FZU - 2038
  6. GOF之结构型模式Ⅱ(重点)
  7. 使用jxl来读取Excel中的数据
  8. 2021安徽高考成绩及录取结婚查询,2020安徽高考录取结果查询时间及通知书发放时间...
  9. myeclipse 的 restart server和Redeploy/Reload application的区别
  10. ssm房屋租赁管理系统ssm房屋管理系统JSP网上租房系统JSP房产信息网站房屋租赁系统房屋
  11. Java 在Word中创建多级项目符号列表和编号列表
  12. 中国好同事!帮程序猿跟姑娘表白,他们组了一支乐队
  13. IOM计算机组成原理,计算机组成原理设计教案.doc
  14. 极限编程-拥抱变化阅读感想(二)
  15. Spring Boot 2.0 配置图文教程 1
  16. for 循环语句基本用法及示例
  17. JAVA程序填空题用公式求e_Java 使用对象 编程练习题
  18. 阿里云ECS端口无法访问问题解决
  19. ViewPager+Fragment刷新更新Fragment
  20. 1001系列之pandas0001如何从Mysql数据库中导入导出数据

热门文章

  1. NFC:Arduino、Android与PhoneGap近场通信
  2. 独辟蹊径品内核 轻松领悟读书高境界
  3. 5.5 tensorflow2实现多项式回归与神经网络、未来一个月购买量预测——python实战
  4. jupyter notebook 内核挂掉
  5. python上下文管理器ContextLib及with语句
  6. keras 多GPU训练,单GPU权重保存和预测
  7. Vue中无法更改element ui组件样式问题
  8. Ubuntu解决依赖关系问题
  9. Google浏览器 — 取出图片颜色值
  10. Linux执行source /etc/profile报错“:command not found”