算法学习笔记--排序

103 阅读3分钟
1.概论

1.固定时间操作:int ---32位 固定时间的操作 比如 数组 在内存是连续的位置

但例如LinkedList 在内存中是一个节点一个节点的查找 ---->>> 所以不是固定时间的操作

2.时间复杂度 O()

image.png

将所有的常数的去掉,得到最高阶的部分,an^2 + bn + c ---->>>> O(n^2)

常数时间的操作 确定算法流程的总操作数量与样本数量之间的表达式关系 只看表达式最高阶项的部分

image.png

3.排序算法

(1)选择排序

	public static void selectionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = 0; i < arr.length - 1; i++) {
			int index = i;
			for (int j = i + 1; j < arr.length; j++) {
				index = arr[j] < arr[index] ? j:index;
			}
			swap(arr,i,index);
		}
	}
    
	public static void swap(int[] arr,int i,int j) {
			int temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
	}
}

(2)冒泡排序

	public static int[] BubbleSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return arr;
		}
		for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 -j; j ++) {
                if (arr[j] > arr[j+1]) {
                    int tmp = arr[i];
                    arr[i] = arr[j+1];
                    arr[j+1] = tmp;
                }
            }
        }
}

public static int[] bubbleSort(int[] arr) {//代码优化
     if (arr == null || arr.length < 2) {
          return arr;
     }
    for (int i = 0; i < arr.length - 1; i++) {
         boolean isSorted  = true;//有序标记,每一轮的初始是true
         for (int j = 0; j < arr.length -i - 1; j++) {
             if (arr[j + 1] < arr[j]) {
                 isSorted  = false;//有元素交换,所以不是有序,标记变为false
                 int t = arr[j];
                 arr[j] = arr[j+1];
                 arr[j+1] = t;
             }
         }
         //一趟下来是否发生位置交换,如果没有交换直接跳出大循环
         if(isSorted )
              break;
     }
     return arr;
}

(3)插入排序

过程: 想让arr[00]上有序,这个范围只有一个数,当然是有序的。 想让arr[01]上有序,所以从arr[1]开始往前看,如果arr[1]<arr[0],就交换。否则什么也不做。 … 想让arr[0i]上有序,所以从arr[i]开始往前看,arr[i]这个数不停向左移动,一直移动到左边的数字不再比自己大,停止移动。 最后一步,想让arr[0N-1]上有序, arr[N-1]这个数不停向左移动,一直移动到左边的数字不再比自己大,停止移动。

    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--) {//这里的j+1始终指向我们开始盯得数
            swap(arr, j, j+1);
        }
    }
}
public static void swap(int[] arr, int i, int j) {
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

如果某个算法流程的复杂程度会根据数据状况的不同而不同,那么你必须要按照最差情况来估计。很明显,在最差情况下,如果arr长度为N,插入排序的每一步常数操作的数量,还是如等差数列一般所以,总的常数操作数量 = a (N^2) + bN + c (a、b、c都是常数)所以插入排序排序的时间复杂度为O(N^2)。

(4)额外空间复杂度---自主空间和输入无关的

  • 需要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。
  • 作为输入参数的空间,不算做额外空间
  • 作为输出结果的空间,也不算额外空间------因为这些都是必要的、和现实目标有关的。所以都不算但除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去。这部分空间就是额外空间。
  • 如果你的流程只需要开辟有限几个变量,额外空间复杂度就是O(1)

(5)常数项-----同样时间复杂度的流程比较常数项----如果两个时间复杂度一样的算法,你还要去在时间上拼优劣,就进入到拼常数时间的阶段,简称拼常数项

例如:插入与冒泡排序---时间的复杂度相同O(n)

生成随机数据直接测-------

(6)最优解 ---时间复杂度尽可能的低的情况下,空间尽可能的少。一般说起最优解都是忽略掉常数项这个因素的,因为这个因素只决定了实现层次的优化和考虑,而和怎么解决整个问题的思想无关

排名从好到差: O(1) O(logN) O(N) O(N*logN) O(N^2) O(N^3) … O(N^K) O(2^N) O(3^N) … O(K^N) O(N!)

2.二分

在有序数组中找,一次砍一半

(1)数组的有序排列的到

image.png

(2)防止内存溢出

image.png

---->>>>> mid = L + [(R-L)>>1]

    if (sortedArr == null || sortedArr.length == 0) {
        return false;
    }
    int L = 0;
    int R = sortedArr.length - 1;
    while (L < R) {
        if (sortedArr[mid] == num) {
            return true;
        }
        else if (sortedArr[mid] > num) {
            R = mid - 1;
        } else {
            L = mid + 1;
        }
    }
    return soredArr[i] = num;
}

(3)局部最小值问题

image.png

    if (arr == null || arr.length == 0) {
        return -1;
    }
    if (arr.length == 1 || arr[0] < arr[1]) {
        return 0;
    }
    if (arr[arr.length - 1] < arr[arr.length - 2]) {
        return arr.length - 1;
    }
    int right = arr.length - 2;
    int left = 1;
    int mid = 0;
    while (left < right) {
        mid = left + (right - left) >> 1;
        if (arr[mid] > arr[mid - 1]) {
            right = mid - 1;
        } else if (arr[mid] < arr[mid + 1]) {
            left = mid + 1;
        } else {
            return ;
        }
        return left;
    }
}