冒泡排序
冒泡排序核心逻辑就是对数组从第一个位置开始进行遍历,如果发现该元素比下一个元素大,则交换位置,如果不大,就拿着下一个比该元素大的元素向下继续比较,以此类推一直比较到最后,每一轮下来就会把当前最大的元素放到最后所以每一轮会确定一个最大元素。
public void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
int flag = 0;
for (int j = 1; j < array.length-i; j++) {
if (array[j - 1] > array[j]) {
swap(array,j-1,j);
flag = 1;
}
}
if (flag == 0) {
break;
}
}
}
这里做了一个优化,当某一轮发现一个元素都没有移动的时候,就表示这个数组已经是有序的了,不理解的可以举一个已经有序的例子推敲一下,所以定义一个flag
如果为0
就表示一整伦下来都没有发生过移动直接跳出循环,排序完成。
时间复杂度:O(N^2)
稳定性:稳定
空间复杂度:O(1)
插入排序
public void insertSort(int[] array) {
for (int i = 1; i < array.length; i++) {
int j = i - 1;
int tmp = array[i];
for (; j >= 0; j--) {
if (array[j] >= tmp) {
array[j+1] = array[j];
} else {
break;
}
}
array[j+1] = tmp;
}
}
时间复杂度:O(N^2)
稳定性:稳定
空间复杂度:O(1)
快速排序(未优化版本)
public void quickSort (int[] array) {
quick(array,0,array.length-1);
}
private void quick(int[] array, int start, int end) {
if (start >= end) {
return;
}
// 一个是霍尔法一个是挖坑法,两个都可
int part = partition1(array,start,end); // 霍尔法
//int part = partition2(array,start,end); // 挖坑法
quick(array,start,part-1);
quick(array,part+1,end);
}
private int partition1(int[] array, int left, int right) {
int tmp = array[left];
int tmpIndex = left;
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
while (left < right && array[left] <= tmp) {
left++;
}
swap(array,left,right);
}
swap(array,tmpIndex,left);
return left;
}
private int partition2(int[] array, int left ,int right) {
int tmp = array[left];
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
array[left] = array[right];
while (left < right && array[left] <= tmp) {
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
最好时间复杂度:O(N*logN)
最坏时间复杂度:O(N^2)
空间复杂度:O(logN)
稳定性:不稳定
快速排序(优化版本)
public void quickSort (int[] array) {
quick(array,0,array.length-1);
}
private void quick(int[] array, int start, int end) {
if (start >= end) {
return;
}
// 优化1(如果是少量的数据,直接插入排序速度是很快的,所以我们用直接插入排序处理数量小于等于15个的情况,不要小看这个优化,因为quickSort是树形的递归模式,在最后一层会有大量的数据存在,这个时候我们直接不进行递归直接进行插入排序可以节省大量的开销)
if (end - start + 1 <= 15) {
insertWithQuick(array,start,end);
return;
}
// 优化2(三数取中优化,如果当一个数组比较趋于有序的情况下,那么构造出来的二叉树就是变成了一个链表,所以才会有时间复杂度区域O(N^2),我们只要打破这个顺序就好,我们直接去一个较为中间的值作为基准值就能均分了)
int midIndex = findMidIndex(array,start,end);
swap(array,start,midIndex);
// 一个是霍尔法一个是挖坑法,两个都可
int part = partition1(array,start,end); // 霍尔法
//int part = partition2(array,start,end); // 挖坑法
quick(array,start,part-1);
quick(array,part+1,end);
}
private int partition1(int[] array, int left, int right) {
int tmp = array[left];
int tmpIndex = left;
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
while (left < right && array[left] <= tmp) {
left++;
}
swap(array,left,right);
}
swap(array,tmpIndex,left);
return left;
}
private int partition2(int[] array, int left ,int right) {
int tmp = array[left];
while (left < right) {
while (left < right && array[right] >= tmp) {
right--;
}
array[left] = array[right];
while (left < right && array[left] <= tmp) {
left++;
}
array[right] = array[left];
}
array[left] = tmp;
return left;
}
// 使用插入排序进行优化
private void insertWithQuick(int[] array, int start, int end) {
for (int i = start; i <= end; i++) {
int j = i - 1;
int tmp = array[i];
for (; j >= start; j--) {
if (array[j] >= tmp) {
array[j+1] = array[j];
} else {
break;
}
}
array[j+1] = tmp;
}
}
// 使用三数取中找到中值下标
private int findMidIndex(int[] array, int start, int end) {
int midIndex = start + (end - start) / 2;
if (array[start] > array[end]) {
// end start
if (array[midIndex] < array[end]) {
return end;
} else if (array[midIndex] > array[start]) {
return start;
} else {
return midIndex;
}
} else {
// start end
if (array[midIndex] < array[start]) {
return start;
} else if (array[midIndex] > array[end]) {
return end;
} else {
return midIndex;
}
}
}
优化后的时间复杂度将会无限趋近于O(N*logN),空间复杂度不变
堆排序
public void heapSort (int[] array) {
createBigHeap(array);
int end = array.length-1;
while (end >= 0) {
swap(array,0,end);
end--;
siftDown(array,0,end);
}
}
private void createBigHeap(int[] array) {
for (int parent = (array.length-2); parent >= 0; parent--) {
siftDown(array,parent,array.length-1);
}
}
private void siftDown(int[] array, int parent, int end) {
int child = parent * 2 + 1;
while (child <= end) {
if (child + 1 <= end && array[child+1] > array[child]) {
child++;
}
if (array[child] > array[parent]) {
swap(array,child,parent);
parent = child;
child = child * 2 + 1;
} else {
break;
}
}
}
时间复杂度:O(N*logN)
空间复杂度:O(logN)
稳定性:不稳定
希尔排序
public void shallSort (int[] array) {
int gap = array.length / 2;
while (gap > 0) {
shall(array,gap);
gap /= 2;
}
}
private void shall(int[] array, int gap) {
for (int i = gap; i < array.length; i++) {
int tmp = array[i];
int j = i - gap;
for (; j >= 0; j -= gap) {
if (array[j] >= tmp) {
array[j + gap] = array[j];
} else {
break;
}
}
array[j+gap] = tmp;
}
}
希尔排序就是直接插入排序的升级版本
时间复杂度:不好计算业界公认的大概是O(N^1.3)
空间复杂度:O(1)
稳定性:不稳定
归并排序
public void mergeSort(int[] array) {
mergeFun(array,0,array.length-1);
}
private void mergeFun(int[] array, int start, int end) {
if (start >= end) {
return;
}
int midIndex = start + (end - start) / 2;
mergeFun(array,start,midIndex);
mergeFun(array,midIndex+1,end);
// merge
merge(array, start, end, midIndex);
}
private void merge(int[] array, int start, int end, int midIndex) {
int rs = midIndex + 1 , k = 0, tmpIndex = start;
int[] tmpArray = new int[end - start +1];
while (start <= midIndex && rs <= end) {
if (array[start] < array[rs]) {
tmpArray[k++] = array[start++];
} else {
tmpArray[k++] = array[rs++];
}
}
while (start <= midIndex) {
tmpArray[k++] = array[start++];
}
while (rs <= end) {
tmpArray[k++] = array[rs++];
}
for (int j = tmpIndex; j <= end; j++) {
array[j] = tmpArray[j - tmpIndex];
}
}
时间复杂度:O(N*logN)
空间复杂度:O(N)
稳定性:稳定
各排序时间消耗对比
为了更直观的观察每个排序有时间效率,我将这几个排序进行了一个对比,先随机生成要给数组大小为100000
的随机数,随机数范围是0-10000
,之后分别计算各个排序所消耗的时间单位是毫秒
public static void main(String[] args) {
Sort sort = new Sort();
// 原数组
int[] array = createRandomNumber(100_000,10_000);
// copy数组
int[] array1 = Arrays.copyOf(array, array.length);
long start1 = System.currentTimeMillis();
sort.quickSort(array1);
System.out.println("quickSort : " + (System.currentTimeMillis()-start1) );
int[] array2 = Arrays.copyOf(array, array.length);
long start2 = System.currentTimeMillis();
sort.shallSort(array2);
System.out.println("shallSort : " + (System.currentTimeMillis()-start2) );
int[] array3 = Arrays.copyOf(array, array.length);
long start3 = System.currentTimeMillis();
sort.heapSort(array3);
System.out.println("heapSort : " + (System.currentTimeMillis()-start3) );
int[] array4 = Arrays.copyOf(array, array.length);
long start4 = System.currentTimeMillis();
sort.mergeSort(array4);
System.out.println("mergeSort : " + (System.currentTimeMillis()-start4) );
int[] array5 = Arrays.copyOf(array, array.length);
long start5 = System.currentTimeMillis();
sort.insertSort(array5);
System.out.println("insertSort : " + (System.currentTimeMillis()-start5) );
int[] array6 = Arrays.copyOf(array, array.length);
long start6 = System.currentTimeMillis();
sort.bubbleSort(array6);
System.out.println("bubbleSort : " + (System.currentTimeMillis()-start6) );
}
private static int[] createRandomNumber (int size, int range) {
int[] array = new int[size];
for (int i = 0; i < size; i++) {
array[i] = new Random().nextInt(range);
}
return array;
}
所以大家还是少用冒泡吧,虽然简单但是效率感人~ 快排还是牛掰