第一部分:排序算法
基于交换的排序:冒泡排序、快速排序
基于插入的排序:直接插入排序、希尔排序
基于选择的排序:简单选择排序、堆排序
冒泡排序
public static int[] Pop(int[] numbers)
{
for(int i=0;i<numbers.length-1;i++)//控制轮数,共需要length-1次轮
{
for(int j=0;j<numbers.length-1-i;j++)//控制每轮的交换次数
{
if(numbers[j]>numbers[j+1])
{
int temp=numbers[j];
numbers[j]=numbers[j+1];
numbers[j+1]=temp;
}
}
}
return numbers;
}
时间复杂度:最好O(n)、最坏O(n2)、平均O(n2) 分析:当序列已经是排好序时,则只需要n-1次比较,无需移动元素,当序列为逆序时,则需要n(n-1)/2次比较和移动
空间复杂度:O(1)
稳定性:两个相邻的相等元素并不会发生交换,所以稳定
快速排序
//递归思想 传入参数,左边界,右边界及数组
public static void quickSort(int[] arr, int leftIndex, int rightIndex) {
if (leftIndex >= rightIndex) {
return;
}
int left = leftIndex;
int right = rightIndex;
//待排序的第一个元素作为基准值
int key = arr[left];
//从左右两边交替扫描,直到left = right
while (left < right) {
while (right > left && arr[right] >= key) {
//从右往左扫描,找到第一个比基准值小的元素
right--;
}
//找到这种元素将arr[right]放入arr[left]中
arr[left] = arr[right];
while (left < right && arr[left] <= key) {
//从左往右扫描,找到第一个比基准值大的元素
left++;
}
//找到这种元素将arr[left]放入arr[right]中
arr[right] = arr[left];
}
//基准值归位
arr[left] = key;
//对基准值左边的元素进行递归排序
quickSort(arr, leftIndex, left - 1);
//对基准值右边的元素进行递归排序。
quickSort(arr, right + 1, rightIndex);
}
时间复杂度:最好O(nlogn) 、最坏O(n^2) 平均O(nlogn) 分析:当基准值选取得当,每次都能将序列均匀划分,则能到O(nlogn),这里要用到递归算法的时间复杂度公式:T[N]=2T[N/2]+f(n)。如果基准值每次都是最大或者最小,则会将所有序列划分到一侧,为O(n^2)。
空间复杂度:O(nlogn)
稳定性:所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j] 交换的时刻
直接插入排序
public static int[] InsertSort(int[] a)
{
for(int i=1;i<a.length;i++)
{
int temp=a[i];
int j=i-1;
//将temp插入到合适位置
while(j>=0&&temp<a[j])
{
a[j+1]=a[j];
j--;
}
a[j+1]=temp;
}
return a;
}
时间复杂度:最好为O(n)、最坏均为O(n^2) 平均O(n^2)
空间复杂度:O(1)
稳定性:稳定
希尔排序
public static int[] ShellSort(int[] a)
{
int gap;
for(gap=a.length/2;gap>0;gap=gap/2)//最外层循环
{
//以gap距离分组
for(int i=0;i<gap;i++)
{
for(int j=i+gap;j<a.length;j+=gap)
{
int temp=a[j];
int k=j-gap;
while(k>=0&&temp<a[k])
{
a[k+gap]=a[k];
k-=gap;
}
a[k+gap]=temp;
}
}
}
return a;
}
时间复杂度:最好O(n) 最坏O(n^2) 平均O(n^1.3)
空间复杂度:O(1)
稳定性:不稳定
简单选择排序
public static void simpleSelectionSort(int[] a)
{
for(int i=0;i<a.length;i++)
{
int max=i;
for(int j=i+1;j<a.length;j++)
{
if(a[max]>a[j])
{
max=j;
}
}
int temp=a[i];
a[i]=a[max];
a[max]=temp;
}
}
时间复杂度:最好最坏平均都为O(n^2)
空间复杂度:O(1)
稳定性:不稳定
堆排序
/**重要性质:对于大顶堆:arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2]
对于小顶堆:arr[i] <= arr[2i + 1] && arr[i] <= arr[2i + 2]
思路:先构造大顶堆,然后交换根节点和数组最后一个数,重复步骤,
大顶堆的构造关键,由下往上遍历
时间复杂度O(nlogn)**/
public static void heapSort(int[] a)
{
if(a == null || a.length==0)
{
return ;
}
int len = a.length;
for(int i=len-1;i>0;i--)
{
heapify(a,0,len);
swap(a,0,i);
len--;
}
}
//构建大顶堆
private static void heapify(int[] arr, int i, int len) {
// 先根据堆性质,找出它左右节点的索引
int left = 2 * i + 1;
int right = 2 * i + 2;
// 默认当前节点(父节点)是最大值。
int largestIndex = i;
//这里注意两个条件书写的先后顺序,若顺序相反则会导致数组下标溢出,因为如果left不满足
//小于len的条件,则不会判断后面的条件,就不会导致数组溢出
if (left < len && arr[left] > arr[largestIndex]) {
// 如果有左节点,并且左节点的值更大,更新最大值的索引
largestIndex = left;
}
if (right < len && arr[right] > arr[largestIndex]) {
// 如果有右节点,并且右节点的值更大,更新最大值的索引
largestIndex = right;
}
if (largestIndex != i) {
// 如果最大值不是当前非叶子节点的值,那么就把当前节点和最大值的子节点值互换
swap(arr, i, largestIndex);
// 因为互换之后,子节点的值变了,如果该子节点也有自己的子节点,仍需要再次调整。
heapify(arr, largestIndex, len);
}
}
//交换
public static void swap(int[] arr,int i,int j)
{
int temp=arr[j];
arr[j]=arr[i];
arr[i]=temp;
}
时间复杂度:最好最坏平均 都为O(nlogn)
空间复杂度:O(1)
稳定性:不稳定
归并排序
public static void mergeSort(int[] a,int left,int right)
{
if(left>=right)
{
return;
}
int mid= (left+right)/2;
mergeSort(a,left,mid);
mergeSort(a,mid+1,right);
merge(a,left,mid,right);
}
//将两个数组合并
public static void merge(int[] a,int left,int mid,int right)
{
//创建辅助数组aux 根据要合并的数组创建相对应大小的辅助数组
int[] aux = new int[right-left+1];
//辅助数组aux 这里i-left 使得aux从0开始赋值
for(int i=left;i<=right;i++)
aux[i-left]=a[i];
//i:临时数组左边比较的元素下标;j:临时数组右边比较的元素的下标;k:原数组将要放置的元素下标
int i=left,j=mid+1;
for(int k=left;k<=right;k++)
{
//检查左下标是否越界
if(i>mid)
{
a[k]=aux[j-left];
j++;
}
else if(j>right)
{
a[k]=aux[i-left];
i++;
}
else if(aux[i-left]<=aux[j-left])
{
a[k]=aux[i-left];
i++;
}
else
{
a[k]=aux[j-left];
j++;
}
}
}
时间复杂度度:最好最坏平均 都为O(nlogn)
空间复杂度:O(n)
稳定性:稳定
桶排序
public static void radixSort(int[] arr){
//先求出数组中的最大的数
int max=arr[0];
for(int i=1;i<arr.length;i++){
if(arr[i]>max){
max=arr[i];
}
}
//得到最大数是几位数,将数字转换成字符串
int maxLength=(max+"").length();
//定义一个二维数组,表示10个桶,每个桶就是一个数组
//为了在放入数时防止数据溢出,我们每个桶的大小为arr.length,
// 即每个桶最多放进数组里的所有元素
int[][] bucket=new int[10][arr.length];
//定义一个一维数组来记录各个桶的每次放入的数据个数
int[] bucketElementCount=new int[10];
for(int i=0,n=1;i<maxLength;i++,n*=10)
{
//对每个元素的对应位进行处理
for(int j=0;j<arr.length;j++){
//取出每个元素的对应位的值
int digiOfElement=arr[j]/n%10;
//放入到对应的桶中
bucket[digiOfElement][bucketElementCount[digiOfElement]]=arr[j];
bucketElementCount[digiOfElement]++;
}
//按照这个桶的顺序即一维数组的下标一次取出数据放入原来数组
int index=0;
//遍历每一桶,并将桶中数据放入到原数组
for(int k=0;k<bucketElementCount.length;k++){
//如果桶中有数据,才放入原来数组
if(bucketElementCount[k]!=0){
for(int m=0;m<bucketElementCount[k];m++){
arr[index++]=bucket[k][m];
}
}
//每一轮处理后,需将bucketElementCount[k]=0
bucketElementCount[k]=0;
}
System.out.println("第"+(i+1)+"轮,对个位数的排序处理 arr="+ Arrays.toString(arr));
}
}
时间复杂度:最好O(n) 最坏O(n*k) 平均 O(n+k)
空间复杂度:O(n+k)
稳定性:稳定
总结:
最好 | 最坏 | 平均 | 空间复杂度 | 稳定性 | |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(nlogn) | 不稳定 |
直接插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n) | O(n^2) | O(n^1.3) | O(1) | 不稳定 |
直接选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
桶排序 | O(n) | O(n*2) | O(n+k) | O(n+k) | 稳定 |