选择排序及优化
特点:
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
选择排序(SelectionSort) | n^2 | n^2 | n^2 | 1 | 不稳定 |
选择排序(Selection),平均时间复杂度、最好/最坏时间复杂度都是n^2,空间复杂度为1,不稳定。
思想
选择排序的思想就是,假设数组(arr)的第一个元素(arr[0])为最大/最小值,并记下它的角标 index=0。然后依次同数组的其他元素比较,如果被比较元素大于/小于该元素,则记录被比较元素并替代index。
如此,每一轮遍历都会将最大值/最小值移动到最左边。从而完成排序。区别与冒泡排序的数组间相邻元素的两两交换,选择排序是将这个比较放到整个数组的元素之间。
代码如下:
public class SelectionSort {
public static void main(String[] args) {
int[] arr = {10,4,5,8,17,6,11,12,23,32,34,95,54,56,77,88,21,43,45,65,23,43,23};
for (int j = 0; j < arr.length - 1; j++) {
int minPos = j;
for (int i = j+1; i < arr.length; i++) {
minPos = arr[i] < arr[minPos] ? i : minPos;
}
swap(arr,minPos,j);
}
print(arr);
}
public static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void print(int arr[]){
System.out.println(Arrays.toString(arr));
}
}
优化之后
优化思想:选择排序每次循环是将最小值/最大值比较之后放到最数组最左侧,那么如果我们每次循环的时候,同时找出最小值和最大值并分别放到数组最左侧和最右侧。如此程序便得到了优化。
代码如下:
public class SelectionSort {
public static void main(String[] args) {
int[] arr = {10,4,5,8,17,6,11,12,23,32,34,95,54,56,77,88,21,43,45,65,23,43,23};
int left = 0;
int right = arr.length-1;
while (left < right){
int minPos = left;
int maxPos = right;
for (int i = left; i <= right; i++) {
minPos = arr[i] < arr[minPos] ? i : minPos;
maxPos = arr[i] > arr[maxPos] ? i : maxPos;
}
swap(arr,maxPos,right);
// 处理特殊情况,最大值和最小值的位置刚好相反。
if (minPos == right){
minPos = maxPos;
}
swap(arr,minPos,left);
left++;
right--;
}
print(arr);
}
public static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void print(int arr[]){
System.out.println(Arrays.toString(arr));
}
}
冒泡排序及优化
特点
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序(Bubble) | n^2 | n^2 | n | 1 | 稳定 |
思想
冒泡排序每次比较数组中相邻的两个元素。举个例子,五个高矮不同的人排队,此时排在第一个的人比第二个高一点,组长让他和第二个人换个位置。换完之后发现它比第三个人还是高一点(此时他在第二个人位置)。以此类推。
代码如下:
public class BubbleSort {
public static void main(String[] args) {
int[] arr1 = {2,4,7,1,4,7,5,9,5,6,5,3};
sort(arr1);
print(arr);
}
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] > arr[j + 1]) {
swap(arr, j);
}
}
}
}
static void swap(int[] arr,int j){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
然而上面的代码的时间复杂度 O(n^2) 但是冒泡算法的最好时间复杂度却为O(n),问题在于整个代码需要优化。
代码如下:
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {2,4,7,1,4,7,5,9,5,6,5,3};
int[] arr2 = {3, 2, 1, 5, 4, 4, 5, 5, 6, 7, 7, 9};
sort(arr);
sort(arr2);
print(arr);
System.out.println("---------------");
print(arr2);
}
static void sort(int[] arr){
boolean flag = false;
int count = 0;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
count++;
if (arr[j] > arr[j + 1]) {
flag = true;
swap(arr, j);
}
}
if(!flag){
break;
}else {
flag = false;
}
}
System.out.println("循环次数: "+count);
}
static void swap(int[] arr,int j){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
插入排序及优化
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
插入排序(Insertion) | n^2 | n^2 | n | 1 | 稳定 |
思想
插入排序每次在待排序的区间内选定一个假设的最小值/最大值,保存角标和值。分别和已经排过序的区间一一比较,并插入合适位置,类似与打牌时,每次取出一张牌插入到手里已经排过序的牌里。
代码如下;
public static void main(String[] args) {
int[] arr = {13,5,24,5,1,23,16,13,87,23};
sort(arr);
print(arr);
}
static void sort(int[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = i; j > 0 && arr[j] < arr[j-1]; j--) {
swap(arr,j,j-1);
}
}
}
static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
优化之后:
public static void main(String[] args) {
int[] arr = {13,5,24,5,1,23,16,13,87,23};
sort(arr);
print(arr);
}
static void sort(int[] arr){
int minIndex = 0;
int minValue = 0;
for (int i = 1; i < arr.length; i++) {
minIndex = i;
minValue = arr[i];
for (int j = i; j > 0 && minValue < arr[j-1]; j--) {
arr[j] = arr[j-1];
minIndex = j-1;
}
if (minIndex != i){
arr[minIndex] = minValue;
}
}
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
希尔排序
特点
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
希尔排序(shell) | n^1.3 | n^2 | n | 1 | 不稳定 |
思想
插入排序可以看成是一种间隔为1的希尔排序,希尔排序主要通过设定间隔(gap)来提高算法的效率。当间隔大的时候,移动次数少,当间隔小的时候,移动距离短。
代码如下:
public class ShellSort {
public static void main(String[] args) {
int[] arr = {32,4,1,3,54,65,1,3,5,32,567,23,33};
sort(arr);
print(arr);
}
static void sort(int[] arr){
// 使用shell来设置间隔(本质上是插入排序)
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j > gap-1 ; j -= gap) {
if(arr[j] < arr[j-gap]){
swap(arr,j,j-gap);
}
}
}
}
}
static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
优化之后:
由于shell排序是通过控制间隔来实现优化,所以好的间隔能够提高算法的效率
使用Knuth序列 公式h=3*h+1
public class ShellSort {
public static void main(String[] args) {
int[] arr = {32,4,1,3,54,65,1,3,5,32,567,23,33};
sort(arr);
print(arr);
}
static void sort(int[] arr){
// 检测使用 knuth序列设置间隔
int h = 1;
while (h <= arr.length / 3){
h = h * 3 + 1;
}
for (int gap = h; gap > 0; gap = (gap-1)/3) {
for (int i = gap; i < arr.length; i++) {
for (int j = i; j > gap-1 ; j-=gap) {
if(arr[j] < arr[j-gap]){
swap(arr,j,j-gap);
}
}
}
}
}
static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
归并排序
特点
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
归并排序(merge) | nlog2^n | nlog2^n | nlog2^n | n | 稳定 |
思想
归并排序采用分治的思想,将大的数列通过递归不断划分成小数组,然后排序。在归并到一起。
public class MergeSort {
public static void main(String[] args) {
int[] arr = {3,4,7,2,6,2,5,3,5};
sort(arr,0,arr.length-1);
print(arr);
}
static void sort(int[] arr,int left,int right){
if(left == right){
return;
}
int mid = left + (right - left) / 2;
// 左边排序
sort(arr,left,mid);
// 右边排序
sort(arr,mid+1, right);
merge(arr,left,mid+1,right);
}
static void merge(int[] arr,int leftPtr,int rightPtr, int rightBound){
int mid = rightPtr - 1;
int[] temp = new int[rightBound - leftPtr + 1];
int i = leftPtr;
int j = rightPtr;
int k = 0;
while (i <= mid && j <= rightBound){
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i <= mid){
temp[k++] = arr[i++];
}
while (j <= rightBound){
temp[k++] = arr[j++];
}
for (int m = 0; m < temp.length; m++) {
arr[leftPtr + m] = temp[m];
}
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
快速排序
特点
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
快速排序(quick) | nlog2^n | n^2 | nlog2^n | nlog2^n | 不稳定 |
思想
通过设定一个中间值,并以此值为间隔将数列分为大于该数值和小于该数值的区间。并通过递归,不断重复这个操作,最终实现数列有序。
public class QuickSort {
// 设定最右边的值为中间值
public static void main(String[] args) {
int[] arr = {7,3,2,10,8,1,9,5,4,6};
sort(arr,0,arr.length-1);
print(arr);
}
static void sort(int[] arr,int leftBound,int rightBound){
if(rightBound <= leftBound){
return;
}
int mid = quick(arr, leftBound, rightBound);
sort(arr,leftBound,mid-1);
sort(arr,mid + 1,rightBound);
}
static int quick(int[] arr,int leftBound,int rightBound){
int pivot = arr[rightBound];
int left = leftBound;
int right = rightBound - 1;
while (left < right){
while (arr[left] <= pivot && left <= right){
left++;
}
while (arr[right] > pivot && left < right){
right--;
}
if(left < right){
swap(arr,left,right);
}
}
if(pivot < arr[left]){
swap(arr,left,rightBound);
}
return left;
}
static void swap(int[] arr,int l,int r){
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
// 以最左侧的值为中间值
static int quick(int[] arr,int leftPst,int rightPst){
int pivot = arr[leftPst];
int left = leftPst;
int right = rightPst;
while (left < right){
while (arr[right] > pivot && left < right){
right--;
}
if(left < right){
arr[left] = arr[right];
left++;
}
while (arr[left] < pivot && left < right) {
left++;
}
if(left < right){
arr[right] = arr[left];
right--;
}
}
arr[left] = pivot;
return left;
}
计数排序
特点
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
计数排序(counting) | n+k | n+k | n+k | n+k | 稳定 |
思想
桶排序的思想,针对数据量大,范围小的数据
public class CountSort {
public static void main(String[] args) {
int[] arr = {1,5,7,6,10,4,6,2,7,1,2,4,5,8,9,3,4,2};
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i] > max){
max = arr[i];
}
}
int[] sort = sort(arr, max);
print(sort);
}
static int[] sort(int[] arr,int max){
// 定义一个范围的数组
int[] result = new int[arr.length];
int count[] = new int[max+1];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
// 算法是不稳定的
for (int i = 0, k = 0; i < count.length; i++) {
while (count[i]-- > 0){
result[k++] = i;
}
}
return result;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
如上的算法是不稳定的。可以通过小技巧来处理,通过将Count数组的每两位值相加,这样可以通过后一个减去前一个得到当前count记录的“桶”里的个数。然后将原数组倒序取出,并通过处理后的count放进新的数组中。由于count对“桶”内个数累加的时候是正序,最后通过倒叙取出并不会改变原相同数据的相对前后位置。
public class CountSort {
public static void main(String[] args) {
int[] arr = {1,5,7,6,10,4,6,2,7,1,2,4,5,8,9,3,4,2};
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[i] > max){
max = arr[i];
}
}
int[] sort = sort(arr, max);
print(sort);
}
static int[] sort(int[] arr,int max){
// 定义一个范围的数组
int[] result = new int[arr.length];
int count[] = new int[max+1];
for (int i = 0; i < arr.length; i++) {
count[arr[i]]++;
}
// 解决技巧
for (int i = 1; i < count.length; i++) {
count[i] = count[i] + count[i-1];
}
for (int i = arr.length-1; i >= 0; i--) {
result[--count[arr[i]]] = arr[i];
}
return result;
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}
基数排序
特点
名称 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
基数排序(radix) | n*k | n*k | n*k | n+k | 稳定 |
public class RadixSort {
public static void main(String[] args) {
int[] arr = {321,453,543,754,222,421};
sort(arr);
print(arr);
}
static void sort(int[] arr){
int result[] = new int[arr.length];
int count[] = new int[10];
for (int i = 0; i < 3; i++) {
int division = (int) Math.pow(10,i);
for (int j = 0; j < arr.length; j++) {
count[arr[j] / division % 10]++;
}
for (int m = 1; m < count.length; m++) {
count[m] = count[m] + count[m-1];
}
for (int n = arr.length-1; n >= 0 ; n--) {
result[--count[arr[n] / division % 10]] = arr[n];
}
System.arraycopy(result,0,arr,0,arr.length);
// 将Count数组全部填充为0
Arrays.fill(count,0);
}
}
static void print(int[] arr){
System.out.println(Arrays.toString(arr));
}
}