概述:
作为算法的鼻祖,八大排序是我们一定要了解学习的,废话不多说,直奔主题。
一、 直接插入排序
1.基本思想
直接插入排序的基本思想是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过为止。
2.算法描述
具体算法描述如下:
①从第一个元素开始,该元素被认为是已被排序
②取出下一个元素,在已排序元素序列中从后往前开始比较
③如果该比较的元素(已排序元素)大于新取出元素,则该元素移到下一位
④重复步骤③,直到找到比新取出元素小于或者等于的已排序元素
⑤将新取出元素插入该元素位置
⑥重复步骤②~③,直到数组排好序
3.算法实现
private static void insertionSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
//取出下一个新的元素,在已经排序的元素中从后开始比较
int temp = arr[i];
for (int j = i; j >= 0; j--) {
if (j > 0 && temp < arr[j - 1]) {
//如果该元素大于取出的元素,将该元素移动到下一个位置
arr[j] = arr[j - 1];
System.out.println("Temping:" + Arrays.toString(arr));
} else {
//如果该元素小于等于取出的元素,将元素插入到该位置
arr[j] = temp;
System.out.println("Sorting:" + Arrays.toString(arr));
break;
}
}
}
}
//交换次数较多的实现
private static void insertionSort1(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j > 0; j--) {
if (arr[j] > arr[j - 1]) {
break;//如果取出元素已经比该元素大,那就直接退出这次循环
}
//如果没有退出该循环,那就交换位置
int temp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = temp;
System.out.println("Sorting:" + Arrays.toString(arr));
}
}
}
二、希尔排序
- 第一个突破O(n^2)的排序算法;是简单插入排序的改进版;它与插入排序的不同之处在于,它会优先比较距离较远的元素。
- 希尔排序是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。
1.基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量主键递减,每组包含的关键词也来越多,当增量减至1时,整个文件恰被分成一组,算法终止。
2.算法描述
具体算法描述如下:
①用gap=arr.length/2 获得步长gap(增量)
②定义指针,指针指向j和j+gap元素
③如果当前元素大于加上步长之后的元素,则交换
3.算法实现
public static void shellSort(int[] arr) {
//获得步长gap,并且不断缩小步长gap的值,直到步长gap的值为1位置
for (int gap=arr.length/2;gap>0;gap/=2){
for (int i=gap;i<arr.length;i++){
//定义指针 j : 指向数组当中的每一个元素 默认 j 指向的是该组当中的第一个元素
// j -= gap :这里的 gap 值得是步长
for (int j=i-gap;j>=0;j-=gap){
// 如果当前元素大于加上步长之后的元素,则交换
if (arr[j]>arr[j+gap]){
int temp=arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
System.out.println("Sorting:"+Arrays.toString(arr));
}
}
}
}
}
三、冒泡排序
1.基本思想
对存放原始数组的数据,按照从前往后的方向进行多次扫描,每次扫描都称为一趟。当发现相邻两个数据的大小次序不符合时,即将这两个数据进行互换,如果从小大小排序,这时较小的数据就会逐个往前移动,好像气泡网上漂浮一样。
下面来看一组动态图
冒泡排序的特点:
升序排序当中每一轮比较会把最大的数沉到最底(这里以从小到大为例),所有相互比较的次数每一轮会比前一轮少一次。
2.算法描述
①比较相邻的两个元素,前一个比后一个大,则交换
②对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
③.针对所有的元素重复以上的步骤,除了最后一个。
④.持续每次对越来越少的元素重复上面的步骤①~③,直到没有任何一对数字需要比较。
3.算法实现
public static void bubbleSort(int[] arr) {
//外层循环移到
for (int i=arr.length - 1;i>0;i--){
//内层循环遍历
for (int j=0;j<i;j++){
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
System.out.println("Sorting:"+ Arrays.toString(arr));
}
}
}
}
四、快速排序
1.基本思想
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
2.算法描述
快速排序使用分治法来把一个串(名单)分为两个子串(子列表)具体算法描述如下:
①把数组当中一个数当基准数
②一般会把数组中最左边的数当成基准数,然后丛两边进行检索。丛右边检索比基准数小的,然后左边检索比基准数大的。如果检索到了,就停下,然后交换这两个元素,然后继续检索。
③递归地(递归)把小于基准值元素的子数列和大于基准值元素的子数列排序。
动态图:
①首先找到一个基准数 temp = 6
②先移动右边的 指针 j ,找到第一个小于 6 的数 也就是 5
·· 然后在移动 执行 i ,找到第一个大于 6 的数 也就是7
然后进行交换
③先移动右边的 指针 j ,找到第一个小于 6 的数 也就是 4
然后在移动 执行 i ,找到第一个大于 6 的数 也就是9
然后进行交换
④i 和 j 一旦相遇,就停止检索 ,把基准数和相遇位置为树进行交换
⑤第一次排序完毕,排序完成以后我们会发现排序的左边比基准数小,右边比基准数大
⑥以后先排基本数左边,排完之后再排基准数右边,方式和第一轮一样
3.算法实现
public class QuickSort {
public static void main(String[] args) {
int[] arr = new int[]{4, 6, 8, 2, 5, 7, 3, 9};
quickSort(arr,0,arr.length-1);
}
public static void quickSort(int[] arr, int left, int right) {
//判断左边的索引是否比右边的大(是否合法),否则继续,是则直接回return
if (left>right){
return;
}
//定义变量保存基准数
int base=arr[left];
//定义变量i,j指向最左边和最右边
int i=left;
int j = right;
//当i和j不相遇时,进行循环
while (i!=j){
//从右向左检索有没有比基准数小的数
while (arr[j]>=base &&i<j){
j--;//j向左移
}
//从左向右检索有没有比基准数大的数
while (arr[i]<=base &&i<j){
i++;//i向右移
}
//交换i和j的值
int temp=arr[i];
arr[i] = arr[j];
arr[j]=temp;
}
//交换基准数和ij相遇位置的值
arr[left]=arr[i];
arr[i]=base;
System.out.println(Arrays.toString(arr));
//下一步该排基准数的左边
quickSort(arr,left,i-1);
//下一步该排基准数的右边
quickSort(arr,j+1,right);
}
}
五、简单选择排序
1.基本思想
选择排序(select sorting)也是一种简单的排序方法。
它的基本思想是:第一次从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次,得到一个按排序码从小到大排列的有序序列。
2.算法描述
- 从未排序序列中,找到关键字最小的元素
- 如果最小元素不是未排序序列的第一个元素,将其和未排序序列第一个元素互换
- 重复1、2步,直到排序结束。
动画演示:
3.算法实现
public static void selectSort(int[] arr){
for (int i = 0; i < arr.length-1; i++){
//当前值作为最小值
int minIndex=i;
int min=arr[i];
for(int j=i+1;j<arr.length; j++){
//比较判断大小,如果找到比当前最小值还要小的,那更新记录最小值和最小值的下标
if (min>arr[j]){
min = arr[j];
minIndex=j;
}
}
//交换
arr[minIndex]=arr[i];
arr[i]=min;
System.out.println("第"+(i+1)+"轮排序:"+Arrays.toString(arr));
}
}
六、堆排序
1.基本思想
堆排序具有以下特点:
①:完全二叉树(从上到下,从左到右,每一层的节点都是满的,最下边一层所有的节点都是连续集中在最左边)。
②:二叉树每个结点的值都大于或者等于其左右孩子节点的值称之为大顶堆。
二叉树每个结点的值都小于或者等于其左右孩子节点的值称之为小顶堆。
(1) 若array[0,…,n-1]表示一颗完全二叉树的顺序存储模式,则双亲节点指针和孩子结点指针之间的内在关系如下:
任意一节点指针 i:父节点:i==0 ? null : (i-1)/2
左孩子:2*i + 1
右孩子:2*i + 2
(2)堆的定义:n个关键字序列array[0,…,n-1],当且仅当满足下列要求:(0 <= i <= (n-1)/2)
① array[i] <= array[2i + 1] 且 array[i] <= array[2i + 2]; 称为小根堆;
② array[i] >= array[2i + 1] 且 array[i] >= array[2i + 2]; 称为大根堆;
2.算法描述
第一步:构建大顶堆
①:初始化状态,右下角为下标
②: index = 5开始,发现他没有兄弟节点,并且父节点小于它,父节点的计算放方式为:
index = [arr.length - 1] /2
③:接下来index减1,那么index节点变为4,这时候他要和兄弟节点进行对比,发现它比兄弟节和父节点都大
那么他就和父节点机型交换。
④:接下来index减2,他有和兄弟节点进行对比,发现它比兄弟节点大,还比父节点大,那么它和父节点进行交换
⑤:交换完成之后,发现2号节点不再大于其子节点了,所有需要在交换一次
//选取孩子结点的左孩子结点,继续向下筛选
parent = lChild;
lChild = 2 * lChild + 1;
⑥:检查一下,已将满足最大的条件了,大顶堆已经构建完成
第二步:将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆
①:0号节点是最大值,我们将它与最后一个节点交换位置
②:此时的堆不是大顶堆需要重现构建
③:再次调整,index = 1 ,此时 下标为1的值为5,值大于左右子树,所以 index -1 ,此时值为1.然后需要进行交换,交换完成之后,发现1号节点不再大于其子节点了,所有需要在交换一次
④:交换后的结果是
⑤:0号节点是最大值,我们将它与最后一个节点交换位置
⑥:此时的堆不是大顶堆需要重现构建,此时 index = 1,1号位置大于其孩子节点的值,所以 index -1 ,此时index = 0,0号位置小于其左子树。所以进行交换
⑦:交换后的结果是
⑧:0号节点是最大值,我们将它与最后一个节点交换位置
⑨:此时的堆不是大顶堆需要重现构建,此时 index = 0,0号位置小于其孩子节点的值,左右子树进行计较之后发现右子树更大,所以和右子树进行交换。
⑩:交换后的结果是
(11)0号节点是最大值,我们将它与最后一个节点交换位置
(12):此时的堆不是大顶堆需要重现构建,此时 index = 0,0号位置小于其孩子节点的值,左右子树进行计较之后发现做子树更大,所以和右子树进行交换。
(13):交换后的结果是
(14)0号节点是最大值,我们将它与最后一个节点交换位置
最终我们就能得到最后的答案
3.算法实现
public static void heapSort(int[] arr) {
//创建堆
for (int i=(arr.length-1)/2;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);
}
}
public static void adjustHeap(int[] arr,int parent,int lengnth){
//将temp作为父节点
int temp=arr[parent];
//左孩子
int lChild=parent*2+1;
while (lChild<lengnth){
//右孩子
int rChild=lChild+1;
//如果有右孩子,并且右孩子节点的值大于左孩子节点的值,则选取右孩子节点
if (rChild<lengnth&&arr[lChild]<arr[rChild]){
lChild++;
}
//如果父节点的值大于孩子节点的值,则直接结束
if (temp>=arr[lChild]){
break;
}
//把孩子节点的值赋给父节点
arr[parent]=arr[lChild];
//选取孩子节点的左孩子节点,继续向下筛选
parent=lChild;
lChild=lChild*2+1;
}
arr[parent]=temp;
}
七、基数排序
1.基本思想
基数排序(Radix sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
基本思想: 将所有待比较数值统一为同样的位数长度,数位较短的数前边补零。然后,从最低位开始,依次进行一次排序,这样从最低位排序一直到最高位排序完成后,就变成一个有序数列。基数排序是使用空间换时间的经典算法
2.算法描述
- 取得数组中的最大数,并取得位数;
- arr为原始数组,从最低位开始取每个位组成radix数组;
- 对radix进行计数排序(利用计数排序适用于小范围数的特点);
动画表示:
3.算法实现
private static void radixSort(int[] arr) {
//得到数组当中最大数的位数
int max=0; //假设第一个数是最大数
for (int i = 0; i < arr.length; i++){
if (max<arr[i]){
max = arr[i];
}
}
//得到最大数的位数
int maxLength=(max+"").length();
//定义一个二位数组,表示10个桶,每个桶就是一个一位数组
int[][] brr=new int[10][arr.length];
//为了记录每个桶当中实际存放了多少数据,我们定义一个一维数组来记录每个桶每次被放入数据的个数
int[] crr=new int[10];
//定义能够针对这个位的除数
int n=1;
//第一轮,针对每个元素的各位进行处理
for (int h=0;h<maxLength;h++){
for (int i = 0; i < arr.length; i++){
//取出每个元素个位的值
int temp=arr[i]/n%10;
//放入对应的桶中
brr[temp][crr[temp]]=arr[i];
crr[temp]++;
}
//按照这个桶的顺序,一维数组的下标一次取出数据,放入原来的数组
int index=0;
//遍历每个桶,并将桶当中的数据放入原数组
for (int k=0;k<crr.length; k++){
//如果桶当中有数据我们才放入到原数组中
if (crr[k]!=0){
//循环遍历,将桶当中的数据放入
for (int i = 0; i <crr[k];i++){
arr[index++]=brr[k][i];
}
}
//每一轮处理完后crr置零
crr[k]=0;
}
n=n*10;
}
}
八、归并排序
1.基本思想
归并排序就是递归得将原始数组递归对半分隔,直到不能再分(只剩下一个元素)后,开始从最小的数组向上归并排序。
归并排序算法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
2.算法描述
- 将一个数组拆分为两个,从中间点拆开,通过递归操作来实现一层一层拆分。
- 从左右数组中选择小的元素放入到临时空间,并移动下标到下一位置。
- 重复步骤2直到某一下标达到尾部。
- 将另一序列剩下的所有元素依次放入临时空间。
- 将临时空间的数据依次放入原数据数组。
动态图:
3.算法实现
public static void mergeSort(int[] arr,int low,int high){
//首先判断low和high是否指向一个地方
if (low>=high){
return;
}
int mid=(low + high)/2;
//先递归左边
mergeSort(arr,low,mid);
//再递归右边
mergeSort(arr,mid+1,high);
//合并
merge(arr,low,mid,high);
}
//合并
public static void merge(int[] arr, int low, int mid,int high){
//第一段的开始
int s1=low;
//第二段的开始
int s2=mid+1;
//定义临时数组
int[] temp=new int[high-low+1];
int i=0; //定义临时数组的下标
//判断大小,将数据放入临时数组
while (s1<=mid&&s2<=high){
if (arr[s1]<=arr[s2]){
temp[i++]=arr[s1++];
}else {
temp[i++]=arr[s2++];
}
}
//判断s1当中是否有数据,如果有如果有将其全部拷贝到临时数组当中
while (s1<=mid){
temp[i++] = arr[s1++];
}
//判断s1当中是否有数据,如果有如果有将其全部拷贝到临时数组当中
while (s2<=high){
temp[i++] = arr[s2++];
}
//将临时数组当中的数据放回原数组
for (int j=0;j<temp.length; j++){
arr[j+low]=temp[j];
}
}