冒泡排序
两两之间比较交换
/*
时间复杂度: 最坏的情况是比较 n-1 轮:O(n^2)
最好的情况是比较 1 轮:O(n)
空间复杂度: 需要一个辅助空间来交换两个元素:O(1)
稳定性: 稳定排序
*/
public void bubbleSort(int[] array){//冒泡排序
//最多比较 n-1 轮
for (int i = 0; i < array.length-1; i++) {
//两两比较,每经过一轮就少比较末尾的一个
for (int j = 0; j < array.length-1-i; j++) {
//如果前一个大于后一个,就交换
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
优化:
设置一个是否交换的信号量,如果有一轮比较没有任何交换,那么 change = false ,就不再进入下一轮交换
/*
时间复杂度: 最坏的情况是比较 n-1 轮:O(n^2)
最好的情况是比较 1 轮:O(n)
空间复杂度: 需要一个辅助空间来交换两个元素:O(1)
稳定性: 稳定排序
*/
public void bubbleSort(int[] array){//冒泡排序
boolean change = true;//假定要交换
for (int i = 0; i < array.length-1 && change; i++) {
change = false;//假定未交换
for (int j = 0; j < array.length-1-i; j++) {
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
change = true;//有交换,置为 true
}
}
}
}
直接插入排序
若数组只含有一个元素,则这个数组肯定有序,所以要从第二个元素开始进行插入
/*
时间复杂度: 最坏的情况:O(N^2)
最好的情况是:O(N)
空间复杂度: O(1)
稳定性: 稳定排序
!!!对于直接插入排序来说,数据越有序,速度越快
经常使用在 数据量不多,且整体数据趋于有序
*/
public void insertSort(int[] array){//插入排序
//若数组只含有一个元素,则这个数组肯定有序,所以要从第二个元素开始进行插入
//最多 n-1 轮
for (int i = 1; i < array.length; i++) {
int tmp = array[i];
int j = i-1;
for (; j >= 0; j--) {
if(array[j] > tmp){
array[j+1] = array[j];
}else{
break;
}
}
//j回退到小于0的地方
array[j+1] = tmp;
}
}
希尔排序
又称缩小增量排序,基本思路是分组的直接插入排序
/*
时间复杂度:和增量有关
空间复杂度:O(1)
稳定性:不稳定排序
*/
public void shellSort(int[] array,int gap){//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;
}
}
public void shell(int[] array){
int gap = array.length/2;
while(gap > 1){
shellSort (array,gap);
gap = gap/2;
}
shellSort(array,1);//保证最后是1组
}
选择排序
先让第一个元素不动,从第二个元素开始寻找值最小的元素和第一个元素进行比较,若第一个元素的值大于所寻找的最小元素,则进行交换,接着让第二个元素不动,从第三个元素开始寻找值最小的元素,若第二个元素的值大于所寻找的最小元素,则进行交换,以此类推
/*
时间复杂度:O(n^2)
时间复杂度:O(1)
稳定性:不稳定排序
*/
public void selectSort(int[] array){
for (int i = 0; i < array.length-1; i++) {
int min = i+1;
for (int j = i+1; j < array.length; j++) {
if(array[min] > array[j]){
//找到最小值的下标
min = j;
}
}
if(array[i] > array[min]){
int tmp = array[i];
array[i] = array[min];
array[min] = tmp;
}
}
}
堆排序
升序是大根堆,降序是小根堆
建立大根堆:
向下调整进行排序:
/*
时间复杂度:O(n * log n)
空间复杂度:O(1)
稳定性:不稳定排序
*/
//升序是大根堆,降序是小根堆
public void heapSort(int[] array){
//1.建堆:O(n)
createHeap(array);
int end = array.length-1;
//交换然后调整 O(log n)
while(end > 0){
int tmp = array[0];
array[0] = array[end];
array[end] = tmp;
shiftDown(array,0,end);
end--;
}
}
public void createHeap(int[] array){
for (int parent = (array.length-1-1)/2; parent >= 0; parent--){
shiftDown(array,parent,array.length);
}
}
public void shiftDown(int[] array,int parent,int len){
int child = 2*parent+1;//左孩子下标
while(child < len ){
if(child+1 < len && array[child] < array[child+1]){
child++;
}
//child 下标就是左右孩子最大值的下标
if(array[child] > array[parent]){
int tmp = array[child];
array[child] = array[parent];
array[parent] = tmp;
parent = child;
child = 2*parent+1;
}else{
break;
}
}
}
快速排序
首先在序列中找一个数作为基准数,下图以 2 为基准数,接下来,需要将这个序列中所有比基准数大的数放在 2 的右边,比基准数小的数放在 2 的左边,然后对 2 左和右以同样的方式进行排序
/*
时间复杂度: 最坏情况,数据有序:O(n^2)
最好情况,每次均匀分割序列:O(n * log n)
空间复杂度: 最坏,单分支:O(n)
最好:O(log n)
稳定性:不稳定排序
*/
public void quickSort(int[] array){
quick(array,0,array.length-1);
}
public void quick(int[] array,int start,int end){
if(start >= end){
return;
}
int set = setFlg(array,start,end);
quick(array,start,set-1);
quick(array,set+1,end);
}
public int setFlg(int[] array,int left,int right){
int tmp = array[left];
while(left < right){
while(left < right && array[right] >= tmp){
right--;
}
//right 遇到小于 tmp 的值
array[left] = array[right];
while(left < right && array[left] <= tmp){
left++;
}
//left 遇到了大于 tmp 的值
array[right] = array[left];
}
//相遇
array[left] = tmp;
return left;
}
优化:
三数取中:
/*
防止单支
优化快排:随机基准法:随机三个取中间大小的值
*/
public int findMidValFlg(int[] array,int left,int right){
int mid = left + ((right - left) >>>1);
if(array[left] < array[right]) {
if (array[mid] < array[left]) {
return left;
} else if (array[mid] > array[right]) {
return right;
} else {
return mid;
}
}else{
if(array[mid] > array[left]){
return left;
}else if(array[mid] < array[right]){
return right;
}else{
return mid;
}
}
}
public void quick_01(int[] array,int start,int end){
if(start >= end){
return;
}
int midval = findMidValFlg(array,start,end);
if(array[midval] > array[start]){
int tmp = array[midval];
array[midval] = array[start];
array[start] = tmp;
}
int set = setFlg(array,start,end);
quick(array,start,set-1);
quick(array,set+1,end);
}
利用直接插入排序:
/*
优化快排:随机基准法:随机三个取中间大小的值
把基准相同的数据从两边移到基准附近
利用直接插入排序越有序速度越快的特点
*/
public void insertSort_01(int[] array,int start,int end){//插入排序
//在 start - end 范围内
for (int i = 1; i <= end; i++) {
int tmp = array[i];
int j = i-1;
for (; j >= start; j--) {
if(array[j] > tmp){
array[j+1] = array[j];
}else{
break;
}
}
array[j+1] = tmp;
}
}
public void quick_011(int[] array,int start,int end){
if(start >= end){
return;
}
if(end-start+1 <= 20){//40自己定,只是一个范围
//直接插入排序
return;
}
int midval = findMidValFlg(array,start,end);
if(array[midval] > array[start]){
int tmp = array[midval];
array[midval] = array[start];
array[start] = tmp;
}
int set = setFlg(array,start,end);
quick(array,start,set-1);
quick(array,set+1,end);
}
非递归:
/*
非递归快排:用栈
先找第一次基准,把左边start,end 右边start,end 放入栈
然后弹出两个,在这两个区间找基准,找到基准后继续左右的 start,end 入栈
入栈前提:基准左边有两个元素:flg > left+1
右边有两个元素: flg < right-1
*/
public void quickSort_01(int[] array){
Stack<Integer> stack = new Stack<>();
int left = 0;
int right = array.length-1;
int flg = setFlg(array,left,right);
if(flg > left+1){
//左边有两个元素
stack.push(left);
stack.push(flg-1);
}
if(flg < right-1){
stack.push(flg+1);
stack.push(right);
}
while(!stack.isEmpty()){
right = stack.pop();
left = stack.pop();
flg = setFlg(array,left,right);
if(flg > left+1){
//左边有两个元素
stack.push(left);
stack.push(flg-1);
}
if(flg < right-1){
stack.push(flg+1);
stack.push(right);
}
}
}
归并排序
将两个或两个以上的有序表合并成一个有序表,通过两两合并有序序列之后再合并,最终获得一个有序序列
/*
归并排序
时间复杂度:O(n * log n)
空间复杂度:O(n)
稳定性:稳定排序,s1 <= e1 && s2 <= e2,不取等号就不稳定
*/
public void mergeSort(int[] arr){
mergeS(arr,0,arr.length-1);
}
public void mergeS(int[] arr,int low,int high){
if(low >= high){
return;
}
int mid = low + ((high-low) >>> 1);
mergeS(arr,low,mid);
mergeS(arr,mid+1,high);
merge(arr,low,mid,high);
}
public void merge(int[] arr,int low,int mid,int high){
int[] tmp = new int[high-low+1];
int k = 0;
int s1 = low;
int e1 = mid;
int s2 = mid+1;
int e2 = high;
while(s1 <= e1 && s2 <= e2){
if(arr[s1] <= arr[s2]){
tmp[k++] = arr[s1++];
/*tmp[k] = arr1[s1];
k++;
s1++;*/
}else{
tmp[k++] = arr[s2++];
}
}
while(s1 <= e1 ){// arr2 空了
tmp[k++] = arr[s1++];
}
while(s2 <= e2){// arr1 空了
tmp[k++] = arr[s2++];
}
//拷贝 tmp 数组的元素放入原来的数组 arr 当中
for (int i = 0; i < k; i++) {
arr[i+low] = tmp[i];
}
}
非递归实现:
public void mergeSort_01(int[] arr){
int num = 1;
while(num < arr.length){
//数组每次都要遍历
for (int i = 0; i < arr.length; i += num*2) {
int left = i;
int mid = (left+num-1);
//防止越界
if(mid >= arr.length){
mid = arr.length-1;
}
int right = mid+num;
if(right >= arr.length){
right = arr.length-1;
}
//下标确定之后进行合并
merge(arr,left,mid,right);
}
num *= 2;
}
}
比较
排序方法 | 时间复杂度 | 最坏情况 | 最好情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
快速排序 | O(n lb n) | O(n^2) | O(n lb n) | O(n) | 不稳定 |
直接插入排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
希尔排序 | 与增量有关 | O(1) | 不稳定 | ||
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n lb n) | O(n lb n) | O(n lb n) | O(1) | 不稳定 |
归并排序 | O(n lb n) | O(n lb n) | O(n lb n) | O(n) | 稳定 |
计数排序
//计数排序,适合数据范围 0~n 之间的
//时间复杂度:O(n)
//空间复杂度:O(M):M代表当前数据的范围
//稳定性:当前不稳定,本质稳定
public void countSort(int[] array){
int maxVal = array[0];
int minVal = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i] < minVal){
minVal = array[i];
}
if(array[i] > maxVal){
maxVal = array[i];
}
}
//此时找到最大最小
int[] count = new int[maxVal-minVal+1];
for (int i = 0; i < array.length; i++) {
int index = array[i];
count[index-minVal]++;
}
//计数数组当中,出现次数已统计完
//接下来输出
int indexArray = 0;
for(int i = 0;i < count.length;i++){
while(count[i] > 0){
//加 minVal
array[indexArray] = i+minVal;
count[i]--;//拷贝一个,计数少一个
indexArray++;//下标向后移动
}
}
}