数据结构与算法——非线性排序
1、冒泡排序
以升序为例
每次循环都从头开始,若当前元素比下一个元素大,就交换
冒泡排序无需额外空间,是原地排序算法,空间复杂度为O(1)O(1)O(1),平均时间复杂度为O(n2)O(n^2)O(n2)
public class BubbleSort {
public static void sort(int[] arr){
for (int i=0;i<arr.length-1;i++){
for (int j=0;j<arr.length-i-1;j++){
if(arr[j+1] < arr[j]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
2、插入排序
以升序为例
将数组分为已排序和未排序前后两部分,将未排序元素依次和已排序元素比较,将已排序元素依次移位,并将当前未排序元素插入
插入排序也是原地排序算法,空间复杂度为O(1)O(1)O(1),平均时间复杂度为O(n2)O(n^2)O(n2)
public class InsertionSort {
public static void sort(int[] arr){
//未排序元素,i和i-1就相当于已排序和未排序的分界线,逐渐向后挪
for (int i=1;i<arr.length;i++){
//缓存当前需要插入到前面的元素
int temp = arr[i];
int j=i-1;
//从上一个元素(也就是已排序元素组的最后一个元素)向前找
for (;j>=0;j--){
if(arr[j] > temp){
//比较元素并移动
arr[j+1] = arr[j];
}else {
//说明j之前的元素都排好了,break减少循环次数
break;
}
}
//插入元素
arr[j+1] = temp;
}
}
}
3、选择排序
每次循环从未排序区域遍历到最后,找到最小的元素,与当前的元素交换位置
选择排序的空间复杂度是O(1)O(1)O(1),时间复杂度是O(n2)O(n^2)O(n2)
由于跨位交换会使相同元素的先后顺序发生改变,所以是不稳定排序
public class SelectionSort {
public static void sort(int[] arr){
for (int i=0;i<arr.length-1;i++){
int tempIndex = i;
for (int j=i+1;j<arr.length;j++){
if(arr[j] < arr[tempIndex]){
tempIndex = j;
}
}
if(tempIndex != i){
int temp = arr[i];
arr[i] = arr[tempIndex];
arr[tempIndex] = temp;
}
}
}
}
4、归并排序
递归将数组从中间一分为二,对两个数组分别排序,然后合并
归并排序的时间复杂度和空间复杂度都是O(nlogn)O(nlog_{}{n})O(nlogn)
public class MergeSort {
public static int[] sort(int[] arr) {
return sort(arr, 0, arr.length - 1);
}
public static int[] sort(int[] arr, int start, int end) {
if (start >= end) {
return new int[]{arr[start]};
}
//将数组从中间分割,左右分别排序,再合并
int mid = (end + start) / 2;
int[] left = sort(arr, start, mid);
int[] right = sort(arr, mid + 1, end);
return merge(left, right);
}
private static int[] merge(int[] left, int[] right) {
int[] arr = new int[left.length + right.length];
int j = 0;
int i = 0;
for (; i < left.length; i++) {
for (; j < right.length; j++) {
if (right[j] < left[i]) {
//右侧数组当前元素比左侧数组当前元素小,就将小的右侧数组元素放到新数组
arr[i + j] = right[j];
} else {
//左侧数组当前元素比左侧数组当前元素小,就不继续遍历右侧数组,直接将小的左侧数组元素放到新数组
break;
}
}
arr[i + j] = left[i];
}
//处理右侧数组剩余元素大于左侧数组最大元素,导致左侧数组遍历完,右侧数组还有剩余元素的情况
for (; j < right.length; j++) {
arr[i + j] = right[j];
}
return arr;
}
}
5、快速排序
随机选择数组中的一个元素作为标准(一般是数组中最后一个元素),根据标准元素将剩余元素重新排列成大于标准和小于标准的左右两部分,把标准元素放到中间,这个操作叫分区,然后对左右两部分再递归分区
快排大部分情况下的时间复杂度是O(nlogn)O(nlog{}{n})O(nlogn),最坏情况下是O(n2)O(n^2)O(n2)
public class FastSort {
public static void sort(int[] arr){
sort(arr,0,arr.length-1);
}
private static void sort(int[] arr,int start,int end){
if(start >= end){
return;
}
//随机选取当前区域中的一个元素作为标准),区域中小于标准的放左面,大于标准的放右面
//分区操作后的子区域是无序的,其顺序由递归的分区排序来保证
int pivotIndex = partition(arr, start, end);
sort(arr,start,pivotIndex-1);
sort(arr,pivotIndex+1,end);
}
private static int partition(int[] arr,int start,int end){
//举例选取末位元素为标准
int pivot = arr[end];
int i=start;
int j=start;
//将当前区域分为前后两个子区域,分别对应外层和内层循环
loop: for (;i<end;i++){
if(arr[i] <= pivot){
//从当前区域的头开始,小于标准的元素不动
j++;
continue;
}
//当前元素大于标准,就从后面找小于标准的元素跟他交换
for (;j<end;j++){
if(arr[j] < pivot){
//后面的元素小于标准,就跟前面的大于标准的元素交换
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
//现在前面的当前元素已经小于标准,就从下一个前面的元素继续找
continue loop;
}
}
//后面的元素找完了,也没有比标准再小的了
break;
}
//把标准元素(数组最后一个元素)跟后面区域的第一个位置的大元素交换位置
int temp = arr[i];
arr[i] = arr[end];
arr[end] = temp;
//现在,前面的元素都小于标准,后面的元素都大于标准,且是无序的
return i;
}
}