冒泡排序
特性:稳定!!序列数量多了很没有效率!!
时间复杂度: O(n)至O(n^2),平均时间复杂度为O(n^2)
空间复杂度:O(1)
算法描述:
引用博客https://www.cnblogs.com/onepixel/articles/7674659.html动图
从数组中第一个数开始,依次遍历数组中的每一个数,通过相邻比较交换;
每一轮循环下来找出剩余未排序数的中的最大数并”冒泡”至数列的顶端,重复该步骤。
import java.util.Scanner;
public class Main {
//交换位置
public static void swap(int a[], int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
//调用换位排序
public static void sort(int a[], int n){
for(int i = 0;i < n;i++){
for(int j = 0;j < n-i-1;j++){
if(a[j] > a[j+1]){
swap(a,j+1,j);
}
}
}
}
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int a[] = new int [n];
for(int i = 0;i < n;i++){
//乱序数组
a[i] = scanner.nextInt();
}
sort(a,n);
for(int i = 0;i < n;i++){
System.out.print(a[i]+" ");
}
}
}
插入排序
特性:稳定!!不适合数据量大的排序,量级小于千可选择此!!
时间复杂度: O(n)至O(n^2),平均时间复杂度是O(n^2)。
空间复杂度: O(1)
算法描述:
1)从第2个元素开始(第一个元素可以认为已经被排序)取出,在已经排序的元素序列中从后向前扫描
2)如果该元素A(已排序)> 新元素B,将该元素移到下一位置
3)重复步骤2,直到找到已排序的元素 <= 新元素的位置
4)将新元素插入到该位置,重复步骤
import java.util.Scanner;
public class Main {
//交换位置
public static void swap(int a[], int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
//调用换位插入排序
public static void Insertsort(int a[], int n){
for(int i = 1;i < n;i++){
for(int j = i;j > 0;j--){
if(a[j] < a[j-1]){
swap(a,j-1,j);
}
}
}
}
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int a[] = new int [n];
for(int i = 0;i < n;i++){
//乱序数组
a[i] = scanner.nextInt();
}
Insertsort(a,n);
for(int i = 0;i < n;i++){
System.out.print(a[i]+" ");
}
}
}
希尔排序
特性:插入排序高效改进版,不稳定!!
时间复杂度:O(n^1.3)到O(n^2)。Shell排序算法的时间复杂度分析比较复杂,实际所需的时间取决于各次排序时增量的个数和增量的取值。研究证明,若增量的取值比较合理,Shell排序算法的平均时间复杂度约为O(n^1.3)。
空间复杂度:O(1)
算法描述:
1)选择一个增量序列,初始一般为d=n/2最终为d=1;
※有的特殊序列可能用n/2会不存在位置的交换而直接退化为插入排序所以序列长度增量不一定为此。
2)按增量序列个数k,对序列进行 k 趟排序;
3)每趟排序,根据对应的增量d,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
import java.util.Scanner;
public class Main {
//调用换位shell排序
public static void Shellsort(int a[], int n){
int d = n/2;
/* while (d <= n){
d = 3*d+1;
}*/
while (d >= 1){
for(int i = d;i < n;i++){
int t = a[i];
int j;
for(j = i-d;j >= 0 && t < a[j];j-=d){
a[j+d] = a[j];
}
a[j+d] = t;
}
d = d/2;
//d = (d-1)/3
}
}
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int a[] = new int [n];
for(int i = 0;i < n;i++){
//乱序数组
a[i] = scanner.nextInt();
}
Shellsort(a,n);
for(int i = 0;i < n;i++){
System.out.print(a[i]+" ");
}
}
}
选择排序
特性:不稳定!!不稳定发生在最小元素与A[i]交换的时刻!
时间复杂度: 最坏、最好和平均复杂度均为O(n2),因此,简单选择排序也是常见排序算法中性能最差的排序算法。简单选择排序的比较次数与文件的初始状态没有关系,在第i趟排序中选出最小排序码的记录,需要做n-i次比较,因此总的比较次数是:∑i=1n−1(n−i)=n(n−1)/2=O(n2)。
空间复杂度:O(1)
算法描述:
1)n个数字可经过n-1趟直接选择排序得到有序结果
2)初始状态,无序区为R[1..n],有序区为空
3)第 i 趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为 R[1..i-1] 和 R[i..n]。
4)该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
5)n-1趟结束,数组有序化
import java.util.Scanner;
public class Main {
//交换位置
public static void swap(int a[], int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
//调用换位选择排序
public static void Choisesort(int a[], int n){
int min,temp;
for(int i = 0;i < n-1;i++){
min = i;
for(int j = i+1;j < n;j++){
if(a[j] < a[min]){
min = j;
}
}
if(min != i){
swap(a,min,i);
}
}
}
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int a[] = new int [n];
for(int i = 0;i < n;i++){
//乱序数组
a[i] = scanner.nextInt();
}
Choisesort(a,n);
for(int i = 0;i < n;i++){
System.out.print(a[i]+" ");
}
}
}
堆排序
※ 堆(heap)
1) 优先队列。队列允许的操作是先进先出(FIFO),队尾插入元素队头取出元素。堆在堆底插入元素,堆顶取出元素(堆中元素是按着一定优先顺序排列比如大小,不是到来的先后顺序)
2) 是完全二叉树或近似完全二叉树:设二叉树深度为h,除了第h层外,其他各层[1,h-1]的结点数都达到最大个数,第h层所有的结点都连续集中在最左边。
3) 用数组来存储,i结点的父结点下标为 (i-1)/2,左右子节点下标为 2*i+1和 2*i+2。
4) 最大堆:堆顶元素是整个堆中最大的,每一个分支可以看成一个最大堆;
最小堆:堆顶元素是整个堆中最小的,每一个分支可以看成一个最小堆。
特性:不稳定!!不稳定发生在堆顶元素与A[i]交换的时刻。
时间复杂度:堆排序的时间复杂度为O(N * logN)
每次重新恢复堆的时间复杂度为O(logN),共N - 1次堆调整操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。两次次操作时间相加还是O(N * logN)。
最坏情况:如果待排序数组是有序的,仍然需要O(N * logN)复杂度的比较操作,只是少了移动的操作;
最好情况:如果待排序数组是逆序的,不仅需要O(N * logN)复杂度的比较操作,而且需要O(N * logN)复杂度的交换操作。总的时间复杂度还是O(N * logN)。
堆排序和快速排序在效率上是差不多的,但是堆排序一般优于快速排序的重要一点是,数据的初始分布情况对堆排序的效率没有大的影响。
空间复杂度:O(1)
算法描述:
1)由输入的无序数组构造一个最大堆(只是满足父结点比子结点值大,叶子结点可能大于父结点的兄弟结点),作为初始的无序区
2)把堆顶元素(最大值)a[1] 和堆尾元素 a[n-1] 互换,然后将新的无序区调整为新的堆。
3)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆。
4)再次将 a[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。
import java.util.Scanner;
public class Main {
//交换位置
public static void swap(int a[], int i, int j){
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
//构造最大堆
public static void MaxHeap(int a[],int n){
for(int i = n/2;i >= 0;i--){
ModifyHeap(a,i,n);
}
}
//调整堆
public static void ModifyHeap(int a[],int i,int n){
int left = 2*i+1;
int right = 2*i+2;
int max = i;
if(left < n && a[left] > a[max] ){
max = left;
}
if(right < n && a[right] > a[max]){
max = right;
}
if(max != i){
swap(a,max,i);
ModifyHeap(a,max,n);
}
}
//堆排序
public static void HeapSort(int a[], int n){
MaxHeap(a,n);
for(int i = n-1;i > 0;i--){
swap(a,0,i);
n--;
ModifyHeap(a,0,n);
}
}
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int a[] = new int [n];
for(int i = 0;i < n;i++){
//乱序数组
a[i] = scanner.nextInt();
}
HeapSort(a,n);
for(int i = 0;i < n;i++){
System.out.print(a[i]+" ");
}
}
}
快速排序
特性:不稳定!!分治思想!!
时间复杂度:平均时间复杂度为O(nlgn)
空间复杂度:
算法描述:
1) 从数列中挑出一个元素,称为 “基准”(pivot),一般为第一个数;
2) 重新排序数列,所有元素比基准值小的摆放在其前,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。
3) 递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
以上只是笼统的解释,其实快排有多种算法,我取其中比较常用的被称为“左右指针法”的算法描述
1)选取一个关键字(key)作为标记,一般取整组记录的第一个数/最后一个,这里采用选取序列第一个数。
2)从left一直向后走,直到找到一个大于key的值,right从后至前,直至找到一个小于key的值,然后交换这两个数。
3)重复第2步,一直往后找,直到left和right相遇,这时将key放置left的位置即可。
该例子为上边动图中的序列:
纵览一下各个排序复杂度