文章目录
一.排序概念
- 稳定排序:相同元素排序前后位置不变
- 不稳定排序:相同元素排序前后位置改变
- 内排序:待排序列完全放在内存中的排序
- 外排序:排序过程中还需要访问外存储器的排序
二.直接插入排序
1.基本思想
把n个待排序的元素看成一个有序表和一个无序表,开始时有序表中只有一个元素,无序表中含有n-1个元素,排序过程中每次从无序表中取出一个元素,将其与有序表中的元素比较,插入到有序表中的合适位置
2.直接插入排序举例
初始状态 | (17) 3 25 14 20 9 |
第一次插入 | (3 17) 25 14 20 9 |
第二次插入 | (3 17 25) 14 20 9 |
第三次插入 | (3 14 17 25) 20 9 |
第四次插入 | (3 4 17 20 25)9 |
第五次插入 | (3 9 14 17 20 25) |
3.代码实现
public static void sort(int arr[])
{
int temp;
for(int i=0;i<arr.length-1;i++)//进行n-1次插入
{
for(int j=i+1;j>0;j--)
if(arr[j]<arr[j-1])//后面的元素是否小于前面的元素
{
temp=arr[j];//交换
arr[j]=arr[j-1];
arr[j-1]=temp;
}
else//没有发生交换就说明当前有序
break;
}
}
//时间复杂度O(N*N)
//稳定
三、希尔排序
1.基本思想
先将整个待排无序序列分割成若干个子序列(相隔d增量),对子序列分别进行直接插入排序,待各个子序列中的元素基本有序,再对全体元素进行一次直接插入排序,因为直接插入排序在元素基本有序的情况下,效率较高
2.希尔排序举例
3.代码实现
public static void sort(int arr[])
{
int n=arr.length;
int h=1;
while (h<n/3)
h=3*h+1;//1 4 13 40 121...步长
while (h>=1)
{
//i=h表示第一组的最后一个元素
//i=h+1表示第二组的最后一个元素
for(int i=h;i<n;i++)
{
for(int j=i;j>=h;j-=h)//同一组前面的元素比后面的元素小,则交换
if(arr[j]<arr[j-h])//组内元素间隔h
{
int temp=arr[j];
arr[j]=arr[j-h];
arr[j-h]=temp;
}
}
h/=3;
}
}
//时间复杂度大致未O(n^1.3)
//不稳定
四、冒泡排序
1.基本思想
对待排列元素从前向后,依次比较相邻元素,若发现逆序则交换,使得较大(较小)的元素从数组前部向后部移动,如果一趟比较下来没有进行交换,则说明数组元素已经有序,每一次都能确定一个元素的最终位置
2.冒泡排序举例
初始序列 | 3 4 1 2 5 | |
第一趟冒泡 | 3 1 2 4 5 | |
第二趟冒泡 | 1 2 3 4 5 | |
第三趟冒泡 | 1 2 3 4 5 | 发现没有交换 停止排序 |
3.代码实现
public static void sort(int arr[])
{
boolean isOrder;
for(int i=0;i<arr.length-1;i++)//n-1趟
{
isOrder=true;
for(int j=0;j<arr.length-1-i;j++)//每一趟的比较次数都在减小
{
if(arr[j]>arr[j+1])
{
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
isOrder=false;
}
}
if(isOrder)
break;
}
}
//时间复杂度O(N*N)
//稳定
五、快速排序
1. 基本思想
任取待排序列中的任意元素作为标准,一般取第一个元素,通过一次划分,将待排元素分成左右两个子序列,左边序列小于基准元素,右边序列大于基准元素,然后对两个子序列重复执行上述操作,直到每一个序列只有一个元素为止
2.快速排序举例
3.代码实现
//以第一个元素为基准
public static void quickSort(int arr[],int lo,int hi)
{
if(hi<=lo)
return;
int j=partition(arr,lo,hi);//j左边的元素不大于a[j],j右边的元素不小于a[j]
quickSort(arr,lo,j-1);
quickSort(arr,j+1,hi);
}
public static int partition(int arr[],int lo,int hi)
{
int i=lo;
int j=hi+1;
int v=arr[lo];//切分的元素
while (true)
{
//保证切分元素左边的元素都比它小,右边的元素都比它大
while (arr[++i]<v)//查找比切分元素大的元素a
if(i==hi)
break;
while (arr[--j]>v)//查找比切分元素小的元素b
if(j==lo)
break;
if(i>=j)//左右扫描指针相等或左指针跑到右指针前面,说明扫描结束
break;
swap(arr,i,j);//交换前面找到的a b
}
swap(arr,lo,j);//最后交换切分元素,将切分元素放到指定的位置
return j;
}
public static void swap(int arr[],int i,int j)
{
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
//最好情况 O(nlogn)
//最坏情况 O(n*n)
//平均情况 O(nlogn)
//不稳定
//以最后一个元素为基准元素
public static void quickSort(int arr[],int lo,int hi)
{
if(hi<=lo)
return;
int j=partition(arr,lo,hi);//j左边的元素不大于a[j],j右边的元素不小于a[j]
quickSort(arr,lo,j-1);
quickSort(arr,j+1,hi);
}
public static int partition(int arr[],int lo,int hi)
{
int i=lo-1;
int j=hi;
int v=arr[hi];//切分的元素
while (true)
{
//保证切分元素左边的元素都比它小,右边的元素都比它大
while (arr[++i]<v)//查找比切分元素大的元素a
if(i==hi)
break;
while (arr[--j]>v)//查找比切分元素小的元素b
if(j==lo)
break;
if(i>=j)//左右扫描指针相等或左指针跑到右指针前面,说明扫描结束
break;
swap(arr,i,j);//交换前面找到的a b
}
swap(arr,hi,i);//最后交换切分元素,将切分元素放到指定的位置
return i;
}
public static void swap(int arr[],int i,int j)
{
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
六、简单选择排序
1. 基本原理
将待排序的元素分为已排序和未排序两组,已排序组初始为空,依次将未排序的元素中的最小值放入已排序的数组中,先找最小的加入,再找次小的加入,再找第三小的加入…
2.简单选择排序举例
3.代码实现
public static void sort(int arr[])
{
for(int i=0;i<arr.length-1;i++)
{
int minNum=arr[i];//假设当前的数最小
int minIndex=i;//假设当前索引是最小数的索引
for(int j=i+1;j<arr.length;j++)
if(minNum>arr[j])
{
minNum=arr[j];
minIndex=j;
}
if(minIndex!=i)//最小的数和刚开始假设的不是同一个数
{
int temp=arr[i];
arr[i]=arr[minIndex];
arr[minIndex]=temp;
}
}
}
//时间复杂度O(N*N)
//不稳定
七、堆排序
1. 堆的概念
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 图b 用来升序
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] 图a 用来降序
2.堆排序基本思想
将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
- 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
- b.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端
- 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序
3.堆排序代码实现
public static void sort(int []arr){
//1.构建大顶堆
for(int i=arr.length/2-1;i>=0;i--){
//从第一个非叶子结点从下至上,从右至左调整结构
adjustHeap(arr,i,arr.length);
}
//2.调整堆结构+交换堆顶元素与末尾元素
for(int j=arr.length-1;j>0;j--){
swap(arr,0,j);//将堆顶元素与末尾元素进行交换
adjustHeap(arr,0,j);//重新对堆进行调整
}
}
/**
* 调整大顶堆(仅是调整过程,建立在大顶堆已构建的基础上)
* @param arr
* @param i
* @param length
*/
public static void adjustHeap(int []arr,int i,int length){
int temp = arr[i];//先取出当前元素父节点i
for(int k=i*2+1;k<length;k=k*2+1){//从i结点的左子结点开始,也就是2i+1处开始
if(k+1<length && arr[k]<arr[k+1]){//如果左子结点小于右子结点,k指向右子结点
k++;
}
if(arr[k] >temp){//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
arr[i] = arr[k];
i = k;
}else{
break;
}
}
arr[i] = temp;//将temp值放到最终的位置,较小的子节点的位置
}
/**
* 交换元素
* @param arr
* @param a
* @param b
*/
public static void swap(int []arr,int a ,int b){
int temp=arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
//时间复杂度 O(nlogn)
//不稳定
八、归并排序
1.基本思想
将两个有序表合成一个有序表,需要用一个临时数组来保存合并两个有序序列后的新序列
2. 归并排序举例
3.代码实现
public static void merge(int arr[],int temp[],int lo,int mid,int hi)
{
int i=lo;
int j=mid+1;
int t=0;
while (i<=mid&&j<=hi)
{
if(arr[i]<arr[j])//如果左边的有序序列的当前元素小于右边有序序列的当前元素
temp[t++]=arr[i++];
else//如果左边的有序序列的当前元素大于右边有序序列的当前元素
temp[t++]=arr[j++];
}
while (i<=mid)//左半边元素未用尽
temp[t++]=arr[i++];
while (j<=hi)//右半边元素未用尽
temp[t++]=arr[j++];
t=0;
int k=lo;
while (k<=hi)//将两个有序序列合并在一起的temp数组重新赋值给arr数组
arr[k++]=temp[t++];
}
public static void mergeSort(int arr[],int temp[],int lo,int hi)
{
if(hi<=lo)
return;
int mid=(lo+hi)/2;
mergeSort(arr,temp,lo,mid);//对左半部分进行排序
mergeSort(arr,temp,mid+1,hi);//对右半部分进行排序
merge(arr,temp,lo,mid,hi);//开始合并左半部分和右半部分
}
}
//时间复杂度O(nlogn)
//稳定
九、基数排序
1.基本思想
将数组中的所有数按位进行分类,由于每一位数的大小都在0-9之间,因此创建下标为0~9的十个数组,根据需要对数进行存储
2.基数排序举例
11 4 12 13 10 5 21 3
先考虑个位
0号数组 | 1号数组 | 2号数组 | 3号数组 | 4号数组 | 5号数组 | 6号数组 | 7号数组 | 8号数组 | 9号数组 |
10 | 11 | 12 | 3 | 4 | 5 | ||||
21 | 13 |
考虑十位
0号数组 | 1号数组 | 2号数组 | 3号数组 | 4号数组 | 5号数组 | 6号数组 | 7号数组 | 8号数组 | 9号数组 |
3 | 10 | 21 | |||||||
4 | 11 | ||||||||
5 | 12 | ||||||||
13 |
- 先按个位数的大小将数组中的各个元素放入对应的桶中
- 再按十位数的大小将数组中的各个元素放入对应的桶中,依此类推
- 最大数字有几位,就需要进行几次排序
- 用于处理数组元素是整数的情况
- 如果需要处理负数需要作相应处理
- 对于给定的n个d位数,基数是r(十位数r=10),我们使用计数排序比较元素的每一位,基数排序耗时Θ(n+r),那么基数排序的复杂度为Θ(d(n+r))。空间复杂度O(rn+r):桶的个数桶的容量+基数(桶的个数)
3.代码实现
public static void radixSort(int arr[])
{
//先求出数组元素中最大数的位数
int max=arr[0];
for(int i=0;i<arr.length;i++)
{
if(arr[i]>max)
max=arr[i];
}
int maxLength=(max+" ").length();//最大数字的位数,转化为字符串求长度
int bucket[][]=new int[10][arr.length];//创建10个桶,每个桶的大小是原数组中元素的个数
//第一维是表示数字0-9 第二维是表示相应基数的元素的个数
int[] bucketElementCounts = new int[10];//记录每个桶中放的元素的个数
for(int i=0,n=1;i<maxLength;i++,n*=10)//最大数字的位数几位决定了需要进行几次排序
{
for(int j=0;j<arr.length;j++)
{
int num=arr[j]/n%10;//获取当前位置的数字
//n=1时获取个位数字 n=10时获取十位数字....
bucket[num][bucketElementCounts[num]++]=arr[j];//将对应位置的数字放进对应的桶中
}
int index=0;
for(int j=0;j<bucketElementCounts.length;j++)//j<10 0-9
{
if(bucketElementCounts[j]!=0)//当前桶中有元素
{
for(int k=0;k<bucketElementCounts[j];k++)
arr[index++]=bucket[j][k];//将j号桶中的k号元素重新放入原数组中
}
bucketElementCounts[j]=0;//清空j号桶
}
}
}