目录

  • 1. 交换排序——冒泡排序
  • 2. 交换排序——快速排序
  • 3. 选择排序——简单选择排序
  • 4. 选择排序——堆排序
    • 什么是堆
    • 堆排序基本思想
    • 步骤图解
    • 代码实现
  • 5. 插入排序——简单插入排序
  • 6. 插入排序——希尔排序
  • 7. 归并排序
  • 8. 基数排序

1. 交换排序——冒泡排序

从要排序序列的第一个元素开始,一次比较相邻元素的值,发现逆序则交换,将值较大的元素逐渐从前向后移动。

public void bubbleSort(int[] arr){for (int i = 0; i < arr.length - 1; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j + 1]) {int t = arr[j];arr[j] = arr[j + 1];arr[j + 1] = t;}}}
}

优化:如果某次排序中,没有发生交换,则可结束排序

public void bubbleSort(int[] arr){boolean flag = false;//表示没有发生过交换for (int i = 0; i < arr.length - 1; i++) {for (int j = 0; j < arr.length - 1 - i; j++) {if (arr[j] > arr[j + 1]) {int t = arr[j];arr[j] = arr[j + 1];arr[j + 1] = t;flag = true;//发生交换}}if (!flag)break;elseflag = false;//重置flag,进行下次判断}
}
  • 时间复杂度:O(n^2)
  • 稳定

2. 交换排序——快速排序

快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

public static void quickSort(int[] arr, int left, int right) {int l = left;int r = right;int pivot = arr[(left + right) / 2];//while循环是为了让比pivot小的值放其左边,比pivot大的值放其右边while (l < r) {while (arr[l] < pivot)//直到找到比pivot小的数l++;while (arr[r] > pivot)//直到找到比pivot大的数r--;if (l >= r)//说明比pivot小的值都在其左边,比pivot大的值都在其右边break;//找到pivot左边比其大的元素,右边比其小的元素;进行交换int t = arr[l];arr[l] = arr[r];arr[r] = t;}//如果l==r,则要l++、r--;否则会出现栈溢出if (l == r) {l++;r--;}//向左递归if (left < r)quickSort(arr, left, r);//向右递归if (right > l)quickSort(arr, l, right);
}
  • 时间复杂度:O(nlog2n)
  • 不稳定

3. 选择排序——简单选择排序

基本思想

  • 第一次从 arr[0]~arr[n-1]中选取最小值,与 arr[0]交换
  • 第二次从 arr[1]~arr[n-1]中选取最小值,与 arr[1]交换
  • 第三次从 arr[2]~arr[n-1]中选取最小值,与 arr[2] 交换…
  • 第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换…
  • 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值,与 arr[n-2]交换

总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列

public void selectSort(int[] arr){for (int i = 0; i < arr.length - 1; i++) {int min = arr[i];//假设当前值最小int minIndex = i;//记录最小值的索引for (int j = i + 1; j < arr.length; j++) {if (arr[j] < min) {min = arr[j];minIndex = j;}}if (minIndex != i) {arr[minIndex] = arr[i];arr[i] = min;}}
}
  • 时间复杂度:O(n^2)
  • 不稳定

4. 选择排序——堆排序

堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。

什么是堆

堆是具有以下性质的完全二叉树

1️⃣ 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆(没有要求结点的左孩子的值和右孩子的值的大小关系)

arr[i] >= arr[2*i+1] && arr[i] >= arr[2*i+2]  //i对应第几个节点,i从0开始编号

我们对堆中的结点按层进行编号,映射到数组中就是下面这个样子:

2️⃣ 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆

arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2]  //i对应第几个节点,i从0开始编号

堆排序基本思想

  1. 将待排序序列构造成一个大顶堆
  2. 此时,整个序列的最大值就是堆顶的根节点。
  3. 将其与末尾元素进行交换,此时末尾就为最大值。
  4. 然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。

一般升序采用大顶堆,降序采用小顶堆

步骤图解

步骤一:构造初始堆,将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)

原始的数组 [4, 6, 8, 5, 9]

  1. 假设给定无序序列结构如下

  1. 此时我们从最后一个非叶子结点开始(叶结点不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整

  1. 找到第二个非叶节点 4,由于[4,9,8]中 9 元素最大,4 和 9 交换

  1. 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中 6 最大,交换 4 和 6

此时,我们就将一个无序序列构造成了一个大顶堆

步骤二:将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换

  1. 将堆顶元素 9 和末尾元素 4 进行交换

  1. 重新调整结构,使其继续满足堆定义

  1. 再将堆顶元素 8 与末尾元素 5 进行交换,得到第二大元素 8

  1. 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序

代码实现

import java.util.Arrays;public class HeapSort {public static void main(String[] args) {int[] arr = {3, 5, 2, 7, 8, 0, -1, 99};heapSort(arr);System.out.println(Arrays.toString(arr));}//堆排序public static void heapSort(int[] arr) {//将无序序列构建成一个堆for (int i = arr.length / 2 - 1; i >= 0; i--) {adjustHeap(arr, i, arr.length);}//将堆顶元素和末尾元素交换,将最大元素放置数组末端//重新调整至堆结构,然后继续将堆顶元素和当前末尾元素交换,以此往复for (int i = arr.length - 1; i > 0; i--) {int temp = arr[i];arr[i] = arr[0];arr[0] = temp;adjustHeap(arr, 0, i);}}/*** 将二叉树调整为堆** @param arr    待调整的数组* @param i      表示非叶子结点在数组中索引* @param length 表示对多少个元素继续调整,length逐渐减少*/public static void adjustHeap(int[] arr, int i, int length) {int temp = arr[i];//k=2i+1是i的左子节点for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {if (k + 1 < length && arr[k] < arr[k + 1])//左子节点的值<右子节点的值k++;//指向右节点if (arr[k] > temp) {//如果子结点的值>父节点的值arr[i] = arr[k];//将较大的值赋给当前节点i = k;//i指向k,继续循环比较} elsebreak;}//for循环后,已经将以i为父结点的树的最大值,放在了顶部arr[i] = temp;//将temp值放到调整后的位置}
}

5. 插入排序——简单插入排序

基本思想:把 n 个待排序的元素看成为一个有序表和一个无序表,开始时 有序表 中只包含一个元素,无序表中包含有 n-1 个元素,排序过程中每次从无序表中取出第一个元素,与有序表中的元素进行比较,将它插入到有序表中的适当位置,使之成为新的有序表

public void InserSort(int[] arr){for (int i = 1; i < arr.length; i++) {int insertVal = arr[i];//待插入左边有序表的数int left_index = i - 1;//左边有序表的索引,初值为有序表最右数的索引while (left_index >= 0 && insertVal < arr[left_index]) {//不断遍历左边的有序表进行比较arr[left_index + 1] = arr[left_index];//比较过的值右移left_index--;//索引-1,继续比较}if (left_index + 1 != i)arr[left_index + 1] = insertVal;}
}
  • 时间复杂度:O(n^2)
  • 稳定

6. 插入排序——希尔排序

简单的插入排序可能存在的问题:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响

# 比如数组 arr = {2,3,4,5,6,1} 这时需要插入的数 1(最小),这样的过程是
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}

为了改进,提出了希尔排序算法:

希尔排序是希尔(Donald Shell)于 1959 年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序

//交换式
public void shellSort(int[] arr){for(int gap=arr.length/2;gap>0;gap/=2){   //步长gap逐次递减for(int i=gap;i<arr.length;i++){for(int j=i-gap;j>=0;j-=gap){if(arr[j]>arr[j+gap]){int t=arr[j];arr[j]=arr[j+gap];arr[j+gap]=t;}}}}
}//改进成移位法
public void shellSort(int[] arr){for (int gap = arr.length / 2; gap > 0; gap /= 2) {//步长gap逐次递减for (int i = gap; i < arr.length; i++) {int left_index = i - gap;//左边有序表的索引,初值为有序表最右数的索引int insertVal = arr[i];//要插入的值while (left_index >= 0 && insertVal < arr[left_index]) {arr[left_index + gap] = arr[left_index];//比较过的值右移left_index -= gap;}arr[left_index + gap] = insertVal;}}
}
  • 时间复杂度:O(nlog2n)
  • 不稳定

7. 归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治策略(divide-and-conquer)

  • 分治法将问题分(divide)成一些小的问题然后递归求解
  • 而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之

再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤

  • 左边序列和右边序列分别有一个索引指向第一个元素,然后进行比较,较小的元素存入一个临时数组temp,较小元素边序列索引右移,以此往复不断比较存入,直到一边的索引走到该子序列的最后;然后将有剩余数据的序列的剩余值直接按序存入temp数组中;

  • 最后所有元素都存入临时数组temp中,此时temp数组中的元素有序

  • 最后将temp数组拷贝回原数组,即实现了排序

package test;import java.util.*;public class Main {public static void main(String[] args) {int[] arr = {8, 10, -1, 6, 7, 3, 0, 40, 70};int[] temp = new int[arr.length];//归并排序需要额外的空间mergeSort(arr, 0, arr.length - 1, temp);System.out.println(Arrays.toString(arr));}//分+合方法public static void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = (left + right) / 2;//中间索引mergeSort(arr, left, mid, temp);//向左递归分解mergeSort(arr, mid + 1, right, temp);//向右递归分解merge(arr, left, mid, right, temp);//合并}}//合并方法public static void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left;//i为指向左边序列第一个元素的索引int j = mid + 1;//j为指向右边序列第一个元素的索引int t = 0;//指向临时temp数组的当前索引//先把左右两边有序数据按规则存入temp数组中,直到有一边的数据全部填充temp中while (i <= mid && j <= right) {if (arr[i] <= arr[j]) {temp[t] = arr[i];t++;i++;} else {temp[t] = arr[j];t++;j++;}}//将有剩余数据的一边全部存入temp中while (i <= mid) {//左边序列有剩余元素temp[t] = arr[i];t++;i++;}while (j <= right) {//右边序列有剩余元素temp[t] = arr[j];t++;j++;}/*** 将temp中的元素拷贝到arr中*  注意:不是每次都拷贝全部元素*  第一次:tempLeft=0,right=1*  第二次:tempLeft=2,right=3*  第三次:tempLeft=0,right=3*/t = 0;int tempLeft = left;while (tempLeft <= right) {arr[tempLeft] = temp[t];t++;tempLeft++;}}
}
  • 时间复杂度:O(nlog2n)
  • 稳定

8. 基数排序

  • 基数排序radix sort属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些“桶”中,达到排序的作用
  • 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法
  • 基数排序(Radix Sort)是桶排序的扩展
  • 基数排序是 1887 年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。

基数排序基本思想

将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。 这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

package test;import java.util.Arrays;public class Main {public static void main(String[] args) {int[] arr = {8, 10, 6, 7, 3, 0, 40, 70};radixSort(arr);System.out.println(Arrays.toString(arr));}public static void radixSort(int[] arr) {//定义一个二维数组,表示10个桶,每一个桶就是一个一维数组int[][] bucket = new int[10][arr.length];//可能每个桶不会用满,所以基数排序是使用空间换时间的经典算法//定义一个一维数组记录每个桶每次放入数据的个数int[] bucketCounts = new int[10];//找到数组中最大数int max = arr[0];for (int i = 0; i < arr.length; i++) {if (arr[i] > max)max = arr[i];}//获得最大数的位数,即为循环的次数,也就是要判断的总位数(个、十、百、千)int maxLength = (max + "").length();//针对每个元素的位进行比较for (int c = 0, n = 1; c < maxLength; c++, n *= 10) {for (int i = 0; i < arr.length; i++) {//取出每个元素对应位的值(个位-->十位-->百位---)int digit = arr[i] / n % 10;//个位:%10  十位:/10再%10  百位:/100再%10//放入桶中bucket[digit][bucketCounts[digit]] = arr[i];bucketCounts[digit]++;}int index = 0;//遍历每个桶,将桶中的数据放回到原数组for (int j = 0; j < 10; j++) {if (bucketCounts[j] != 0) {//如果桶不为空for (int k = 0; k < bucketCounts[j]; k++) {arr[index] = bucket[j][k];index++;}}bucketCounts[j] = 0;//清空每个桶的数据}}}
}

负数问题:在找最大数的同时找最小值 如果最小值小于0就给每个值加上最小值的相反数,再比较

  1. 基数排序是对传统桶排序的扩展,速度很快
  2. 基数排序是经典的空间换时间的方式,占用内存很大,当对海量数据排序时,容易造成OutOfMemoryError
  3. 基数排序时稳定的
  4. 有负数的数组,我们不用基数排序来进行排序,如果要支持负数,参考: https://code.i-harness.com/zh-CN/q/e98fa9

数据结构——八大排序算法(面试必备)相关推荐

  1. 【数据结构排序算法系列】数据结构八大排序算法

    排序算法在计算机应用中随处可见,如Windows操作系统的文件管理中会自动对用户创建的文件按照一定的规则排序(这个规则用户可以自定义,默认按照文件名排序)因此熟练掌握各种排序算法是非常重要的,本博客将 ...

  2. [数据结构]八大排序算法总结

    作者: 华丞臧 专栏:[数据结构] 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注).如果有错误的地方,欢迎在评论区指出. 推荐一款刷题网站

  3. Python数据结构常见的八大排序算法(详细整理)

    前言 八大排序,三大查找是<数据结构>当中非常基础的知识点,在这里为了复习顺带总结了一下常见的八种排序算法. 常见的八大排序算法,他们之间关系如下: 排序算法.png 他们的性能比较: 下 ...

  4. 算法面试必备-----数据分析常见面试题

    算法面试必备-----数据分析常见面试题 算法面试必备-----数据分析常见面试题 1.统计学问题 问题:贝叶斯公式复述并解释应用场景 问题:朴素贝叶斯的理解 问题:参数估计 问题:极大似然估计 问题 ...

  5. 算法面试必备-----数据库与SQL面试题

    算法面试必备-----数据库与SQL面试题 算法面试必备-----数据库与SQL面试题 1.数据库理论问题 问题:什么是数据库,数据库管理系统,数据库系统,数据库管理员? 问题:什么是元组,码,候选码 ...

  6. python 排序算法 简书_Python---简析八大排序算法

    前言 1 .排序的概念 排序是计算机内经常进行的一种操作,其目的是将一组"无序"的记录序列调整为"有序"的记录序列. 排序分为内部排序和外部排序. 若整个排序过 ...

  7. 八大排序算法的 Python 实现

    八大排序算法的 Python 实现 本文用Python实现了插入排序.希尔排序.冒泡排序.快速排序.直接选择排序.堆排序.归并排序.基数排序. 1.插入排序 描述 插入排序的基本操作就是将一个数据插入 ...

  8. 八大排序算法图文讲解

    排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 常见的内部排序算法有:插入排序.希尔排序. ...

  9. C语言八大排序算法,附动图和详细代码解释!

    文章来源:电子工程专辑.C语言与程序设计.竹雨听闲 一.前言 如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二. ...

  10. 硬核!C语言八大排序算法,附动图和详细代码解释!

    来源 :C语言与程序设计.竹雨听闲等 一 前言 如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功. 想写出精炼.优秀的代码,不通过不断的锤炼,是很难做到的. 二 八大排序算法 ...

最新文章

  1. 数据库存储引擎大揭秘,不看不知道这里面的骚操作可真多!
  2. 干货 | 新手请速戳!30个精选SQL面试问题QA集锦
  3. php 安装oracle扩展,win PHP7安装oracle扩展
  4. C++编程练习(10)----“图的最小生成树“(Prim算法、Kruskal算法)
  5. js将canvas保存成图片并下载
  6. contains方法_【原创】Pandas数据处理系列(二):常用处理方法笔记
  7. Class yii\base\Exception
  8. 真实教育场景手写/表格/公式OCR数据集
  9. Ajax.Responders
  10. 《Windows via C/C++》学习笔记 —— 内核对象的“线程同步”之“信号量”
  11. Spark 和 Hadoop MapReduce 对比
  12. 斗鱼连接弹幕Demo_pythonC#
  13. 【OR】YALMIP Bilevel规划
  14. 腾讯微博qq说说备份导出工具_电竞和游戏火了,和它走得很近的腾讯微博却早已透心凉...
  15. 重要极限二:x趋近于无穷大,(1+1/x)^x的极限
  16. 「Git」常用工作流介绍
  17. c# 开发winform控件
  18. windows 的cmd设置代理方法
  19. 二维码如何转为链接,看了这篇文章才知道多好用!
  20. Redis和Mecahe的简介

热门文章

  1. Android开发——回调函数实例
  2. 多领域中文语音识别数据集 WenetSpeech 正式发布——有效下载教程
  3. java中面向对象的例子_java面向对象九个经典例子程序
  4. vs 编译nmake工程
  5. grads 风向杆值大小
  6. Linux dstat监控工具简讲
  7. Python 爬虫逆向破解案例实战 (二):STEAM密码加密 (RSA) 逆向
  8. Android中的Builder模式
  9. HP P1108打印机安装
  10. Ubuntu18.04下小米、TPLink、腾达USB无线网卡跳坑记录