冒泡排序
基本思想:通过比较相邻的元素,每一趟找到一个最小值放到放到最前面
public static void bubbleSort(int[] arr){
boolean flag=true;
for(int i=0;i<arr.length&&flag;i++){
flag=false;//如果内循环if条件不满足的话说明已经排好序了,没有必要继续循环了
for(int j=arr.length-1;j>i;j--){
if(arr[j-1]>arr[j]){
int temp=arr[j-1];
arr[j-1]=arr[j];
arr[j]=temp;
flag=true;
}
}
}
}
冒泡排序复杂度:
<1>最好情况下 :数组本身有序,只需要进行n-1次比较,不需要交换,时间复杂度为O(n)
<2>最坏情况下 :数组为逆序,此时需要比较n*(n-1)/2,并作等数量级的记录移动,时间复杂度为O(n^2)
选择排序
基本思想:每一次从无序区间选择出最大或者最小元素,放到无序区间的后面或者前面。
public static void selectSort(int[] arr){
for(int i=0;i<arr.length-1;i++){
int min=i;
for(int j=i+1;j<arr.length;j++){//循环一趟找出无序区间中最小的元素
if(arr[min]>arr[j]){
min=j;
}
}
if(i!=min){
int temp=arr[i];
arr[i]=arr[min];
arr[min]=temp;
}
}
}
选择排序复杂度:
<1>简单排序最大的特点:交换移动数据次数相当少,最好情况下交换0次,最差请求交换n-1次;适用于数组个数不多,
<2> 但每个数组元素较大的情况。
<3>时间复杂度:无论是最好最差情况,比较次数一样多,n(n-1)/2,总的时间复杂度O(n^2)
<4> 简单选择排序性能上略优于冒泡排序。
插入排序
基本思想:每次选择无序区间的第一个元素,在有序区间内选择合适的位置插入
public static void insertSort(int[] arr){
for(int i=1;i<arr.length;i++){
//有序区间【0,i】
//无序区间【i,arr.length】
int v=arr[i];//无序区间的第一个元素
int j=i-1;//有序元素最后一个元素的下标
for(;j>=0&&arr[j]>v;j--){
arr[j+1]=arr[j];
}
arr[j+1]=v;
}
}
直接插入排序复杂度分析
<1>最好情况下:在本身有序的情况下,共比较了n-1次,没有移动的记录,时间复杂度为O(n)。
<2> 最坏情况下:在逆序情况下:比较了 n(n-1)/2次,而移动次数为(n+2)(n-2)/2次。
<3>平均比较 移动 次数:n2/4,故直接插入排序的时间复杂度为O(n2)。
<4>直接插入排序法比冒泡和简单选择排序的性能要好一些。
希尔排序
基本思想:在有序区间选择数据应该插入的位置时,因为区间的有序性,可以利用折半查找的思想。
将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
public static void shellSort(int[] array) {
int gap=array.length/2;
while(gap>1){
//需要循环进行分组插入排序
insertSortGap(array,gap);
gap=gap/2;
}
insertSortGap(array,1);
}
private static void insertSortGap(int[] array, int gap) {
for (int bound = gap; bound < array.length; bound++) {
int v = array[bound];
int cur = bound - gap; // 这个操作是在找同组中的上一个元素
for (; cur >= 0; cur -= gap) {
// 注意!!!! 这个条件如果写成 >= , 咱的插入排序就不是稳定排序了
if (array[cur] > v) {
array[cur + gap] = array[cur];
} else {
// 此时说明已经找到了合适的位置
break;
}
}
array[cur + gap] = v;
}
}
希尔排序复杂度分析
<1> 希尔排序的关键并不是随便分组后各自排序,而是将相隔某个增量的数组组成一个子序列,实现跳跃式的移动。
<2>增量的选择实验证明当增量序列为delt[k]=2^(t-k+1)-1(0<=k<=t<=log2(n+1))时,效果较佳,但注意最后增量值必须等于1。
<3> 此时时间复杂度为O(n^1.5)
<4>希尔排序因为是跳跃式记录,故不是一个稳定的排序算法。
堆排序
基本思想:将待排序序列构造成一个大顶堆或者小顶堆,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾元素就为最大值
然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了
public static void heapSort(int[] array){
//先创建一个堆
ccreateHeap(array);
//循环把堆顶元素交换到最后,并进行调整堆
//当循环到array.length-1的时候也就有序了
for(int i=0;i<array.length-1;i++){
//当前堆的元素个数
int heapSize=array.length-i;
// 交换 堆顶元素 和 堆的最后一个元素
// 堆的元素个数相当于 array.length - i
// 堆的最后一个元素下标 array.length - i - 1
// 取堆的最后一个元素
swap(array,0,heapSize-1);
heapSize--;
// 交换完成之后, 要把最后一个元素从堆中删掉
// 堆的长度就又进一步缩水了 array.length - i - 1
// 数组中
// [0, array.length - i - 1) 待排序区间
// [array.length - i - 1, array.length) 已排序区间
// [注意!!!!] 这个代码中的边界条件特别容易搞错~~ -1 还是 不减 还是 + 1, 最好代入数值来验证.
// 例如可以验证下 i = 0 的时候, 咱们的逻辑是否合理.
shiftDown(array, heapSize, 0);
}
}
private static void ccreateHeap(int[] array) {
for(int i=(array.length-1-1)/2;i>=0;i--){
shiftDown(array,array.length,i);
}
}
private static void shiftDown(int[] array, int length, int i) {
int parent=i;
int child=2*parent+1;
while(child<length){
if(child+1<length&&array[child+1]>array[child]){
child=child+1;
}
if(array[child]>array[parent]){
swap(array,child,parent);
}else{
break;
}
parent=child;
child=2*parent+1;
}
}
private static void swap(int[] array, int child, int parent) {
int temp=array[child];
array[child]=array[parent];
array[parent]=temp;
}
public static void main(String[] args) {
int[] arr={2,8,0,4,8,5,8,7};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
堆排序复杂度分析
<1> 构建大堆顶的时间复杂度为O(n)
<2>第i次取堆顶记录重建堆需要O(logi)的时间,取n-1次,故重建堆的时间复杂度为O(nlogn)
<3>无论最好,最坏,平均时间复杂度均为O(nlogn)
<4>空间复杂度,只需要一个暂存单元
<5>由于记录的比较与交换是跳跃式进行,故是一种不稳定的排序方法
<6>由于初始构建堆所需要的比较次数较多,不适合带排序序列个数较少的情况。
归并排序
基本排序:假设初始序列含有n个记录,则可以看成是n个能有序的子序列,每个子序列的长度为1,然后两两归并,得到┎n/2┒(┎x┒表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,…,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
public static void mergeSort(int[] array,int left,int right){
if(left<right){
int mid=(left+right)/2;
mergeSort(array,left,mid);
mergeSort(array,mid+1,right);
merge(array,left,mid,right);
}
}
private static void merge(int[] array, int left, int mid, int right) {
int[] tmp=new int[array.length];//辅助数组
int p1=left,p2=mid+1,k=left;//p1,p2表示检测指针,k存放指针
while(p1<=mid&&p2<=right){
if(array[p1]<=array[p2]){
tmp[k++]=array[p1++];
}else{
tmp[k++]=array[p2++];
}
}
while(p1<=mid){//如果第一个序列未检测完,直接将后面所有元素加到合并的序列中
tmp[k++]=array[p1++];
}
while(p2<=right){
tmp[k++]=array[p2++];
}
//复制回原素组
for(int i=left;i<=right;i++){
array[i]=tmp[i];
}
}
归并排序复杂度分析
<1> 无论最好、最坏、平均来讲总的时间复杂度为O(nlogn)
<2>对于递归归并:由于需要与原始记录序列同样数量的存储空间存放归并结果及递归时深度为logn的栈空间,空间复杂度O(n+logn)
<3> 非递归方式的归并排序:空间复杂度为O(n)
<4>归并排序为稳定的排序算法。
快速排序
基本思想:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可以分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
public static void quickSort(int[] array){
quickSortHelp(array,0,array.length-1);
}
public static void quickSortHelp(int[] a,int left,int right){
if (left >= right)//如果左边的值大于右边的值说明已经完成了一组的执行
{
return;
}
int temp = a[left];//将第一个元素设为对比元素(参考值)
int i = left;
int j = right;
while (i < j)
{
while (i<j && a[j]>temp)//如果这个参考值小于右边所比较的元素,则j--,进行右边的下一个元素与参考值比较
{
j--;
}
a[i] = a[j]; //找到了一个比参考值小的数,把这个数放在前面 (原来取参考值的位置)
while (i <j && a[i] <= temp)//如果参考值大于等于右边所比较的元素,则i++;进行下一个元素与参考值的比较
{
i++;
}
a[j] = a[i]; //找到了一个比参考值大的数,把这个数放到后面(刚刚放再前面那个数的位置)
}
a[i] = temp; //当在当组内找完一遍以后就把中间数temp回归
quickSortHelp(a, left, i - 1); //最后用同样的方式对分出来的左边的小组进行同上的做法
quickSortHelp(a, j + 1, right); //用同样的方式对分出来的右边的小组进行同上的做法
}
快速排序时间复杂度分析
<1>最优与平均时间复杂度O(nlogn)
<2>最坏时间复杂度O(n^2)
<3> 空间复杂度O(logn)
<4>快速排序是一种不稳定的排序方法
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(nlogn)-O(n^2) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n^2) | O(nlogn)-O(n) | 不稳定 |