稳定性指的是排序的值相同的元素,在排序后位置没有变,就是排序前,一个元素比另一个排序值相同的元素在前,排序后还应该在前,那么就是稳定的。
1.冒泡排序:开始第一位置和第二位置比较,第一位置大就交换,然后第二位置和第三位置比较,第二位置大就交换,一直到最后,这样就找到了最大值,然后再循环找第二大的值,第三大的值,直到循环结束。比较了N*N/2次,数据随机的话一半数据要交换N*N/4。平均情况下O(n^2),最坏情况下是O(N^2)最好情况下是O(N)应该是顺序的情况下,只要判断一下第一次比较有没有交换数据就行了,没有交换数据就说明是排好序的,所以是O(N)。这是稳定的,因为是两两比较,相同就不交换顺序。不需要额外空间
public static void bubbleSort(int[] a){
int b = 0;
for(int i=a.length-1;i>0;i--){
for(int j=0;j<i;j++){
if(a[j+1]<a[j]){
b = a[j+1];
a[j+1]=a[j];
a[j]=b;
}
}
}
}
2.选择排序:所有数据和第一个比较,找到最小的和第一位置交换,这样第一位置就是最小的,然后第二位置以后的数据和第二位置的数据比较,找到最小的放在第二位置,一直循环结束就排好序了。比较了N*N/2次和冒泡一样,但是它就交换了N次。所以比冒泡排序好,虽然还是O(n^2),什么情况下都是O(N^2)。不稳定,因为是找最小的和前面的交换,例如5,6,5,2,9,把第一个5和2交换了,那么第一个5和第二个5位置就变化了。不需要额外空间
public static void selectSort(int[] a){
for(int i=0;i<a.length-1;i++){
int b =i;
for(int j=i+1;j<a.length;j++){
if(a[b]>a[j]){
b=j;
}
}
int c = a[b];
a[b]=a[i];
a[i]=c;
}
}
3.插入排序:从第二个位置开始,跟前一个比较,小就和前一个交换,然后再跟前一个比较,一直找到自己的位置为止。接下来从第三个位置开始和前一个位置数据比较,找到自己位置为止。开始比较的数据为标记数据,标记数据的左边是有序的。比较次数为N*N/4,复制次数和比较次数差不多,而复制的时间比交换的时间快,所以这个比冒泡排序快很多,比选择排序略快。平均和最坏情况下O(N^2),最好情况是排好序的不需要交换,只要比较N次就行啦O(N),稳定的,因为是标记的元素和前面的元素比较,一样就不交换。不需要额外空间
public static void insertSort(int[] a){
for(int i=1;i<a.length;i++){
int b = a[i];
int c =i;
while(c>0&&a[c-1]>b){
a[c]=a[c-1];
--c;
}
a[c]=b;
}
}
4.归并排序:原理就是把原数组分成2分之1,然后对两半在进行对半分,一直到1为止,然后比较两半的数据,因为两半的数据是有序的,所以先比较两半的第一个元素,那个比较小的放到一个新的数组中,从原数组移除他,再比较两个数组开头一直到两个数组合成一个有序数组为止,每个两半都这样那么整个数组就是有序的了。什么情况下都是O(N*log(N)).这个算法是稳定的。非常好的算法,但是需要额外空间。
package aaaa;
import java.util.Arrays;
public class MergeSort {
public static void main(String[] args) {
int[] nums = { 2, 4, 8, 1, 3, 6, 7, 0, 9, 5 };
MergeSort.sort(nums, 0, nums.length-1);
for(int a:nums){
System.out.print(a+",");
}
}
public static void sort(int[] nums, int low, int high) {
if(low>=high){
return;
}
int mid = (low + high) / 2;
sort(nums, low, mid); //把数组左边排序为有序的数组
sort(nums, mid + 1, high); //把数组右边排序为有序的数组
merge(nums, low, mid, high); //归并两个数组
}
public static void merge(int[] nums, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;//左边数组的开始索引
int j = mid + 1;//右边数组的开始索引
int k=0;
// 比较两个数组,把较小的数先移到临时数组中
while (i <= mid && j <= high) {
if (nums[i] < nums[j]) {
temp[k++] = nums[i++];
} else {
temp[k++] = nums[j++];
}
}
// 把左边数组剩余的数移入数组
while (i <= mid) {
temp[k++] = nums[i++];
}
// 把右边数组剩余的数移入数组
while (j <= high) {
temp[k++] = nums[j++];
}
//把临时数组里的数覆盖掉原数组
for (k=0; k < temp.length; k++) {
nums[k + low] = temp[k];
}
}
}
5.希尔排序
希尔排序比插入排序快的多的原因,当H值比较大,数据移动的距离很长,移动个数很少,这是很有效率的。当H减小后,移动个数增多,但是大部分数据已经排好序了,这时候使用插入排序是很有效率的。所以希尔排序在数据量不较多的时候比插入排序好很多,数据量小的时候差不多。这是不稳定的,因为是H变量间隔交换,有可能把相同元素数据交换到后面去。不需要额外空间,平均和最坏都在O(N^1.5)左右,最好情况是O(N)。适用于中等规模数据排序。大数据还是比快速排序慢。
public static void shellSort(int[] nums){
int temp,i,j;
int h=1;
while(h<=nums.length/3){
h=h*3+1;
}
while(h>0){
for(i=h;i<nums.length;i++){
temp = nums[i];
j=i;
while(j>h-1&&nums[j-h]>=temp){
nums[j]=nums[j-h];
j=j-h;
}
nums[j]=temp;
}
h=(h-1)/3;
}
}
6.快速排序
划分的概念:规定一个数据,把比它小的移到左边,比它大的移到右边。一般快速排序的枢纽为最右边一个,划分好后,把枢纽和划分后的右数组的最左边数据交换。对于快速排序最好的就是枢纽左边右边数据数量一样O(N*logN),平均也是O(N*logN),这样划分的次数比较少。最坏情况就是一次枢纽的一边没有数据,全在另一边,这样划分的次数比较多,这是效率变为O(N^2),有个潜在问题,划分次数增加,那么递归次数增加,每次方法调用都要增加递归工作栈的大小,次数太多可能会溢出。这是不稳定的。不需要额外的空间。
public static void fastSort(int[] nums,int left,int right){
if(left>=right){
return;
}else{
int a = nums[right];//枢纽,默认最右边的数据
int b = partition(nums,left,right,a);//返回上次枢纽所在的位置
fastSort(nums, left, b-1);//左划分
fastSort(nums, b+1, right);//右划分
}
}
public static int partition(int[] nums,int left,int right,int pivot){
int lef = left-1;//-1,左指针,左指针向右移动,比枢纽大停止移动,表示要交换数据
int rig = right;//length-1,右指针,右指针向左移动,不枢纽小停止移动,表示要交换数据
int temp;//临时存放数据
while(true){
while(lef<right&&nums[++lef]<pivot);//判断左边的数据是不是大于枢纽
while(rig>0&&nums[--rig]>pivot);//判断右边的数据是不是大于枢纽
if(lef>=rig){//判断是不是划分好了
break;
}else{//交换左右指针数据
temp=nums[lef];
nums[lef]=nums[rig];
nums[rig]=temp;
}
}
temp = nums[right];//交换枢纽和划分后右数组最左边的位置
nums[right]=nums[lef];
nums[lef]=temp;
return lef;
}
7.基数排序
以10为基数的运算:
1.把个位数按照0到9的顺序排好。
2.把十位数按照0到9的顺序排好
3.依次直到所有数据的数字项都为0。
稳定的算法,算法复杂度和数据项的个数和关键字的大小有关。需要额外的空间。
8.堆排序
堆排序的时间复杂都为O(N*logN)。不需要额外的空间。是不稳定的。