文章目录- 1、堆排序
- 2、归并排序
- 3、快速排序
- 4、基数排序
- 5、计数排序
- 6、选择排序
- 7、冒泡排序
- 8、插入排序
- 9、希尔排序
- 10、空间复杂度
- 11、时间复杂度 与稳定性
- 辅助记忆:
- 1)推排序——
- 2)归并排序——
- 3)快速排序——
- 辅助记忆:
- 1)推排序——
- 2)归并排序——
- 3)快速排序——
1、堆排序
代码语言:javascript复制import java.util.*;
public class HeapSort {
public int[] heapSort(int[] A, int n) {
//Floyd 构堆法
for(int i=n/2-1;i>=0;i--)
moveDown(A,i,n-1);
for(int i=n-1;i>0;i--){
swap(A,i,0); //堆顶最大的元素交换到最后,倒数第二,倒数第三。。。。。。
moveDown(A,0,i-1); //新堆顶元素沿树下移
}
return A;
}
//主要思想:如果左、右孩子存在,找出其中较大的当成大孩子,将大孩子与父亲交换
public void moveDown(int[] data,int first,int last){
int largest = 2*first 1; //左孩子当大孩子
while(largest<=last){ //左孩子存在则执行循环
if(largest<last && data[largest]<data[largest 1]) //“右孩子存在” 并且 “左孩子小于右孩子”
largest ; //右孩子当成大孩子
if(data[first]<data[largest]){ //父亲小于大孩子
swap(data,first,largest); //父亲与大孩子交换
first=largest; //把大孩子的位置当父亲,孩子的左孩子当大孩子
largest = 2*first 1;
}
else
break;
}
}
public void swap(int[] data,int a,int b){
int tmp=data[a];
data[a]=data[b];
data[b]=tmp;
}
}
2、归并排序
代码语言:javascript复制package snippet;
public class MegerSort {
public static void mmegerSort(int[] data){
int n=data.length;
megerSort(data,0,n-1);
}
private static void megerSort(int[] data, int first, int last) {
if(last-first<2)
return; //递归出口,data至少有两个元素
int mid=(last first)/2;
megerSort(data, 0, mid); //data的左半部分归并排序
megerSort(data,mid 1,last); //data的右半部分归并排序
meger(data,first,mid,last); //合并排序过后的左右两部分
}
//合并函数,合并的过程中排序
private static void meger(int[] data, int first, int mid, int last) {
int[] tmp=new int[last-first 1]; //临时数组,来合并数组
int i=0, m=first, n=mid 1;
//通过循环把左右数组中较大元素放入临时数组
while(m<=mid && n<=last){ //左右两数组都有元素
if(data[n]<data[m])
tmp[i ]=data[n ];
else
tmp[i ]=data[m ];
}
//将data中剩余的元素放进tmp
if(m<=mid)
for(;m<=mid;m )
tmp[i ]=data[m];
if(n<=last)
for(;n<=last;n )
tmp[i ]=data[n];
//再把临时数组中的元素给data
System.arraycopy(tmp, 0, data, first, last-first 1);
}
}
3、快速排序
代码语言:javascript复制public class quickSort {
public static void quickSort(int[] arr){
if(arr == null){
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int low, int high){
if(arr == null || low >= high){
return;
}
int index = partitionIt(arr, low, high);
quickSort(arr, low, index - 1);
quickSort(arr, index 1, high);
}
private static int partitionIt(int[] array, int first, int last) {
//为什么j加一个1,而i没有加1,是因为下面的循环判断是从--j和 i开始的。
//而基准元素选的array[left]即第一个元素,所以左游标从第二个元素开始比较。
int low = first;
int high = last 1;
int temp = array[first];// pivot 为选取的基准元素
while (true) {
while (low < last && array[ low] < temp) {
}
while (high > first && array[--high] > temp) {
}
if (low >= high) { //左右游标相遇时停止
break;
} else {
swap(array, low, high); //左右游标未相遇时交换各自所指元素
}
}
swap(array, first, high);//基准元素和游标相遇时所指元素交换为最后一次交换
return high; //一趟排序完成,返回基准元素位置(注意这里基准元素已经交换位置了)
}
public static void swap(int[] arr, int a, int b){
int tmp = arr[a];
arr[a] = arr[b];
arr[b] = tmp;
}
public static void main(String[] args) {
int[] arr = {9, 8, 7, 3, 5, 4, 10, 2, 4 ,5, 20, 21, 0, -1, 40, 23, 25, 39, 35};
quickSort(arr);
for(int i : arr) {
System.out.println(i);
}
}
}
4、基数排序
代码语言:javascript复制import java.util.*;
public class RadixSort {
public int[] radixSort(int[] A, int n) {
//1.建十个桶,每个桶高度为n
int[][] bound=new int[10][n];
//2.分别按个位、十位、百位、千位将数放入桶再倒出
int m=1; //位数
int a=1; //这个a是为了依次将个位,十位,百位,千位的数作为余数
int[] count=new int[n]; //每个桶内的元素的个数
while(m<=4){
//装桶
for(int p=0;p<n;p ){
int rem=(A[p]/a); //这个式子是取每个位作为余数的经典
bound[rem][count[rem]]=A[p];
count[rem] ; //桶内的元素的个数
}
//倒桶
int k=0;
for(int j=0;j<10;j ){
if(count[j]>0) //如果该桶内有元素
for(int p=0;p<count[j];p ) //将桶内元素依次倒出
A[k ]=bound[j][p];
count[j]=0; //该桶倒出之后,桶内元素数目清零
}
m ; //位数增加
a*=10; //系数乘10
}
return A;
}
}
5、计数排序
适用场景:数据分布氛围小,例如身高、年龄,但是对于均匀分布的整数,不适合
计数排序非常基础,他的主要目的是对整数排序并且会比普通的排序算法性能更好。例如,输入{1, 3, 5, 2, 1, 4}给计数排序,会输出{1, 1, 2, 3, 4, 5}。这个算法由以下步骤组成:
- 初始化一个计数数组,大小是输入数组中的最大的数。
- 遍历输入数组,遇到一个数就在计数数组对应的位置上加一。例如:遇到5,就将计数数组第五个位置的数加一。
- 把计数数组直接覆盖到输出数组(节约空间)。 ● 例子 输入{3, 4, 3, 2, 1},最大是4,数组长度是5。 建立计数数组{0, 0, 0, 0}。 遍历输入数组: {3, 4, 3, 2, 1} -> {0, 0, 1, 0} {3, 4, 3, 2, 1} -> {0, 0, 1, 1} {3, 4, 3, 2, 1} -> {0, 0, 2, 1} {3, 4, 3, 2, 1} -> {0, 1, 2, 1} {3, 4, 3, 2, 1} -> {1, 1, 2, 1} 计数数组现在是{1, 1, 2, 1},我们现在把它写回到输入数组里: {0, 1, 2, 1} -> {1, 4, 3, 2, 1} {o, o, 2, 1} -> {1, 2, 3, 2, 1} {o, o, 1, 1} -> {1, 2, 3, 2, 1} {o, o, o, 1} -> {1, 2, 3, 3, 1} {o, o, o, o} -> {1, 2, 3, 3, 4} 这样就排好序了。 ● 时间:O(n k),n是输入数组长度,k是最大的数的大小。 ● 空间:O(n k),n是输入数组长度,k是最大的数的大小
import java.util.*;
public class CountingSort {
public int[] countingSort(int[] A, int n) {
//找最大最小值,建桶
int min = A[0];
int max = A[0];
for(int a : A){
min = Math.min(a,min);
max = Math.max(a,max);
}
int bucketLength = max-min 1; //桶长度
int[] bucket = new int[bucketLength];
//放内容
for(int a : A){
bucket[a-min] ; //某数有几个,相应位置为几,0个则为0
}
//倒出内容
int index = 0;
for(int i = 0; i < bucketLength; i ){ //主要是为了遍历bucket[i]
for(int j = 0; j < bucket[i]; j ){ //0个的不放,有几个的数放几次
A[index ] = i min;
}
}
return A;
}
}
6、选择排序
代码语言:javascript复制import java.util.*;
public class SelectionSort {
public int[] selectionSort(int[] A, int n) {
//依次从A[i]及其之后的数中比较出最小的放在i的位置上
for(int i=0;i<n-1;i ){
int mindex=i; //先把i设为最小
for(int j=i;j<n;j ){
if(A[mindex]>A[j]){
mindex=j;
}
}
int tmp=A[mindex];
A[mindex]=A[i];
A[i]=tmp;
}
return A;
}
}
7、冒泡排序
代码语言:javascript复制import java.util.*;
public class BubbleSort {
public int[] bubbleSort(int[] A, int n) {
if(A==null)
return null;
for(int i=0;i<n-1;i ){
for(int j=0;j<n-1-i;j ){
if(A[j]>A[j 1]){
int tmp=A[j];
A[j]=A[j 1];
A[j 1]=tmp;
}
}
}
return A;
}
}
8、插入排序
代码语言:javascript复制import java.util.*;
public class InsertionSort {
public int[] insertionSort(int[] A, int n) {
int i,j,tmp; //这里很关键
for(i=1;i<n;i ){ //这里i从1开始,是让A[1]与A[0]先比
tmp=A[i]; //分别将A[i]...A[n-1]拿出来
for(j=i;j>0&&tmp<A[j-1];j--) //j>0 && tmp<A[j-1]是经典,因为本子上有
A[j]=A[j-1]; //A[j-1]向后移
A[j]=tmp; //这里是经典,因为j在j--已减小了
}
return A;
}
}
9、希尔排序
代码语言:javascript复制import java.util.*;
public class ShellSort {
public int[] shellSort(int[] A, int n) {
int i,j,k,h,hcnt,tmp;
int[] increments=new int[20];
//计算增量,并将其放入增量数组
for(h=1,i=0;h<n;i ){
increments[i]=h;
h=3*h 1; //增量计算公式
}
//用不同的增量分割数组并利用插入排序排列子数组
for(i--;i>=0;i--){ //第一个i-- 巧妙
h=increments[i];
//h个数组依次
for(hcnt=h;hcnt<2*h;hcnt ){
//每个子数组进行插入排序
for(j=hcnt;j<n;){
tmp=A[j];
k=j;
while(k-h>=0 && tmp<A[k-h]){
A[k]=A[k-h];
k-=h;
}
A[k]=tmp;
j =h;
}
}
}
return A;
}
}
10、空间复杂度
M是选择桶的数量, 快速排序根据枢轴的不同空间复杂度不同 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uLIY9VEB-1580433071814)(https://ws4.sinaimg.cn/large/006tNbRwly1fuwjvav55jj30pk0jsdon.jpg)]
11、时间复杂度 与稳定性
辅助记忆:
***时间复杂度记忆- *** 冒泡、选择、直接 排序需要两个for循环,每次只关注一个元素,平均时间复杂度为O(n2)(一遍找元素O(n),一遍找位置O(n)) 快速、归并、希尔、堆基于二分思想,log以2为底,平均时间复杂度为O(nlogn)(一遍找元素O(n),一遍找位置O(logn)) *稳定性记忆-“快希选堆”(快牺牲稳定性)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z3LN4Dzr-1580433071816)(https://ws2.sinaimg.cn/large/006tNbRwly1fuwklsdwrcj317u0k0tc2.jpg)]
1)推排序——
在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为.log2i.+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。
2)归并排序——
假如用T(n)表示使用归并排序对n个元素构成的数组进行排序而使用的时间,用mergeTime来表示将两个子分组合并起来而花费的时间。那么
T(n) = T(n/2) T(n/2) mergetime
而megeTime就是归并两个子数组所耗费的时间,以最大时间来算,最多需要n-1次来比较两个子数组的元素,然后n次移动到临时数组中。那么mergetime就是2n -1 。
因此 T(n) = T(n/2) T(n/2) 2n -1 。
3)快速排序——
在最差的情况下,要把n个元素的数组划分,需要n次比较和n次移动。假设用T(n) 来表示使用快速排序算法来排序n个元素的数组所耗费的时间。那么
T(n) = T(n/2) T(n/2) 2n
如此看来,有人可能就想到了,从数学公式上看,归并排序还比快排要少个1呢,是不是要快些?其实,不是这样的,请注意到那几个字"最差的情况下",就是在我每次选择的基准,取决于输入的基准,都是最不理想的基准,恰好要移动最多次才能达到目的。但是这种情况的出现的概率是1/(2^n),非常非常小,而且,这是最普通的快速排序方法,先人提出了很多改进优化的方案,其中一种被很常用的就是采用随机函数来选择基准来避免最坏的情况的发生。