1. 冒泡排序
思路:前一个数和后一个数相互比较,大则向后走,直到数组结束
使用最后一个数往前,可以减少循环次数,因为一次冒泡下去,最大数一定位于最后一个
时间复杂度: O(n^2) 空间复杂度O(1)
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int e = arr.length - 1; e > 0; e--) {
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
2. 插入排序
插入排序设计到一个局部的交换,每次交换,目的就是把小的数往前面插,插入排序从遍历的第一个数之前进行,当下标满足≥0的情况的时候,且后一个值小于自己的时候,就开始交换,此时触发自己向前比较,看看有没有比自己大的数需要插到自己的后面来。
时间复杂度:O(n^2) 空间复杂度:O(1)
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
//判断下标,判断后一个值是否比自己小,是的话交换,然后继续对比之前的
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
3. 选择排序
设置一个最小下标变量(设置为当前遍历的就好),然后遍历之后的所有的数,看是否有比自己小的数,有的话,下边变为此小数的下标。出来做一次交换,第一个数就是最小的,然后再次遍历,第二个数第二小…
时间复杂度:O(n^2) 空间复杂度:O(1)
public static void selectionSort(int[] arr) {
if(arr==null || arr.length<2){
return;
}
for (int i = 0; i < arr.length; i++) {
//设置一个最小值
int minIndex = i;
for (int j = i+1; j < arr.length; j++) {
minIndex = arr[minIndex] > arr[j] ? j : minIndex;
}
swap(arr,i,minIndex);
}
}
4. 堆排序
堆排序思想:首先将数组排成一颗树,数的结构是认为构造的!按照顺序即可,然后对数进行一个调整,使得树为大根堆,就是根节点比左右节点都大。然后进行首尾交换,因为首部的也就是头节点的数一定是最大的,交换后将数组长度减一,此时最大值就在最后了。
然后就进行一个调整,计算出左节点的值,如果左节点没有越界,就判断右节点在不越界的情况下与左节点的值的大小,得出较大那个节点的下标。判断在此下标中数组的值与父节点的值的大小。大的话需要判断与父节点下标,不相等需要交换值,然后对父节点的index
置于当前交换的节点处,然后计算此节点的左节点,继续循环,
建堆时间复杂度:O(n), 堆排序时间复杂度:O(nlogn),总的时间复杂度为:O(nlogn), 空间复杂度:O(1)
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
//堆插入
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
int size = arr.length;
//交换首尾
swap(arr, 0, --size);
while (size > 0) {
//堆调整
heapify(arr, 0, size);
//再次交换首尾
swap(arr, 0, --size);
}
}
//建造大根堆
public static void heapInsert(int[] arr, int index) {
//(i-1)/2:表示父节点
// 2*i+1:表示左节点
// 2*i+2:表示右节点
while (arr[index] > arr[(index - 1) / 2]) {
//交换两者位置
swap(arr, index, (index - 1) / 2);
//交换下标为进一步对比父节点
index = (index - 1) / 2;
}
}
//首尾交换之后,最大值已经排出,需要将第二大的数放置在最上的节点处,做调整
public static void heapify(int[] arr, int index, int size) {
//选定左节点
int left = index * 2 + 1;
//判断左节点的下标没有越界
while (left < size) {
//判断右节点没有越界的情况下左右节点的大小情况
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
//判断左右节点中数值较大的节点的下标的值与头结点值的大小
largest = arr[largest] > arr[index] ? largest : index;
//判断下标与index相等,可以判定此时就是同一个值,循环终止
if (largest == index) {
break;
}
//交换左右节点中较大的值与父节点的值
swap(arr, largest, index);
//将左或者右的小标赋给index
index = largest;
//继续对下一个分支做处理
left = index * 2 + 1;
}
}
5. 快速排序
快速排序的原理:以需要排序的数组的最后一个数为基准,排出前面是小于这个数的,后面是大于这个数的
如下:
3 2 8 7 4
第一次:less=-1,l=0,more=r=4
l<more 且arr[l]<arr[r] 此时++less与l值交换,相当于less前移一位,l前移一位 3比较完 下一个元素2
l<more 且arr[l]<arr[r] 此时++less与l值交换,相当于less前移一位,l前移一位 2比较完 下一个元素8
l<more 但arr[l]>arr[r] 此时–more,交换,l不变,less不变,就是将l位置的值移到r位置之前(最后需要交换),现在数组中的情况是3 2 4 7 8,8比较完
l<more 且arr[l]<arr[r] 此时++less与l值交换,相当于less前移一位,l前移一位 4比较完 下一个元素7
l<more 但arr[l]>arr[r] 此时–more,交换,l不变,less不变,就是将l位置的值移到r位置之前(最后需要交换),现在数组中的情况是3 2 4 7 8,因为more移到了l的位置,相当于,没有变化,此时7比较完,l==more,循环结束
然后交换刚刚没有交换的值即4与8交换,此时数组为3 2 8 7 4
返回的两个数的数组为[less+1,more]即[2,3]
此时继续做调整,此时的话,3位置的数为4,在4之前都是小于4的,4之后都是大于4,继续重复上面的步骤即可。
时间复杂度:O(nlogn) 空间复杂度: O(1)
public static void quickSort(int[] arr){
if (arr==null || arr.length<2){
return;
}
quickSort(arr,0,arr.length-1);
}
private static void quickSort(int[] arr, int l, int r) {
//运用递归就是一个调整过程
if(l < r){
//调整
int[] p = partation(arr,l,r);
//返回两个数的数组,一个是前半部分的,一个是后半部分的
quickSort(arr,l,p[0]-1);
quickSort(arr,p[1]+1,r);
}
}
private static int[] partation(int[] arr, int l, int r) {
int less = l-1;
int more = r;
while(l < more){
if(arr[l] < arr[r]){
swap(arr, ++less, l++);
}else if(arr[l] > arr[r]){
//这里没有交换值,知识more往需要与r位置上的数交换的位置走
swap(arr, --more, l);
}else{
l++;
}
}
//交换r和more的值
swap(arr, more, r);
return new int[]{less+1, more};
}
private static void swap(int[] arr, int l, int r) {
int temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
}
6. 归并排序
归并排序运用了分治的思想,这里的归并排序主要是利用了将需要排序的数组分成若干个小的部分,小的部分排序,然后在归并为一个大的,在归并中利用双指针来对每一个小组中的左右两部分做排序处理,最后可以得到左右均是排好序的数组,这时最后一次大的归并可以排好所有的数组中的数。
时间复杂度:O(nlogn) 空间复杂度:n最后一次+logn每个小次 取大的为O(n)
public class MergeSort {
public static void main(String[] args) {
int[] arr = new int[]{1,3,5,9,7,0,2,8};
mergeSort(arr);
for (int i : arr) {
System.out.print(i+" ");
}
}
public static void mergeSort(int[] arr){
//边界判断
if(arr.length <= 1 || arr == null){
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int l, int r){
//终止条件
if(l==r){
return;
}
int m = l + ((r-l)>>1);
mergeSort(arr, l, m);
mergeSort(arr, m+1, r);
merge(arr, l, m, r);
}
public static void merge(int[] arr, int l, int m, int r){
//构建一个数组,此时左右是排好序的,只是排序一下大家
int[] help = new int[r-l+1];
int p1 = l;
int p2 = m+1;
int i = 0;
while(p1<=m && p2<=r){
help[i++] = arr[p1]<arr[p2] ? arr[p1++] : arr[p2++];
}
while(p1 <= m){
help[i++] = arr[p1++];
}
while(p2<=r){
help[i++] = arr[p2++];
}
for (int j = 0; j < help.length; j++) {
arr[l+j] = help[j];
}
}
}
7. 桶排序
桶排序原理很简单,就是构建数组中最大值个数目的桶,然后通过遍历将数放进对应的桶中,标识1,表明桶中有数,放完之后,遍历桶,只要有数就取出编号即可,依次取出则是排序的过程。这个桶排序耗时还是比较多的。后面有更加细一点的桶排序。
public static void bucketSort(int[] arr){
//判断边界问题
if(arr == null || arr.length < 2){
return;
}
//获取数组中最大的值,以便构建桶数目
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
//创建桶
int[] buckets = new int[max+1];
//将所需要排序的数放进相应的桶中
for (int i = 0; i < arr.length; i++) {
buckets[arr[i]]++;
}
//从桶中依次取出可以得到排好序的数组
int i = 0;
for (int j = 0; j < buckets.length; j++) {
//如果此桶中有数,取出
if(buckets[j]-- > 0){
arr[i++] = j;
}
}
}
8. 基数排序
基数排序:基数排序是桶排序法的升级版,其实原理也差不多,不过基数排序的好处就是每一次都能统一数组中所有数的一位是排好序的。举个例子:
1 43 23 21 22 56 --------个位 1 22 43 56 21 23 --------十位 1 21 43 56 22 23 --------排好序
上述的例子是两位的,第一次会发现在个位上是排好序的,此时赋值给数组,可以知道个位是排好序的,再从十位来就可以把它们都正确的放在属于自己的位置。
时间复杂度:O(N*M)(N是数据个数,M是数据最大位数) 空间复杂度:空间复杂度O(M)
public static void radixSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
radixSort(arr, 0, arr.length - 1, maxBits(arr));
}
private static void radixSort(int[] arr, int begin, int end, int digit) {
//基数
final int radix = 10;
int i = 0;
int j = 0;
//有多少可以存桶
int count[] = new int[radix];
//构建桶存储数据
int[] bucket = new int[end - begin + 1];
for (int d = 1; d <= digit; d++) {
//初始化桶中的数据个数
for (i = 0; i < radix; i++) {
count[i] = 0;
}
//统计每一个桶中的个数
for (i=begin; i<=end; i++) {
j = getDigit(arr[i],d);
count[j]++;
}
//设置一个右边界,主要目的是区分开不同的桶
for(i=1; i < radix; i++){
count[i] = count[i]+count[i-1];
}
//从后往前放入桶中,要是从前往后的话,同一个桶的数会逆序
for(i=end; i>=begin; i--){
j = getDigit(arr[i],d);
bucket[count[j]-1] = arr[i];
count[j]--;
}
//可以对数组中任意位置排序,如果是一个数组,可以不用写j
for(i=begin, j=0; i<=end; i++,j++){
arr[i] = bucket[j];
}
}
}
//获取相应位置上的数
public static int getDigit(int num, int d){
return (int)(num/(Math.pow(10, d-1))%10);
}
//获取数组中最大数的位数
private static int maxBits(int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int bits = 0;
while(max != 0){
bits++;
max /= 10;
}
return bits;
}
9. 荷兰国旗问题的排序
这个排序是以一个中间值为参考点,大于它的在其后,小于在之前。荷兰国旗三色问题就是这个意思。
public static void partition(int[] arr, int s, int e, int p){
//最前面的指针用于交换小值
int l = s - 1;
//最后面的指针用于交换大值
int r = e + 1;
//s与r碰面就停止
while(s < r){
//小于参考点就交换
if (arr[s] < p){
swap(arr, ++l, s++);
//大于参考点的话不能移动交换,因为不能确保交换后的值一定是p
}else if (arr[s] > p){
swap(arr, --r, s);
}else {
//相等直接扩展自己相同的区域就好
s++;
}
}
}
public static void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}